From 8ffda53e3d475067f9624a349a311ae3ff5131d4 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 16 Nov 2020 18:11:15 +0100 Subject: [PATCH 001/141] Chief and Wing Commander - Added back in --- Moose Development/Moose/Ops/ChiefOfStaff.lua | 386 +++++++++++++++++ Moose Development/Moose/Ops/WingCommander.lua | 387 ++++++++++++++++++ Moose Setup/Moose.files | 2 + 3 files changed, 775 insertions(+) create mode 100644 Moose Development/Moose/Ops/ChiefOfStaff.lua create mode 100644 Moose Development/Moose/Ops/WingCommander.lua diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua new file mode 100644 index 000000000..f64a4823d --- /dev/null +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -0,0 +1,386 @@ +---- **Ops** - Chief of Staff. +-- +-- **Main Features:** +-- +-- * Assigns targets to Airforce, Army and Navy +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.WingCommander +-- @image OPS_WingCommander.png + + +--- WINGCOMMANDER class. +-- @type WINGCOMMANDER +-- @field #string ClassName Name of the class. +-- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #string lid Class id string for output to DCS log file. +-- @field #table airwings Table of airwings which are commanded. +-- @field #table missionqueue Mission queue. +-- @field Ops.ChiefOfStaff#CHIEF chief Chief of staff. +-- @extends Core.Fsm#FSM + +--- Be surprised! +-- +-- === +-- +-- ![Banner Image](..\Presentations\WingCommander\WINGCOMMANDER_Main.jpg) +-- +-- # The WINGCOMMANDER Concept +-- +-- A wing commander is the head of airwings. He will find the best AIRWING to perform an assigned AUFTRAG (mission). +-- +-- +-- @field #WINGCOMMANDER +WINGCOMMANDER = { + ClassName = "WINGCOMMANDER", + Debug = nil, + lid = nil, + airwings = {}, + missionqueue = {}, +} + +--- WINGCOMMANDER class version. +-- @field #string version +WINGCOMMANDER.version="0.1.0" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Improve airwing selection. Mostly done! +-- NOGO: Maybe it's possible to preselect the assets for the mission. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new WINGCOMMANDER object and start the FSM. +-- @param #WINGCOMMANDER self +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:New() + + -- Inherit everything from INTEL class. + local self=BASE:Inherit(self, FSM:New()) --#WINGCOMMANDER + + self.lid="WINGCOMMANDER | " + + -- Start state. + self:SetStartState("NotReadyYet") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("NotReadyYet", "Start", "OnDuty") -- Start WC. + self:AddTransition("*", "Status", "*") -- Status report. + self:AddTransition("*", "Stop", "Stopped") -- Stop WC. + + self:AddTransition("*", "AssignMission", "*") -- Mission was assigned to an AIRWING. + self:AddTransition("*", "CancelMission", "*") -- Cancel mission. + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the WINGCOMMANDER. Initializes parameters and starts event handlers. + -- @function [parent=#WINGCOMMANDER] Start + -- @param #WINGCOMMANDER self + + --- Triggers the FSM event "Start" after a delay. Starts the WINGCOMMANDER. Initializes parameters and starts event handlers. + -- @function [parent=#WINGCOMMANDER] __Start + -- @param #WINGCOMMANDER self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the WINGCOMMANDER and all its event handlers. + -- @param #WINGCOMMANDER self + + --- Triggers the FSM event "Stop" after a delay. Stops the WINGCOMMANDER and all its event handlers. + -- @function [parent=#WINGCOMMANDER] __Stop + -- @param #WINGCOMMANDER self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#WINGCOMMANDER] Status + -- @param #WINGCOMMANDER self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#WINGCOMMANDER] __Status + -- @param #WINGCOMMANDER self + -- @param #number delay Delay in seconds. + + + -- Debug trace. + if false then + self.Debug=true + BASE:TraceOnOff(true) + BASE:TraceClass(self.ClassName) + BASE:TraceLevel(1) + end + self.Debug=true + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Add an airwing to the wingcommander. +-- @param #WINGCOMMANDER self +-- @param Ops.AirWing#AIRWING Airwing The airwing to add. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:AddAirwing(Airwing) + + -- This airwing is managed by this wing commander. + Airwing.wingcommander=self + + table.insert(self.airwings, Airwing) + + return self +end + +--- Add mission to mission queue. +-- @param #WINGCOMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission Mission to be added. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:AddMission(Mission) + + Mission.wingcommander=self + + table.insert(self.missionqueue, Mission) + + return self +end + +--- Remove mission from queue. +-- @param #WINGCOMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:RemoveMission(Mission) + + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission.auftragsnummer==Mission.auftragsnummer then + self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status)) + table.remove(self.missionqueue, i) + break + end + + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. +-- @param #WINGCOMMANDER self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WINGCOMMANDER:onafterStart(From, Event, To) + + -- Short info. + local text=string.format("Starting Wing Commander") + self:I(self.lid..text) + + -- Start attached airwings. + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + if airwing:GetState()=="NotReadyYet" then + airwing:Start() + end + end + + self:__Status(-1) +end + +--- On after "Status" event. +-- @param #WINGCOMMANDER self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WINGCOMMANDER:onafterStatus(From, Event, To) + + -- FSM state. + local fsmstate=self:GetState() + + -- Check mission queue and assign one PLANNED mission. + self:CheckMissionQueue() + + -- Mission queue. + if #self.missionqueue>0 then + + local text="Mission queue:" + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + local target=mission:GetTargetName() or "unknown" + + text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target) + end + self:I(self.lid..text) + + end + + self:__Status(-30) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Events +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "AssignMission" event. Mission is added to the AIRWING mission queue. +-- @param #WINGCOMMANDER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.AirWing#AIRWING Airwing The AIRWING. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function WINGCOMMANDER:onafterAssignMission(From, Event, To, Airwing, Mission) + + self:I(self.lid..string.format("Assigning mission %s (%s) to airwing %s", Mission.name, Mission.type, Airwing.alias)) + Airwing:AddMission(Mission) + +end + +--- On after "CancelMission" event. +-- @param #WINGCOMMANDER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission) + + self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status)) + + if Mission.status==AUFTRAG.Status.PLANNED then + + -- Mission is still in planning stage. Should not have an airbase assigned ==> Just remove it form the queue. + self:RemoveMission(Mission) + + else + + -- Airwing will cancel mission. + if Mission.airwing then + Mission.airwing:CancelMission(Mission) + end + + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Resources +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check mission queue and assign ONE planned mission. +-- @param #WINGCOMMANDER self +function WINGCOMMANDER:CheckMissionQueue() + + -- TODO: Sort mission queue. wrt what? Threat level? + + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + -- We look for PLANNED missions. + if mission.status==AUFTRAG.Status.PLANNED then + + --- + -- PLANNNED Mission + --- + + local airwing=self:GetAirwingForMission(mission) + + if airwing then + + -- Add mission to airwing. + self:AssignMission(airwing, mission) + + return + end + + else + + --- + -- Missions NOT in PLANNED state + --- + + end + + end + +end + +--- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets. +-- @param #WINGCOMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return Ops.AirWing#AIRWING The airwing best for this mission. +function WINGCOMMANDER:GetAirwingForMission(Mission) + + -- Table of airwings that can do the mission. + local airwings={} + + -- Loop over all airwings. + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + + -- Check if airwing can do this mission. + local can,assets=airwing:CanMission(Mission) + + -- Can it? + if can then + + -- Get coordinate of the target. + local coord=Mission:GetTargetCoordinate() + + if coord then + + -- Distance from airwing to target. + local dist=UTILS.MetersToNM(coord:Get2DDistance(airwing:GetCoordinate())) + + -- Add airwing to table of airwings that can. + table.insert(airwings, {airwing=airwing, dist=dist, targetcoord=coord, nassets=#assets}) + + end + + end + + end + + -- Can anyone? + if #airwings>0 then + + --- Something like: + -- * Closest airwing that can should be first prio. + -- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the airwing with more resources should get the job. + local function score(a) + local d=math.round(a.dist/10) + end + + -- Sort table wrt distance and number of assets. + -- Distances within 10 NM are equal and the airwing with more assets is preferred. + local function sortdist(a,b) + local ad=math.round(a.dist/10) -- dist 55 NM ==> 5.5 ==> 6 + local bd=math.round(b.dist/10) -- dist 63 NM ==> 6.3 ==> 6 + return adb.nassets) + end + table.sort(airwings, sortdist) + + -- This is the closest airwing to the target. + local airwing=airwings[1].airwing --Ops.AirWing#AIRWING + + return airwing + end + + return nil +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Ops/WingCommander.lua b/Moose Development/Moose/Ops/WingCommander.lua new file mode 100644 index 000000000..d3aaeceae --- /dev/null +++ b/Moose Development/Moose/Ops/WingCommander.lua @@ -0,0 +1,387 @@ +--- **Ops** - Commander Air Wing. +-- +-- **Main Features:** +-- +-- * Manages AIRWINGS +-- * Handles missions (AUFTRAG) and finds the best airwing able to do the job +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.WingCommander +-- @image OPS_WingCommander.png + + +--- WINGCOMMANDER class. +-- @type WINGCOMMANDER +-- @field #string ClassName Name of the class. +-- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #string lid Class id string for output to DCS log file. +-- @field #table airwings Table of airwings which are commanded. +-- @field #table missionqueue Mission queue. +-- @field Ops.ChiefOfStaff#CHIEF chief Chief of staff. +-- @extends Core.Fsm#FSM + +--- Be surprised! +-- +-- === +-- +-- ![Banner Image](..\Presentations\WingCommander\WINGCOMMANDER_Main.jpg) +-- +-- # The WINGCOMMANDER Concept +-- +-- A wing commander is the head of airwings. He will find the best AIRWING to perform an assigned AUFTRAG (mission). +-- +-- +-- @field #WINGCOMMANDER +WINGCOMMANDER = { + ClassName = "WINGCOMMANDER", + Debug = nil, + lid = nil, + airwings = {}, + missionqueue = {}, +} + +--- WINGCOMMANDER class version. +-- @field #string version +WINGCOMMANDER.version="0.1.0" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Improve airwing selection. Mostly done! +-- NOGO: Maybe it's possible to preselect the assets for the mission. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new WINGCOMMANDER object and start the FSM. +-- @param #WINGCOMMANDER self +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:New() + + -- Inherit everything from INTEL class. + local self=BASE:Inherit(self, FSM:New()) --#WINGCOMMANDER + + self.lid="WINGCOMMANDER | " + + -- Start state. + self:SetStartState("NotReadyYet") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("NotReadyYet", "Start", "OnDuty") -- Start WC. + self:AddTransition("*", "Status", "*") -- Status report. + self:AddTransition("*", "Stop", "Stopped") -- Stop WC. + + self:AddTransition("*", "AssignMission", "*") -- Mission was assigned to an AIRWING. + self:AddTransition("*", "CancelMission", "*") -- Cancel mission. + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the WINGCOMMANDER. Initializes parameters and starts event handlers. + -- @function [parent=#WINGCOMMANDER] Start + -- @param #WINGCOMMANDER self + + --- Triggers the FSM event "Start" after a delay. Starts the WINGCOMMANDER. Initializes parameters and starts event handlers. + -- @function [parent=#WINGCOMMANDER] __Start + -- @param #WINGCOMMANDER self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the WINGCOMMANDER and all its event handlers. + -- @param #WINGCOMMANDER self + + --- Triggers the FSM event "Stop" after a delay. Stops the WINGCOMMANDER and all its event handlers. + -- @function [parent=#WINGCOMMANDER] __Stop + -- @param #WINGCOMMANDER self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#WINGCOMMANDER] Status + -- @param #WINGCOMMANDER self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#WINGCOMMANDER] __Status + -- @param #WINGCOMMANDER self + -- @param #number delay Delay in seconds. + + + -- Debug trace. + if false then + self.Debug=true + BASE:TraceOnOff(true) + BASE:TraceClass(self.ClassName) + BASE:TraceLevel(1) + end + self.Debug=true + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Add an airwing to the wingcommander. +-- @param #WINGCOMMANDER self +-- @param Ops.AirWing#AIRWING Airwing The airwing to add. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:AddAirwing(Airwing) + + -- This airwing is managed by this wing commander. + Airwing.wingcommander=self + + table.insert(self.airwings, Airwing) + + return self +end + +--- Add mission to mission queue. +-- @param #WINGCOMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission Mission to be added. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:AddMission(Mission) + + Mission.wingcommander=self + + table.insert(self.missionqueue, Mission) + + return self +end + +--- Remove mission from queue. +-- @param #WINGCOMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:RemoveMission(Mission) + + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission.auftragsnummer==Mission.auftragsnummer then + self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status)) + table.remove(self.missionqueue, i) + break + end + + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. +-- @param #WINGCOMMANDER self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WINGCOMMANDER:onafterStart(From, Event, To) + + -- Short info. + local text=string.format("Starting Wing Commander") + self:I(self.lid..text) + + -- Start attached airwings. + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + if airwing:GetState()=="NotReadyYet" then + airwing:Start() + end + end + + self:__Status(-1) +end + +--- On after "Status" event. +-- @param #WINGCOMMANDER self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WINGCOMMANDER:onafterStatus(From, Event, To) + + -- FSM state. + local fsmstate=self:GetState() + + -- Check mission queue and assign one PLANNED mission. + self:CheckMissionQueue() + + -- Mission queue. + if #self.missionqueue>0 then + + local text="Mission queue:" + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + local target=mission:GetTargetName() or "unknown" + + text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target) + end + self:I(self.lid..text) + + end + + self:__Status(-30) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Events +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "AssignMission" event. Mission is added to the AIRWING mission queue. +-- @param #WINGCOMMANDER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.AirWing#AIRWING Airwing The AIRWING. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function WINGCOMMANDER:onafterAssignMission(From, Event, To, Airwing, Mission) + + self:I(self.lid..string.format("Assigning mission %s (%s) to airwing %s", Mission.name, Mission.type, Airwing.alias)) + Airwing:AddMission(Mission) + +end + +--- On after "CancelMission" event. +-- @param #WINGCOMMANDER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission) + + self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status)) + + if Mission.status==AUFTRAG.Status.PLANNED then + + -- Mission is still in planning stage. Should not have an airbase assigned ==> Just remove it form the queue. + self:RemoveMission(Mission) + + else + + -- Airwing will cancel mission. + if Mission.airwing then + Mission.airwing:CancelMission(Mission) + end + + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Resources +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check mission queue and assign ONE planned mission. +-- @param #WINGCOMMANDER self +function WINGCOMMANDER:CheckMissionQueue() + + -- TODO: Sort mission queue. wrt what? Threat level? + + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + -- We look for PLANNED missions. + if mission.status==AUFTRAG.Status.PLANNED then + + --- + -- PLANNNED Mission + --- + + local airwing=self:GetAirwingForMission(mission) + + if airwing then + + -- Add mission to airwing. + self:AssignMission(airwing, mission) + + return + end + + else + + --- + -- Missions NOT in PLANNED state + --- + + end + + end + +end + +--- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets. +-- @param #WINGCOMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return Ops.AirWing#AIRWING The airwing best for this mission. +function WINGCOMMANDER:GetAirwingForMission(Mission) + + -- Table of airwings that can do the mission. + local airwings={} + + -- Loop over all airwings. + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + + -- Check if airwing can do this mission. + local can,assets=airwing:CanMission(Mission) + + -- Can it? + if can then + + -- Get coordinate of the target. + local coord=Mission:GetTargetCoordinate() + + if coord then + + -- Distance from airwing to target. + local dist=UTILS.MetersToNM(coord:Get2DDistance(airwing:GetCoordinate())) + + -- Add airwing to table of airwings that can. + table.insert(airwings, {airwing=airwing, dist=dist, targetcoord=coord, nassets=#assets}) + + end + + end + + end + + -- Can anyone? + if #airwings>0 then + + --- Something like: + -- * Closest airwing that can should be first prio. + -- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the airwing with more resources should get the job. + local function score(a) + local d=math.round(a.dist/10) + end + + -- Sort table wrt distance and number of assets. + -- Distances within 10 NM are equal and the airwing with more assets is preferred. + local function sortdist(a,b) + local ad=math.round(a.dist/10) -- dist 55 NM ==> 5.5 ==> 6 + local bd=math.round(b.dist/10) -- dist 63 NM ==> 6.3 ==> 6 + return adb.nassets) + end + table.sort(airwings, sortdist) + + -- This is the closest airwing to the target. + local airwing=airwings[1].airwing --Ops.AirWing#AIRWING + + return airwing + end + + return nil +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index f92bb5664..85249125e 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -75,6 +75,8 @@ Ops/NavyGroup.lua Ops/Squadron.lua Ops/AirWing.lua Ops/Intelligence.lua +Ops/WingCommander.lua +Ops/ChiefOfStaff.lua AI/AI_Balancer.lua AI/AI_Air.lua From eeffa3128287b715f04e24382af401981e8818db Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 26 Nov 2020 11:47:05 +0100 Subject: [PATCH 002/141] Update ChiefOfStaff.lua --- Moose Development/Moose/Ops/ChiefOfStaff.lua | 635 ++++++++++++++----- 1 file changed, 487 insertions(+), 148 deletions(-) diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua index f64a4823d..b3f56fced 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -2,110 +2,144 @@ -- -- **Main Features:** -- --- * Assigns targets to Airforce, Army and Navy +-- * Stuff -- -- === -- -- ### Author: **funkyfranky** --- @module Ops.WingCommander --- @image OPS_WingCommander.png +-- @module Ops.Chief +-- @image OPS_Chief.png ---- WINGCOMMANDER class. --- @type WINGCOMMANDER +--- CHIEF class. +-- @type CHIEF -- @field #string ClassName Name of the class. -- @field #boolean Debug Debug mode. Messages to all about status. -- @field #string lid Class id string for output to DCS log file. --- @field #table airwings Table of airwings which are commanded. -- @field #table missionqueue Mission queue. --- @field Ops.ChiefOfStaff#CHIEF chief Chief of staff. --- @extends Core.Fsm#FSM +-- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory. +-- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected. +-- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged. +-- @field #string Defcon Defence condition. +-- @field Ops.WingCommander#WINGCOMMANDER wingcommander Wing commander, commanding airborne forces. +-- @field Ops.Admiral#ADMIRAL admiral Admiral commanding navy forces. +-- @field Ops.General#GENERAL genaral General commanding army forces. +-- @extends Ops.Intelligence#INTEL --- Be surprised! -- -- === -- --- ![Banner Image](..\Presentations\WingCommander\WINGCOMMANDER_Main.jpg) +-- ![Banner Image](..\Presentations\WingCommander\CHIEF_Main.jpg) -- --- # The WINGCOMMANDER Concept +-- # The CHIEF Concept -- --- A wing commander is the head of airwings. He will find the best AIRWING to perform an assigned AUFTRAG (mission). +-- The Chief of staff gathers intel and assigns missions (AUFTRAG) the airforce (WINGCOMMANDER), army (GENERAL) or navy (ADMIRAL). +-- +-- **Note** that currently only assignments to airborne forces (WINGCOMMANDER) are implemented. -- -- --- @field #WINGCOMMANDER -WINGCOMMANDER = { - ClassName = "WINGCOMMANDER", +-- @field #CHIEF +CHIEF = { + ClassName = "CHIEF", Debug = nil, lid = nil, - airwings = {}, + wingcommander = nil, + admiral = nil, + general = nil, missionqueue = {}, + borderzoneset = nil, + yellowzoneset = nil, + engagezoneset = nil, } ---- WINGCOMMANDER class version. +--- Defence condition. +-- @type CHIEF.DEFCON +-- @field #string GREEN No enemy activities detected. +-- @field #string YELLOW Enemy near our border. +-- @field #string RED Enemy within our border. +CHIEF.DEFCON = { + GREEN="Green", + YELLOW="Yellow", + RED="Red", +} + +--- CHIEF class version. -- @field #string version -WINGCOMMANDER.version="0.1.0" +CHIEF.version="0.0.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Improve airwing selection. Mostly done! +-- TODO: Define A2A and A2G parameters. +-- DONE: Add/remove spawned flightgroups to detection set. +-- DONE: Borderzones. -- NOGO: Maybe it's possible to preselect the assets for the mission. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new WINGCOMMANDER object and start the FSM. --- @param #WINGCOMMANDER self --- @return #WINGCOMMANDER self -function WINGCOMMANDER:New() +--- Create a new CHIEF object and start the FSM. +-- @param #CHIEF self +-- @param Core.Set#SET_GROUP AgentSet Set of agents (groups) providing intel. Default is an empty set. +-- @param #number Coalition Coalition side, e.g. `coaliton.side.BLUE`. Can also be passed as a string "red", "blue" or "neutral". +-- @return #CHIEF self +function CHIEF:New(AgentSet, Coalition) + + AgentSet=AgentSet or SET_GROUP:New() -- Inherit everything from INTEL class. - local self=BASE:Inherit(self, FSM:New()) --#WINGCOMMANDER - - self.lid="WINGCOMMANDER | " + local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition)) --#CHIEF - -- Start state. - self:SetStartState("NotReadyYet") + -- Set some string id for output to DCS.log file. + --self.lid=string.format("CHIEF | ") + + self:SetBorderZones() + self:SetYellowZones() + + self:SetThreatLevelRange() + + self.Defcon=CHIEF.DEFCON.GREEN -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("NotReadyYet", "Start", "OnDuty") -- Start WC. - self:AddTransition("*", "Status", "*") -- Status report. - self:AddTransition("*", "Stop", "Stopped") -- Stop WC. - - self:AddTransition("*", "AssignMission", "*") -- Mission was assigned to an AIRWING. - self:AddTransition("*", "CancelMission", "*") -- Cancel mission. + -- From State --> Event --> To State + self:AddTransition("*", "AssignMissionAirforce", "*") -- Assign mission to a WINGCOMMANDER. + self:AddTransition("*", "AssignMissionNavy", "*") -- Assign mission to an ADMIRAL. + self:AddTransition("*", "AssignMissionArmy", "*") -- Assign mission to a GENERAL. + self:AddTransition("*", "CancelMission", "*") -- Cancel mission. + self:AddTransition("*", "Defcon", "*") -- Change defence condition. + self:AddTransition("*", "DeclareWar", "*") -- Declare War. ------------------------ --- Pseudo Functions --- ------------------------ - --- Triggers the FSM event "Start". Starts the WINGCOMMANDER. Initializes parameters and starts event handlers. - -- @function [parent=#WINGCOMMANDER] Start - -- @param #WINGCOMMANDER self + --- Triggers the FSM event "Start". Starts the CHIEF. Initializes parameters and starts event handlers. + -- @function [parent=#CHIEF] Start + -- @param #CHIEF self - --- Triggers the FSM event "Start" after a delay. Starts the WINGCOMMANDER. Initializes parameters and starts event handlers. - -- @function [parent=#WINGCOMMANDER] __Start - -- @param #WINGCOMMANDER self + --- Triggers the FSM event "Start" after a delay. Starts the CHIEF. Initializes parameters and starts event handlers. + -- @function [parent=#CHIEF] __Start + -- @param #CHIEF self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Stop". Stops the WINGCOMMANDER and all its event handlers. - -- @param #WINGCOMMANDER self + --- Triggers the FSM event "Stop". Stops the CHIEF and all its event handlers. + -- @param #CHIEF self - --- Triggers the FSM event "Stop" after a delay. Stops the WINGCOMMANDER and all its event handlers. - -- @function [parent=#WINGCOMMANDER] __Stop - -- @param #WINGCOMMANDER self + --- Triggers the FSM event "Stop" after a delay. Stops the CHIEF and all its event handlers. + -- @function [parent=#CHIEF] __Stop + -- @param #CHIEF self -- @param #number delay Delay in seconds. --- Triggers the FSM event "Status". - -- @function [parent=#WINGCOMMANDER] Status - -- @param #WINGCOMMANDER self + -- @function [parent=#CHIEF] Status + -- @param #CHIEF self --- Triggers the FSM event "Status" after a delay. - -- @function [parent=#WINGCOMMANDER] __Status - -- @param #WINGCOMMANDER self + -- @function [parent=#CHIEF] __Status + -- @param #CHIEF self -- @param #number delay Delay in seconds. @@ -116,7 +150,6 @@ function WINGCOMMANDER:New() BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) end - self.Debug=true return self end @@ -125,27 +158,101 @@ end -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Add an airwing to the wingcommander. --- @param #WINGCOMMANDER self --- @param Ops.AirWing#AIRWING Airwing The airwing to add. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:AddAirwing(Airwing) +--- Set this to be an air-to-any dispatcher, i.e. engaging air, ground and naval targets. This is the default anyway. +-- @param #CHIEF self +-- @return #CHIEF self +function CHIEF:SetAirToAny() - -- This airwing is managed by this wing commander. - Airwing.wingcommander=self + self:SetFilterCategory({}) + + return self +end - table.insert(self.airwings, Airwing) +--- Set this to be an air-to-air dispatcher. +-- @param #CHIEF self +-- @return #CHIEF self +function CHIEF:SetAirToAir() + + self:SetFilterCategory({Unit.Category.AIRPLANE, Unit.Category.HELICOPTER}) + + return self +end + +--- Set this to be an air-to-ground dispatcher, i.e. engage only ground units +-- @param #CHIEF self +-- @return #CHIEF self +function CHIEF:SetAirToGround() + + self:SetFilterCategory({Unit.Category.GROUND_UNIT}) + + return self +end + +--- Set this to be an air-to-sea dispatcher, i.e. engage only naval units. +-- @param #CHIEF self +-- @return #CHIEF self +function CHIEF:SetAirToSea() + + self:SetFilterCategory({Unit.Category.SHIP}) + + return self +end + +--- Set this to be an air-to-surface dispatcher, i.e. engaging ground and naval groups. +-- @param #CHIEF self +-- @return #CHIEF self +function CHIEF:SetAirToSurface() + + self:SetFilterCategory({Unit.Category.GROUND_UNIT, Unit.Category.SHIP}) + + return self +end + +--- Set a threat level range that will be engaged. Threat level is a number between 0 and 10, where 10 is a very dangerous threat. +-- Targets with threat level 0 are usually harmless. +-- @param #CHIEF self +-- @param #number ThreatLevelMin Min threat level. Default 1. +-- @param #number ThreatLevelMax Max threat level. Default 10. +-- @return #CHIEF self +function CHIEF:SetThreatLevelRange(ThreatLevelMin, ThreatLevelMax) + + self.threatLevelMin=ThreatLevelMin or 1 + self.threatLevelMax=ThreatLevelMax or 10 + + return self +end + +--- Set defence condition. +-- @param #CHIEF self +-- @param #string Defcon Defence condition. See @{#CHIEF.DEFCON}, e.g. `CHIEF.DEFCON.RED`. +-- @return #CHIEF self +function CHIEF:SetDefcon(Defcon) + + self.Defcon=Defcon + --self:Defcon(Defcon) + + return self +end + + +--- Set the wing commander for the airforce. +-- @param #CHIEF self +-- @param Ops.WingCommander#WINGCOMMANDER WingCommander The WINGCOMMANDER object. +-- @return #CHIEF self +function CHIEF:SetWingCommander(WingCommander) + + self.wingcommander=WingCommander + + self.wingcommander.chief=self return self end --- Add mission to mission queue. --- @param #WINGCOMMANDER self +-- @param #CHIEF self -- @param Ops.Auftrag#AUFTRAG Mission Mission to be added. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:AddMission(Mission) - - Mission.wingcommander=self +-- @return #CHIEF self +function CHIEF:AddMission(Mission) table.insert(self.missionqueue, Mission) @@ -153,10 +260,10 @@ function WINGCOMMANDER:AddMission(Mission) end --- Remove mission from queue. --- @param #WINGCOMMANDER self +-- @param #CHIEF self -- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:RemoveMission(Mission) +-- @return #CHIEF self +function CHIEF:RemoveMission(Mission) for i,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG @@ -172,50 +279,206 @@ function WINGCOMMANDER:RemoveMission(Mission) return self end +--- Set border zone set. +-- @param #CHIEF self +-- @param Core.Set#SET_ZONE BorderZoneSet Set of zones, defining our borders. +-- @return #CHIEF self +function CHIEF:SetBorderZones(BorderZoneSet) + + -- Border zones. + self.borderzoneset=BorderZoneSet or SET_ZONE:New() + + return self +end + +--- Add a zone defining your territory. +-- @param #CHIEF self +-- @param Core.Zone#ZONE BorderZone The zone defining the border of your territory. +-- @return #CHIEF self +function CHIEF:AddBorderZone(BorderZone) + + -- Add a border zone. + self.borderzoneset:AddZone(BorderZone) + + return self +end + +--- Set yellow zone set. Detected enemy troops in this zone will trigger defence condition YELLOW. +-- @param #CHIEF self +-- @param Core.Set#SET_ZONE YellowZoneSet Set of zones, defining our borders. +-- @return #CHIEF self +function CHIEF:SetYellowZones(YellowZoneSet) + + -- Border zones. + self.yellowzoneset=YellowZoneSet or SET_ZONE:New() + + return self +end + +--- Add a zone defining an area outside your territory that is monitored for enemy activity. +-- @param #CHIEF self +-- @param Core.Zone#ZONE YellowZone The zone defining the border of your territory. +-- @return #CHIEF self +function CHIEF:AddYellowZone(YellowZone) + + -- Add a border zone. + self.yellowzoneset:AddZone(YellowZone) + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. --- @param #WINGCOMMANDER self +--- On after Start event. +-- @param #CHIEF self -- @param Wrapper.Group#GROUP Group Flight group. -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function WINGCOMMANDER:onafterStart(From, Event, To) +function CHIEF:onafterStart(From, Event, To) -- Short info. - local text=string.format("Starting Wing Commander") + local text=string.format("Starting Chief of Staff") self:I(self.lid..text) + + -- Start parent INTEL. + self:GetParent(self).onafterStart(self, From, Event, To) - -- Start attached airwings. - for _,_airwing in pairs(self.airwings) do - local airwing=_airwing --Ops.AirWing#AIRWING - if airwing:GetState()=="NotReadyYet" then - airwing:Start() + -- Start wingcommander. + if self.wingcommander then + if self.wingcommander:GetState()=="NotReadyYet" then + self.wingcommander:Start() end end - self:__Status(-1) end --- On after "Status" event. --- @param #WINGCOMMANDER self +-- @param #CHIEF self -- @param Wrapper.Group#GROUP Group Flight group. -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function WINGCOMMANDER:onafterStatus(From, Event, To) +function CHIEF:onafterStatus(From, Event, To) + + -- Start parent INTEL. + self:GetParent(self).onafterStatus(self, From, Event, To) -- FSM state. local fsmstate=self:GetState() + + -- Clean up missions where the contact was lost. + for _,_contact in pairs(self.ContactsLost) do + local contact=_contact --#INTEL.Contact + + if contact.mission and contact.mission:IsNotOver() then + + local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.", contact.groupname, contact.mission.type:upper(), contact.mission.name) + MESSAGE:New(text, 120, "CHIEF"):ToAll() + self:I(self.lid..text) + + -- Cancel this mission. + contact.mission:Cancel() + + end + + end + + -- Create missions for all new contacts. + local Nred=0 ; local Nyellow=0 ; local Nengage=0 + for _,_contact in pairs(self.Contacts) do + local contact=_contact --#CHIEF.Contact + local group=contact.group --Wrapper.Group#GROUP + + local inred=self:CheckGroupInBorder(group) + if inred then + Nred=Nred+1 + end + + local inyellow=self:CheckGroupInYellow(group) + if inyellow then + Nyellow=Nyellow+1 + end + + -- Is this a threat? + local threat=contact.threatlevel>=self.threatLevelMin and contact.threatlevel<=self.threatLevelMax + + local redalert=true + if self.borderzoneset:Count()>0 then + redalert=inred + end + + if redalert and threat and not contact.mission then + + -- Create a mission based on group category. + local mission=AUFTRAG:NewAUTO(group) + + -- Add mission to queue. + if mission then + + --TODO: Better amount of necessary assets. Count units in asset and in contact. Might need nassetMin/Max. + mission.nassets=1 + + -- Missons are repeated max 3 times on failure. + mission.NrepeatFailure=3 + + -- Set mission contact. + contact.mission=mission + + -- Add mission to queue. + self:AddMission(mission) + end + + end + + end + + --- + -- Defcon + --- + + -- 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 + self:SetDefcon(CHIEF.DEFCON.RED) + elseif Nyellow>0 then + self:SetDefcon(CHIEF.DEFCON.YELLOW) + else + self:SetDefcon(CHIEF.DEFCON.GREEN) + end + + --- + -- Mission Queue + --- + -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() + local text=string.format("Defcon=%s Missions=%d Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred) + self:I(self.lid..text) + + --- + -- Contacts + --- + + -- Info about contacts. + if #self.Contacts>0 then + local text="Contacts:" + for i,_contact in pairs(self.Contacts) do + local contact=_contact --#CHIEF.Contact + local mtext="N/A" + if contact.mission then + mtext=string.format("Mission %s (%s) %s", contact.mission.name, contact.mission.type, contact.mission.status:upper()) + end + text=text..string.format("\n[%d] %s Type=%s (%s): Threat=%d Mission=%s", i, contact.groupname, contact.categoryname, contact.typename, contact.threatlevel, mtext) + end + self:I(self.lid..text) + end + -- Mission queue. if #self.missionqueue>0 then - local text="Mission queue:" for i,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG @@ -225,37 +488,38 @@ function WINGCOMMANDER:onafterStatus(From, Event, To) text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target) end self:I(self.lid..text) - end - self:__Status(-30) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after "AssignMission" event. Mission is added to the AIRWING mission queue. --- @param #WINGCOMMANDER self +--- On after "AssignMissionAssignAirforce" event. +-- @param #CHIEF self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.AirWing#AIRWING Airwing The AIRWING. -- @param Ops.Auftrag#AUFTRAG Mission The mission. -function WINGCOMMANDER:onafterAssignMission(From, Event, To, Airwing, Mission) +function CHIEF:onafterAssignMissionAirforce(From, Event, To, Mission) - self:I(self.lid..string.format("Assigning mission %s (%s) to airwing %s", Mission.name, Mission.type, Airwing.alias)) - Airwing:AddMission(Mission) + if self.wingcommander then + self:I(self.lid..string.format("Assigning mission %s (%s) to WINGCOMMANDER", Mission.name, Mission.type)) + self.wingcommander:AddMission(Mission) + else + self:E(self.lid..string.format("Mission cannot be assigned as no WINGCOMMANDER is defined.")) + end end --- On after "CancelMission" event. --- @param #WINGCOMMANDER self +-- @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 WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission) +function CHIEF:onafterCancelMission(From, Event, To, Mission) self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status)) @@ -268,20 +532,76 @@ function WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission) -- Airwing will cancel mission. if Mission.airwing then - Mission.airwing:CancelMission(Mission) + Mission.airwing:MissionCancel(Mission) end end end +--- On before "Defcon" event. +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string Defcon New defence condition. +function CHIEF:onbeforeDefcon(From, Event, To, Defcon) + + local gotit=false + for _,defcon in pairs(CHIEF.DEFCON) do + if defcon==Defcon then + gotit=true + end + end + + if not gotit then + self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s", tostring(Defcon))) + return false + end + + -- Defcon did not change. + if Defcon==self.Defcon then + self:I(self.lid..string.format("Defcon %s unchanged. No processing transition.", tostring(Defcon))) + return false + end + + return true +end + +--- On after "Defcon" event. +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string Defcon New defence condition. +function CHIEF:onafterDefcon(From, Event, To, Defcon) + self:I(self.lid..string.format("Changing Defcon from %s --> %s", self.Defcon, Defcon)) + + -- Set new defcon. + self.Defcon=Defcon +end + +--- On after "DeclareWar" event. +-- @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 + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Resources ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Check mission queue and assign ONE planned mission. --- @param #WINGCOMMANDER self -function WINGCOMMANDER:CheckMissionQueue() +-- @param #CHIEF self +function CHIEF:CheckMissionQueue() -- TODO: Sort mission queue. wrt what? Threat level? @@ -295,14 +615,18 @@ function WINGCOMMANDER:CheckMissionQueue() -- PLANNNED Mission --- + -- Check if there is an airwing that can do the mission. local airwing=self:GetAirwingForMission(mission) if airwing then -- Add mission to airwing. - self:AssignMission(airwing, mission) + self:AssignMissionAirforce(mission) return + + else + self:T(self.lid.."NO airwing") end else @@ -318,69 +642,84 @@ function WINGCOMMANDER:CheckMissionQueue() end --- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets. --- @param #WINGCOMMANDER self +-- @param #CHIEF self -- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @return Ops.AirWing#AIRWING The airwing best for this mission. -function WINGCOMMANDER:GetAirwingForMission(Mission) +function CHIEF:GetAirwingForMission(Mission) - -- Table of airwings that can do the mission. - local airwings={} - - -- Loop over all airwings. - for _,_airwing in pairs(self.airwings) do - local airwing=_airwing --Ops.AirWing#AIRWING - - -- Check if airwing can do this mission. - local can,assets=airwing:CanMission(Mission) - - -- Can it? - if can then - - -- Get coordinate of the target. - local coord=Mission:GetTargetCoordinate() - - if coord then - - -- Distance from airwing to target. - local dist=UTILS.MetersToNM(coord:Get2DDistance(airwing:GetCoordinate())) - - -- Add airwing to table of airwings that can. - table.insert(airwings, {airwing=airwing, dist=dist, targetcoord=coord, nassets=#assets}) - - end - - end - - end - - -- Can anyone? - if #airwings>0 then - - --- Something like: - -- * Closest airwing that can should be first prio. - -- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the airwing with more resources should get the job. - local function score(a) - local d=math.round(a.dist/10) - end - - -- Sort table wrt distance and number of assets. - -- Distances within 10 NM are equal and the airwing with more assets is preferred. - local function sortdist(a,b) - local ad=math.round(a.dist/10) -- dist 55 NM ==> 5.5 ==> 6 - local bd=math.round(b.dist/10) -- dist 63 NM ==> 6.3 ==> 6 - return adb.nassets) - end - table.sort(airwings, sortdist) - - -- This is the closest airwing to the target. - local airwing=airwings[1].airwing --Ops.AirWing#AIRWING - - return airwing + if self.wingcommander then + return self.wingcommander:GetAirwingForMission(Mission) end return nil 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. +function CHIEF:CheckGroupInBorder(group) + + local inside=self:CheckGroupInZones(group, self.borderzoneset) + + return inside +end + +--- Check if group is near our border (yellow zone). +-- @param #CHIEF self +-- @param Wrapper.Group#GROUP group The group. +-- @return #boolean If true, group is in any zone. +function CHIEF:CheckGroupInYellow(group) + + -- Check inside yellow but not inside our border. + local inside=self:CheckGroupInZones(group, self.yellowzoneset) and not self:CheckGroupInZones(group, self.borderzoneset) + + return inside +end + +--- Check if group is inside a zone. +-- @param #CHIEF self +-- @param Wrapper.Group#GROUP group The group. +-- @param Core.Set#SET_ZONE zoneset Set of zones. +-- @return #boolean If true, group is in any zone. +function CHIEF:CheckGroupInZones(group, zoneset) + + for _,_zone in pairs(zoneset.Set or {}) do + local zone=_zone --Core.Zone#ZONE + + if group:IsPartlyOrCompletelyInZone(zone) then + return true + end + end + + return false +end + +--- Check resources. +-- @param #CHIEF self +-- @return #table +function CHIEF:CheckResources() + + local capabilities={} + + for _,MissionType in pairs(AUFTRAG.Type) do + capabilities[MissionType]=0 + + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + + -- Get Number of assets that can do this type of missions. + local _,assets=airwing:CanMission(MissionType) + + -- Add up airwing resources. + capabilities[MissionType]=capabilities[MissionType]+#assets + end + + end + + return capabilities +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file From 801d00fa26abfb28e2a21d9f69a96cd5ac08789b Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 30 Nov 2020 11:38:36 +0100 Subject: [PATCH 003/141] Stuff --- Moose Development/Moose/Modules.lua | 3 + Moose Development/Moose/Ops/Army.lua | 434 ++++++++++++++++++++++++ Moose Development/Moose/Ops/Platoon.lua | 0 3 files changed, 437 insertions(+) create mode 100644 Moose Development/Moose/Ops/Army.lua create mode 100644 Moose Development/Moose/Ops/Platoon.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 8e1cd2860..2474442f4 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -82,6 +82,9 @@ __Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/ChiefOfStaff.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Army.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Platoon.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/Army.lua b/Moose Development/Moose/Ops/Army.lua new file mode 100644 index 000000000..884a6ed22 --- /dev/null +++ b/Moose Development/Moose/Ops/Army.lua @@ -0,0 +1,434 @@ +--- **Ops** - Army Warehouse. +-- +-- **Main Features:** +-- +-- * Manage platoons +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.Army +-- @image OPS_AirWing.png + + +--- ARMY class. +-- @type ARMY +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity of output. +-- @field #string lid Class id string for output to DCS log file. +-- @field #table menu Table of menu items. +-- @field #table squadrons Table of squadrons. +-- @field #table missionqueue Mission queue table. +-- @field #table payloads Playloads for specific aircraft and mission types. +-- @field #number payloadcounter Running index of payloads. +-- @field Core.Set#SET_ZONE zonesetCAP Set of CAP zones. +-- @field Core.Set#SET_ZONE zonesetTANKER Set of TANKER zones. +-- @field Core.Set#SET_ZONE zonesetAWACS Set of AWACS zones. +-- @field #number nflightsCAP Number of CAP flights constantly in the air. +-- @field #number nflightsAWACS Number of AWACS flights constantly in the air. +-- @field #number nflightsTANKERboom Number of TANKER flights with BOOM constantly in the air. +-- @field #number nflightsTANKERprobe Number of TANKER flights with PROBE constantly in the air. +-- @field #number nflightsRescueHelo Number of Rescue helo flights constantly in the air. +-- @field #table pointsCAP Table of CAP points. +-- @field #table pointsTANKER Table of Tanker points. +-- @field #table pointsAWACS Table of AWACS points. +-- @field Ops.WingCommander#WINGCOMMANDER wingcommander The wing commander responsible for this airwing. +-- +-- @field Ops.RescueHelo#RESCUEHELO rescuehelo The rescue helo. +-- @field Ops.RecoveryTanker#RECOVERYTANKER recoverytanker The recoverytanker. +-- +-- @extends Functional.Warehouse#WAREHOUSE + +--- Be surprised! +-- +-- === +-- +-- ![Banner Image](..\Presentations\OPS\AirWing\_Main.png) +-- +-- # The ARMY Concept +-- +-- An ARMY consists of multiple SQUADRONS. These squadrons "live" in a WAREHOUSE, i.e. a physical structure that is connected to an airbase (airdrome, FRAP or ship). +-- For an airwing to be operational, it needs airframes, weapons/fuel and an airbase. +-- +-- # Create an Army +-- +-- ## Constructing the Army +-- +-- airwing=ARMY:New("Warehouse Batumi", "8th Fighter Wing") +-- airwing:Start() +-- +-- The first parameter specified the warehouse, i.e. the static building housing the airwing (or the name of the aircraft carrier). The second parameter is optional +-- and sets an alias. +-- +-- ## Adding Squadrons +-- +-- At this point the airwing does not have any assets (aircraft). In order to add these, one needs to first define SQUADRONS. +-- +-- VFA151=SQUADRON:New("F-14 Group", 8, "VFA-151 (Vigilantes)") +-- VFA151:AddMissionCapability({AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT}) +-- +-- airwing:AddSquadron(VFA151) +-- +-- This adds eight Tomcat groups beloning to VFA-151 to the airwing. This squadron has the ability to perform combat air patrols and intercepts. +-- +-- ## Adding Payloads +-- +-- Adding pure airframes is not enough. The aircraft also need weapons (and fuel) for certain missions. These must be given to the airwing from template groups +-- defined in the Mission Editor. +-- +-- -- F-14 payloads for CAP and INTERCEPT. Phoenix are first, sparrows are second choice. +-- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-54C"), 2, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.GCICAP}, 80) +-- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-7M"), 20, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.GCICAP}) +-- +-- This will add two AIM-54C and 20 AIM-7M payloads. +-- +-- If the airwing gets an intercept or patrol mission assigned, it will first use the AIM-54s. Once these are consumed, the AIM-7s are attached to the aircraft. +-- +-- When an airwing does not have a payload for a certain mission type, the mission cannot be carried out. +-- +-- You can set the number of payloads to "unlimited" by setting its quantity to -1. +-- +-- # Adding Missions +-- +-- Various mission types can be added easily via the AUFTRAG class. +-- +-- Once you created an AUFTRAG you can add it to the ARMY with the :AddMission(mission) function. +-- +-- This mission will be put into the ARMY queue. Once the mission start time is reached and all resources (airframes and pylons) are available, the mission is started. +-- If the mission stop time is over (and the mission is not finished), it will be cancelled and removed from the queue. This applies also to mission that were not even +-- started. +-- +-- # Command an Army +-- +-- An airwing can receive missions from a WINGCOMMANDER. See docs of that class for details. +-- +-- However, you are still free to add missions at anytime. +-- +-- +-- @field #ARMY +ARMY = { + ClassName = "ARMY", + verbose = 0, + lid = nil, + menu = nil, + squadrons = {}, + missionqueue = {}, + payloads = {}, + payloadcounter = 0, + pointsCAP = {}, + pointsTANKER = {}, + pointsAWACS = {}, + wingcommander = nil, +} + +--- Squadron asset. +-- @type ARMY.SquadronAsset +-- @field #ARMY.Payload payload The payload of the asset. +-- @field Ops.FlightGroup#FLIGHTGROUP flightgroup The flightgroup object. +-- @field #string squadname Name of the squadron this asset belongs to. +-- @field #number Treturned Time stamp when asset returned to the airwing. +-- @extends Functional.Warehouse#WAREHOUSE.Assetitem + +--- ARMY class version. +-- @field #string version +ARMY.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- ToDo list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot! + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new ARMY class object. +-- @param #ARMY self +-- @param #string warehousename Name of the warehouse static or unit object representing the warehouse. +-- @param #string airwingname Name of the air wing, e.g. "ARMY-8". +-- @return #ARMY self +function ARMY:New(warehousename, airwingname) + + -- Inherit everything from WAREHOUSE class. + local self=BASE:Inherit(self, WAREHOUSE:New(warehousename, airwingname)) -- #ARMY + + -- Nil check. + if not self then + BASE:E(string.format("ERROR: Could not find warehouse %s!", warehousename)) + return nil + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("ARMY %s | ", self.alias) + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. + self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the ARMY. Initializes parameters and starts event handlers. + -- @function [parent=#ARMY] Start + -- @param #ARMY self + + --- Triggers the FSM event "Start" after a delay. Starts the ARMY. Initializes parameters and starts event handlers. + -- @function [parent=#ARMY] __Start + -- @param #ARMY self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the ARMY and all its event handlers. + -- @param #ARMY self + + --- Triggers the FSM event "Stop" after a delay. Stops the ARMY and all its event handlers. + -- @function [parent=#ARMY] __Stop + -- @param #ARMY self + -- @param #number delay Delay in seconds. + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Start AIRWING FSM. +-- @param #AIRWING self +function AIRWING:onafterStart(From, Event, To) + + -- Start parent Warehouse. + self:GetParent(self).onafterStart(self, From, Event, To) + + -- Info. + self:I(self.lid..string.format("Starting AIRWING v%s", AIRWING.version)) + +end + +--- Update status. +-- @param #AIRWING self +function AIRWING:onafterStatus(From, Event, To) + + -- Status of parent Warehouse. + self:GetParent(self).onafterStatus(self, From, Event, To) + + local fsmstate=self:GetState() + + + -- General info: + if self.verbose>=1 then + + -- Count missions not over yet. + local Nmissions=self:CountMissionsInQueue() + + -- Assets tot + local Npq, Np, Nq=self:CountAssetsOnMission() + + local assets=string.format("%d (OnMission: Total=%d, Active=%d, Queued=%d)", self:CountAssets(), Npq, Np, Nq) + + -- Output. + local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d, Assets=%s", fsmstate, Nmissions, Npayloads, #self.payloads, #self.squadrons, assets) + self:I(self.lid..text) + end + + ------------------ + -- Mission Info -- + ------------------ + if self.verbose>=2 then + local text=string.format("Missions Total=%d:", #self.missionqueue) + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end + local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.nassets) + local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage()) + + text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target) + end + self:I(self.lid..text) + end + + ------------------- + -- Squadron Info -- + ------------------- + if self.verbose>=3 then + local text="Squadrons:" + for i,_squadron in pairs(self.squadrons) do + local squadron=_squadron --Ops.Squadron#SQUADRON + + local callsign=squadron.callsignName and UTILS.GetCallsignName(squadron.callsignName) or "N/A" + local modex=squadron.modex and squadron.modex or -1 + local skill=squadron.skill and tostring(squadron.skill) or "N/A" + + -- Squadron text + text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, squadron:CountAssetsInStock(), #squadron.assets, callsign, modex, skill) + end + self:I(self.lid..text) + end + + -------------- + -- Mission --- + -------------- + + -- Check if any missions should be cancelled. + self:_CheckMissions() + + -- Get next mission. + local mission=self:_GetNextMission() + + -- Request mission execution. + if mission then + self:MissionRequest(mission) + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Stuff +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check if mission is not over and ready to cancel. +-- @param #AIRWING self +function AIRWING:_CheckMissions() + + -- Loop over missions in queue. + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission:IsNotOver() and mission:IsReadyToCancel() then + mission:Cancel() + end + end + +end +--- Get next mission. +-- @param #AIRWING self +-- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. +function AIRWING:_GetNextMission() + + -- Number of missions. + local Nmissions=#self.missionqueue + + -- Treat special cases. + if Nmissions==0 then + return nil + end + + -- Sort results table wrt prio and start time. + local function _sort(a, b) + local taskA=a --Ops.Auftrag#AUFTRAG + local taskB=b --Ops.Auftrag#AUFTRAG + return (taskA.prio0 then + self:E(self.lid..string.format("ERROR: mission %s of type %s has already assets attached!", mission.name, mission.type)) + end + mission.assets={} + + -- Assign assets to mission. + for i=1,mission.nassets do + local asset=assets[i] --#AIRWING.SquadronAsset + + -- Should not happen as we just checked! + if not asset.payload then + self:E(self.lid.."ERROR: No payload for asset! This should not happen!") + end + + -- Add asset to mission. + mission:AddAsset(asset) + end + + -- Now return the remaining payloads. + for i=mission.nassets+1,#assets do + local asset=assets[i] --#AIRWING.SquadronAsset + for _,uid in pairs(gotpayload) do + if uid==asset.uid then + self:ReturnPayloadFromAsset(asset) + break + end + end + end + + return mission + end + + end -- mission due? + end -- mission loop + + return nil +end \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Platoon.lua b/Moose Development/Moose/Ops/Platoon.lua new file mode 100644 index 000000000..e69de29bb From f4cb6df8d4bb7cc9c1b3a8e92a1671b8d550273b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 19 Jan 2021 16:53:22 +0100 Subject: [PATCH 004/141] Ops respawn --- Moose Development/Moose/Ops/ArmyGroup.lua | 39 ++++++- Moose Development/Moose/Ops/FlightGroup.lua | 20 ---- Moose Development/Moose/Ops/OpsGroup.lua | 119 +++++++++++++++++++- 3 files changed, 152 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index a5754efd9..de31c0a03 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -394,11 +394,43 @@ function ARMYGROUP:onafterStatus(From, Event, To) else -- Info text. - local text=string.format("State %s: Alive=%s", fsmstate, tostring(self:IsAlive())) - self:T2(self.lid..text) + if self.verbose>=1 then + local text=string.format("State %s: Alive=%s", fsmstate, tostring(self:IsAlive())) + self:I(self.lid..text) + end end + --- + -- Elements + --- + + if self.verbose>=2 then + local text="Elements:" + for i,_element in pairs(self.elements) do + local element=_element --#ARMYGROUP.Element + + local name=element.name + local status=element.status + local unit=element.unit + --local life=unit:GetLifeRelative() or 0 + local life,life0=self:GetLifePoints(element) + + local life0=element.life0 + + -- Get ammo. + local ammo=self:GetAmmoElement(element) + + -- Output text for element. + text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d", + i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles) + end + if #self.elements==0 then + text=text.." none!" + end + self:I(self.lid..text) + end + --- -- Tasks & Missions @@ -965,8 +997,11 @@ function ARMYGROUP:OnEventBirth(EventData) if self.respawning then + self:I(self.lid.."Respawning unit "..tostring(unitname)) + local function reset() self.respawning=nil + self:_CheckGroupDone() end -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ce641b949..92f7e4edc 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2025,26 +2025,6 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) end ---- On after "Respawn" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #table Template The template used to respawn the group. -function FLIGHTGROUP:onafterRespawn(From, Event, To, Template) - - self:T(self.lid.."Respawning group!") - - local template=UTILS.DeepCopy(Template or self.template) - - if self.group and self.group:InAir() then - template.lateActivation=false - self.respawning=true - self.group=self.group:Respawn(template) - end - -end - --- Check if flight is done, i.e. -- -- * passed the final waypoint, diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c202ab0ff..bdc8d445d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -399,6 +399,7 @@ function OPSGROUP:New(Group) -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("InUtero", "Spawned", "Spawned") -- The whole group was spawned. + self:AddTransition("*", "Respawn", "*") -- Respawn group. self:AddTransition("*", "Dead", "Dead") -- The whole group is dead. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -408,7 +409,6 @@ function OPSGROUP:New(Group) self:AddTransition("*", "Damaged", "*") -- Someone in the group took damage. self:AddTransition("*", "UpdateRoute", "*") -- Update route of group. Only if airborne. - self:AddTransition("*", "Respawn", "*") -- Respawn group. self:AddTransition("*", "PassingWaypoint", "*") -- Passing waypoint. self:AddTransition("*", "DetectedUnit", "*") -- Unit was detected (again) in this detection cycle. @@ -497,12 +497,34 @@ end --- Returns the absolute (average) life points of the group. -- @param #OPSGROUP self +-- @param #OPSGROUP.Element Element (Optional) Only get life points of this element. -- @return #number Life points. If group contains more than one element, the average is given. -- @return #number Initial life points. -function OPSGROUP:GetLifePoints() - if self.group then - return self.group:GetLife(), self.group:GetLife0() +function OPSGROUP:GetLifePoints(Element) + + local life=0 + local life0=0 + + if Element then + + local unit=Element.unit + + if unit then + life=unit:GetLife() + life0=unit:GetLife0() + end + + else + + for _,element in pairs(self.elements) do + local l,l0=self:GetLifePoints(element) + life=life+l + life0=life+l0 + end + end + + return life, life0 end @@ -3800,6 +3822,95 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end +--- On after "Respawn" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #table Template The template used to respawn the group. +function OPSGROUP:onafterRespawn(From, Event, To, Template) + + self:I(self.lid.."Respawning group!") + + local template=UTILS.DeepCopy(Template or self.template) + + template.lateActivation=false + + self:_Respawn(template,Reset) + +end + +--- Respawn the group. +-- @param #OPSGROUP self +-- @param #table Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. +-- @param #boolean Reset Reset positions if TRUE. +-- @return #OPSGROUP self +function OPSGROUP:_Respawn(Template, Reset) + + env.info("FF _Respawn") + + -- Given template or get old. + Template=Template or UTILS.DeepCopy(self.template) + + -- Get correct heading. + local function _Heading(course) + local h + if course<=180 then + h=math.rad(course) + else + h=-math.rad(360-course) + end + return h + end + + if self:IsAlive() then + + --- + -- Group is ALIVE + + -- Get units. + local units=self.group:GetUnits() + + -- Loop over template units. + for UnitID, Unit in pairs(Template.units) do + + for _,_unit in pairs(units) do + local unit=_unit --Wrapper.Unit#UNIT + + if unit:GetName()==Unit.name then + local vec3=unit:GetVec3() + local heading=unit:GetHeading() + Unit.x=vec3.x + Unit.y=vec3.z + Unit.alt=vec3.y + Unit.heading=math.rad(heading) + Unit.psi=-Unit.heading + end + end + + end + + -- Despawn old group. Dont trigger any remove unit event since this is a respawn. + self:Despawn(0, true) + + else + + end + + -- Currently respawning. + self.respawning=true + + self:I({Template=Template}) + + -- Spawn new group. + _DATABASE:Spawn(Template) + + -- Reset events. + --self:ResetEvents() + + return self +end + --- On before "Dead" event. -- @param #OPSGROUP self -- @param #string From From state. From c247a98402607c6f11b9580f8522cec370fe12ca Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 23 Jan 2021 16:46:44 +0100 Subject: [PATCH 005/141] OPS cargo --- Moose Development/Moose/Ops/ArmyGroup.lua | 255 ++--- Moose Development/Moose/Ops/FlightGroup.lua | 151 +-- Moose Development/Moose/Ops/NavyGroup.lua | 94 +- Moose Development/Moose/Ops/OpsGroup.lua | 1117 ++++++++++++++----- 4 files changed, 967 insertions(+), 650 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index de31c0a03..5a9570e75 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -64,6 +64,7 @@ ARMYGROUP = { -- @field #number length Length of element in meters. -- @field #number width Width of element in meters. -- @field #number height Height of element in meters. +-- @extends Ops.OpsGroup#OPSGROUP.Element --- Target -- @type ARMYGROUP.Target @@ -78,10 +79,10 @@ ARMYGROUP.version="0.4.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Retreat. -- TODO: Suppression of fire. -- TODO: Check if group is mobile. -- TODO: F10 menu. +-- DONE: Retreat. -- DONE: Rearm. Specify a point where to go and wait until ammo is full. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -116,13 +117,13 @@ function ARMYGROUP:New(Group) self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards. self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. - self:AddTransition("*", "Retreat", "Retreating") -- - self:AddTransition("Retreating", "Retreated", "Retreated") -- + self:AddTransition("*", "Retreat", "Retreating") -- Order a retreat. + self:AddTransition("Retreating", "Retreated", "Retreated") -- Group retreated. - self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target - self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target - self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target - self:AddTransition("Engaging", "Disengage", "Cruising") -- Engage a target + self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target from Cruising state + self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target from Holding state + self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target from OnDetour state + self:AddTransition("Engaging", "Disengage", "Cruising") -- Disengage and back to cruising. self:AddTransition("*", "Rearm", "Rearm") -- Group is send to a coordinate and waits until ammo is refilled. self:AddTransition("Rearm", "Rearming", "Rearming") -- Group has arrived at the rearming coodinate and is waiting to be fully rearmed. @@ -152,8 +153,7 @@ function ARMYGROUP:New(Group) -- Handle events: self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.Dead, self.OnEventDead) - self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) - + self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) --self:HandleEvent(EVENTS.Hit, self.OnEventHit) -- Start the status monitoring. @@ -343,7 +343,9 @@ function ARMYGROUP:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() - if self:IsAlive() then + local alive=self:IsAlive() + + if alive then --- -- Detection @@ -371,6 +373,10 @@ function ARMYGROUP:onafterStatus(From, Event, To) self:_UpdateEngageTarget() end + end + + if alive~=nil then + if self.verbose>=1 then -- Get number of tasks and missions. @@ -379,14 +385,20 @@ function ARMYGROUP:onafterStatus(From, Event, To) local roe=self:GetROE() local alarm=self:GetAlarmstate() - local speed=UTILS.MpsToKnots(self.velocity) + local speed=UTILS.MpsToKnots(self.velocity or 0) local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) local formation=self.option.Formation or "unknown" local ammo=self:GetAmmoTot() + + local cargo=0 + for _,_element in pairs(self.elements) do + local element=_element --Ops.OpsGroup#OPSGROUP.Element + cargo=cargo+element.weightCargo + end -- Info text. - local text=string.format("%s [ROE-AS=%d-%d T/M=%d/%d]: Wp=%d/%d-->%d (final %s), Life=%.1f, Speed=%.1f (%d), Heading=%03d, Ammo=%d", - fsmstate, roe, alarm, nTaskTot, nMissions, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), tostring(self.passedfinalwp), self.life or 0, speed, speedEx, self.heading, ammo.Total) + local text=string.format("%s [ROE-AS=%d-%d T/M=%d/%d]: Wp=%d/%d-->%d (final %s), Life=%.1f, Speed=%.1f (%d), Heading=%03d, Ammo=%d, Cargo=%.1f", + fsmstate, roe, alarm, nTaskTot, nMissions, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), tostring(self.passedfinalwp), self.life or 0, speed, speedEx, self.heading or 0, ammo.Total, cargo) self:I(self.lid..text) end @@ -408,7 +420,7 @@ function ARMYGROUP:onafterStatus(From, Event, To) if self.verbose>=2 then local text="Elements:" for i,_element in pairs(self.elements) do - local element=_element --#ARMYGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element local name=element.name local status=element.status @@ -443,6 +455,26 @@ function ARMYGROUP:onafterStatus(From, Event, To) self:__Status(-30) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- DCS Events ==> See OPSGROUP +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Event function handling when a unit is hit. +-- @param #ARMYGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function ARMYGROUP:OnEventHit(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + -- TODO: suppression + + end +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -452,7 +484,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #ARMYGROUP.Element Element The group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The group element. function ARMYGROUP:onafterElementSpawned(From, Event, To, Element) self:T(self.lid..string.format("Element spawned %s", Element.name)) @@ -811,6 +843,40 @@ function ARMYGROUP:onafterRetreated(From, Event, To) end +--- On after "Board" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP.Element Carrier to board. +function ARMYGROUP:onafterBoard(From, Event, To, Carrier, Formation) + + local Coordinate=Carrier.unit:GetCoordinate() + + local Speed=UTILS.KmphToKnots(self.speedMax*0.2) + + local waypoint=self:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, true) + + +end + +--- On after "Pickup" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP.Element Carrier to board. +function ARMYGROUP:onafterBoard(From, Event, To, Carrier, Formation) + + local Coordinate=Carrier.unit:GetCoordinate() + + local Speed=UTILS.KmphToKnots(self.speedMax*0.2) + + local waypoint=self:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, true) + + +end + --- On after "EngageTarget" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -980,113 +1046,6 @@ function ARMYGROUP:onafterStop(From, Event, To) end -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Events DCS -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Event function handling the birth of a unit. --- @param #ARMYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function ARMYGROUP:OnEventBirth(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - if self.respawning then - - self:I(self.lid.."Respawning unit "..tostring(unitname)) - - local function reset() - self.respawning=nil - self:_CheckGroupDone() - end - - -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. - -- TODO: Can I do this more rigorously? - self:ScheduleOnce(1, reset) - - else - - -- Get element. - local element=self:GetElementByName(unitname) - - -- Set element to spawned state. - self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", element.name)) - self:ElementSpawned(element) - - end - - end - -end - ---- Event function handling the crash of a unit. --- @param #ARMYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function ARMYGROUP:OnEventDead(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - self:T(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName)) - - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- Get element. - local element=self:GetElementByName(unitname) - - if element then - self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed", element.name)) - self:ElementDestroyed(element) - end - - end - -end - ---- Event function handling when a unit is removed from the game. --- @param #ARMYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function ARMYGROUP:OnEventRemoveUnit(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- Get element. - local element=self:GetElementByName(unitname) - - if element then - self:T(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) - self:ElementDead(element) - end - - end - -end - ---- Event function handling when a unit is hit. --- @param #ARMYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function ARMYGROUP:OnEventHit(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- TODO: suppression - - end -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1197,49 +1156,17 @@ function ARMYGROUP:_InitGroup() -- Units of the group. local units=self.group:GetUnits() - for _,_unit in pairs(units) do - local unit=_unit --Wrapper.Unit#UNIT - - -- TODO: this is wrong when grouping is used! - local unittemplate=unit:GetTemplate() - - local element={} --#ARMYGROUP.Element - element.name=unit:GetName() - element.unit=unit - element.status=OPSGROUP.ElementStatus.INUTERO - element.typename=unit:GetTypeName() - element.skill=unittemplate.skill or "Unknown" - element.ai=true - element.category=element.unit:GetUnitCategory() - element.categoryname=element.unit:GetCategoryName() - element.size, element.length, element.height, element.width=unit:GetObjectSize() - element.ammo0=self:GetAmmoUnit(unit, false) - element.life0=unit:GetLife0() - element.life=element.life0 - - -- Debug text. - if self.verbose>=2 then - local text=string.format("Adding element %s: status=%s, skill=%s, life=%.3f category=%s (%d), size: %.1f (L=%.1f H=%.1f W=%.1f)", - element.name, element.status, element.skill, element.life, element.categoryname, element.category, element.size, element.length, element.height, element.width) - self:I(self.lid..text) - end - - -- Add element to table. - table.insert(self.elements, element) - - -- Get Descriptors. - self.descriptors=self.descriptors or unit:GetDesc() - - -- Set type name. - self.actype=self.actype or unit:GetTypeName() - - if unit:IsAlive() then - -- Trigger spawned event. - self:ElementSpawned(element) - end - + -- Add elemets. + for _,unit in pairs(units) do + self:_AddElementByName(unit:GetName()) end - + + -- Get Descriptors. + self.descriptors=units[1]:GetDesc() + + -- Set type name. + self.actype=units[1]:GetTypeName() + -- Debug info. if self.verbose>=1 then local text=string.format("Initialized Army Group %s:\n", self.groupname) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 92f7e4edc..4ae70e3da 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -175,22 +175,13 @@ FLIGHTGROUP.Attribute = { --- Flight group element. -- @type FLIGHTGROUP.Element --- @field #string name Name of the element, i.e. the unit/client. --- @field Wrapper.Unit#UNIT unit Element unit object. --- @field Wrapper.Group#GROUP group Group object of the element. -- @field #string modex Tail number. --- @field #string skill Skill level. --- @field #boolean ai If true, element is AI. -- @field Wrapper.Client#CLIENT client The client if element is occupied by a human player. -- @field #table pylons Table of pylons. -- @field #number fuelmass Mass of fuel in kg. --- @field #number category Aircraft category. --- @field #string categoryname Aircraft category name. -- @field #string callsign Call sign, e.g. "Uzi 1-1". --- @field #string status Status, i.e. born, parking, taxiing. See @{#OPSGROUP.ElementStatus}. --- @field #number damage Damage of element in percent. -- @field Wrapper.Airbase#AIRBASE.ParkingSpot parking The parking spot table the element is parking on. - +-- @extends Ops.OpsGroup#OPSGROUP.Element --- FLIGHTGROUP class version. -- @field #string version @@ -243,7 +234,7 @@ function FLIGHTGROUP:New(group) self.lid=string.format("FLIGHTGROUP %s | ", self.groupname) -- Defaults - --self:SetVerbosity(0) + self.isFlightgroup=true self:SetFuelLowThreshold() self:SetFuelLowRTB() self:SetFuelCriticalThreshold() @@ -251,7 +242,6 @@ function FLIGHTGROUP:New(group) self:SetDefaultROE() self:SetDefaultROT() self:SetDetection() - self.isFlightgroup=true -- Holding flag. self.flaghold=USERFLAG:New(string.format("%s_FlagHold", self.groupname)) @@ -314,13 +304,6 @@ function FLIGHTGROUP:New(group) -- TODO: Add pseudo functions. - -- Debug trace. - if false then - BASE:TraceOnOff(true) - BASE:TraceClass(self.ClassName) - BASE:TraceLevel(1) - end - -- Add to data base. _DATABASE:AddFlightGroup(self) @@ -554,7 +537,7 @@ end --- Disable to automatically engage detected targets. -- @param #FLIGHTGROUP self --- @return #OPSGROUP self +-- @return #FLIGHTGROUP self function FLIGHTGROUP:SetEngageDetectedOff() self.engagedetectedOn=false return self @@ -747,7 +730,7 @@ function FLIGHTGROUP:GetFuelMin() local fuelmin=math.huge for i,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element local unit=element.unit @@ -1119,64 +1102,9 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Events +-- DCS Events ==> See also OPSGROUP ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Flightgroup event function, handling the birth of a unit. --- @param #FLIGHTGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function FLIGHTGROUP:OnEventBirth(EventData) - - --env.info(string.format("EVENT: Birth for unit %s", tostring(EventData.IniUnitName))) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- Set group. - self.group=self.group or EventData.IniGroup - - if self.respawning then - - local function reset() - self.respawning=nil - end - - -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. - -- TODO: Can I do this more rigorously? - self:ScheduleOnce(1, reset) - - else - - -- Set homebase if not already set. - if EventData.Place then - self.homebase=self.homebase or EventData.Place - end - - if self.homebase and not self.destbase then - self.destbase=self.homebase - end - - -- Get element. - local element=self:GetElementByName(unitname) - - -- Create element spawned event if not already present. - if not self:_IsElement(unitname) then - element=self:AddElementByName(unitname) - end - - -- Set element to spawned state. - self:T(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", element.name, self.homebase and self.homebase:GetName() or "unknown")) - self:ElementSpawned(element) - - end - - end - -end - --- Flightgroup event function handling the crash of a unit. -- @param #FLIGHTGROUP self -- @param Core.Event#EVENTDATA EventData Event data. @@ -1356,69 +1284,7 @@ function FLIGHTGROUP:OnEventUnitLost(EventData) end ---- Flightgroup event function handling the crash of a unit. --- @param #FLIGHTGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function FLIGHTGROUP:OnEventKill(EventData) - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - - -- Target name - local targetname=tostring(EventData.TgtUnitName) - - -- Debug info. - self:T2(self.lid..string.format("EVENT: Unit %s killed object %s!", tostring(EventData.IniUnitName), targetname)) - - -- Check if this was a UNIT or STATIC object. - local target=UNIT:FindByName(targetname) - if not target then - target=STATIC:FindByName(targetname, false) - end - - -- Only count UNITS and STATICs (not SCENERY) - if target then - - -- Debug info. - self:T(self.lid..string.format("EVENT: Unit %s killed unit/static %s!", tostring(EventData.IniUnitName), targetname)) - - -- Kill counter. - self.Nkills=self.Nkills+1 - - -- Check if on a mission. - local mission=self:GetMissionCurrent() - if mission then - mission.Nkills=mission.Nkills+1 -- Increase mission kill counter. - end - - end - - end - -end - ---- Flightgroup event function handling the crash of a unit. --- @param #FLIGHTGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function FLIGHTGROUP:OnEventRemoveUnit(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- Get element. - local element=self:GetElementByName(unitname) - - if element then - self:T3(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) - self:ElementDead(element) - end - - end - -end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM functions @@ -1743,7 +1609,7 @@ function FLIGHTGROUP:onafterTaxiing(From, Event, To) self.Tparking=nil -- TODO: need a better check for the airbase. - local airbase=self:GetClosestAirbase() --self.group:GetCoordinate():GetClosestAirbase(nil, self.group:GetCoalition()) + local airbase=self:GetClosestAirbase() if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then @@ -2894,7 +2760,7 @@ function FLIGHTGROUP:_InitGroup() -- Add elemets. for _,unit in pairs(self.group:GetUnits()) do - local element=self:AddElementByName(unit:GetName()) + self:_AddElementByName(unit:GetName()) end -- Get first unit. This is used to extract other parameters. @@ -2960,10 +2826,11 @@ function FLIGHTGROUP:AddElementByName(unitname) local element={} --#FLIGHTGROUP.Element element.name=unitname - element.unit=unit element.status=OPSGROUP.ElementStatus.INUTERO + element.unit=unit element.group=unit:GetGroup() + -- TODO: this is wrong when grouping is used! local unittemplate=element.unit:GetTemplate() diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 7fa71c335..a2b64b9eb 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -611,6 +611,12 @@ function NAVYGROUP:onafterStatus(From, Event, To) self:__Status(-30) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- DCS Events ==> See OPSGROUP +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- See OPSGROUP! + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1032,94 +1038,6 @@ function NAVYGROUP:onafterStop(From, Event, To) end -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Events DCS -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Event function handling the birth of a unit. --- @param #NAVYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function NAVYGROUP:OnEventBirth(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - if self.respawning then - - local function reset() - self.respawning=nil - end - - -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. - -- TODO: Can I do this more rigorously? - self:ScheduleOnce(1, reset) - - else - - -- Get element. - local element=self:GetElementByName(unitname) - - -- Set element to spawned state. - self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", element.name)) - self:ElementSpawned(element) - - end - - end - -end - ---- Flightgroup event function handling the crash of a unit. --- @param #NAVYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function NAVYGROUP:OnEventDead(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - self:T(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName)) - - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- Get element. - local element=self:GetElementByName(unitname) - - if element then - self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed", element.name)) - self:ElementDestroyed(element) - end - - end - -end - ---- Flightgroup event function handling the crash of a unit. --- @param #NAVYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function NAVYGROUP:OnEventRemoveUnit(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- Get element. - local element=self:GetElementByName(unitname) - - if element then - self:T(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) - self:ElementDead(element) - end - - end - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index bdc8d445d..2a0e9e157 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -95,6 +95,9 @@ -- @field #OPSGROUP.Ammo ammo Initial ammount of ammo. -- @field #OPSGROUP.WeaponData weaponData Weapon data table with key=BitType. -- +-- @field #OPSGROUP.Element carrier Carrier the group is loaded into as cargo. +-- @field #table cargo Table containing all cargo of the carrier. +-- -- @extends Core.Fsm#FSM --- *A small group of determined and like-minded people can change the course of history.* --- Mahatma Gandhi @@ -149,20 +152,44 @@ OPSGROUP = { Ndestroyed = 0, Nkills = 0, weaponData = {}, + cargo = {}, } --- OPS group element. -- @type OPSGROUP.Element +-- -- @field #string name Name of the element, i.e. the unit. +-- @field #string status The element status. See @{#OPSGROUP.ElementStatus}. -- @field Wrapper.Unit#UNIT unit The UNIT object. --- @field #string status The element status. +-- @field Wrapper.Group#GROUP group The GROUP object. +-- @field DCS#Unit DCSunit The DCS unit object. +-- @field #boolean ai If true, element is AI. +-- @field #string skill Skill level. +-- -- @field #string typename Type name. +-- @field #number category Aircraft category. +-- @field #string categoryname Aircraft category name. +-- +-- @field #number size Size (max of length, width, height) in meters. -- @field #number length Length of element in meters. -- @field #number width Width of element in meters. -- @field #number height Height of element in meters. +-- +-- @field DCS#Vec3 vec3 Last known 3D position vector. +-- @field DCS#Vec3 orientX Last known ordientation vector in the direction of the nose X. +-- @field #number heading Last known heading in degrees. +-- -- @field #number life0 Initial life points. -- @field #number life Life points when last updated. +-- @field #number damage Damage of element in percent. +-- +-- @field DCS#Object.Desc descriptors Descriptors table. +-- @field #number weightEmpty Empty weight in kg. +-- @field #number weightMaxTotal Max. total weight in kg. +-- @field #number weightMaxCargo Max. cargo weight in kg. +-- @field #number weightCargo Current cargo weight in kg. +-- @field #number weight Current weight including cargo in kg. --- Status of group element. -- @type OPSGROUP.ElementStatus @@ -328,7 +355,19 @@ OPSGROUP.TaskType={ -- @field Wrapper.Marker#MARKER marker Marker on the F10 map. -- @field #string formation Ground formation. Similar to action but on/off road. ---- NavyGroup version. +--- Cargo data. +-- @type OPSGROUP.CargoStatus +-- @field #string Reserved +-- @field #string Loaded + +--- Cargo data. +-- @type OPSGROUP.Cargo +-- @field #OPSGROUP opsgroup The cargo opsgroup. +-- @field #OPSGROUP.CargoStatus status Status of the cargo group. +-- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. +-- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. + +--- OpsGroup version. -- @field #string version OPSGROUP.version="0.7.1" @@ -401,6 +440,7 @@ function OPSGROUP:New(Group) self:AddTransition("InUtero", "Spawned", "Spawned") -- The whole group was spawned. self:AddTransition("*", "Respawn", "*") -- Respawn group. self:AddTransition("*", "Dead", "Dead") -- The whole group is dead. + self:AddTransition("*", "InUtero", "InUtero") -- Deactivated group goes back to mummy. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Status", "*") -- Status update. @@ -453,11 +493,28 @@ function OPSGROUP:New(Group) self:AddTransition("*", "UnpauseMission", "*") -- Unpause the the paused mission. self:AddTransition("*", "MissionDone", "*") -- Mission is over. + self:AddTransition("*", "ElementInUtero", "*") -- An element is in utero again. self:AddTransition("*", "ElementSpawned", "*") -- An element was spawned. self:AddTransition("*", "ElementDestroyed", "*") -- An element was destroyed. self:AddTransition("*", "ElementDead", "*") -- An element is dead. self:AddTransition("*", "ElementDamaged", "*") -- An element was damaged. + self:AddTransition("*", "Board", "Boarding") -- Group is boarding a cargo carrier. + self:AddTransition("*", "Embark", "*") -- Group was loaded into a cargo carrier. + self:AddTransition("InUtero", "Unboard", "*") -- Group was unloaded from a cargo carrier. + + self:AddTransition("*", "Pickup", "Pickingup") -- Carrier and is on route to pick up cargo. + self:AddTransition("Pickingup", "Loading", "Loading") -- Carrier is loading cargo. + self:AddTransition("Loading", "Load", "Loading") -- Carrier loads cargo into carrier. + + self:AddTransition("Loading", "Pickup", "Pickingup") -- Carrier is picking up another cargo. + + self:AddTransition("Loading", "Transport", "Transporting")-- Carrier is transporting cargo. + + self:AddTransition("Transporting", "Dropoff", "Droppingoff") -- Carrier is dropping off cargo. + self:AddTransition("Droppingoff", "Dropoff", "Droppingoff") -- Carrier is dropping off cargo. + self:AddTransition("Droppingoff", "Droppedoff", "Droppedoff") -- Carrier has dropped off all its cargo. + ------------------------ --- Pseudo Functions --- ------------------------ @@ -860,78 +917,244 @@ function OPSGROUP:GetDCSUnits() return nil end ---- Despawn the group. The whole group is despawned and (optionally) a "Remove Unit" event is generated for all current units of the group. + +--- Get current 2D position vector of the group. -- @param #OPSGROUP self +-- @param #string UnitName (Optional) Get position of a specifc unit of the group. Default is the first existing unit in the group. +-- @return DCS#Vec2 Vector with x,y components. +function OPSGROUP:GetVec2(UnitName) + + local vec3=self:GetVec3(UnitName) + + if vec3 then + local vec2={x=vec3.x, y=vec3.z} + return vec2 + end + + return nil +end + + +--- Get current 3D position vector of the group. +-- @param #OPSGROUP self +-- @param #string UnitName (Optional) Get position of a specifc unit of the group. Default is the first existing unit in the group. +-- @return DCS#Vec3 Vector with x,y,z components. +function OPSGROUP:GetVec3(UnitName) + + if self:IsExist() then + + local unit=nil --DCS#Unit + if UnitName then + unit=Unit.getByName(UnitName) + else + unit=self:GetDCSUnit() + end + + + if unit then + local vec3=unit:getPoint() + return vec3 + end + + end + + return nil +end + +--- Get current coordinate of the group. +-- @param #OPSGROUP self +-- @param #boolean NewObject Create a new coordiante object. +-- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. +function OPSGROUP:GetCoordinate(NewObject) + + local vec3=self:GetVec3() + + if vec3 then + + self.coordinate=self.coordinate or COORDINATE:New(0,0,0) + + self.coordinate.x=vec3.x + self.coordinate.y=vec3.y + self.coordinate.z=vec3.z + + if NewObject then + local coord=COORDINATE:NewFromCoordinate(self.coordinate) + return coord + else + return self.coordinate + end + else + self:E(self.lid.."WARNING: Group is not alive. Cannot get coordinate!") + end + + return nil +end + +--- Get current velocity of the group. +-- @param #OPSGROUP self +-- @param #string UnitName (Optional) Get heading of a specific unit of the group. Default is from the first existing unit in the group. +-- @return #number Velocity in m/s. +function OPSGROUP:GetVelocity(UnitName) + + if self:IsExist() then + + local unit=nil --DCS#Unit + + if UnitName then + unit=Unit.getByName(UnitName) + else + unit=self:GetDCSUnit() + end + + if unit then + + local velvec3=unit:getVelocity() + + local vel=UTILS.VecNorm(velvec3) + + return vel + end + + else + self:E(self.lid.."WARNING: Group does not exist. Cannot get velocity!") + end + + return nil +end + +--- Get current heading of the group. +-- @param #OPSGROUP self +-- @param #string UnitName (Optional) Get heading of a specific unit of the group. Default is from the first existing unit in the group. +-- @return #number Current heading of the group in degrees. +function OPSGROUP:GetHeading(UnitName) + + if self:IsExist() then + + local unit=nil --DCS#Unit + if UnitName then + unit=Unit.getByName(UnitName) + else + unit=self:GetDCSUnit() + end + + if unit then + + local pos=unit:getPosition() + + local heading=math.atan2(pos.x.z, pos.x.x) + + if heading<0 then + heading=heading+ 2*math.pi + end + + heading=math.deg(heading) + + return heading + end + + else + self:E(self.lid.."WARNING: Group does not exist. Cannot get heading!") + end + + return nil +end + +--- Get current orientation of the group. +-- @param #OPSGROUP self +-- @param #string UnitName (Optional) Get orientation of a specific unit of the group. Default is the first existing unit of the group. +-- @return DCS#Vec3 Orientation X parallel to where the "nose" is pointing. +-- @return DCS#Vec3 Orientation Y pointing "upwards". +-- @return DCS#Vec3 Orientation Z perpendicular to the "nose". +function OPSGROUP:GetOrientation(UnitName) + + if self:IsExist() then + + local unit=nil --DCS#Unit + + if UnitName then + unit=Unit.getByName(UnitName) + else + unit=self:GetDCSUnit() + end + + if unit then + + local pos=unit:getPosition() + + return pos.x, pos.y, pos.z + end + + else + self:E(self.lid.."WARNING: Group does not exist. Cannot get orientation!") + end + + return nil +end + +--- Get current orientation of the first unit in the group. +-- @param #OPSGROUP self +-- @param #string UnitName (Optional) Get orientation of a specific unit of the group. Default is the first existing unit of the group. +-- @return DCS#Vec3 Orientation X parallel to where the "nose" is pointing. +function OPSGROUP:GetOrientationX(UnitName) + + local X,Y,Z=self:GetOrientation(UnitName) + + return X +end + + + +--- Check if task description is unique. +-- @param #OPSGROUP self +-- @param #string description Task destription +-- @return #boolean If true, no other task has the same description. +function OPSGROUP:CheckTaskDescriptionUnique(description) + + -- Loop over tasks in queue + for _,_task in pairs(self.taskqueue) do + local task=_task --#OPSGROUP.Task + if task.description==description then + return false + end + end + + return true +end + + +--- Despawn a unit of the group. A "Remove Unit" event is generated by default. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit -- @param #number Delay Delay in seconds before the group will be despawned. Default immediately. -- @param #boolean NoEventRemoveUnit If true, no event "Remove Unit" is generated. -- @return #OPSGROUP self -function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) +function OPSGROUP:DespawnUnit(UnitName, Delay, NoEventRemoveUnit) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, OPSGROUP.Despawn, self, 0, NoEventRemoveUnit) - else + env.info("FF despawn element .."..tostring(UnitName)) - local DCSGroup=self:GetDCSGroup() + local element=self:GetElementByName(UnitName) + + if element then + + -- Get DCS unit object. + local DCSunit=Unit.getByName(UnitName) - if DCSGroup then + if DCSunit then - -- Destroy DCS group. - DCSGroup:destroy() + -- Despawn unit. + DCSunit:destroy() + -- Element goes back in utero. + self:ElementInUtero(element) + if not NoEventRemoveUnit then - - -- Get all units. - local units=self:GetDCSUnits() - - -- Create a "Remove Unit" event. - local EventTime=timer.getTime() - for i=1,#units do - self:CreateEventRemoveUnit(EventTime, units[i]) - end - - end - end - end - - return self -end - ---- Destroy group. The whole group is despawned and a *Unit Lost* for aircraft or *Dead* event for ground/naval units is generated for all current units. --- @param #OPSGROUP self --- @param #number Delay Delay in seconds before the group will be destroyed. Default immediately. --- @return #OPSGROUP self -function OPSGROUP:Destroy(Delay) - - if Delay and Delay>0 then - self:ScheduleOnce(Delay, OPSGROUP.Destroy, self) - else - - local DCSGroup=self:GetDCSGroup() - - if DCSGroup then - - self:T(self.lid.."Destroying group") - - -- Destroy DCS group. - DCSGroup:destroy() - - -- Get all units. - local units=self:GetDCSUnits() - - -- Create a "Unit Lost" event. - local EventTime=timer.getTime() - for i=1,#units do - if self.isAircraft then - self:CreateEventUnitLost(EventTime, units[i]) - else - self:CreateEventDead(EventTime, units[i]) - end + self:CreateEventRemoveUnit(timer.getTime(), DCSunit) end + end end - return self end --- Despawn an element/unit of the group. @@ -970,178 +1193,99 @@ function OPSGROUP:DespawnElement(Element, Delay, NoEventRemoveUnit) return self end ---- Get current 2D position vector of the group. +--- Despawn the group. The whole group is despawned and (optionally) a "Remove Unit" event is generated for all current units of the group. -- @param #OPSGROUP self --- @return DCS#Vec2 Vector with x,y components. -function OPSGROUP:GetVec2() +-- @param #number Delay Delay in seconds before the group will be despawned. Default immediately. +-- @param #boolean NoEventRemoveUnit If true, no event "Remove Unit" is generated. +-- @return #OPSGROUP self +function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) - local vec3=self:GetVec3() - - if vec3 then - local vec2={x=vec3.x, y=vec3.z} - return vec2 - end - - return nil -end - - ---- Get current 3D position vector of the group. --- @param #OPSGROUP self --- @return DCS#Vec3 Vector with x,y,z components. -function OPSGROUP:GetVec3() - if self:IsExist() then - - local unit=self:GetDCSUnit() - - if unit then - local vec3=unit:getPoint() - - return vec3 - end - - end - return nil -end - ---- Get current coordinate of the group. --- @param #OPSGROUP self --- @param #boolean NewObject Create a new coordiante object. --- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. -function OPSGROUP:GetCoordinate(NewObject) - - local vec3=self:GetVec3() - - if vec3 then - - self.coordinate=self.coordinate or COORDINATE:New(0,0,0) - - self.coordinate.x=vec3.x - self.coordinate.y=vec3.y - self.coordinate.z=vec3.z - - if NewObject then - local coord=COORDINATE:NewFromCoordinate(self.coordinate) - return coord - else - return self.coordinate - end + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.Despawn, self, 0, NoEventRemoveUnit) else - self:E(self.lid.."WARNING: Group is not alive. Cannot get coordinate!") - end - - return nil -end ---- Get current velocity of the group. --- @param #OPSGROUP self --- @return #number Velocity in m/s. -function OPSGROUP:GetVelocity() - if self:IsExist() then - - local unit=self:GetDCSUnit(1) + local DCSGroup=self:GetDCSGroup() - if unit then - - local velvec3=unit:getVelocity() - - local vel=UTILS.VecNorm(velvec3) - - return vel + if DCSGroup then + -- Get all units. + local units=self:GetDCSUnits() + + for i=1,#units do + local unit=units[i] + if unit then + local name=unit:getName() + if name then + self:DespawnUnit(name, 0, NoEventRemoveUnit) + end + end + end + end - else - self:E(self.lid.."WARNING: Group does not exist. Cannot get velocity!") end - return nil + + return self end ---- Get current heading of the group. +--- Destroy a unit of the group. A *Unit Lost* for aircraft or *Dead* event for ground/naval units is generated. -- @param #OPSGROUP self --- @return #number Current heading of the group in degrees. -function OPSGROUP:GetHeading() +-- @param #string UnitName Name of the unit which should be destroyed. +-- @param #number Delay Delay in seconds before the group will be destroyed. Default immediately. +-- @return #OPSGROUP self +function OPSGROUP:DestroyUnit(UnitName, Delay) - if self:IsExist() then + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.DestroyUnit, self, UnitName, 0) + else - local unit=self:GetDCSUnit() + local unit=Unit.getByName(UnitName) if unit then + + -- Create a "Unit Lost" event. + local EventTime=timer.getTime() - local pos=unit:getPosition() - - local heading=math.atan2(pos.x.z, pos.x.x) - - if heading<0 then - heading=heading+ 2*math.pi + if self.isAircraft then + self:CreateEventUnitLost(EventTime, unit) + else + self:CreateEventDead(EventTime, unit) end - heading=math.deg(heading) - - return heading end - + + end + +end + +--- Destroy group. The whole group is despawned and a *Unit Lost* for aircraft or *Dead* event for ground/naval units is generated for all current units. +-- @param #OPSGROUP self +-- @param #number Delay Delay in seconds before the group will be destroyed. Default immediately. +-- @return #OPSGROUP self +function OPSGROUP:Destroy(Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.Destroy, self, 0) else - self:E(self.lid.."WARNING: Group does not exist. Cannot get heading!") - end - - return nil -end ---- Get current orientation of the first unit in the group. --- @param #OPSGROUP self --- @return DCS#Vec3 Orientation X parallel to where the "nose" is pointing. --- @return DCS#Vec3 Orientation Y pointing "upwards". --- @return DCS#Vec3 Orientation Z perpendicular to the "nose". -function OPSGROUP:GetOrientation() - - if self:IsExist() then - - local unit=self:GetDCSUnit() + -- Get all units. + local units=self:GetDCSUnits() - if unit then + if units then + + -- Create a "Unit Lost" event. + for _,unit in pairs(units) do + if unit then + self:DestroyUnit(unit:getName()) + end + end - local pos=unit:getPosition() - - return pos.x, pos.y, pos.z - end - - else - self:E(self.lid.."WARNING: Group does not exist. Cannot get orientation!") - end - - return nil -end - ---- Get current orientation of the first unit in the group. --- @param #OPSGROUP self --- @return DCS#Vec3 Orientation X parallel to where the "nose" is pointing. -function OPSGROUP:GetOrientationX() - - local X,Y,Z=self:GetOrientation() - - return X -end - - - ---- Check if task description is unique. --- @param #OPSGROUP self --- @param #string description Task destription --- @return #boolean If true, no other task has the same description. -function OPSGROUP:CheckTaskDescriptionUnique(description) - - -- Loop over tasks in queue - for _,_task in pairs(self.taskqueue) do - local task=_task --#OPSGROUP.Task - if task.description==description then - return false end + end - return true + return self end - --- Activate a *late activated* group. -- @param #OPSGROUP self -- @param #number delay (Optional) Delay in seconds before the group is activated. Default is immediately. @@ -1170,6 +1314,31 @@ function OPSGROUP:Activate(delay) return self end +--- Deactivate the group. Group will be respawned in late activated state. +-- @param #OPSGROUP self +-- @param #number delay (Optional) Delay in seconds before the group is deactivated. Default is immediately. +-- @return #OPSGROUP self +function OPSGROUP:Deactivate(delay) + + if delay and delay>0 then + self:ScheduleOnce(delay, OPSGROUP.Deactivate, self) + else + + if self:IsAlive()==true then + + self.template.lateActivation=true + + local template=UTILS.DeepCopy(self.template) + + self:_Respawn(0, template) + + end + + end + + return self +end + --- Self destruction of group. An explosion is created at the position of each element. -- @param #OPSGROUP self -- @param #number Delay Delay in seconds. Default now. @@ -1742,6 +1911,150 @@ function OPSGROUP:RemoveWaypoint(wpindex) return self end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- DCS Events +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Event function handling the birth of a unit. +-- @param #OPSGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function OPSGROUP:OnEventBirth(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + if self.respawning then + + self:I(self.lid.."Respawning unit "..tostring(unitname)) + + local function reset() + self.respawning=nil + self:_CheckGroupDone() + end + + -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. + -- TODO: Can I do this more rigorously? + self:ScheduleOnce(1, reset) + + else + + -- Set homebase if not already set. + if self.isFlightgroup then + if EventData.Place then + self.homebase=self.homebase or EventData.Place + end + + if self.homebase and not self.destbase then + self.destbase=self.homebase + end + self:T(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", unitname, self.homebase and self.homebase:GetName() or "unknown")) + else + self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) + end + + -- Get element. + local element=self:GetElementByName(unitname) + + -- Set element to spawned state. + self:ElementSpawned(element) + + end + + end + +end + +--- Event function handling the crash of a unit. +-- @param #OPSGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function OPSGROUP:OnEventDead(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + self:T(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName)) + + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + -- Get element. + local element=self:GetElementByName(unitname) + + if element then + self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed", element.name)) + self:ElementDestroyed(element) + end + + end + +end + +--- Event function handling when a unit is removed from the game. +-- @param #OPSGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function OPSGROUP:OnEventRemoveUnit(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + -- Get element. + local element=self:GetElementByName(unitname) + + if element then + self:T(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) + self:ElementDead(element) + end + + end + +end + +--- Event function handling the event that a unit achieved a kill. +-- @param #OPSGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function OPSGROUP:OnEventKill(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + + -- Target name + local targetname=tostring(EventData.TgtUnitName) + + -- Debug info. + self:T2(self.lid..string.format("EVENT: Unit %s killed object %s!", tostring(EventData.IniUnitName), targetname)) + + -- Check if this was a UNIT or STATIC object. + local target=UNIT:FindByName(targetname) + if not target then + target=STATIC:FindByName(targetname, false) + end + + -- Only count UNITS and STATICs (not SCENERY) + if target then + + -- Debug info. + self:T(self.lid..string.format("EVENT: Unit %s killed unit/static %s!", tostring(EventData.IniUnitName), targetname)) + + -- Kill counter. + self.Nkills=self.Nkills+1 + + -- Check if on a mission. + local mission=self:GetMissionCurrent() + if mission then + mission.Nkills=mission.Nkills+1 -- Increase mission kill counter. + end + + end + + end + +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Task Functions @@ -3748,6 +4061,19 @@ function OPSGROUP:_UpdateLaser() end +--- On after "ElementInUtero" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.Element Element The flight group element. +function OPSGROUP:onafterElementInUtero(From, Event, To, Element) + self:I(self.lid..string.format("Element in utero %s", Element.name)) + + -- Set element status. + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.INUTERO) + +end --- On after "ElementDestroyed" event. -- @param #OPSGROUP self @@ -3827,7 +4153,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #table Template The template used to respawn the group. +-- @param #table Template The template used to respawn the group. Default is the inital template of the group. function OPSGROUP:onafterRespawn(From, Event, To, Template) self:I(self.lid.."Respawning group!") @@ -3836,78 +4162,113 @@ function OPSGROUP:onafterRespawn(From, Event, To, Template) template.lateActivation=false - self:_Respawn(template,Reset) + --self.respawning=true + + self:_Respawn(0, template, Reset) end --- Respawn the group. -- @param #OPSGROUP self +-- @param #number Delay Delay in seconds before respawn happens. Default 0. -- @param #table Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. -- @param #boolean Reset Reset positions if TRUE. -- @return #OPSGROUP self -function OPSGROUP:_Respawn(Template, Reset) +function OPSGROUP:_Respawn(Delay, Template, Reset) - env.info("FF _Respawn") + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP._Respawn, self, 0, Template, Reset) + else - -- Given template or get old. - Template=Template or UTILS.DeepCopy(self.template) + env.info("FF _Respawn") - -- Get correct heading. - local function _Heading(course) - local h - if course<=180 then - h=math.rad(course) + -- Given template or get old. + Template=Template or UTILS.DeepCopy(self.template) + + -- Get correct heading. + local function _Heading(course) + local h + if course<=180 then + h=math.rad(course) + else + h=-math.rad(360-course) + end + return h + end + + if self:IsAlive() then + + --- + -- Group is ALIVE + --- + + -- Get units. + local units=self.group:GetUnits() + + -- Loop over template units. + for UnitID, Unit in pairs(Template.units) do + + for _,_unit in pairs(units) do + local unit=_unit --Wrapper.Unit#UNIT + + if unit:GetName()==Unit.name then + local vec3=unit:GetVec3() + local heading=unit:GetHeading() + Unit.x=vec3.x + Unit.y=vec3.z + Unit.alt=vec3.y + Unit.heading=math.rad(heading) + Unit.psi=-Unit.heading + end + end + + end + + -- Despawn old group. Dont trigger any remove unit event since this is a respawn. + self:Despawn(0, true) + else - h=-math.rad(360-course) - end - return h - end - if self:IsAlive() then - - --- - -- Group is ALIVE - - -- Get units. - local units=self.group:GetUnits() - - -- Loop over template units. - for UnitID, Unit in pairs(Template.units) do - - for _,_unit in pairs(units) do - local unit=_unit --Wrapper.Unit#UNIT - - if unit:GetName()==Unit.name then - local vec3=unit:GetVec3() - local heading=unit:GetHeading() + --- + -- Group is DESPAWNED + --- + + --[[ + + -- Loop over template units. + for UnitID, Unit in pairs(Template.units) do + + local element=self:GetElementByName(Unit.name) + + if element then + local vec3=element.vec3 + local heading=element.heading Unit.x=vec3.x Unit.y=vec3.z Unit.alt=vec3.y Unit.heading=math.rad(heading) Unit.psi=-Unit.heading end + end - + + ]] + end - -- Despawn old group. Dont trigger any remove unit event since this is a respawn. - self:Despawn(0, true) + -- Currently respawning. + --self.respawning=true + + self:I({Template=Template}) + + -- Spawn new group. + _DATABASE:Spawn(Template) + + -- Reset events. + --self:ResetEvents() - else - end - -- Currently respawning. - self.respawning=true - - self:I({Template=Template}) - - -- Spawn new group. - _DATABASE:Spawn(Template) - - -- Reset events. - --self:ResetEvents() - return self end @@ -3974,6 +4335,140 @@ function OPSGROUP:onafterStop(From, Event, To) self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Cargo Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "Load" event. Carrier loads a cargo group into ints cargo bay. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP CargoGroup The OPSGROUP loaded as cargo. +function OPSGROUP:onafterLoad(From, Event, To, CargoGroup) + + env.info("FF load") + + local weight=500 + + local carrier=nil --#OPSGROUP.Element + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + local cargobay=element.weightMaxCargo-element.weightCargo + + if cargobay>=weight then + carrier=element + break + end + + end + + if carrier then + + -- TODO: add function to set/add/get internal cargo. + carrier.weightCargo=carrier.weightCargo+weight + + -- This function is only really used for aircraft and sets the total internal cargo weight. + trigger.action.setUnitInternalCargo(carrier.name, carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + + -- Embark ==> Loaded + CargoGroup:Embark(carrier) + + else + self:E(self.lid.."Cound not find a carrier with enough cargo capacity!") + end + +end + +--- On after "Unload" event. Carrier unloads a cargo group from its cargo bay. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP CargoGroup The OPSGROUP loaded as cargo. +-- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. +-- @param #number Heading Heading of group. +function OPSGROUP:onafterUnload(From, Event, To, CargoGroup, Coordinate, Heading) + + --TODO: Add check if CargoGroup is + if CargoGroup:IsInUtero() then + CargoGroup:Unboard(Coordinate, Heading) + end + +end + +--- On after "Stop" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.Element Carrier The OPSGROUP element +function OPSGROUP:onafterEmbark(From, Event, To, Carrier) + + env.info("FF embark") + + -- Despawn this group. + self:Despawn(0, true) + + self.carrier=Carrier + +end + +--- On after "Unboard" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. Can also be a DCS#Vec3 object. +-- @param #number Heading Heading the group has in degrees. Default is last known heading of the group. +function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) + + env.info("FF Unboard") + + -- Template for the respawned group. + local Template=UTILS.DeepCopy(self.template) + + -- Loop over template units. + for _,Unit in pairs(Template.units) do + + local element=self:GetElementByName(Unit.name) + + if element then + + local vec3=element.vec3 + + -- Relative pos vector. + local rvec2={x=Unit.x-Template.x, y=Unit.y-Template.y} --DCS#Vec2 + + local cvec2={x=Coordinate.x, y=Coordinate.z} --DCS#Vec2 + + -- Position. + Unit.x=cvec2.x+rvec2.x + Unit.y=cvec2.y+rvec2.y + Unit.alt=land.getHeight({x=Unit.x, y=Unit.y}) + + -- Heading. + Unit.heading=Heading and math.rad(Heading) or Unit.heading + Unit.psi=-Unit.heading + + end + + end + + -- Reduce carrier weight. + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + self.carrier.weightCargo=self.carrier.weightCargo-element.weight + end + -- This function is only really used for aircraft and sets the total internal cargo weight. + trigger.action.setUnitInternalCargo(self.carrier.name, self.carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + + -- Respawn group. + self:_Respawn(0, Template) + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Internal Check Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -5429,7 +5924,7 @@ end -- @return #OPSGROUP self function OPSGROUP:_UpdatePosition() - if self:IsAlive() then + if self:IsAlive()~=nil then -- Backup last state to monitor differences. self.positionLast=self.position or self:GetVec3() @@ -5443,6 +5938,11 @@ function OPSGROUP:_UpdatePosition() self.orientX=self:GetOrientationX() self.velocity=self:GetVelocity() + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + element.vec3=self:GetVec3(element.name) + end + -- Update time. local Tnow=timer.getTime() self.dTpositionUpdate=self.TpositionUpdate and Tnow-self.TpositionUpdate or 0 @@ -5451,11 +5951,11 @@ function OPSGROUP:_UpdatePosition() if not self.traveldist then self.traveldist=0 end - + + -- Travel distance since last check. self.travelds=UTILS.VecNorm(UTILS.VecSubstract(self.position, self.positionLast)) - -- Add up travelled distance. - + -- Add up travelled distance. self.traveldist=self.traveldist+self.travelds -- Debug info. @@ -5632,7 +6132,16 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) self:T3(self.lid..string.format("Element %s: %s", Element.name, Element.status)) end - if newstatus==OPSGROUP.ElementStatus.SPAWNED then + if newstatus==OPSGROUP.ElementStatus.INUTERO then + --- + -- INUTERO + --- + + if self:_AllSimilarStatus(newstatus) then + self:__InUtero(-0.5) + end + + elseif newstatus==OPSGROUP.ElementStatus.SPAWNED then --- -- SPAWNED --- @@ -6048,6 +6557,102 @@ function OPSGROUP:_CoordinateFromObject(Object) return nil end +--- Add a unit/element to the OPS group. +-- @param #OPSGROUP self +-- @param #string unitname Name of unit. +-- @return #OPSGROUP.Element The element or nil. +function OPSGROUP:_AddElementByName(unitname) + + local unit=UNIT:FindByName(unitname) + + if unit then + + -- TODO: this is wrong when grouping is used! + local unittemplate=unit:GetTemplate() + + + local element={} --#OPSGROUP.Element + + -- Name and status. + element.name=unitname + element.status=OPSGROUP.ElementStatus.INUTERO + + -- Unit and group. + element.unit=unit + element.DCSunit=Unit.getByName(unitname) + element.group=unit:GetGroup() + + -- Skill etc. + element.skill=unittemplate.skill or "Unknown" + if element.skill=="Client" or element.skill=="Player" then + element.ai=false + element.client=CLIENT:FindByName(unitname) + else + element.ai=true + end + + -- Descriptors and type/category. + element.descriptors=unit:GetDesc() + self:I({desc=element.descriptors}) + + element.category=unit:GetUnitCategory() + element.categoryname=unit:GetCategoryName() + element.typename=unit:GetTypeName() + + -- Ammo. + element.ammo0=self:GetAmmoUnit(unit, false) + + -- Life points. + element.life=unit:GetLife() + element.life0=math.max(unit:GetLife0(), element.life) -- Some units report a life0 that is smaller than its initial life points. + + -- Size and dimensions. + element.size, element.length, element.height, element.width=unit:GetObjectSize() + + -- Weight and cargo. + element.weightEmpty=element.descriptors.massEmpty or 666 + element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+10*95 --If max mass is not given, we assume 10 soldiers. + element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) + element.weightCargo=0 + element.weight=element.weightEmpty+element.weightCargo + + + -- FLIGHTGROUP specific. + if self.isFlightgroup then + element.callsign=element.unit:GetCallsign() + element.modex=unittemplate.onboard_num + element.payload=unittemplate.payload + element.pylons=unittemplate.payload and unittemplate.payload.pylons or nil + element.fuelmass0=unittemplate.payload and unittemplate.payload.fuel or 0 + element.fuelmass=element.fuelmass0 + element.fuelrel=element.unit:GetFuel() + end + + -- Debug text. + local text=string.format("Adding element %s: status=%s, skill=%s, life=%.1f/%.1f category=%s (%d), type=%s, size=%.1f (L=%.1f H=%.1f W=%.1f), weight=%.1f/%.1f (cargo=%.1f/%.1f)", + element.name, element.status, element.skill, element.life, element.life0, element.categoryname, element.category, element.typename, + element.size, element.length, element.height, element.width, element.weight, element.weightMaxTotal, element.weightCargo, element.weightMaxCargo) + self:I(self.lid..text) + + -- Debug text. + --local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s", + --element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel*100, element.category, element.categoryname, element.callsign, tostring(element.ai)) + --self:T(self.lid..text) + + -- Add element to table. + table.insert(self.elements, element) + + -- Trigger spawned event if alive. + if unit:IsAlive() then + self:ElementSpawned(element) + end + + return element + end + + return nil +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 6b91e686680606660841d22e06c414fa24241e5a Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 26 Jan 2021 12:39:49 +0100 Subject: [PATCH 006/141] OPS Cargo --- Moose Development/Moose/Core/Database.lua | 17 +- Moose Development/Moose/Ops/ArmyGroup.lua | 33 ++- Moose Development/Moose/Ops/FlightGroup.lua | 40 +--- Moose Development/Moose/Ops/NavyGroup.lua | 13 +- Moose Development/Moose/Ops/OpsGroup.lua | 251 +++++++++++++++++++- 5 files changed, 295 insertions(+), 59 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 5997f05ca..ecb2d28e1 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1420,19 +1420,18 @@ function DATABASE:SetPlayerSettings( PlayerName, Settings ) self.PLAYERSETTINGS[PlayerName] = Settings end ---- Add a flight group to the data base. +--- Add an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) to the data base. -- @param #DATABASE self --- @param Ops.FlightGroup#FLIGHTGROUP flightgroup -function DATABASE:AddFlightGroup(flightgroup) - self:T({NewFlightGroup=flightgroup.groupname}) - self.FLIGHTGROUPS[flightgroup.groupname]=flightgroup +-- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group added to the DB. +function DATABASE:AddOpsGroup(opsgroup) + self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup end ---- Get a flight group from the data base. +--- Get an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) from the data base. -- @param #DATABASE self --- @param #string groupname Group name of the flight group. Can also be passed as GROUP object. --- @return Ops.FlightGroup#FLIGHTGROUP Flight group object. -function DATABASE:GetFlightGroup(groupname) +-- @param #string groupname Group name of the group. Can also be passed as GROUP object. +-- @return Ops.OpsGroup#OPSGROUP OPS group object. +function DATABASE:GetOpsGroup(groupname) -- Get group and group name. if type(groupname)=="string" then diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 5a9570e75..2a085c6a9 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -91,12 +91,19 @@ ARMYGROUP.version="0.4.0" --- Create a new ARMYGROUP class object. -- @param #ARMYGROUP self --- @param Wrapper.Group#GROUP Group The group object. Can also be given by its group name as `#string`. +-- @param Wrapper.Group#GROUP group The GROUP object. Can also be given by its group name as `#string`. -- @return #ARMYGROUP self -function ARMYGROUP:New(Group) +function ARMYGROUP:New(group) + + -- First check if we already have an OPS group for this group. + local og=_DATABASE:GetOpsGroup(group) + if og then + og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) + return og + end -- Inherit everything from FSM class. - local self=BASE:Inherit(self, OPSGROUP:New(Group)) -- #ARMYGROUP + local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #ARMYGROUP -- Set some string id for output to DCS.log file. self.lid=string.format("ARMYGROUP %s | ", self.groupname) @@ -264,6 +271,26 @@ function ARMYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clo return task end +--- Add a *scheduled* task to transport group(s). +-- @param #ARMYGROUP self +-- @param Core.Set#SET_GROUP GroupSet Set of cargo groups. Can also be a singe @{Wrapper.Group#GROUP} object. +-- @param Core.Zone#ZONE PickupZone Zone where the cargo is picked up. +-- @param Core.Zone#ZONE DeployZone Zone where the cargo is delivered to. +-- @param #string Clock Time when to start the attack. +-- @param #number Prio Priority of the task. +-- @return Ops.OpsGroup#OPSGROUP.Task The task table. +function ARMYGROUP:AddTaskCargoGroup(GroupSet, PickupZone, DeployZone, Clock, Prio) + + local DCStask={} + DCStask.id="CargoTransport" + DCStask.params={} + DCStask.params.cargoqueu=1 + + local task=self:AddTask(DCStask, Clock, nil, Prio) + + return task +end + --- Define a set of possible retreat zones. -- @param #ARMYGROUP self -- @param Core.Set#SET_ZONE RetreatZoneSet The retreat zone set. Default is an empty set. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 4ae70e3da..b4bc02f7f 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -221,10 +221,10 @@ FLIGHTGROUP.version="0.6.1" function FLIGHTGROUP:New(group) -- First check if we already have a flight group for this group. - local fg=_DATABASE:GetFlightGroup(group) - if fg then - fg:I(fg.lid..string.format("WARNING: Flight group already exists in data base!")) - return fg + local og=_DATABASE:GetOpsGroup(group) + if og then + og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) + return og end -- Inherit everything from FSM class. @@ -304,9 +304,6 @@ function FLIGHTGROUP:New(group) -- TODO: Add pseudo functions. - -- Add to data base. - _DATABASE:AddFlightGroup(self) - -- Handle events: self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.EngineStartup, self.OnEventEngineStartup) @@ -2143,7 +2140,7 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp wp[#wp+1]=p0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {TaskArrived, TaskHold, TaskKlar}, "Holding Point") -- Approach point: 10 NN in direction of runway. - if airbase:GetAirbaseCategory()==Airbase.Category.AIRDROME then + if airbase:IsAirdrome() then --- -- Airdrome @@ -2156,7 +2153,7 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp local pland=airbase:GetCoordinate():Translate(x2, runway.heading-180):SetAltitude(h2) wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), airbase, {}, "Landing") - elseif airbase:GetAirbaseCategory()==Airbase.Category.SHIP then + elseif airbase:IsShip() then --- -- Ship @@ -2169,33 +2166,12 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp if self.isAI then - local routeto=false - if fc or world.event.S_EVENT_KILL then - routeto=true - end - -- Clear all tasks. -- Warning, looks like this can make DCS CRASH! Had this after calling RTB once passed the final waypoint. --self:ClearTasks() - - -- Respawn? - if routeto then - -- Just route the group. Respawn might happen when going from holding to final. - self:Route(wp, 1) - - else - - -- Get group template. - local Template=self.group:GetTemplate() - - -- Set route points. - Template.route.points=wp - - --Respawn the group with new waypoints. - self:Respawn(Template) - - end + -- Just route the group. Respawn might happen when going from holding to final. + self:Route(wp, 1) end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index a2b64b9eb..782788c2f 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -107,12 +107,19 @@ NAVYGROUP.version="0.5.0" --- Create a new NAVYGROUP class object. -- @param #NAVYGROUP self --- @param #string GroupName Name of the group. +-- @param Wrapper.Group#GROUP group The group object. Can also be given by its group name as `#string`. -- @return #NAVYGROUP self -function NAVYGROUP:New(GroupName) +function NAVYGROUP:New(group) + + -- First check if we already have an OPS group for this group. + local og=_DATABASE:GetOpsGroup(group) + if og then + og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) + return og + end -- Inherit everything from FSM class. - local self=BASE:Inherit(self, OPSGROUP:New(GroupName)) -- #NAVYGROUP + local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #NAVYGROUP -- Set some string id for output to DCS.log file. self.lid=string.format("NAVYGROUP %s | ", self.groupname) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 2a0e9e157..c9af6e1da 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -97,6 +97,8 @@ -- -- @field #OPSGROUP.Element carrier Carrier the group is loaded into as cargo. -- @field #table cargo Table containing all cargo of the carrier. +-- @field #table cargoqueue Table containing cargo groups to be transported. +-- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. -- -- @extends Core.Fsm#FSM @@ -153,6 +155,7 @@ OPSGROUP = { Nkills = 0, weaponData = {}, cargo = {}, + cargoqueue = {}, } @@ -355,12 +358,21 @@ OPSGROUP.TaskType={ -- @field Wrapper.Marker#MARKER marker Marker on the F10 map. -- @field #string formation Ground formation. Similar to action but on/off road. ---- Cargo data. +--- Cargo status. -- @type OPSGROUP.CargoStatus +-- @field #string Waiting -- @field #string Reserved -- @field #string Loaded +-- @field #string Delivered ---- Cargo data. +--- Cargo transport data. +-- @type OPSGROUP.CargoTransport +-- @field #table cargos Cargos. Each element is a #OPSGROUP.Cargo +-- @field #string status Status of the cargo group. +-- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. +-- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. + +--- Cargo group data. -- @type OPSGROUP.Cargo -- @field #OPSGROUP opsgroup The cargo opsgroup. -- @field #OPSGROUP.CargoStatus status Status of the cargo group. @@ -386,20 +398,20 @@ OPSGROUP.version="0.7.1" --- Create a new OPSGROUP class object. -- @param #OPSGROUP self --- @param Wrapper.Group#GROUP Group The group object. Can also be given by its group name as `#string`. +-- @param Wrapper.Group#GROUP group The GROUP object. Can also be given by its group name as `#string`. -- @return #OPSGROUP self -function OPSGROUP:New(Group) +function OPSGROUP:New(group) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #OPSGROUP -- Get group and group name. - if type(Group)=="string" then - self.groupname=Group + if type(group)=="string" then + self.groupname=group self.group=GROUP:FindByName(self.groupname) else - self.group=Group - self.groupname=Group:GetName() + self.group=group + self.groupname=group:GetName() end -- Set some string id for output to DCS.log file. @@ -504,12 +516,13 @@ function OPSGROUP:New(Group) self:AddTransition("InUtero", "Unboard", "*") -- Group was unloaded from a cargo carrier. self:AddTransition("*", "Pickup", "Pickingup") -- Carrier and is on route to pick up cargo. - self:AddTransition("Pickingup", "Loading", "Loading") -- Carrier is loading cargo. + self:AddTransition("*", "Loading", "Loading") -- Carrier is loading cargo. self:AddTransition("Loading", "Load", "Loading") -- Carrier loads cargo into carrier. + self:AddTransition("Loading", "Loaded", "Loaded") -- Carrier loads cargo into carrier. self:AddTransition("Loading", "Pickup", "Pickingup") -- Carrier is picking up another cargo. - self:AddTransition("Loading", "Transport", "Transporting")-- Carrier is transporting cargo. + self:AddTransition("Loading", "Transport", "*") -- Carrier is transporting cargo. self:AddTransition("Transporting", "Dropoff", "Droppingoff") -- Carrier is dropping off cargo. self:AddTransition("Droppingoff", "Dropoff", "Droppingoff") -- Carrier is dropping off cargo. @@ -538,6 +551,10 @@ function OPSGROUP:New(Group) -- TODO: Add pseudo functions. + + -- Add to data base. + _DATABASE:AddOpsGroup(self) + return self end @@ -4339,6 +4356,189 @@ end -- Cargo Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Create a +-- @param #OPSGROUP self +-- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. +-- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @return #OPSGROUP.CargoTransport Cargo transport. +function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone) + + local transport={} --#OPSGROUP.CargoTransport + + transport.pickupzone=Pickupzone + transport.deployzone=Deployzone + transport.id=1 + transport.status="Planning" + + transport.cargos={} + if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then + local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + env.info("FF adding cargo group "..cargo.opsgroup:GetName()) + table.insert(transport.cargos, cargo) + else + for _,group in pairs(GroupSet.Set) do + local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + table.insert(transport.cargos, cargo) + end + end + + return transport +end + +--- Create a +-- @param #OPSGROUP self +-- @param Wrapper.Group#GROUP group The GROUP object. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. +-- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @return #OPSGROUP.Cargo Cargo data. +function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) + + local opsgroup=nil + + if group:IsInstanceOf("OPSGROUP") then + opsgroup=group + else + + opsgroup=_DATABASE:GetOpsGroup(group) + + if not opsgroup then + if group:IsAir() then + opsgroup=FLIGHTGROUP:New(group) + elseif group:IsShip() then + opsgroup=NAVYGROUP:New(group) + else + opsgroup=ARMYGROUP:New(group) + end + + end + + end + + local cargo={} --#OPSGROUP.Cargo + + cargo.opsgroup=opsgroup + cargo.status="Unknown" + cargo.pickupzone=Pickupzone + cargo.deployzone=Deployzone + + return cargo +end + +--- On after "Load" event. Carrier loads a cargo group into ints cargo bay. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE Zone Pickup zone. +function OPSGROUP:onafterPickup(From, Event, To, Zone) + + env.info("FF pickup") + + local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) + + if inzone then + + -- We are already in the pickup zone ==> initiate loading. + self:Loading() + + else + + -- Get a random coordinate in the pickup zone and let the carrier go there. + local Coordinate=Zone:GetRandomCoordinate() + + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + waypoint.detour=true + + end + +end + +--- Get total weight of the group including cargo. +-- @param #OPSGROUP self +-- @return #number Total weight in kg. +function OPSGROUP:GetWeightTotal() + + local weight=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + weight=weight+element.weight + end + + end + + return weight +end + +--- Get weight of the internal cargo the group is carriing right now. +-- @param #OPSGROUP self +-- @return #number Cargo weight in kg. +function OPSGROUP:GetWeightCargo() + + local weight=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + weight=weight+element.weightCargo + end + + end + + return weight +end + + +--- On after "Loading" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterLoading(From, Event, To) + + env.info("FF loading") + + --- Find a carrier which can load a given weight. + local function _findCarrier(weight) + local carrier=nil --#OPSGROUP.Element + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + local cargobay=element.weightMaxCargo-element.weightCargo + + if cargobay>=weight then + return element + end + + end + + return nil + end + + + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --#OPSGROUP.Cargo + + local weight=cargo.opsgroup:GetWeightTotal() + + local carrier=_findCarrier(weight) + + if carrier then + + -- Order cargo group to board the carrier. + cargo.opsgroup:Board(carrier) + + else + + env.info("FF cannot board carrier") + + end + end + +end + --- On after "Load" event. Carrier loads a cargo group into ints cargo bay. -- @param #OPSGROUP self -- @param #string From From state. @@ -4398,7 +4598,28 @@ function OPSGROUP:onafterUnload(From, Event, To, CargoGroup, Coordinate, Heading end ---- On after "Stop" event. +--- +-- Cargo Group Functions +--- + +--- On after "Board" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.Element Carrier The OPSGROUP element +function OPSGROUP:onafterBoard(From, Event, To, Carrier) + + env.info("FF board") + + self.carrier=Carrier + + self:Embark(Carrier) + +end + + +--- On after "Embark" event. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. @@ -5201,10 +5422,16 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Trigger Retreated event. opsgroup:Retreated() + elseif opsgroup:Is("Pickingup") then + --TODO: make IsPickingup() function. + + opsgroup:FullStop() + opsgroup:Loading() + elseif opsgroup:IsEngaging() then -- Nothing to do really. - + else -- Trigger DetourReached event. From 242462b9ba636d0ee9fc7c89788635ec6f829391 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 29 Jan 2021 00:32:38 +0100 Subject: [PATCH 007/141] OPS Cargo --- Moose Development/Moose/Ops/ArmyGroup.lua | 63 ++-- Moose Development/Moose/Ops/OpsGroup.lua | 385 ++++++++++++++++------ 2 files changed, 316 insertions(+), 132 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 2a085c6a9..78d060050 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -469,6 +469,35 @@ function ARMYGROUP:onafterStatus(From, Event, To) end self:I(self.lid..text) end + + + --- + -- Cargo + --- + + if self.cargoTransport then + + local text=string.format("Cargo: %s %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) + + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + local name=cargo.opsgroup:GetName() + local gstatus=cargo.opsgroup:GetState() + local cstatus=cargo.opsgroup.cargoStatus + text=text..string.format("\n- %s [%s]: %s", name, gstatus, cstatus) + + if self:IsPickingup() then + + elseif self:IsLoading() then + + elseif self:IsLoaded() then + + end + + end + + self:I(self.lid..text) + end --- @@ -870,40 +899,6 @@ function ARMYGROUP:onafterRetreated(From, Event, To) end ---- On after "Board" event. --- @param #ARMYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP.Element Carrier to board. -function ARMYGROUP:onafterBoard(From, Event, To, Carrier, Formation) - - local Coordinate=Carrier.unit:GetCoordinate() - - local Speed=UTILS.KmphToKnots(self.speedMax*0.2) - - local waypoint=self:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, true) - - -end - ---- On after "Pickup" event. --- @param #ARMYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP.Element Carrier to board. -function ARMYGROUP:onafterBoard(From, Event, To, Carrier, Formation) - - local Coordinate=Carrier.unit:GetCoordinate() - - local Speed=UTILS.KmphToKnots(self.speedMax*0.2) - - local waypoint=self:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, true) - - -end - --- On after "EngageTarget" event. -- @param #ARMYGROUP self -- @param #string From From state. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c9af6e1da..366bbc5ff 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -99,6 +99,8 @@ -- @field #table cargo Table containing all cargo of the carrier. -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. +-- @field #string cargoStatus Cargo status of this group acting as cargo. +-- @field #string carrierStatus Carrier status of this group acting as cargo carrier. -- -- @extends Core.Fsm#FSM @@ -358,24 +360,50 @@ OPSGROUP.TaskType={ -- @field Wrapper.Marker#MARKER marker Marker on the F10 map. -- @field #string formation Ground formation. Similar to action but on/off road. +--- Cargo Carrier status. +-- @type OPSGROUP.CarrierStatus +-- @field #string EMPTY Carrier is empty and ready for cargo transport. +-- @field #string PICKUP Carrier is on its way to pickup cargo. +-- @field #string LOADING Carrier is loading cargo. +-- @field #string LOADED Carrier has loaded cargo. +-- @field #string TRANSPORTING Carrier is transporting cargo. +OPSGROUP.CarrierStatus={ + EMPTY="empty", + PICKUP="pickup", + LOADING="loading", + LOADED="loaded", + TRANSPORTING="transporting", +} + --- Cargo status. -- @type OPSGROUP.CargoStatus --- @field #string Waiting --- @field #string Reserved --- @field #string Loaded --- @field #string Delivered +-- @field #string NOTCARGO This group is no cargo yet. +-- @field #string WAITING Cargo is awaiting transporter. +-- @field #string ASSIGNED Cargo is assigned to a carrier. +-- @field #string BOARDING Cargo is boarding a carrier. +-- @field #string LOADED Cargo is loaded into a carrier. +-- @field #string DELIVERED Cargo was delivered at its destination. +OPSGROUP.CargoStatus={ + NOTCARGO="not cargo", + WAITING="waiting for carrier", + ASSIGNED="assigned to carrier", + BOARDING="boarding", + LOADED="loaded", + DELIVERED="delivered", +} + --- Cargo transport data. -- @type OPSGROUP.CargoTransport --- @field #table cargos Cargos. Each element is a #OPSGROUP.Cargo --- @field #string status Status of the cargo group. +-- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. +-- @field #string status Status of the carrier. See @{#OPSGROUP.CarrierStatus}. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. --- Cargo group data. --- @type OPSGROUP.Cargo +-- @type OPSGROUP.CargoGroup -- @field #OPSGROUP opsgroup The cargo opsgroup. --- @field #OPSGROUP.CargoStatus status Status of the cargo group. +-- @field #string status Status of the cargo group. See @{#OPSGROUP.CargoStatus}. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. @@ -439,6 +467,8 @@ function OPSGROUP:New(group) self.spot.timer=TIMER:New(self._UpdateLaser, self) self.spot.Coordinate=COORDINATE:New(0, 0, 0) self:SetLaser(1688, true, false, 0.5) + self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + self.carrierStatus=OPSGROUP.CarrierStatus.EMPTY -- Init task counter. self.taskcurrent=0 @@ -515,18 +545,12 @@ function OPSGROUP:New(group) self:AddTransition("*", "Embark", "*") -- Group was loaded into a cargo carrier. self:AddTransition("InUtero", "Unboard", "*") -- Group was unloaded from a cargo carrier. - self:AddTransition("*", "Pickup", "Pickingup") -- Carrier and is on route to pick up cargo. - self:AddTransition("*", "Loading", "Loading") -- Carrier is loading cargo. - self:AddTransition("Loading", "Load", "Loading") -- Carrier loads cargo into carrier. - self:AddTransition("Loading", "Loaded", "Loaded") -- Carrier loads cargo into carrier. - - self:AddTransition("Loading", "Pickup", "Pickingup") -- Carrier is picking up another cargo. - - self:AddTransition("Loading", "Transport", "*") -- Carrier is transporting cargo. - - self:AddTransition("Transporting", "Dropoff", "Droppingoff") -- Carrier is dropping off cargo. - self:AddTransition("Droppingoff", "Dropoff", "Droppingoff") -- Carrier is dropping off cargo. - self:AddTransition("Droppingoff", "Droppedoff", "Droppedoff") -- Carrier has dropped off all its cargo. + self:AddTransition("*", "Pickup", "*") -- Carrier and is on route to pick up cargo. + self:AddTransition("*", "Loading", "*") -- Carrier is loading cargo. + self:AddTransition("*", "Load", "*") -- Carrier loads cargo into carrier. + self:AddTransition("*", "Loaded", "*") -- Carrier loaded all assigned cargo into carrier. + self:AddTransition("*", "Transport", "*") -- Carrier is transporting cargo. + self:AddTransition("*", "Deploy", "*") -- Carrier is dropping off cargo. ------------------------ --- Pseudo Functions --- @@ -1495,6 +1519,37 @@ function OPSGROUP:IsEngaging() return self:is("Engaging") end +--- Check if the group is picking up cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is picking up. +function OPSGROUP:IsPickingup() + return self.carrierStatus==OPSGROUP.CarrierStatus.PICKUP +end + + +--- Check if the group is picking up cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is picking up. +function OPSGROUP:IsLoading() + return self.carrierStatus==OPSGROUP.CarrierStatus.LOADING +end + +--- Check if the group is picking up cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is picking up. +function OPSGROUP:IsLoaded() + return self.carrierStatus==OPSGROUP.CarrierStatus.LOADED +end + +--- Check if the group is picking up cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is picking up. +function OPSGROUP:IsTransporting() + return self.carrierStatus==OPSGROUP.CarrierStatus.TRANSPORTING +end + + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -4356,30 +4411,45 @@ end -- Cargo Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a +--- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. -- @param Core.Zone#ZONE Pickupzone Pickup zone. -- @param Core.Zone#ZONE Deployzone Deploy zone. -- @return #OPSGROUP.CargoTransport Cargo transport. +function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone) + + local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone) + + table.insert(self.cargoqueue, cargotransport) + + return cargotransport +end + +--- Create a cargo transport assignment. +-- @param #OPSGROUP self +-- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. +-- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @return #OPSGROUP.CargoTransport Cargo transport. function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone) local transport={} --#OPSGROUP.CargoTransport transport.pickupzone=Pickupzone transport.deployzone=Deployzone - transport.id=1 - transport.status="Planning" - + transport.uid=1 + transport.status="Planning" transport.cargos={} + if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) env.info("FF adding cargo group "..cargo.opsgroup:GetName()) - table.insert(transport.cargos, cargo) + table.insert(transport.cargos, cargo) else for _,group in pairs(GroupSet.Set) do - local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) - table.insert(transport.cargos, cargo) + local cargo=self:CreateCargoGroupData(group, Pickupzone, Deployzone) + table.insert(transport.cargos, cargo) end end @@ -4391,7 +4461,7 @@ end -- @param Wrapper.Group#GROUP group The GROUP object. -- @param Core.Zone#ZONE Pickupzone Pickup zone. -- @param Core.Zone#ZONE Deployzone Deploy zone. --- @return #OPSGROUP.Cargo Cargo data. +-- @return #OPSGROUP.CargoGroup Cargo group data. function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) local opsgroup=nil @@ -4425,18 +4495,83 @@ function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) return cargo end ---- On after "Load" event. Carrier loads a cargo group into ints cargo bay. +--- Get total weight of the group including cargo. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @return #number Total weight in kg. +function OPSGROUP:GetWeightTotal(UnitName) + + local weight=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then + + weight=weight+element.weight + + end + + end + + return weight +end + +--- Get weight of the internal cargo the group is carriing right now. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @return #number Cargo weight in kg. +function OPSGROUP:GetWeightCargo(UnitName) + + local weight=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then + + weight=weight+element.weightCargo + + end + + end + + return weight +end + + +--- Create a cargo transport assignment. +-- @param #OPSGROUP self +-- @param #OPSGROUP.CargoGroup CargoGroup Cargo group object. +-- @return #OPSGROUP self +function OPSGROUP:_AddCargoGroup(CargoGroup) + + --CargoGroup.status=OPSGROUP.CargoStatus. + + table.insert(self.cargo, CargoGroup) + + return self +end + +--- On after "Pickup" event. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Zone#ZONE Zone Pickup zone. -function OPSGROUP:onafterPickup(From, Event, To, Zone) +-- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport assignment. +function OPSGROUP:onafterPickup(From, Event, To, CargoTransport) env.info("FF pickup") + + -- Set carrier status. + self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP + + local Zone=CargoTransport.pickupzone local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) + self.cargoTransport=CargoTransport + + + if inzone then -- We are already in the pickup zone ==> initiate loading. @@ -4454,52 +4589,21 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) end ---- Get total weight of the group including cargo. --- @param #OPSGROUP self --- @return #number Total weight in kg. -function OPSGROUP:GetWeightTotal() - - local weight=0 - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - - if element and element.status~=OPSGROUP.ElementStatus.DEAD then - weight=weight+element.weight - end - - end - - return weight -end - ---- Get weight of the internal cargo the group is carriing right now. --- @param #OPSGROUP self --- @return #number Cargo weight in kg. -function OPSGROUP:GetWeightCargo() - - local weight=0 - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - - if element and element.status~=OPSGROUP.ElementStatus.DEAD then - weight=weight+element.weightCargo - end - - end - - return weight -end - - --- On after "Loading" event. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function OPSGROUP:onafterLoading(From, Event, To) - +-- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport assignment. +function OPSGROUP:onafterLoading(From, Event, To, CargoTransport) env.info("FF loading") + -- Set carrier status. + self.carrierStatus=OPSGROUP.CarrierStatus.LOADING + + + CargoTransport=CargoTransport or self.cargoTransport + --- Find a carrier which can load a given weight. local function _findCarrier(weight) local carrier=nil --#OPSGROUP.Element @@ -4518,8 +4622,8 @@ function OPSGROUP:onafterLoading(From, Event, To) end - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --#OPSGROUP.Cargo + for _,_cargo in pairs(CargoTransport.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup local weight=cargo.opsgroup:GetWeightTotal() @@ -4528,7 +4632,8 @@ function OPSGROUP:onafterLoading(From, Event, To) if carrier then -- Order cargo group to board the carrier. - cargo.opsgroup:Board(carrier) + env.info("FF order group to board carrier") + cargo.opsgroup:Board(self, carrier) else @@ -4546,23 +4651,12 @@ end -- @param #string To To state. -- @param #OPSGROUP CargoGroup The OPSGROUP loaded as cargo. function OPSGROUP:onafterLoad(From, Event, To, CargoGroup) - env.info("FF load") - local weight=500 + local weight=CargoGroup:GetWeightTotal() + + local carrier=CargoGroup.carrier - local carrier=nil --#OPSGROUP.Element - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - - local cargobay=element.weightMaxCargo-element.weightCargo - - if cargobay>=weight then - carrier=element - break - end - - end if carrier then @@ -4575,12 +4669,91 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup) -- Embark ==> Loaded CargoGroup:Embark(carrier) + env.info("FF carrier loaded (todo)") + self:Loaded() + else - self:E(self.lid.."Cound not find a carrier with enough cargo capacity!") + self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") end end +--- On after "Loaded" event. Carrier has loaded all (possible) cargo at the pickup zone. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterLoaded(From, Event, To) + env.info("FF loaded") + + --TODO: analyze current cargo for deploy zones and initiate transport. + + env.info("FF ordering carrier to transport") + self:Transport(self.cargoTransport.deployzone) + + +end + +--- On after "Transport" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE Zone Deploy zone. +function OPSGROUP:onafterTransport(From, Event, To, Zone) + env.info("FF transport") + + -- Set carrier status. + self.carrierStatus=OPSGROUP.CarrierStatus.TRANSPORTING + + -- Check if already in deploy zone. + local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) + + if inzone then + + -- We are already in the pickup zone ==> initiate loading. + self:Deploy() + + else + + -- Get a random coordinate in the pickup zone and let the carrier go there. + local Coordinate=Zone:GetRandomCoordinate() + + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + waypoint.detour=true + + end + +end + +--- On after "Transport" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE Zone Deploy zone. +function OPSGROUP:onafterDeploy(From, Event, To, Zone) + env.info("FF deploy at zone ".. (Zone and Zone:GetName() or "Not given!")) + + for _,_cargo in pairs(self.cargo) do + local cargo=_cargo --#OPSGROUP.CargoGroup + + local zone=Zone or cargo.deployzone + + if zone:IsCoordinateInZone(self:GetCoordinate()) then + + local Coordinate=zone:GetRandomCoordinate() + local Heading=math.random(0,359) + + -- Unload. + self:Unload(cargo.opsgroup, Coordinate, Heading) + + end + + end + +end + --- On after "Unload" event. Carrier unloads a cargo group from its cargo bay. -- @param #OPSGROUP self -- @param #string From From state. @@ -4591,7 +4764,7 @@ end -- @param #number Heading Heading of group. function OPSGROUP:onafterUnload(From, Event, To, CargoGroup, Coordinate, Heading) - --TODO: Add check if CargoGroup is + --TODO: Add check if CargoGroup is cargo of this carrier. if CargoGroup:IsInUtero() then CargoGroup:Unboard(Coordinate, Heading) end @@ -4607,14 +4780,20 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. +-- @param #OPSGROUP CarrierGroup The carrier group. -- @param #OPSGROUP.Element Carrier The OPSGROUP element -function OPSGROUP:onafterBoard(From, Event, To, Carrier) - +function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) env.info("FF board") - - self.carrier=Carrier - self:Embark(Carrier) + -- Set cargo status. + self.cargoStatus=OPSGROUP.CargoStatus.BOARDING + + -- Set carrier. + self.carrier=Carrier + self.carrierGroup=CarrierGroup + + -- Trigger embark event. + self.carrierGroup:Load(self) end @@ -4626,8 +4805,10 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Carrier The OPSGROUP element function OPSGROUP:onafterEmbark(From, Event, To, Carrier) - env.info("FF embark") + + -- Set cargo status. + self.cargoStatus=OPSGROUP.CargoStatus.LOADED -- Despawn this group. self:Despawn(0, true) @@ -4644,8 +4825,10 @@ end -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. Can also be a DCS#Vec3 object. -- @param #number Heading Heading the group has in degrees. Default is last known heading of the group. function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) - env.info("FF Unboard") + + -- Set cargo status. + self.cargoStatus=OPSGROUP.CargoStatus.DELIVERED -- Template for the respawned group. local Template=UTILS.DeepCopy(self.template) @@ -5422,12 +5605,18 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Trigger Retreated event. opsgroup:Retreated() - elseif opsgroup:Is("Pickingup") then - --TODO: make IsPickingup() function. + elseif opsgroup:IsPickingup() then opsgroup:FullStop() + opsgroup:Loading() + elseif opsgroup:IsTransporting() then + + opsgroup:FullStop() + + opsgroup:Deploy() + elseif opsgroup:IsEngaging() then -- Nothing to do really. From bcf8973eede4c6c2fc82faed08489776202366fc Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 30 Jan 2021 21:29:29 +0100 Subject: [PATCH 008/141] OPS --- Moose Development/Moose/Ops/ArmyGroup.lua | 68 ++++++++++++---- Moose Development/Moose/Ops/FlightGroup.lua | 13 ++- Moose Development/Moose/Ops/OpsGroup.lua | 90 ++++++++++++++------- 3 files changed, 123 insertions(+), 48 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 78d060050..95a622aa5 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -353,10 +353,10 @@ end function ARMYGROUP:onbeforeStatus(From, Event, To) if self:IsDead() then - self:T(self.lid..string.format("Onbefore Status DEAD ==> false")) + self:I(self.lid..string.format("Onbefore Status DEAD ==> false")) return false elseif self:IsStopped() then - self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) + self:I(self.lid..string.format("Onbefore Status STOPPED ==> false")) return false end @@ -476,7 +476,7 @@ function ARMYGROUP:onafterStatus(From, Event, To) --- if self.cargoTransport then - + local text=string.format("Cargo: %s %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) for _,_cargo in pairs(self.cargoTransport.cargos) do @@ -484,19 +484,57 @@ function ARMYGROUP:onafterStatus(From, Event, To) local name=cargo.opsgroup:GetName() local gstatus=cargo.opsgroup:GetState() local cstatus=cargo.opsgroup.cargoStatus - text=text..string.format("\n- %s [%s]: %s", name, gstatus, cstatus) - - if self:IsPickingup() then - - elseif self:IsLoading() then - - elseif self:IsLoaded() then - - end - + text=text..string.format("\n- %s [%s]: %s", name, gstatus, cstatus) end - self:I(self.lid..text) + self:I(self.lid..text) + + + if self:IsNotCarrier() then + + env.info("FF not carrier ==> pickup") + + -- Initiate the cargo transport process. + self:Pickup(self.cargoTransport.pickupzone) + + elseif self:IsPickingup() then + + env.info("FF picking up") + + elseif self:IsLoading() then + + env.info("FF loading") + + local boarding=false + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if cargo.opsgroup.carrierGroup:GetName()==self:GetName() and cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.BOARDING then + boarding=true + end + + end + + -- Boarding finished ==> Transport cargo. + if not boarding then + env.info("FF boarding finished ==> Loaded") + self:Loaded() + end + + elseif self:IsLoaded() then + + env.info("FF loaded") + + elseif self:IsTransporting() then + + env.info("FF transporting") + + elseif self:IsUnloading() then + + env.info("FF unloading") + + end + end @@ -1190,7 +1228,7 @@ function ARMYGROUP:_InitGroup() self.actype=units[1]:GetTypeName() -- Debug info. - if self.verbose>=1 then + if self.verbose>=0 then local text=string.format("Initialized Army Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index b4bc02f7f..0fd9130d6 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1010,6 +1010,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- Engage Detected Targets --- if self:IsAirborne() and self.detectionOn and self.engagedetectedOn and not (self.fuellow or self.fuelcritical) then + env.info("FF 100") -- Target. local targetgroup=nil --Wrapper.Group#GROUP @@ -1019,8 +1020,12 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) for _,_group in pairs(self.detectedgroups:GetSet()) do local group=_group --Wrapper.Group#GROUP + env.info("FF 200") + if group and group:IsAlive() then + env.info("FF 300") + -- Get 3D vector of target. local targetVec3=group:GetVec3() @@ -1029,11 +1034,13 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if distance<=self.engagedetectedRmax and distance1 then -- Route group to all defined waypoints remaining. - self:Route(wp, 1) + self:Route(wp) else diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 366bbc5ff..9341875a9 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -362,17 +362,21 @@ OPSGROUP.TaskType={ --- Cargo Carrier status. -- @type OPSGROUP.CarrierStatus +-- @field #string NOTCARRIER This group is not a carrier yet. -- @field #string EMPTY Carrier is empty and ready for cargo transport. -- @field #string PICKUP Carrier is on its way to pickup cargo. -- @field #string LOADING Carrier is loading cargo. -- @field #string LOADED Carrier has loaded cargo. -- @field #string TRANSPORTING Carrier is transporting cargo. +-- @field #string UNLOADING Carrier is unloading cargo. OPSGROUP.CarrierStatus={ + NOTCARRIER="not carrier", EMPTY="empty", PICKUP="pickup", LOADING="loading", LOADED="loaded", TRANSPORTING="transporting", + UNLOADING="unloading", } --- Cargo status. @@ -468,7 +472,7 @@ function OPSGROUP:New(group) self.spot.Coordinate=COORDINATE:New(0, 0, 0) self:SetLaser(1688, true, false, 0.5) self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - self.carrierStatus=OPSGROUP.CarrierStatus.EMPTY + self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER -- Init task counter. self.taskcurrent=0 @@ -1519,6 +1523,13 @@ function OPSGROUP:IsEngaging() return self:is("Engaging") end +--- Check if the group is not a carrier yet. +-- @param #OPSGROUP self +-- @return #boolean If true, group is not a carrier. +function OPSGROUP:IsNotCarrier() + return self.carrierStatus==OPSGROUP.CarrierStatus.NOTCARRIER +end + --- Check if the group is picking up cargo. -- @param #OPSGROUP self -- @return #boolean If true, group is picking up. @@ -1548,7 +1559,12 @@ function OPSGROUP:IsTransporting() return self.carrierStatus==OPSGROUP.CarrierStatus.TRANSPORTING end - +--- Check if the group is unloading cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is unloading. +function OPSGROUP:IsUnloading() + return self.carrierStatus==OPSGROUP.CarrierStatus.UNLOADING +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoint Functions @@ -4556,22 +4572,16 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport assignment. -function OPSGROUP:onafterPickup(From, Event, To, CargoTransport) +-- @param Core.Zone#ZONE Zone Pickup zone. +function OPSGROUP:onafterPickup(From, Event, To, Zone) env.info("FF pickup") -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP - - local Zone=CargoTransport.pickupzone local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) - - self.cargoTransport=CargoTransport - - - + if inzone then -- We are already in the pickup zone ==> initiate loading. @@ -4594,16 +4604,12 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport assignment. -function OPSGROUP:onafterLoading(From, Event, To, CargoTransport) +function OPSGROUP:onafterLoading(From, Event, To) env.info("FF loading") -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.LOADING - - CargoTransport=CargoTransport or self.cargoTransport - --- Find a carrier which can load a given weight. local function _findCarrier(weight) local carrier=nil --#OPSGROUP.Element @@ -4622,7 +4628,7 @@ function OPSGROUP:onafterLoading(From, Event, To, CargoTransport) end - for _,_cargo in pairs(CargoTransport.cargos) do + for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup local weight=cargo.opsgroup:GetWeightTotal() @@ -4631,6 +4637,9 @@ function OPSGROUP:onafterLoading(From, Event, To, CargoTransport) if carrier then + -- Set cargo status. + cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED + -- Order cargo group to board the carrier. env.info("FF order group to board carrier") cargo.opsgroup:Board(self, carrier) @@ -4664,13 +4673,13 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup) carrier.weightCargo=carrier.weightCargo+weight -- This function is only really used for aircraft and sets the total internal cargo weight. - trigger.action.setUnitInternalCargo(carrier.name, carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + --trigger.action.setUnitInternalCargo(carrier.name, carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo -- Embark ==> Loaded CargoGroup:Embark(carrier) - env.info("FF carrier loaded (todo)") - self:Loaded() + --env.info("FF carrier loaded (todo)") + --self:Loaded() else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") @@ -4734,21 +4743,27 @@ end -- @param Core.Zone#ZONE Zone Deploy zone. function OPSGROUP:onafterDeploy(From, Event, To, Zone) env.info("FF deploy at zone ".. (Zone and Zone:GetName() or "Not given!")) + + self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING - for _,_cargo in pairs(self.cargo) do + for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - local zone=Zone or cargo.deployzone + env.info("FF deploy cargo "..cargo.opsgroup:GetName()) + + local zone=Zone or cargo.deployzone --Core.Zone#ZONE - if zone:IsCoordinateInZone(self:GetCoordinate()) then + --if zone:IsCoordinateInZone(self:GetCoordinate()) then local Coordinate=zone:GetRandomCoordinate() local Heading=math.random(0,359) -- Unload. - self:Unload(cargo.opsgroup, Coordinate, Heading) + env.info("FF unload cargo "..cargo.opsgroup:GetName()) + cargo.opsgroup:Unboard(Coordinate, Heading) + --self:Unload(cargo.opsgroup, Coordinate, Heading) - end + --end end @@ -4792,8 +4807,15 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self.carrier=Carrier self.carrierGroup=CarrierGroup + -- Add to current cargo of carrier. + --CarrierGroup:_AddCargoGroup(self) + + --TODO: make cargo run to carrier + --TODO: check if cargo is mobile. if not ==> load + --TODO: check if cargo is alive=true. if only exists ==> load. + -- Trigger embark event. - self.carrierGroup:Load(self) + self.carrierGroup:__Load(10, self) end @@ -4827,11 +4849,12 @@ end function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) env.info("FF Unboard") - -- Set cargo status. - self.cargoStatus=OPSGROUP.CargoStatus.DELIVERED - -- Template for the respawned group. local Template=UTILS.DeepCopy(self.template) + + env.info("FF template") + self:I({template=Template}) + self:I({template=self.template}) -- Loop over template units. for _,Unit in pairs(Template.units) do @@ -4866,7 +4889,14 @@ function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) self.carrier.weightCargo=self.carrier.weightCargo-element.weight end -- This function is only really used for aircraft and sets the total internal cargo weight. - trigger.action.setUnitInternalCargo(self.carrier.name, self.carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + if self.isFlightgroup then + trigger.action.setUnitInternalCargo(self.carrier.name, self.carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + end + + -- Set cargo status. + self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + self.carrier=nil + self.carrierGroup=nil -- Respawn group. self:_Respawn(0, Template) From 016198e7f22d979fcd220a6e7f07e9b009b29161 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 31 Jan 2021 23:28:04 +0100 Subject: [PATCH 009/141] OPS Cargo --- Moose Development/Moose/Core/Database.lua | 2 + Moose Development/Moose/Ops/ArmyGroup.lua | 42 +++++-- Moose Development/Moose/Ops/FlightGroup.lua | 7 +- Moose Development/Moose/Ops/NavyGroup.lua | 3 + Moose Development/Moose/Ops/OpsGroup.lua | 122 +++++++++++++++----- 5 files changed, 133 insertions(+), 43 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index ecb2d28e1..8ae42a151 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1424,6 +1424,7 @@ end -- @param #DATABASE self -- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group added to the DB. function DATABASE:AddOpsGroup(opsgroup) + env.info("Adding OPSGROUP "..tostring(opsgroup.groupname)) self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup end @@ -1439,6 +1440,7 @@ function DATABASE:GetOpsGroup(groupname) groupname=groupname:GetName() end + env.info("Getting OPSGROUP "..tostring(groupname)) return self.FLIGHTGROUPS[groupname] end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 95a622aa5..93df801d9 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -171,6 +171,9 @@ function ARMYGROUP:New(group) -- Start check zone timer. self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 30) + + -- Add OPSGROUP to _DATABASE. + _DATABASE:AddOpsGroup(self) return self end @@ -477,22 +480,24 @@ function ARMYGROUP:onafterStatus(From, Event, To) if self.cargoTransport then - local text=string.format("Cargo: %s %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) - + -- Debug + local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup local name=cargo.opsgroup:GetName() local gstatus=cargo.opsgroup:GetState() local cstatus=cargo.opsgroup.cargoStatus - text=text..string.format("\n- %s [%s]: %s", name, gstatus, cstatus) - end - + text=text..string.format("\n- %s [%s]: %s", name, gstatus, cstatus) + --self:I({template=cargo.opsgroup.template}) + end self:I(self.lid..text) if self:IsNotCarrier() then env.info("FF not carrier ==> pickup") + + --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. -- Initiate the cargo transport process. self:Pickup(self.cargoTransport.pickupzone) @@ -500,6 +505,8 @@ function ARMYGROUP:onafterStatus(From, Event, To) elseif self:IsPickingup() then env.info("FF picking up") + + --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. elseif self:IsLoading() then @@ -509,7 +516,7 @@ function ARMYGROUP:onafterStatus(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - if cargo.opsgroup.carrierGroup:GetName()==self:GetName() and cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.BOARDING then + if cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup:GetName()==self:GetName() and cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.BOARDING then boarding=true end @@ -523,15 +530,32 @@ function ARMYGROUP:onafterStatus(From, Event, To) elseif self:IsLoaded() then - env.info("FF loaded") + env.info("FF loaded (nothing to do?)") elseif self:IsTransporting() then - env.info("FF transporting") + env.info("FF transporting (nothing to do)") elseif self:IsUnloading() then - env.info("FF unloading") + env.info("FF unloading ==> Checking if all cargo was delivered") + + local delivered=true + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if (cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup:GetName()==self:GetName()) and not cargo.delivered then + delivered=false + break + end + + end + + -- Boarding finished ==> Transport cargo. + if delivered then + env.info("FF unloading finished ==> Unloaded") + self:Unloaded() + end end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 7417a35cc..9cefad402 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -332,6 +332,9 @@ function FLIGHTGROUP:New(group) -- Start check zone timer. self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(3, 10) + -- Add OPSGROUP to _DATABASE. + _DATABASE:AddOpsGroup(self) + return self end @@ -2603,9 +2606,7 @@ function FLIGHTGROUP:onafterStop(From, Event, To) -- Call OPSGROUP function. self:GetParent(self).onafterStop(self, From, Event, To) - - -- Remove flight from data base. - _DATABASE.FLIGHTGROUPS[self.groupname]=nil + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 782788c2f..5cf389465 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -188,6 +188,9 @@ function NAVYGROUP:New(group) -- Start check zone timer. self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 60) + + -- Add OPSGROUP to _DATABASE. + _DATABASE:AddOpsGroup(self) return self end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8b13d0d86..ef76d7532 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -410,6 +410,7 @@ OPSGROUP.CargoStatus={ -- @field #string status Status of the cargo group. See @{#OPSGROUP.CargoStatus}. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. +-- @field #boolean delivered If true, group was delivered. --- OpsGroup version. -- @field #string version @@ -555,6 +556,7 @@ function OPSGROUP:New(group) self:AddTransition("*", "Loaded", "*") -- Carrier loaded all assigned cargo into carrier. self:AddTransition("*", "Transport", "*") -- Carrier is transporting cargo. self:AddTransition("*", "Deploy", "*") -- Carrier is dropping off cargo. + self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its cargo. ------------------------ --- Pseudo Functions --- @@ -581,7 +583,7 @@ function OPSGROUP:New(group) -- Add to data base. - _DATABASE:AddOpsGroup(self) + --_DATABASE:AddOpsGroup(self) return self end @@ -4419,8 +4421,11 @@ function OPSGROUP:onafterStop(From, Event, To) self:E(self.lid..text) end + -- Remove flight from data base. + _DATABASE.FLIGHTGROUPS[self.groupname]=nil + -- Debug output. - self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") + self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database") end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -4496,12 +4501,13 @@ function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) else opsgroup=ARMYGROUP:New(group) end - + else + env.info("FF found opsgroup in createcargo") end end - local cargo={} --#OPSGROUP.Cargo + local cargo={} --#OPSGROUP.CargoGroup cargo.opsgroup=opsgroup cargo.status="Unknown" @@ -4631,22 +4637,29 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - local weight=cargo.opsgroup:GetWeightTotal() + if not cargo.delivered then - local carrier=_findCarrier(weight) - - if carrier then - - -- Set cargo status. - cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED - - -- Order cargo group to board the carrier. - env.info("FF order group to board carrier") - cargo.opsgroup:Board(self, carrier) + local weight=cargo.opsgroup:GetWeightTotal() - else - - env.info("FF cannot board carrier") + local carrier=_findCarrier(weight) + + if carrier then + + -- Set cargo status. + cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED + + -- Order cargo group to board the carrier. + env.info("FF order group to board carrier") + cargo.opsgroup:Board(self, carrier) + + --TODO: only one group for testing + break + + else + + env.info("FF cannot board carrier") + + end end end @@ -4744,26 +4757,33 @@ end function OPSGROUP:onafterDeploy(From, Event, To, Zone) env.info("FF deploy at zone ".. (Zone and Zone:GetName() or "Not given!")) + -- Set carrier status to UNLOADING. self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - env.info("FF deploy cargo "..cargo.opsgroup:GetName()) - - local zone=Zone or cargo.deployzone --Core.Zone#ZONE + if cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.LOADED and cargo.opsgroup.carrierGroup:GetName()==self.groupname then - --if zone:IsCoordinateInZone(self:GetCoordinate()) then - - local Coordinate=zone:GetRandomCoordinate() - local Heading=math.random(0,359) + env.info("FF deploy cargo "..cargo.opsgroup:GetName()) + + local zone=Zone or cargo.deployzone --Core.Zone#ZONE - -- Unload. - env.info("FF unload cargo "..cargo.opsgroup:GetName()) - cargo.opsgroup:Unboard(Coordinate, Heading) - --self:Unload(cargo.opsgroup, Coordinate, Heading) - - --end + --if zone:IsCoordinateInZone(self:GetCoordinate()) then + + local Coordinate=zone:GetRandomCoordinate() + local Heading=math.random(0,359) + + cargo.delivered=true + + -- Unload. + env.info("FF unload cargo "..cargo.opsgroup:GetName()) + cargo.opsgroup:Unboard(Coordinate, Heading) + --self:Unload(cargo.opsgroup, Coordinate, Heading) + + --end + + end end @@ -4786,6 +4806,46 @@ function OPSGROUP:onafterUnload(From, Event, To, CargoGroup, Coordinate, Heading end +--- On after "Unloaded" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE Zone Deploy zone. +function OPSGROUP:onafterUnloaded(From, Event, To) + env.info("FF unloaded") + + local pickup=false + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + -- Check for waiting or undelivered non cargo groups. + --if cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.WAITING or (cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.NOTCARGO and not cargo.delivered) then + if not cargo.delivered then + pickup=true + break + end + + end + + if pickup then + + env.info("FF cargo left ==> pickup") + self:Pickup(self.cargoTransport.pickupzone) + + else + + -- No current transport assignment. + self.cargoTransport=nil + + env.info("FF all delivered ==> check group done") + self:_CheckGroupDone() + + end + +end + + --- -- Cargo Group Functions --- From ee3ead9aac4f9e07e4f66c4bad3cd7a2575834eb Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 2 Feb 2021 23:20:55 +0100 Subject: [PATCH 010/141] OPS Cargo --- Moose Development/Moose/DCS.lua | 18 +- Moose Development/Moose/Ops/ArmyGroup.lua | 87 +--- Moose Development/Moose/Ops/FlightGroup.lua | 206 ++++++++- Moose Development/Moose/Ops/OpsGroup.lua | 486 +++++++++++++++----- 4 files changed, 585 insertions(+), 212 deletions(-) diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 2d190d215..6824d9c4f 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -460,6 +460,22 @@ do -- Types --@field #boolean lateActivated --@field #boolean uncontrolled + --- DCS template data structure. + -- @type Template + -- @field #boolean uncontrolled Aircraft is uncontrolled. + -- @field #boolean lateActivation Group is late activated. + -- @field #number x 2D Position on x-axis in meters. + -- @field #number y 2D Position on y-axis in meters. + -- @field #table units Unit list. + -- + + --- Unit data structure. + --@type Template.Unit + --@field #string name Name of the unit. + --@field #number x + --@field #number y + --@field #number alt + end -- @@ -1452,4 +1468,4 @@ do -- AI AI = {} --#AI -end -- AI \ No newline at end of file +end -- AI diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 93df801d9..2cf8ab17a 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -464,8 +464,8 @@ function ARMYGROUP:onafterStatus(From, Event, To) local ammo=self:GetAmmoElement(element) -- Output text for element. - text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d", - i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles) + text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg", + i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles, element.weightCargo, element.weightMaxCargo) end if #self.elements==0 then text=text.." none!" @@ -478,88 +478,7 @@ function ARMYGROUP:onafterStatus(From, Event, To) -- Cargo --- - if self.cargoTransport then - - -- Debug - local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - local name=cargo.opsgroup:GetName() - local gstatus=cargo.opsgroup:GetState() - local cstatus=cargo.opsgroup.cargoStatus - text=text..string.format("\n- %s [%s]: %s", name, gstatus, cstatus) - --self:I({template=cargo.opsgroup.template}) - end - self:I(self.lid..text) - - - if self:IsNotCarrier() then - - env.info("FF not carrier ==> pickup") - - --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. - - -- Initiate the cargo transport process. - self:Pickup(self.cargoTransport.pickupzone) - - elseif self:IsPickingup() then - - env.info("FF picking up") - - --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. - - elseif self:IsLoading() then - - env.info("FF loading") - - local boarding=false - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - if cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup:GetName()==self:GetName() and cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.BOARDING then - boarding=true - end - - end - - -- Boarding finished ==> Transport cargo. - if not boarding then - env.info("FF boarding finished ==> Loaded") - self:Loaded() - end - - elseif self:IsLoaded() then - - env.info("FF loaded (nothing to do?)") - - elseif self:IsTransporting() then - - env.info("FF transporting (nothing to do)") - - elseif self:IsUnloading() then - - env.info("FF unloading ==> Checking if all cargo was delivered") - - local delivered=true - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - if (cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup:GetName()==self:GetName()) and not cargo.delivered then - delivered=false - break - end - - end - - -- Boarding finished ==> Transport cargo. - if delivered then - env.info("FF unloading finished ==> Unloaded") - self:Unloaded() - end - - end - - end + self:_CheckCargoTransport() --- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 9cefad402..c84cba1be 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -249,6 +249,7 @@ function FLIGHTGROUP:New(group) -- Add FSM transitions. -- From State --> Event --> To State + self:AddTransition("*", "LandAtAirbase", "Inbound") -- Helo group is ordered to land at a specific point. self:AddTransition("*", "RTB", "Inbound") -- Group is returning to destination base. self:AddTransition("*", "RTZ", "Inbound") -- Group is returning to destination zone. Not implemented yet! self:AddTransition("Inbound", "Holding", "Holding") -- Group is in holding pattern. @@ -795,7 +796,7 @@ function FLIGHTGROUP:onbeforeStatus(From, Event, To) local text=string.format("Element %s is dead at t=%.3f! Maybe despawned without notice or landed at a too small airbase. Calling ElementDead in 60 sec to give other events a chance", tostring(element.name), timer.getTime()) self:E(self.lid..text) - self:__ElementDead(60, element) + --self:__ElementDead(60, element) end end @@ -1013,7 +1014,6 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- Engage Detected Targets --- if self:IsAirborne() and self.detectionOn and self.engagedetectedOn and not (self.fuellow or self.fuelcritical) then - env.info("FF 100") -- Target. local targetgroup=nil --Wrapper.Group#GROUP @@ -1023,12 +1023,8 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) for _,_group in pairs(self.detectedgroups:GetSet()) do local group=_group --Wrapper.Group#GROUP - env.info("FF 200") - if group and group:IsAlive() then - env.info("FF 300") - -- Get 3D vector of target. local targetVec3=group:GetVec3() @@ -1037,8 +1033,6 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if distance<=self.engagedetectedRmax and distanceself.currentwp then + self.passedfinalwp=false + end + + -- Speed in knots. + Speed=Speed or self.speedCruise + + -- Get coordinate of airbase. + local Coordinate=Airbase:GetCoordinate() + + -- Create air waypoint. + local wp=Coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO,COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed, nil, Airbase, {}, "Landing Temp", nil) + + -- Create waypoint data table. + local waypoint=self:_CreateWaypoint(wp) + + -- Set altitude. + if Altitude then + waypoint.alt=UTILS.FeetToMeters(Altitude) + end + + -- Add waypoint to table. + self:_AddWaypoint(waypoint, wpnumber) + + -- Debug info. + self:T(self.lid..string.format("Adding AIR waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, Speed, self.currentwp, #self.waypoints)) + + -- Update route. + if Updateroute==nil or Updateroute==true then + self:__UpdateRoute(-1) + end + + return waypoint +end + --- Check if a unit is an element of the flightgroup. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ef76d7532..77de1541b 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -96,6 +96,7 @@ -- @field #OPSGROUP.WeaponData weaponData Weapon data table with key=BitType. -- -- @field #OPSGROUP.Element carrier Carrier the group is loaded into as cargo. +-- @field #OPSGROUP carrierGroup Carrier group transporting this group as cargo. -- @field #table cargo Table containing all cargo of the carrier. -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. @@ -546,7 +547,7 @@ function OPSGROUP:New(group) self:AddTransition("*", "ElementDead", "*") -- An element is dead. self:AddTransition("*", "ElementDamaged", "*") -- An element was damaged. - self:AddTransition("*", "Board", "Boarding") -- Group is boarding a cargo carrier. + self:AddTransition("*", "Board", "*") -- Group is boarding a cargo carrier. self:AddTransition("*", "Embark", "*") -- Group was loaded into a cargo carrier. self:AddTransition("InUtero", "Unboard", "*") -- Group was unloaded from a cargo carrier. @@ -556,6 +557,7 @@ function OPSGROUP:New(group) self:AddTransition("*", "Loaded", "*") -- Carrier loaded all assigned cargo into carrier. self:AddTransition("*", "Transport", "*") -- Carrier is transporting cargo. self:AddTransition("*", "Deploy", "*") -- Carrier is dropping off cargo. + self:AddTransition("*", "Unload", "*") -- Carrier unload a cargo group. self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its cargo. ------------------------ @@ -1255,6 +1257,9 @@ function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) if DCSGroup then + -- Clear any task ==> makes DCS crash! + --self.group:ClearTasks() + -- Get all units. local units=self:GetDCSUnits() @@ -1539,7 +1544,6 @@ function OPSGROUP:IsPickingup() return self.carrierStatus==OPSGROUP.CarrierStatus.PICKUP end - --- Check if the group is picking up cargo. -- @param #OPSGROUP self -- @return #boolean If true, group is picking up. @@ -1547,16 +1551,9 @@ function OPSGROUP:IsLoading() return self.carrierStatus==OPSGROUP.CarrierStatus.LOADING end ---- Check if the group is picking up cargo. +--- Check if the group is transporting cargo. -- @param #OPSGROUP self --- @return #boolean If true, group is picking up. -function OPSGROUP:IsLoaded() - return self.carrierStatus==OPSGROUP.CarrierStatus.LOADED -end - ---- Check if the group is picking up cargo. --- @param #OPSGROUP self --- @return #boolean If true, group is picking up. +-- @return #boolean If true, group is transporting. function OPSGROUP:IsTransporting() return self.carrierStatus==OPSGROUP.CarrierStatus.TRANSPORTING end @@ -1568,6 +1565,48 @@ function OPSGROUP:IsUnloading() return self.carrierStatus==OPSGROUP.CarrierStatus.UNLOADING end + +--- Check if the group is **not** cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is *not* cargo. +function OPSGROUP:IsNotCargo() + return self.cargoStatus==OPSGROUP.CargoStatus.NOTCARGO +end + +--- Check if the group is currently boarding a carrier. +-- @param #OPSGROUP self +-- @param #string CarrierGroupName (Optional) Additionally check if group is boarding this particular carrier group. +-- @return #boolean If true, group is boarding. +function OPSGROUP:IsBoarding(CarrierGroupName) + if CarrierGroupName then + if self.carrierGroup and self.carrierGroup.groupname~=CarrierGroupName then + return false + end + end + return self.cargoStatus==OPSGROUP.CargoStatus.BOARDING +end + +--- Check if the group is currently loaded into a carrier. +-- @param #OPSGROUP self +-- @param #string CarrierGroupName (Optional) Additionally check if group is loaded into this particular carrier group. +-- @return #boolean If true, group is loaded. +function OPSGROUP:IsLoaded(CarrierGroupName) + if CarrierGroupName then + if self.carrierGroup and self.carrierGroup.groupname~=CarrierGroupName then + return false + end + end + return self.cargoStatus==OPSGROUP.CargoStatus.LOADED +end + +--- Check if the group is cargo and waiting for a carrier to pick it up. +-- @param #OPSGROUP self +-- @return #boolean If true, group is waiting for a carrier. +function OPSGROUP:IsWaitingAsCargo() + return self.cargoStatus==OPSGROUP.CargoStatus.WAITING +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2562,9 +2601,14 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) if self.taskcurrent>0 then self:TaskCancel() end - + -- Set current task. self.taskcurrent=Task.id + + -- + if self:GetTaskCurrent()==nil then + table.insert(self.taskqueue, Task) + end -- Set time stamp. Task.timestamp=timer.getAbsTime() @@ -4432,6 +4476,111 @@ end -- Cargo Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Check cargo transport assignments. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:_CheckCargoTransport() + + if self.cargoTransport then + + -- TODO: Check if this group can actually transport any cargo. + + -- Debug info. + local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + local name=cargo.opsgroup:GetName() + local gstatus=cargo.opsgroup:GetState() + local cstatus=cargo.opsgroup.cargoStatus + local weight=cargo.opsgroup:GetWeightTotal() + text=text..string.format("\n- %s [%s]: %s (weight %.1f kg)", name, gstatus, cstatus, weight) + end + self:I(self.lid..text) + + + if self:IsNotCarrier() then + + env.info("FF not carrier ==> pickup") + + --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. + + -- Initiate the cargo transport process. + self:Pickup(self.cargoTransport.pickupzone) + + elseif self:IsPickingup() then + + env.info("FF picking up") + + --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. + + elseif self:IsLoading() then + + env.info("FF loading") + + local boarding=false + local gotcargo=false + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + -- Check if anyone is still boarding. + if cargo.opsgroup:IsBoarding(self.groupname) then + boarding=true + end + + -- Check if we have any cargo to transport. + if cargo.opsgroup:IsLoaded(self.groupname) then + gotcargo=true + end + + end + + -- Boarding finished ==> Transport cargo. + if gotcargo and not boarding then + env.info("FF boarding finished ==> Loaded") + self:Loaded() + end + + -- No cargo and noone is boarding ==> check again if we can make anyone board. + if not gotcargo and not boarding then + self:Loading() + end + + elseif self:IsLoaded() then + + env.info("FF loaded (nothing to do?)") + + elseif self:IsTransporting() then + + env.info("FF transporting (nothing to do)") + + elseif self:IsUnloading() then + + env.info("FF unloading ==> Checking if all cargo was delivered") + + local delivered=true + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if (cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup:GetName()==self:GetName()) and not cargo.delivered then + delivered=false + break + end + + end + + -- Boarding finished ==> Transport cargo. + if delivered then + env.info("FF unloading finished ==> Unloaded") + self:Unloaded() + end + + end + + end + + return self +end + --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. @@ -4474,6 +4623,12 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone) end end + local text=string.format("Created Cargo Transport (UID=%d) from %s -->%s", transport.uid, transport.pickupzone:GetName(), transport.deployzone:GetName()) + for _,_cargo in pairs(transport.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal()) + end + return transport end @@ -4536,7 +4691,7 @@ function OPSGROUP:GetWeightTotal(UnitName) end return weight -end +end --- Get weight of the internal cargo the group is carriing right now. -- @param #OPSGROUP self @@ -4559,6 +4714,41 @@ function OPSGROUP:GetWeightCargo(UnitName) return weight end +--- Add weight to the internal cargo of an element of the group. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @param #number Weight Cargo weight to be added in kg. +function OPSGROUP:AddWeightCargo(UnitName, Weight) + + local element=self:GetElementByName(UnitName) + + if element and element.unit and element.unit:IsAlive() then + + -- Add weight. + element.weightCargo=element.weightCargo+Weight + + -- For airborne units, we set the weight in game. + if self.isFlightgroup then + trigger.action.setUnitInternalCargo(element.name, element.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + end + + end + + return self +end + +--- Reduce weight to the internal cargo of an element of the group. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. +-- @param #number Weight Cargo weight to be reduced in kg. +function OPSGROUP:RedWeightCargo(UnitName, Weight) + + -- Reduce weight. + self:AddWeightCargo(UnitName, -Weight) + + return self +end + --- Create a cargo transport assignment. -- @param #OPSGROUP self @@ -4580,12 +4770,13 @@ end -- @param #string To To state. -- @param Core.Zone#ZONE Zone Pickup zone. function OPSGROUP:onafterPickup(From, Event, To, Zone) - - env.info("FF pickup") + -- Debug info. + self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.PICKUP)) -- Set carrier status. - self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP - + self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP + + -- Check if already in the pickup zone. local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) if inzone then @@ -4598,8 +4789,16 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Get a random coordinate in the pickup zone and let the carrier go there. local Coordinate=Zone:GetRandomCoordinate() - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) - waypoint.detour=true + --TODO: Add NAVYGROUP and FLIGHTGROUP waypoint + if self.isFlightgroup then + Coordinate:SetAltitude(200) + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) + waypoint.detour=true + else + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + waypoint.detour=true + end + end @@ -4611,25 +4810,29 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterLoading(From, Event, To) - env.info("FF loading") + -- Debug info. + self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.LOADING)) -- Set carrier status. - self.carrierStatus=OPSGROUP.CarrierStatus.LOADING + self.carrierStatus=OPSGROUP.CarrierStatus.LOADING + + -- Create a temp array and monitor the free cargo space for each element. + local cargobay={} + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + cargobay[element.name]=element.weightMaxCargo-element.weightCargo + end + --- Find a carrier which can load a given weight. local function _findCarrier(weight) local carrier=nil --#OPSGROUP.Element for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - - local cargobay=element.weightMaxCargo-element.weightCargo - - if cargobay>=weight then + local element=_element --#OPSGROUP.Element + if cargobay[element.name]>=weight then return element - end - - end - + end + end return nil end @@ -4637,30 +4840,37 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - if not cargo.delivered then + if cargo.opsgroup:IsNotCargo() and not cargo.delivered then - local weight=cargo.opsgroup:GetWeightTotal() + -- Check if cargo is in pickup zone. + local inzone=self.cargoTransport.pickupzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) - local carrier=_findCarrier(weight) + -- First check if cargo is not delivered yet. + if inzone then - if carrier then - - -- Set cargo status. - cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED - - -- Order cargo group to board the carrier. - env.info("FF order group to board carrier") - cargo.opsgroup:Board(self, carrier) + local weight=cargo.opsgroup:GetWeightTotal() - --TODO: only one group for testing - break + local carrier=_findCarrier(weight) - else - - env.info("FF cannot board carrier") + if carrier then + + -- Decrease free cargo bay. + cargobay[carrier.name]=cargobay[carrier.name]-weight + + -- Set cargo status. + cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED + + -- Order cargo group to board the carrier. + env.info("FF order group to board carrier") + cargo.opsgroup:Board(self, carrier) + + else + + env.info("FF cannot board carrier") + + end end - end end @@ -4682,18 +4892,12 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup) if carrier then - -- TODO: add function to set/add/get internal cargo. - carrier.weightCargo=carrier.weightCargo+weight - - -- This function is only really used for aircraft and sets the total internal cargo weight. - --trigger.action.setUnitInternalCargo(carrier.name, carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + -- Add weight to carrier. + self:AddWeightCargo(carrier.name, weight) -- Embark ==> Loaded CargoGroup:Embark(carrier) - - --env.info("FF carrier loaded (todo)") - --self:Loaded() - + else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") end @@ -4708,12 +4912,15 @@ end function OPSGROUP:onafterLoaded(From, Event, To) env.info("FF loaded") - --TODO: analyze current cargo for deploy zones and initiate transport. - - env.info("FF ordering carrier to transport") - self:Transport(self.cargoTransport.deployzone) - - + -- Cancel landedAt task. + if self.isFlightgroup and self:IsLandedAt() then + local Task=self:GetTaskCurrent() + self:TaskCancel(Task) + end + + -- Order group to transport. + self:__Transport(1, self.cargoTransport.deployzone) + end --- On after "Transport" event. @@ -4723,8 +4930,9 @@ end -- @param #string To To state. -- @param Core.Zone#ZONE Zone Deploy zone. function OPSGROUP:onafterTransport(From, Event, To, Zone) - env.info("FF transport") - + -- Debug info. + self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.TRANSPORTING)) + -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.TRANSPORTING @@ -4734,28 +4942,38 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) if inzone then -- We are already in the pickup zone ==> initiate loading. - self:Deploy() + self:Deploy(Zone) else -- Get a random coordinate in the pickup zone and let the carrier go there. local Coordinate=Zone:GetRandomCoordinate() - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) - waypoint.detour=true + --TODO: Add NAVYGROUP and FLIGHTGROUP waypoint + if self.isFlightgroup then + Coordinate:SetAltitude(200) + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) + waypoint.detour=true + local dist=self:GetCoordinate():Get2DDistance(waypoint.coordinate) + env.info(string.format("FF adding transport detour wp with uid=%d at dist=%d m", waypoint.uid, dist)) + else + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + waypoint.detour=true + end end end ---- On after "Transport" event. +--- On after "Deploy" event. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Core.Zone#ZONE Zone Deploy zone. function OPSGROUP:onafterDeploy(From, Event, To, Zone) - env.info("FF deploy at zone ".. (Zone and Zone:GetName() or "Not given!")) + -- Debug info. + self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.UNLOADING)) -- Set carrier status to UNLOADING. self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING @@ -4763,7 +4981,8 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - if cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.LOADED and cargo.opsgroup.carrierGroup:GetName()==self.groupname then + -- Check that cargo is loaded into this group. + if cargo.opsgroup:IsLoaded(self.groupname) then env.info("FF deploy cargo "..cargo.opsgroup:GetName()) @@ -4774,12 +4993,12 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) local Coordinate=zone:GetRandomCoordinate() local Heading=math.random(0,359) + -- Cargo was delivered. cargo.delivered=true -- Unload. env.info("FF unload cargo "..cargo.opsgroup:GetName()) - cargo.opsgroup:Unboard(Coordinate, Heading) - --self:Unload(cargo.opsgroup, Coordinate, Heading) + self:Unload(cargo.opsgroup, Coordinate, Heading) --end @@ -4794,14 +5013,14 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #OPSGROUP CargoGroup The OPSGROUP loaded as cargo. +-- @param #OPSGROUP OpsGroup The OPSGROUP loaded as cargo. -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. -- @param #number Heading Heading of group. -function OPSGROUP:onafterUnload(From, Event, To, CargoGroup, Coordinate, Heading) +function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading) --TODO: Add check if CargoGroup is cargo of this carrier. - if CargoGroup:IsInUtero() then - CargoGroup:Unboard(Coordinate, Heading) + if OpsGroup:IsInUtero() then + OpsGroup:Unboard(Coordinate, Heading) end end @@ -4815,6 +5034,12 @@ end function OPSGROUP:onafterUnloaded(From, Event, To) env.info("FF unloaded") + -- Cancel landedAt task. + if self.isFlightgroup and self:IsLandedAt() then + local Task=self:GetTaskCurrent() + self:TaskCancel(Task) + end + local pickup=false for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup @@ -4839,7 +5064,7 @@ function OPSGROUP:onafterUnloaded(From, Event, To) self.cargoTransport=nil env.info("FF all delivered ==> check group done") - self:_CheckGroupDone() + self:_CheckGroupDone(0.1) end @@ -4858,7 +5083,8 @@ end -- @param #OPSGROUP CarrierGroup The carrier group. -- @param #OPSGROUP.Element Carrier The OPSGROUP element function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) - env.info("FF board") + -- Debug info. + self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.BOARDING)) -- Set cargo status. self.cargoStatus=OPSGROUP.CargoStatus.BOARDING @@ -4874,8 +5100,20 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) --TODO: check if cargo is mobile. if not ==> load --TODO: check if cargo is alive=true. if only exists ==> load. - -- Trigger embark event. - self.carrierGroup:__Load(10, self) + if self.speedMax>0 then + + local Coordinate=Carrier.unit:GetCoordinate() + + --TODO: NAVYGROUP and FLIGHTGROUP + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + waypoint.detour=true + + else + + -- Trigger Load event in 10 seconds. + self.carrierGroup:__Load(10, self) + + end end @@ -4887,7 +5125,8 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Carrier The OPSGROUP element function OPSGROUP:onafterEmbark(From, Event, To, Carrier) - env.info("FF embark") + -- Debug info. + self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.LOADED)) -- Set cargo status. self.cargoStatus=OPSGROUP.CargoStatus.LOADED @@ -4907,14 +5146,12 @@ end -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. Can also be a DCS#Vec3 object. -- @param #number Heading Heading the group has in degrees. Default is last known heading of the group. function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) - env.info("FF Unboard") + -- Debug info. + self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) + -- Template for the respawned group. - local Template=UTILS.DeepCopy(self.template) - - env.info("FF template") - self:I({template=Template}) - self:I({template=self.template}) + local Template=UTILS.DeepCopy(self.template) --DCS#Template -- Loop over template units. for _,Unit in pairs(Template.units) do @@ -4942,16 +5179,10 @@ function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) end end - + -- Reduce carrier weight. - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - self.carrier.weightCargo=self.carrier.weightCargo-element.weight - end - -- This function is only really used for aircraft and sets the total internal cargo weight. - if self.isFlightgroup then - trigger.action.setUnitInternalCargo(self.carrier.name, self.carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo - end + local weight=self:GetWeightTotal() + self.carrierGroup:RedWeightCargo(self.carrier.name, weight) -- Set cargo status. self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO @@ -5669,7 +5900,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Debug message. local text=string.format("Group passing waypoint uid=%d", uid) - opsgroup:T(opsgroup.lid..text) + opsgroup:I(opsgroup.lid..text) -- Trigger PassingWaypoint event. if waypoint.astar then @@ -5697,15 +5928,48 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif opsgroup:IsPickingup() then - opsgroup:FullStop() + if opsgroup.isFlightgroup then - opsgroup:Loading() + -- Land at current pos and wait for 60 min max. + opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) + else + -- Stop and loading. + opsgroup:FullStop() + opsgroup:Loading() + end + elseif opsgroup:IsTransporting() then - - opsgroup:FullStop() + + if opsgroup.isFlightgroup then + + env.info("FF passing waypointg in state istransporting ==> land at") - opsgroup:Deploy() + -- Land at current pos and wait for 60 min max. + opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) + + else + -- Stop and loading. + opsgroup:FullStop() + opsgroup:Deploy() + end + + elseif opsgroup:IsBoarding() then + + if opsgroup.carrierGroup and opsgroup.carrierGroup:IsAlive() then + + if opsgroup.carrier and opsgroup.carrier.unit and opsgroup.carrier.unit:IsAlive() then + + -- Load group into the carrier. + opsgroup.carrierGroup:Load(opsgroup) + + else + opsgroup:E(opsgroup.lid.."ERROR: Group cannot board assigned carrier UNIT as it is NOT alive!") + end + + else + opsgroup:E(opsgroup.lid.."ERROR: Group cannot board assigned carrier GROUP as it is NOT alive!") + end elseif opsgroup:IsEngaging() then @@ -6644,7 +6908,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__InUtero(-0.5) + self:InUtero() end elseif newstatus==OPSGROUP.ElementStatus.SPAWNED then @@ -6761,13 +7025,17 @@ end -- @return #OPSGROUP.Element The element. function OPSGROUP:GetElementByName(unitname) - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element + if unitname and type(unitname)=="string" then - if element.name==unitname then - return element + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if element.name==unitname then + return element + end + end - + end return nil @@ -7117,7 +7385,7 @@ function OPSGROUP:_AddElementByName(unitname) -- Weight and cargo. element.weightEmpty=element.descriptors.massEmpty or 666 - element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+10*95 --If max mass is not given, we assume 10 soldiers. + element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+2*95 --If max mass is not given, we assume 10 soldiers. element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) element.weightCargo=0 element.weight=element.weightEmpty+element.weightCargo From 7d83c251a8c0a80dc8c30c09c10f920192a52944 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 3 Feb 2021 22:51:18 +0100 Subject: [PATCH 011/141] OPS Cargo --- Moose Development/Moose/Ops/FlightGroup.lua | 31 +- Moose Development/Moose/Ops/NavyGroup.lua | 58 ++-- Moose Development/Moose/Ops/OpsGroup.lua | 298 ++++++++++++++------ 3 files changed, 241 insertions(+), 146 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index c84cba1be..efef49eee 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -57,7 +57,6 @@ -- @field #number Tparking Abs. mission time stamp when the group was spawned uncontrolled and is parking. -- @field #table menu F10 radio menu. -- @field #string controlstatus Flight control status. --- @field #boolean ishelo If true, the is a helicopter group. -- @field #number callsignName Callsign name. -- @field #number callsignNumber Callsign number. -- @field #boolean despawnAfterLanding If true, group is despawned after landed at an airbase. @@ -146,7 +145,7 @@ FLIGHTGROUP = { Tholding = nil, Tparking = nil, menu = nil, - ishelo = nil, + isHelo = nil, } @@ -1004,7 +1003,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) --- -- Airboss Helo --- - if self.ishelo and self.airboss and self:IsHolding() then + if self.isHelo and self.airboss and self:IsHolding() then if self.airboss:IsRecovering() or self:IsFuelCritical() then self:ClearToLand() end @@ -1445,7 +1444,7 @@ function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) else -- Helos with skids land directly on parking spots. - if self.ishelo then + if self.isHelo then local Spot=self:GetParkingSpot(Element, 10, airbase) @@ -2204,8 +2203,8 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Defaults: SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise) - SpeedHold=SpeedHold or (self.ishelo and 80 or 250) - SpeedLand=SpeedLand or (self.ishelo and 40 or 170) + SpeedHold=SpeedHold or (self.isHelo and 80 or 250) + SpeedLand=SpeedLand or (self.isHelo and 40 or 170) -- Clear holding time in any case. self.Tholding=nil @@ -2214,7 +2213,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) local text=string.format("Flight group set to hold at airbase %s. SpeedTo=%d, SpeedHold=%d, SpeedLand=%d", airbase:GetName(), SpeedTo, SpeedHold, SpeedLand) self:T(self.lid..text) - local althold=self.ishelo and 1000+math.random(10)*100 or math.random(4,10)*1000 + local althold=self.isHelo and 1000+math.random(10)*100 or math.random(4,10)*1000 -- Holding points. local c0=self.group:GetCoordinate() @@ -2244,8 +2243,8 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) end -- Altitude above ground for a glide slope of 3 degrees. - local x1=self.ishelo and UTILS.NMToMeters(5.0) or UTILS.NMToMeters(10) - local x2=self.ishelo and UTILS.NMToMeters(2.5) or UTILS.NMToMeters(5) + local x1=self.isHelo and UTILS.NMToMeters(5.0) or UTILS.NMToMeters(10) + local x2=self.isHelo and UTILS.NMToMeters(2.5) or UTILS.NMToMeters(5) local alpha=math.rad(3) local h1=x1*math.tan(alpha) local h2=x2*math.tan(alpha) @@ -2365,8 +2364,8 @@ end function FLIGHTGROUP:onafterWait(From, Event, To, Coord, Altitude, Speed) Coord=Coord or self.group:GetCoordinate() - Altitude=Altitude or (self.ishelo and 1000 or 10000) - Speed=Speed or (self.ishelo and 80 or 250) + Altitude=Altitude or (self.isHelo and 1000 or 10000) + Speed=Speed or (self.isHelo and 80 or 250) -- Debug message. local text=string.format("Flight group set to wait/orbit at altitude %d m and speed %.1f km/h", Altitude, Speed) @@ -2464,7 +2463,7 @@ function FLIGHTGROUP:onafterHolding(From, Event, To) elseif self.airboss then - if self.ishelo then + if self.isHelo then local carrierpos=self.airboss:GetCoordinate() local carrierheading=self.airboss:GetHeading() @@ -2578,7 +2577,7 @@ end -- @param Core.Point#COORDINATE Coordinate The coordinate where to land. Default is current position. -- @param #number Duration The duration in seconds to remain on ground. Default 600 sec (10 min). function FLIGHTGROUP:onbeforeLandAt(From, Event, To, Coordinate, Duration) - return self.ishelo + return self.isHelo end --- On after "LandAt" event. Order helicopter to land at a specific point. @@ -2804,7 +2803,7 @@ function FLIGHTGROUP:_InitGroup() self.isGround=false -- Helo group. - self.ishelo=group:IsHelicopter() + self.isHelo=group:IsHelicopter() -- Is (template) group uncontrolled. self.isUncontrolled=self.template.uncontrolled @@ -2816,7 +2815,7 @@ function FLIGHTGROUP:_InitGroup() self.speedMax=group:GetSpeedMax() -- Cruise speed limit 350 kts for fixed and 80 knots for rotary wings. - local speedCruiseLimit=self.ishelo and UTILS.KnotsToKmph(80) or UTILS.KnotsToKmph(350) + local speedCruiseLimit=self.isHelo and UTILS.KnotsToKmph(80) or UTILS.KnotsToKmph(350) -- Cruise speed: 70% of max speed but within limit. self.speedCruise=math.min(self.speedMax*0.7, speedCruiseLimit) @@ -2844,7 +2843,7 @@ function FLIGHTGROUP:_InitGroup() self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) -- Set default formation. - if self.ishelo then + if self.isHelo then self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300 else self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 5cf389465..00b1c3de0 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -609,6 +609,11 @@ function NAVYGROUP:onafterStatus(From, Event, To) end + --- + -- Cargo + --- + + self:_CheckCargoTransport() --- -- Tasks & Missions @@ -1164,50 +1169,19 @@ function NAVYGROUP:_InitGroup() -- Get all units of the group. local units=self.group:GetUnits() - for _,_unit in pairs(units) do - local unit=_unit --Wrapper.Unit#UNIT - - -- Get unit template. - local unittemplate=unit:GetTemplate() - - local element={} --#NAVYGROUP.Element - element.name=unit:GetName() - element.unit=unit - element.status=OPSGROUP.ElementStatus.INUTERO - element.typename=unit:GetTypeName() - element.skill=unittemplate.skill or "Unknown" - element.ai=true - element.category=element.unit:GetUnitCategory() - element.categoryname=element.unit:GetCategoryName() - element.size, element.length, element.height, element.width=unit:GetObjectSize() - element.ammo0=self:GetAmmoUnit(unit, false) - - -- Debug text. - if self.verbose>=2 then - local text=string.format("Adding element %s: status=%s, skill=%s, category=%s (%d), size: %.1f (L=%.1f H=%.1f W=%.1f)", - element.name, element.status, element.skill, element.categoryname, element.category, element.size, element.length, element.height, element.width) - self:I(self.lid..text) - end - - -- Add element to table. - table.insert(self.elements, element) - - -- Get Descriptors. - self.descriptors=self.descriptors or unit:GetDesc() - - -- Set type name. - self.actype=self.actype or unit:GetTypeName() - - if unit:IsAlive() then - -- Trigger spawned event. - self:ElementSpawned(element) - end - + -- Add elemets. + for _,unit in pairs(units) do + self:_AddElementByName(unit:GetName()) end - - + + -- Get Descriptors. + self.descriptors=units[1]:GetDesc() + + -- Set type name. + self.actype=units[1]:GetTypeName() + -- Debug info. - if self.verbose>=1 then + if self.verbose>=0 then local text=string.format("Initialized Navy Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 77de1541b..082e4ae7c 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -25,6 +25,7 @@ -- @field #boolean isFlightgroup Is a FLIGHTGROUP. -- @field #boolean isArmygroup Is an ARMYGROUP. -- @field #boolean isNavygroup Is a NAVYGROUP. +-- @field #boolean isHelo If true, the is a helicopter group. -- @field #table elements Table of elements, i.e. units of the group. -- @field #boolean isAI If true, group is purely AI. -- @field #boolean isAircraft If true, group is airplane or helicopter. @@ -97,7 +98,6 @@ -- -- @field #OPSGROUP.Element carrier Carrier the group is loaded into as cargo. -- @field #OPSGROUP carrierGroup Carrier group transporting this group as cargo. --- @field #table cargo Table containing all cargo of the carrier. -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. -- @field #string cargoStatus Cargo status of this group acting as cargo. @@ -157,7 +157,6 @@ OPSGROUP = { Ndestroyed = 0, Nkills = 0, weaponData = {}, - cargo = {}, cargoqueue = {}, } @@ -402,8 +401,13 @@ OPSGROUP.CargoStatus={ -- @type OPSGROUP.CargoTransport -- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. -- @field #string status Status of the carrier. See @{#OPSGROUP.CarrierStatus}. +-- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). +-- @field #number importance Importance of this transport. Smaller=higher. +-- @field #number Tstart Start time in abs. seconds. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. +-- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. +-- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. --- Cargo group data. -- @type OPSGROUP.CargoGroup @@ -3513,7 +3517,6 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) elseif self.isArmygroup then NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) end - else @@ -4298,7 +4301,7 @@ function OPSGROUP:onafterRespawn(From, Event, To, Template) --self.respawning=true - self:_Respawn(0, template, Reset) + self:_Respawn(0, template) end @@ -4314,7 +4317,7 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) self:ScheduleOnce(Delay, OPSGROUP._Respawn, self, 0, Template, Reset) else - env.info("FF _Respawn") + self:I(self.lid.."FF _Respawn") -- Given template or get old. Template=Template or UTILS.DeepCopy(self.template) @@ -4390,9 +4393,7 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) end - -- Currently respawning. - --self.respawning=true - + -- Debug output. self:I({Template=Template}) -- Spawn new group. @@ -4481,26 +4482,93 @@ end -- @return #OPSGROUP self function OPSGROUP:_CheckCargoTransport() + -- Abs. missin time in seconds. + local Time=timer.getAbsTime() + if self.cargoTransport then - -- TODO: Check if this group can actually transport any cargo. + + local done=true + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if cargo.delivered then + -- This one is delivered. + elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + -- This one is dead. + else + done=false --Someone is not done! + end + + --TODO: check if cargo is too heavy for this carrier group ==> elseif + + end + + if done then + self.cargoTransport.status="Delivered" + end + + end + + -- Check if there is anything in the queue. + if not self.cargoTransport then + + -- Current position. + local coord=self:GetCoordinate() + + -- Sort results table wrt prio and distance to pickup zone. + local function _sort(a, b) + local transportA=a --#OPSGROUP.CargoTransport + local transportB=b --#OPSGROUP.CargoTransport + local distA=transportA.pickupzone:GetCoordinate():Get2DDistance(coord) + local distB=transportB.pickupzone:GetCoordinate():Get2DDistance(coord) + return (transportA.prio=cargotransport.Tstart and cargotransport.status~="Delivered" and (cargotransport.importance==nil or cargotransport.importance<=vip) then + self.cargoTransport=cargotransport + break + end + + end + + end + + -- Now handle the transport. + if self.cargoTransport then -- Debug info. - local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - local name=cargo.opsgroup:GetName() - local gstatus=cargo.opsgroup:GetState() - local cstatus=cargo.opsgroup.cargoStatus - local weight=cargo.opsgroup:GetWeightTotal() - text=text..string.format("\n- %s [%s]: %s (weight %.1f kg)", name, gstatus, cstatus, weight) - end - self:I(self.lid..text) + if self.verbose>=0 then + local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + local name=cargo.opsgroup:GetName() + local gstatus=cargo.opsgroup:GetState() + local cstatus=cargo.opsgroup.cargoStatus + local weight=cargo.opsgroup:GetWeightTotal() + text=text..string.format("\n- %s (%.1f kg) [%s]: %s delivered=%s", name, weight, gstatus, cstatus, tostring(cargo.delivered)) + end + self:I(self.lid..text) + end if self:IsNotCarrier() then - env.info("FF not carrier ==> pickup") + self:I(self.lid.."Not carrier ==> pickup") --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. @@ -4509,13 +4577,13 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsPickingup() then - env.info("FF picking up") + self:I(self.lid.."Picking up...") --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. elseif self:IsLoading() then - env.info("FF loading") + self:I(self.lid.."Loading...") local boarding=false local gotcargo=false @@ -4536,7 +4604,7 @@ function OPSGROUP:_CheckCargoTransport() -- Boarding finished ==> Transport cargo. if gotcargo and not boarding then - env.info("FF boarding finished ==> Loaded") + self:I(self.lid.."Boarding finished ==> Loaded") self:Loaded() end @@ -4544,18 +4612,14 @@ function OPSGROUP:_CheckCargoTransport() if not gotcargo and not boarding then self:Loading() end - - elseif self:IsLoaded() then - - env.info("FF loaded (nothing to do?)") elseif self:IsTransporting() then - env.info("FF transporting (nothing to do)") + self:I(self.lid.."Transporting (nothing to do)") elseif self:IsUnloading() then - env.info("FF unloading ==> Checking if all cargo was delivered") + self:I(self.lid.."Unloading ==> Checking if all cargo was delivered") local delivered=true for _,_cargo in pairs(self.cargoTransport.cargos) do @@ -4570,7 +4634,7 @@ function OPSGROUP:_CheckCargoTransport() -- Boarding finished ==> Transport cargo. if delivered then - env.info("FF unloading finished ==> Unloaded") + self:I(self.lid.."Unloading finished ==> Unloaded") self:Unloaded() end @@ -4584,12 +4648,17 @@ end --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. --- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! +-- @param Core.Zone#ZONE Deployzone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. +-- @param #number Prio Priority of this transport assignment. Should be a number between 1 (high prio) and 100 (low prio). +-- @param #number Importance Importance of this transport assignment (lower=more important). A transport is only considered, if all more important (if any) transports are done. Default `nil`. +-- @param #string ClockStart Start time in format "HH:MM(:SS)(+D)", e.g. "13:05:30" or "08:30+1". Can also be given as a `#number`, in which case it is interpreted as relative amount in seconds from now on. +-- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. +-- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone) +function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone) - local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone) + local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone) table.insert(self.cargoqueue, cargotransport) @@ -4599,40 +4668,72 @@ end --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. --- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! +-- @param Core.Zone#ZONE Deployzone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. +-- @param #number Prio Priority of this transport assignment. Should be a number between 1 (high prio) and 100 (low prio). +-- @param #number Importance Importance of this transport assignment (lower=more important). A transport is only considered, if all more important (if any) transports are done. Default `nil`. +-- @param #string ClockStart Start time in format "HH:MM:SS+D", e.g. "13:05:30" or "08:30+1". Can also be passed as a `#number` in which case it is interpreted as relative amount in seconds from now on. +-- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. +-- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone) +function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone) - local transport={} --#OPSGROUP.CargoTransport + -- Current mission time. + local Tnow=timer.getAbsTime() + -- Set start time. Default in 5 sec. + local Tstart=Tnow+5 + if ClockStart and type(ClockStart)=="number" then + Tstart=Tnow+ClockStart + elseif ClockStart and type(ClockStart)=="string" then + Tstart=UTILS.ClockToSeconds(ClockStart) + end + + -- Data structure. + local transport={} --#OPSGROUP.CargoTransport transport.pickupzone=Pickupzone transport.deployzone=Deployzone transport.uid=1 - transport.status="Planning" + transport.status="Planning" + transport.embarkzone=Embarkzone or Pickupzone + transport.disembarkzone=Disembarkzone or Deployzone + transport.prio=Prio or 50 + transport.importance=Importance + transport.Tstart=Tstart transport.cargos={} + -- Check type of GroupSet provided. if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then + -- We got a single GROUP or OPSGROUP objectg. local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) - env.info("FF adding cargo group "..cargo.opsgroup:GetName()) table.insert(transport.cargos, cargo) else + -- We got a SET_GROUP object. for _,group in pairs(GroupSet.Set) do local cargo=self:CreateCargoGroupData(group, Pickupzone, Deployzone) table.insert(transport.cargos, cargo) end end - local text=string.format("Created Cargo Transport (UID=%d) from %s -->%s", transport.uid, transport.pickupzone:GetName(), transport.deployzone:GetName()) - for _,_cargo in pairs(transport.cargos) do - local cargo=_cargo --#OPSGROUP.CargoGroup - text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal()) + -- Debug info. + if self.verbose>=0 then + local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", + transport.uid, transport.pickupzone:GetName(), transport.embarkzone:GetName(), transport.deployzone:GetName(), transport.disembarkzone:GetName()) + local Weight=0 + for _,_cargo in pairs(transport.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + local weight=cargo.opsgroup:GetWeightTotal() + Weight=Weight+weight + text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) + end + text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #transport.cargos, Weight) + self:I(self.lid..text) end return transport end ---- Create a +--- Create a cargo group data structure. -- @param #OPSGROUP self -- @param Wrapper.Group#GROUP group The GROUP object. -- @param Core.Zone#ZONE Pickupzone Pickup zone. @@ -4665,6 +4766,7 @@ function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) local cargo={} --#OPSGROUP.CargoGroup cargo.opsgroup=opsgroup + cargo.delivered=false cargo.status="Unknown" cargo.pickupzone=Pickupzone cargo.deployzone=Deployzone @@ -4743,26 +4845,12 @@ end -- @param #number Weight Cargo weight to be reduced in kg. function OPSGROUP:RedWeightCargo(UnitName, Weight) - -- Reduce weight. + -- Reduce weight by adding negative weight. self:AddWeightCargo(UnitName, -Weight) return self end - ---- Create a cargo transport assignment. --- @param #OPSGROUP self --- @param #OPSGROUP.CargoGroup CargoGroup Cargo group object. --- @return #OPSGROUP self -function OPSGROUP:_AddCargoGroup(CargoGroup) - - --CargoGroup.status=OPSGROUP.CargoStatus. - - table.insert(self.cargo, CargoGroup) - - return self -end - --- On after "Pickup" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -4789,17 +4877,23 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Get a random coordinate in the pickup zone and let the carrier go there. local Coordinate=Zone:GetRandomCoordinate() - --TODO: Add NAVYGROUP and FLIGHTGROUP waypoint + -- Add waypoint. if self.isFlightgroup then - Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) - waypoint.detour=true - else - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + if self.isHelo then + Coordinate:SetAltitude(200) + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) + waypoint.detour=true + else + --TODO: airplane! pickup at airbase. check if already at airbase or make plane go there. + end + elseif self.isNavygroup then + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) + waypoint.detour=true + elseif self.isArmygroup then + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true end - end end @@ -4840,20 +4934,26 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup + env.info("FF trying cargo!") + if cargo.opsgroup:IsNotCargo() and not cargo.delivered then -- Check if cargo is in pickup zone. - local inzone=self.cargoTransport.pickupzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) + local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) -- First check if cargo is not delivered yet. if inzone then + env.info("FF trying cargo 2!") + local weight=cargo.opsgroup:GetWeightTotal() local carrier=_findCarrier(weight) if carrier then + env.info("FF trying cargo3!") + -- Decrease free cargo bay. cargobay[carrier.name]=cargobay[carrier.name]-weight @@ -4869,9 +4969,15 @@ function OPSGROUP:onafterLoading(From, Event, To) env.info("FF cannot board carrier") end - + + else + env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) end + + else + env.info("FF cargo already cargo or delivered") end + end end @@ -4949,16 +5055,24 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) -- Get a random coordinate in the pickup zone and let the carrier go there. local Coordinate=Zone:GetRandomCoordinate() - --TODO: Add NAVYGROUP and FLIGHTGROUP waypoint + -- Set waypoint. if self.isFlightgroup then - Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) - waypoint.detour=true - local dist=self:GetCoordinate():Get2DDistance(waypoint.coordinate) - env.info(string.format("FF adding transport detour wp with uid=%d at dist=%d m", waypoint.uid, dist)) - else + -- FLIGHTGROUP + if self.isHelo then + Coordinate:SetAltitude(200) + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) + waypoint.detour=true + else + -- TODO: airplane! let plane fly to airbase. + end + elseif self.isArmygroup then + -- ARMYGROUP local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) waypoint.detour=true + elseif self.isNavygroup then + -- NAVYGROUP + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) + waypoint.detour=true end end @@ -4986,7 +5100,7 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) env.info("FF deploy cargo "..cargo.opsgroup:GetName()) - local zone=Zone or cargo.deployzone --Core.Zone#ZONE + local zone=Zone or self.cargoTransport.disembarkzone --Core.Zone#ZONE --if zone:IsCoordinateInZone(self:GetCoordinate()) then @@ -5093,20 +5207,21 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self.carrier=Carrier self.carrierGroup=CarrierGroup - -- Add to current cargo of carrier. - --CarrierGroup:_AddCargoGroup(self) + -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. + local board=self.speedMax>0 and self:IsLateActivated()==false and (self.isArmygroup or self.isNavygroup) - --TODO: make cargo run to carrier - --TODO: check if cargo is mobile. if not ==> load - --TODO: check if cargo is alive=true. if only exists ==> load. - - if self.speedMax>0 then + if board then + -- TODO: Implement embarkzone. local Coordinate=Carrier.unit:GetCoordinate() - --TODO: NAVYGROUP and FLIGHTGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) - waypoint.detour=true + if self.isArmygroup then + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + waypoint.detour=true + else + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) + waypoint.detour=true + end else @@ -5129,11 +5244,18 @@ function OPSGROUP:onafterEmbark(From, Event, To, Carrier) self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.LOADED)) -- Set cargo status. - self.cargoStatus=OPSGROUP.CargoStatus.LOADED + self.cargoStatus=OPSGROUP.CargoStatus.LOADED + + -- Clear all waypoints. + for i=1,#self.waypoints do + table.remove(self.waypoints, i) + end + self.waypoints={} -- Despawn this group. self:Despawn(0, true) + -- Set carrier (again). self.carrier=Carrier end From 8bb9f0d7c0172635b566de42fb7dd46fcc53fd99 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 6 Feb 2021 09:41:24 +0100 Subject: [PATCH 012/141] OPS Cargo --- Moose Development/Moose/Core/Point.lua | 17 +- Moose Development/Moose/Ops/FlightGroup.lua | 77 ++- Moose Development/Moose/Ops/OpsGroup.lua | 504 +++++++++++++++++--- 3 files changed, 504 insertions(+), 94 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 864b56bd1..612cd4cfd 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1745,10 +1745,9 @@ do -- COORDINATE --- Creates an explosion at the point of a certain intensity. -- @param #COORDINATE self -- @param #number ExplosionIntensity Intensity of the explosion in kg TNT. Default 100 kg. - -- @param #number Delay Delay before explosion in seconds. + -- @param #number Delay (Optional) Delay before explosion is triggered in seconds. -- @return #COORDINATE self function COORDINATE:Explosion( ExplosionIntensity, Delay ) - self:F2( { ExplosionIntensity } ) ExplosionIntensity=ExplosionIntensity or 100 if Delay and Delay>0 then self:ScheduleOnce(Delay, self.Explosion, self, ExplosionIntensity) @@ -1760,11 +1759,17 @@ do -- COORDINATE --- Creates an illumination bomb at the point. -- @param #COORDINATE self - -- @param #number power Power of illumination bomb in Candela. + -- @param #number Power Power of illumination bomb in Candela. Default 1000 cd. + -- @param #number Delay (Optional) Delay before bomb is ignited in seconds. -- @return #COORDINATE self - function COORDINATE:IlluminationBomb(power) - self:F2() - trigger.action.illuminationBomb( self:GetVec3(), power ) + function COORDINATE:IlluminationBomb(Power, Delay) + Power=Power or 1000 + if Delay and Delay>0 then + self:ScheduleOnce(Delay, self.IlluminationBomb, self, Power) + else + trigger.action.illuminationBomb(self:GetVec3(), Power) + end + return self end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index efef49eee..1c78a5144 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -31,10 +31,6 @@ --- FLIGHTGROUP class. -- @type FLIGHTGROUP --- @field Wrapper.Airbase#AIRBASE homebase The home base of the flight group. --- @field Wrapper.Airbase#AIRBASE destbase The destination base of the flight group. --- @field Core.Zone#ZONE homezone The home zone of the flight group. Set when spawn happens in air. --- @field Core.Zone#ZONE destzone The destination zone of the flight group. Set when final waypoint is in air. -- @field #string actype Type name of the aircraft. -- @field #number rangemax Max range in km. -- @field #number ceiling Max altitude the aircraft can fly at in meters. @@ -690,10 +686,17 @@ function FLIGHTGROUP:StartUncontrolled(delay) self:ScheduleOnce(delay, FLIGHTGROUP.StartUncontrolled, self) else - if self:IsAlive() then - --TODO: check Alive==true and Alive==false ==> Activate first - self:T(self.lid.."Starting uncontrolled group") - self.group:StartUncontrolled(delay) + local alive=self:IsAlive() + + if alive~=nil then + -- Check if group is already active. + local _delay=0 + if alive==false then + self:Activate() + _delay=1 + end + self:I(self.lid.."Starting uncontrolled group") + self.group:StartUncontrolled(_delay) self.isUncontrolled=true else self:E(self.lid.."ERROR: Could not start uncontrolled group as it is NOT alive!") @@ -1443,18 +1446,21 @@ function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) else + -- Set element status. + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.LANDED, airbase) + -- Helos with skids land directly on parking spots. if self.isHelo then local Spot=self:GetParkingSpot(Element, 10, airbase) - self:_SetElementParkingAt(Element, Spot) + if Spot then + self:_SetElementParkingAt(Element, Spot) + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.ARRIVED) + end end - -- Set element status. - self:_UpdateStatus(Element, OPSGROUP.ElementStatus.LANDED, airbase) - end end @@ -1469,6 +1475,7 @@ end function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element, airbase, Parking) self:T(self.lid..string.format("Element arrived %s at %s airbase using parking spot %d", Element.name, airbase and airbase:GetName() or "unknown", Parking and Parking.TerminalID or -99)) + -- Set element parking. self:_SetElementParkingAt(Element, Parking) -- Set element status. @@ -1661,9 +1668,22 @@ end -- @param #string To To state. function FLIGHTGROUP:onafterAirborne(From, Event, To) self:T(self.lid..string.format("Flight airborne")) + + -- No current airbase any more. + self.currbase=nil if self.isAI then - self:_CheckGroupDone(1) + if self:IsTransporting() then + env.info("FF transporting land at airbase ") + local airbase=self.cargoTransport.deployzone:GetAirbase() + self:LandAtAirbase(airbase) + elseif self:IsPickingup() then + env.info("FF pickingup land at airbase ") + local airbase=self.cargoTransport.pickupzone:GetAirbase() + self:LandAtAirbase(airbase) + else + self:_CheckGroupDone(1) + end else self:_UpdateMenu() end @@ -1715,7 +1735,6 @@ function FLIGHTGROUP:onafterLandedAt(From, Event, To) end - --- On after "Arrived" event. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -1801,6 +1820,13 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) -- Reset. self.isLandingAtAirbase=nil + -- Init (un-)loading process. + if self:IsPickingup() then + self:__Loading(-1) + elseif self:IsTransporting() then + self:__Deploy(-1) + end + else -- Depawn after 5 min. Important to trigger dead events before DCS despawns on its own without any notification. self:Despawn(5*60) @@ -2031,6 +2057,9 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Number of mission remaining. local nMissions=self:CountRemainingMissison() + + -- Number of cargo transports remaining. + local nTransports=self:CountRemainingTransports() -- Final waypoint passed? if self.passedfinalwp then @@ -2039,7 +2068,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay) if self.currentmission==nil and self.taskcurrent==0 then -- Number of remaining tasks/missions? - if nTasks==0 and nMissions==0 then + if nTasks==0 and nMissions==0 and nTransports==0 then local destbase=self.destbase or self.homebase local destzone=self.destzone or self.homezone @@ -2201,6 +2230,9 @@ end -- @param #number SpeedLand Landing speed in knots. Default 170 kts. function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) + -- Set current airbase. + self.currbase=airbase + -- Defaults: SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise) SpeedHold=SpeedHold or (self.isHelo and 80 or 250) @@ -2287,10 +2319,10 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) local pland=airbase:GetCoordinate():Translate(x2, runway.heading-180):SetAltitude(h2) wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), airbase, {}, "Landing") - elseif airbase:IsShip() then + elseif airbase:IsShip() or airbase:IsHelipad() then --- - -- Ship + -- Ship or Helipad --- local pland=airbase:GetCoordinate() @@ -2713,6 +2745,8 @@ function FLIGHTGROUP:onafterStop(From, Event, To) end end + + self.currbase=nil -- Handle events: self:UnHandleEvent(EVENTS.Birth) @@ -3542,12 +3576,21 @@ end -- @return Wrapper.Airbase#AIRBASE.ParkingSpot Parking spot or nil if no spot is within distance threshold. function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) + -- Coordinate of unit landed local coord=element.unit:GetCoordinate() + -- Airbase. airbase=airbase or self:GetClosestAirbase() --coord:GetClosestAirbase(nil, self:GetCoalition()) -- TODO: replace by airbase.parking if AIRBASE is updated. local parking=airbase:GetParkingSpotsTable() + + -- If airbase is ship, translate parking coords. Alternatively, we just move the coordinate of the unit to the origin of the map, which is way more efficient. + if airbase and airbase:IsShip() then + coord.x=0 + coord.z=0 + maxdist=100 + end local spot=nil --Wrapper.Airbase#AIRBASE.ParkingSpot local dist=nil diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 082e4ae7c..8ef7b1815 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -33,6 +33,11 @@ -- @field #boolean isGround If true, group is some ground unit. -- @field #table waypoints Table of waypoints. -- @field #table waypoints0 Table of initial waypoints. +-- @field Wrapper.Airbase#AIRBASE homebase The home base of the flight group. +-- @field Wrapper.Airbase#AIRBASE destbase The destination base of the flight group. +-- @field Wrapper.Airbase#AIRBASE currbase The current airbase of the flight group, i.e. where it is currently located or landing at. +-- @field Core.Zone#ZONE homezone The home zone of the flight group. Set when spawn happens in air. +-- @field Core.Zone#ZONE destzone The destination zone of the flight group. Set when final waypoint is in air. -- @field #number currentwp Current waypoint index. This is the index of the last passed waypoint. -- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. -- @field #table taskqueue Queue of tasks. @@ -99,6 +104,7 @@ -- @field #OPSGROUP.Element carrier Carrier the group is loaded into as cargo. -- @field #OPSGROUP carrierGroup Carrier group transporting this group as cargo. -- @field #table cargoqueue Table containing cargo groups to be transported. +-- @field #table cargoBay Table containing OPSGROUP loaded into this group. -- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. -- @field #string cargoStatus Cargo status of this group acting as cargo. -- @field #string carrierStatus Carrier status of this group acting as cargo carrier. @@ -158,6 +164,7 @@ OPSGROUP = { Nkills = 0, weaponData = {}, cargoqueue = {}, + cargoBay = {}, } @@ -396,11 +403,23 @@ OPSGROUP.CargoStatus={ DELIVERED="delivered", } +--- Cargo transport status. +-- @type OPSGROUP.TransportStatus +-- @field #string PLANNING Planning state. +-- @field #string SCHEDULED Transport is scheduled in the cargo queue. +-- @field #string EXECUTING Transport is being executed. +-- @field #string DELIVERED Transport was delivered. +OPSGROUP.TransportStatus={ + PLANNING="planning", + SCHEDULED="scheduled", + EXECUTING="executing", + DELIVERED="delivered", +} --- Cargo transport data. -- @type OPSGROUP.CargoTransport -- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. --- @field #string status Status of the carrier. See @{#OPSGROUP.CarrierStatus}. +-- @field #string status Status of the transport. See @{#OPSGROUP.TransportStatus}. -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). -- @field #number importance Importance of this transport. Smaller=higher. -- @field #number Tstart Start time in abs. seconds. @@ -408,6 +427,7 @@ OPSGROUP.CargoStatus={ -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. +-- @field #OPSGROUP carrierGroup The new carrier group. --- Cargo group data. -- @type OPSGROUP.CargoGroup @@ -1420,6 +1440,27 @@ function OPSGROUP:SelfDestruction(Delay, ExplosionPower) end +--- Check if this is a FLIGHTGROUP. +-- @param #OPSGROUP self +-- @return #boolean If true, this is an airplane or helo group. +function OPSGROUP:IsFlightgroup() + return self.isFlightgroup +end + +--- Check if this is a ARMYGROUP. +-- @param #OPSGROUP self +-- @return #boolean If true, this is a ground group. +function OPSGROUP:IsArmygroup() + return self.isArmygroup +end + +--- Check if this is a NAVYGROUP. +-- @param #OPSGROUP self +-- @return #boolean If true, this is a ship group. +function OPSGROUP:IsNavygroup() + return self.isNavygroup +end + --- Check if group is exists. -- @param #OPSGROUP self @@ -2076,13 +2117,18 @@ function OPSGROUP:OnEventBirth(EventData) -- Set homebase if not already set. if self.isFlightgroup then + if EventData.Place then self.homebase=self.homebase or EventData.Place + self.currbase=EventData.Place + else + self.currbase=nil end if self.homebase and not self.destbase then self.destbase=self.homebase end + self:T(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", unitname, self.homebase and self.homebase:GetName() or "unknown")) else self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) @@ -2929,6 +2975,28 @@ function OPSGROUP:CountRemainingMissison() return N end +--- Count remaining cargo transport assignments. +-- @param #OPSGROUP self +-- @return #number Number of unfinished transports in the queue. +function OPSGROUP:CountRemainingTransports() + + local N=0 + + -- Loop over mission queue. + for _,_transport in pairs(self.cargoqueue) do + local transport=_transport --#OPSGROUP.CargoTransport + + -- Count not delivered (executing or scheduled) assignments. + if transport and transport.status~=OPSGROUP.TransportStatus.DELIVERED then + + N=N+1 + + end + end + + return N +end + --- Get next mission. -- @param #OPSGROUP self -- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. @@ -4485,6 +4553,31 @@ function OPSGROUP:_CheckCargoTransport() -- Abs. missin time in seconds. local Time=timer.getAbsTime() + -- Cargo queue info. + local text="Cargo bay:" + for cargogroupname, carriername in pairs(self.cargoBay) do + text=text..string.format("\n- %s in carrier %s", tostring(cargogroupname), tostring(carriername)) + end + self:I(self.lid..text) + + -- Cargo queue info. + local text="Cargo queue:" + for i,_transport in pairs(self.cargoqueue) do + local transport=_transport --#OPSGROUP.CargoTransport + text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport.status, transport.pickupzone:GetName(), transport.deployzone:GetName()) + for j,_cargo in pairs(transport.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + local state=cargo.opsgroup:GetState() + local status=cargo.opsgroup.cargoStatus + local name=cargo.opsgroup.groupname + local carriergroup=cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup.groupname or "N/A" + local carrierelement=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "N/A" + text=text..string.format("\n (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s", j, name, state, status, carriergroup, carrierelement, tostring(cargo.delivered)) + end + end + self:I(self.lid..text) + + if self.cargoTransport then -- TODO: Check if this group can actually transport any cargo. @@ -4505,7 +4598,7 @@ function OPSGROUP:_CheckCargoTransport() end if done then - self.cargoTransport.status="Delivered" + self.cargoTransport.status=OPSGROUP.TransportStatus.DELIVERED end end @@ -4539,7 +4632,8 @@ function OPSGROUP:_CheckCargoTransport() for _,_cargotransport in pairs(self.cargoqueue) do local cargotransport=_cargotransport --#OPSGROUP.CargoTransport - if Time>=cargotransport.Tstart and cargotransport.status~="Delivered" and (cargotransport.importance==nil or cargotransport.importance<=vip) then + if Time>=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then + cargotransport.status=OPSGROUP.TransportStatus.EXECUTING self.cargoTransport=cargotransport break end @@ -4560,7 +4654,9 @@ function OPSGROUP:_CheckCargoTransport() local gstatus=cargo.opsgroup:GetState() local cstatus=cargo.opsgroup.cargoStatus local weight=cargo.opsgroup:GetWeightTotal() - text=text..string.format("\n- %s (%.1f kg) [%s]: %s delivered=%s", name, weight, gstatus, cstatus, tostring(cargo.delivered)) + local carriername=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "none" + local carriergroup=cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup.groupname or "none" + text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s", name, weight, gstatus, cstatus, carriername, carriergroup, tostring(cargo.delivered)) end self:I(self.lid..text) end @@ -4577,13 +4673,15 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsPickingup() then - self:I(self.lid.."Picking up...") + -- Debug Info. + self:T(self.lid.."Picking up...") --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. elseif self:IsLoading() then self:I(self.lid.."Loading...") + self.Tloading=self.Tloading or Time local boarding=false local gotcargo=false @@ -4608,14 +4706,15 @@ function OPSGROUP:_CheckCargoTransport() self:Loaded() end - -- No cargo and noone is boarding ==> check again if we can make anyone board. + -- No cargo and no one is boarding ==> check again if we can make anyone board. if not gotcargo and not boarding then self:Loading() end elseif self:IsTransporting() then - self:I(self.lid.."Transporting (nothing to do)") + -- Debug info. + self:T(self.lid.."Transporting (nothing to do)") elseif self:IsUnloading() then @@ -4641,6 +4740,8 @@ function OPSGROUP:_CheckCargoTransport() end end + + -- TODO: Remove delivered transports from cargo queue! return self end @@ -4655,16 +4756,39 @@ end -- @param #string ClockStart Start time in format "HH:MM(:SS)(+D)", e.g. "13:05:30" or "08:30+1". Can also be given as a `#number`, in which case it is interpreted as relative amount in seconds from now on. -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. +-- @param #OPSGROUP NewCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone) +function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) - local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone) + -- Create a new cargo transport assignment. + local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) + -- Set state to SCHEDULED. + cargotransport.status=OPSGROUP.TransportStatus.SCHEDULED + + --Add to cargo queue table.insert(self.cargoqueue, cargotransport) return cargotransport end +--- Delete a cargo transport assignment from the cargo queue +-- @param #OPSGROUP self +-- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport do be deleted. +-- @return #OPSGROUP self +function OPSGROUP:DelCargoTransport(CargoTransport) + + for i,_transport in pairs(self.cargoqueue) do + local transport=_transport --#OPSGROUP.CargoTransport + if transport.uid==CargoTransport.uid then + table.remove(self.cargoqueue, i) + return self + end + end + + return self +end + --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. @@ -4675,8 +4799,9 @@ end -- @param #string ClockStart Start time in format "HH:MM:SS+D", e.g. "13:05:30" or "08:30+1". Can also be passed as a `#number` in which case it is interpreted as relative amount in seconds from now on. -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. +-- @param #OPSGROUP NewCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone) +function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) -- Current mission time. local Tnow=timer.getAbsTime() @@ -4691,15 +4816,16 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I -- Data structure. local transport={} --#OPSGROUP.CargoTransport + transport.uid=1 + transport.status=OPSGROUP.TransportStatus.PLANNING transport.pickupzone=Pickupzone transport.deployzone=Deployzone - transport.uid=1 - transport.status="Planning" transport.embarkzone=Embarkzone or Pickupzone transport.disembarkzone=Disembarkzone or Deployzone transport.prio=Prio or 50 transport.importance=Importance transport.Tstart=Tstart + transport.carrierGroup=NewCarrierGroup transport.cargos={} -- Check type of GroupSet provided. @@ -4793,7 +4919,27 @@ function OPSGROUP:GetWeightTotal(UnitName) end return weight -end +end + +--- Get free cargo bay weight. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @return #number Total weight in kg. +function OPSGROUP:GetFreeCargobay(UnitName) + + local Free=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then + local free=element.weightMaxCargo-element.weightCargo + Free=Free+free + end + end + + return Free +end + --- Get weight of the internal cargo the group is carriing right now. -- @param #OPSGROUP self @@ -4851,6 +4997,24 @@ function OPSGROUP:RedWeightCargo(UnitName, Weight) return self end +--- Add weight to the internal cargo of an element of the group. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. +-- @return #OPSGROUP.Element Carrier able to transport the cargo. +function OPSGROUP:FindCarrierForCargo(CargoGroup) + + local weight=CargoGroup:GetWeightTotal() + + for _,element in pairs(self.elements) do + local free=self:GetFreeCargobay(element.name) + if free>=weight then + return element + end + end + + return nil +end + --- On after "Pickup" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -4866,8 +5030,27 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Check if already in the pickup zone. local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) + + local airbasePickup=nil --Wrapper.Airbase#AIRBASE + if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then + airbasePickup=Zone:GetAirbase() + end + + -- Check if group is already ready for loading. + local ready4loading=false + if self:IsArmygroup() or self:IsNavygroup() then + ready4loading=inzone + else + -- Aircraft is already parking at the pickup airbase. + ready4loading=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() + + -- If a helo is landed in the zone, we also are ready for loading. + if ready4loading==false and self.isHelo and self:IsLandedAt() and inzone then + ready4loading=true + end + end - if inzone then + if ready4loading then -- We are already in the pickup zone ==> initiate loading. self:Loading() @@ -4879,12 +5062,40 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Add waypoint. if self.isFlightgroup then - if self.isHelo then + + --- + -- Pickup at airbase + --- + + if airbasePickup then + + local airbaseCurrent=self.currbase + + if airbaseCurrent then + + -- Activate uncontrolled group. + if self:IsParking() then + self:StartUncontrolled() + end + + else + -- Order group to land at an airbase. + self:LandAtAirbase(airbasePickup) + end + + elseif self.isHelo or self.isVTOL then + + --- + -- Helo or VTOL can also land in a zone + --- + + -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + else - --TODO: airplane! pickup at airbase. check if already at airbase or make plane go there. + self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") end elseif self.isNavygroup then local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) @@ -4910,6 +5121,9 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.LOADING + -- Loading time stamp. + self.Tloading=timer.getAbsTime() + -- Create a temp array and monitor the free cargo space for each element. local cargobay={} for _,_element in pairs(self.elements) do @@ -4934,8 +5148,6 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - env.info("FF trying cargo!") - if cargo.opsgroup:IsNotCargo() and not cargo.delivered then -- Check if cargo is in pickup zone. @@ -4944,16 +5156,12 @@ function OPSGROUP:onafterLoading(From, Event, To) -- First check if cargo is not delivered yet. if inzone then - env.info("FF trying cargo 2!") - local weight=cargo.opsgroup:GetWeightTotal() local carrier=_findCarrier(weight) if carrier then - env.info("FF trying cargo3!") - -- Decrease free cargo bay. cargobay[carrier.name]=cargobay[carrier.name]-weight @@ -4961,7 +5169,6 @@ function OPSGROUP:onafterLoading(From, Event, To) cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED -- Order cargo group to board the carrier. - env.info("FF order group to board carrier") cargo.opsgroup:Board(self, carrier) else @@ -4974,8 +5181,14 @@ function OPSGROUP:onafterLoading(From, Event, To) env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) end - else - env.info("FF cargo already cargo or delivered") + else + + env.info("FF cargo already cargo or delivered") + + if not cargo.delivered then + + end + end end @@ -4988,21 +5201,33 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #OPSGROUP CargoGroup The OPSGROUP loaded as cargo. -function OPSGROUP:onafterLoad(From, Event, To, CargoGroup) - env.info("FF load") - - local weight=CargoGroup:GetWeightTotal() +-- @param #OPSGROUP.Element Carrier The carrier element/unit. +function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) + -- Debug info. + self:I(self.lid..string.format("Loading group %s", tostring(CargoGroup.groupname))) - local carrier=CargoGroup.carrier - + -- Carrier element. + local carrier=Carrier or CargoGroup.carrier --#OPSGROUP.Element + + -- No carrier provided. + if not carrier then + -- Try to find a carrier manually. + carrier=self:FindCarrierForCargo(CargoGroup) + end if carrier then + + -- Cargo weight. + local weight=CargoGroup:GetWeightTotal() -- Add weight to carrier. self:AddWeightCargo(carrier.name, weight) - -- Embark ==> Loaded - CargoGroup:Embark(carrier) + -- Fill cargo bay. + self.cargoBay[CargoGroup.groupname]=carrier.name + + -- Embark ==> Loaded. + CargoGroup:Embark(self, carrier) else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") @@ -5041,10 +5266,31 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.TRANSPORTING + + --TODO: This is all very similar to the onafterPickup() function. Could make it general. -- Check if already in deploy zone. local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) + local airbaseDeploy=nil --Wrapper.Airbase#AIRBASE + if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then + airbaseDeploy=Zone:GetAirbase() + end + + -- Check if group is already ready for loading. + local ready2deploy=false + if self:IsArmygroup() or self:IsNavygroup() then + ready2deploy=inzone + else + -- Aircraft is already parking at the pickup airbase. + ready2deploy=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() + + -- If a helo is landed in the zone, we also are ready for loading. + if ready2deploy==false and self.isHelo and self:IsLandedAt() and inzone then + ready2deploy=true + end + end + if inzone then -- We are already in the pickup zone ==> initiate loading. @@ -5052,27 +5298,59 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) else - -- Get a random coordinate in the pickup zone and let the carrier go there. + -- Get a random coordinate in the deploy zone and let the carrier go there. local Coordinate=Zone:GetRandomCoordinate() - - -- Set waypoint. + + -- Add waypoint. if self.isFlightgroup then - -- FLIGHTGROUP - if self.isHelo then + + --- + -- Deploy at airbase + --- + + if airbaseDeploy then + + local airbaseCurrent=self.currbase + + if airbaseCurrent then + + -- Activate uncontrolled group. + if self:IsParking() then + self:StartUncontrolled() + end + + else + -- Order group to land at an airbase. + self:LandAtAirbase(airbaseDeploy) + end + + elseif self.isHelo or self.isVTOL then + + --- + -- Helo or VTOL can also land in a zone + --- + + -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + else - -- TODO: airplane! let plane fly to airbase. + self:E(self.lid.."ERROR: Carrier aircraft cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") end + elseif self.isArmygroup then + -- ARMYGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + elseif self.isNavygroup then + -- NAVYGROUP local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + waypoint.detour=true + end end @@ -5100,21 +5378,42 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) env.info("FF deploy cargo "..cargo.opsgroup:GetName()) + -- Deploy or disembark zone. local zone=Zone or self.cargoTransport.disembarkzone --Core.Zone#ZONE - --if zone:IsCoordinateInZone(self:GetCoordinate()) then + -- New carrier group. + local carrierGroup=self.cargoTransport.carrierGroup + + -- Cargo was delivered (somehow). + cargo.delivered=true + + if carrierGroup then + + local carrier=carrierGroup:FindCarrierForCargo(cargo.opsgroup) + + if carrier then + self:Unload(cargo.opsgroup) + carrierGroup:Load(cargo.opsgroup, carrier) + else + env.info("ERROR: No element of the group can take this cargo!") + end + + elseif zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then + + -- + env.info("ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") + + else + -- Random coordinate in local Coordinate=zone:GetRandomCoordinate() local Heading=math.random(0,359) - - -- Cargo was delivered. - cargo.delivered=true - + -- Unload. env.info("FF unload cargo "..cargo.opsgroup:GetName()) self:Unload(cargo.opsgroup, Coordinate, Heading) - --end + end end @@ -5122,6 +5421,19 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) end +--- On before "Unload" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP OpsGroup The OPSGROUP loaded as cargo. +-- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. +-- @param #number Heading Heading of group. +function OPSGROUP:onbeforeUnload(From, Event, To, OpsGroup, Coordinate, Heading) + --TODO: Add check if CargoGroup is cargo of this carrier. + return true +end + --- On after "Unload" event. Carrier unloads a cargo group from its cargo bay. -- @param #OPSGROUP self -- @param #string From From state. @@ -5132,9 +5444,33 @@ end -- @param #number Heading Heading of group. function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading) - --TODO: Add check if CargoGroup is cargo of this carrier. - if OpsGroup:IsInUtero() then + -- Not in cargo bay any more. + self.cargoBay[OpsGroup.groupname]=nil + + if Coordinate then + + --- + -- Respawn at a coordinate. + --- + OpsGroup:Unboard(Coordinate, Heading) + else + + --- + -- Just remove from this carrier. + --- + + -- Set cargo status. + OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + + -- Reduce carrier weight. + local weight=OpsGroup:GetWeightTotal() + self:RedWeightCargo(OpsGroup.carrier.name, weight) + + -- No carrier. + OpsGroup.carrier=nil + OpsGroup.carrierGroup=nil + end end @@ -5146,7 +5482,8 @@ end -- @param #string To To state. -- @param Core.Zone#ZONE Zone Deploy zone. function OPSGROUP:onafterUnloaded(From, Event, To) - env.info("FF unloaded") + -- Debug info + self:I(self.lid.."Cargo unloaded..") -- Cancel landedAt task. if self.isFlightgroup and self:IsLandedAt() then @@ -5154,6 +5491,7 @@ function OPSGROUP:onafterUnloaded(From, Event, To) self:TaskCancel(Task) end + -- Check if there is still cargo to pickup. local pickup=false for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup @@ -5169,15 +5507,30 @@ function OPSGROUP:onafterUnloaded(From, Event, To) if pickup then - env.info("FF cargo left ==> pickup") + -- Pickup the next batch. + self:I(self.lid.."Still cargo left ==> pickup") self:Pickup(self.cargoTransport.pickupzone) else + -- Debug info. + self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.NOTCARRIER)) + + -- This is not a carrier anymore. + self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER + -- No current transport assignment. self.cargoTransport=nil + + -- Startup uncontrolled aircraft to allow it to go back. + if self:IsFlightgroup() then + if self:IsUncontrolled() then + self:StartUncontrolled() + end + end - env.info("FF all delivered ==> check group done") + -- Check group done. + self:I(self.lid.."All cargo delivered ==> check group done") self:_CheckGroupDone(0.1) end @@ -5238,8 +5591,9 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #OPSGROUP.Element Carrier The OPSGROUP element -function OPSGROUP:onafterEmbark(From, Event, To, Carrier) +-- @param #OPSGROUP CarrierGroup The carrier OPSGROUP. +-- @param #OPSGROUP.Element Carrier The OPSGROUP element carriing this group. +function OPSGROUP:onafterEmbark(From, Event, To, CarrierGroup, Carrier) -- Debug info. self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.LOADED)) @@ -5252,11 +5606,14 @@ function OPSGROUP:onafterEmbark(From, Event, To, Carrier) end self.waypoints={} - -- Despawn this group. - self:Despawn(0, true) - -- Set carrier (again). self.carrier=Carrier + self.carrierGroup=CarrierGroup + + -- Despawn this group. + if self:IsAlive() then + self:Despawn(0, true) + end end @@ -5267,10 +5624,21 @@ end -- @param #string To To state. -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. Can also be a DCS#Vec3 object. -- @param #number Heading Heading the group has in degrees. Default is last known heading of the group. -function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) +-- @param #number Delay Delay in seconds, before the group is respawned. +function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading, Delay) -- Debug info. self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) + -- Set cargo status. + self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + + -- Reduce carrier weight. + local weight=self:GetWeightTotal() + self.carrierGroup:RedWeightCargo(self.carrier.name, weight) + + -- No carrier. + self.carrier=nil + self.carrierGroup=nil -- Template for the respawned group. local Template=UTILS.DeepCopy(self.template) --DCS#Template @@ -5301,18 +5669,9 @@ function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) end end - - -- Reduce carrier weight. - local weight=self:GetWeightTotal() - self.carrierGroup:RedWeightCargo(self.carrier.name, weight) - - -- Set cargo status. - self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - self.carrier=nil - self.carrierGroup=nil -- Respawn group. - self:_Respawn(0, Template) + self:_Respawn(Delay or 0, Template) end @@ -5522,7 +5881,9 @@ function OPSGROUP:_CheckGroupDone(delay) local speed=self:GetSpeedToWaypoint(i) -- Start route at first waypoint. - self:UpdateRoute(i, speed) + --self:UpdateRoute(i, speed) + + self:Cruise(speed) self:T(self.lid..string.format("Adinfinitum=TRUE ==> Goto WP index=%d at speed=%d knots", i, speed)) @@ -5556,7 +5917,8 @@ function OPSGROUP:_CheckGroupDone(delay) if #self.waypoints>0 then self:T(self.lid..string.format("NOT Passed final WP, #WP>0 ==> Update Route")) - self:UpdateRoute() + --self:UpdateRoute() + self:Cruise() else self:E(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop")) self:__FullStop(-1) From c5a4776b3a82384e45a5e784e385fefe3099a936 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 7 Feb 2021 01:24:13 +0100 Subject: [PATCH 013/141] OPS Cargo --- Moose Development/Moose/Core/Zone.lua | 58 ++-- Moose Development/Moose/Ops/ArmyGroup.lua | 2 + Moose Development/Moose/Ops/FlightGroup.lua | 29 +- Moose Development/Moose/Ops/NavyGroup.lua | 5 +- Moose Development/Moose/Ops/OpsGroup.lua | 323 +++++++++++++------- 5 files changed, 289 insertions(+), 128 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 88d487622..9fd532dbd 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1057,24 +1057,48 @@ end --- Returns a random Vec2 location within the zone. -- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! -- @return DCS#Vec2 The random location within the zone. -function ZONE_RADIUS:GetRandomVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) +function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) - local Point = {} local Vec2 = self:GetVec2() local _inner = inner or 0 local _outer = outer or self:GetRadius() + + if surfacetypes and type(surfacetypes)~="table" then + surfacetypes={surfacetypes} + end - local angle = math.random() * math.pi * 2; - Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer); - Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer); + local function _getpoint() + local point = {} + local angle = math.random() * math.pi * 2 + point.x = Vec2.x + math.cos(angle) * math.random(_inner, _outer) + point.y = Vec2.y + math.sin(angle) * math.random(_inner, _outer) + return point + end + + local function _checkSurface(point) + for _,sf in pairs(surfacetypes) do + if sf==land.getSurfaceType(point) then + return true + end + end + return false + end - self:T( { Point } ) + local point=_getpoint() - return Point + if surfacetypes then + local N=1 ; local Nmax=1000 + while _checkSurface(point)==false and N<=Nmax do + point=_getpoint() + N=N+1 + end + end + + return point end --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. @@ -1126,15 +1150,15 @@ end --- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone. -- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#COORDINATE -function ZONE_RADIUS:GetRandomCoordinate( inner, outer ) - self:F( self.ZoneName, inner, outer ) +-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! +-- @return Core.Point#COORDINATE The random coordinate. +function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes) - local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2(inner, outer) ) + local vec2=self:GetRandomVec2(inner, outer, surfacetypes) - self:T3( { Coordinate = Coordinate } ) + local Coordinate = COORDINATE:NewFromVec2(vec2) return Coordinate end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 2cf8ab17a..0e062ef69 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1176,6 +1176,8 @@ function ARMYGROUP:_InitGroup() text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 1c78a5144..f0743bb8c 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -358,6 +358,14 @@ function FLIGHTGROUP:SetAirwing(airwing) return self end +--- Set if aircraft is VTOL capable. Unfortunately, there is no DCS way to determine this via scripting. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetVTOL() + self.isVTOL=true + return self +end + --- Get airwing the flight group belongs to. -- @param #FLIGHTGROUP self -- @return Ops.AirWing#AIRWING The AIRWING object. @@ -1674,13 +1682,17 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) if self.isAI then if self:IsTransporting() then - env.info("FF transporting land at airbase ") - local airbase=self.cargoTransport.deployzone:GetAirbase() - self:LandAtAirbase(airbase) + if self.cargoTransport and self.cargoTransport.deployzone and self.cargoTransport.deployzone:IsInstanceOf("ZONE_AIRBASE") then + env.info("FF transporting land at airbase ") + local airbase=self.cargoTransport.deployzone:GetAirbase() + self:LandAtAirbase(airbase) + end elseif self:IsPickingup() then - env.info("FF pickingup land at airbase ") - local airbase=self.cargoTransport.pickupzone:GetAirbase() - self:LandAtAirbase(airbase) + if self.cargoTransport and self.cargoTransport.pickupzone and self.cargoTransport.pickupzone:IsInstanceOf("ZONE_AIRBASE") then + env.info("FF pickingup land at airbase ") + local airbase=self.cargoTransport.pickupzone:GetAirbase() + self:LandAtAirbase(airbase) + end else self:_CheckGroupDone(1) end @@ -2919,12 +2931,14 @@ function FLIGHTGROUP:_InitGroup() self.refueltype=select(2, unit:IsRefuelable()) -- Debug info. - if self.verbose>=1 then + if self.verbose>=0 then local text=string.format("Initialized Flight Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Range max = %.1f km\n", self.rangemax/1000) text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) text=text..string.format("Tanker type = %s\n", tostring(self.tankertype)) text=text..string.format("Refuel type = %s\n", tostring(self.refueltype)) text=text..string.format("AI = %s\n", tostring(self.isAI)) @@ -3295,6 +3309,7 @@ function FLIGHTGROUP:InitWaypoints() -- Get home and destination airbases from waypoints. self.homebase=self.homebase or self:GetHomebaseFromWaypoints() self.destbase=self.destbase or self:GetDestinationFromWaypoints() + self.currbase=self:GetHomebaseFromWaypoints() -- Remove the landing waypoint. We use RTB for that. It makes adding new waypoints easier as we do not have to check if the last waypoint is the landing waypoint. if self.destbase then diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 00b1c3de0..38e66f59c 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -677,7 +677,8 @@ function NAVYGROUP:onafterSpawned(From, Event, To) -- Set radio. if self.radioDefault then - self:SwitchRadio() + -- CAREFUL: This makes DCS crash for some ships like speed boats or Higgins boats! (On a respawn for example). Looks like the command SetFrequency is causing this. + --self:SwitchRadio() else self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, false) end @@ -1186,6 +1187,8 @@ function NAVYGROUP:_InitGroup() text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8ef7b1815..3f9bd6cab 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -19,13 +19,14 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #string groupname Name of the group. -- @field Wrapper.Group#GROUP group Group object. --- @field #table template Template of the group. +-- @field DCS#Template template Template table of the group. -- @field #boolean isLateActivated Is the group late activated. -- @field #boolean isUncontrolled Is the group uncontrolled. -- @field #boolean isFlightgroup Is a FLIGHTGROUP. -- @field #boolean isArmygroup Is an ARMYGROUP. -- @field #boolean isNavygroup Is a NAVYGROUP. -- @field #boolean isHelo If true, the is a helicopter group. +-- @field #boolean isVTOL If true, the is capable of Vertical TakeOff and Landing (VTOL). -- @field #table elements Table of elements, i.e. units of the group. -- @field #boolean isAI If true, group is purely AI. -- @field #boolean isAircraft If true, group is airplane or helicopter. @@ -108,6 +109,7 @@ -- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. -- @field #string cargoStatus Cargo status of this group acting as cargo. -- @field #string carrierStatus Carrier status of this group acting as cargo carrier. +-- @field #number cargocounter Running number of cargo UIDs. -- -- @extends Core.Fsm#FSM @@ -165,6 +167,7 @@ OPSGROUP = { weaponData = {}, cargoqueue = {}, cargoBay = {}, + cargocounter = 1, } @@ -499,6 +502,7 @@ function OPSGROUP:New(group) self:SetLaser(1688, true, false, 0.5) self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER + self.cargocounter=1 -- Init task counter. self.taskcurrent=0 @@ -582,7 +586,8 @@ function OPSGROUP:New(group) self:AddTransition("*", "Transport", "*") -- Carrier is transporting cargo. self:AddTransition("*", "Deploy", "*") -- Carrier is dropping off cargo. self:AddTransition("*", "Unload", "*") -- Carrier unload a cargo group. - self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its cargo. + self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its current cargo. + self:AddTransition("*", "Delivered", "*") -- Carrier delivered ALL cargo of the transport assignment. ------------------------ --- Pseudo Functions --- @@ -1283,7 +1288,7 @@ function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) -- Clear any task ==> makes DCS crash! --self.group:ClearTasks() - + -- Get all units. local units=self:GetDCSUnits() @@ -1292,11 +1297,12 @@ function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) if unit then local name=unit:getName() if name then + -- Despawn the unit. self:DespawnUnit(name, 0, NoEventRemoveUnit) end end end - + end end @@ -3002,6 +3008,11 @@ end -- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. function OPSGROUP:_GetNextMission() + -- Check if group is acting as carrier or cargo at the moment. + if self:IsTransporting() or self:IsPickingup() or self:IsLoading() or self:IsLoaded() then + return nil + end + -- Number of missions. local Nmissions=#self.missionqueue @@ -4376,7 +4387,7 @@ end --- Respawn the group. -- @param #OPSGROUP self -- @param #number Delay Delay in seconds before respawn happens. Default 0. --- @param #table Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. +-- @param DCS#Template Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. -- @param #boolean Reset Reset positions if TRUE. -- @return #OPSGROUP self function OPSGROUP:_Respawn(Delay, Template, Reset) @@ -4389,17 +4400,6 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Given template or get old. Template=Template or UTILS.DeepCopy(self.template) - - -- Get correct heading. - local function _Heading(course) - local h - if course<=180 then - h=math.rad(course) - else - h=-math.rad(360-course) - end - return h - end if self:IsAlive() then @@ -4467,6 +4467,10 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Spawn new group. _DATABASE:Spawn(Template) + -- Set activation and controlled state. + self.isLateActivated=Template.lateActivation + self.isUncontrolled=Template.uncontrolled + -- Reset events. --self:ResetEvents() @@ -4577,69 +4581,15 @@ function OPSGROUP:_CheckCargoTransport() end self:I(self.lid..text) - - if self.cargoTransport then - -- TODO: Check if this group can actually transport any cargo. - - local done=true - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - if cargo.delivered then - -- This one is delivered. - elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then - -- This one is dead. - else - done=false --Someone is not done! - end - - --TODO: check if cargo is too heavy for this carrier group ==> elseif - - end + -- Loop over cargo queue and check if everything was delivered. + for i=#self.cargoqueue,1,-1 do + local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport + self:_CheckDelivered(transport) + end - if done then - self.cargoTransport.status=OPSGROUP.TransportStatus.DELIVERED - end - - end - -- Check if there is anything in the queue. - if not self.cargoTransport then - - -- Current position. - local coord=self:GetCoordinate() - - -- Sort results table wrt prio and distance to pickup zone. - local function _sort(a, b) - local transportA=a --#OPSGROUP.CargoTransport - local transportB=b --#OPSGROUP.CargoTransport - local distA=transportA.pickupzone:GetCoordinate():Get2DDistance(coord) - local distB=transportB.pickupzone:GetCoordinate():Get2DDistance(coord) - return (transportA.prio=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then - cargotransport.status=OPSGROUP.TransportStatus.EXECUTING - self.cargoTransport=cargotransport - break - end - - end - + if not self.cargoTransport then + self.cargoTransport=self:_GetNextCargoTransport() end -- Now handle the transport. @@ -4718,6 +4668,7 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsUnloading() then + -- Debug info. self:I(self.lid.."Unloading ==> Checking if all cargo was delivered") local delivered=true @@ -4731,7 +4682,7 @@ function OPSGROUP:_CheckCargoTransport() end - -- Boarding finished ==> Transport cargo. + -- Unloading finished ==> pickup next batch or call it a day. if delivered then self:I(self.lid.."Unloading finished ==> Unloaded") self:Unloaded() @@ -4746,6 +4697,83 @@ function OPSGROUP:_CheckCargoTransport() return self end +--- Get cargo transport from cargo queue. +-- @param #OPSGROUP self +-- @return #OPSGROUP.CargoTransport The next due cargo transport or `nil`. +function OPSGROUP:_GetNextCargoTransport() + + -- Abs. mission time in seconds. + local Time=timer.getAbsTime() + + -- Current position. + local coord=self:GetCoordinate() + + -- Sort results table wrt prio and distance to pickup zone. + local function _sort(a, b) + local transportA=a --#OPSGROUP.CargoTransport + local transportB=b --#OPSGROUP.CargoTransport + local distA=transportA.pickupzone:GetCoordinate():Get2DDistance(coord) + local distB=transportB.pickupzone:GetCoordinate():Get2DDistance(coord) + return (transportA.prio=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then + return cargotransport + end + + end + + return nil +end + +--- Check if all cargo of this transport assignment was delivered. +-- @param #OPSGROUP self +-- @param #OPSGROUP.CargoTransport The next due cargo transport or `nil`. +-- @return #boolean If true, all cargo was delivered. +function OPSGROUP:_CheckDelivered(CargoTransport) + + -- TODO: Check if this group can actually transport any cargo. + + local done=true + for _,_cargo in pairs(CargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if cargo.delivered then + -- This one is delivered. + elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + -- This one is dead. + else + done=false --Someone is not done! + end + + --TODO: check if ALL remaining cargo is too heavy for this carrier group ==> el + + end + + if done then + self:Delivered(CargoTransport) + end + + -- Debug info. + self:I(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport.status, tostring(done))) + + return done +end + --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. @@ -4763,11 +4791,15 @@ function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Impo -- Create a new cargo transport assignment. local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) - -- Set state to SCHEDULED. - cargotransport.status=OPSGROUP.TransportStatus.SCHEDULED + if cargotransport then - --Add to cargo queue - table.insert(self.cargoqueue, cargotransport) + -- Set state to SCHEDULED. + cargotransport.status=OPSGROUP.TransportStatus.SCHEDULED + + --Add to cargo queue + table.insert(self.cargoqueue, cargotransport) + + end return cargotransport end @@ -4816,7 +4848,7 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I -- Data structure. local transport={} --#OPSGROUP.CargoTransport - transport.uid=1 + transport.uid=self.cargocounter transport.status=OPSGROUP.TransportStatus.PLANNING transport.pickupzone=Pickupzone transport.deployzone=Deployzone @@ -4830,15 +4862,27 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I -- Check type of GroupSet provided. if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then + -- We got a single GROUP or OPSGROUP objectg. local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) - table.insert(transport.cargos, cargo) - else - -- We got a SET_GROUP object. - for _,group in pairs(GroupSet.Set) do - local cargo=self:CreateCargoGroupData(group, Pickupzone, Deployzone) + + if cargo and self:CanCargo(cargo.opsgroup) then table.insert(transport.cargos, cargo) end + + else + + -- We got a SET_GROUP object. + + for _,group in pairs(GroupSet.Set) do + + local cargo=self:CreateCargoGroupData(group, Pickupzone, Deployzone) + + if cargo and self:CanCargo(cargo.opsgroup) then + table.insert(transport.cargos, cargo) + end + + end end -- Debug info. @@ -4856,7 +4900,13 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I self:I(self.lid..text) end - return transport + if #transport.cargos>0 then + self.cargocounter=self.cargocounter+1 + return transport + else + self:E(self.lid.."ERROR: Cargo too heavy for this carrier group!") + return nil + end end --- Create a cargo group data structure. @@ -4884,7 +4934,7 @@ function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) opsgroup=ARMYGROUP:New(group) end else - env.info("FF found opsgroup in createcargo") + --env.info("FF found opsgroup in createcargo") end end @@ -4997,6 +5047,25 @@ function OPSGROUP:RedWeightCargo(UnitName, Weight) return self end +--- Check if the group can be carrier of a cargo group. +-- **Note** that the cargo group *cannot* be split into units, i.e. the largest cargo bay of any element of the group must be able to load the whole cargo group in one piece. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. +-- @return #boolean If `true`, there is an element of the group that can load the whole cargo group. +function OPSGROUP:CanCargo(CargoGroup) + + local weight=CargoGroup:GetWeightTotal() + + for _,element in pairs(self.elements) do + local can=element.weightMaxCargo>=weight + if can then + return true + end + end + + return false +end + --- Add weight to the internal cargo of an element of the group. -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. @@ -5033,6 +5102,7 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) local airbasePickup=nil --Wrapper.Airbase#AIRBASE if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then + env.info("FF 001") airbasePickup=Zone:GetAirbase() end @@ -5041,16 +5111,21 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) if self:IsArmygroup() or self:IsNavygroup() then ready4loading=inzone else + env.info("FF 002 currbase="..(self.currbase and self.currbase:GetName() or "unknown")) + -- Aircraft is already parking at the pickup airbase. ready4loading=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() -- If a helo is landed in the zone, we also are ready for loading. - if ready4loading==false and self.isHelo and self:IsLandedAt() and inzone then + if ready4loading==false and (self.isHelo or self.isVTOL) and self:IsLandedAt() and inzone then ready4loading=true + env.info("FF 003") end end if ready4loading then + + env.info("FF 004") -- We are already in the pickup zone ==> initiate loading. self:Loading() @@ -5062,24 +5137,28 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Add waypoint. if self.isFlightgroup then - - --- - -- Pickup at airbase - --- if airbasePickup then + + --- + -- Pickup at airbase + --- local airbaseCurrent=self.currbase if airbaseCurrent then + + env.info("FF 100") -- Activate uncontrolled group. if self:IsParking() then + env.info("FF 200") self:StartUncontrolled() end else -- Order group to land at an airbase. + env.info("FF 300") self:LandAtAirbase(airbasePickup) end @@ -5286,7 +5365,7 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) ready2deploy=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() -- If a helo is landed in the zone, we also are ready for loading. - if ready2deploy==false and self.isHelo and self:IsLandedAt() and inzone then + if ready2deploy==false and (self.isHelo or self.isVTOL) and self:IsLandedAt() and inzone then ready2deploy=true end end @@ -5454,6 +5533,7 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading) --- OpsGroup:Unboard(Coordinate, Heading) + else --- @@ -5480,7 +5560,6 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Zone#ZONE Zone Deploy zone. function OPSGROUP:onafterUnloaded(From, Event, To) -- Debug info self:I(self.lid.."Cargo unloaded..") @@ -5519,8 +5598,8 @@ function OPSGROUP:onafterUnloaded(From, Event, To) -- This is not a carrier anymore. self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER - -- No current transport assignment. - self.cargoTransport=nil + -- Everything delivered. + self:Delivered(self.cargoTransport) -- Startup uncontrolled aircraft to allow it to go back. if self:IsFlightgroup() then @@ -5537,6 +5616,27 @@ function OPSGROUP:onafterUnloaded(From, Event, To) end +--- On after "Delivered" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.CargoTransport CargoTransport +function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) + + -- Set cargo status. + CargoTransport.status=OPSGROUP.TransportStatus.DELIVERED + + -- Check if this was the current transport. + if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then + self.cargoTransport=nil + end + + -- Remove cargo transport from cargo queue. + self:DelCargoTransport(CargoTransport) + +end + --- -- Cargo Group Functions @@ -5569,7 +5669,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) local Coordinate=Carrier.unit:GetCoordinate() if self.isArmygroup then - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true else local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) @@ -5642,6 +5742,9 @@ function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading, Delay) -- Template for the respawned group. local Template=UTILS.DeepCopy(self.template) --DCS#Template + + -- No late activation. + Template.lateActivation=false -- Loop over template units. for _,Unit in pairs(Template.units) do @@ -7869,11 +7972,25 @@ function OPSGROUP:_AddElementByName(unitname) -- Weight and cargo. element.weightEmpty=element.descriptors.massEmpty or 666 - element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+2*95 --If max mass is not given, we assume 10 soldiers. - element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) element.weightCargo=0 - element.weight=element.weightEmpty+element.weightCargo + element.weight=element.weightEmpty+element.weightCargo + -- Looks like only aircraft have a massMax value in the descriptors. + element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+2*95 --If max mass is not given, we assume 10 soldiers. + + + if self.isArmygroup then + + element.weightMaxTotal=element.weightEmpty+10*95 --If max mass is not given, we assume 10 soldiers. + + elseif self.isNavygroup then + + element.weightMaxTotal=element.weightEmpty+10*1000 + + end + + -- Max cargo weight + element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) -- FLIGHTGROUP specific. if self.isFlightgroup then From 8c55541d0e01dd3164bbedafcfb89720e4a0c396 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 8 Feb 2021 16:27:58 +0100 Subject: [PATCH 014/141] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 57 ++++++++++++------------ 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 3f9bd6cab..76448b6be 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4310,9 +4310,12 @@ function OPSGROUP:onafterElementDestroyed(From, Event, To, Element) -- Increase counter. self.Ndestroyed=self.Ndestroyed+1 + + -- Element is dead. + self:ElementDead(Element) -- Set element status. - self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) + --self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) end @@ -4323,7 +4326,7 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Element The flight group element. function OPSGROUP:onafterElementDead(From, Event, To, Element) - self:T(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime())) + self:I(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime())) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) @@ -4361,6 +4364,19 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end end end + + -- Check cargo bay and declare cargo groups dead + for groupname, carriername in pairs(self.cargoBay or {}) do + if Element.name==carriername then + local opsgroup=_DATABASE:GetOpsGroup(groupname) + if opsgroup and not (opsgroup:IsDead() or opsgroup:IsStopped()) then + for _,element in pairs(opsgroup.elements) do + env.info("FF cargo element dead "..element.name) + opsgroup:ElementDead(element) + end + end + end + end end @@ -4584,7 +4600,10 @@ function OPSGROUP:_CheckCargoTransport() -- Loop over cargo queue and check if everything was delivered. for i=#self.cargoqueue,1,-1 do local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport - self:_CheckDelivered(transport) + local delivered=self:_CheckDelivered(transport) + if delivered then + self:Delivered(transport) + end end -- Check if there is anything in the queue. @@ -4742,12 +4761,10 @@ end --- Check if all cargo of this transport assignment was delivered. -- @param #OPSGROUP self --- @param #OPSGROUP.CargoTransport The next due cargo transport or `nil`. +-- @param #OPSGROUP.CargoTransport CargoTransport The next due cargo transport or `nil`. -- @return #boolean If true, all cargo was delivered. function OPSGROUP:_CheckDelivered(CargoTransport) - -- TODO: Check if this group can actually transport any cargo. - local done=true for _,_cargo in pairs(CargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup @@ -4759,15 +4776,9 @@ function OPSGROUP:_CheckDelivered(CargoTransport) else done=false --Someone is not done! end - - --TODO: check if ALL remaining cargo is too heavy for this carrier group ==> el - + end - if done then - self:Delivered(CargoTransport) - end - -- Debug info. self:I(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport.status, tostring(done))) @@ -5453,7 +5464,8 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) local cargo=_cargo --#OPSGROUP.CargoGroup -- Check that cargo is loaded into this group. - if cargo.opsgroup:IsLoaded(self.groupname) then + -- TODO: Could be that the element carriing this cargo group is DEAD! + if cargo.opsgroup:IsLoaded(self.groupname) and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then env.info("FF deploy cargo "..cargo.opsgroup:GetName()) @@ -5570,21 +5582,10 @@ function OPSGROUP:onafterUnloaded(From, Event, To) self:TaskCancel(Task) end - -- Check if there is still cargo to pickup. - local pickup=false - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - -- Check for waiting or undelivered non cargo groups. - --if cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.WAITING or (cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.NOTCARGO and not cargo.delivered) then - if not cargo.delivered then - pickup=true - break - end - - end + -- Check everything was delivered (or is dead). + local delivered=self:_CheckDelivered(self.cargoTransport) - if pickup then + if not delivered then -- Pickup the next batch. self:I(self.lid.."Still cargo left ==> pickup") From 0656c33e0595a6ca18f8e32788979b93982a1749 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 10 Feb 2021 23:50:37 +0100 Subject: [PATCH 015/141] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 358 ++++++++++++----------- 1 file changed, 181 insertions(+), 177 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 76448b6be..68212dd62 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -431,6 +431,7 @@ OPSGROUP.TransportStatus={ -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. -- @field #OPSGROUP carrierGroup The new carrier group. +-- @field #OPSGROUP carrierGroupFrom The carrier group --- Cargo group data. -- @type OPSGROUP.CargoGroup @@ -575,8 +576,8 @@ function OPSGROUP:New(group) self:AddTransition("*", "ElementDead", "*") -- An element is dead. self:AddTransition("*", "ElementDamaged", "*") -- An element was damaged. - self:AddTransition("*", "Board", "*") -- Group is boarding a cargo carrier. - self:AddTransition("*", "Embark", "*") -- Group was loaded into a cargo carrier. + self:AddTransition("*", "Board", "*") -- Group is ordered to board the carrier. + self:AddTransition("*", "Embarked", "*") -- Group was loaded into a cargo carrier. self:AddTransition("InUtero", "Unboard", "*") -- Group was unloaded from a cargo carrier. self:AddTransition("*", "Pickup", "*") -- Carrier and is on route to pick up cargo. @@ -1039,13 +1040,13 @@ function OPSGROUP:GetVec3(UnitName) return nil end ---- Get current coordinate of the group. +--- Get current coordinate of the group. If the current position cannot be determined, the last known position is returned. -- @param #OPSGROUP self -- @param #boolean NewObject Create a new coordiante object. -- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. function OPSGROUP:GetCoordinate(NewObject) - local vec3=self:GetVec3() + local vec3=self:GetVec3() or self.position if vec3 then @@ -1062,7 +1063,7 @@ function OPSGROUP:GetCoordinate(NewObject) return self.coordinate end else - self:E(self.lid.."WARNING: Group is not alive. Cannot get coordinate!") + self:E(self.lid.."WARNING: Cannot get coordinate!") end return nil @@ -3009,7 +3010,7 @@ end function OPSGROUP:_GetNextMission() -- Check if group is acting as carrier or cargo at the moment. - if self:IsTransporting() or self:IsPickingup() or self:IsLoading() or self:IsLoaded() then + if self:IsTransporting() or self:IsPickingup() or self:IsLoading() or self:IsUnloading() or self:IsLoaded() then return nil end @@ -4796,11 +4797,12 @@ end -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. -- @param #OPSGROUP NewCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. +-- @param #OPSGROUP FromCarrierGroup (Optional) The OPSGROUP where the cargo loaded from. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) +function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup, FromCarrierGroup) -- Create a new cargo transport assignment. - local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) + local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup, FromCarrierGroup) if cargotransport then @@ -4843,8 +4845,9 @@ end -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. -- @param #OPSGROUP NewCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. +-- @param #OPSGROUP FromCarrierGroup (Optional) The OPSGROUP where the cargo loaded from. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) +function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup, FromCarrierGroup) -- Current mission time. local Tnow=timer.getAbsTime() @@ -4869,6 +4872,7 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I transport.importance=Importance transport.Tstart=Tstart transport.carrierGroup=NewCarrierGroup + transport.carrierGroupFrom=FromCarrierGroup transport.cargos={} -- Check type of GroupSet provided. @@ -5188,11 +5192,15 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") end elseif self.isNavygroup then + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + waypoint.detour=true + elseif self.isArmygroup then + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + end end @@ -5238,47 +5246,47 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - if cargo.opsgroup:IsNotCargo() and not cargo.delivered then + if not cargo.delivered then - -- Check if cargo is in pickup zone. - local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) + if cargo.opsgroup:IsNotCargo() then - -- First check if cargo is not delivered yet. - if inzone then - - local weight=cargo.opsgroup:GetWeightTotal() + -- Check if cargo is in pickup zone. + local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) - local carrier=_findCarrier(weight) + -- First check if cargo is not delivered yet. + if inzone then - if carrier then - - -- Decrease free cargo bay. - cargobay[carrier.name]=cargobay[carrier.name]-weight - - -- Set cargo status. - cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED - - -- Order cargo group to board the carrier. - cargo.opsgroup:Board(self, carrier) + local weight=cargo.opsgroup:GetWeightTotal() + local carrier=_findCarrier(weight) + + if carrier then + + -- Decrease free cargo bay. + cargobay[carrier.name]=cargobay[carrier.name]-weight + + -- Set cargo status. + cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED + + -- Order cargo group to board the carrier. + cargo.opsgroup:Board(self, carrier) + + else + + env.info("FF cannot board carrier") + + end + else - - env.info("FF cannot board carrier") - + env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) end - - else - env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) - end - else - - env.info("FF cargo already cargo or delivered") - - if not cargo.delivered then + elseif self.cargoTransport.carrierGroupFrom and cargo.opsgroup:IsLoaded(self.cargoTransport.carrierGroupFrom:GetName()) then end - + + else + env.info("FF cargo already delivered") end end @@ -5316,8 +5324,33 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) -- Fill cargo bay. self.cargoBay[CargoGroup.groupname]=carrier.name - -- Embark ==> Loaded. - CargoGroup:Embark(self, carrier) + --- + -- Embark Cargo + --- + + -- Debug info. + CargoGroup:I(CargoGroup.lid..string.format("New cargo status %s --> %s", CargoGroup.cargoStatus, OPSGROUP.CargoStatus.LOADED)) + + -- Set cargo status. + CargoGroup.cargoStatus=OPSGROUP.CargoStatus.LOADED + + -- Clear all waypoints. + for i=1,#CargoGroup.waypoints do + table.remove(CargoGroup.waypoints, i) + end + CargoGroup.waypoints={} + + -- Set carrier (again). + CargoGroup.carrier=carrier + CargoGroup.carrierGroup=self + + -- Despawn this group. + if CargoGroup:IsAlive() then + CargoGroup:Despawn(0, true) + end + + -- Trigger embarked event. + --CargoGroup:Embarked(self, Carrier) else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") @@ -5464,7 +5497,7 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) local cargo=_cargo --#OPSGROUP.CargoGroup -- Check that cargo is loaded into this group. - -- TODO: Could be that the element carriing this cargo group is DEAD! + -- NOTE: Could be that the element carriing this cargo group is DEAD, which would mean that the cargo group is also DEAD. if cargo.opsgroup:IsLoaded(self.groupname) and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then env.info("FF deploy cargo "..cargo.opsgroup:GetName()) @@ -5483,6 +5516,7 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) local carrier=carrierGroup:FindCarrierForCargo(cargo.opsgroup) if carrier then + -- Unload from this and directly load into the other carrier. self:Unload(cargo.opsgroup) carrierGroup:Load(cargo.opsgroup, carrier) else @@ -5496,13 +5530,20 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) else - -- Random coordinate in - local Coordinate=zone:GetRandomCoordinate() - local Heading=math.random(0,359) - - -- Unload. - env.info("FF unload cargo "..cargo.opsgroup:GetName()) - self:Unload(cargo.opsgroup, Coordinate, Heading) + + if not self.cargoTransport.inactiveUnload then + + -- Random coordinate/heading in the zone. + local Coordinate=zone:GetRandomCoordinate() + local Heading=math.random(0,359) + + -- Unload. + env.info("FF unload cargo "..cargo.opsgroup:GetName()) + self:Unload(cargo.opsgroup, Coordinate, Heading) + else + env.info("FF unload cargo Inactive "..cargo.opsgroup:GetName()) + self:Unload(cargo.opsgroup) + end end @@ -5533,35 +5574,78 @@ end -- @param #OPSGROUP OpsGroup The OPSGROUP loaded as cargo. -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. -- @param #number Heading Heading of group. -function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading) +-- @param #boolean Activated If true, group is active. If false, group is spawned in late activated state. +function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, Activated) -- Not in cargo bay any more. self.cargoBay[OpsGroup.groupname]=nil + + -- Reduce carrier weight. + local weight=OpsGroup:GetWeightTotal() + self:RedWeightCargo(OpsGroup.carrier.name, weight) + + + -- Debug info. + OpsGroup:I(OpsGroup.lid..string.format("New cargo status %s --> %s", OpsGroup.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) + + -- Set cargo status. + OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + + -- No carrier. + OpsGroup.carrier=nil + OpsGroup.carrierGroup=nil if Coordinate then --- -- Respawn at a coordinate. --- - - OpsGroup:Unboard(Coordinate, Heading) + + -- Template for the respawned group. + local Template=UTILS.DeepCopy(OpsGroup.template) --DCS#Template + -- No late activation. + Template.lateActivation=Activated + + -- Loop over template units. + for _,Unit in pairs(Template.units) do + + local element=OpsGroup:GetElementByName(Unit.name) + + if element then + + local vec3=element.vec3 + + -- Relative pos vector. + local rvec2={x=Unit.x-Template.x, y=Unit.y-Template.y} --DCS#Vec2 + + local cvec2={x=Coordinate.x, y=Coordinate.z} --DCS#Vec2 + + -- Position. + Unit.x=cvec2.x+rvec2.x + Unit.y=cvec2.y+rvec2.y + Unit.alt=land.getHeight({x=Unit.x, y=Unit.y}) + + -- Heading. + Unit.heading=Heading and math.rad(Heading) or Unit.heading + Unit.psi=-Unit.heading + + end + + end + + -- Respawn group. + OpsGroup:_Respawn(0, Template) + else --- -- Just remove from this carrier. --- - - -- Set cargo status. - OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - - -- Reduce carrier weight. - local weight=OpsGroup:GetWeightTotal() - self:RedWeightCargo(OpsGroup.carrier.name, weight) - -- No carrier. - OpsGroup.carrier=nil - OpsGroup.carrierGroup=nil + -- Nothing to do. + + OpsGroup.position=self:GetVec3() end @@ -5593,26 +5677,10 @@ function OPSGROUP:onafterUnloaded(From, Event, To) else - -- Debug info. - self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.NOTCARRIER)) - - -- This is not a carrier anymore. - self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER - -- Everything delivered. + self:I(self.lid.."Still ALL unloaded ==> delivered") self:Delivered(self.cargoTransport) - - -- Startup uncontrolled aircraft to allow it to go back. - if self:IsFlightgroup() then - if self:IsUncontrolled() then - self:StartUncontrolled() - end - end - - -- Check group done. - self:I(self.lid.."All cargo delivered ==> check group done") - self:_CheckGroupDone(0.1) - + end end @@ -5630,6 +5698,35 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Check if this was the current transport. if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then + + -- Debug info. + self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.NOTCARRIER)) + + -- This is not a carrier anymore. + self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER + + if self:IsPickingup() then + -- Delete pickup waypoint? + elseif self:IsLoading() then + -- Nothing to do? + elseif self:IsTransporting() then + -- This should not happen. Carrier is transporting, how can the cargo be delivered? + elseif self:IsUnloading() then + -- Nothing to do? + end + + -- Startup uncontrolled aircraft to allow it to go back. + if self:IsFlightgroup() then + if self:IsUncontrolled() then + self:StartUncontrolled() + end + end + + -- Check group done. + self:I(self.lid.."All cargo delivered ==> check group done") + self:_CheckGroupDone(0.1) + + -- No current transport any more. self.cargoTransport=nil end @@ -5662,7 +5759,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self.carrierGroup=CarrierGroup -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. - local board=self.speedMax>0 and self:IsLateActivated()==false and (self.isArmygroup or self.isNavygroup) + local board=self.speedMax>0 and self:IsLateActivated()==false and (self.isArmygroup or self.isNavygroup) and self.carrierGroup:IsLateActivated()==false if board then @@ -5680,105 +5777,12 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) else -- Trigger Load event in 10 seconds. - self.carrierGroup:__Load(10, self) + self.carrierGroup:Load(self) end end - ---- On after "Embark" event. --- @param #OPSGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #OPSGROUP CarrierGroup The carrier OPSGROUP. --- @param #OPSGROUP.Element Carrier The OPSGROUP element carriing this group. -function OPSGROUP:onafterEmbark(From, Event, To, CarrierGroup, Carrier) - -- Debug info. - self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.LOADED)) - - -- Set cargo status. - self.cargoStatus=OPSGROUP.CargoStatus.LOADED - - -- Clear all waypoints. - for i=1,#self.waypoints do - table.remove(self.waypoints, i) - end - self.waypoints={} - - -- Set carrier (again). - self.carrier=Carrier - self.carrierGroup=CarrierGroup - - -- Despawn this group. - if self:IsAlive() then - self:Despawn(0, true) - end - -end - ---- On after "Unboard" event. --- @param #OPSGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. Can also be a DCS#Vec3 object. --- @param #number Heading Heading the group has in degrees. Default is last known heading of the group. --- @param #number Delay Delay in seconds, before the group is respawned. -function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading, Delay) - -- Debug info. - self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) - - -- Set cargo status. - self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - - -- Reduce carrier weight. - local weight=self:GetWeightTotal() - self.carrierGroup:RedWeightCargo(self.carrier.name, weight) - - -- No carrier. - self.carrier=nil - self.carrierGroup=nil - - -- Template for the respawned group. - local Template=UTILS.DeepCopy(self.template) --DCS#Template - - -- No late activation. - Template.lateActivation=false - - -- Loop over template units. - for _,Unit in pairs(Template.units) do - - local element=self:GetElementByName(Unit.name) - - if element then - - local vec3=element.vec3 - - -- Relative pos vector. - local rvec2={x=Unit.x-Template.x, y=Unit.y-Template.y} --DCS#Vec2 - - local cvec2={x=Coordinate.x, y=Coordinate.z} --DCS#Vec2 - - -- Position. - Unit.x=cvec2.x+rvec2.x - Unit.y=cvec2.y+rvec2.y - Unit.alt=land.getHeight({x=Unit.x, y=Unit.y}) - - -- Heading. - Unit.heading=Heading and math.rad(Heading) or Unit.heading - Unit.psi=-Unit.heading - - end - - end - - -- Respawn group. - self:_Respawn(Delay or 0, Template) - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Internal Check Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 96b0393f91658b2ff7deaf4ee8c0a9d50399ede0 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 11 Feb 2021 23:58:56 +0100 Subject: [PATCH 016/141] OPS Cargo --- Moose Development/Moose/Ops/FlightGroup.lua | 8 +- Moose Development/Moose/Ops/OpsGroup.lua | 263 ++++++++++++-------- 2 files changed, 169 insertions(+), 102 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index f0743bb8c..64cdfea98 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1738,11 +1738,11 @@ end function FLIGHTGROUP:onafterLandedAt(From, Event, To) self:T(self.lid..string.format("Flight landed at")) - + -- Trigger (un-)loading process. if self:IsPickingup() then - self:Loading() + self:__Loading(-1) elseif self:IsTransporting() then - self:Deploy() + self:__Unloading(-1) end end @@ -1836,7 +1836,7 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) if self:IsPickingup() then self:__Loading(-1) elseif self:IsTransporting() then - self:__Deploy(-1) + self:__Unloading(-1) end else diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 68212dd62..f249f9a6d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -578,14 +578,14 @@ function OPSGROUP:New(group) self:AddTransition("*", "Board", "*") -- Group is ordered to board the carrier. self:AddTransition("*", "Embarked", "*") -- Group was loaded into a cargo carrier. - self:AddTransition("InUtero", "Unboard", "*") -- Group was unloaded from a cargo carrier. + self:AddTransition("*", "Disembarked", "*") -- Group was unloaded from a cargo carrier. self:AddTransition("*", "Pickup", "*") -- Carrier and is on route to pick up cargo. self:AddTransition("*", "Loading", "*") -- Carrier is loading cargo. self:AddTransition("*", "Load", "*") -- Carrier loads cargo into carrier. - self:AddTransition("*", "Loaded", "*") -- Carrier loaded all assigned cargo into carrier. + self:AddTransition("*", "Loaded", "*") -- Carrier loaded all assigned/possible cargo into carrier. self:AddTransition("*", "Transport", "*") -- Carrier is transporting cargo. - self:AddTransition("*", "Deploy", "*") -- Carrier is dropping off cargo. + self:AddTransition("*", "Unloading", "*") -- Carrier is unloading the cargo. self:AddTransition("*", "Unload", "*") -- Carrier unload a cargo group. self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its current cargo. self:AddTransition("*", "Delivered", "*") -- Carrier delivered ALL cargo of the transport assignment. @@ -613,10 +613,6 @@ function OPSGROUP:New(group) -- TODO: Add pseudo functions. - - -- Add to data base. - --_DATABASE:AddOpsGroup(self) - return self end @@ -4366,7 +4362,7 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end end - -- Check cargo bay and declare cargo groups dead + -- Check cargo bay and declare cargo groups dead. for groupname, carriername in pairs(self.cargoBay or {}) do if Element.name==carriername then local opsgroup=_DATABASE:GetOpsGroup(groupname) @@ -4574,29 +4570,37 @@ function OPSGROUP:_CheckCargoTransport() -- Abs. missin time in seconds. local Time=timer.getAbsTime() - -- Cargo queue info. - local text="Cargo bay:" - for cargogroupname, carriername in pairs(self.cargoBay) do - text=text..string.format("\n- %s in carrier %s", tostring(cargogroupname), tostring(carriername)) - end - self:I(self.lid..text) - - -- Cargo queue info. - local text="Cargo queue:" - for i,_transport in pairs(self.cargoqueue) do - local transport=_transport --#OPSGROUP.CargoTransport - text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport.status, transport.pickupzone:GetName(), transport.deployzone:GetName()) - for j,_cargo in pairs(transport.cargos) do - local cargo=_cargo --#OPSGROUP.CargoGroup - local state=cargo.opsgroup:GetState() - local status=cargo.opsgroup.cargoStatus - local name=cargo.opsgroup.groupname - local carriergroup=cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup.groupname or "N/A" - local carrierelement=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "N/A" - text=text..string.format("\n (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s", j, name, state, status, carriergroup, carrierelement, tostring(cargo.delivered)) + -- Cargo bay debug info. + if self.verbose>=0 then + local text="" + for cargogroupname, carriername in pairs(self.cargoBay) do + text=text..string.format("\n- %s in carrier %s", tostring(cargogroupname), tostring(carriername)) + end + if text~="" then + self:I(self.lid.."Cargo bay:"..text) + end + end + + -- Cargo queue debug info. + if self.verbose>=0 then + local text="Cargo queue:" + for i,_transport in pairs(self.cargoqueue) do + local transport=_transport --#OPSGROUP.CargoTransport + text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport.status, transport.pickupzone:GetName(), transport.deployzone:GetName()) + for j,_cargo in pairs(transport.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + local state=cargo.opsgroup:GetState() + local status=cargo.opsgroup.cargoStatus + local name=cargo.opsgroup.groupname + local carriergroup=cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup.groupname or "N/A" + local carrierelement=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "N/A" + text=text..string.format("\n (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s", j, name, state, status, carriergroup, carrierelement, tostring(cargo.delivered)) + end + end + if text~="" then + self:I(self.lid.."Cargo queue:"..text) end end - self:I(self.lid..text) -- Loop over cargo queue and check if everything was delivered. for i=#self.cargoqueue,1,-1 do @@ -4609,7 +4613,8 @@ function OPSGROUP:_CheckCargoTransport() -- Check if there is anything in the queue. if not self.cargoTransport then - self.cargoTransport=self:_GetNextCargoTransport() + self.cargoTransport=self:_GetNextCargoTransport() + self.cargoTransport.status=OPSGROUP.TransportStatus.EXECUTING end -- Now handle the transport. @@ -4634,6 +4639,7 @@ function OPSGROUP:_CheckCargoTransport() if self:IsNotCarrier() then + -- Debug info. self:I(self.lid.."Not carrier ==> pickup") --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. @@ -4650,7 +4656,11 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsLoading() then + -- Debug info. self:I(self.lid.."Loading...") + + -- Set loading time stamp. + --TODO: Check max loading time. If exceeded ==> abort transport. self.Tloading=self.Tloading or Time local boarding=false @@ -4706,6 +4716,8 @@ function OPSGROUP:_CheckCargoTransport() if delivered then self:I(self.lid.."Unloading finished ==> Unloaded") self:Unloaded() + else + self:Unloading() end end @@ -4717,6 +4729,45 @@ function OPSGROUP:_CheckCargoTransport() return self end +--- Add OPSGROUP to cargo bay of a carrier. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group. +-- @param #OPSGROUP.Element CarrierElement The element of the carrier. +function OPSGROUP:_AddCargobay(CargoGroup, CarrierElement) + + -- Cargo weight. + local weight=CargoGroup:GetWeightTotal() + + -- Add weight to carrier. + self:AddWeightCargo(CarrierElement.name, weight) + + -- Fill cargo bay. + self.cargoBay[CargoGroup.groupname]=CarrierElement.name + + return self +end + +--- Remove OPSGROUP from cargo bay of a carrier. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group. +-- @param #OPSGROUP.Element CarrierElement The element of the carrier. +function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) + + if self.cargoBay[CargoGroup.groupname] then + + -- Not in cargo bay any more. + self.cargoBay[CargoGroup.groupname]=nil + + -- Reduce carrier weight. + local weight=CargoGroup:GetWeightTotal() + self:RedWeightCargo(CargoGroup.carrier.name, weight) + + else + env.info("ERROR: Group is not in cargo bay. Cannot remove it!") + end + +end + --- Get cargo transport from cargo queue. -- @param #OPSGROUP self -- @return #OPSGROUP.CargoTransport The next due cargo transport or `nil`. @@ -4781,7 +4832,7 @@ function OPSGROUP:_CheckDelivered(CargoTransport) end -- Debug info. - self:I(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport.status, tostring(done))) + self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport.status, tostring(done))) return done end @@ -4844,10 +4895,9 @@ end -- @param #string ClockStart Start time in format "HH:MM:SS+D", e.g. "13:05:30" or "08:30+1". Can also be passed as a `#number` in which case it is interpreted as relative amount in seconds from now on. -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. --- @param #OPSGROUP NewCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. --- @param #OPSGROUP FromCarrierGroup (Optional) The OPSGROUP where the cargo loaded from. +-- @param #OPSGROUP DisembarkCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup, FromCarrierGroup) +function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) -- Current mission time. local Tnow=timer.getAbsTime() @@ -4871,8 +4921,7 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I transport.prio=Prio or 50 transport.importance=Importance transport.Tstart=Tstart - transport.carrierGroup=NewCarrierGroup - transport.carrierGroupFrom=FromCarrierGroup + transport.carrierGroup=DisembarkCarrierGroup transport.cargos={} -- Check type of GroupSet provided. @@ -5104,20 +5153,21 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Zone#ZONE Zone Pickup zone. -function OPSGROUP:onafterPickup(From, Event, To, Zone) +function OPSGROUP:onafterPickup(From, Event, To) -- Debug info. self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.PICKUP)) -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP + -- Pickup zone. + local Zone=self.cargoTransport.pickupzone + -- Check if already in the pickup zone. local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) local airbasePickup=nil --Wrapper.Airbase#AIRBASE if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then - env.info("FF 001") airbasePickup=Zone:GetAirbase() end @@ -5126,22 +5176,18 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) if self:IsArmygroup() or self:IsNavygroup() then ready4loading=inzone else - env.info("FF 002 currbase="..(self.currbase and self.currbase:GetName() or "unknown")) -- Aircraft is already parking at the pickup airbase. ready4loading=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() -- If a helo is landed in the zone, we also are ready for loading. - if ready4loading==false and (self.isHelo or self.isVTOL) and self:IsLandedAt() and inzone then + if ready4loading==false and self.isHelo and self:IsLandedAt() and inzone then ready4loading=true - env.info("FF 003") end end if ready4loading then - - env.info("FF 004") - + -- We are already in the pickup zone ==> initiate loading. self:Loading() @@ -5159,28 +5205,27 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Pickup at airbase --- + -- Current airbase. local airbaseCurrent=self.currbase if airbaseCurrent then - - env.info("FF 100") -- Activate uncontrolled group. if self:IsParking() then - env.info("FF 200") self:StartUncontrolled() end else + -- Order group to land at an airbase. - env.info("FF 300") self:LandAtAirbase(airbasePickup) + end - elseif self.isHelo or self.isVTOL then + elseif self.isHelo then --- - -- Helo or VTOL can also land in a zone + -- Helo can also land in a zone (NOTE: currently VTOL cannot!) --- -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. @@ -5315,14 +5360,8 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) if carrier then - -- Cargo weight. - local weight=CargoGroup:GetWeightTotal() - - -- Add weight to carrier. - self:AddWeightCargo(carrier.name, weight) - - -- Fill cargo bay. - self.cargoBay[CargoGroup.groupname]=carrier.name + -- Add into carrier bay. + self:_AddCargobay(CargoGroup, carrier) --- -- Embark Cargo @@ -5350,7 +5389,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) end -- Trigger embarked event. - --CargoGroup:Embarked(self, Carrier) + CargoGroup:Embarked(self, Carrier) else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") @@ -5382,8 +5421,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Zone#ZONE Zone Deploy zone. -function OPSGROUP:onafterTransport(From, Event, To, Zone) +function OPSGROUP:onafterTransport(From, Event, To) -- Debug info. self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.TRANSPORTING)) @@ -5391,7 +5429,10 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) self.carrierStatus=OPSGROUP.CarrierStatus.TRANSPORTING --TODO: This is all very similar to the onafterPickup() function. Could make it general. - + + -- Deploy zone. + local Zone=self.cargoTransport.deployzone + -- Check if already in deploy zone. local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) @@ -5416,8 +5457,8 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) if inzone then - -- We are already in the pickup zone ==> initiate loading. - self:Deploy(Zone) + -- We are already in deploy zone ==> initiate unloading. + self:Unloading() else @@ -5480,18 +5521,20 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) end ---- On after "Deploy" event. +--- On after "Unloading" event. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Zone#ZONE Zone Deploy zone. -function OPSGROUP:onafterDeploy(From, Event, To, Zone) +function OPSGROUP:onafterUnloading(From, Event, To) -- Debug info. self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.UNLOADING)) -- Set carrier status to UNLOADING. self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING + + -- Deploy zone. + local zone=self.cargoTransport.disembarkzone --Core.Zone#ZONE for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup @@ -5501,10 +5544,7 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) if cargo.opsgroup:IsLoaded(self.groupname) and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then env.info("FF deploy cargo "..cargo.opsgroup:GetName()) - - -- Deploy or disembark zone. - local zone=Zone or self.cargoTransport.disembarkzone --Core.Zone#ZONE - + -- New carrier group. local carrierGroup=self.cargoTransport.carrierGroup @@ -5513,6 +5553,11 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) if carrierGroup then + --- + -- Delivered to another carrier group. + --- + + -- Get new carrier. local carrier=carrierGroup:FindCarrierForCargo(cargo.opsgroup) if carrier then @@ -5524,12 +5569,19 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) end elseif zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then + + --- + -- Delivered to a ship via helo or VTOL + --- -- env.info("ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") else - + + --- + -- Delivered to deploy zone + --- if not self.cargoTransport.inactiveUnload then @@ -5574,16 +5626,11 @@ end -- @param #OPSGROUP OpsGroup The OPSGROUP loaded as cargo. -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. -- @param #number Heading Heading of group. --- @param #boolean Activated If true, group is active. If false, group is spawned in late activated state. +-- @param #boolean Activated If `true`, group is active. If `false`, group is spawned in late activated state. function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, Activated) - - -- Not in cargo bay any more. - self.cargoBay[OpsGroup.groupname]=nil - - -- Reduce carrier weight. - local weight=OpsGroup:GetWeightTotal() - self:RedWeightCargo(OpsGroup.carrier.name, weight) + -- Remove group from carrier bay. + self:_DelCargobay(OpsGroup) -- Debug info. OpsGroup:I(OpsGroup.lid..string.format("New cargo status %s --> %s", OpsGroup.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) @@ -5605,7 +5652,11 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, local Template=UTILS.DeepCopy(OpsGroup.template) --DCS#Template -- No late activation. - Template.lateActivation=Activated + if Activated==false then + Template.lateActivation=true + else + Template.lateActivation=false + end -- Loop over template units. for _,Unit in pairs(Template.units) do @@ -5758,27 +5809,43 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self.carrier=Carrier self.carrierGroup=CarrierGroup - -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. - local board=self.speedMax>0 and self:IsLateActivated()==false and (self.isArmygroup or self.isNavygroup) and self.carrierGroup:IsLateActivated()==false + -- Army or navy group. + local isArmyOrNavy=CarrierGroup:IsArmygroup() or CarrierGroup:IsNavygroup() - if board then + -- Check that carrier is standing still. + if isArmyOrNavy and CarrierGroup:IsHolding() or CarrierGroup:IsParking() or CarrierGroup:IsLandedAt() then - -- TODO: Implement embarkzone. - local Coordinate=Carrier.unit:GetCoordinate() - - if self.isArmygroup then - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. + local board=(self.speedMax>0) and (not self:IsLateActivated()) and (self.isArmygroup or self.isNavygroup) and (not self.carrierGroup:IsLateActivated()) + + if board then + + -- Debug info. + self:I(self.lid..string.format("Boarding group=%s [%s], carrier=%s", CarrierGroup:GetName(), CarrierGroup:GetState(), Carrier.name)) + + -- TODO: Implement embarkzone. + local Coordinate=Carrier.unit:GetCoordinate() + + if self.isArmygroup then + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) + waypoint.detour=true + else + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) + waypoint.detour=true + end + else - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + + env.info("FF board with direct load") + + -- Trigger Load event. + self.carrierGroup:Load(self) + end else - - -- Trigger Load event in 10 seconds. - self.carrierGroup:Load(self) - + env.info("FF Carrier not ready for boarding yet ==> repeating boarding call in 10 sec") + self:__Board(-10, CarrierGroup, Carrier) end end @@ -6543,7 +6610,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else -- Stop and loading. opsgroup:FullStop() - opsgroup:Deploy() + opsgroup:Unloading() end elseif opsgroup:IsBoarding() then From 74e84a73ddd12aa02ad96392cd5aa0c92f74faad Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 13 Feb 2021 00:09:25 +0100 Subject: [PATCH 017/141] OPS Cargo --- Moose Development/Moose/Ops/FlightGroup.lua | 12 ++-- Moose Development/Moose/Ops/OpsGroup.lua | 65 +++++++++++++-------- 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 64cdfea98..ba95760fc 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -187,16 +187,12 @@ FLIGHTGROUP.version="0.6.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: VTOL aircraft. --- TODO: Use new UnitLost event instead of crash/dead. --- TODO: Options EPLRS, Afterburner restrict etc. --- DONE: Add TACAN beacon. --- TODO: Damage? --- TODO: shot events? --- TODO: Marks to add waypoints/tasks on-the-fly. -- TODO: Mark assigned parking spot on F10 map. -- TODO: Let user request a parking spot via F10 marker :) --- TODO: Monitor traveled distance in air ==> calculate fuel consumption ==> calculate range remaining. Will this give half way accurate results? --- TODO: Out of AG/AA missiles. Safe state of out-of-ammo. +-- DONE: Use new UnitLost event instead of crash/dead. +-- DONE: Monitor traveled distance in air ==> calculate fuel consumption ==> calculate range remaining. Will this give half way accurate results? +-- DONE: Out of AG/AA missiles. Safe state of out-of-ammo. +-- DONE: Add TACAN beacon. -- DONE: Add tasks. -- DONE: Waypoints, read, add, insert, detour. -- DONE: Get ammo. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index f249f9a6d..fe5ea4e1e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -425,13 +425,12 @@ OPSGROUP.TransportStatus={ -- @field #string status Status of the transport. See @{#OPSGROUP.TransportStatus}. -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). -- @field #number importance Importance of this transport. Smaller=higher. --- @field #number Tstart Start time in abs. seconds. +-- @field #number Tstart Start time in *abs.* seconds. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. -- @field #OPSGROUP carrierGroup The new carrier group. --- @field #OPSGROUP carrierGroupFrom The carrier group --- Cargo group data. -- @type OPSGROUP.CargoGroup @@ -453,6 +452,10 @@ OPSGROUP.version="0.7.1" -- TODO: Invisible/immortal. -- TODO: F10 menu. -- TODO: Add pseudo function. +-- TODO: Options EPLRS, Afterburner restrict etc. +-- TODO: Damage? +-- TODO: Shot events? +-- TODO: Marks to add waypoints/tasks on-the-fly. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -4583,7 +4586,7 @@ function OPSGROUP:_CheckCargoTransport() -- Cargo queue debug info. if self.verbose>=0 then - local text="Cargo queue:" + local text="" for i,_transport in pairs(self.cargoqueue) do local transport=_transport --#OPSGROUP.CargoTransport text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport.status, transport.pickupzone:GetName(), transport.deployzone:GetName()) @@ -4614,7 +4617,6 @@ function OPSGROUP:_CheckCargoTransport() -- Check if there is anything in the queue. if not self.cargoTransport then self.cargoTransport=self:_GetNextCargoTransport() - self.cargoTransport.status=OPSGROUP.TransportStatus.EXECUTING end -- Now handle the transport. @@ -4641,19 +4643,15 @@ function OPSGROUP:_CheckCargoTransport() -- Debug info. self:I(self.lid.."Not carrier ==> pickup") - - --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. -- Initiate the cargo transport process. - self:Pickup(self.cargoTransport.pickupzone) + self:Pickup() elseif self:IsPickingup() then -- Debug Info. self:T(self.lid.."Picking up...") - - --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. - + elseif self:IsLoading() then -- Debug info. @@ -4723,8 +4721,6 @@ function OPSGROUP:_CheckCargoTransport() end end - - -- TODO: Remove delivered transports from cargo queue! return self end @@ -4763,7 +4759,7 @@ function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) self:RedWeightCargo(CargoGroup.carrier.name, weight) else - env.info("ERROR: Group is not in cargo bay. Cannot remove it!") + env.error("ERROR: Group is not in cargo bay. Cannot remove it!") end end @@ -4803,6 +4799,7 @@ function OPSGROUP:_GetNextCargoTransport() local cargotransport=_cargotransport --#OPSGROUP.CargoTransport if Time>=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then + cargotransport.status=OPSGROUP.TransportStatus.EXECUTING return cargotransport end @@ -4847,13 +4844,12 @@ end -- @param #string ClockStart Start time in format "HH:MM(:SS)(+D)", e.g. "13:05:30" or "08:30+1". Can also be given as a `#number`, in which case it is interpreted as relative amount in seconds from now on. -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. --- @param #OPSGROUP NewCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. --- @param #OPSGROUP FromCarrierGroup (Optional) The OPSGROUP where the cargo loaded from. +-- @param #OPSGROUP DisembarkCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup, FromCarrierGroup) +function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) -- Create a new cargo transport assignment. - local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup, FromCarrierGroup) + local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) if cargotransport then @@ -5189,7 +5185,10 @@ function OPSGROUP:onafterPickup(From, Event, To) if ready4loading then -- We are already in the pickup zone ==> initiate loading. - self:Loading() + if (self:IsArmygroup() or self:IsNavygroup()) and not self:IsHolding() then + self:FullStop() + end + self:__Loading(-5) else @@ -5252,6 +5251,15 @@ function OPSGROUP:onafterPickup(From, Event, To) end +--- On before "Loading" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onbeforeLoading(From, Event, To) + +end + --- On after "Loading" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -5325,9 +5333,7 @@ function OPSGROUP:onafterLoading(From, Event, To) else env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) end - - elseif self.cargoTransport.carrierGroupFrom and cargo.opsgroup:IsLoaded(self.cargoTransport.carrierGroupFrom:GetName()) then - + end else @@ -5699,6 +5705,9 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, OpsGroup.position=self:GetVec3() end + + -- Trigger "Disembarked" event. + OpsGroup:Disembarked() end @@ -5816,7 +5825,12 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) if isArmyOrNavy and CarrierGroup:IsHolding() or CarrierGroup:IsParking() or CarrierGroup:IsLandedAt() then -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. - local board=(self.speedMax>0) and (not self:IsLateActivated()) and (self.isArmygroup or self.isNavygroup) and (not self.carrierGroup:IsLateActivated()) + local board=self.speedMax>0 and self:IsAlive() and isArmyOrNavy and self.carrierGroup:IsAlive() + + -- Armygroup cannot board ship ==> Load directly. + if self:IsArmygroup() and self.carrierGroup:IsNavygroup() then + board=false + end if board then @@ -6595,7 +6609,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else -- Stop and loading. opsgroup:FullStop() - opsgroup:Loading() + opsgroup:__Loading(-5) end elseif opsgroup:IsTransporting() then @@ -8062,7 +8076,10 @@ function OPSGROUP:_AddElementByName(unitname) end -- Max cargo weight - element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) + --element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) + + unit:SetCargoBayWeightLimit() + element.weightMaxCargo=unit.__.CargoBayWeightLimit -- FLIGHTGROUP specific. if self.isFlightgroup then From 084499fa0ebecff7018bcee3fbc2bd54bb4c9a8e Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 14 Feb 2021 00:19:03 +0100 Subject: [PATCH 018/141] OPS Wait --- Moose Development/Moose/Ops/ArmyGroup.lua | 19 +++ Moose Development/Moose/Ops/FlightGroup.lua | 3 +- Moose Development/Moose/Ops/NavyGroup.lua | 19 +++ Moose Development/Moose/Ops/OpsGroup.lua | 136 ++++++++++++++++---- 4 files changed, 148 insertions(+), 29 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 0e062ef69..70928e07e 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -575,6 +575,21 @@ function ARMYGROUP:onafterSpawned(From, Event, To) end +--- On before "UpdateRoute" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number n Waypoint number. Default is next waypoint. +-- @param #number Speed Speed in knots. Default cruise speed. +-- @param #number Formation Formation of the group. +function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Formation) + if self:IsWaiting() then + return false + end + return true +end + --- On after "UpdateRoute" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -1028,6 +1043,10 @@ end -- @param #number Formation Formation. function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation) + -- Not waiting anymore. + self.Twaiting=nil + self.dTwait=nil + self:__UpdateRoute(-1, nil, Speed, Formation) end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ba95760fc..cb03b0085 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -140,6 +140,7 @@ FLIGHTGROUP = { flaghold = nil, Tholding = nil, Tparking = nil, + Twaiting = nil, menu = nil, isHelo = nil, } @@ -251,8 +252,6 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "LandAt", "LandingAt") -- Helo group is ordered to land at a specific point. self:AddTransition("LandingAt", "LandedAt", "LandedAt") -- Helo group landed landed at a specific point. - self:AddTransition("*", "Wait", "*") -- Group is orbiting. - self:AddTransition("*", "FuelLow", "*") -- Fuel state of group is low. Default ~25%. self:AddTransition("*", "FuelCritical", "*") -- Fuel state of group is critical. Default ~10%. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 38e66f59c..210e5d856 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -694,6 +694,21 @@ function NAVYGROUP:onafterSpawned(From, Event, To) end +--- On before "UpdateRoute" event. +-- @param #NAVYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number n Waypoint number. Default is next waypoint. +-- @param #number Speed Speed in knots to the next waypoint. +-- @param #number Depth Depth in meters to the next waypoint. +function NAVYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Depth) + if self:IsWaiting() then + return false + end + return true +end + --- On after "UpdateRoute" event. -- @param #NAVYGROUP self -- @param #string From From state. @@ -962,6 +977,10 @@ end -- @param #number Speed Speed in knots until next waypoint is reached. Default is speed set for waypoint. function NAVYGROUP:onafterCruise(From, Event, To, Speed) + -- Not waiting anymore. + self.Twaiting=nil + self.dTwait=nil + -- No set depth. self.depth=nil diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index fe5ea4e1e..8c10af305 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -41,6 +41,8 @@ -- @field Core.Zone#ZONE destzone The destination zone of the flight group. Set when final waypoint is in air. -- @field #number currentwp Current waypoint index. This is the index of the last passed waypoint. -- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. +-- @field #number Twaiting Abs. mission time stamp when the group was ordered to wait. +-- @field #number dTwait Time to wait in seconds. Default `nil` (for ever). -- @field #table taskqueue Queue of tasks. -- @field #number taskcounter Running number of task ids. -- @field #number taskcurrent ID of current task. If 0, there is no current task assigned. @@ -530,6 +532,8 @@ function OPSGROUP:New(group) self:AddTransition("*", "UpdateRoute", "*") -- Update route of group. Only if airborne. self:AddTransition("*", "PassingWaypoint", "*") -- Passing waypoint. + + self:AddTransition("*", "Wait", "*") -- Group will wait for further orders. self:AddTransition("*", "DetectedUnit", "*") -- Unit was detected (again) in this detection cycle. self:AddTransition("*", "DetectedUnitNew", "*") -- Add a newly detected unit to the detected units set. @@ -1045,7 +1049,17 @@ end -- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. function OPSGROUP:GetCoordinate(NewObject) - local vec3=self:GetVec3() or self.position + local vec3=nil --DCS#Vec3 + + -- TODO: get vec3 of carrier group. move this stuff to GetVec3 + if self.carrier and self.carrier.status~=OPSGROUP.ElementStatus.DEAD then + -- Get the carrier position. + vec3=self.carrier.unit:GetVec3() + else + self:GetVec3() + end + + vec3=vec3 or self.position if vec3 then @@ -3009,7 +3023,7 @@ end function OPSGROUP:_GetNextMission() -- Check if group is acting as carrier or cargo at the moment. - if self:IsTransporting() or self:IsPickingup() or self:IsLoading() or self:IsUnloading() or self:IsLoaded() then + if self:IsPickingup() or self:IsLoading() or self:IsTransporting() or self:IsUnloading() or self:IsLoaded() then return nil end @@ -3514,7 +3528,7 @@ function OPSGROUP:_QueueUpdate() -- Mission --- - -- First check if group is alive? Late activated groups are activated and uncontrolled units are started automatically. + -- First check if group is alive? Late activated groups are activated and uncontrolled units are started automatically. if self:IsExist() then local mission=self:_GetNextMission() @@ -3568,6 +3582,34 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- On after "PassingWaypoint" event. +-- @param #OPSGROUP self +-- @param #boolean If true, group is currently waiting. +function OPSGROUP:IsWaiting() + if self.Twaiting then + return true + end + return false +end + +--- On after "Wait" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number Duration Duration in seconds how long the group will be waiting. Default `nil` (for ever). +function OPSGROUP:onafterWait(From, Event, To, Duration) + + -- Order Group to hold. + self:FullStop() + + self.Twaiting=timer.getAbsTime() + + self.dTwait=Duration + +end + + --- On after "PassingWaypoint" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -4574,7 +4616,7 @@ function OPSGROUP:_CheckCargoTransport() local Time=timer.getAbsTime() -- Cargo bay debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text="" for cargogroupname, carriername in pairs(self.cargoBay) do text=text..string.format("\n- %s in carrier %s", tostring(cargogroupname), tostring(carriername)) @@ -4585,7 +4627,7 @@ function OPSGROUP:_CheckCargoTransport() end -- Cargo queue debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text="" for i,_transport in pairs(self.cargoqueue) do local transport=_transport --#OPSGROUP.CargoTransport @@ -4623,7 +4665,7 @@ function OPSGROUP:_CheckCargoTransport() if self.cargoTransport then -- Debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup @@ -5184,10 +5226,12 @@ function OPSGROUP:onafterPickup(From, Event, To) if ready4loading then - -- We are already in the pickup zone ==> initiate loading. + -- We are already in the pickup zone ==> wait and initiate loading. if (self:IsArmygroup() or self:IsNavygroup()) and not self:IsHolding() then - self:FullStop() + self:Wait() end + + -- Start loading. self:__Loading(-5) else @@ -5239,12 +5283,16 @@ function OPSGROUP:onafterPickup(From, Event, To) local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + + self:__Cruise(-2) elseif self.isArmygroup then local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + self:__Cruise(-2) + end end @@ -5294,14 +5342,15 @@ function OPSGROUP:onafterLoading(From, Event, To) end return nil end - for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup if not cargo.delivered then - - if cargo.opsgroup:IsNotCargo() then + + -- Check that group is not cargo already and not busy. + -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() + if cargo.opsgroup:IsNotCargo() and not (cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading() or cargo.opsgroup:IsLoaded()) then -- Check if cargo is in pickup zone. local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) @@ -5344,6 +5393,16 @@ function OPSGROUP:onafterLoading(From, Event, To) end +--- Clear waypoints. +-- @param #OPSGROUP self +function OPSGROUP:ClearWaypoints() + -- Clear all waypoints. + for i=1,#self.waypoints do + table.remove(self.waypoints, i) + end + self.waypoints={} +end + --- On after "Load" event. Carrier loads a cargo group into ints cargo bay. -- @param #OPSGROUP self -- @param #string From From state. @@ -5380,10 +5439,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) CargoGroup.cargoStatus=OPSGROUP.CargoStatus.LOADED -- Clear all waypoints. - for i=1,#CargoGroup.waypoints do - table.remove(CargoGroup.waypoints, i) - end - CargoGroup.waypoints={} + CargoGroup:ClearWaypoints() -- Set carrier (again). CargoGroup.carrier=carrier @@ -5463,9 +5519,14 @@ function OPSGROUP:onafterTransport(From, Event, To) if inzone then - -- We are already in deploy zone ==> initiate unloading. - self:Unloading() - + -- We are already in the pickup zone ==> wait and initiate unloading. + if (self:IsArmygroup() or self:IsNavygroup()) and not self:IsHolding() then + self:Wait() + end + + -- Start loading. + self:__UnLoading(-5) + else -- Get a random coordinate in the deploy zone and let the carrier go there. @@ -5515,11 +5576,17 @@ function OPSGROUP:onafterTransport(From, Event, To) local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + -- Give cruise command. + self:Cruise() + elseif self.isNavygroup then -- NAVYGROUP local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + + -- Give cruise command. + self:Cruise() end @@ -5591,8 +5658,10 @@ function OPSGROUP:onafterUnloading(From, Event, To) if not self.cargoTransport.inactiveUnload then + local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) + -- Random coordinate/heading in the zone. - local Coordinate=zone:GetRandomCoordinate() + local Coordinate=zoneCarrier:GetRandomCoordinate(20) local Heading=math.random(0,359) -- Unload. @@ -5839,18 +5908,24 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) -- TODO: Implement embarkzone. local Coordinate=Carrier.unit:GetCoordinate() + + -- Clear all waypoints. + self:ClearWaypoints() if self.isArmygroup then local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + self:Cruise() else local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + self:Cruise() end else - env.info("FF board with direct load") + -- Debug info. + self:I(self.lid..string.format("FF board with direct load to carrier %s", self.carrierGroup:GetName())) -- Trigger Load event. self.carrierGroup:Load(self) @@ -6033,10 +6108,17 @@ function OPSGROUP:_CheckGroupDone(delay) self:ScheduleOnce(delay, self._CheckGroupDone, self) else + -- Group is engaging something. if self:IsEngaging() then self:UpdateRoute() return end + + -- Group is waiting. We deny all updates. + if self:IsWaiting() then + -- If group is waiting, we assume that is the way it is meant to be. + return + end -- Get current waypoint. local waypoint=self:GetWaypoint(self.currentwp) @@ -6069,11 +6151,10 @@ function OPSGROUP:_CheckGroupDone(delay) -- Get positive speed to first waypoint. local speed=self:GetSpeedToWaypoint(i) - -- Start route at first waypoint. - --self:UpdateRoute(i, speed) - + -- Cruise. self:Cruise(speed) + -- Debug info. self:T(self.lid..string.format("Adinfinitum=TRUE ==> Goto WP index=%d at speed=%d knots", i, speed)) else @@ -6181,7 +6262,7 @@ function OPSGROUP:_CheckDamage() self.life=0 local damaged=false for _,_element in pairs(self.elements) do - local element=_element --Ops.OpsGroup#OPSGROUP + local element=_element --Ops.OpsGroup#OPSGROUP.Element -- Current life points. local life=element.unit:GetLife() @@ -6413,7 +6494,8 @@ function OPSGROUP:_AddWaypoint(waypoint, wpnumber) -- Switch to cruise mode. if self:IsHolding() then - self:Cruise() + -- Disable this for now. Cruise has to be commanded manually now. If group is ordered to hold, it will hold until told to move again. + --self:Cruise() end end @@ -6608,7 +6690,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else -- Stop and loading. - opsgroup:FullStop() + opsgroup:Wait() opsgroup:__Loading(-5) end @@ -6623,7 +6705,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else -- Stop and loading. - opsgroup:FullStop() + opsgroup:Wait() opsgroup:Unloading() end From 5990ab1cc98c32ffddd331c7488632985991c895 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 15 Feb 2021 23:19:35 +0100 Subject: [PATCH 019/141] OPSTRANSPORT - New class. --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/NavyGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 112 +++++----- Moose Development/Moose/Ops/OpsTransport.lua | 209 +++++++++++++++++++ 4 files changed, 273 insertions(+), 51 deletions(-) create mode 100644 Moose Development/Moose/Ops/OpsTransport.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 95d211370..8a941f0db 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -83,6 +83,7 @@ __Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 210e5d856..9f8f4ba9d 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -582,7 +582,7 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Recovery Windows --- - if self.verbose>=2 then + if self.verbose>=2 and #self.Qintowind>0 then -- Debug output: local text=string.format(self.lid.."Turn into wind time windows:") diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8c10af305..d49774f96 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -375,7 +375,6 @@ OPSGROUP.TaskType={ --- Cargo Carrier status. -- @type OPSGROUP.CarrierStatus -- @field #string NOTCARRIER This group is not a carrier yet. --- @field #string EMPTY Carrier is empty and ready for cargo transport. -- @field #string PICKUP Carrier is on its way to pickup cargo. -- @field #string LOADING Carrier is loading cargo. -- @field #string LOADED Carrier has loaded cargo. @@ -383,7 +382,6 @@ OPSGROUP.TaskType={ -- @field #string UNLOADING Carrier is unloading cargo. OPSGROUP.CarrierStatus={ NOTCARRIER="not carrier", - EMPTY="empty", PICKUP="pickup", LOADING="loading", LOADED="loaded", @@ -394,18 +392,14 @@ OPSGROUP.CarrierStatus={ --- Cargo status. -- @type OPSGROUP.CargoStatus -- @field #string NOTCARGO This group is no cargo yet. --- @field #string WAITING Cargo is awaiting transporter. -- @field #string ASSIGNED Cargo is assigned to a carrier. -- @field #string BOARDING Cargo is boarding a carrier. -- @field #string LOADED Cargo is loaded into a carrier. --- @field #string DELIVERED Cargo was delivered at its destination. OPSGROUP.CargoStatus={ NOTCARGO="not cargo", - WAITING="waiting for carrier", ASSIGNED="assigned to carrier", BOARDING="boarding", LOADED="loaded", - DELIVERED="delivered", } --- Cargo transport status. @@ -1023,6 +1017,17 @@ end -- @return DCS#Vec3 Vector with x,y,z components. function OPSGROUP:GetVec3(UnitName) + local vec3=nil --DCS#Vec3 + + -- First check if this group is loaded into a carrier + if self.carrier and self.carrier.status~=OPSGROUP.ElementStatus.DEAD and self:IsLoaded() then + local unit=self.carrier.unit + if unit and unit:IsAlive()~=nil then + vec3=unit:GetVec3() + return vec3 + end + end + if self:IsExist() then local unit=nil --DCS#Unit @@ -1049,18 +1054,8 @@ end -- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. function OPSGROUP:GetCoordinate(NewObject) - local vec3=nil --DCS#Vec3 + local vec3=self:GetVec3() or self.position --DCS#Vec3 - -- TODO: get vec3 of carrier group. move this stuff to GetVec3 - if self.carrier and self.carrier.status~=OPSGROUP.ElementStatus.DEAD then - -- Get the carrier position. - vec3=self.carrier.unit:GetVec3() - else - self:GetVec3() - end - - vec3=vec3 or self.position - if vec3 then self.coordinate=self.coordinate or COORDINATE:New(0,0,0) @@ -4646,16 +4641,7 @@ function OPSGROUP:_CheckCargoTransport() self:I(self.lid.."Cargo queue:"..text) end end - - -- Loop over cargo queue and check if everything was delivered. - for i=#self.cargoqueue,1,-1 do - local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport - local delivered=self:_CheckDelivered(transport) - if delivered then - self:Delivered(transport) - end - end - + -- Check if there is anything in the queue. if not self.cargoTransport then self.cargoTransport=self:_GetNextCargoTransport() @@ -4696,12 +4682,12 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsLoading() then - -- Debug info. - self:I(self.lid.."Loading...") - -- Set loading time stamp. --TODO: Check max loading time. If exceeded ==> abort transport. - self.Tloading=self.Tloading or Time + self.Tloading=self.Tloading or Time + + -- Debug info. + self:I(self.lid.."Loading...") local boarding=false local gotcargo=false @@ -4763,6 +4749,15 @@ function OPSGROUP:_CheckCargoTransport() end end + + -- Loop over cargo queue and check if everything was delivered. + for i=#self.cargoqueue,1,-1 do + local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport + local delivered=self:_CheckDelivered(transport) + if delivered then + self:Delivered(transport) + end + end return self end @@ -4773,6 +4768,8 @@ end -- @param #OPSGROUP.Element CarrierElement The element of the carrier. function OPSGROUP:_AddCargobay(CargoGroup, CarrierElement) + --TODO: Check group is not already in cargobay of this carrier or any other carrier. + -- Cargo weight. local weight=CargoGroup:GetWeightTotal() @@ -4789,6 +4786,7 @@ end -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group. -- @param #OPSGROUP.Element CarrierElement The element of the carrier. +-- @return #boolean If `true`, cargo could be removed. function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) if self.cargoBay[CargoGroup.groupname] then @@ -4799,11 +4797,12 @@ function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) -- Reduce carrier weight. local weight=CargoGroup:GetWeightTotal() self:RedWeightCargo(CargoGroup.carrier.name, weight) - - else - env.error("ERROR: Group is not in cargo bay. Cannot remove it!") + + return true end + env.error(self.lid.."ERROR: Group is not in cargo bay. Cannot remove it!") + return false end --- Get cargo transport from cargo queue. @@ -4840,7 +4839,7 @@ function OPSGROUP:_GetNextCargoTransport() for _,_cargotransport in pairs(self.cargoqueue) do local cargotransport=_cargotransport --#OPSGROUP.CargoTransport - if Time>=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then + if Time>=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then cargotransport.status=OPSGROUP.TransportStatus.EXECUTING return cargotransport end @@ -4887,11 +4886,11 @@ end -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. -- @param #OPSGROUP DisembarkCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. --- @return #OPSGROUP.CargoTransport Cargo transport. +-- @return Ops.OpsTransport#OPSTRANSPORT Cargo transport. function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) -- Create a new cargo transport assignment. - local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) + local cargotransport=OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) if cargotransport then @@ -4906,6 +4905,19 @@ function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Impo return cargotransport end +--- Create a cargo transport assignment. +-- @param #OPSGROUP self +-- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The troop transport assignment. +function OPSGROUP:AddOpsTransport(OpsTransport) + + -- Set state to SCHEDULED. + OpsTransport.status=OPSTRANSPORT.Status.SCHEDULED + + --Add to cargo queue + table.insert(self.cargoqueue, OpsTransport) + +end + --- Delete a cargo transport assignment from the cargo queue -- @param #OPSGROUP self -- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport do be deleted. @@ -5468,7 +5480,7 @@ function OPSGROUP:onafterLoaded(From, Event, To) env.info("FF loaded") -- Cancel landedAt task. - if self.isFlightgroup and self:IsLandedAt() then + if self:IsFlightgroup() and self:IsLandedAt() then local Task=self:GetTaskCurrent() self:TaskCancel(Task) end @@ -5661,12 +5673,12 @@ function OPSGROUP:onafterUnloading(From, Event, To) local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) -- Random coordinate/heading in the zone. - local Coordinate=zoneCarrier:GetRandomCoordinate(20) + local Coordinate=zoneCarrier:GetRandomCoordinate(50) local Heading=math.random(0,359) -- Unload. env.info("FF unload cargo "..cargo.opsgroup:GetName()) - self:Unload(cargo.opsgroup, Coordinate, Heading) + self:Unload(cargo.opsgroup, Coordinate, nil, Heading) else env.info("FF unload cargo Inactive "..cargo.opsgroup:GetName()) self:Unload(cargo.opsgroup) @@ -5700,9 +5712,9 @@ end -- @param #string To To state. -- @param #OPSGROUP OpsGroup The OPSGROUP loaded as cargo. -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. --- @param #number Heading Heading of group. -- @param #boolean Activated If `true`, group is active. If `false`, group is spawned in late activated state. -function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, Activated) +-- @param #number Heading (Optional) Heading of group in degrees. Default is random heading for each unit. +function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated, Heading) -- Remove group from carrier bay. self:_DelCargobay(OpsGroup) @@ -5713,10 +5725,6 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, -- Set cargo status. OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - -- No carrier. - OpsGroup.carrier=nil - OpsGroup.carrierGroup=nil - if Coordinate then --- @@ -5776,7 +5784,11 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, end -- Trigger "Disembarked" event. - OpsGroup:Disembarked() + OpsGroup:Disembarked(OpsGroup.carrierGroup, OpsGroup.carrier) + + -- No carrier any more. + OpsGroup.carrier=nil + OpsGroup.carrierGroup=nil end @@ -5790,7 +5802,7 @@ function OPSGROUP:onafterUnloaded(From, Event, To) self:I(self.lid.."Cargo unloaded..") -- Cancel landedAt task. - if self.isFlightgroup and self:IsLandedAt() then + if self:IsFlightgroup() and self:IsLandedAt() then local Task=self:GetTaskCurrent() self:TaskCancel(Task) end @@ -5801,13 +5813,13 @@ function OPSGROUP:onafterUnloaded(From, Event, To) if not delivered then -- Pickup the next batch. - self:I(self.lid.."Still cargo left ==> pickup") + self:I(self.lid.."Unloaded: Still cargo left ==> Pickup") self:Pickup(self.cargoTransport.pickupzone) else -- Everything delivered. - self:I(self.lid.."Still ALL unloaded ==> delivered") + self:I(self.lid.."Unloaded: ALL cargo unloaded ==> Delivered (current)") self:Delivered(self.cargoTransport) end diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua new file mode 100644 index 000000000..2c3f452c6 --- /dev/null +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -0,0 +1,209 @@ +--- **Ops** - Troop transport assignment of OPS groups. +-- +-- ## Main Features: +-- +-- * Patrol waypoints *ad infinitum* +-- +-- === +-- +-- ## Example Missions: +-- +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Armygroup). +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- == +-- +-- @module Ops.OpsTransport +-- @image OPS_OpsTransport.png + + +--- OPSTRANSPORT class. +-- @type OPSTRANSPORT +-- @field #string ClassName Name of the class. +-- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. +-- @field #string status Status of the transport. See @{#OPSTRANSPORT.Status}. +-- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). +-- @field #number importance Importance of this transport. Smaller=higher. +-- @field #number Tstart Start time in *abs.* seconds. +-- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. +-- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. +-- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. +-- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. +-- @field Ops.OpsGroup#OPSGROUP carrierGroup The new carrier group. +-- @extends Core.Fsm#FSM + +--- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge +-- +-- === +-- +-- ![Banner Image](..\Presentations\OPS\ArmyGroup\_Main.png) +-- +-- # The OPSTRANSPORT Concept +-- +-- This class enhances naval groups. +-- +-- @field #OPSTRANSPORT +OPSTRANSPORT = { + ClassName = "OPSTRANSPORT", + verbose = 1, + cargos = {}, +} + +--- Cargo transport status. +-- @type OPSTRANSPORT.Status +-- @field #string PLANNING Planning state. +-- @field #string SCHEDULED Transport is scheduled in the cargo queue. +-- @field #string EXECUTING Transport is being executed. +-- @field #string DELIVERED Transport was delivered. +OPSTRANSPORT.Status={ + PLANNING="planning", + SCHEDULED="scheduled", + EXECUTING="executing", + DELIVERED="delivered", +} + +_OPSTRANSPORTID=0 + +--- Army Group version. +-- @field #string version +OPSTRANSPORT.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new OPSTRANSPORT class object. Essential input are the troops that should be transported and the zones where the troops are picked up and deployed. +-- @param #OPSTRANSPORT self +-- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! +-- @param Core.Zone#ZONE Deployzone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #OPSTRANSPORT + + -- Set some string id for output to DCS.log file. + self.lid=string.format("OPSTRANSPORT %s --> %s | ", Pickupzone:GetName(), Deployzone:GetName()) + + _OPSTRANSPORTID=_OPSTRANSPORTID+1 + + self.uid=_OPSTRANSPORTID + self.status=OPSTRANSPORT.Status.PLANNING + self.pickupzone=Pickupzone + self.deployzone=Deployzone + self.embarkzone=Pickupzone + self.disembarkzone=Deployzone + self.prio=50 + self.importance=nil + self.Tstart=timer.getAbsTime() + self.carrierGroup=nil + self.cargos={} + + + -- Check type of GroupSet provided. + if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then + + -- We got a single GROUP or OPSGROUP objectg. + local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + + if cargo then --and self:CanCargo(cargo.opsgroup) + table.insert(self.cargos, cargo) + end + + else + + -- We got a SET_GROUP object. + + for _,group in pairs(GroupSet.Set) do + + local cargo=self:_CreateCargoGroupData(group, Pickupzone, Deployzone) + + if cargo then --and self:CanCargo(cargo.opsgroup) then + table.insert(self.cargos, cargo) + end + + end + end + + -- Debug info. + if self.verbose>=0 then + local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", + self.uid, self.pickupzone:GetName(), self.embarkzone:GetName(), self.deployzone:GetName(), self.disembarkzone:GetName()) + local Weight=0 + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + local weight=cargo.opsgroup:GetWeightTotal() + Weight=Weight+weight + text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) + end + text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #self.cargos, Weight) + self:I(self.lid..text) + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new OPSTRANSPORT class object. +-- @param #OPSTRANSPORT self +-- @param Core.Zone#ZONE EmbarkZone Zone where the troops are embarked. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetEmbarkZone(EmbarkZone) + self.embarkzone=EmbarkZone or self.pickupzone + return self +end + + +--- Create a cargo group data structure. +-- @param #OPSTRANSPORT self +-- @param Wrapper.Group#GROUP group The GROUP object. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. +-- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @return #OPSGROUP.CargoGroup Cargo group data. +function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) + + local opsgroup=nil + + if group:IsInstanceOf("OPSGROUP") then + opsgroup=group + else + + opsgroup=_DATABASE:GetOpsGroup(group) + + if not opsgroup then + if group:IsAir() then + opsgroup=FLIGHTGROUP:New(group) + elseif group:IsShip() then + opsgroup=NAVYGROUP:New(group) + else + opsgroup=ARMYGROUP:New(group) + end + else + --env.info("FF found opsgroup in createcargo") + end + + end + + local cargo={} --#OPSGROUP.CargoGroup + + cargo.opsgroup=opsgroup + cargo.delivered=false + cargo.status="Unknown" + cargo.pickupzone=Pickupzone + cargo.deployzone=Deployzone + + return cargo +end From dcf1a56756116aeb832fa760649d884e95ca2131 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 16 Feb 2021 00:15:22 +0100 Subject: [PATCH 020/141] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 36 +++++++++++++++--------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index d49774f96..67b6f5ea1 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4839,7 +4839,7 @@ function OPSGROUP:_GetNextCargoTransport() for _,_cargotransport in pairs(self.cargoqueue) do local cargotransport=_cargotransport --#OPSGROUP.CargoTransport - if Time>=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then + if Time>=cargotransport.Tstart and cargotransport.status~=OPSGROUP.TransportStatus.DELIVERED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then cargotransport.status=OPSGROUP.TransportStatus.EXECUTING return cargotransport end @@ -4859,12 +4859,16 @@ function OPSGROUP:_CheckDelivered(CargoTransport) for _,_cargo in pairs(CargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - if cargo.delivered then - -- This one is delivered. - elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then - -- This one is dead. - else - done=false --Someone is not done! + if self:CanCargo(cargo.opsgroup) then + + if cargo.delivered then + -- This one is delivered. + elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + -- This one is dead. + else + done=false --Someone is not done! + end + end end @@ -5168,13 +5172,17 @@ end -- @return #boolean If `true`, there is an element of the group that can load the whole cargo group. function OPSGROUP:CanCargo(CargoGroup) - local weight=CargoGroup:GetWeightTotal() - - for _,element in pairs(self.elements) do - local can=element.weightMaxCargo>=weight - if can then - return true + if CargoGroup then + + local weight=CargoGroup:GetWeightTotal() + + for _,element in pairs(self.elements) do + local can=element.weightMaxCargo>=weight + if can then + return true + end end + end return false @@ -5358,7 +5366,7 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - if not cargo.delivered then + if self:CanCargo(cargo.opsgroup) and not cargo.delivered then -- Check that group is not cargo already and not busy. -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() From 2c92bb9d619fd294b096a36b52d0e65dec4076a7 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 17 Feb 2021 00:29:59 +0100 Subject: [PATCH 021/141] OPS Cargo --- Moose Development/Moose/Ops/OpsGroup.lua | 27 +-- Moose Development/Moose/Ops/OpsTransport.lua | 189 ++++++++++++++++++- 2 files changed, 196 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 67b6f5ea1..ee91b7867 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -438,7 +438,7 @@ OPSGROUP.TransportStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="0.7.1" +OPSGROUP.version="0.7.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -4625,8 +4625,8 @@ function OPSGROUP:_CheckCargoTransport() if self.verbose>=1 then local text="" for i,_transport in pairs(self.cargoqueue) do - local transport=_transport --#OPSGROUP.CargoTransport - text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport.status, transport.pickupzone:GetName(), transport.deployzone:GetName()) + local transport=_transport --#Ops.OpsTransport#OPSTRANSPORT + text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport:GetState(), transport.pickupzone:GetName(), transport.deployzone:GetName()) for j,_cargo in pairs(transport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup local state=cargo.opsgroup:GetState() @@ -4837,10 +4837,11 @@ function OPSGROUP:_GetNextCargoTransport() -- Find next transport assignment. for _,_cargotransport in pairs(self.cargoqueue) do - local cargotransport=_cargotransport --#OPSGROUP.CargoTransport + local cargotransport=_cargotransport --Ops.OpsTransport#OPSTRANSPORT - if Time>=cargotransport.Tstart and cargotransport.status~=OPSGROUP.TransportStatus.DELIVERED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then - cargotransport.status=OPSGROUP.TransportStatus.EXECUTING + if Time>=cargotransport.Tstart and cargotransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then + cargotransport:Executing() + cargotransport:SetCarrierTransportStatus(self, OPSTRANSPORT.Status.EXECUTING) return cargotransport end @@ -4851,7 +4852,7 @@ end --- Check if all cargo of this transport assignment was delivered. -- @param #OPSGROUP self --- @param #OPSGROUP.CargoTransport CargoTransport The next due cargo transport or `nil`. +-- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport The next due cargo transport or `nil`. -- @return #boolean If true, all cargo was delivered. function OPSGROUP:_CheckDelivered(CargoTransport) @@ -4874,7 +4875,7 @@ function OPSGROUP:_CheckDelivered(CargoTransport) end -- Debug info. - self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport.status, tostring(done))) + self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport:GetState(), tostring(done))) return done end @@ -4914,8 +4915,9 @@ end -- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The troop transport assignment. function OPSGROUP:AddOpsTransport(OpsTransport) - -- Set state to SCHEDULED. - OpsTransport.status=OPSTRANSPORT.Status.SCHEDULED + -- Add this group as carrier for the transport. + OpsTransport:_AddCarrier(self) + --Add to cargo queue table.insert(self.cargoqueue, OpsTransport) @@ -5868,7 +5870,10 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) if self:IsFlightgroup() then if self:IsUncontrolled() then self:StartUncontrolled() - end + elseif self:IsLandedAt() then + local Task=self:GetTaskCurrent() + self:TaskCancel(Task) + end end -- Check group done. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 2c3f452c6..bc361e92a 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -23,7 +23,11 @@ --- OPSTRANSPORT class. -- @type OPSTRANSPORT -- @field #string ClassName Name of the class. +-- @field #string lid Log ID. +-- @field #number uid Unique ID of the transport. +-- @field #number verbose Verbosity level. -- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. +-- @field #table carriers Carriers assigned for this transport. -- @field #string status Status of the transport. See @{#OPSTRANSPORT.Status}. -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). -- @field #number importance Importance of this transport. Smaller=higher. @@ -50,6 +54,8 @@ OPSTRANSPORT = { ClassName = "OPSTRANSPORT", verbose = 1, cargos = {}, + carriers = {}, + carrierTransportStatus = {}, } --- Cargo transport status. @@ -59,17 +65,18 @@ OPSTRANSPORT = { -- @field #string EXECUTING Transport is being executed. -- @field #string DELIVERED Transport was delivered. OPSTRANSPORT.Status={ - PLANNING="planning", + PLANNED="planned", SCHEDULED="scheduled", EXECUTING="executing", DELIVERED="delivered", } +--- Transport ID. _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.1" +OPSTRANSPORT.version="0.0.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -92,10 +99,10 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #OPSTRANSPORT + _OPSTRANSPORTID=_OPSTRANSPORTID+1 + -- Set some string id for output to DCS.log file. - self.lid=string.format("OPSTRANSPORT %s --> %s | ", Pickupzone:GetName(), Deployzone:GetName()) - - _OPSTRANSPORTID=_OPSTRANSPORTID+1 + self.lid=string.format("OPSTRANSPORT [UID=%d] %s --> %s | ", _OPSTRANSPORTID, Pickupzone:GetName(), Deployzone:GetName()) self.uid=_OPSTRANSPORTID self.status=OPSTRANSPORT.Status.PLANNING @@ -107,14 +114,16 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self.importance=nil self.Tstart=timer.getAbsTime() self.carrierGroup=nil - self.cargos={} + self.cargos={} + self.carriers={} + -- Check type of GroupSet provided. if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then - -- We got a single GROUP or OPSGROUP objectg. - local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + -- We got a single GROUP or OPSGROUP object. + local cargo=self:_CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) if cargo then --and self:CanCargo(cargo.opsgroup) table.insert(self.cargos, cargo) @@ -149,6 +158,22 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #self.cargos, Weight) self:I(self.lid..text) end + + + -- FMS start state is PLANNED. + self:SetStartState(OPSTRANSPORT.Status.PLANNED) + + -- PLANNED --> SCHEDULED --> EXECUTING --> DELIVERED + self:AddTransition("*", "Planned", OPSTRANSPORT.Status.PLANNED) -- Cargo transport was planned. + self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. + self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. + self:AddTransition(OPSTRANSPORT.Status.EXECUTING, "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. + + self:AddTransition("*", "Status", "*") + self:AddTransition("*", "Stop", "*") + + -- Call status update + self:__Status(-1) return self end @@ -157,7 +182,7 @@ end -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new OPSTRANSPORT class object. +--- Set embark zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE EmbarkZone Zone where the troops are embarked. -- @return #OPSTRANSPORT self @@ -166,6 +191,42 @@ function OPSTRANSPORT:SetEmbarkZone(EmbarkZone) return self end +--- Add a carrier assigned for this transport. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:_AddCarrier(CarrierGroup) + + self:SetCarrierTransportStatus(CarrierGroup, OPSTRANSPORT.Status.SCHEDULED) + + self:Scheduled() + + table.insert(self.carriers, CarrierGroup) + + return self +end + +--- Add a carrier assigned for this transport. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. +-- @param #string Status Carrier Status. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetCarrierTransportStatus(CarrierGroup, Status) + + self.carrierTransportStatus[CarrierGroup.groupname]=Status + + return self +end + +--- Get carrier transport status. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. +-- @return #string Carrier status. +function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup) + return self.carrierTransportStatus[CarrierGroup.groupname] +end + + --- Create a cargo group data structure. -- @param #OPSTRANSPORT self @@ -207,3 +268,113 @@ function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) return cargo end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Status Update +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "Status" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterStatus(From, Event, To) + + local fsmstate=self:GetState() + + local text=string.format("State=%s", fsmstate) + + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + end + + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier + end + + self:I(self.lid..text) + + -- Check if all cargo was delivered (or is dead). + self:_CheckDelivered() + + self:__Status(-30) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Event Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "Planned" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterPlanned(From, Event, To) + self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.PLANNED)) +end + +--- On after "Scheduled" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterScheduled(From, Event, To) + self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.SCHEDULED)) +end + +--- On after "Executing" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterExecuting(From, Event, To) + self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.EXECUTING)) +end + +--- On after "Delivered" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterDelivered(From, Event, To) + self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.DELIVERED)) + + -- TODO: Inform all assigned carriers that cargo was delivered. They can have this in the queue or are currently processing this transport. + + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + if self:GetCarrierTransportStatus(carrier)~=OPSTRANSPORT.Status.DELIVERED then + carrier:Delivered(self) + end + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +--- Check if all cargo of this transport assignment was delivered. +-- @param #OPSTRANSPORT self +function OPSTRANSPORT:_CheckDelivered() + + local done=true + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if cargo.delivered then + -- This one is delivered. + elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + -- This one is dead. + else + done=false --Someone is not done! + end + + end + + if done then + self:Delivered() + end + +end From c0401447dda855554847da18982bf55def39edab Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 18 Feb 2021 00:10:17 +0100 Subject: [PATCH 022/141] OPS Cargo --- Moose Development/Moose/Ops/OpsGroup.lua | 52 ++++--- Moose Development/Moose/Ops/OpsTransport.lua | 153 +++++++++++++------ 2 files changed, 132 insertions(+), 73 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ee91b7867..7dbeabb77 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -108,7 +108,7 @@ -- @field #OPSGROUP carrierGroup Carrier group transporting this group as cargo. -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #table cargoBay Table containing OPSGROUP loaded into this group. --- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. +-- @field Ops.OpsTransport#OPSTRANSPORT cargoTransport Current cargo transport assignment. -- @field #string cargoStatus Cargo status of this group acting as cargo. -- @field #string carrierStatus Carrier status of this group acting as cargo carrier. -- @field #number cargocounter Running number of cargo UIDs. @@ -2999,7 +2999,7 @@ function OPSGROUP:CountRemainingTransports() -- Loop over mission queue. for _,_transport in pairs(self.cargoqueue) do - local transport=_transport --#OPSGROUP.CargoTransport + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT -- Count not delivered (executing or scheduled) assignments. if transport and transport.status~=OPSGROUP.TransportStatus.DELIVERED then @@ -4752,7 +4752,7 @@ function OPSGROUP:_CheckCargoTransport() -- Loop over cargo queue and check if everything was delivered. for i=#self.cargoqueue,1,-1 do - local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport + local transport=self.cargoqueue[i] --Ops.OpsTransport#OPSTRANSPORT local delivered=self:_CheckDelivered(transport) if delivered then self:Delivered(transport) @@ -4807,7 +4807,7 @@ end --- Get cargo transport from cargo queue. -- @param #OPSGROUP self --- @return #OPSGROUP.CargoTransport The next due cargo transport or `nil`. +-- @return Ops.OpsTransport#OPSTRANSPORT The next due cargo transport or `nil`. function OPSGROUP:_GetNextCargoTransport() -- Abs. mission time in seconds. @@ -4818,8 +4818,8 @@ function OPSGROUP:_GetNextCargoTransport() -- Sort results table wrt prio and distance to pickup zone. local function _sort(a, b) - local transportA=a --#OPSGROUP.CargoTransport - local transportB=b --#OPSGROUP.CargoTransport + local transportA=a --Ops.OpsTransport#OPSTRANSPORT + local transportB=b --Ops.OpsTransport#OPSTRANSPORT local distA=transportA.pickupzone:GetCoordinate():Get2DDistance(coord) local distB=transportB.pickupzone:GetCoordinate():Get2DDistance(coord) return (transportA.prio %s", OpsGroup.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) @@ -5841,7 +5845,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #OPSGROUP.CargoTransport CargoTransport +-- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Set cargo status. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index bc361e92a..56f28b878 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -2,13 +2,13 @@ -- -- ## Main Features: -- --- * Patrol waypoints *ad infinitum* +-- * Transport troops from A to B. -- -- === -- -- ## Example Missions: -- --- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Armygroup). +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Transport). -- -- === -- @@ -32,22 +32,25 @@ -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). -- @field #number importance Importance of this transport. Smaller=higher. -- @field #number Tstart Start time in *abs.* seconds. +-- @field #number Tstop Stop time in *abs.* seconds. Default `#nil` (never stops). -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. -- @field Ops.OpsGroup#OPSGROUP carrierGroup The new carrier group. +-- @field disembarkActivation Activation setting when group is disembared from carrier. +-- @field disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. -- @extends Core.Fsm#FSM ---- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge +--- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill -- -- === -- --- ![Banner Image](..\Presentations\OPS\ArmyGroup\_Main.png) +-- ![Banner Image](..\Presentations\OPS\Transport\_Main.png) -- -- # The OPSTRANSPORT Concept -- --- This class enhances naval groups. +-- Transport OPSGROUPS using carriers such as APCs, helicopters or airplanes. -- -- @field #OPSTRANSPORT OPSTRANSPORT = { @@ -99,11 +102,13 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #OPSTRANSPORT + -- Increase ID counter. _OPSTRANSPORTID=_OPSTRANSPORTID+1 -- Set some string id for output to DCS.log file. self.lid=string.format("OPSTRANSPORT [UID=%d] %s --> %s | ", _OPSTRANSPORTID, Pickupzone:GetName(), Deployzone:GetName()) + -- Defaults. self.uid=_OPSTRANSPORTID self.status=OPSTRANSPORT.Status.PLANNING self.pickupzone=Pickupzone @@ -112,53 +117,15 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self.disembarkzone=Deployzone self.prio=50 self.importance=nil - self.Tstart=timer.getAbsTime() + self.Tstart=timer.getAbsTime()+5 self.carrierGroup=nil self.cargos={} self.carriers={} - - - -- Check type of GroupSet provided. - if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then - - -- We got a single GROUP or OPSGROUP object. - local cargo=self:_CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) - - if cargo then --and self:CanCargo(cargo.opsgroup) - table.insert(self.cargos, cargo) - end - - else - - -- We got a SET_GROUP object. - - for _,group in pairs(GroupSet.Set) do - - local cargo=self:_CreateCargoGroupData(group, Pickupzone, Deployzone) - - if cargo then --and self:CanCargo(cargo.opsgroup) then - table.insert(self.cargos, cargo) - end - - end + if GroupSet then + self:AddCargoGroups(GroupSet, Pickupzone, Deployzone) end - -- Debug info. - if self.verbose>=0 then - local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", - self.uid, self.pickupzone:GetName(), self.embarkzone:GetName(), self.deployzone:GetName(), self.disembarkzone:GetName()) - local Weight=0 - for _,_cargo in pairs(self.cargos) do - local cargo=_cargo --#OPSGROUP.CargoGroup - local weight=cargo.opsgroup:GetWeightTotal() - Weight=Weight+weight - text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) - end - text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #self.cargos, Weight) - self:I(self.lid..text) - end - -- FMS start state is PLANNED. self:SetStartState(OPSTRANSPORT.Status.PLANNED) @@ -182,6 +149,56 @@ end -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Add cargo groups to be transported. +-- @param #OPSTRANSPORT self +-- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be passed as a single GROUP or OPSGROUP object. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) + + -- Check type of GroupSet provided. + if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then + + -- We got a single GROUP or OPSGROUP object. + local cargo=self:_CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + + if cargo then --and self:CanCargo(cargo.opsgroup) + table.insert(self.cargos, cargo) + end + + else + + -- We got a SET_GROUP object. + + for _,group in pairs(GroupSet.Set) do + + local cargo=self:_CreateCargoGroupData(group, Pickupzone, Deployzone) + + if cargo then + table.insert(self.cargos, cargo) + end + + end + end + + -- Debug info. + if self.verbose>=0 then + local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", + self.uid, self.pickupzone:GetName(), self.embarkzone:GetName(), self.deployzone:GetName(), self.disembarkzone:GetName()) + local Weight=0 + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + local weight=cargo.opsgroup:GetWeightTotal() + Weight=Weight+weight + text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) + end + text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #self.cargos, Weight) + self:I(self.lid..text) + end + + + return self +end + --- Set embark zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE EmbarkZone Zone where the troops are embarked. @@ -191,6 +208,43 @@ function OPSTRANSPORT:SetEmbarkZone(EmbarkZone) return self end +--- Set disembark zone. +-- @param #OPSTRANSPORT self +-- @param Core.Zone#ZONE DisembarkZone Zone where the troops are disembarked. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetDisembarkZone(DisembarkZone) + self.disembarkzone=DisembarkZone or self.deployzone + return self +end + +--- Set activation status of group when disembarked from transport carrier. +-- @param #OPSTRANSPORT self +-- @param #boolean Active If `true` or `nil`, group is activated when disembarked. If `false`, group is late activated and needs to be activated manually. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetDisembarkActivation(Active) + if Active==true or Active==nil then + self.disembarkActivation=true + else + self.disembarkActivation=false + end + return self +end + + +--- Set if group remains *in utero* after disembarkment from carrier. Can be used to directly load the group into another carrier. Similar to disembark in late activated state. +-- @param #OPSTRANSPORT self +-- @param #boolean InUtero If `true` or `nil`, group remains *in utero* after disembarkment. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetDisembarkInUtero(InUtero) + if InUtero==true or InUtero==nil then + self.disembarkInUtero=true + else + self.disembarkInUtero=false + end + return self +end + + --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. @@ -240,7 +294,7 @@ function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) if group:IsInstanceOf("OPSGROUP") then opsgroup=group - else + elseif group:IsInstanceOf("GROUP") then opsgroup=_DATABASE:GetOpsGroup(group) @@ -252,10 +306,11 @@ function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) else opsgroup=ARMYGROUP:New(group) end - else - --env.info("FF found opsgroup in createcargo") end + else + self:E(self.lid.."ERROR: Cargo must be a GROUP or OPSGROUP object!") + return nil end local cargo={} --#OPSGROUP.CargoGroup From c50f6b72e7c62f8d802dbf9daddd7580135a4216 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 19 Feb 2021 00:11:55 +0100 Subject: [PATCH 023/141] OPS Cargo --- Moose Development/Moose/Ops/ArmyGroup.lua | 5 + Moose Development/Moose/Ops/OpsGroup.lua | 96 +++++++++++++------- Moose Development/Moose/Ops/OpsTransport.lua | 94 ++++++++++++++++--- 3 files changed, 149 insertions(+), 46 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 70928e07e..f758ff079 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1082,10 +1082,15 @@ end -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + env.info("FF Current waypoint index="..self.currentwp) + env.info("FF Add waypoint after index="..(AfterWaypointWithID or "nil")) + local coordinate=self:_CoordinateFromObject(Coordinate) -- Set waypoint index. local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) + + env.info("FF Add waypoint index="..wpnumber) -- Check if final waypoint is still passed. if wpnumber>self.currentwp then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 7dbeabb77..3f3dc427b 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -5303,15 +5303,17 @@ function OPSGROUP:onafterPickup(From, Event, To) end elseif self.isNavygroup then - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + -- Navy Group + + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true self:__Cruise(-2) elseif self.isArmygroup then - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + -- Army Group + + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true self:__Cruise(-2) @@ -5496,7 +5498,7 @@ function OPSGROUP:onafterLoaded(From, Event, To) end -- Order group to transport. - self:__Transport(1, self.cargoTransport.deployzone) + self:__Transport(1) end @@ -5551,8 +5553,16 @@ function OPSGROUP:onafterTransport(From, Event, To) else - -- Get a random coordinate in the deploy zone and let the carrier go there. - local Coordinate=Zone:GetRandomCoordinate() + -- Coord where the carrier goes to unload. + local Coordinate=nil --Core.Point#COORDINATE + + if self.cargoTransport.carrierGroup and self.cargoTransport.carrierGroup:IsLoading() then + -- Coordinate of the new carrier. + Coordinate=self.cargoTransport.carrierGroup:GetCoordinate() + else + -- Get a random coordinate in the deploy zone and let the carrier go there. + Coordinate=Zone:GetRandomCoordinate() + end -- Add waypoint. if self.isFlightgroup then @@ -5594,9 +5604,8 @@ function OPSGROUP:onafterTransport(From, Event, To) elseif self.isArmygroup then - -- ARMYGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + -- ARMYGROUP + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true -- Give cruise command. self:Cruise() @@ -5604,8 +5613,7 @@ function OPSGROUP:onafterTransport(From, Event, To) elseif self.isNavygroup then -- NAVYGROUP - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true -- Give cruise command. self:Cruise() @@ -5629,7 +5637,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING -- Deploy zone. - local zone=self.cargoTransport.disembarkzone --Core.Zone#ZONE + local zone=self.cargoTransport.disembarkzone or self.cargoTransport.deployzone --Core.Zone#ZONE for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup @@ -5663,7 +5671,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) env.info("ERROR: No element of the group can take this cargo!") end - elseif zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then + elseif zone and zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then --- -- Delivered to a ship via helo or VTOL @@ -5684,20 +5692,37 @@ function OPSGROUP:onafterUnloading(From, Event, To) self:Unload(cargo.opsgroup) else + + local Coordinate=nil - -- TODO: honor disembark zone! + if self.cargoTransport.disembarkzone then + + -- Random coordinate in disembark zone. + Coordinate=self.cargoTransport.disembarkzone:GetRandomCoordinate() + + else - local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) - - -- Random coordinate/heading in the zone. - local Coordinate=zoneCarrier:GetRandomCoordinate(50) + -- TODO: Optimize with random Vec2 + + -- Create a zone around the carrier. + local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) + + -- Random coordinate/heading in the zone. + Coordinate=zoneCarrier:GetRandomCoordinate(50) + + end + + -- Random heading of the group. local Heading=math.random(0,359) - -- Unload. + -- Unload to Coordinate. self:Unload(cargo.opsgroup, Coordinate, self.cargoTransport.disembarkActivation, Heading) end - + + -- Trigger "Unloaded" event for current cargo transport + self.cargoTransport:Unloaded(cargo.opsgroup) + end end @@ -5799,7 +5824,7 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated -- Trigger "Disembarked" event. OpsGroup:Disembarked(OpsGroup.carrierGroup, OpsGroup.carrier) - + -- No carrier any more. OpsGroup.carrier=nil OpsGroup.carrierGroup=nil @@ -5882,7 +5907,8 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Check group done. self:I(self.lid.."All cargo delivered ==> check group done") - self:_CheckGroupDone(0.1) + self:__Cruise(0.1) + self:_CheckGroupDone(0.2) -- No current transport any more. self.cargoTransport=nil @@ -6718,7 +6744,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) else - -- Stop and loading. + -- Wait and load cargo. opsgroup:Wait() opsgroup:__Loading(-5) end @@ -6733,8 +6759,8 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) else - -- Stop and loading. - opsgroup:Wait() + -- Stop and unload. + opsgroup:Wait() opsgroup:Unloading() end @@ -8101,15 +8127,19 @@ end -- @return Core.Point#COORDINATE The coordinate of the object. function OPSGROUP:_CoordinateFromObject(Object) - if Object:IsInstanceOf("COORDINATE") then - return Object - else - if Object:IsInstanceOf("POSITIONABLE") or Object:IsInstanceOf("ZONE_BASE") then - self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate") - return Object:GetCoordinate() + if Object then + if Object:IsInstanceOf("COORDINATE") then + return Object else - self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!") + if Object:IsInstanceOf("POSITIONABLE") or Object:IsInstanceOf("ZONE_BASE") then + self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate") + return Object:GetCoordinate() + else + self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!") + end end + else + self:E(self.lid.."ERROR: Object passed is nil!") end return nil diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 56f28b878..c354ff996 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -1,4 +1,4 @@ ---- **Ops** - Troop transport assignment of OPS groups. +--- **Ops** - Troop transport assignment for OPS groups. -- -- ## Main Features: -- @@ -106,22 +106,24 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) _OPSTRANSPORTID=_OPSTRANSPORTID+1 -- Set some string id for output to DCS.log file. - self.lid=string.format("OPSTRANSPORT [UID=%d] %s --> %s | ", _OPSTRANSPORTID, Pickupzone:GetName(), Deployzone:GetName()) + self.lid=string.format("OPSTRANSPORT [UID=%d] | ", _OPSTRANSPORTID) -- Defaults. self.uid=_OPSTRANSPORTID - self.status=OPSTRANSPORT.Status.PLANNING + self.status=OPSTRANSPORT.Status.PLANNING + self.pickupzone=Pickupzone self.deployzone=Deployzone self.embarkzone=Pickupzone - self.disembarkzone=Deployzone - self.prio=50 - self.importance=nil - self.Tstart=timer.getAbsTime()+5 self.carrierGroup=nil self.cargos={} self.carriers={} + + self:SetPriority() + self:SetTime() + + -- Add cargo groups. if GroupSet then self:AddCargoGroups(GroupSet, Pickupzone, Deployzone) end @@ -139,6 +141,9 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self:AddTransition("*", "Status", "*") self:AddTransition("*", "Stop", "*") + self:AddTransition("*", "Unloaded", "*") + + -- Call status update self:__Status(-1) @@ -183,7 +188,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) -- Debug info. if self.verbose>=0 then local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", - self.uid, self.pickupzone:GetName(), self.embarkzone:GetName(), self.deployzone:GetName(), self.disembarkzone:GetName()) + self.uid, self.pickupzone:GetName(), self.embarkzone and self.embarkzone:GetName() or "none", self.deployzone:GetName(), self.disembarkzone and self.disembarkzone:GetName() or "none") local Weight=0 for _,_cargo in pairs(self.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup @@ -213,7 +218,7 @@ end -- @param Core.Zone#ZONE DisembarkZone Zone where the troops are disembarked. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetDisembarkZone(DisembarkZone) - self.disembarkzone=DisembarkZone or self.deployzone + self.disembarkzone=DisembarkZone return self end @@ -260,6 +265,56 @@ function OPSTRANSPORT:_AddCarrier(CarrierGroup) return self end +--- Set transport start and stop time. +-- @param #OPSTRANSPORT self +-- @param #string ClockStart Time the transport is started, e.g. "05:00" for 5 am. If specified as a #number, it will be relative (in seconds) to the current mission time. Default is 5 seconds after mission was added. +-- @param #string ClockStop (Optional) Time the transport is stopped, e.g. "13:00" for 1 pm. If mission could not be started at that time, it will be removed from the queue. If specified as a #number it will be relative (in seconds) to the current mission time. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetTime(ClockStart, ClockStop) + + -- Current mission time. + local Tnow=timer.getAbsTime() + + -- Set start time. Default in 5 sec. + local Tstart=Tnow+5 + if ClockStart and type(ClockStart)=="number" then + Tstart=Tnow+ClockStart + elseif ClockStart and type(ClockStart)=="string" then + Tstart=UTILS.ClockToSeconds(ClockStart) + end + + -- Set stop time. Default nil. + local Tstop=nil + if ClockStop and type(ClockStop)=="number" then + Tstop=Tnow+ClockStop + elseif ClockStop and type(ClockStop)=="string" then + Tstop=UTILS.ClockToSeconds(ClockStop) + end + + self.Tstart=Tstart + self.Tstop=Tstop + + if Tstop then + self.duration=self.Tstop-self.Tstart + end + + return self +end + +--- Set mission priority and (optional) urgency. Urgent missions can cancel other running missions. +-- @param #OPSTRANSPORT self +-- @param #number Prio Priority 1=high, 100=low. Default 50. +-- @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 Urgent If *true*, another running mission might be cancelled if it has a lower priority. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetPriority(Prio, Importance, Urgent) + self.prio=Prio or 50 + self.urgent=Urgent + self.importance=Importance + return self +end + + --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. @@ -337,14 +392,18 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) local fsmstate=self:GetState() - local text=string.format("State=%s", fsmstate) + local text=string.format("State=%s: %s --> %s", fsmstate, self.pickupzone:GetName(), self.deployzone:GetName()) + text=text..string.format("\nCargos:") for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + text=text..string.format("\n- %s: %s %s, carrier=%s", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), cargo.opsgroup.cargoStatus, cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "none") end + text=text..string.format("\nCarriers:") for _,_carrier in pairs(self.carriers) do - local carrier=_carrier + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + text=text..string.format("\n- %s: %s %s, cargo=%d kg", carrier:GetName(), carrier:GetState(), carrier.carrierStatus, carrier:GetWeightCargo()) end self:I(self.lid..text) @@ -394,8 +453,7 @@ end function OPSTRANSPORT:onafterDelivered(From, Event, To) self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.DELIVERED)) - -- TODO: Inform all assigned carriers that cargo was delivered. They can have this in the queue or are currently processing this transport. - + -- Inform all assigned carriers that cargo was delivered. They can have this in the queue or are currently processing this transport. for _,_carrier in pairs(self.carriers) do local carrier=_carrier --Ops.OpsGroup#OPSGROUP if self:GetCarrierTransportStatus(carrier)~=OPSTRANSPORT.Status.DELIVERED then @@ -405,6 +463,16 @@ function OPSTRANSPORT:onafterDelivered(From, Event, To) end +--- On after "Unloaded" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup OPSGROUP that was unloaded from a carrier. +function OPSTRANSPORT:onafterUnloaded(From, Event, To, OpsGroup) + self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroup:GetName())) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 302018feff2455ad460bba3d0dbb48df49b0beef Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 20 Feb 2021 00:03:45 +0100 Subject: [PATCH 024/141] OPS Cargo --- Moose Development/Moose/Ops/ArmyGroup.lua | 4 ++ Moose Development/Moose/Ops/FlightGroup.lua | 13 +++- Moose Development/Moose/Ops/NavyGroup.lua | 4 ++ Moose Development/Moose/Ops/OpsGroup.lua | 54 +++++++++++++--- Moose Development/Moose/Ops/OpsTransport.lua | 65 +++++++++++++++++--- 5 files changed, 119 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index f758ff079..7660edc0f 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -540,6 +540,10 @@ function ARMYGROUP:onafterSpawned(From, Event, To) -- Update position. self:_UpdatePosition() + + -- Not dead or destroyed yet. + self.isDead=false + self.isDestroyed=false if self.isAI then diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index cb03b0085..8472b6ad0 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1529,6 +1529,10 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) -- Update position. self:_UpdatePosition() + + -- Not dead or destroyed yet. + self.isDead=false + self.isDestroyed=false if self.isAI then @@ -2067,12 +2071,15 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Number of cargo transports remaining. local nTransports=self:CountRemainingTransports() + + -- Debug info. + self:T(self.lid..string.format("Remaining (final=%s): missions=%d, tasks=%d, transports=%d", tostring(self.passedfinalwp), nMissions, nTasks, nTransports)) -- Final waypoint passed? if self.passedfinalwp then -- Got current mission or task? - if self.currentmission==nil and self.taskcurrent==0 then + if self.currentmission==nil and self.taskcurrent==0 and self.cargoTransport==nil then -- Number of remaining tasks/missions? if nTasks==0 and nMissions==0 and nTransports==0 then @@ -2082,10 +2089,10 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Send flight to destination. if destbase then - self:T(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTB!") + self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") self:__RTB(-3, destbase) elseif destzone then - self:T(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTZ!") + self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTZ!") self:__RTZ(-3, destzone) else self:T(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!") diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 9f8f4ba9d..8c78e0ede 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -660,6 +660,10 @@ function NAVYGROUP:onafterSpawned(From, Event, To) -- Update position. self:_UpdatePosition() + + -- Not dead or destroyed yet. + self.isDead=false + self.isDestroyed=false if self.isAI then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 3f3dc427b..3dacfb5f3 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -32,6 +32,8 @@ -- @field #boolean isAircraft If true, group is airplane or helicopter. -- @field #boolean isNaval If true, group is ships or submarine. -- @field #boolean isGround If true, group is some ground unit. +-- @field #boolean isDestroyed If true, the whole group was destroyed. +-- @field #boolean isDead If true, the whole group is dead. -- @field #table waypoints Table of waypoints. -- @field #table waypoints0 Table of initial waypoints. -- @field Wrapper.Airbase#AIRBASE homebase The home base of the flight group. @@ -1537,7 +1539,18 @@ end -- @param #OPSGROUP self -- @return #boolean If true, all units/elements of the group are dead. function OPSGROUP:IsDead() - return self:Is("Dead") + if self.isDead then + return true + else + return self:Is("Dead") + end +end + +--- Check if group was destroyed. +-- @param #OPSGROUP self +-- @return #boolean If true, all units/elements of the group were destroyed. +function OPSGROUP:IsDestroyed() + return self.isDestroyed end --- Check if FSM is stopped. @@ -2994,6 +3007,7 @@ end -- @param #OPSGROUP self -- @return #number Number of unfinished transports in the queue. function OPSGROUP:CountRemainingTransports() + env.info("FF Count remaining transports="..#self.cargoqueue) local N=0 @@ -3001,14 +3015,17 @@ function OPSGROUP:CountRemainingTransports() for _,_transport in pairs(self.cargoqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + self:I(self.lid..string.format("Transport status=%s [%s]", transport:GetCarrierTransportStatus(self), transport:GetState())) + -- Count not delivered (executing or scheduled) assignments. - if transport and transport.status~=OPSGROUP.TransportStatus.DELIVERED then + if transport and transport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.SCHEDULED and transport:GetState()~=OPSTRANSPORT.Status.DELIVERED then N=N+1 end end + env.info("FF Count remaining transports="..N) return N end @@ -4532,6 +4549,16 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) return self end +--- On after "Destroyed" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterDestroyed(From, Event, To) + self:T(self.lid..string.format("Group destroyed at t=%.3f", timer.getTime())) + self.isDestroyed=true +end + --- On before "Dead" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -4550,10 +4577,9 @@ end -- @param #string To To state. function OPSGROUP:onafterDead(From, Event, To) self:T(self.lid..string.format("Group dead at t=%.3f", timer.getTime())) - - -- Delete waypoints so they are re-initialized at the next spawn. - self.waypoints=nil - self.groupinitialized=false + + -- Is dead now. + self.isDead=true -- Cancel all missions. for _,_mission in pairs(self.missionqueue) do @@ -4565,6 +4591,10 @@ function OPSGROUP:onafterDead(From, Event, To) mission:GroupDead(self) end + + -- Delete waypoints so they are re-initialized at the next spawn. + self.waypoints=nil + self.groupinitialized=false -- Stop in a sec. self:__Stop(-5) @@ -4922,6 +4952,7 @@ function OPSGROUP:AddOpsTransport(OpsTransport) --Add to cargo queue table.insert(self.cargoqueue, OpsTransport) + self:I(self.lid.."FF adding transport to carrier, #self.cargoqueue="..#self.cargoqueue) end --- Delete a cargo transport assignment from the cargo queue @@ -5654,6 +5685,9 @@ function OPSGROUP:onafterUnloading(From, Event, To) -- Cargo was delivered (somehow). cargo.delivered=true + -- Increase number of delivered cargos. + self.cargoTransport.Ndelivered=self.cargoTransport.Ndelivered+1 + if carrierGroup then --- @@ -5902,12 +5936,14 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) elseif self:IsLandedAt() then local Task=self:GetTaskCurrent() self:TaskCancel(Task) - end + end + else + -- Army & Navy: give Cruise command to "wake up" from waiting status. + self:__Cruise(0.1) end -- Check group done. self:I(self.lid.."All cargo delivered ==> check group done") - self:__Cruise(0.1) self:_CheckGroupDone(0.2) -- No current transport any more. @@ -7504,7 +7540,7 @@ end -- @return #OPSGROUP self function OPSGROUP:_UpdatePosition() - if self:IsAlive()~=nil then + if self:IsExist() then -- Backup last state to monitor differences. self.positionLast=self.position or self:GetVec3() diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index c354ff996..6f098e30d 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -38,8 +38,11 @@ -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. -- @field Ops.OpsGroup#OPSGROUP carrierGroup The new carrier group. --- @field disembarkActivation Activation setting when group is disembared from carrier. --- @field disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. +-- @field #boolean disembarkActivation Activation setting when group is disembared from carrier. +-- @field #boolean disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. +-- @field #number Ncargo Total number of cargo groups. +-- @field #number Ncarrier Total number of assigned carriers. +-- @field #number Ndelivered Total number of cargo groups delivered. -- @extends Core.Fsm#FSM --- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill @@ -118,6 +121,9 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self.carrierGroup=nil self.cargos={} self.carriers={} + self.Ncargo=0 + self.Ncarrier=0 + self.Ndelivered=0 self:SetPriority() @@ -168,6 +174,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) if cargo then --and self:CanCargo(cargo.opsgroup) table.insert(self.cargos, cargo) + self.Ncargo=self.Ncargo+1 end else @@ -180,6 +187,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) if cargo then table.insert(self.cargos, cargo) + self.Ncargo=self.Ncargo+1 end end @@ -249,6 +257,21 @@ function OPSTRANSPORT:SetDisembarkInUtero(InUtero) return self end +--- Check if an OPS group is assigned as carrier for this transport. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CarrierGroup Potential carrier OPSGROUP. +-- @return #boolean If true, group is an assigned carrier. +function OPSTRANSPORT:IsCarrier(CarrierGroup) + + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + if carrier.groupname==CarrierGroup.groupname then + return true + end + end + + return false +end --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self @@ -256,11 +279,19 @@ end -- @return #OPSTRANSPORT self function OPSTRANSPORT:_AddCarrier(CarrierGroup) - self:SetCarrierTransportStatus(CarrierGroup, OPSTRANSPORT.Status.SCHEDULED) + if not self:IsCarrier(CarrierGroup) then - self:Scheduled() - - table.insert(self.carriers, CarrierGroup) + -- Increase carrier count. + self.Ncarrier=self.Ncarrier+1 + + -- Set trans + self:SetCarrierTransportStatus(CarrierGroup, OPSTRANSPORT.Status.SCHEDULED) + + self:Scheduled() + + table.insert(self.carriers, CarrierGroup) + + end return self end @@ -390,9 +421,11 @@ end -- @param #string To To state. function OPSTRANSPORT:onafterStatus(From, Event, To) + -- Current FSM state. local fsmstate=self:GetState() + - local text=string.format("State=%s: %s --> %s", fsmstate, self.pickupzone:GetName(), self.deployzone:GetName()) + local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, self.Ncarrier) text=text..string.format("\nCargos:") for _,_cargo in pairs(self.cargos) do @@ -483,20 +516,34 @@ end function OPSTRANSPORT:_CheckDelivered() local done=true + local dead=true for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup if cargo.delivered then -- This one is delivered. - elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + dead=false + elseif cargo.opsgroup==nil then + -- This one is nil?! + dead=false + elseif cargo.opsgroup:IsDestroyed() then + -- This one was destroyed. + elseif cargo.opsgroup:IsDead() then -- This one is dead. + dead=false + elseif cargo.opsgroup:IsStopped() then + -- This one is stopped. + dead=false else done=false --Someone is not done! + dead=false end end - if done then + if dead then + --self:CargoDead() + elseif done then self:Delivered() end From c4084156acd7ae6029acecad2ddbabd2a9ec0cda Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 21 Feb 2021 00:19:32 +0100 Subject: [PATCH 025/141] OPS Cargo --- Moose Development/Moose/Ops/OpsGroup.lua | 135 +++++++++++++++---- Moose Development/Moose/Ops/OpsTransport.lua | 28 ++-- 2 files changed, 123 insertions(+), 40 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 3dacfb5f3..c0879f357 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4641,7 +4641,7 @@ function OPSGROUP:_CheckCargoTransport() local Time=timer.getAbsTime() -- Cargo bay debug info. - if self.verbose>=1 then + if self.verbose>=3 then local text="" for cargogroupname, carriername in pairs(self.cargoBay) do text=text..string.format("\n- %s in carrier %s", tostring(cargogroupname), tostring(carriername)) @@ -4652,7 +4652,7 @@ function OPSGROUP:_CheckCargoTransport() end -- Cargo queue debug info. - if self.verbose>=1 then + if self.verbose>=3 then local text="" for i,_transport in pairs(self.cargoqueue) do local transport=_transport --#Ops.OpsTransport#OPSTRANSPORT @@ -4681,7 +4681,7 @@ function OPSGROUP:_CheckCargoTransport() if self.cargoTransport then -- Debug info. - if self.verbose>=1 then + if self.verbose>=2 then local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup @@ -4785,7 +4785,7 @@ function OPSGROUP:_CheckCargoTransport() local transport=self.cargoqueue[i] --Ops.OpsTransport#OPSTRANSPORT local delivered=self:_CheckDelivered(transport) if delivered then - self:Delivered(transport) + --self:Delivered(transport) end end @@ -5141,6 +5141,36 @@ function OPSGROUP:GetFreeCargobay(UnitName) return Free end +--- Get max weight of cargo (group) this group can load. This is the largest free cargo bay of any (not dead) element of the group. +-- Optionally, you can calculate the current max weight possible, which accounts for currently loaded cargo. +-- @param #OPSGROUP self +-- @param #boolean Currently If true, calculate the max weight currently possible in case there is already cargo loaded. +-- @return #number Max weight in kg. +function OPSGROUP:GetFreeCargobayMax(Currently) + + local maxweight=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if element.status~=OPSGROUP.ElementStatus.DEAD then + + local weight=element.weightMaxCargo + + if Currently then + weight=weight-element.weightCargo + end + + -- Check if this element can load more. + if weight>maxweight then + maxweight=weight + end + + end + end + + return maxweight +end + --- Get weight of the internal cargo the group is carriing right now. -- @param #OPSGROUP self @@ -5163,6 +5193,26 @@ function OPSGROUP:GetWeightCargo(UnitName) return weight end +--- Get max weight of the internal cargo the group can carry. +-- @param #OPSGROUP self +-- @return #number Cargo weight in kg. +function OPSGROUP:GetWeightCargoMax() + + local weight=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if element.status~=OPSGROUP.ElementStatus.DEAD then + + weight=weight+element.weightMaxCargo + + end + + end + + return weight +end + --- Add weight to the internal cargo of an element of the group. -- @param #OPSGROUP self -- @param #string UnitName Name of the unit. Default is of the whole group. @@ -5246,7 +5296,7 @@ end -- @param #string To To state. function OPSGROUP:onafterPickup(From, Event, To) -- Debug info. - self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.PICKUP)) + self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.PICKUP)) -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP @@ -5370,7 +5420,7 @@ end -- @param #string To To state. function OPSGROUP:onafterLoading(From, Event, To) -- Debug info. - self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.LOADING)) + self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.LOADING)) -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.LOADING @@ -5398,23 +5448,29 @@ function OPSGROUP:onafterLoading(From, Event, To) return nil end + --TODO: sort cargos wrt weight. + + -- Loop over all cargos. for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - if self:CanCargo(cargo.opsgroup) and not cargo.delivered then + -- Check that cargo weight is + if self:CanCargo(cargo.opsgroup) and not (cargo.delivered or cargo.opsgroup:IsDead()) then -- Check that group is not cargo already and not busy. -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() if cargo.opsgroup:IsNotCargo() and not (cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading() or cargo.opsgroup:IsLoaded()) then - -- Check if cargo is in pickup zone. + -- Check if cargo is in embark/pickup zone. local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) - -- First check if cargo is not delivered yet. + -- Cargo MUST be inside zone or it will not be loaded! if inzone then + -- Weight of cargo. local weight=cargo.opsgroup:GetWeightTotal() + -- Find a carrier that has enough free cargo bay for this group. local carrier=_findCarrier(weight) if carrier then @@ -5429,19 +5485,20 @@ function OPSGROUP:onafterLoading(From, Event, To) cargo.opsgroup:Board(self, carrier) else - - env.info("FF cannot board carrier") - + -- Debug info. + env.info("FF cannot board carrier") end else + -- Debug info. env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) end end - else - env.info("FF cargo already delivered") + else + -- Debug info. + self:T3(self.lid.."Cargo already delivered, is dead or carrier cannot") end end @@ -5451,11 +5508,11 @@ end --- Clear waypoints. -- @param #OPSGROUP self function OPSGROUP:ClearWaypoints() - -- Clear all waypoints. - for i=1,#self.waypoints do - table.remove(self.waypoints, i) - end - self.waypoints={} + -- Clear all waypoints. + for i=1,#self.waypoints do + table.remove(self.waypoints, i) + end + self.waypoints={} end --- On after "Load" event. Carrier loads a cargo group into ints cargo bay. @@ -5488,7 +5545,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) --- -- Debug info. - CargoGroup:I(CargoGroup.lid..string.format("New cargo status %s --> %s", CargoGroup.cargoStatus, OPSGROUP.CargoStatus.LOADED)) + CargoGroup:I(CargoGroup.lid..string.format("New cargo status: %s --> %s", CargoGroup.cargoStatus, OPSGROUP.CargoStatus.LOADED)) -- Set cargo status. CargoGroup.cargoStatus=OPSGROUP.CargoStatus.LOADED @@ -5540,7 +5597,7 @@ end -- @param #string To To state. function OPSGROUP:onafterTransport(From, Event, To) -- Debug info. - self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.TRANSPORTING)) + self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.TRANSPORTING)) -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.TRANSPORTING @@ -5662,7 +5719,7 @@ end -- @param #string To To state. function OPSGROUP:onafterUnloading(From, Event, To) -- Debug info. - self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.UNLOADING)) + self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.UNLOADING)) -- Set carrier status to UNLOADING. self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING @@ -5793,7 +5850,7 @@ end function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated, Heading) -- Debug info. - OpsGroup:I(OpsGroup.lid..string.format("New cargo status %s --> %s", OpsGroup.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) + OpsGroup:I(OpsGroup.lid..string.format("New cargo status: %s --> %s", OpsGroup.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) -- Set cargo status. OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO @@ -5914,13 +5971,15 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then -- Debug info. - self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.NOTCARRIER)) + self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.NOTCARRIER)) - -- This is not a carrier anymore. - self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER - + -- Checks if self:IsPickingup() then -- Delete pickup waypoint? + local wpindex=self:GetWaypointIndexNext(false) + if wpindex then + self:RemoveWaypoint(wpindex) + end elseif self:IsLoading() then -- Nothing to do? elseif self:IsTransporting() then @@ -5929,6 +5988,9 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Nothing to do? end + -- This is not a carrier anymore. + self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER + -- Startup uncontrolled aircraft to allow it to go back. if self:IsFlightgroup() then if self:IsUncontrolled() then @@ -5960,6 +6022,23 @@ end -- Cargo Group Functions --- +--- On before "Board" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP CarrierGroup The carrier group. +-- @param #OPSGROUP.Element Carrier The OPSGROUP element +function OPSGROUP:onbeforeBoard(From, Event, To, CarrierGroup, Carrier) + + if self:IsDead() then + self:I(self.lid.."Group DEAD ==> Deny Board transition!") + return false + end + + return true +end + --- On after "Board" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -5969,7 +6048,7 @@ end -- @param #OPSGROUP.Element Carrier The OPSGROUP element function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) -- Debug info. - self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.BOARDING)) + self:I(self.lid..string.format("New cargo status: %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.BOARDING)) -- Set cargo status. self.cargoStatus=OPSGROUP.CargoStatus.BOARDING diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 6f098e30d..323dd6017 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -37,6 +37,7 @@ -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. +-- @field Core.Zone#ZONE unboardzone (Optional) Zone where the cargo is going to after disembarkment. -- @field Ops.OpsGroup#OPSGROUP carrierGroup The new carrier group. -- @field #boolean disembarkActivation Activation setting when group is disembared from carrier. -- @field #boolean disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. @@ -82,13 +83,14 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.2" +OPSTRANSPORT.version="0.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: A lot. +-- TODO: Add start conditions. +-- TODO: Check carrier(s) dead. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -143,14 +145,12 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. self:AddTransition(OPSTRANSPORT.Status.EXECUTING, "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. + self:AddTransition("*", "Status", "*") + self:AddTransition("*", "Stop", "*") + self:AddTransition("*", "Unloaded", "*") + - self:AddTransition("*", "Status", "*") - self:AddTransition("*", "Stop", "*") - - self:AddTransition("*", "Unloaded", "*") - - - -- Call status update + -- Call status update. self:__Status(-1) return self @@ -430,13 +430,15 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) text=text..string.format("\nCargos:") for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - text=text..string.format("\n- %s: %s %s, carrier=%s", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), cargo.opsgroup.cargoStatus, cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "none") + local name=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "none" + text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s", cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name) end text=text..string.format("\nCarriers:") for _,_carrier in pairs(self.carriers) do local carrier=_carrier --Ops.OpsGroup#OPSGROUP - text=text..string.format("\n- %s: %s %s, cargo=%d kg", carrier:GetName(), carrier:GetState(), carrier.carrierStatus, carrier:GetWeightCargo()) + text=text..string.format("\n- %s: %s [%s], cargo=%d/%d kg, free cargo bay %d/%d kg", + carrier:GetName(), carrier.carrierStatus:upper(), carrier:GetState(), carrier:GetWeightCargo(), carrier:GetWeightCargoMax(), carrier:GetFreeCargobayMax(true), carrier:GetFreeCargobayMax()) end self:I(self.lid..text) @@ -510,7 +512,6 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- Check if all cargo of this transport assignment was delivered. -- @param #OPSTRANSPORT self function OPSTRANSPORT:_CheckDelivered() @@ -543,7 +544,10 @@ function OPSTRANSPORT:_CheckDelivered() if dead then --self:CargoDead() + self:I(self.lid.."All cargo DEAD!") + self:Delivered() elseif done then + self:I(self.lid.."All cargo delivered") self:Delivered() end From 5cc023d1fef7790e6ff6144af09170eec703958a Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 21 Feb 2021 11:53:52 +0100 Subject: [PATCH 026/141] OPS Cargo --- Moose Development/Moose/Ops/OpsGroup.lua | 7 +- Moose Development/Moose/Ops/OpsTransport.lua | 88 +++++++++++++++++++- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c0879f357..20f482f2b 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4840,9 +4840,6 @@ end -- @return Ops.OpsTransport#OPSTRANSPORT The next due cargo transport or `nil`. function OPSGROUP:_GetNextCargoTransport() - -- Abs. mission time in seconds. - local Time=timer.getAbsTime() - -- Current position. local coord=self:GetCoordinate() @@ -4869,7 +4866,9 @@ function OPSGROUP:_GetNextCargoTransport() for _,_cargotransport in pairs(self.cargoqueue) do local cargotransport=_cargotransport --Ops.OpsTransport#OPSTRANSPORT - if Time>=cargotransport.Tstart and cargotransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then + local carrierstatusScheduled=cargotransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.SCHEDULED + + if cargotransport:IsReadyToGo() and carrierstatusScheduled and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then cargotransport:Executing() cargotransport:SetCarrierTransportStatus(self, OPSTRANSPORT.Status.EXECUTING) return cargotransport diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 323dd6017..6bf39a147 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -33,6 +33,7 @@ -- @field #number importance Importance of this transport. Smaller=higher. -- @field #number Tstart Start time in *abs.* seconds. -- @field #number Tstop Stop time in *abs.* seconds. Default `#nil` (never stops). +-- @field #table conditionStart Start conditions. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. @@ -62,7 +63,8 @@ OPSTRANSPORT = { verbose = 1, cargos = {}, carriers = {}, - carrierTransportStatus = {}, + carrierTransportStatus = {}, + conditionStart = {}, } --- Cargo transport status. @@ -78,6 +80,11 @@ OPSTRANSPORT.Status={ DELIVERED="delivered", } +--- Generic mission condition. +-- @type OPSTRANSPORT.Condition +-- @field #function func Callback function to check for a condition. Should return a #boolean. +-- @field #table arg Optional arguments passed to the condition callback function. + --- Transport ID. _OPSTRANSPORTID=0 @@ -89,7 +96,7 @@ OPSTRANSPORT.version="0.0.3" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Add start conditions. +-- DONE: Add start conditions. -- TODO: Check carrier(s) dead. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -345,6 +352,26 @@ function OPSTRANSPORT:SetPriority(Prio, Importance, Urgent) return self end +--- Add start condition. +-- @param #OPSTRANSPORT self +-- @param #function ConditionFunction Function that needs to be true before the transport can be started. Must return a #boolean. +-- @param ... Condition function arguments if any. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:AddConditionStart(ConditionFunction, ...) + + local condition={} --#OPSTRANSPORT.Condition + + condition.func=ConditionFunction + condition.arg={} + if arg then + condition.arg=arg + end + + table.insert(self.conditionStart, condition) + + return self +end + --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self @@ -367,7 +394,6 @@ function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup) end - --- Create a cargo group data structure. -- @param #OPSTRANSPORT self -- @param Wrapper.Group#GROUP group The GROUP object. @@ -410,6 +436,38 @@ function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) return cargo end +--- Check if transport is ready to be started. +-- * Start time passed. +-- * Stop time did not pass already. +-- * All start conditions are true. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, mission can be started. +function OPSTRANSPORT:IsReadyToGo() + + local Tnow=timer.getAbsTime() + + -- Start time did not pass yet. + if self.Tstart and Tnowself.Tstop or false then + return false + end + + -- All start conditions true? + local startme=self:EvalConditionsAll(self.conditionStart) + + if not startme then + return false + end + + + -- We're good to go! + return true +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status Update ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -552,3 +610,27 @@ function OPSTRANSPORT:_CheckDelivered() end end + +--- Check if all given condition are true. +-- @param #OPSTRANSPORT self +-- @param #table Conditions Table of conditions. +-- @return #boolean If true, all conditions were true. Returns false if at least one condition returned false. +function OPSTRANSPORT:EvalConditionsAll(Conditions) + + -- Any stop condition must be true. + for _,_condition in pairs(Conditions or {}) do + local condition=_condition --#OPSTRANSPORT.Condition + + -- Call function. + local istrue=condition.func(unpack(condition.arg)) + + -- Any false will return false. + if not istrue then + return false + end + + end + + -- All conditions were true. + return true +end From 4a6377c20445cd399c0d39bf9ff959789bca5fac Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 27 Feb 2021 21:17:58 +0100 Subject: [PATCH 027/141] Ops Cargo --- Moose Development/Moose/Core/Database.lua | 16 + Moose Development/Moose/Core/Set.lua | 602 +++++++++++++++- Moose Development/Moose/Ops/ArmyGroup.lua | 5 - Moose Development/Moose/Ops/FlightGroup.lua | 5 +- Moose Development/Moose/Ops/NavyGroup.lua | 29 +- Moose Development/Moose/Ops/OpsGroup.lua | 684 ++++++++++--------- Moose Development/Moose/Ops/OpsTransport.lua | 400 ++++++++--- 7 files changed, 1276 insertions(+), 465 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 8ae42a151..35077c05b 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1444,6 +1444,22 @@ function DATABASE:GetOpsGroup(groupname) return self.FLIGHTGROUPS[groupname] end +--- Find an OPSGROUP (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) in the data base. +-- @param #DATABASE self +-- @param #string groupname Group name of the group. Can also be passed as GROUP object. +-- @return Ops.OpsGroup#OPSGROUP OPS group object. +function DATABASE:FindOpsGroup(groupname) + + -- Get group and group name. + if type(groupname)=="string" then + else + groupname=groupname:GetName() + end + + env.info("Getting OPSGROUP "..tostring(groupname)) + return self.FLIGHTGROUPS[groupname] +end + --- Add a flight control to the data base. -- @param #DATABASE self -- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index c8cb5dd05..5fb677d9e 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -233,7 +233,7 @@ do -- SET_BASE -- @param Core.Base#BASE Object The object itself. -- @return Core.Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) - self:F2( { ObjectName = ObjectName, Object = Object } ) + self:I( { ObjectName = ObjectName, Object = Object } ) -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set if self.Set[ObjectName] then @@ -5948,3 +5948,603 @@ do -- SET_ZONE_GOAL end end + + + +do -- SET_OPSGROUP + + --- @type SET_OPSGROUP + -- @extends Core.Set#SET_BASE + + --- Mission designers can use the @{Core.Set#SET_OPSGROUP} class to build sets of OPS groups belonging to certain: + -- + -- * Coalitions + -- * Categories + -- * Countries + -- * Contain a certain string pattern + -- + -- ## SET_OPSGROUP constructor + -- + -- Create a new SET_OPSGROUP object with the @{#SET_OPSGROUP.New} method: + -- + -- * @{#SET_OPSGROUP.New}: Creates a new SET_OPSGROUP object. + -- + -- ## Add or Remove GROUP(s) from SET_OPSGROUP + -- + -- GROUPS can be added and removed using the @{Core.Set#SET_OPSGROUP.AddGroupsByName} and @{Core.Set#SET_OPSGROUP.RemoveGroupsByName} respectively. + -- These methods take a single GROUP name or an array of GROUP names to be added or removed from SET_OPSGROUP. + -- + -- ## SET_OPSGROUP filter criteria + -- + -- You can set filter criteria to define the set of groups within the SET_OPSGROUP. + -- Filter criteria are defined by: + -- + -- * @{#SET_OPSGROUP.FilterCoalitions}: Builds the SET_OPSGROUP with the groups belonging to the coalition(s). + -- * @{#SET_OPSGROUP.FilterCategories}: Builds the SET_OPSGROUP with the groups belonging to the category(ies). + -- * @{#SET_OPSGROUP.FilterCountries}: Builds the SET_OPSGROUP with the groups belonging to the country(ies). + -- * @{#SET_OPSGROUP.FilterPrefixes}: Builds the SET_OPSGROUP with the groups *containing* the given string in the group name. **Attention!** Bad naming convention, as this not really filtering *prefixes*. + -- * @{#SET_OPSGROUP.FilterActive}: Builds the SET_OPSGROUP with the groups that are only active. Groups that are inactive (late activation) won't be included in the set! + -- + -- For the Category Filter, extra methods have been added: + -- + -- * @{#SET_OPSGROUP.FilterCategoryAirplane}: Builds the SET_OPSGROUP from airplanes. + -- * @{#SET_OPSGROUP.FilterCategoryHelicopter}: Builds the SET_OPSGROUP from helicopters. + -- * @{#SET_OPSGROUP.FilterCategoryGround}: Builds the SET_OPSGROUP from ground vehicles or infantry. + -- * @{#SET_OPSGROUP.FilterCategoryShip}: Builds the SET_OPSGROUP from ships. + -- + -- + -- Once the filter criteria have been set for the SET_OPSGROUP, you can start filtering using: + -- + -- * @{#SET_OPSGROUP.FilterStart}: Starts the filtering of the groups within the SET_OPSGROUP and add or remove GROUP objects **dynamically**. + -- * @{#SET_OPSGROUP.FilterOnce}: Filters of the groups **once**. + -- + -- + -- ## SET_OPSGROUP iterators + -- + -- Once the filters have been defined and the SET_OPSGROUP has been built, you can iterate the SET_OPSGROUP with the available iterator methods. + -- The iterator methods will walk the SET_OPSGROUP set, and call for each element within the set a function that you provide. + -- The following iterator methods are currently available within the SET_OPSGROUP: + -- + -- * @{#SET_OPSGROUP.ForEachGroup}: Calls a function for each alive group it finds within the SET_OPSGROUP. + -- + -- ## SET_OPSGROUP trigger events on the GROUP objects. + -- + -- The SET is derived from the FSM class, which provides extra capabilities to track the contents of the GROUP objects in the SET_OPSGROUP. + -- + -- ### When a GROUP object crashes or is dead, the SET_OPSGROUP will trigger a **Dead** event. + -- + -- You can handle the event using the OnBefore and OnAfter event handlers. + -- The event handlers need to have the paramters From, Event, To, GroupObject. + -- The GroupObject is the GROUP object that is dead and within the SET_OPSGROUP, and is passed as a parameter to the event handler. + -- See the following example: + -- + -- -- Create the SetCarrier SET_OPSGROUP collection. + -- + -- local SetHelicopter = SET_OPSGROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() + -- + -- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. + -- + -- function SetHelicopter:OnAfterDead( From, Event, To, GroupObject ) + -- self:F( { GroupObject = GroupObject:GetName() } ) + -- end + -- + -- + -- === + -- + -- @field #SET_OPSGROUP SET_OPSGROUP + -- + SET_OPSGROUP = { + ClassName = "SET_OPSGROUP", + Filter = { + Coalitions = nil, + Categories = nil, + Countries = nil, + GroupPrefixes = nil, + }, + FilterMeta = { + Coalitions = { + red = coalition.side.RED, + blue = coalition.side.BLUE, + neutral = coalition.side.NEUTRAL, + }, + Categories = { + plane = Group.Category.AIRPLANE, + helicopter = Group.Category.HELICOPTER, + ground = Group.Category.GROUND, + ship = Group.Category.SHIP, + }, + }, -- FilterMeta + } + + + --- Creates a new SET_OPSGROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP + function SET_OPSGROUP:New() + + -- Inherit SET_BASE. + local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.GROUPS)) -- #SET_OPSGROUP + + -- Include non activated + self:FilterActive( false ) + + return self + end + + --- Gets the Set. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:GetAliveSet() + + local AliveSet = SET_OPSGROUP:New() + + -- Clean the Set before returning with only the alive Groups. + for GroupName, GroupObject in pairs(self.Set) do + local GroupObject=GroupObject --Wrapper.Group#GROUP + + if GroupObject and GroupObject:IsAlive() then + AliveSet:Add(GroupName, GroupObject) + end + end + + return AliveSet.Set or {} + end + + --- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. + -- @param #SET_BASE self + -- @param #string ObjectName The name of the object. + -- @param Core.Base#BASE Object The object itself. + -- @return Core.Base#BASE The added BASE Object. + function SET_OPSGROUP:Add(ObjectName, Object) + self:I( { ObjectName = ObjectName, Object = Object } ) + + -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set + if self.Set[ObjectName] then + self:Remove(ObjectName, true) + end + + local object=nil --Ops.OpsGroup#OPSGROUP + if Object:IsInstanceOf("GROUP") then + + --- + -- GROUP Object + --- + + -- Fist, look up in the DATABASE if an OPSGROUP already exists. + object=_DATABASE:FindOpsGroup(ObjectName) + + if not object then + + if Object:IsShip() then + object=NAVYGROUP:New(Object) + elseif Object:IsGround() then + object=ARMYGROUP:New(Object) + elseif Object:IsAir() then + object=FLIGHTGROUP:New(Object) + else + env.error("ERROR: Unknown category of group object!") + end + end + + elseif Object:IsInstanceOf("OPSGROUP") then + -- We already have an OPSGROUP. + object=Object + else + env.error("ERROR: Object must be a GROUP or OPSGROUP!") + end + + -- Add object to set. + self.Set[ObjectName]=object + + -- Add Object name to Index. + table.insert(self.Index, ObjectName) + + -- Trigger Added event. + self:Added(ObjectName, object) + end + + --- Add a GROUP or OPSGROUP object to the set. + -- **NOTE** that an OPSGROUP is automatically created from the GROUP if it does not exist already. + -- @param Core.Set#SET_OPSGROUP self + -- @param Wrapper.Group#GROUP group The GROUP which should be added to the set. Can also be given as an #OPSGROUP object. + -- @return Core.Set#SET_OPSGROUP self + function SET_OPSGROUP:AddGroup(group) + + local groupname=group:GetName() + + self:Add(groupname, group ) + + return self + end + + --- Add GROUP(s) or OPSGROUP(s) to the set. + -- @param Core.Set#SET_OPSGROUP self + -- @param #string AddGroupNames A single name or an array of GROUP names. + -- @return Core.Set#SET_OPSGROUP self + function SET_OPSGROUP:AddGroupsByName( AddGroupNames ) + + local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } + + for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do + self:Add(AddGroupName, GROUP:FindByName(AddGroupName)) + end + + return self + end + + --- Remove GROUP(s) or OPSGROUP(s) from the set. + -- @param Core.Set#SET_OPSGROUP self + -- @param Wrapper.Group#GROUP RemoveGroupNames A single name or an array of GROUP names. + -- @return Core.Set#SET_OPSGROUP self + function SET_OPSGROUP:RemoveGroupsByName( RemoveGroupNames ) + + local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } + + for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do + self:Remove( RemoveGroupName ) + end + + return self + end + + --- Finds an OPSGROUP based on the group name. + -- @param #SET_OPSGROUP self + -- @param #string GroupName Name of the group. + -- @return Ops.OpsGroup#OPSGROUP The found OPSGROUP (FLIGHTGROUP, ARMYGROUP or NAVYGROUP) or `#nil` if the group is not in the set. + function SET_OPSGROUP:FindGroup(GroupName) + local GroupFound = self.Set[GroupName] + return GroupFound + end + + --- Finds a FLIGHTGROUP based on the group name. + -- @param #SET_OPSGROUP self + -- @param #string GroupName Name of the group. + -- @return Ops.FlightGroup#FLIGHTGROUP The found FLIGHTGROUP or `#nil` if the group is not in the set. + function SET_OPSGROUP:FindFlightGroup(GroupName) + local GroupFound = self:FindGroup(GroupName) + return GroupFound + end + + --- Finds a ARMYGROUP based on the group name. + -- @param #SET_OPSGROUP self + -- @param #string GroupName Name of the group. + -- @return Ops.ArmyGroup#ARMYGROUP The found ARMYGROUP or `#nil` if the group is not in the set. + function SET_OPSGROUP:FindArmyGroup(GroupName) + local GroupFound = self:FindGroup(GroupName) + return GroupFound + end + + + --- Finds a NAVYGROUP based on the group name. + -- @param #SET_OPSGROUP self + -- @param #string GroupName Name of the group. + -- @return Ops.NavyGroup#NAVYGROUP The found NAVYGROUP or `#nil` if the group is not in the set. + function SET_OPSGROUP:FindNavyGroup(GroupName) + local GroupFound = self:FindGroup(GroupName) + return GroupFound + end + + --- Builds a set of groups of coalitions. + -- Possible current coalitions are red, blue and neutral. + -- @param #SET_OPSGROUP self + -- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCoalitions(Coalitions) + + -- Create an empty set. + if not self.Filter.Coalitions then + self.Filter.Coalitions={} + end + + -- Ensure we got a table. + if type(Coalitions)~="table" then + Coalitions = {Coalitions} + end + + -- Set filter. + for CoalitionID, Coalition in pairs( Coalitions ) do + self.Filter.Coalitions[Coalition] = Coalition + end + + return self + end + + + --- Builds a set of groups out of categories. + -- + -- Possible current categories are: + -- + -- * "plane" for fixed wing groups + -- * "helicopter" for rotary wing groups + -- * "ground" for ground groups + -- * "ship" for naval groups + -- + -- @param #SET_OPSGROUP self + -- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship" or combinations as a table, for example `{"plane", "helicopter"}`. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategories( Categories ) + + if not self.Filter.Categories then + self.Filter.Categories={} + end + + if type(Categories)~="table" then + Categories={Categories} + end + + for CategoryID, Category in pairs( Categories ) do + self.Filter.Categories[Category] = Category + end + + return self + end + + --- Builds a set of groups out of ground category. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryGround() + self:FilterCategories("ground") + return self + end + + --- Builds a set of groups out of airplane category. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryAirplane() + self:FilterCategories("plane") + return self + end + + --- Builds a set of groups out of aicraft category (planes and helicopters). + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryAircraft() + self:FilterCategories({"plane", "helicopter"}) + return self + end + + --- Builds a set of groups out of helicopter category. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryHelicopter() + self:FilterCategories("helicopter") + return self + end + + --- Builds a set of groups out of ship category. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryShip() + self:FilterCategories("ship") + return self + end + + --- Builds a set of groups of defined countries. + -- @param #SET_OPSGROUP self + -- @param #string Countries Can take those country strings known within DCS world. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCountries(Countries) + + -- Create empty table if necessary. + if not self.Filter.Countries then + self.Filter.Countries = {} + end + + -- Ensure input is a table. + if type(Countries)~="table" then + Countries={Countries} + end + + -- Set filter. + for CountryID, Country in pairs( Countries ) do + self.Filter.Countries[Country] = Country + end + + return self + end + + + --- Builds a set of groups that contain the given string in their group name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. + -- @param #SET_OPSGROUP self + -- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterPrefixes(Prefixes) + + -- Create emtpy table if necessary. + if not self.Filter.GroupPrefixes then + self.Filter.GroupPrefixes={} + end + + -- Ensure we have a table. + if type(Prefixes)~="table" then + Prefixes={Prefixes} + end + + -- Set group prefixes. + for PrefixID, Prefix in pairs(Prefixes) do + self.Filter.GroupPrefixes[Prefix]=Prefix + end + + return self + end + + --- Builds a set of groups that are only active. + -- Only the groups that are active will be included within the set. + -- @param #SET_OPSGROUP self + -- @param #boolean Active (optional) Include only active groups to the set. + -- Include inactive groups if you provide false. + -- @return #SET_OPSGROUP self + -- @usage + -- + -- -- Include only active groups to the set. + -- GroupSet = SET_OPSGROUP:New():FilterActive():FilterStart() + -- + -- -- Include only active groups to the set of the blue coalition, and filter one time. + -- GroupSet = SET_OPSGROUP:New():FilterActive():FilterCoalition( "blue" ):FilterOnce() + -- + -- -- Include only active groups to the set of the blue coalition, and filter one time. + -- -- Later, reset to include back inactive groups to the set. + -- GroupSet = SET_OPSGROUP:New():FilterActive():FilterCoalition( "blue" ):FilterOnce() + -- ... logic ... + -- GroupSet = SET_OPSGROUP:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() + -- + function SET_OPSGROUP:FilterActive( Active ) + Active = Active or not ( Active == false ) + self.Filter.Active = Active + return self + end + + + --- Starts the filtering. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterStart() + + if _DATABASE then + self:_FilterStart() + self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) + self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) + end + + return self + end + + --- Handles the OnDead or OnCrash event for alive groups set. + -- Note: The GROUP object in the SET_OPSGROUP collection will only be removed if the last unit is destroyed of the GROUP. + -- @param #SET_OPSGROUP self + -- @param Core.Event#EVENTDATA Event + function SET_OPSGROUP:_EventOnDeadOrCrash( Event ) + self:F( { Event } ) + + if Event.IniDCSUnit then + local ObjectName, Object = self:FindInDatabase( Event ) + if ObjectName then + if Event.IniDCSGroup:getSize() == 1 then -- Only remove if the last unit of the group was destroyed. + self:Remove( ObjectName ) + end + end + end + end + + --- Handles the Database to check on an event (birth) that the Object was added in the Database. + -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! + -- @param #SET_OPSGROUP self + -- @param Core.Event#EVENTDATA Event + -- @return #string The name of the GROUP + -- @return #table The GROUP + function SET_OPSGROUP:AddInDatabase( Event ) + + if Event.IniObjectCategory==1 then + + if not self.Database[Event.IniDCSGroupName] then + self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName ) + end + + end + + return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] + end + + --- Handles the Database to check on any event that Object exists in the Database. + -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! + -- @param #SET_OPSGROUP self + -- @param Core.Event#EVENTDATA Event Event data table. + -- @return #string The name of the GROUP + -- @return #table The GROUP + function SET_OPSGROUP:FindInDatabase(Event) + return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] + end + + --- Iterate the set and call an iterator function for each OPSGROUP object. + -- @param #SET_OPSGROUP self + -- @param #function IteratorFunction The function that will be called for all OPSGROUPs in the set. **NOTE** that the function must have the OPSGROUP as first parameter! + -- @param ... (Optional) arguments passed to the `IteratorFunction`. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:ForEachGroup( IteratorFunction, ... ) + + self:ForEach(IteratorFunction, arg, self:GetSet()) + + return self + end + + --- Check include object. + -- @param #SET_OPSGROUP self + -- @param Wrapper.Group#GROUP MGroup The group that is checked for inclusion. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:IsIncludeObject(MGroup) + + -- Assume it is and check later if not. + local MGroupInclude=true + + -- Filter active. + if self.Filter.Active~=nil then + + local MGroupActive = false + + if self.Filter.Active==false or (self.Filter.Active==true and MGroup:IsActive()==true) then + MGroupActive = true + end + + MGroupInclude = MGroupInclude and MGroupActive + end + + -- Filter coalitions. + if self.Filter.Coalitions then + + local MGroupCoalition = false + + for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do + if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName]==MGroup:GetCoalition() then + MGroupCoalition = true + end + end + + MGroupInclude = MGroupInclude and MGroupCoalition + end + + -- Filter categories. + if self.Filter.Categories then + + local MGroupCategory = false + + for CategoryID, CategoryName in pairs( self.Filter.Categories ) do + if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName]==MGroup:GetCategory() then + MGroupCategory = true + end + end + + MGroupInclude = MGroupInclude and MGroupCategory + end + + -- Filter countries. + if self.Filter.Countries then + local MGroupCountry = false + for CountryID, CountryName in pairs( self.Filter.Countries ) do + if country.id[CountryName] == MGroup:GetCountry() then + MGroupCountry = true + end + end + MGroupInclude = MGroupInclude and MGroupCountry + end + + -- Filter "prefixes". + if self.Filter.GroupPrefixes then + + local MGroupPrefix = false + + for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do + if string.find( MGroup:GetName(), GroupPrefix:gsub ("-", "%%-"), 1 ) then --Not sure why "-" is replaced by "%-" ?! + MGroupPrefix = true + end + end + + MGroupInclude = MGroupInclude and MGroupPrefix + end + + return MGroupInclude + end + +end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 7660edc0f..fc82983ac 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1086,15 +1086,10 @@ end -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) - env.info("FF Current waypoint index="..self.currentwp) - env.info("FF Add waypoint after index="..(AfterWaypointWithID or "nil")) - local coordinate=self:_CoordinateFromObject(Coordinate) -- Set waypoint index. local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) - - env.info("FF Add waypoint index="..wpnumber) -- Check if final waypoint is still passed. if wpnumber>self.currentwp then diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 8472b6ad0..ad8146281 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1682,13 +1682,11 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) if self.isAI then if self:IsTransporting() then if self.cargoTransport and self.cargoTransport.deployzone and self.cargoTransport.deployzone:IsInstanceOf("ZONE_AIRBASE") then - env.info("FF transporting land at airbase ") local airbase=self.cargoTransport.deployzone:GetAirbase() self:LandAtAirbase(airbase) end elseif self:IsPickingup() then if self.cargoTransport and self.cargoTransport.pickupzone and self.cargoTransport.pickupzone:IsInstanceOf("ZONE_AIRBASE") then - env.info("FF pickingup land at airbase ") local airbase=self.cargoTransport.pickupzone:GetAirbase() self:LandAtAirbase(airbase) end @@ -2959,6 +2957,9 @@ function FLIGHTGROUP:_InitGroup() text=text..string.format("Start Rwy = %s\n", tostring(self:IsTakeoffRunway())) self:I(self.lid..text) end + + env.info("DCS Unit BOOM_AND_RECEPTACLE="..tostring(Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)) + env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE)) -- Init done. self.groupinitialized=true diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 8c78e0ede..4f233faac 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -498,7 +498,8 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check if group started or stopped turning. self:_CheckTurning() - local freepath=UTILS.NMToMeters(10) + local disttoWP=math.min(self:GetDistanceToWaypoint(), UTILS.NMToMeters(10)) + local freepath=disttoWP -- Only check if not currently turning. if not self:IsTurning() then @@ -506,7 +507,7 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check free path ahead. freepath=self:_CheckFreePath(freepath, 100) - if freepath<5000 then + if disttoWP>1 and freepath= 5 meters. + if dist<5 then + return + end + local boxwidth=dist*2 local spacex=dist*0.1 local delta=dist/10 -- Create a grid of nodes. We only want nodes of surface type water. - astar:CreateGrid({land.SurfaceType.WATER}, boxwidth, spacex, delta, delta*2, self.Debug) + astar:CreateGrid({land.SurfaceType.WATER}, boxwidth, spacex, delta, delta, self.verbose>10) -- Valid neighbour nodes need to have line of sight. astar:SetValidNeighbourLoS(self.pathCorridor) @@ -1539,7 +1550,9 @@ function NAVYGROUP:_FindPathToNextWaypoint() uid=wp.uid -- Debug: smoke and mark path. - --node.coordinate:MarkToAll(string.format("Path node #%d", i)) + if self.verbose>=10 then + node.coordinate:MarkToAll(string.format("Path node #%d", i)) + end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 20f482f2b..047912831 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -108,6 +108,7 @@ -- -- @field #OPSGROUP.Element carrier Carrier the group is loaded into as cargo. -- @field #OPSGROUP carrierGroup Carrier group transporting this group as cargo. +-- @field #OPSGROUP.MyCarrier mycarrier Carrier group for this group. -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #table cargoBay Table containing OPSGROUP loaded into this group. -- @field Ops.OpsTransport#OPSTRANSPORT cargoTransport Current cargo transport assignment. @@ -172,6 +173,7 @@ OPSGROUP = { cargoqueue = {}, cargoBay = {}, cargocounter = 1, + mycarrier = {}, } @@ -418,25 +420,18 @@ OPSGROUP.TransportStatus={ } --- Cargo transport data. --- @type OPSGROUP.CargoTransport --- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. --- @field #string status Status of the transport. See @{#OPSGROUP.TransportStatus}. --- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). --- @field #number importance Importance of this transport. Smaller=higher. --- @field #number Tstart Start time in *abs.* seconds. --- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. --- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. --- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. --- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. --- @field #OPSGROUP carrierGroup The new carrier group. +-- @type OPSGROUP.MyCarrier +-- @field #OPSGROUP group The carrier group. +-- @field #OPSGROUP.Element element The carrier element. +-- @field #boolean reserved If `true`, the carrier has caro space reserved for me. --- Cargo group data. -- @type OPSGROUP.CargoGroup -- @field #OPSGROUP opsgroup The cargo opsgroup. --- @field #string status Status of the cargo group. See @{#OPSGROUP.CargoStatus}. --- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. --- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. --- @field #boolean delivered If true, group was delivered. +-- @field #boolean delivered If `true`, group was delivered. +-- @field #OPSGROUP disembarkCarrierGroup Carrier group where the cargo group is directly loaded to. +-- @field #OPSGROUP disembarkCarrierElement Carrier element to which the cargo group is directly loaded to. +-- @field #string status Status of the cargo group. Not used yet. --- OpsGroup version. -- @field #string version @@ -1022,8 +1017,9 @@ function OPSGROUP:GetVec3(UnitName) local vec3=nil --DCS#Vec3 -- First check if this group is loaded into a carrier - if self.carrier and self.carrier.status~=OPSGROUP.ElementStatus.DEAD and self:IsLoaded() then - local unit=self.carrier.unit + local carrier=self:_GetMyCarrierElement() + if carrier and carrier.status~=OPSGROUP.ElementStatus.DEAD and self:IsLoaded() then + local unit=carrier.unit if unit and unit:IsAlive()~=nil then vec3=unit:GetVec3() return vec3 @@ -1219,8 +1215,10 @@ end -- @return #OPSGROUP self function OPSGROUP:DespawnUnit(UnitName, Delay, NoEventRemoveUnit) - env.info("FF despawn element .."..tostring(UnitName)) + -- Debug info. + self:T(self.lid.."Despawn element "..tostring(UnitName)) + -- Get element. local element=self:GetElementByName(UnitName) if element then @@ -1652,7 +1650,8 @@ end -- @return #boolean If true, group is boarding. function OPSGROUP:IsBoarding(CarrierGroupName) if CarrierGroupName then - if self.carrierGroup and self.carrierGroup.groupname~=CarrierGroupName then + local carrierGroup=self:_GetMyCarrierGroup() + if carrierGroup and carrierGroup.groupname~=CarrierGroupName then return false end end @@ -1665,7 +1664,8 @@ end -- @return #boolean If true, group is loaded. function OPSGROUP:IsLoaded(CarrierGroupName) if CarrierGroupName then - if self.carrierGroup and self.carrierGroup.groupname~=CarrierGroupName then + local carrierGroup=self:_GetMyCarrierGroup() + if carrierGroup and carrierGroup.groupname~=CarrierGroupName then return false end end @@ -3007,7 +3007,6 @@ end -- @param #OPSGROUP self -- @return #number Number of unfinished transports in the queue. function OPSGROUP:CountRemainingTransports() - env.info("FF Count remaining transports="..#self.cargoqueue) local N=0 @@ -3015,17 +3014,15 @@ function OPSGROUP:CountRemainingTransports() for _,_transport in pairs(self.cargoqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT - self:I(self.lid..string.format("Transport status=%s [%s]", transport:GetCarrierTransportStatus(self), transport:GetState())) + -- Debug info. + self:T(self.lid..string.format("Transport status=%s [%s]", transport:GetCarrierTransportStatus(self), transport:GetState())) -- Count not delivered (executing or scheduled) assignments. if transport and transport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.SCHEDULED and transport:GetState()~=OPSTRANSPORT.Status.DELIVERED then - - N=N+1 - + N=N+1 end end - env.info("FF Count remaining transports="..N) return N end @@ -4114,8 +4111,6 @@ end -- @param #string To To state. function OPSGROUP:onafterLaserLostLOS(From, Event, To) - --env.info("FF lost LOS") - -- No of sight. self.spot.LOS=false @@ -4123,8 +4118,6 @@ function OPSGROUP:onafterLaserLostLOS(From, Event, To) self.spot.lostLOS=true if self.spot.On then - - --env.info("FF lost LOS ==> pause laser") -- Switch laser off. self:LaserPause() @@ -4142,19 +4135,14 @@ function OPSGROUP:onafterLaserGotLOS(From, Event, To) -- Has line of sight. self.spot.LOS=true - - --env.info("FF Laser Got LOS") if self.spot.lostLOS then -- Did not loose LOS anymore. self.spot.lostLOS=false - - --env.info("FF had lost LOS and regained it") -- Resume laser if currently paused. if self.spot.Paused then - --env.info("FF laser was paused ==> resume") self:LaserResume() end @@ -4205,8 +4193,6 @@ function OPSGROUP:SetLaserTarget(Target) else self.spot.offsetTarget={x=0, 2, z=0} end - - --env.info(string.format("Target offset %.3f", y)) else self:E("WARNING: LASER target is not alive!") @@ -4310,8 +4296,6 @@ function OPSGROUP:_UpdateLaser() -- Check current LOS. local los=self:HasLoS(self.spot.Coordinate, self.spot.element, self.spot.offset) - --env.info(string.format("FF check LOS current=%s previous=%s", tostring(los), tostring(self.spot.LOS))) - if los then -- Got LOS if self.spot.lostLOS then @@ -4425,8 +4409,13 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) local opsgroup=_DATABASE:GetOpsGroup(groupname) if opsgroup and not (opsgroup:IsDead() or opsgroup:IsStopped()) then for _,element in pairs(opsgroup.elements) do - env.info("FF cargo element dead "..element.name) + + -- Debug info. + self:T2(self.lid.."Cargo element dead "..element.name) + + -- Trigger dead event. opsgroup:ElementDead(element) + end end end @@ -4662,9 +4651,10 @@ function OPSGROUP:_CheckCargoTransport() local state=cargo.opsgroup:GetState() local status=cargo.opsgroup.cargoStatus local name=cargo.opsgroup.groupname - local carriergroup=cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup.groupname or "N/A" - local carrierelement=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "N/A" - text=text..string.format("\n (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s", j, name, state, status, carriergroup, carrierelement, tostring(cargo.delivered)) + local carriergroup, carrierelement, reserved=cargo.opsgroup:_GetMyCarrier() + local carrierGroupname=carriergroup and carriergroup.groupname or "none" + local carrierElementname=carrierelement and carrierelement.name or "none" + text=text..string.format("\n (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s", j, name, state, status, carrierGroupname, carrierElementname, tostring(cargo.delivered)) end end if text~="" then @@ -4689,9 +4679,10 @@ function OPSGROUP:_CheckCargoTransport() local gstatus=cargo.opsgroup:GetState() local cstatus=cargo.opsgroup.cargoStatus local weight=cargo.opsgroup:GetWeightTotal() - local carriername=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "none" - local carriergroup=cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup.groupname or "none" - text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s", name, weight, gstatus, cstatus, carriername, carriergroup, tostring(cargo.delivered)) + local carriergroup, carrierelement, reserved=cargo.opsgroup:_GetMyCarrier() + local carrierGroupname=carriergroup and carriergroup.groupname or "none" + local carrierElementname=carrierelement and carrierelement.name or "none" + text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s", name, weight, gstatus, cstatus, carrierElementname, carrierGroupname, tostring(cargo.delivered)) end self:I(self.lid..text) end @@ -4761,7 +4752,10 @@ function OPSGROUP:_CheckCargoTransport() for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - if (cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup:GetName()==self:GetName()) and not cargo.delivered then + local carrierGroup=cargo.opsgroup:_GetMyCarrierGroup() + + -- Check that this group is + if (carrierGroup and carrierGroup:GetName()==self:GetName()) and not cargo.delivered then delivered=false break end @@ -4780,15 +4774,6 @@ function OPSGROUP:_CheckCargoTransport() end - -- Loop over cargo queue and check if everything was delivered. - for i=#self.cargoqueue,1,-1 do - local transport=self.cargoqueue[i] --Ops.OpsTransport#OPSTRANSPORT - local delivered=self:_CheckDelivered(transport) - if delivered then - --self:Delivered(transport) - end - end - return self end @@ -4825,8 +4810,14 @@ function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) self.cargoBay[CargoGroup.groupname]=nil -- Reduce carrier weight. - local weight=CargoGroup:GetWeightTotal() - self:RedWeightCargo(CargoGroup.carrier.name, weight) + local weight=CargoGroup:GetWeightTotal() + + -- Get carrier of group. + local carrier=CargoGroup:_GetMyCarrierElement() + + if carrier then + self:RedWeightCargo(carrier.name, weight) + end return true end @@ -4909,36 +4900,6 @@ function OPSGROUP:_CheckDelivered(CargoTransport) return done end ---- Create a cargo transport assignment. --- @param #OPSGROUP self --- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! --- @param Core.Zone#ZONE Deployzone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. --- @param #number Prio Priority of this transport assignment. Should be a number between 1 (high prio) and 100 (low prio). --- @param #number Importance Importance of this transport assignment (lower=more important). A transport is only considered, if all more important (if any) transports are done. Default `nil`. --- @param #string ClockStart Start time in format "HH:MM(:SS)(+D)", e.g. "13:05:30" or "08:30+1". Can also be given as a `#number`, in which case it is interpreted as relative amount in seconds from now on. --- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. --- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. --- @param #OPSGROUP DisembarkCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. --- @return Ops.OpsTransport#OPSTRANSPORT Cargo transport. -function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) - - -- Create a new cargo transport assignment. - local cargotransport=OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) - - if cargotransport then - - -- Set state to SCHEDULED. - cargotransport.status=OPSGROUP.TransportStatus.SCHEDULED - - --Add to cargo queue - table.insert(self.cargoqueue, cargotransport) - - end - - return cargotransport -end - --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The troop transport assignment. @@ -4971,134 +4932,6 @@ function OPSGROUP:DelCargoTransport(CargoTransport) return self end ---- Create a cargo transport assignment. --- @param #OPSGROUP self --- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! --- @param Core.Zone#ZONE Deployzone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. --- @param #number Prio Priority of this transport assignment. Should be a number between 1 (high prio) and 100 (low prio). --- @param #number Importance Importance of this transport assignment (lower=more important). A transport is only considered, if all more important (if any) transports are done. Default `nil`. --- @param #string ClockStart Start time in format "HH:MM:SS+D", e.g. "13:05:30" or "08:30+1". Can also be passed as a `#number` in which case it is interpreted as relative amount in seconds from now on. --- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. --- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. --- @param #OPSGROUP DisembarkCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. --- @return Ops.OpsTransport#OPSTRANSPORT Cargo transport. -function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) - - -- Current mission time. - local Tnow=timer.getAbsTime() - - -- Set start time. Default in 5 sec. - local Tstart=Tnow+5 - if ClockStart and type(ClockStart)=="number" then - Tstart=Tnow+ClockStart - elseif ClockStart and type(ClockStart)=="string" then - Tstart=UTILS.ClockToSeconds(ClockStart) - end - - -- Data structure. - local transport={} --Ops.OpsTransport#OPSTRANSPORT - transport.uid=self.cargocounter - transport.status=OPSGROUP.TransportStatus.PLANNING - transport.pickupzone=Pickupzone - transport.deployzone=Deployzone - transport.embarkzone=Embarkzone or Pickupzone - transport.disembarkzone=Disembarkzone or Deployzone - transport.prio=Prio or 50 - transport.importance=Importance - transport.Tstart=Tstart - transport.carrierGroup=DisembarkCarrierGroup - transport.cargos={} - - -- Check type of GroupSet provided. - if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then - - -- We got a single GROUP or OPSGROUP objectg. - local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) - - if cargo and self:CanCargo(cargo.opsgroup) then - table.insert(transport.cargos, cargo) - end - - else - - -- We got a SET_GROUP object. - - for _,group in pairs(GroupSet.Set) do - - local cargo=self:CreateCargoGroupData(group, Pickupzone, Deployzone) - - if cargo and self:CanCargo(cargo.opsgroup) then - table.insert(transport.cargos, cargo) - end - - end - end - - -- Debug info. - if self.verbose>=0 then - local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", - transport.uid, transport.pickupzone:GetName(), transport.embarkzone:GetName(), transport.deployzone:GetName(), transport.disembarkzone:GetName()) - local Weight=0 - for _,_cargo in pairs(transport.cargos) do - local cargo=_cargo --#OPSGROUP.CargoGroup - local weight=cargo.opsgroup:GetWeightTotal() - Weight=Weight+weight - text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) - end - text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #transport.cargos, Weight) - self:I(self.lid..text) - end - - if #transport.cargos>0 then - self.cargocounter=self.cargocounter+1 - return transport - else - self:E(self.lid.."ERROR: Cargo too heavy for this carrier group!") - return nil - end -end - ---- Create a cargo group data structure. --- @param #OPSGROUP self --- @param Wrapper.Group#GROUP group The GROUP object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. --- @param Core.Zone#ZONE Deployzone Deploy zone. --- @return #OPSGROUP.CargoGroup Cargo group data. -function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) - - local opsgroup=nil - - if group:IsInstanceOf("OPSGROUP") then - opsgroup=group - else - - opsgroup=_DATABASE:GetOpsGroup(group) - - if not opsgroup then - if group:IsAir() then - opsgroup=FLIGHTGROUP:New(group) - elseif group:IsShip() then - opsgroup=NAVYGROUP:New(group) - else - opsgroup=ARMYGROUP:New(group) - end - else - --env.info("FF found opsgroup in createcargo") - end - - end - - local cargo={} --#OPSGROUP.CargoGroup - - cargo.opsgroup=opsgroup - cargo.delivered=false - cargo.status="Unknown" - cargo.pickupzone=Pickupzone - cargo.deployzone=Deployzone - - return cargo -end --- Get total weight of the group including cargo. -- @param #OPSGROUP self @@ -5124,7 +4957,7 @@ end --- Get free cargo bay weight. -- @param #OPSGROUP self -- @param #string UnitName Name of the unit. Default is of the whole group. --- @return #number Total weight in kg. +-- @return #number Free cargo bay in kg. function OPSGROUP:GetFreeCargobay(UnitName) local Free=0 @@ -5133,10 +4966,17 @@ function OPSGROUP:GetFreeCargobay(UnitName) if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then local free=element.weightMaxCargo-element.weightCargo + --[[ + for _,_opsgroup in pairs(element.reservedCargos or {}) do + local opsgroup=_opsgroup --#OPSGROUP + free=free-opsgroup:GetWeightTotal() + end + ]] Free=Free+free end end + self:I(self.lid..string.format("Free cargo bay=%d kg (unit=%s)", Free, (UnitName or "whole group"))) return Free end @@ -5188,6 +5028,21 @@ function OPSGROUP:GetWeightCargo(UnitName) end end + + local gewicht=0 + for groupname, carriername in pairs(self.cargoBay) do + local element=self:GetElementByName(carriername) + if (UnitName==nil or UnitName==carriername) and (element and element.status~=OPSGROUP.ElementStatus.DEAD) then + local opsgroup=_DATABASE:FindOpsGroup(groupname) + if opsgroup then + gewicht=gewicht+opsgroup:GetWeightTotal() + end + end + end + + if gewicht~=weight then + self:I(self.lid..string.format("ERROR: FF weight!=gewicht: weight=%.1f, gewicht=%.1f", weight, gewicht)) + end return weight end @@ -5247,7 +5102,7 @@ function OPSGROUP:RedWeightCargo(UnitName, Weight) return self end ---- Check if the group can be carrier of a cargo group. +--- Check if the group can *in principle* be carrier of a cargo group. This checks the max cargo capacity of the group but *not* how much cargo is already loaded (if any). -- **Note** that the cargo group *cannot* be split into units, i.e. the largest cargo bay of any element of the group must be able to load the whole cargo group in one piece. -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. @@ -5258,11 +5113,14 @@ function OPSGROUP:CanCargo(CargoGroup) local weight=CargoGroup:GetWeightTotal() - for _,element in pairs(self.elements) do - local can=element.weightMaxCargo>=weight - if can then + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + -- Check that element is not dead and has + if element and element.status~=OPSGROUP.ElementStatus.DEAD and element.weightMaxCargo>=weight then return true end + end end @@ -5278,16 +5136,104 @@ function OPSGROUP:FindCarrierForCargo(CargoGroup) local weight=CargoGroup:GetWeightTotal() - for _,element in pairs(self.elements) do + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + local free=self:GetFreeCargobay(element.name) + if free>=weight then return element + else + self:T3(self.lid..string.format("%s: Weight %d>%d free cargo bay", element.name, weight, free)) end + end return nil end +--- Reserve cargo space for a cargo group. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. +-- @return #OPSGROUP.Element Carrier able to transport the cargo. +function OPSGROUP:ReserveCargoSpace(CargoGroup) + + local element=self:FindCarrierForCargo(CargoGroup) + + if element then + element.reservedCargo=element.reservedCargo or {} + table.insert(element.reservedCargo, CargoGroup) + end + + return nil +end + +--- Set my carrier. +-- @param #OPSGROUP self +-- @param #OPSGROUP CarrierGroup Carrier group. +-- @param #OPSGROUP.Element CarrierElement Carrier element. +-- @param #boolean Reserved If `true`, reserve space for me. +function OPSGROUP:_SetMyCarrier(CarrierGroup, CarrierElement, Reserved) + + self.mycarrier.group=CarrierGroup + self.mycarrier.element=CarrierElement + self.mycarrier.reserved=Reserved + +end + +--- Get my carrier group. +-- @param #OPSGROUP self +-- @return #OPSGROUP Carrier group. +function OPSGROUP:_GetMyCarrierGroup() + if self.mycarrier and self.mycarrier.group then + return self.mycarrier.group + end + return nil +end + +--- Get my carrier element. +-- @param #OPSGROUP self +-- @return #OPSGROUP.Element Carrier element. +function OPSGROUP:_GetMyCarrierElement() + if self.mycarrier and self.mycarrier.element then + return self.mycarrier.element + end + return nil +end + +--- Is my carrier reserved. +-- @param #OPSGROUP self +-- @return #boolean If `true`, space for me was reserved. +function OPSGROUP:_IsMyCarrierReserved() + if self.mycarrier then + return self.mycarrier.reserved + end + return nil +end + + + +--- Get my carrier. +-- @param #OPSGROUP self +-- @return #OPSGROUP Carrier group. +-- @return #OPSGROUP.Element Carrier element. +-- @return #boolean If `true`, space is reserved for me +function OPSGROUP:_GetMyCarrier() + return self.mycarrier.group, self.mycarrier.element, self.mycarrier.reserved +end + + +--- Remove my carrier. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:_RemoveMyCarrier() + self.mycarrier.group=nil + self.mycarrier.element=nil + self.mycarrier.reserved=nil + self.mycarrier={} + return self +end + --- On after "Pickup" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -5403,14 +5349,6 @@ function OPSGROUP:onafterPickup(From, Event, To) end ---- On before "Loading" event. --- @param #OPSGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function OPSGROUP:onbeforeLoading(From, Event, To) - -end --- On after "Loading" event. -- @param #OPSGROUP self @@ -5485,12 +5423,12 @@ function OPSGROUP:onafterLoading(From, Event, To) else -- Debug info. - env.info("FF cannot board carrier") + self:T(self.lid.."Cannot board carrier!") end else -- Debug info. - env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) + self:T(self.lid.."Cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) end end @@ -5526,7 +5464,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) self:I(self.lid..string.format("Loading group %s", tostring(CargoGroup.groupname))) -- Carrier element. - local carrier=Carrier or CargoGroup.carrier --#OPSGROUP.Element + local carrier=Carrier or CargoGroup:_GetMyCarrierElement() --#OPSGROUP.Element -- No carrier provided. if not carrier then @@ -5553,8 +5491,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) CargoGroup:ClearWaypoints() -- Set carrier (again). - CargoGroup.carrier=carrier - CargoGroup.carrierGroup=self + CargoGroup:_SetMyCarrier(self, carrier, false) -- Despawn this group. if CargoGroup:IsAlive() then @@ -5576,7 +5513,9 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterLoaded(From, Event, To) - env.info("FF loaded") + + -- Debug info. + self:T(self.lid.."Carrier Loaded ==> Transport") -- Cancel landedAt task. if self:IsFlightgroup() and self:IsLandedAt() then @@ -5691,6 +5630,17 @@ function OPSGROUP:onafterTransport(From, Event, To) elseif self.isArmygroup then + local path=self.cargoTransport:_GetPathTransport() + if path then + + for _,_zone in pairs(path.zones:GetSet()) do + local zone=_zone --Core.Zone#ZONE + local coordinate=zone:GetRandomCoordinate(nil, nil, nil) --TODO: surface type land + local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.temp=true + end + + end + -- ARMYGROUP local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true @@ -5699,8 +5649,24 @@ function OPSGROUP:onafterTransport(From, Event, To) elseif self.isNavygroup then + local cwp=self:GetWaypointCurrent() + local uid=cwp and cwp.uid or nil + + -- Get a (random) pre-defined transport path. + local path=self.cargoTransport:_GetPathTransport() + + if path then + -- Loop over zones + for i,coordinate in pairs(path) do + local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) + waypoint.temp=true + uid=waypoint.uid + coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) + end + end + -- NAVYGROUP - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=true -- Give cruise command. self:Cruise() @@ -5731,93 +5697,107 @@ function OPSGROUP:onafterUnloading(From, Event, To) -- Check that cargo is loaded into this group. -- NOTE: Could be that the element carriing this cargo group is DEAD, which would mean that the cargo group is also DEAD. - if cargo.opsgroup:IsLoaded(self.groupname) and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then + if cargo.opsgroup:IsLoaded(self.groupname) and not cargo.opsgroup:IsDead() then - env.info("FF deploy cargo "..cargo.opsgroup:GetName()) - - -- New carrier group. - local carrierGroup=self.cargoTransport.carrierGroup + -- Disembark to carrier. + local needscarrier=false + local carrier=nil + local carrierGroup=nil - -- Cargo was delivered (somehow). - cargo.delivered=true - - -- Increase number of delivered cargos. - self.cargoTransport.Ndelivered=self.cargoTransport.Ndelivered+1 - - if carrierGroup then - - --- - -- Delivered to another carrier group. - --- - - -- Get new carrier. - local carrier=carrierGroup:FindCarrierForCargo(cargo.opsgroup) - - if carrier then - -- Unload from this and directly load into the other carrier. - self:Unload(cargo.opsgroup) - carrierGroup:Load(cargo.opsgroup, carrier) - else - env.info("ERROR: No element of the group can take this cargo!") - end - - elseif zone and zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then - - --- - -- Delivered to a ship via helo or VTOL - --- - - -- - env.info("ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") - - else - - --- - -- Delivered to deploy zone - --- - - if self.cargoTransport.disembarkInUtero then - - -- Unload but keep "in utero" (no coordinate provided). - self:Unload(cargo.opsgroup) - - else - - local Coordinate=nil - - if self.cargoTransport.disembarkzone then - -- Random coordinate in disembark zone. - Coordinate=self.cargoTransport.disembarkzone:GetRandomCoordinate() - - else + if self.cargoTransport.disembarkCarriers and #self.cargoTransport.disembarkCarriers>0 then + + needscarrier=true - -- TODO: Optimize with random Vec2 - - -- Create a zone around the carrier. - local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) - - -- Random coordinate/heading in the zone. - Coordinate=zoneCarrier:GetRandomCoordinate(50) - - end - - -- Random heading of the group. - local Heading=math.random(0,359) - - -- Unload to Coordinate. - self:Unload(cargo.opsgroup, Coordinate, self.cargoTransport.disembarkActivation, Heading) - - end + carrier, carrierGroup=self.cargoTransport:FindTransferCarrierForCargo(cargo.opsgroup, zone) - -- Trigger "Unloaded" event for current cargo transport - self.cargoTransport:Unloaded(cargo.opsgroup) - + --TODO: max unloading time if transfer carrier does not arrive in the zone. + end - end + if needscarrier==false or (needscarrier and carrier and carrierGroup) then + + -- Cargo was delivered (somehow). + cargo.delivered=true + + -- Increase number of delivered cargos. + self.cargoTransport.Ndelivered=self.cargoTransport.Ndelivered+1 + + if carrier and carrierGroup then + + --- + -- Delivered to another carrier group. + --- + + -- Unload from this and directly load into the other carrier. + self:Unload(cargo.opsgroup) + carrierGroup:Load(cargo.opsgroup, carrier) + + elseif zone and zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then - end + --- + -- Delivered to a ship via helo or VTOL + --- + + -- Issue warning. + env.info("ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") + --TODO: Dumb into sea. + + else + + --- + -- Delivered to deploy zone + --- + + if self.cargoTransport.disembarkInUtero then + + -- Unload but keep "in utero" (no coordinate provided). + self:Unload(cargo.opsgroup) + + else + + local Coordinate=nil + + if self.cargoTransport.disembarkzone then + + -- Random coordinate in disembark zone. + Coordinate=self.cargoTransport.disembarkzone:GetRandomCoordinate() + + else + + -- TODO: Optimize with random Vec2 + + -- Create a zone around the carrier. + local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) + + -- Random coordinate/heading in the zone. + Coordinate=zoneCarrier:GetRandomCoordinate(50) + + end + + -- Random heading of the group. + local Heading=math.random(0,359) + + -- Unload to Coordinate. + self:Unload(cargo.opsgroup, Coordinate, self.cargoTransport.disembarkActivation, Heading) + + end + + -- Trigger "Unloaded" event for current cargo transport + self.cargoTransport:Unloaded(cargo.opsgroup) + + + end + + else + self:T(self.lid.."Cargo needs carrier but no carrier is avaiable (yet)!") + end + + else + -- Not loaded or dead + end + + end -- loop over cargos end @@ -5854,6 +5834,8 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated -- Set cargo status. OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + --TODO: Unload flightgroup. Find parking spot etc. + if Coordinate then --- @@ -5899,6 +5881,14 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated -- Respawn group. OpsGroup:_Respawn(0, Template) + + if self:IsNavygroup() then + self.currentwp=1 + NAVYGROUP.AddWaypoint(self, Coordinate, nil, nil, nil, false) + elseif self:IsArmygroup() then + self.currentwp=1 + ARMYGROUP.AddWaypoint(self, Coordinate, nil, nil, nil, false) + end else @@ -6033,6 +6023,14 @@ function OPSGROUP:onbeforeBoard(From, Event, To, CarrierGroup, Carrier) if self:IsDead() then self:I(self.lid.."Group DEAD ==> Deny Board transition!") return false + elseif CarrierGroup:IsDead() then + self:I(self.lid.."Carrier Group DEAD ==> Deny Board transition!") + self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + return false + elseif Carrier.status==OPSGROUP.ElementStatus.DEAD then + self:I(self.lid.."Carrier Element DEAD ==> Deny Board transition!") + self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + return false end return true @@ -6052,21 +6050,21 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) -- Set cargo status. self.cargoStatus=OPSGROUP.CargoStatus.BOARDING - -- Set carrier. - self.carrier=Carrier - self.carrierGroup=CarrierGroup + -- Set carrier. As long as the group is not loaded, we only reserve the cargo space. + self:_SetMyCarrier(CarrierGroup, Carrier, true) - -- Army or navy group. - local isArmyOrNavy=CarrierGroup:IsArmygroup() or CarrierGroup:IsNavygroup() + -- Army or Navy group. + local CarrierIsArmyOrNavy=CarrierGroup:IsArmygroup() or CarrierGroup:IsNavygroup() + local CargoIsArmyOrNavy=self:IsArmygroup() or self:IsNavygroup() -- Check that carrier is standing still. - if isArmyOrNavy and CarrierGroup:IsHolding() or CarrierGroup:IsParking() or CarrierGroup:IsLandedAt() then + if (CarrierIsArmyOrNavy and CarrierGroup:IsHolding()) or (CarrierGroup:IsParking() or CarrierGroup:IsLandedAt()) then -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. - local board=self.speedMax>0 and self:IsAlive() and isArmyOrNavy and self.carrierGroup:IsAlive() + local board=self.speedMax>0 and CargoIsArmyOrNavy and self:IsAlive() and CarrierGroup:IsAlive() -- Armygroup cannot board ship ==> Load directly. - if self:IsArmygroup() and self.carrierGroup:IsNavygroup() then + if self:IsArmygroup() and CarrierGroup:IsNavygroup() then board=false end @@ -6093,16 +6091,20 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) else + --- + -- Direct load into carrier. + --- + -- Debug info. - self:I(self.lid..string.format("FF board with direct load to carrier %s", self.carrierGroup:GetName())) + self:I(self.lid..string.format("Board with direct load to carrier %s", CarrierGroup:GetName())) -- Trigger Load event. - self.carrierGroup:Load(self) + CarrierGroup:Load(self) end else - env.info("FF Carrier not ready for boarding yet ==> repeating boarding call in 10 sec") + self:T(self.lid.."Carrier not ready for boarding yet ==> repeating boarding call in 10 sec") self:__Board(-10, CarrierGroup, Carrier) end @@ -6827,7 +6829,17 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) opsgroup:I(opsgroup.lid..text) -- Trigger PassingWaypoint event. - if waypoint.astar then + if waypoint.temp then + + -- Remove temp waypoint. + opsgroup:RemoveWaypointByID(uid) + + if opsgroup:IsNavygroup() or opsgroup:IsArmygroup() then + --TODO: not sure if this works with FLIGHTGROUPS + opsgroup:Cruise() + end + + elseif waypoint.astar then -- Remove Astar waypoint. opsgroup:RemoveWaypointByID(uid) @@ -6866,8 +6878,6 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif opsgroup:IsTransporting() then if opsgroup.isFlightgroup then - - env.info("FF passing waypointg in state istransporting ==> land at") -- Land at current pos and wait for 60 min max. opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) @@ -6880,12 +6890,15 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif opsgroup:IsBoarding() then - if opsgroup.carrierGroup and opsgroup.carrierGroup:IsAlive() then + local carrierGroup=opsgroup:_GetMyCarrierGroup() + local carrier=opsgroup:_GetMyCarrierElement() + + if carrierGroup and carrierGroup:IsAlive() then - if opsgroup.carrier and opsgroup.carrier.unit and opsgroup.carrier.unit:IsAlive() then + if carrier and carrier.unit and carrier.unit:IsAlive() then -- Load group into the carrier. - opsgroup.carrierGroup:Load(opsgroup) + carrierGroup:Load(opsgroup) else opsgroup:E(opsgroup.lid.."ERROR: Group cannot board assigned carrier UNIT as it is NOT alive!") @@ -7651,10 +7664,7 @@ function OPSGROUP:_UpdatePosition() -- Add up travelled distance. self.traveldist=self.traveldist+self.travelds - - -- Debug info. - --env.info(string.format("FF Traveled %.1f m", self.traveldist)) - + end return self diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 6bf39a147..ef21c495a 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -26,9 +26,8 @@ -- @field #string lid Log ID. -- @field #number uid Unique ID of the transport. -- @field #number verbose Verbosity level. --- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. +-- @field #table cargos Cargos. Each element is a @{Ops.OpsGroup#OPSGROUP.CargoGroup}. -- @field #table carriers Carriers assigned for this transport. --- @field #string status Status of the transport. See @{#OPSTRANSPORT.Status}. -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). -- @field #number importance Importance of this transport. Smaller=higher. -- @field #number Tstart Start time in *abs.* seconds. @@ -39,12 +38,13 @@ -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. -- @field Core.Zone#ZONE unboardzone (Optional) Zone where the cargo is going to after disembarkment. --- @field Ops.OpsGroup#OPSGROUP carrierGroup The new carrier group. -- @field #boolean disembarkActivation Activation setting when group is disembared from carrier. -- @field #boolean disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. +-- @field #table disembarkCarriers Table of carriers to which the cargo is disembared. This is a direct transfer from the old to the new carrier. -- @field #number Ncargo Total number of cargo groups. -- @field #number Ncarrier Total number of assigned carriers. -- @field #number Ndelivered Total number of cargo groups delivered. +-- @field #table pathsTransport Paths of `#OPSGROUP.Path`. -- @extends Core.Fsm#FSM --- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill @@ -64,12 +64,13 @@ OPSTRANSPORT = { cargos = {}, carriers = {}, carrierTransportStatus = {}, - conditionStart = {}, + conditionStart = {}, + pathsTransport = {}, } --- Cargo transport status. -- @type OPSTRANSPORT.Status --- @field #string PLANNING Planning state. +-- @field #string PLANNED Planning state. -- @field #string SCHEDULED Transport is scheduled in the cargo queue. -- @field #string EXECUTING Transport is being executed. -- @field #string DELIVERED Transport was delivered. @@ -80,6 +81,12 @@ OPSTRANSPORT.Status={ DELIVERED="delivered", } +--- Path. +-- @type OPSTRANSPORT.Path +-- @field #table coords Table of coordinates. +-- @field #number radius Radomization radius in meters. Default 0 m. +-- @field #number altitude Altitude in feet AGL. Only for aircraft. + --- Generic mission condition. -- @type OPSTRANSPORT.Condition -- @field #function func Callback function to check for a condition. Should return a #boolean. @@ -115,32 +122,30 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) local self=BASE:Inherit(self, FSM:New()) -- #OPSTRANSPORT -- Increase ID counter. - _OPSTRANSPORTID=_OPSTRANSPORTID+1 + _OPSTRANSPORTID=_OPSTRANSPORTID+1 -- Set some string id for output to DCS.log file. self.lid=string.format("OPSTRANSPORT [UID=%d] | ", _OPSTRANSPORTID) -- Defaults. self.uid=_OPSTRANSPORTID - self.status=OPSTRANSPORT.Status.PLANNING + --self.status=OPSTRANSPORT.Status.PLANNING self.pickupzone=Pickupzone self.deployzone=Deployzone self.embarkzone=Pickupzone - self.carrierGroup=nil self.cargos={} self.carriers={} self.Ncargo=0 self.Ncarrier=0 self.Ndelivered=0 - self:SetPriority() self:SetTime() - -- Add cargo groups. + -- Add cargo groups (could also be added later). if GroupSet then - self:AddCargoGroups(GroupSet, Pickupzone, Deployzone) + self:AddCargoGroups(GroupSet) end @@ -152,8 +157,11 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. self:AddTransition(OPSTRANSPORT.Status.EXECUTING, "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. + self:AddTransition("*", "Status", "*") self:AddTransition("*", "Stop", "*") + + self:AddTransition("*", "Loaded", "*") self:AddTransition("*", "Unloaded", "*") @@ -171,13 +179,13 @@ end -- @param #OPSTRANSPORT self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be passed as a single GROUP or OPSGROUP object. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) +function OPSTRANSPORT:AddCargoGroups(GroupSet) -- Check type of GroupSet provided. if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then -- We got a single GROUP or OPSGROUP object. - local cargo=self:_CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + local cargo=self:_CreateCargoGroupData(GroupSet) if cargo then --and self:CanCargo(cargo.opsgroup) table.insert(self.cargos, cargo) @@ -190,7 +198,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) for _,group in pairs(GroupSet.Set) do - local cargo=self:_CreateCargoGroupData(group, Pickupzone, Deployzone) + local cargo=self:_CreateCargoGroupData(group) if cargo then table.insert(self.cargos, cargo) @@ -202,11 +210,10 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) -- Debug info. if self.verbose>=0 then - local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", - self.uid, self.pickupzone:GetName(), self.embarkzone and self.embarkzone:GetName() or "none", self.deployzone:GetName(), self.disembarkzone and self.disembarkzone:GetName() or "none") + local text=string.format("Added cargo groups:") local Weight=0 for _,_cargo in pairs(self.cargos) do - local cargo=_cargo --#OPSGROUP.CargoGroup + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup local weight=cargo.opsgroup:GetWeightTotal() Weight=Weight+weight text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) @@ -250,6 +257,40 @@ function OPSTRANSPORT:SetDisembarkActivation(Active) return self end +--- Set transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. +-- @param #OPSTRANSPORT self +-- @param Core.Set#SET_GROUP Carriers Carrier set. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetDisembarkCarriers(Carriers) + + self:I(self.lid.."Setting transfer carriers!") + + -- Create table. + self.disembarkCarriers=self.disembarkCarriers or {} + + if Carriers:IsInstanceOf("GROUP") or Carriers:IsInstanceOf("OPSGROUP") then + + local carrier=self:_GetOpsGroupFromObject(Carriers) + if carrier then + table.insert(self.disembarkCarriers, carrier) + end + + elseif Carriers:IsInstanceOf("SET_GROUP") or Carriers:IsInstanceOf("SET_OPSGROUP") then + + for _,object in pairs(Carriers:GetSet()) do + local carrier=self:_GetOpsGroupFromObject(object) + if carrier then + table.insert(self.disembarkCarriers, carrier) + end + end + + else + self:E(self.lid.."ERROR: Carriers must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!") + end + + return self +end + --- Set if group remains *in utero* after disembarkment from carrier. Can be used to directly load the group into another carrier. Similar to disembark in late activated state. -- @param #OPSTRANSPORT self @@ -264,21 +305,7 @@ function OPSTRANSPORT:SetDisembarkInUtero(InUtero) return self end ---- Check if an OPS group is assigned as carrier for this transport. --- @param #OPSTRANSPORT self --- @param Ops.OpsGroup#OPSGROUP CarrierGroup Potential carrier OPSGROUP. --- @return #boolean If true, group is an assigned carrier. -function OPSTRANSPORT:IsCarrier(CarrierGroup) - for _,_carrier in pairs(self.carriers) do - local carrier=_carrier --Ops.OpsGroup#OPSGROUP - if carrier.groupname==CarrierGroup.groupname then - return true - end - end - - return false -end --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self @@ -358,20 +385,76 @@ end -- @param ... Condition function arguments if any. -- @return #OPSTRANSPORT self function OPSTRANSPORT:AddConditionStart(ConditionFunction, ...) + + if ConditionFunction then - local condition={} --#OPSTRANSPORT.Condition - - condition.func=ConditionFunction - condition.arg={} - if arg then - condition.arg=arg + local condition={} --#OPSTRANSPORT.Condition + + condition.func=ConditionFunction + condition.arg={} + if arg then + condition.arg=arg + end + + table.insert(self.conditionStart, condition) + end - table.insert(self.conditionStart, condition) - return self end +--- Add path used for transportation from the pickup to the deploy zone. If multiple paths are defined, a random one is chosen. +-- @param #OPSTRANSPORT self +-- @param Wrapper.Group#GROUP PathGroup A (late activated) GROUP defining a transport path by their waypoints. +-- @param #number Radius Randomization radius in meters. Default 0 m. +-- @param #number Altitude Altitude in feet AGL. Only for aircraft. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:AddPathTransport(PathGroup, Radius, Altitude) + + local path={} --#OPSTRANSPORT.Path + path.coords={} + path.radius=Radius or 0 + path.altitude=Altitude + + -- Get route points. + local waypoints=PathGroup:GetTaskRoute() + + for _,wp in pairs(waypoints) do + local coord=COORDINATE:New(wp.x, wp.alt, wp.y) + table.insert(path.coords, coord) + end + + -- Add path. + table.insert(self.pathsTransport, path) + + return self +end + +--- Get a path for transportation. +-- @param #OPSTRANSPORT self +-- @return #table The path of COORDINATEs. +function OPSTRANSPORT:_GetPathTransport() + + if self.pathsTransport and #self.pathsTransport>0 then + + -- Get a random path for transport. + local path=self.pathsTransport[math.random(#self.pathsTransport)] --#OPSTRANSPORT.Path + + + local coordinates={} + for _,coord in ipairs(path.coords) do + + -- TODO: Add randomization. + + table.insert(coordinates, coord) + end + + return coordinates + end + + return nil +end + --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self @@ -394,46 +477,22 @@ function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup) end ---- Create a cargo group data structure. --- @param #OPSTRANSPORT self --- @param Wrapper.Group#GROUP group The GROUP object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. --- @param Core.Zone#ZONE Deployzone Deploy zone. --- @return #OPSGROUP.CargoGroup Cargo group data. -function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) - local opsgroup=nil - - if group:IsInstanceOf("OPSGROUP") then - opsgroup=group - elseif group:IsInstanceOf("GROUP") then - - opsgroup=_DATABASE:GetOpsGroup(group) - - if not opsgroup then - if group:IsAir() then - opsgroup=FLIGHTGROUP:New(group) - elseif group:IsShip() then - opsgroup=NAVYGROUP:New(group) - else - opsgroup=ARMYGROUP:New(group) - end + +--- Check if an OPS group is assigned as carrier for this transport. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CarrierGroup Potential carrier OPSGROUP. +-- @return #boolean If true, group is an assigned carrier. +function OPSTRANSPORT:IsCarrier(CarrierGroup) + + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + if carrier.groupname==CarrierGroup.groupname then + return true end - - else - self:E(self.lid.."ERROR: Cargo must be a GROUP or OPSGROUP object!") - return nil end - local cargo={} --#OPSGROUP.CargoGroup - - cargo.opsgroup=opsgroup - cargo.delivered=false - cargo.status="Unknown" - cargo.pickupzone=Pickupzone - cargo.deployzone=Deployzone - - return cargo + return false end --- Check if transport is ready to be started. @@ -443,31 +502,49 @@ end -- @param #OPSTRANSPORT self -- @return #boolean If true, mission can be started. function OPSTRANSPORT:IsReadyToGo() + + -- Debug text. + local text=self.lid.."Is ReadyToGo? " + -- Current abs time. local Tnow=timer.getAbsTime() -- Start time did not pass yet. if self.Tstart and Tnowself.Tstop or false then + text=text.."Nope, stop time already passed!" + self:T(text) return false end -- All start conditions true? local startme=self:EvalConditionsAll(self.conditionStart) + -- Nope, not yet. if not startme then + text=text..("No way, at least one start condition is not true!") + self:I(text) return false end - -- We're good to go! + text=text.."Yes!" + self:T(text) return true end +--- Check if all cargo was delivered (or is dead). +-- @param #OPSTRANSPORT self +-- @return #boolean If true, all possible cargo was delivered. +function OPSTRANSPORT:IsDelivered() + return self:is(OPSTRANSPORT.Status.DELIVERED) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status Update ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -482,14 +559,16 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) -- Current FSM state. local fsmstate=self:GetState() - + -- Info text. local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, self.Ncarrier) text=text..string.format("\nCargos:") for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - local name=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "none" - text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s", cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name) + local carrier=cargo.opsgroup:_GetMyCarrierElement() + local name=carrier and carrier.name or "none" + local cstate=carrier and carrier.status or "N/A" + text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s]", cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name, cstate) end text=text..string.format("\nCarriers:") @@ -504,7 +583,10 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) -- Check if all cargo was delivered (or is dead). self:_CheckDelivered() - self:__Status(-30) + -- Update status again. + if not self:IsDelivered() then + self:__Status(-30) + end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -574,39 +656,44 @@ end -- @param #OPSTRANSPORT self function OPSTRANSPORT:_CheckDelivered() - local done=true - local dead=true - for _,_cargo in pairs(self.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - if cargo.delivered then - -- This one is delivered. - dead=false - elseif cargo.opsgroup==nil then - -- This one is nil?! - dead=false - elseif cargo.opsgroup:IsDestroyed() then - -- This one was destroyed. - elseif cargo.opsgroup:IsDead() then - -- This one is dead. - dead=false - elseif cargo.opsgroup:IsStopped() then - -- This one is stopped. - dead=false - else - done=false --Someone is not done! - dead=false + -- First check that at least one cargo was added (as we allow to do that later). + if self.Ncargo>0 then + + local done=true + local dead=true + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if cargo.delivered then + -- This one is delivered. + dead=false + elseif cargo.opsgroup==nil then + -- This one is nil?! + dead=false + elseif cargo.opsgroup:IsDestroyed() then + -- This one was destroyed. + elseif cargo.opsgroup:IsDead() then + -- This one is dead. + dead=false + elseif cargo.opsgroup:IsStopped() then + -- This one is stopped. + dead=false + else + done=false --Someone is not done! + dead=false + end + end - - end - - if dead then - --self:CargoDead() - self:I(self.lid.."All cargo DEAD!") - self:Delivered() - elseif done then - self:I(self.lid.."All cargo delivered") - self:Delivered() + + if dead then + --self:CargoDead() + self:I(self.lid.."All cargo DEAD!") + self:Delivered() + elseif done then + self:I(self.lid.."All cargo delivered") + self:Delivered() + end + end end @@ -634,3 +721,92 @@ function OPSTRANSPORT:EvalConditionsAll(Conditions) -- All conditions were true. return true end + + + +--- Find transfer carrier element for cargo group. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CargoGroup The cargo group that needs to be loaded into a carrier unit/element of the carrier group. +-- @param Core.Zone#ZONE Zone (Optional) Zone where the carrier must be in. +-- @return Ops.OpsGroup#OPSGROUP.Element New carrier element for cargo or nil. +-- @return Ops.OpsGroup#OPSGROUP New carrier group for cargo or nil. +function OPSTRANSPORT:FindTransferCarrierForCargo(CargoGroup, Zone) + + local carrier=nil --Ops.OpsGroup#OPSGROUP.Element + local carrierGroup=nil --Ops.OpsGroup#OPSGROUP + + --TODO: maybe sort the carriers wrt to largest free cargo bay. Or better smallest free cargo bay that can take the cargo group weight. + + for _,_carrier in pairs(self.disembarkCarriers or {}) do + local carrierGroup=_carrier --Ops.OpsGroup#OPSGROUP + + -- Find an element of the group that has enough free space. + carrier=carrierGroup:FindCarrierForCargo(CargoGroup) + + if carrier then + if Zone==nil or Zone:IsCoordinateInZone(carrier.unit:GetCoordinate()) then + return carrier, carrierGroup + else + self:T3(self.lid.."Got transfer carrier but carrier not in zone (yet)!") + end + else + self:T3(self.lid.."No transfer carrier available!") + end + end + + return nil, nil +end + +--- Create a cargo group data structure. +-- @param #OPSTRANSPORT self +-- @param Wrapper.Group#GROUP group The GROUP or OPSGROUP object. +-- @return Ops.OpsGroup#OPSGROUP.CargoGroup Cargo group data. +function OPSTRANSPORT:_CreateCargoGroupData(group) + + local opsgroup=self:_GetOpsGroupFromObject(group) + + local cargo={} --Ops.OpsGroup#OPSGROUP.CargoGroup + + cargo.opsgroup=opsgroup + cargo.delivered=false + cargo.status="Unknown" + cargo.disembarkCarrierElement=nil + cargo.disembarkCarrierGroup=nil + + return cargo +end + +--- Get an OPSGROUIP +-- @param #OPSTRANSPORT self +-- @param Core.Base#BASE Object The object, which can be a GROUP or OPSGROUP. +-- @return Ops.OpsGroup#OPSGROUP Ops Group. +function OPSTRANSPORT:_GetOpsGroupFromObject(Object) + + local opsgroup=nil + + if Object:IsInstanceOf("OPSGROUP") then + -- We already have an OPSGROUP + opsgroup=Object + elseif Object:IsInstanceOf("GROUP") then + + -- Look into DB and try to find an existing OPSGROUP. + opsgroup=_DATABASE:GetOpsGroup(Object) + + if not opsgroup then + if Object:IsAir() then + opsgroup=FLIGHTGROUP:New(Object) + elseif Object:IsShip() then + opsgroup=NAVYGROUP:New(Object) + else + opsgroup=ARMYGROUP:New(Object) + end + end + + else + self:E(self.lid.."ERROR: Object must be a GROUP or OPSGROUP object!") + return nil + end + + return opsgroup +end + From 3b2cbea1c43753825d2b8c1d82b51e00eeb5004f Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 2 Mar 2021 21:12:51 +0100 Subject: [PATCH 028/141] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 047912831..c365e03cc 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -179,7 +179,6 @@ OPSGROUP = { --- OPS group element. -- @type OPSGROUP.Element --- -- @field #string name Name of the element, i.e. the unit. -- @field #string status The element status. See @{#OPSGROUP.ElementStatus}. -- @field Wrapper.Unit#UNIT unit The UNIT object. @@ -187,6 +186,10 @@ OPSGROUP = { -- @field DCS#Unit DCSunit The DCS unit object. -- @field #boolean ai If true, element is AI. -- @field #string skill Skill level. +-- +-- @field Core.Zone#ZONE zoneBoundingbox Bounding box zone of the +-- @field Core.Zone#ZONE zoneLoad Loading zone. +-- @field Core.Zone#ZONE zoneUnload Unloading zone. -- -- @field #string typename Type name. -- @field #number category Aircraft category. From f4a3f6d43370dbecf7b91ce23d8115eb02c46e61 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 3 Mar 2021 00:03:39 +0100 Subject: [PATCH 029/141] OPS --- Moose Development/Moose/Ops/OpsGroup.lua | 56 +++++++++++++++++++ Moose Development/Moose/Utilities/Utils.lua | 62 +++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c365e03cc..60622ee81 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -5772,6 +5772,19 @@ function OPSGROUP:onafterUnloading(From, Event, To) -- Create a zone around the carrier. local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) + + + local d={} + d.p1={x=vec2.x-l/2, y=vec2.y-w/2} --DCS#Vec2 + d.p2={x=vec2.x-l/2, y=vec2.y+w/2} --DCS#Vec2 + d.p3={x=d2.x+20, y=d2.y+20} + d.p4={x=d1.x+20, y=d1.y+20} + + for _,_p in pairs(d) do + local p=_p --#DCSVec2 + end + + local zoneCarrier=ZONE_POLYGON_BASE:New("Carrier", {d1, d2, d3, d4}) -- Random coordinate/heading in the zone. Coordinate=zoneCarrier:GetRandomCoordinate(50) @@ -7978,6 +7991,48 @@ function OPSGROUP:GetElementByName(unitname) return nil end +--- Get the bounding box of the element. +-- @param #OPSGROUP self +-- @param #string UnitName Name of unit. +-- @return Core.Zone#ZONE_POLYGON Bounding box polygon zone. +function OPSGROUP:GetElementBoundingBox(UnitName) + + local element=self:GetElementByName(UnitName) + + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + + local l=element.length + local w=element.width + + local heading=element.unit:GetHeading() + + env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) + + local vec2=self:GetVec2(element.name) + + -- Set of + local b={} + b[1]={y=l/2, x=-w/2} --DCS#Vec2 + b[2]={y=l/2, x=w/2} --DCS#Vec2 + b[3]={y=-l/2, x=w/2} --DCS#Vec2 + b[4]={y=-l/2, x=-w/2} --DCS#Vec2 + + for i,p in pairs(b) do + b[i]=UTILS.Vec2Rotate2D(p, heading) + end + + local d=UTILS.Vec2Norm(vec2) + local h=UTILS.Vec2Hdg(vec2) + for i,p in pairs(b) do + --b[i]=UTILS.Vec2Translate(p, d, h) + end + + return ZONE_POLYGON_BASE:New(element.name, b) + end + + return nil +end + --- Get the first element of a group, which is alive. -- @param #OPSGROUP self -- @return #OPSGROUP.Element The element or `#nil` if no element is alive any more. @@ -7995,6 +8050,7 @@ function OPSGROUP:GetElementAlive() return nil end + --- Get number of elements alive. -- @param #OPSGROUP self -- @param #string status (Optional) Only count number, which are in a special status. diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index adb9cea66..bc5a1a352 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -931,6 +931,15 @@ function UTILS.VecDot(a, b) return a.x*b.x + a.y*b.y + a.z*b.z end +--- Calculate the [dot product](https://en.wikipedia.org/wiki/Dot_product) of two 2D vectors. The result is a number. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @param DCS#Vec2 b Vector in 2D with x, y components. +-- @return #number Scalar product of the two vectors a*b. +function UTILS.Vec2Dot(a, b) + return a.x*b.x + a.y*b.y +end + + --- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 3D vector. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @return #number Norm of the vector. @@ -938,6 +947,13 @@ function UTILS.VecNorm(a) return math.sqrt(UTILS.VecDot(a, a)) end +--- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 2D vector. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @return #number Norm of the vector. +function UTILS.Vec2Norm(a) + return math.sqrt(UTILS.Vec2Dot(a, a)) +end + --- Calculate the distance between two 2D vectors. -- @param DCS#Vec2 a Vector in 3D with x, y components. -- @param DCS#Vec2 b Vector in 3D with x, y components. @@ -1020,6 +1036,17 @@ function UTILS.VecHdg(a) return h end +--- Calculate "heading" of a 2D vector in the X-Y plane. +-- @param DCS#Vec2 a Vector in "D with x, y components. +-- @return #number Heading in degrees in [0,360). +function UTILS.Vec2Hdg(a) + local h=math.deg(math.atan2(a.y, a.x)) + if h<0 then + h=h+360 + end + return h +end + --- Calculate the difference between two "heading", i.e. angles in [0,360) deg. -- @param #number h1 Heading one. -- @param #number h2 Heading two. @@ -1056,6 +1083,22 @@ function UTILS.VecTranslate(a, distance, angle) return {x=TX, y=a.y, z=TY} end +--- Translate 2D vector in the 2D (x,z) plane. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @param #number distance The distance to translate. +-- @param #number angle Rotation angle in degrees. +-- @return DCS#Vec2 Translated vector. +function UTILS.Vec2Translate(a, distance, angle) + + local SX = a.x + local SY = a.y + local Radians=math.rad(angle or 0) + local TX=distance*math.cos(Radians)+SX + local TY=distance*math.sin(Radians)+SY + + return {x=TX, y=TY} +end + --- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param #number angle Rotation angle in degrees. @@ -1076,6 +1119,25 @@ function UTILS.Rotate2D(a, angle) return A end +--- Rotate 2D vector in the 2D (x,z) plane. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @param #number angle Rotation angle in degrees. +-- @return DCS#Vec2 Vector rotated in the (x,y) plane. +function UTILS.Vec2Rotate2D(a, angle) + + local phi=math.rad(angle) + + local x=a.y + local y=a.x + + local Z=x*math.cos(phi)-y*math.sin(phi) + local X=x*math.sin(phi)+y*math.cos(phi) + + local A={x=X, y=Z} + + return A +end + --- Converts a TACAN Channel/Mode couple into a frequency in Hz. -- @param #number TACANChannel The TACAN channel, i.e. the 10 in "10X". From 56e2b06e9dd9a644cb978d4a051626806322af77 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 7 Mar 2021 23:47:29 +0100 Subject: [PATCH 030/141] OPS Cargo --- Moose Development/Moose/Core/Zone.lua | 28 +- Moose Development/Moose/Ops/OpsGroup.lua | 371 ++++++++++++++---- Moose Development/Moose/Ops/OpsTransport.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 10 +- .../Moose/Wrapper/Positionable.lua | 33 +- 5 files changed, 332 insertions(+), 112 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 9fd532dbd..c4b0fd348 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1728,26 +1728,28 @@ end -- @param #ZONE_POLYGON_BASE self -- @return DCS#Vec2 The Vec2 coordinate. function ZONE_POLYGON_BASE:GetRandomVec2() - self:F2() - --- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way... - local Vec2Found = false - local Vec2 + -- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way... + + -- Get the bounding square. local BS = self:GetBoundingSquare() - self:T2( BS ) + local Nmax=1000 ; local n=0 + while n0 then @@ -1451,13 +1451,136 @@ function OPSGROUP:SelfDestruction(Delay, ExplosionPower) local unit=element.unit if unit and unit:IsAlive() then - unit:Explode(ExplosionPower) + unit:Explode(ExplosionPower or 100) end end end + return self end +--- Set that this carrier is an all aspect loader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierLoaderAllAspect(Length, Width) + self.carrierLoader.type="front" + self.carrierLoader.length=Length or 50 + self.carrierLoader.width=Width or 20 + return self +end + +--- Set that this carrier is a front loader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierLoaderFront(Length, Width) + self.carrierLoader.type="front" + self.carrierLoader.length=Length or 50 + self.carrierLoader.width=Width or 20 + return self +end + +--- Set that this carrier is a back loader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierLoaderBack(Length, Width) + self.carrierLoader.type="back" + self.carrierLoader.length=Length or 50 + self.carrierLoader.width=Width or 20 + return self +end + +--- Set that this carrier is a starboard (right side) loader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierLoaderStarboard(Length, Width) + self.carrierLoader.type="right" + self.carrierLoader.length=Length or 50 + self.carrierLoader.width=Width or 20 + return self +end + +--- Set that this carrier is a port (left side) loader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierLoaderPort(Length, Width) + self.carrierLoader.type="left" + self.carrierLoader.length=Length or 50 + self.carrierLoader.width=Width or 20 + return self +end + + +--- Set that this carrier is an all aspect unloader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierUnloaderAllAspect(Length, Width) + self.carrierUnloader.type="front" + self.carrierUnloader.length=Length or 50 + self.carrierUnloader.width=Width or 20 + return self +end + +--- Set that this carrier is a front unloader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierUnloaderFront(Length, Width) + self.carrierUnloader.type="front" + self.carrierUnloader.length=Length or 50 + self.carrierUnloader.width=Width or 20 + return self +end + +--- Set that this carrier is a back unloader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierUnloaderBack(Length, Width) + self.carrierUnloader.type="back" + self.carrierUnloader.length=Length or 50 + self.carrierUnloader.width=Width or 20 + return self +end + +--- Set that this carrier is a starboard (right side) unloader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierUnloaderStarboard(Length, Width) + self.carrierUnloader.type="right" + self.carrierUnloader.length=Length or 50 + self.carrierUnloader.width=Width or 20 + return self +end + +--- Set that this carrier is a port (left side) unloader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierUnloaderPort(Length, Width) + self.carrierUnloader.type="left" + self.carrierUnloader.length=Length or 50 + self.carrierUnloader.width=Width or 20 + return self +end + + --- Check if this is a FLIGHTGROUP. -- @param #OPSGROUP self -- @return #boolean If true, this is an airplane or helo group. @@ -5767,27 +5890,12 @@ function OPSGROUP:onafterUnloading(From, Event, To) Coordinate=self.cargoTransport.disembarkzone:GetRandomCoordinate() else - - -- TODO: Optimize with random Vec2 - - -- Create a zone around the carrier. - local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) - - local d={} - d.p1={x=vec2.x-l/2, y=vec2.y-w/2} --DCS#Vec2 - d.p2={x=vec2.x-l/2, y=vec2.y+w/2} --DCS#Vec2 - d.p3={x=d2.x+20, y=d2.y+20} - d.p4={x=d1.x+20, y=d1.y+20} - - for _,_p in pairs(d) do - local p=_p --#DCSVec2 - end - - local zoneCarrier=ZONE_POLYGON_BASE:New("Carrier", {d1, d2, d3, d4}) + -- Get random point in disembark zone. + local zoneCarrier=self:GetElementZoneUnload(cargo.opsgroup:_GetMyCarrierElement().name) -- Random coordinate/heading in the zone. - Coordinate=zoneCarrier:GetRandomCoordinate(50) + Coordinate=zoneCarrier:GetRandomCoordinate() end @@ -5800,8 +5908,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) end -- Trigger "Unloaded" event for current cargo transport - self.cargoTransport:Unloaded(cargo.opsgroup) - + self.cargoTransport:Unloaded(cargo.opsgroup) end @@ -5898,13 +6005,16 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated -- Respawn group. OpsGroup:_Respawn(0, Template) - if self:IsNavygroup() then - self.currentwp=1 - NAVYGROUP.AddWaypoint(self, Coordinate, nil, nil, nil, false) - elseif self:IsArmygroup() then - self.currentwp=1 - ARMYGROUP.AddWaypoint(self, Coordinate, nil, nil, nil, false) - end + -- Add current waypoint. These have been cleard on loading. + if OpsGroup:IsNavygroup() then + OpsGroup.currentwp=1 + OpsGroup.passedfinalwp=true + NAVYGROUP.AddWaypoint(OpsGroup, Coordinate, nil, nil, nil, false) + elseif OpsGroup:IsArmygroup() then + OpsGroup.currentwp=1 + OpsGroup.passedfinalwp=true + ARMYGROUP.AddWaypoint(OpsGroup, Coordinate, nil, nil, nil, false) + end else @@ -5919,11 +6029,10 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated end -- Trigger "Disembarked" event. - OpsGroup:Disembarked(OpsGroup.carrierGroup, OpsGroup.carrier) + OpsGroup:Disembarked(OpsGroup:_GetMyCarrierGroup(), OpsGroup:_GetMyCarrierElement()) - -- No carrier any more. - OpsGroup.carrier=nil - OpsGroup.carrierGroup=nil + -- Remove my carrier. + OpsGroup:_RemoveMyCarrier() end @@ -5966,11 +6075,8 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport +-- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport The cargo transport assignment. function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) - - -- Set cargo status. - CargoTransport.status=OPSGROUP.TransportStatus.DELIVERED -- Check if this was the current transport. if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then @@ -7995,44 +8101,173 @@ end -- @param #OPSGROUP self -- @param #string UnitName Name of unit. -- @return Core.Zone#ZONE_POLYGON Bounding box polygon zone. -function OPSGROUP:GetElementBoundingBox(UnitName) +function OPSGROUP:GetElementZoneBoundingBox(UnitName) local element=self:GetElementByName(UnitName) if element and element.status~=OPSGROUP.ElementStatus.DEAD then + + -- Create a new zone if necessary. + element.zoneBoundingbox=element.zoneBoundingbox or ZONE_POLYGON_BASE:New(element.name.." Zone Bounding Box", {}) local l=element.length local w=element.width - local heading=element.unit:GetHeading() + local X=self:GetOrientationX(element.name) + local heading=math.deg(math.atan2(X.z, X.x)) env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) - - local vec2=self:GetVec2(element.name) - - -- Set of + + -- Set of edges facing "North" at the origin of the map. local b={} - b[1]={y=l/2, x=-w/2} --DCS#Vec2 - b[2]={y=l/2, x=w/2} --DCS#Vec2 - b[3]={y=-l/2, x=w/2} --DCS#Vec2 - b[4]={y=-l/2, x=-w/2} --DCS#Vec2 + b[1]={x=l/2, y=-w/2} --DCS#Vec2 + b[2]={x=l/2, y=w/2} --DCS#Vec2 + b[3]={x=-l/2, y=w/2} --DCS#Vec2 + b[4]={x=-l/2, y=-w/2} --DCS#Vec2 + -- Rotate box to match current heading of the unit. for i,p in pairs(b) do b[i]=UTILS.Vec2Rotate2D(p, heading) end - + + -- Translate the zone to the positon of the unit. + local vec2=self:GetVec2(element.name) local d=UTILS.Vec2Norm(vec2) local h=UTILS.Vec2Hdg(vec2) for i,p in pairs(b) do - --b[i]=UTILS.Vec2Translate(p, d, h) + b[i]=UTILS.Vec2Translate(p, d, h) end + + -- Update existing zone. + element.zoneBoundingbox:UpdateFromVec2(b) - return ZONE_POLYGON_BASE:New(element.name, b) + return element.zoneBoundingbox end return nil end +--- Get the loading zone of the element. +-- @param #OPSGROUP self +-- @param #string UnitName Name of unit. +-- @return Core.Zone#ZONE_POLYGON Bounding box polygon zone. +function OPSGROUP:GetElementZoneLoad(UnitName) + + local element=self:GetElementByName(UnitName) + + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + + element.zoneLoad=element.zoneLoad or ZONE_POLYGON_BASE:New(element.name.." Zone Load", {}) + + self:_GetElementZoneLoader(element, element.zoneLoad, self.carrierLoader) + + return element.zoneLoad + end + + return nil +end + +--- Get the unloading zone of the element. +-- @param #OPSGROUP self +-- @param #string UnitName Name of unit. +-- @return Core.Zone#ZONE_POLYGON Bounding box polygon zone. +function OPSGROUP:GetElementZoneUnload(UnitName) + + local element=self:GetElementByName(UnitName) + + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + + element.zoneUnload=element.zoneUnload or ZONE_POLYGON_BASE:New(element.name.." Zone Unload", {}) + + self:_GetElementZoneLoader(element, element.zoneUnload, self.carrierUnloader) + + return element.zoneUnload + end + + return nil +end + +--- Get/update the (un-)loading zone of the element. +-- @param #OPSGROUP self +-- @param #OPSGROUP.Element Element Element. +-- @param Core.Zone#ZONE_POLYGON Zone The zone. +-- @param #OPSGROUP.CarrierLoader Loader Loader parameters. +-- @return Core.Zone#ZONE_POLYGON Bounding box polygon zone. +function OPSGROUP:_GetElementZoneLoader(Element, Zone, Loader) + + if Element.status~=OPSGROUP.ElementStatus.DEAD then + + local l=Element.length + local w=Element.width + + -- Orientation 3D vector where the "nose" is pointing. + local X=self:GetOrientationX(Element.name) + + -- Heading in deg. + local heading=math.deg(math.atan2(X.z, X.x)) + + env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) + + -- Bounding box at the origin of the map facing "North". + local b={} + + -- Create polygon rectangles. + if Loader.type:lower()=="front" then + table.insert(b, {x= l/2, y=-Loader.width/2}) -- left, low + table.insert(b, {x= l/2+Loader.length, y=-Loader.width/2}) -- left, up + table.insert(b, {x= l/2+Loader.length, y= Loader.width/2}) -- right, up + table.insert(b, {x= l/2, y= Loader.width/2}) -- right, low + elseif Loader.type:lower()=="back" then + table.insert(b, {x=-l/2, y=-Loader.width/2}) -- left, low + table.insert(b, {x=-l/2-Loader.length, y=-Loader.width/2}) -- left, up + table.insert(b, {x=-l/2-Loader.length, y= Loader.width/2}) -- right, up + table.insert(b, {x=-l/2, y= Loader.width/2}) -- right, low + elseif Loader.type:lower()=="left" then + table.insert(b, {x= Loader.length/2, y= -w/2}) -- right, up + table.insert(b, {x= Loader.length/2, y= -w/2-Loader.width}) -- left, up + table.insert(b, {x=-Loader.length/2, y= -w/2-Loader.width}) -- left, down + table.insert(b, {x=-Loader.length/2, y= -w/2}) -- right, down + elseif Loader.type:lower()=="right" then + table.insert(b, {x= Loader.length/2, y= w/2}) -- right, up + table.insert(b, {x= Loader.length/2, y= w/2+Loader.width}) -- left, up + table.insert(b, {x=-Loader.length/2, y= w/2+Loader.width}) -- left, down + table.insert(b, {x=-Loader.length/2, y= w/2}) -- right, down + else + -- All aspect. Rectangle around the unit but need to cut out the area of the unit itself. + b[1]={x= l/2, y=-w/2} --DCS#Vec2 + b[2]={x= l/2, y= w/2} --DCS#Vec2 + b[3]={x=-l/2, y= w/2} --DCS#Vec2 + b[4]={x=-l/2, y=-w/2} --DCS#Vec2 + table.insert(b, {x=b[1].x+Loader.length, y=b[1].y-Loader.width}) + table.insert(b, {x=b[2].x+Loader.length, y=b[2].y+Loader.width}) + table.insert(b, {x=b[3].x-Loader.length, y=b[3].y+Loader.width}) + table.insert(b, {x=b[4].x-Loader.length, y=b[4].y-Loader.width}) + end + + -- Rotate edges to match the current heading of the unit. + for i,p in pairs(b) do + b[i]=UTILS.Vec2Rotate2D(p, heading) + end + + -- Translate box to the current position of the unit. + local vec2=self:GetVec2(Element.name) + local d=UTILS.Vec2Norm(vec2) + local h=UTILS.Vec2Hdg(vec2) + + for i,p in pairs(b) do + b[i]=UTILS.Vec2Translate(p, d, h) + end + + -- Update existing zone. + Zone:UpdateFromVec2(b) + + return Zone + end + + return nil +end + + --- Get the first element of a group, which is alive. -- @param #OPSGROUP self -- @return #OPSGROUP.Element The element or `#nil` if no element is alive any more. @@ -8399,9 +8634,7 @@ function OPSGROUP:_AddElementByName(unitname) end - -- Max cargo weight - --element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) - + -- Max cargo weight: unit:SetCargoBayWeightLimit() element.weightMaxCargo=unit.__.CargoBayWeightLimit diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index ef21c495a..2c03b672f 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -97,7 +97,7 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.3" +OPSTRANSPORT.version="0.0.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index bc5a1a352..25f832ee5 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1127,13 +1127,13 @@ function UTILS.Vec2Rotate2D(a, angle) local phi=math.rad(angle) - local x=a.y - local y=a.x + local x=a.x + local y=a.y - local Z=x*math.cos(phi)-y*math.sin(phi) - local X=x*math.sin(phi)+y*math.cos(phi) + local X=x*math.cos(phi)-y*math.sin(phi) + local Y=x*math.sin(phi)+y*math.cos(phi) - local A={x=X, y=Z} + local A={x=X, y=Y} return A end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 67f892052..338417123 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1394,18 +1394,6 @@ do -- Cargo return ItemCount end --- --- Get Cargo Bay Free Volume in m3. --- -- @param #POSITIONABLE self --- -- @return #number CargoBayFreeVolume --- function POSITIONABLE:GetCargoBayFreeVolume() --- local CargoVolume = 0 --- for CargoName, Cargo in pairs( self.__.Cargo ) do --- CargoVolume = CargoVolume + Cargo:GetVolume() --- end --- return self.__.CargoBayVolumeLimit - CargoVolume --- end --- - --- Get Cargo Bay Free Weight in kg. -- @param #POSITIONABLE self -- @return #number CargoBayFreeWeight @@ -1423,13 +1411,6 @@ do -- Cargo return self.__.CargoBayWeightLimit - CargoWeight end --- --- Get Cargo Bay Volume Limit in m3. --- -- @param #POSITIONABLE self --- -- @param #number VolumeLimit --- function POSITIONABLE:SetCargoBayVolumeLimit( VolumeLimit ) --- self.__.CargoBayVolumeLimit = VolumeLimit --- end - --- Set Cargo Bay Weight Limit in kg. -- @param #POSITIONABLE self -- @param #number WeightLimit @@ -1458,11 +1439,13 @@ do -- Cargo self:F({Desc=Desc}) local Weights = { - ["Type_071"] = 245000, - ["LHA_Tarawa"] = 500000, - ["Ropucha-class"] = 150000, - ["Dry-cargo ship-1"] = 70000, - ["Dry-cargo ship-2"] = 70000, + ["Type_071"] = 245000, + ["LHA_Tarawa"] = 500000, + ["Ropucha-class"] = 150000, + ["Dry-cargo ship-1"] = 70000, + ["Dry-cargo ship-2"] = 70000, + ["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). + ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. } self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 ) @@ -1507,9 +1490,11 @@ do -- Cargo } local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 95 + self.__.CargoBayWeightLimit = CargoBayWeightLimit end end + self:F({CargoBayWeightLimit = self.__.CargoBayWeightLimit}) end end --- Cargo From ca44e2762bc4c6afcc0f847ba574cd869ab37884 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 7 Apr 2021 20:53:15 +0200 Subject: [PATCH 031/141] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index a1d03a63a..6ad1cf5d0 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -445,7 +445,9 @@ OPSGROUP.version="0.7.2" -- TODO: Invisible/immortal. -- TODO: F10 menu. -- TODO: Add pseudo function. --- TODO: Options EPLRS, Afterburner restrict etc. +-- TODO: Options EPLRS +-- TODO: Afterburner restrict +-- TODO: What more options? -- TODO: Damage? -- TODO: Shot events? -- TODO: Marks to add waypoints/tasks on-the-fly. @@ -5029,6 +5031,7 @@ end --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The troop transport assignment. +-- @return #OPSGROUP self function OPSGROUP:AddOpsTransport(OpsTransport) -- Add this group as carrier for the transport. @@ -5039,6 +5042,8 @@ function OPSGROUP:AddOpsTransport(OpsTransport) table.insert(self.cargoqueue, OpsTransport) self:I(self.lid.."FF adding transport to carrier, #self.cargoqueue="..#self.cargoqueue) + + return self end --- Delete a cargo transport assignment from the cargo queue @@ -7190,7 +7195,7 @@ function OPSGROUP:SwitchROT(rot) self.group:OptionROT(self.option.ROT) - self:T(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) + self:I(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) end From 2693e76483a40deebc7b9df90a9aed2b7c1623cf Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 15 Apr 2021 21:13:35 +0200 Subject: [PATCH 032/141] Update Airboss.lua --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 041843d56..2109475a7 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -6131,7 +6131,7 @@ function AIRBOSS:_ScanCarrierZone() local putintomarshal=false -- Get flight group. - local flight=_DATABASE:GetFlightGroup(groupname) + local flight=_DATABASE:GetOpsGroup(groupname) if flight and flight:IsInbound() and flight.destbase:GetName()==self.carrier:GetName() then if flight.ishelo then From 990f748e42863cf749d4cb1851eb42718d1ff31b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 27 Apr 2021 09:19:33 +0200 Subject: [PATCH 033/141] Update Database.lua --- Moose Development/Moose/Core/Database.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 35077c05b..ef3547338 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1544,7 +1544,7 @@ function DATABASE:_RegisterTemplates() if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group - self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID) + self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID) else From 421005a3b6ed4a4eeebbfb1a2605e57bc6f019e8 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 30 Apr 2021 11:53:57 +0200 Subject: [PATCH 034/141] event backfixed --- Moose Development/Moose/Core/Event.lua | 6 ++++-- Moose Development/Moose/Ops/FlightGroup.lua | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 07ddcfdf1..9b5251f46 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1114,9 +1114,10 @@ function EVENT:onEvent( Event ) Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() end + env.info("FF I am here first") if Event.TgtObjectCategory == Object.Category.STATIC then + env.info("FF I am here") BASE:T({Event = Event}) - --[[ Event.TgtDCSUnit = Event.target Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() Event.TgtUnitName = Event.TgtDCSUnitName @@ -1124,8 +1125,8 @@ function EVENT:onEvent( Event ) Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() Event.TgtCategory = Event.TgtDCSUnit:getDesc().category Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() - --]] -- Same as for Event Initiator above 2.7 issue + --[[ Event.TgtDCSUnit = Event.target local ID=Event.initiator.id_ Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID)) @@ -1134,6 +1135,7 @@ function EVENT:onEvent( Event ) Event.TgtCoalition = Event.IniCoalition Event.TgtCategory = Event.IniCategory Event.TgtTypeName = "Ejected Pilot" + ]] end if Event.TgtObjectCategory == Object.Category.SCENERY then diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ff0f49aa4..c4b916448 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -56,6 +56,7 @@ -- @field #number callsignName Callsign name. -- @field #number callsignNumber Callsign number. -- @field #boolean despawnAfterLanding If true, group is despawned after landed at an airbase. +-- @field #number RTBRecallCount Number that counts RTB calls. -- -- @extends Ops.OpsGroup#OPSGROUP @@ -145,6 +146,7 @@ FLIGHTGROUP = { Twaiting = nil, menu = nil, isHelo = nil, + RTBRecallCount = 0, } From 1ce46c016c8ee9c86001c020f59e618a7bd7da7e Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 10 May 2021 00:14:07 +0200 Subject: [PATCH 035/141] Cargo --- Moose Development/Moose/Ops/OpsGroup.lua | 41 +++++++++-- Moose Development/Moose/Ops/OpsTransport.lua | 72 ++++++++++++++++++-- 2 files changed, 100 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 113bfff23..6f8412eb5 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -435,13 +435,14 @@ OPSGROUP.CargoStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="0.7.2" +OPSGROUP.version="0.7.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: AI on/off. +-- TODO: Emission on/off. -- TODO: Invisible/immortal. -- TODO: F10 menu. -- TODO: Add pseudo function. @@ -1112,7 +1113,7 @@ function OPSGROUP:GetVelocity(UnitName) return nil end ---- Get current heading of the group. +--- Get current heading of the group or (optionally) of a specific unit of the group. -- @param #OPSGROUP self -- @param #string UnitName (Optional) Get heading of a specific unit of the group. Default is from the first existing unit in the group. -- @return #number Current heading of the group in degrees. @@ -1181,7 +1182,7 @@ function OPSGROUP:GetOrientation(UnitName) return nil end ---- Get current orientation of the first unit in the group. +--- Get current "X" orientation of the first unit in the group. -- @param #OPSGROUP self -- @param #string UnitName (Optional) Get orientation of a specific unit of the group. Default is the first existing unit of the group. -- @return DCS#Vec3 Orientation X parallel to where the "nose" is pointing. @@ -1736,6 +1737,13 @@ function OPSGROUP:IsNotCarrier() return self.carrierStatus==OPSGROUP.CarrierStatus.NOTCARRIER end +--- Check if the group is a carrier. +-- @param #OPSGROUP self +-- @return #boolean If true, group is a carrier. +function OPSGROUP:IsCarrier() + return not self:IsNotCarrier() +end + --- Check if the group is picking up cargo. -- @param #OPSGROUP self -- @return #boolean If true, group is picking up. @@ -1765,6 +1773,13 @@ function OPSGROUP:IsUnloading() end +--- Check if the group is assigned as cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is cargo. +function OPSGROUP:IsCargo() + return not self:IsNotCargo() +end + --- Check if the group is **not** cargo. -- @param #OPSGROUP self -- @return #boolean If true, group is *not* cargo. @@ -4548,6 +4563,12 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end end end + + if self:IsCarrier() then + if self.cargoTransport then + self.cargoTransport:CarrierGroupDead() + end + end end @@ -4693,7 +4714,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterDead(From, Event, To) - self:T(self.lid..string.format("Group dead at t=%.3f", timer.getTime())) + self:I(self.lid..string.format("Group dead at t=%.3f", timer.getTime())) -- Is dead now. self.isDead=true @@ -4708,11 +4729,17 @@ function OPSGROUP:onafterDead(From, Event, To) mission:GroupDead(self) end + + -- Inform all transports in the queue that this carrier group is dead now. + for i,_transport in pairs(self.cargoqueue) do + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + transport:DeadCarrierGroup(self) + end -- Delete waypoints so they are re-initialized at the next spawn. self.waypoints=nil self.groupinitialized=false - + -- Stop in a sec. self:__Stop(-5) end @@ -5037,16 +5064,16 @@ function OPSGROUP:AddOpsTransport(OpsTransport) -- Add this group as carrier for the transport. OpsTransport:_AddCarrier(self) - --Add to cargo queue table.insert(self.cargoqueue, OpsTransport) + -- Debug message. self:I(self.lid.."FF adding transport to carrier, #self.cargoqueue="..#self.cargoqueue) return self end ---- Delete a cargo transport assignment from the cargo queue +--- Delete a cargo transport assignment from the cargo queue. -- @param #OPSGROUP self -- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport Cargo transport do be deleted. -- @return #OPSGROUP self diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 2c03b672f..92a9ff9ff 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -3,6 +3,9 @@ -- ## Main Features: -- -- * Transport troops from A to B. +-- * Supports ground, naval and airborne (airplanes and helicopters) units as carriers +-- * Use combined forces (ground, naval, air) to transport the troops. +-- * Additional FSM events to hook into and customize your mission design. -- -- === -- @@ -55,7 +58,8 @@ -- -- # The OPSTRANSPORT Concept -- --- Transport OPSGROUPS using carriers such as APCs, helicopters or airplanes. +-- This class simulates troop transport using carriers such as APCs, ships helicopters or airplanes. The carriers and transported groups need to be OPSGROUPS (see ARMYGROUP, NAVYGROUP and FLIGHTGROUP classed). +-- -- -- @field #OPSTRANSPORT OPSTRANSPORT = { @@ -97,14 +101,14 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.5" +OPSTRANSPORT.version="0.0.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- DONE: Add start conditions. --- TODO: Check carrier(s) dead. +-- DONE: Check carrier(s) dead. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -164,6 +168,9 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self:AddTransition("*", "Loaded", "*") self:AddTransition("*", "Unloaded", "*") + self:AddTransition("*", "DeadCarrierUnit", "*") + self:AddTransition("*", "DeadCarrierGroup", "*") + self:AddTransition("*", "DeadCarrierAll", "*") -- Call status update. self:__Status(-1) @@ -313,16 +320,19 @@ end -- @return #OPSTRANSPORT self function OPSTRANSPORT:_AddCarrier(CarrierGroup) + -- Check that this is not already an assigned carrier. if not self:IsCarrier(CarrierGroup) then -- Increase carrier count. self.Ncarrier=self.Ncarrier+1 - -- Set trans + -- Set transport status to SCHEDULED. self:SetCarrierTransportStatus(CarrierGroup, OPSTRANSPORT.Status.SCHEDULED) + -- Call scheduled event. self:Scheduled() + -- Add carrier to table. table.insert(self.carriers, CarrierGroup) end @@ -330,6 +340,32 @@ function OPSTRANSPORT:_AddCarrier(CarrierGroup) return self end +--- Remove group from the current carrier list/table. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:_DelCarrier(CarrierGroup) + + if self:IsCarrier(CarrierGroup) then + + for i,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + if carrier.groupname==CarrierGroup.groupname then + self:I(self.lid..string.format("Removing carrier %s", CarrierGroup.groupname)) + table.remove(self.carriers, i) + end + end + + if #self.carriers==0 then + self:DeadCarrierAll() + end + + end + + return self +end + + --- Set transport start and stop time. -- @param #OPSTRANSPORT self -- @param #string ClockStart Time the transport is started, e.g. "05:00" for 5 am. If specified as a #number, it will be relative (in seconds) to the current mission time. Default is 5 seconds after mission was added. @@ -560,7 +596,7 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) local fsmstate=self:GetState() -- Info text. - local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, self.Ncarrier) + local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, #self.carriers,self.Ncarrier) text=text..string.format("\nCargos:") for _,_cargo in pairs(self.cargos) do @@ -648,6 +684,30 @@ function OPSTRANSPORT:onafterUnloaded(From, Event, To, OpsGroup) self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroup:GetName())) end +--- On after "DeadCarrierGroup" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup Carrier OPSGROUP that is dead. +function OPSTRANSPORT:onafterDeadCarrierGroup(From, Event, To, OpsGroup) + self:I(self.lid..string.format("Carrier OPSGROUP %s dead!", OpsGroup:GetName())) + -- Remove group from carrier list/table. + self:_DelCarrier(OpsGroup) +end + +--- On after "DeadCarrierAll" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterDeadCarrierAll(From, Event, To) + self:I(self.lid..string.format("ALL Carrier OPSGROUPs are dead! Setting stage to PLANNED if not all cargo was delivered.")) + if not self:IsDelivered() then + self:Planned() + end +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -776,7 +836,7 @@ function OPSTRANSPORT:_CreateCargoGroupData(group) return cargo end ---- Get an OPSGROUIP +--- Get an OPSGROUP from a given OPSGROUP or GROUP object. If the object is a GROUUP, an OPSGROUP is created automatically. -- @param #OPSTRANSPORT self -- @param Core.Base#BASE Object The object, which can be a GROUP or OPSGROUP. -- @return Ops.OpsGroup#OPSGROUP Ops Group. From e4aa23ce3d232294e8630493423ac72242c3c14e Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 15 May 2021 00:29:20 +0200 Subject: [PATCH 036/141] Update Point.lua --- Moose Development/Moose/Core/Point.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 7117ba484..94683b74f 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1607,8 +1607,13 @@ do -- COORDINATE roadtype="railroads" end local x,y = land.getClosestPointOnRoads(roadtype, self.x, self.z) - local vec2={ x = x, y = y } - return COORDINATE:NewFromVec2(vec2) + if x and y then + local vec2={ x = x, y = y } + local coord=COORDINATE:NewFromVec2(vec2) + return coord + else + return nil + end end From a2808163a7487eda99a2700ba103f81f9105dc0e Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 20 May 2021 23:23:12 +0200 Subject: [PATCH 037/141] Boom and Probe --- Moose Development/Moose/Ops/AirWing.lua | 47 +++++++++++++++---------- Moose Development/Moose/Ops/Auftrag.lua | 2 +- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 202eb631d..60d412343 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -148,12 +148,13 @@ AIRWING = { -- @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 #number noccupied Number of flights on this patrol point. -- @field Wrapper.Marker#MARKER marker F10 marker. --- AIRWING class version. -- @field #string version -AIRWING.version="0.5.1" +AIRWING.version="0.6.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -739,8 +740,9 @@ end -- @param #number Heading Heading in degrees. Default random (0, 360] degrees. -- @param #number LegLength Length of race-track orbit in NM. Default 15 NM. -- @param #number Speed Orbit speed in knots. Default 350 knots. +-- @param #number RefuelSystem Refueling system: 0=Boom, 1=Probe. Default nil=any. -- @return #AIRWING.PatrolData Patrol point table. -function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegLength) +function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) local patrolpoint={} --#AIRWING.PatrolData patrolpoint.type=Type or "Unknown" @@ -750,6 +752,7 @@ function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegL patrolpoint.altitude=Altitude or math.random(10,20)*1000 patrolpoint.speed=Speed or 350 patrolpoint.noccupied=0 + patrolpoint.refuelsystem=RefuelSystem if self.markpoints then patrolpoint.marker=MARKER:New(Coordinate, "New Patrol Point"):ToAll() @@ -783,10 +786,11 @@ end -- @param #number Speed Orbit speed in knots. -- @param #number Heading Heading in degrees. -- @param #number LegLength Length of race-track orbit in NM. +-- @param #number RefuelSystem Set refueling system of tanker: 0=boom, 1=probe. Default any (=nil). -- @return #AIRWING self -function AIRWING:AddPatrolPointTANKER(Coordinate, Altitude, Speed, Heading, LegLength) +function AIRWING:AddPatrolPointTANKER(Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) - local patrolpoint=self:NewPatrolPoint("Tanker", Coordinate, Altitude, Speed, Heading, LegLength) + local patrolpoint=self:NewPatrolPoint("Tanker", Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) table.insert(self.pointsTANKER, patrolpoint) @@ -919,11 +923,12 @@ function AIRWING:onafterStatus(From, Event, To) end ---- Get patrol data +--- Get patrol data. -- @param #AIRWING self -- @param #table PatrolPoints Patrol data points. --- @return #AIRWING.PatrolData -function AIRWING:_GetPatrolData(PatrolPoints) +-- @param #number RefuelSystem If provided, only return points with the specific refueling system. +-- @return #AIRWING.PatrolData Patrol point data table. +function AIRWING:_GetPatrolData(PatrolPoints, RefuelSystem) -- Sort wrt lowest number of flights on this point. local function sort(a,b) @@ -934,14 +939,18 @@ function AIRWING:_GetPatrolData(PatrolPoints) -- Sort data wrt number of flights at that point. table.sort(PatrolPoints, sort) - return PatrolPoints[1] - - else - return self:NewPatrolPoint() - + for _,_patrolpoint in pairs(PatrolPoints) do + local patrolpoint=_patrolpoint --#AIRWING.PatrolData + if (RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem) or RefuelSystem==nil or patrolpoint.refuelsystem==nil then + return patrolpoint + end + end + end + -- Return a new point. + return self:NewPatrolPoint() end --- Check how many CAP missions are assigned and add number of missing missions. @@ -980,28 +989,29 @@ function AIRWING:CheckTANKER() local Nboom=0 local Nprob=0 - -- Count tanker mission. + -- Count tanker missions. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG if mission:IsNotOver() and mission.type==AUFTRAG.Type.TANKER then - if mission.refuelSystem==0 then + if mission.refuelSystem==Unit.RefuelingSystem.BOOM_AND_RECEPTACLE then Nboom=Nboom+1 - elseif mission.refuelSystem==1 then + elseif mission.refuelSystem==Unit.RefuelingSystem.PROBE_AND_DROGUE then Nprob=Nprob+1 end end end - + + -- Check missing boom tankers. for i=1,self.nflightsTANKERboom-Nboom do local patrol=self:_GetPatrolData(self.pointsTANKER) local altitude=patrol.altitude+1000*patrol.noccupied - local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, 1) + local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, Unit.RefuelingSystem.BOOM_AND_RECEPTACLE) mission.patroldata=patrol @@ -1013,13 +1023,14 @@ function AIRWING:CheckTANKER() end + -- Check missing probe tankers. for i=1,self.nflightsTANKERprobe-Nprob do local patrol=self:_GetPatrolData(self.pointsTANKER) local altitude=patrol.altitude+1000*patrol.noccupied - local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, 0) + local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, Unit.RefuelingSystem.PROBE_AND_DROGUE) mission.patroldata=patrol diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index f285e461d..7a952cba8 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -688,7 +688,7 @@ end -- @param #number Speed Orbit speed in knots. 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 10 NM. --- @param #number RefuelSystem Refueling system (1=boom, 0=probe). This info is *only* for AIRWINGs so they launch the right tanker type. +-- @param #number RefuelSystem Refueling system (0=boom, 1=probe). This info is *only* for AIRWINGs so they launch the right tanker type. -- @return #AUFTRAG self function AUFTRAG:NewTANKER(Coordinate, Altitude, Speed, Heading, Leg, RefuelSystem) From 2544ec5587c4a5d8cf06b4a571c0eb6d0d94c756 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 30 May 2021 01:44:36 +0200 Subject: [PATCH 038/141] Update SRS.lua --- Moose Development/Moose/Sound/SRS.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 78cbaeaf2..08eafd52f 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -463,7 +463,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp else -- Add gender. if gender and gender~="female" then - command=command..string.format(" --gender=%s", tostring(gender)) + command=command..string.format(" -g %s", tostring(gender)) end -- Add culture. if culture and culture~="en-GB" then From 231c5bfea77852d436fb5b221bc5f1463d3e148b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 22 Jun 2021 12:18:25 +0200 Subject: [PATCH 039/141] Ops --- Moose Development/Moose/Core/Set.lua | 31 ++++++++++++++++++++ Moose Development/Moose/Ops/FlightGroup.lua | 8 ++++- Moose Development/Moose/Ops/NavyGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 19 +++++++++++- Moose Development/Moose/Ops/OpsTransport.lua | 20 +++++++++++++ 5 files changed, 77 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index fbcf05e45..7dfdfca49 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1400,6 +1400,22 @@ do -- SET_GROUP return self end + --- Activate late activated groups. + -- @param #SET_GROUP self + -- @param #number Delay Delay in seconds. + -- @return #SET_GROUP self + function SET_GROUP:Activate(Delay) + local Set = self:GetSet() + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + local group=GroupData --Wrapper.Group#GROUP + if group and group:IsAlive()==false then + group:Activate(Delay) + end + end + return self + end + + --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -6411,6 +6427,21 @@ do -- SET_OPSGROUP return self end + + --- Activate late activated groups in the set. + -- @param #SET_OPSGROUP self + -- @param #number Delay Delay in seconds. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:Activate(Delay) + local Set = self:GetSet() + for GroupID, GroupData in pairs(Set) do + local group=GroupData --Ops.OpsGroup#OPSGROUP + if group and group:IsAlive()==false then + group:Activate(Delay) + end + end + return self + end --- Handles the OnDead or OnCrash event for alive groups set. -- Note: The GROUP object in the SET_OPSGROUP collection will only be removed if the last unit is destroyed of the GROUP. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index c4b916448..bcf8227e7 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -417,6 +417,9 @@ end -- @param Wrapper.Airbase#AIRBASE HomeAirbase The home airbase. -- @return #FLIGHTGROUP self function FLIGHTGROUP:SetHomebase(HomeAirbase) + if type(HomeAirbase)=="string" then + HomeAirbase=AIRBASE:FindByName(HomeAirbase) + end self.homebase=HomeAirbase return self end @@ -426,6 +429,9 @@ end -- @param Wrapper.Airbase#AIRBASE DestinationAirbase The destination airbase. -- @return #FLIGHTGROUP self function FLIGHTGROUP:SetDestinationbase(DestinationAirbase) + if type(DestinationAirbase)=="string" then + DestinationAirbase=AIRBASE:FindByName(DestinationAirbase) + end self.destbase=DestinationAirbase return self end @@ -730,7 +736,7 @@ function FLIGHTGROUP:StartUncontrolled(delay) end self:I(self.lid.."Starting uncontrolled group") self.group:StartUncontrolled(_delay) - self.isUncontrolled=true + self.isUncontrolled=false else self:E(self.lid.."ERROR: Could not start uncontrolled group as it is NOT alive!") end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 4f233faac..fb836d489 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1005,7 +1005,7 @@ function NAVYGROUP:onafterDive(From, Event, To, Depth, Speed) Depth=Depth or 50 - self:T(self.lid..string.format("Diving to %d meters", Depth)) + self:I(self.lid..string.format("Diving to %d meters", Depth)) self.depth=Depth diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 6f8412eb5..2792b93f5 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -5447,6 +5447,8 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Add waypoint. if self.isFlightgroup then + + env.info("FF pickup is flightgroup") if airbasePickup then @@ -5461,10 +5463,13 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Activate uncontrolled group. if self:IsParking() then + env.info("FF pickup start uncontrolled while parking at current airbase") self:StartUncontrolled() end else + + env.info("FF pickup land at airbase") -- Order group to land at an airbase. self:LandAtAirbase(airbasePickup) @@ -5477,6 +5482,14 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Helo can also land in a zone (NOTE: currently VTOL cannot!) --- + env.info("FF pickup helo addwaypoint") + + -- Activate uncontrolled group. + if self:IsParking() then + env.info("FF pickup start uncontrolled while parking airbase") + self:StartUncontrolled() + end + -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) @@ -5485,6 +5498,7 @@ function OPSGROUP:onafterPickup(From, Event, To) else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") end + elseif self.isNavygroup then -- Navy Group @@ -5656,8 +5670,11 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) CargoGroup:Despawn(0, true) end - -- Trigger embarked event. + -- Trigger embarked event for cargo group. CargoGroup:Embarked(self, Carrier) + + -- Trigger "Loaded" event for current cargo transport. + self.cargoTransport:Loaded(CargoGroup, Carrier) else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 92a9ff9ff..4eca27af6 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -415,6 +415,15 @@ function OPSTRANSPORT:SetPriority(Prio, Importance, Urgent) return self end +--- Set verbosity. +-- @param #OPSTRANSPORT self +-- @param #number Verbosity Be more verbose. Default 0 +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetVerbosity(Verbosity) + self.verbose=Verbosity or 0 + return self +end + --- Add start condition. -- @param #OPSTRANSPORT self -- @param #function ConditionFunction Function that needs to be true before the transport can be started. Must return a #boolean. @@ -674,6 +683,17 @@ function OPSTRANSPORT:onafterDelivered(From, Event, To) end +--- On after "Loaded" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup OPSGROUP that was loaded into a carrier. +-- @param Ops.OpsGroup#OPSGROUP.Element Carrier Carrier element. +function OPSTRANSPORT:onafterLoaded(From, Event, To, OpsGroup, Carrier) + self:I(self.lid..string.format("Loaded OPSGROUP %s into carrier %s", OpsGroup:GetName(), tostring(Carrier.name))) +end + --- On after "Unloaded" event. -- @param #OPSTRANSPORT self -- @param #string From From state. From 978be4e38386138af72e2ce01b55d132b8f500c0 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 23 Jun 2021 17:20:51 +0200 Subject: [PATCH 040/141] OPS Transport --- Moose Development/Moose/Ops/FlightGroup.lua | 7 +- Moose Development/Moose/Ops/OpsGroup.lua | 41 +++++-- Moose Development/Moose/Ops/OpsTransport.lua | 101 ++++++++++++++++-- .../Moose/Wrapper/Positionable.lua | 5 +- 4 files changed, 131 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index bcf8227e7..87b98c88b 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2252,9 +2252,14 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) allowed=false Tsuspend=-20 local groupspeed = self.group:GetVelocityMPS() - if groupspeed <= 1 then self.RTBRecallCount = self.RTBRecallCount+1 end + if groupspeed<=1 and not self:IsParking() then + self.RTBRecallCount = self.RTBRecallCount+1 + end if self.RTBRecallCount > 6 then + self:I(self.lid..string.format("WARNING: Group is not moving and was called RTB %d times. Assuming a problem and despawning!", self.RTBRecallCount)) + self.RTBRecallCount=0 self:Despawn(5) + return end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 2792b93f5..4c29c2338 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1104,6 +1104,9 @@ function OPSGROUP:GetVelocity(UnitName) local vel=UTILS.VecNorm(velvec3) return vel + + else + self:E(self.lid.."WARNING: Unit does not exist. Cannot get velocity!") end else @@ -1751,9 +1754,9 @@ function OPSGROUP:IsPickingup() return self.carrierStatus==OPSGROUP.CarrierStatus.PICKUP end ---- Check if the group is picking up cargo. +--- Check if the group is loading cargo. -- @param #OPSGROUP self --- @return #boolean If true, group is picking up. +-- @return #boolean If true, group is loading. function OPSGROUP:IsLoading() return self.carrierStatus==OPSGROUP.CarrierStatus.LOADING end @@ -1803,14 +1806,20 @@ end --- Check if the group is currently loaded into a carrier. -- @param #OPSGROUP self --- @param #string CarrierGroupName (Optional) Additionally check if group is loaded into this particular carrier group. +-- @param #string CarrierGroupName (Optional) Additionally check if group is loaded into a particular carrier group(s). -- @return #boolean If true, group is loaded. function OPSGROUP:IsLoaded(CarrierGroupName) if CarrierGroupName then - local carrierGroup=self:_GetMyCarrierGroup() - if carrierGroup and carrierGroup.groupname~=CarrierGroupName then - return false + if type(CarrierGroupName)~="table" then + CarrierGroupName={CarrierGroupName} end + for _,CarrierName in pairs(CarrierGroupName) do + local carrierGroup=self:_GetMyCarrierGroup() + if carrierGroup and carrierGroup.groupname==CarrierName then + return true + end + end + return false end return self.cargoStatus==OPSGROUP.CargoStatus.LOADED end @@ -4881,16 +4890,23 @@ function OPSGROUP:_CheckCargoTransport() end end + + self:I(self.lid.."gotcargo="..tostring(gotcargo)) + self:I(self.lid.."boarding="..tostring(boarding)) + self:I(self.lid.."required="..tostring(self.cargoTransport:_CheckRequiredCargos())) -- Boarding finished ==> Transport cargo. - if gotcargo and not boarding then + if gotcargo and self.cargoTransport:_CheckRequiredCargos() and not boarding then self:I(self.lid.."Boarding finished ==> Loaded") self:Loaded() + else + -- No cargo and no one is boarding ==> check again if we can make anyone board. + self:Loading() end -- No cargo and no one is boarding ==> check again if we can make anyone board. if not gotcargo and not boarding then - self:Loading() + --self:Loading() end elseif self:IsTransporting() then @@ -5671,10 +5687,10 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) end -- Trigger embarked event for cargo group. - CargoGroup:Embarked(self, Carrier) + CargoGroup:Embarked(self, carrier) -- Trigger "Loaded" event for current cargo transport. - self.cargoTransport:Loaded(CargoGroup, Carrier) + self.cargoTransport:Loaded(CargoGroup, carrier) else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") @@ -5904,6 +5920,9 @@ function OPSGROUP:onafterUnloading(From, Event, To) -- Delivered to another carrier group. --- + -- Debug info. + self:I(self.lid..string.format("Transferring cargo %s to new carrier group %s", cargo.opsgroup:GetName(), carrierGroup:GetName())) + -- Unload from this and directly load into the other carrier. self:Unload(cargo.opsgroup) carrierGroup:Load(cargo.opsgroup, carrier) @@ -6229,7 +6248,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) local CargoIsArmyOrNavy=self:IsArmygroup() or self:IsNavygroup() -- Check that carrier is standing still. - if (CarrierIsArmyOrNavy and CarrierGroup:IsHolding()) or (CarrierGroup:IsParking() or CarrierGroup:IsLandedAt()) then + if (CarrierIsArmyOrNavy and (CarrierGroup:IsHolding() and CarrierGroup:GetVelocity(Carrier.name)<=1)) or (CarrierGroup:IsFlightgroup() and (CarrierGroup:IsParking() or CarrierGroup:IsLandedAt())) then -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. local board=self.speedMax>0 and CargoIsArmyOrNavy and self:IsAlive() and CarrierGroup:IsAlive() diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 4eca27af6..7d9e725c3 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -44,6 +44,7 @@ -- @field #boolean disembarkActivation Activation setting when group is disembared from carrier. -- @field #boolean disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. -- @field #table disembarkCarriers Table of carriers to which the cargo is disembared. This is a direct transfer from the old to the new carrier. +-- @field #table requiredCargos Table of cargo groups that must be loaded before the first transport is started. -- @field #number Ncargo Total number of cargo groups. -- @field #number Ncarrier Total number of assigned carriers. -- @field #number Ndelivered Total number of cargo groups delivered. @@ -70,6 +71,7 @@ OPSTRANSPORT = { carrierTransportStatus = {}, conditionStart = {}, pathsTransport = {}, + requiredCargos = {}, } --- Cargo transport status. @@ -266,7 +268,7 @@ end --- Set transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. -- @param #OPSTRANSPORT self --- @param Core.Set#SET_GROUP Carriers Carrier set. +-- @param Core.Set#SET_GROUP Carriers Carrier set. Can also be passed as a #GROUP, #OPSGROUP or #SET_OPSGROUP object. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetDisembarkCarriers(Carriers) @@ -313,6 +315,41 @@ function OPSTRANSPORT:SetDisembarkInUtero(InUtero) 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 +-- @param Core.Set#SET_GROUP Cargos Required cargo set. Can also be passed as a #GROUP, #OPSGROUP or #SET_OPSGROUP object. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetRequiredCargos(Cargos) + + self:I(self.lid.."Setting required cargos!") + + -- Create table. + self.requiredCargos=self.requiredCargos or {} + + if Cargos:IsInstanceOf("GROUP") or Cargos:IsInstanceOf("OPSGROUP") then + + local cargo=self:_GetOpsGroupFromObject(Cargos) + if cargo then + table.insert(self.requiredCargos, cargo) + end + + elseif Cargos:IsInstanceOf("SET_GROUP") or Cargos:IsInstanceOf("SET_OPSGROUP") then + + for _,object in pairs(Cargos:GetSet()) do + local cargo=self:_GetOpsGroupFromObject(object) + if cargo then + table.insert(self.requiredCargos, cargo) + end + end + + else + self:E(self.lid.."ERROR: Required Cargos must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!") + end + + return self +end + + --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self @@ -365,6 +402,22 @@ function OPSTRANSPORT:_DelCarrier(CarrierGroup) return self end +--- Get a list of alive carriers. +-- @param #OPSTRANSPORT self +-- @return #table Names of all carriers +function OPSTRANSPORT:_GetCarrierNames() + + local names={} + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + if carrier:IsAlive()~=nil then + table.insert(names, carrier.groupname) + end + end + + return names +end + --- Set transport start and stop time. -- @param #OPSTRANSPORT self @@ -778,6 +831,31 @@ function OPSTRANSPORT:_CheckDelivered() end +--- Check if all required cargos are loaded. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, all required cargos are loaded or there is no required cargo. +function OPSTRANSPORT:_CheckRequiredCargos() + + if self.requiredCargos==nil or #self.requiredCargos==0 then + return true + end + + local carrierNames=self:_GetCarrierNames() + + local gotit=true + for _,_cargo in pairs(self.requiredCargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP + + + if not cargo:IsLoaded(carrierNames) then + return false + end + + end + + return true +end + --- Check if all given condition are true. -- @param #OPSTRANSPORT self -- @param #table Conditions Table of conditions. @@ -820,17 +898,22 @@ function OPSTRANSPORT:FindTransferCarrierForCargo(CargoGroup, Zone) for _,_carrier in pairs(self.disembarkCarriers or {}) do local carrierGroup=_carrier --Ops.OpsGroup#OPSGROUP - -- Find an element of the group that has enough free space. - carrier=carrierGroup:FindCarrierForCargo(CargoGroup) + -- First check if carrier is alive and loading cargo. + if carrierGroup and carrierGroup:IsAlive() and carrierGroup:IsLoading() then - if carrier then - if Zone==nil or Zone:IsCoordinateInZone(carrier.unit:GetCoordinate()) then - return carrier, carrierGroup + -- Find an element of the group that has enough free space. + carrier=carrierGroup:FindCarrierForCargo(CargoGroup) + + if carrier then + if Zone==nil or Zone:IsCoordinateInZone(carrier.unit:GetCoordinate()) then + return carrier, carrierGroup + else + self:T3(self.lid.."Got transfer carrier but carrier not in zone (yet)!") + end else - self:T3(self.lid.."Got transfer carrier but carrier not in zone (yet)!") + self:T3(self.lid.."No transfer carrier available!") end - else - self:T3(self.lid.."No transfer carrier available!") + end end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 86843e1c1..01a6269d9 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1449,8 +1449,8 @@ do -- Cargo self:F({Desc=Desc}) local Weights = { - ["C-17A"] = 35000, --77519 cannot be used, because it loads way too much apcs and infantry., - ["C-130"] = 22000 --The real value cannot be used, because it loads way too much apcs and infantry., + ["C-17A"] = 35000, --77519 cannot be used, because it loads way too much apcs and infantry. + ["C-130"] = 22000 --The real value cannot be used, because it loads way too much apcs and infantry. } self.__.CargoBayWeightLimit = Weights[Desc.typeName] or ( Desc.massMax - ( Desc.massEmpty + Desc.fuelMassMax ) ) @@ -1467,6 +1467,7 @@ do -- Cargo ["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. ["LST_Mk2"] =2100000, -- Can carry 2100 tons according to wiki source! + ["speedboat"] = 500, -- 500 kg ~ 5 persons } self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 ) From 36669c80da151c85f91a3d318945a8d053abfa3a Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 25 Jun 2021 13:23:56 +0200 Subject: [PATCH 041/141] OPSTRANSPORT --- Moose Development/Moose/Ops/FlightGroup.lua | 20 +++-- Moose Development/Moose/Ops/OpsGroup.lua | 79 ++++++++++++++----- Moose Development/Moose/Ops/OpsTransport.lua | 44 ++++++++++- Moose Development/Moose/Wrapper/Airbase.lua | 12 +++ .../Moose/Wrapper/Positionable.lua | 19 ++--- 5 files changed, 139 insertions(+), 35 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 87b98c88b..61ccc80d7 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1474,7 +1474,7 @@ function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase) self:_UpdateStatus(Element, OPSGROUP.ElementStatus.TAKEOFF, airbase) -- Trigger element airborne event. - self:__ElementAirborne(2, Element) + self:__ElementAirborne(0.1, Element) end --- On after "ElementAirborne" event. @@ -1749,7 +1749,7 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) self:LandAtAirbase(airbase) end else - self:_CheckGroupDone(1) + self:_CheckGroupDone() end else self:_UpdateMenu() @@ -2194,7 +2194,8 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Send flight to destination. if destbase then self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") - self:__RTB(-3, destbase) + --self:RTB(destbase) + self:__RTB(-0.1, destbase) elseif destzone then self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTZ!") self:__RTZ(-3, destzone) @@ -2378,6 +2379,9 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Holding points. local c0=self.group:GetCoordinate() + local zone=airbase:GetZone() + env.info("FF landatairbase zone:") + self:I({zone=zone}) local p0=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(althold)) local p1=nil local wpap=nil @@ -3075,8 +3079,8 @@ function FLIGHTGROUP:_InitGroup() self:I(self.lid..text) end - env.info("DCS Unit BOOM_AND_RECEPTACLE="..tostring(Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)) - env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE)) + --env.info("DCS Unit BOOM_AND_RECEPTACLE="..tostring(Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)) + --env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE)) -- Init done. self.groupinitialized=true @@ -3711,6 +3715,8 @@ end -- @return Wrapper.Airbase#AIRBASE.ParkingSpot Parking spot or nil if no spot is within distance threshold. function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) + env.info("FF Get Parking spot for element "..element.name) + -- Coordinate of unit landed local coord=element.unit:GetCoordinate() @@ -3724,7 +3730,8 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) if airbase and airbase:IsShip() then coord.x=0 coord.z=0 - maxdist=100 + maxdist=500 -- 100 meters was not enough, e.g. on the Seawise Giant, where the spot is 139 meters from the "center" + env.info("FF Airbase is ship") end local spot=nil --Wrapper.Airbase#AIRBASE.ParkingSpot @@ -3733,6 +3740,7 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) for _,_parking in pairs(parking) do local parking=_parking --Wrapper.Airbase#AIRBASE.ParkingSpot dist=coord:Get2DDistance(parking.Coordinate) + env.info(string.format("FF parking %d dist=%.1f", parking.TerminalID, dist)) if dist 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. + self:_SetMyCarrier(CarrierGroup, Carrier, true) + end + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -8085,7 +8126,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__Airborne(-0.5) + self:__Airborne(-0.1) end elseif newstatus==OPSGROUP.ElementStatus.LANDED then diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 7d9e725c3..5c093a086 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -103,7 +103,7 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.6" +OPSTRANSPORT.version="0.0.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -418,6 +418,23 @@ function OPSTRANSPORT:_GetCarrierNames() return names end +--- Get (all) cargo @{Ops.OpsGroup#OPSGROUP}s. Optionally, only delivered or undelivered groups can be returned. +-- @param #OPSTRANSPORT self +-- @param #boolean Delivered If `true`, only delivered groups are returned. If `false` only undelivered groups are returned. If `nil`, all groups are returned. +-- @return #table Ops groups. +function OPSTRANSPORT:GetCargoOpsGroups(Delivered) + + local opsgroups={} + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + if Delivered==nil or cargo.delivered==Delivered then + table.insert(opsgroups, cargo.opsgroup) + end + end + + return opsgroups +end + --- Set transport start and stop time. -- @param #OPSTRANSPORT self @@ -574,8 +591,33 @@ function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup) return self.carrierTransportStatus[CarrierGroup.groupname] end +--- Get unique ID of the transport assignment. +-- @param #OPSTRANSPORT self +-- @return #number UID. +function OPSTRANSPORT:GetUID() + return self.uid +end +--- Get number of delivered cargo groups. +-- @param #OPSTRANSPORT self +-- @return #number Total number of delivered cargo groups. +function OPSTRANSPORT:GetNcargoDelivered() + return self.Ndelivered +end +--- Get number of cargo groups. +-- @param #OPSTRANSPORT self +-- @return #number Total number of cargo groups. +function OPSTRANSPORT:GetNcargoTotal() + return self.Ncargo +end + +--- Get number of carrier groups assigned for this transport. +-- @param #OPSTRANSPORT self +-- @return #number Total number of carrier groups. +function OPSTRANSPORT:GetNcarrier() + return self.Ncarrier +end --- Check if an OPS group is assigned as carrier for this transport. -- @param #OPSTRANSPORT self diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index ca6ba1ca1..aba50cabd 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -533,6 +533,9 @@ function AIRBASE:Register(AirbaseName) -- Get descriptors. self.descriptors=self:GetDesc() + + -- Debug info. + --self:I({airbase=AirbaseName, descriptors=self.descriptors}) -- Category. self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME @@ -544,12 +547,21 @@ function AIRBASE:Register(AirbaseName) self.isHelipad=true elseif self.category==Airbase.Category.SHIP then self.isShip=true + -- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects() + if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then + self.isHelipad=true + self.isShip=false + self.category=Airbase.Category.HELIPAD + _DATABASE:AddStatic(AirbaseName) + end else self:E("ERROR: Unknown airbase category!") end + -- Init parking spots. self:_InitParkingSpots() + -- Get 2D position vector. local vec2=self:GetVec2() -- Init coordinate. diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 01a6269d9..6589193df 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1459,15 +1459,16 @@ do -- Cargo self:F({Desc=Desc}) local Weights = { - ["Type_071"] = 245000, - ["LHA_Tarawa"] = 500000, - ["Ropucha-class"] = 150000, - ["Dry-cargo ship-1"] = 70000, - ["Dry-cargo ship-2"] = 70000, - ["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). - ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. - ["LST_Mk2"] =2100000, -- Can carry 2100 tons according to wiki source! - ["speedboat"] = 500, -- 500 kg ~ 5 persons + ["Type_071"] = 245000, + ["LHA_Tarawa"] = 500000, + ["Ropucha-class"] = 150000, + ["Dry-cargo ship-1"] = 70000, + ["Dry-cargo ship-2"] = 70000, + ["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). + ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. + ["LST_Mk2"] = 2100000, -- Can carry 2100 tons according to wiki source! + ["speedboat"] = 500, -- 500 kg ~ 5 persons + ["Seawise_Giant"] =261000000, -- Gross tonnage is 261,000 tonns. } self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 ) From a861f8d9d46ffa5ca43841c834442e8595065165 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 25 Jun 2021 21:13:24 +0200 Subject: [PATCH 042/141] OPS --- Moose Development/Moose/Ops/FlightGroup.lua | 8 +------- Moose Development/Moose/Ops/OpsGroup.lua | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 61ccc80d7..2ec865140 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2379,9 +2379,6 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Holding points. local c0=self.group:GetCoordinate() - local zone=airbase:GetZone() - env.info("FF landatairbase zone:") - self:I({zone=zone}) local p0=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(althold)) local p1=nil local wpap=nil @@ -3715,8 +3712,6 @@ end -- @return Wrapper.Airbase#AIRBASE.ParkingSpot Parking spot or nil if no spot is within distance threshold. function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) - env.info("FF Get Parking spot for element "..element.name) - -- Coordinate of unit landed local coord=element.unit:GetCoordinate() @@ -3731,7 +3726,6 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) coord.x=0 coord.z=0 maxdist=500 -- 100 meters was not enough, e.g. on the Seawise Giant, where the spot is 139 meters from the "center" - env.info("FF Airbase is ship") end local spot=nil --Wrapper.Airbase#AIRBASE.ParkingSpot @@ -3740,7 +3734,7 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) for _,_parking in pairs(parking) do local parking=_parking --Wrapper.Airbase#AIRBASE.ParkingSpot dist=coord:Get2DDistance(parking.Coordinate) - env.info(string.format("FF parking %d dist=%.1f", parking.TerminalID, dist)) + --env.info(string.format("FF parking %d dist=%.1f", parking.TerminalID, dist)) if dist pickup") -- Initiate the cargo transport process. - self:Pickup() + self:__Pickup(-1) elseif self:IsPickingup() then @@ -4897,9 +4900,10 @@ function OPSGROUP:_CheckCargoTransport() end - self:I(self.lid.."gotcargo="..tostring(gotcargo)) - self:I(self.lid.."boarding="..tostring(boarding)) - self:I(self.lid.."required="..tostring(self.cargoTransport:_CheckRequiredCargos())) + -- Debug. + --self:I(self.lid.."gotcargo="..tostring(gotcargo)) + --self:I(self.lid.."boarding="..tostring(boarding)) + --self:I(self.lid.."required="..tostring(self.cargoTransport:_CheckRequiredCargos())) -- Boarding finished ==> Transport cargo. if gotcargo and self.cargoTransport:_CheckRequiredCargos() and not boarding then @@ -5090,7 +5094,7 @@ function OPSGROUP:AddOpsTransport(OpsTransport) table.insert(self.cargoqueue, OpsTransport) -- Debug message. - self:I(self.lid.."FF adding transport to carrier, #self.cargoqueue="..#self.cargoqueue) + self:T(self.lid.."Adding transport to carrier, #self.cargoqueue="..#self.cargoqueue) return self end @@ -8708,7 +8712,7 @@ function OPSGROUP:_AddElementByName(unitname) -- Descriptors and type/category. element.descriptors=unit:GetDesc() - self:I({desc=element.descriptors}) + --self:I({desc=element.descriptors}) element.category=unit:GetUnitCategory() element.categoryname=unit:GetCategoryName() @@ -8762,7 +8766,7 @@ function OPSGROUP:_AddElementByName(unitname) local text=string.format("Adding element %s: status=%s, skill=%s, life=%.1f/%.1f category=%s (%d), type=%s, size=%.1f (L=%.1f H=%.1f W=%.1f), weight=%.1f/%.1f (cargo=%.1f/%.1f)", element.name, element.status, element.skill, element.life, element.life0, element.categoryname, element.category, element.typename, element.size, element.length, element.height, element.width, element.weight, element.weightMaxTotal, element.weightCargo, element.weightMaxCargo) - self:I(self.lid..text) + self:T(self.lid..text) -- Debug text. --local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s", From b012c5b2aa64f6cc18c3ec3728ef7c4dd4f5ae7f Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 28 Jun 2021 13:17:55 +0200 Subject: [PATCH 043/141] OPS Transport - Improved cargo bay weight calculation. --- Moose Development/Moose/Ops/OpsGroup.lua | 282 +++++++++++++++---- Moose Development/Moose/Ops/OpsTransport.lua | 13 +- 2 files changed, 234 insertions(+), 61 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 5032bcc29..13d1464e5 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -219,6 +219,7 @@ OPSGROUP = { -- @field #number weightMaxCargo Max. cargo weight in kg. -- @field #number weightCargo Current cargo weight in kg. -- @field #number weight Current weight including cargo in kg. +-- @field #table cargoBay Cargo bay. --- Status of group element. -- @type OPSGROUP.ElementStatus @@ -420,12 +421,17 @@ OPSGROUP.CargoStatus={ -- @field #number length Length of (un-)loading zone in meters. -- @field #number width Width of (un-)loading zone in meters. ---- Cargo transport data. +--- Data of the carrier that has loaded this group. -- @type OPSGROUP.MyCarrier -- @field #OPSGROUP group The carrier group. -- @field #OPSGROUP.Element element The carrier element. -- @field #boolean reserved If `true`, the carrier has caro space reserved for me. +--- Element cargo bay data. +-- @type OPSGROUP.MyCargo +-- @field #OPSGROUP group The cargo group. +-- @field #boolean reserved If `true`, the cargo bay space is reserved but cargo has not actually been loaded yet. + --- Cargo group data. -- @type OPSGROUP.CargoGroup -- @field #OPSGROUP opsgroup The cargo opsgroup. @@ -4562,6 +4568,7 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end -- Check cargo bay and declare cargo groups dead. + --[[ for groupname, carriername in pairs(self.cargoBay or {}) do if Element.name==carriername then local opsgroup=_DATABASE:GetOpsGroup(groupname) @@ -4578,6 +4585,26 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end end end + ]] + + -- Check cargo bay and declare cargo groups dead. + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + if cargo.group and not (cargo.group:IsDead() or cargo.group:IsStopped()) then + for _,cargoelement in pairs(cargo.group.elements) do + + -- Debug info. + self:T2(self.lid.."Cargo element dead "..cargoelement.name) + + -- Trigger dead event. + cargo.group:ElementDead(cargoelement) + + end + end + end + end if self:IsCarrier() then if self.cargoTransport then @@ -4800,6 +4827,7 @@ function OPSGROUP:_CheckCargoTransport() local Time=timer.getAbsTime() -- Cargo bay debug info. + --[[ if self.verbose>=3 then local text="" for cargogroupname, carriername in pairs(self.cargoBay) do @@ -4809,6 +4837,22 @@ function OPSGROUP:_CheckCargoTransport() self:I(self.lid.."Cargo bay:"..text) end end + ]] + + -- Check cargo bay and declare cargo groups dead. + if self.verbose>=0 then + local text="" + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + text=text..string.format("\n- %s in carrier %s, reserved=%s", tostring(cargo.group:GetName()), tostring(element.name), tostring(cargo.reserved)) + end + end + if text~="" then + self:I(self.lid.."Cargo bay:"..text) + end + end -- Cargo queue debug info. if self.verbose>=3 then @@ -4958,49 +5002,157 @@ function OPSGROUP:_CheckCargoTransport() return self end + +--- Check if a group is in the cargo bay. +-- @param #OPSGROUP self +-- @param #OPSGROUP OpsGroup Group to check. +-- @return #boolean If `true`, group is in the cargo bay. +function OPSGROUP:_IsInCargobay(OpsGroup) + + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + if cargo.group.groupname==OpsGroup.groupname then + return true + end + end + end + + return false +end + --- Add OPSGROUP to cargo bay of a carrier. -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group. -- @param #OPSGROUP.Element CarrierElement The element of the carrier. -function OPSGROUP:_AddCargobay(CargoGroup, CarrierElement) +-- @param #boolean Reserved Only reserve the cargo bay space. +function OPSGROUP:_AddCargobay(CargoGroup, CarrierElement, Reserved) --TODO: Check group is not already in cargobay of this carrier or any other carrier. + + local cargo=self:_GetCargobay(CargoGroup) + + if cargo then + cargo.reserved=Reserved + else + cargo={} --#OPSGROUP.MyCargo + cargo.group=CargoGroup + cargo.reserved=Reserved + + table.insert(CarrierElement.cargoBay, cargo) + end + + + + -- Set my carrier. + CargoGroup:_SetMyCarrier(self, CarrierElement, Reserved) + + -- Fill cargo bay (obsolete). + self.cargoBay[CargoGroup.groupname]=CarrierElement.name + + if not Reserved then + -- Cargo weight. local weight=CargoGroup:GetWeightTotal() -- Add weight to carrier. self:AddWeightCargo(CarrierElement.name, weight) - - -- Fill cargo bay. - self.cargoBay[CargoGroup.groupname]=CarrierElement.name + + end return self end +--- Get cargo bay item. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group. +-- @return #OPSGROUP.MyCargo Cargo bay item or `nil` if the group is not in the carrier. +-- @return #number CargoBayIndex Index of item in the cargo bay table. +-- @return #OPSGROUP.Element Carrier element. +function OPSGROUP:_GetCargobay(CargoGroup) + + -- Loop over elements and their cargo bay items. + local CarrierElement=nil --#OPSGROUP.Element + local cargobayIndex=nil + local reserved=nil + for i,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + for j,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + if cargo.group and cargo.group.groupname==CargoGroup.groupname then + return cargo, j, element + end + end + end + + return nil, nil, nil +end + --- Remove OPSGROUP from cargo bay of a carrier. -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group. --- @param #OPSGROUP.Element CarrierElement The element of the carrier. -- @return #boolean If `true`, cargo could be removed. -function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) +function OPSGROUP:_DelCargobay(CargoGroup) if self.cargoBay[CargoGroup.groupname] then -- Not in cargo bay any more. self.cargoBay[CargoGroup.groupname]=nil - -- Reduce carrier weight. - local weight=CargoGroup:GetWeightTotal() - - -- Get carrier of group. - local carrier=CargoGroup:_GetMyCarrierElement() - - if carrier then - self:RedWeightCargo(carrier.name, weight) + end + + + --[[ + local MyCarrierGroup, MyCarrierElement, MyIsReserved=CargoGroup:_GetMyCarrier() + + if MyCarrierGroup and MyCarrierGroup.groupname==self.groupname then + if not IsReserved then + + -- Reduce carrier weight. + local weight=CargoGroup:GetWeightTotal() + + self:RedWeightCargo(CarrierElement.name, weight) + + end + + end + ]] + + + -- Loop over elements and their cargo bay items. + --[[ + local CarrierElement=nil --#OPSGROUP.Element + local cargobayIndex=nil + local reserved=nil + for i,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + for j,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + if cargo.group and cargo.group.groupname==CargoGroup.groupname then + CarrierElement=element + cargobayIndex=j + reserved=cargo.reserved + end + end + end + ]] + + local cargoBayItem, cargoBayIndex, CarrierElement=self:_GetCargobay(CargoGroup) + + if cargoBayItem and cargoBayIndex then + + -- Remove + table.remove(CarrierElement.cargoBay, cargoBayIndex) + + -- Reduce weight (if cargo space was not just reserved). + if not cargoBayItem.reserved then + local weight=CargoGroup:GetWeightTotal() + self:RedWeightCargo(CarrierElement.name, weight) end - return true + return true end env.error(self.lid.."ERROR: Group is not in cargo bay. Cannot remove it!") @@ -5117,11 +5269,12 @@ function OPSGROUP:DelCargoTransport(CargoTransport) end ---- Get total weight of the group including cargo. +--- Get total weight of the group including cargo. Optionally, the total weight of a specific unit can be requested. -- @param #OPSGROUP self -- @param #string UnitName Name of the unit. Default is of the whole group. +-- @param #boolean IncludeReserved If `false`, cargo weight that is only *reserved* is **not** counted. By default (`true` or `nil`), the reserved cargo is included. -- @return #number Total weight in kg. -function OPSGROUP:GetWeightTotal(UnitName) +function OPSGROUP:GetWeightTotal(UnitName, IncludeReserved) local weight=0 for _,_element in pairs(self.elements) do @@ -5129,7 +5282,21 @@ function OPSGROUP:GetWeightTotal(UnitName) if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then - weight=weight+element.weight + weight=weight+element.weightEmpty + + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + + local wcargo=0 + + -- Count cargo that is not reserved or if reserved cargo should be included. + if (not cargo.reserved) or (cargo.reserved==true and (IncludeReserved==true or IncludeReserved==nil)) then + wcargo=cargo.group:GetWeightTotal(element.name) + end + + weight=weight+wcargo + + end end @@ -5141,24 +5308,15 @@ end --- Get free cargo bay weight. -- @param #OPSGROUP self -- @param #string UnitName Name of the unit. Default is of the whole group. +-- @param #boolean IncludeReserved If `false`, cargo weight that is only *reserved* is **not** counted. By default (`true` or `nil`), the reserved cargo is included. -- @return #number Free cargo bay in kg. -function OPSGROUP:GetFreeCargobay(UnitName) +function OPSGROUP:GetFreeCargobay(UnitName, IncludeReserved) - local Free=0 - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - - if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then - local free=element.weightMaxCargo-element.weightCargo - --[[ - for _,_opsgroup in pairs(element.reservedCargos or {}) do - local opsgroup=_opsgroup --#OPSGROUP - free=free-opsgroup:GetWeightTotal() - end - ]] - Free=Free+free - end - end + local weightCargoMax=self:GetWeightCargoMax(UnitName) + + local weightCargo=self:GetWeightCargo(UnitName, IncludeReserved) + + local Free=weightCargoMax-weightCargo self:I(self.lid..string.format("Free cargo bay=%d kg (unit=%s)", Free, (UnitName or "whole group"))) return Free @@ -5198,8 +5356,9 @@ end --- Get weight of the internal cargo the group is carriing right now. -- @param #OPSGROUP self -- @param #string UnitName Name of the unit. Default is of the whole group. +-- @param #boolean IncludeReserved If `false`, cargo weight that is only *reserved* is **not** counted. By default (`true` or `nil`), the reserved cargo is included. -- @return #number Cargo weight in kg. -function OPSGROUP:GetWeightCargo(UnitName) +function OPSGROUP:GetWeightCargo(UnitName, IncludeReserved) local weight=0 for _,_element in pairs(self.elements) do @@ -5213,34 +5372,36 @@ function OPSGROUP:GetWeightCargo(UnitName) end - local gewicht=0 - for groupname, carriername in pairs(self.cargoBay) do - local element=self:GetElementByName(carriername) - if (UnitName==nil or UnitName==carriername) and (element and element.status~=OPSGROUP.ElementStatus.DEAD) then - local opsgroup=_DATABASE:FindOpsGroup(groupname) - if opsgroup then - gewicht=gewicht+opsgroup:GetWeightTotal() + local gewicht=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if (UnitName==nil or UnitName==element.name) and (element and element.status~=OPSGROUP.ElementStatus.DEAD) then + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + if (not cargo.reserved) or (cargo.reserved==true and (IncludeReserved==true or IncludeReserved==nil)) then + gewicht=gewicht+cargo.group:GetWeightTotal() + end end end end - - if gewicht~=weight then + if IncludeReserved==false and gewicht~=weight then self:I(self.lid..string.format("ERROR: FF weight!=gewicht: weight=%.1f, gewicht=%.1f", weight, gewicht)) end - return weight + return gewicht end ---- Get max weight of the internal cargo the group can carry. +--- Get max weight of the internal cargo the group can carry. Optionally, the max cargo weight of a specific unit can be requested. -- @param #OPSGROUP self --- @return #number Cargo weight in kg. -function OPSGROUP:GetWeightCargoMax() +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @return #number Max cargo weight in kg. This does **not** include any cargo loaded or reserved currently. +function OPSGROUP:GetWeightCargoMax(UnitName) local weight=0 for _,_element in pairs(self.elements) do local element=_element --#OPSGROUP.Element - if element.status~=OPSGROUP.ElementStatus.DEAD then + if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then weight=weight+element.weightMaxCargo @@ -5263,6 +5424,9 @@ function OPSGROUP:AddWeightCargo(UnitName, Weight) -- Add weight. element.weightCargo=element.weightCargo+Weight + + -- Debug info. + self:I(self.lid..string.format("FF %s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg", UnitName, Weight, element.weightCargo)) -- For airborne units, we set the weight in game. if self.isFlightgroup then @@ -5613,6 +5777,8 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Find a carrier that has enough free cargo bay for this group. local carrier=_findCarrier(weight) + + local carrier=self:FindCarrierForCargo(cargo.opsgroup) if carrier then @@ -5694,9 +5860,6 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) if carrier then - -- Add into carrier bay. - self:_AddCargobay(CargoGroup, carrier) - --- -- Embark Cargo --- @@ -5710,8 +5873,8 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) -- Clear all waypoints. CargoGroup:ClearWaypoints() - -- Set carrier (again). - CargoGroup:_SetMyCarrier(self, carrier, false) + -- Add into carrier bay. + self:_AddCargobay(CargoGroup, carrier, false) -- Despawn this group. if CargoGroup:IsAlive() then @@ -6308,7 +6471,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) end -- Set carrier. As long as the group is not loaded, we only reserve the cargo space. - self:_SetMyCarrier(CarrierGroup, Carrier, true) + CarrierGroup:_AddCargobay(self, Carrier, true) else @@ -6337,8 +6500,9 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self:T(self.lid.."Carrier not ready for boarding yet ==> 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. - self:_SetMyCarrier(CarrierGroup, Carrier, true) + -- Set carrier. As long as the group is not loaded, we only reserve the cargo space.´ + CarrierGroup:_AddCargobay(self, Carrier, true) + --self:_SetMyCarrier(CarrierGroup, Carrier, true) end @@ -8750,6 +8914,8 @@ function OPSGROUP:_AddElementByName(unitname) -- Max cargo weight: unit:SetCargoBayWeightLimit() element.weightMaxCargo=unit.__.CargoBayWeightLimit + + element.cargoBay={} -- FLIGHTGROUP specific. if self.isFlightgroup then diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 5c093a086..3b5434add 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -59,7 +59,12 @@ -- -- # The OPSTRANSPORT Concept -- --- This class simulates troop transport using carriers such as APCs, ships helicopters or airplanes. The carriers and transported groups need to be OPSGROUPS (see ARMYGROUP, NAVYGROUP and FLIGHTGROUP classed). +-- This class simulates troop transport using carriers such as APCs, ships, helicopters or airplanes. The carriers and transported groups need to be OPSGROUPS (see ARMYGROUP, NAVYGROUP and FLIGHTGROUP classes). +-- +-- **IMPORTANT NOTES** +-- +-- * Cargo groups are **not** split and distributed into different carrier *units*. That means that the whole cargo group **must fit** into one of the carrier units. +-- * Cargo groups must be inside the pickup zones to be considered for loading. Groups not inside the pickup zone will not get the command to board. -- -- -- @field #OPSTRANSPORT @@ -714,8 +719,10 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) text=text..string.format("\nCarriers:") for _,_carrier in pairs(self.carriers) do local carrier=_carrier --Ops.OpsGroup#OPSGROUP - text=text..string.format("\n- %s: %s [%s], cargo=%d/%d kg, free cargo bay %d/%d kg", - carrier:GetName(), carrier.carrierStatus:upper(), carrier:GetState(), carrier:GetWeightCargo(), carrier:GetWeightCargoMax(), carrier:GetFreeCargobayMax(true), carrier:GetFreeCargobayMax()) + text=text..string.format("\n- %s: %s [%s], Cargo Bay [current/reserved/total]=%d/%d/%d kg [free %d/%d/%d kg]", + carrier:GetName(), carrier.carrierStatus:upper(), carrier:GetState(), + carrier:GetWeightCargo(nil, false), carrier:GetWeightCargo(), carrier:GetWeightCargoMax(), + carrier:GetFreeCargobay(nil, false), carrier:GetFreeCargobay(), carrier:GetFreeCargobayMax()) end self:I(self.lid..text) From fa3e387dd1c133d7e0ccead9d496aa1a8d89069e Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 30 Jun 2021 23:23:41 +0200 Subject: [PATCH 044/141] OPS --- Moose Development/Moose/Ops/ArmyGroup.lua | 17 -- Moose Development/Moose/Ops/FlightGroup.lua | 62 +---- Moose Development/Moose/Ops/NavyGroup.lua | 16 -- Moose Development/Moose/Ops/OpsGroup.lua | 258 +++++++++----------- 4 files changed, 120 insertions(+), 233 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index fc82983ac..f2953ede0 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1055,23 +1055,6 @@ function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation) end ---- On after "Stop" event. --- @param #ARMYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function ARMYGROUP:onafterStop(From, Event, To) - - -- Handle events: - self:UnHandleEvent(EVENTS.Birth) - self:UnHandleEvent(EVENTS.Dead) - self:UnHandleEvent(EVENTS.RemoveUnit) - - -- Call OPSGROUP function. - self:GetParent(self).onafterStop(self, From, Event, To) - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 2ec865140..c68ae0edc 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2079,26 +2079,6 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) end ---- On after "Respawn" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #table Template The template used to respawn the group. -function FLIGHTGROUP:onafterRespawn(From, Event, To, Template) - - self:T(self.lid.."Respawning group!") - - local template=UTILS.DeepCopy(Template or self.template) - - if self.group and self.group:InAir() then - template.lateActivation=false - self.respawning=true - self.group=self.group:Respawn(template) - end - -end - --- On after "OutOfMissilesAA" event. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -2856,44 +2836,6 @@ function FLIGHTGROUP:onafterFuelCritical(From, Event, To) end end ---- On after "Stop" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function FLIGHTGROUP:onafterStop(From, Event, To) - - -- Check if group is still alive. - if self:IsAlive() then - - -- Set element parking spot to FREE (after arrived for example). - if self.flightcontrol then - for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element - self:_SetElementParkingFree(element) - end - end - - end - - self.currbase=nil - - -- Handle events: - self:UnHandleEvent(EVENTS.Birth) - self:UnHandleEvent(EVENTS.EngineStartup) - self:UnHandleEvent(EVENTS.Takeoff) - self:UnHandleEvent(EVENTS.Land) - self:UnHandleEvent(EVENTS.EngineShutdown) - self:UnHandleEvent(EVENTS.PilotDead) - self:UnHandleEvent(EVENTS.Ejection) - self:UnHandleEvent(EVENTS.Crash) - self:UnHandleEvent(EVENTS.RemoveUnit) - - -- Call OPSGROUP function. - self:GetParent(self).onafterStop(self, From, Event, To) - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Task functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3433,14 +3375,14 @@ function FLIGHTGROUP:InitWaypoints() self.currbase=self:GetHomebaseFromWaypoints() -- Remove the landing waypoint. We use RTB for that. It makes adding new waypoints easier as we do not have to check if the last waypoint is the landing waypoint. - if self.destbase then + if self.destbase and #self.waypoints>1 then table.remove(self.waypoints, #self.waypoints) else self.destbase=self.homebase end -- Debug info. - self:T(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown")) + self:I(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown")) -- Update route. if #self.waypoints>0 then diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index fb836d489..d78b0a6a5 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1062,22 +1062,6 @@ function NAVYGROUP:onafterCollisionWarning(From, Event, To, Distance) self.collisionwarning=true end ---- On after Start event. Starts the NAVYGROUP FSM and event handlers. --- @param #NAVYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function NAVYGROUP:onafterStop(From, Event, To) - - -- Handle events: - self:UnHandleEvent(EVENTS.Birth) - self:UnHandleEvent(EVENTS.Dead) - self:UnHandleEvent(EVENTS.RemoveUnit) - - -- Call OPSGROUP function. - self:GetParent(self).onafterStop(self, From, Event, To) - -end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 13d1464e5..5273b0176 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -378,6 +378,7 @@ OPSGROUP.TaskType={ -- @field #boolean detour If true, this waypoint is not part of the normal route. -- @field #boolean intowind If true, this waypoint is a turn into wind route point. -- @field #boolean astar If true, this waypint was found by A* pathfinding algorithm. +-- @field #boolean temp If true, this is a temporary waypoint and will be deleted when passed. Also the passing waypoint FSM event is not triggered. -- @field #number npassed Number of times a groups passed this waypoint. -- @field Core.Point#COORDINATE coordinate Waypoint coordinate. -- @field Core.Point#COORDINATE roadcoord Closest point to road. @@ -2292,50 +2293,34 @@ function OPSGROUP:OnEventBirth(EventData) local group=EventData.IniGroup local unitname=EventData.IniUnitName - if self.respawning then - self:I(self.lid.."Respawning unit "..tostring(unitname)) + -- Set homebase if not already set. + if self.isFlightgroup then - local function reset() - self.respawning=nil - self:_CheckGroupDone() - end - - -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. - -- TODO: Can I do this more rigorously? - self:ScheduleOnce(1, reset) - - else - - -- Set homebase if not already set. - if self.isFlightgroup then - - if EventData.Place then - self.homebase=self.homebase or EventData.Place - self.currbase=EventData.Place - else - self.currbase=nil - end - - if self.homebase and not self.destbase then - self.destbase=self.homebase - end - - self:T(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", unitname, self.homebase and self.homebase:GetName() or "unknown")) + if EventData.Place then + self.homebase=self.homebase or EventData.Place + self.currbase=EventData.Place else - self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) + self.currbase=nil end - -- Get element. - local element=self:GetElementByName(unitname) - - -- Set element to spawned state. - self:ElementSpawned(element) + if self.homebase and not self.destbase then + self.destbase=self.homebase + end + self:I(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned", unitname, self.homebase and self.homebase:GetName() or "unknown")) + else + self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) end - end + -- Get element. + local element=self:GetElementByName(unitname) + -- Set element to spawned state. + self:ElementSpawned(element) + + end + end --- Event function handling the crash of a unit. @@ -3755,7 +3740,7 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after "PassingWaypoint" event. +--- Check if group is currently waiting. -- @param #OPSGROUP self -- @param #boolean If true, group is currently waiting. function OPSGROUP:IsWaiting() @@ -3776,8 +3761,10 @@ function OPSGROUP:onafterWait(From, Event, To, Duration) -- Order Group to hold. self:FullStop() + -- Set time stamp. self.Twaiting=timer.getAbsTime() + -- Max waiting self.dTwait=Duration end @@ -4567,25 +4554,6 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end end - -- Check cargo bay and declare cargo groups dead. - --[[ - for groupname, carriername in pairs(self.cargoBay or {}) do - if Element.name==carriername then - local opsgroup=_DATABASE:GetOpsGroup(groupname) - if opsgroup and not (opsgroup:IsDead() or opsgroup:IsStopped()) then - for _,element in pairs(opsgroup.elements) do - - -- Debug info. - self:T2(self.lid.."Cargo element dead "..element.name) - - -- Trigger dead event. - opsgroup:ElementDead(element) - - end - end - end - end - ]] -- Check cargo bay and declare cargo groups dead. for _,_element in pairs(self.elements) do @@ -4593,25 +4561,31 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) for _,_cargo in pairs(element.cargoBay) do local cargo=_cargo --#OPSGROUP.MyCargo if cargo.group and not (cargo.group:IsDead() or cargo.group:IsStopped()) then - for _,cargoelement in pairs(cargo.group.elements) do - - -- Debug info. - self:T2(self.lid.."Cargo element dead "..cargoelement.name) - - -- Trigger dead event. - cargo.group:ElementDead(cargoelement) - + + -- Remove my carrier + cargo.group:_RemoveMyCarrier() + + if cargo.reserved then + -- This group was not loaded yet ==> Not cargo any more. + cargo.group.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + else + + -- Carrier dead ==> cargo dead. + for _,cargoelement in pairs(cargo.group.elements) do + + -- Debug info. + self:T2(self.lid.."Cargo element dead "..cargoelement.name) + + -- Trigger dead event. + cargo.group:ElementDead(cargoelement) + + end end + end end end - if self:IsCarrier() then - if self.cargoTransport then - self.cargoTransport:DeadCarrierGroup(self) - end - end - end --- On after "Respawn" event. @@ -4713,6 +4687,10 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Debug output. self:I({Template=Template}) + + --if self:IsStopped() then + --self:InitWaypoints() + --end -- Spawn new group. _DATABASE:Spawn(Template) @@ -4720,6 +4698,7 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Set activation and controlled state. self.isLateActivated=Template.lateActivation self.isUncontrolled=Template.uncontrolled + -- Reset events. --self:ResetEvents() @@ -4772,20 +4751,45 @@ function OPSGROUP:onafterDead(From, Event, To) end + -- Delete waypoints so they are re-initialized at the next spawn. + self:ClearWaypoints() + self.groupinitialized=false + + -- Set cargo status to NOTCARGO. + self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER + + -- Remove from cargo bay of carrier. + local mycarrier=self:_GetMyCarrierGroup() + if mycarrier and not mycarrier:IsDead() then + mycarrier:_DelCargobay(self) + self:_RemoveMyCarrier() + end + -- Inform all transports in the queue that this carrier group is dead now. for i,_transport in pairs(self.cargoqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT - transport:DeadCarrierGroup(self) + transport:__DeadCarrierGroup(1, self) end - - -- Delete waypoints so they are re-initialized at the next spawn. - self.waypoints=nil - self.groupinitialized=false -- Stop in a sec. self:__Stop(-5) end +--- On before "Stop" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onbeforeStop(From, Event, To) + + if self:IsAlive() then + return false + end + + return true +end + --- On after "Stop" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -4793,6 +4797,23 @@ end -- @param #string To To state. function OPSGROUP:onafterStop(From, Event, To) + -- Handle events: + self:UnHandleEvent(EVENTS.Birth) + self:UnHandleEvent(EVENTS.Dead) + self:UnHandleEvent(EVENTS.RemoveUnit) + + -- Handle events: + if self.isFlightgroup then + self:UnHandleEvent(EVENTS.EngineStartup) + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + self:UnHandleEvent(EVENTS.EngineShutdown) + self:UnHandleEvent(EVENTS.PilotDead) + self:UnHandleEvent(EVENTS.Ejection) + self:UnHandleEvent(EVENTS.Crash) + self.currbase=nil + end + -- Stop check timers. self.timerCheckZone:Stop() self.timerQueueUpdate:Stop() @@ -4827,18 +4848,6 @@ function OPSGROUP:_CheckCargoTransport() local Time=timer.getAbsTime() -- Cargo bay debug info. - --[[ - if self.verbose>=3 then - local text="" - for cargogroupname, carriername in pairs(self.cargoBay) do - text=text..string.format("\n- %s in carrier %s", tostring(cargogroupname), tostring(carriername)) - end - if text~="" then - self:I(self.lid.."Cargo bay:"..text) - end - end - ]] - -- Check cargo bay and declare cargo groups dead. if self.verbose>=0 then local text="" @@ -5044,7 +5053,6 @@ function OPSGROUP:_AddCargobay(CargoGroup, CarrierElement, Reserved) table.insert(CarrierElement.cargoBay, cargo) end - -- Set my carrier. CargoGroup:_SetMyCarrier(self, CarrierElement, Reserved) @@ -5500,22 +5508,6 @@ function OPSGROUP:FindCarrierForCargo(CargoGroup) return nil end ---- Reserve cargo space for a cargo group. --- @param #OPSGROUP self --- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. --- @return #OPSGROUP.Element Carrier able to transport the cargo. -function OPSGROUP:ReserveCargoSpace(CargoGroup) - - local element=self:FindCarrierForCargo(CargoGroup) - - if element then - element.reservedCargo=element.reservedCargo or {} - table.insert(element.reservedCargo, CargoGroup) - end - - return nil -end - --- Set my carrier. -- @param #OPSGROUP self -- @param #OPSGROUP CarrierGroup Carrier group. @@ -5688,8 +5680,7 @@ function OPSGROUP:onafterPickup(From, Event, To) -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=true else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") @@ -5733,26 +5724,6 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Loading time stamp. self.Tloading=timer.getAbsTime() - -- Create a temp array and monitor the free cargo space for each element. - local cargobay={} - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - cargobay[element.name]=element.weightMaxCargo-element.weightCargo - end - - - --- Find a carrier which can load a given weight. - local function _findCarrier(weight) - local carrier=nil --#OPSGROUP.Element - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - if cargobay[element.name]>=weight then - return element - end - end - return nil - end - --TODO: sort cargos wrt weight. -- Loop over all cargos. @@ -5772,19 +5743,11 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Cargo MUST be inside zone or it will not be loaded! if inzone then - -- Weight of cargo. - local weight=cargo.opsgroup:GetWeightTotal() - - -- Find a carrier that has enough free cargo bay for this group. - local carrier=_findCarrier(weight) - + -- Find a carrier for this cargo. local carrier=self:FindCarrierForCargo(cargo.opsgroup) if carrier then - -- Decrease free cargo bay. - cargobay[carrier.name]=cargobay[carrier.name]-weight - -- Set cargo status. cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED @@ -5918,6 +5881,23 @@ function OPSGROUP:onafterLoaded(From, Event, To) end +--- On before "Transport" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onbeforeTransport(From, Event, To) + + if self.cargoTransport==nil then + return false + elseif self.cargoTransport:IsDelivered() then --could be if all cargo was dead on boarding + return false + end + + return true +end + + --- On after "Transport" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -6011,8 +5991,7 @@ function OPSGROUP:onafterTransport(From, Event, To) -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=true else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") @@ -6461,12 +6440,10 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self:ClearWaypoints() if self.isArmygroup then - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=0 self:Cruise() else - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=0 self:Cruise() end @@ -7322,6 +7299,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) opsgroup:Cruise() else opsgroup:E("ERROR: waypoint.detour should be 0 or 1") + opsgroup:FullStop() end end From 3c6e8d6d01e31f1ce06c0cc690e8a2e2a7d99aa1 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 1 Jul 2021 12:29:29 +0200 Subject: [PATCH 045/141] OPS - Transport - Respawn --- Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 177 +++++++++---------- Moose Development/Moose/Ops/OpsTransport.lua | 70 +++++--- 3 files changed, 121 insertions(+), 128 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index c68ae0edc..5534aa11e 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -863,7 +863,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() - + -- Update position. self:_UpdatePosition() diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 5273b0176..fc0022178 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -525,7 +525,7 @@ function OPSGROUP:New(group) -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("InUtero", "Spawned", "Spawned") -- The whole group was spawned. - self:AddTransition("*", "Respawn", "*") -- Respawn group. + self:AddTransition("*", "Respawn", "InUtero") -- Respawn group. self:AddTransition("*", "Dead", "Dead") -- The whole group is dead. self:AddTransition("*", "InUtero", "InUtero") -- Deactivated group goes back to mummy. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -4473,7 +4473,7 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Element The flight group element. function OPSGROUP:onafterElementInUtero(From, Event, To, Element) - self:I(self.lid..string.format("Element in utero %s", Element.name)) + self:T(self.lid..string.format("Element in utero %s", Element.name)) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.INUTERO) @@ -4503,9 +4503,6 @@ function OPSGROUP:onafterElementDestroyed(From, Event, To, Element) -- Element is dead. self:ElementDead(Element) - -- Set element status. - --self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) - end --- On after "ElementDead" event. @@ -4554,36 +4551,40 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end end - - -- Check cargo bay and declare cargo groups dead. - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - for _,_cargo in pairs(element.cargoBay) do - local cargo=_cargo --#OPSGROUP.MyCargo - if cargo.group and not (cargo.group:IsDead() or cargo.group:IsStopped()) then + + -- Clear cargo bay of element. + --for _,_cargo in pairs(Element.cargoBay) do + for i=#Element.cargoBay,1,-1 do + local cargo=Element.cargoBay[i] --#OPSGROUP.MyCargo --_cargo --#OPSGROUP.MyCargo + + -- Remove from cargo bay. + self:_DelCargobay(cargo.group) + + if cargo.group and not (cargo.group:IsDead() or cargo.group:IsStopped()) then + + -- Remove my carrier + cargo.group:_RemoveMyCarrier() - -- Remove my carrier - cargo.group:_RemoveMyCarrier() + if cargo.reserved then + + -- This group was not loaded yet ==> Not cargo any more. + cargo.group.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - if cargo.reserved then - -- This group was not loaded yet ==> Not cargo any more. - cargo.group.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - else - - -- Carrier dead ==> cargo dead. - for _,cargoelement in pairs(cargo.group.elements) do - - -- Debug info. - self:T2(self.lid.."Cargo element dead "..cargoelement.name) - - -- Trigger dead event. - cargo.group:ElementDead(cargoelement) - - end + else + + -- Carrier dead ==> cargo dead. + for _,cargoelement in pairs(cargo.group.elements) do + + -- Debug info. + self:T2(self.lid.."Cargo element dead "..cargoelement.name) + + -- Trigger dead event. + cargo.group:ElementDead(cargoelement) + end - - end - end + end + + end end end @@ -4596,14 +4597,15 @@ end -- @param #table Template The template used to respawn the group. Default is the inital template of the group. function OPSGROUP:onafterRespawn(From, Event, To, Template) + -- Debug info. self:I(self.lid.."Respawning group!") + -- Copy template. local template=UTILS.DeepCopy(Template or self.template) + -- Late activation off. template.lateActivation=false - --self.respawning=true - self:_Respawn(0, template) end @@ -4686,12 +4688,8 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) end -- Debug output. - self:I({Template=Template}) + self:T({Template=Template}) - --if self:IsStopped() then - --self:InitWaypoints() - --end - -- Spawn new group. _DATABASE:Spawn(Template) @@ -4699,6 +4697,10 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) self.isLateActivated=Template.lateActivation self.isUncontrolled=Template.uncontrolled + -- Not dead or destroyed any more. + self.isDead=false + self.isDestroyed=false + self.Ndestroyed=0 -- Reset events. --self:ResetEvents() @@ -4765,7 +4767,7 @@ function OPSGROUP:onafterDead(From, Event, To) mycarrier:_DelCargobay(self) self:_RemoveMyCarrier() end - + -- Inform all transports in the queue that this carrier group is dead now. for i,_transport in pairs(self.cargoqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT @@ -4783,7 +4785,9 @@ end -- @param #string To To state. function OPSGROUP:onbeforeStop(From, Event, To) + -- We check if if self:IsAlive() then + self:E(self.lid..string.format("WARNING: Group is still alive! Will not stop the FSM. Use :Despawn() instead")) return false end @@ -4849,7 +4853,7 @@ function OPSGROUP:_CheckCargoTransport() -- Cargo bay debug info. -- Check cargo bay and declare cargo groups dead. - if self.verbose>=0 then + if self.verbose>=1 then local text="" for _,_element in pairs(self.elements) do local element=_element --#OPSGROUP.Element @@ -4858,9 +4862,10 @@ function OPSGROUP:_CheckCargoTransport() text=text..string.format("\n- %s in carrier %s, reserved=%s", tostring(cargo.group:GetName()), tostring(element.name), tostring(cargo.reserved)) end end - if text~="" then - self:I(self.lid.."Cargo bay:"..text) - end + if text=="" then + text=" empty" + end + self:I(self.lid.."Cargo bay:"..text) end -- Cargo queue debug info. @@ -5110,47 +5115,15 @@ function OPSGROUP:_DelCargobay(CargoGroup) self.cargoBay[CargoGroup.groupname]=nil end - - - --[[ - local MyCarrierGroup, MyCarrierElement, MyIsReserved=CargoGroup:_GetMyCarrier() - - if MyCarrierGroup and MyCarrierGroup.groupname==self.groupname then - if not IsReserved then - - -- Reduce carrier weight. - local weight=CargoGroup:GetWeightTotal() - - self:RedWeightCargo(CarrierElement.name, weight) - - end - - end - ]] - - - -- Loop over elements and their cargo bay items. - --[[ - local CarrierElement=nil --#OPSGROUP.Element - local cargobayIndex=nil - local reserved=nil - for i,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - for j,_cargo in pairs(element.cargoBay) do - local cargo=_cargo --#OPSGROUP.MyCargo - if cargo.group and cargo.group.groupname==CargoGroup.groupname then - CarrierElement=element - cargobayIndex=j - reserved=cargo.reserved - end - end - end - ]] - + + -- Get cargo bay info. local cargoBayItem, cargoBayIndex, CarrierElement=self:_GetCargobay(CargoGroup) if cargoBayItem and cargoBayIndex then + -- Debug info. + self:T(self.lid..string.format("Removing cargo group %s from cargo bay (index=%d) of carrier %s", CargoGroup:GetName(), cargoBayIndex, CarrierElement.name)) + -- Remove table.remove(CarrierElement.cargoBay, cargoBayIndex) @@ -5163,7 +5136,7 @@ function OPSGROUP:_DelCargobay(CargoGroup) return true end - env.error(self.lid.."ERROR: Group is not in cargo bay. Cannot remove it!") + self:E(self.lid.."ERROR: Group is not in cargo bay. Cannot remove it!") return false end @@ -5320,13 +5293,18 @@ end -- @return #number Free cargo bay in kg. function OPSGROUP:GetFreeCargobay(UnitName, IncludeReserved) + -- Max cargo weight. local weightCargoMax=self:GetWeightCargoMax(UnitName) + -- Current cargo weight. local weightCargo=self:GetWeightCargo(UnitName, IncludeReserved) + -- Free cargo. local Free=weightCargoMax-weightCargo - self:I(self.lid..string.format("Free cargo bay=%d kg (unit=%s)", Free, (UnitName or "whole group"))) + -- Debug info. + self:T(self.lid..string.format("Free cargo bay=%d kg (unit=%s)", Free, (UnitName or "whole group"))) + return Free end @@ -5368,6 +5346,7 @@ end -- @return #number Cargo weight in kg. function OPSGROUP:GetWeightCargo(UnitName, IncludeReserved) + -- Calculate weight based on actual cargo weight. local weight=0 for _,_element in pairs(self.elements) do local element=_element --#OPSGROUP.Element @@ -5380,20 +5359,28 @@ function OPSGROUP:GetWeightCargo(UnitName, IncludeReserved) end + -- Calculate weight from stuff in cargo bay. By default this includes the reserved weight if a cargo group was assigned and is currently boarding. local gewicht=0 for _,_element in pairs(self.elements) do local element=_element --#OPSGROUP.Element if (UnitName==nil or UnitName==element.name) and (element and element.status~=OPSGROUP.ElementStatus.DEAD) then for _,_cargo in pairs(element.cargoBay) do local cargo=_cargo --#OPSGROUP.MyCargo - if (not cargo.reserved) or (cargo.reserved==true and (IncludeReserved==true or IncludeReserved==nil)) then - gewicht=gewicht+cargo.group:GetWeightTotal() + if (not cargo.reserved) or (cargo.reserved==true and (IncludeReserved==true or IncludeReserved==nil)) then + local cargoweight=cargo.group:GetWeightTotal() + gewicht=gewicht+cargoweight + --self:I(self.lid..string.format("unit=%s (reserved=%s): cargo=%s weight=%d, total weight=%d", tostring(UnitName), tostring(IncludeReserved), cargo.group:GetName(), cargoweight, weight)) end end end end + + -- Debug info. + self:T2(self.lid..string.format("Unit=%s (reserved=%s): weight=%d, gewicht=%d", tostring(UnitName), tostring(IncludeReserved), weight, gewicht)) + + -- Quick check. if IncludeReserved==false and gewicht~=weight then - self:I(self.lid..string.format("ERROR: FF weight!=gewicht: weight=%.1f, gewicht=%.1f", weight, gewicht)) + self:E(self.lid..string.format("ERROR: FF weight!=gewicht: weight=%.1f, gewicht=%.1f", weight, gewicht)) end return gewicht @@ -5434,7 +5421,7 @@ function OPSGROUP:AddWeightCargo(UnitName, Weight) element.weightCargo=element.weightCargo+Weight -- Debug info. - self:I(self.lid..string.format("FF %s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg", UnitName, Weight, element.weightCargo)) + self:T(self.lid..string.format("%s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg", UnitName, Weight, element.weightCargo)) -- For airborne units, we set the weight in game. if self.isFlightgroup then @@ -5516,7 +5503,7 @@ end function OPSGROUP:_SetMyCarrier(CarrierGroup, CarrierElement, Reserved) -- Debug info. - self:I(self.lid..string.format("Setting My Carrier: %s (%s), reserved=%s", CarrierGroup:GetName(), tostring(CarrierElement.name), tostring(Reserved))) + self:T(self.lid..string.format("Setting My Carrier: %s (%s), reserved=%s", CarrierGroup:GetName(), tostring(CarrierElement.name), tostring(Reserved))) self.mycarrier.group=CarrierGroup self.mycarrier.element=CarrierElement @@ -5572,6 +5559,7 @@ end -- @param #OPSGROUP self -- @return #OPSGROUP self function OPSGROUP:_RemoveMyCarrier() + self:I(self.lid..string.format("Removing my carrier!")) self.mycarrier.group=nil self.mycarrier.element=nil self.mycarrier.reserved=nil @@ -5635,8 +5623,6 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Add waypoint. if self.isFlightgroup then - - env.info("FF pickup is flightgroup") if airbasePickup then @@ -5651,13 +5637,10 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Activate uncontrolled group. if self:IsParking() then - env.info("FF pickup start uncontrolled while parking at current airbase") self:StartUncontrolled() end else - - env.info("FF pickup land at airbase") -- Order group to land at an airbase. self:LandAtAirbase(airbasePickup) @@ -5669,12 +5652,9 @@ function OPSGROUP:onafterPickup(From, Event, To) --- -- Helo can also land in a zone (NOTE: currently VTOL cannot!) --- - - env.info("FF pickup helo addwaypoint") -- Activate uncontrolled group. if self:IsParking() then - env.info("FF pickup start uncontrolled while parking airbase") self:StartUncontrolled() end @@ -5809,6 +5789,7 @@ end -- @param #OPSGROUP CargoGroup The OPSGROUP loaded as cargo. -- @param #OPSGROUP.Element Carrier The carrier element/unit. function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) + -- Debug info. self:I(self.lid..string.format("Loading group %s", tostring(CargoGroup.groupname))) @@ -5904,6 +5885,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterTransport(From, Event, To) + -- Debug info. self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.TRANSPORTING)) @@ -6107,7 +6089,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) --- -- Issue warning. - env.info("ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") + self:E(self.lid.."ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") --TODO: Dumb into sea. else @@ -6479,7 +6461,6 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) -- Set carrier. As long as the group is not loaded, we only reserve the cargo space.´ CarrierGroup:_AddCargobay(self, Carrier, true) - --self:_SetMyCarrier(CarrierGroup, Carrier, true) end diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 3b5434add..c8d69a5ca 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -32,9 +32,11 @@ -- @field #table cargos Cargos. Each element is a @{Ops.OpsGroup#OPSGROUP.CargoGroup}. -- @field #table carriers Carriers assigned for this transport. -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). +-- @field #boolean urgent If true, transport is urgent. -- @field #number importance Importance of this transport. Smaller=higher. -- @field #number Tstart Start time in *abs.* seconds. -- @field #number Tstop Stop time in *abs.* seconds. Default `#nil` (never stops). +-- @field #number duration Duration (`Tstop-Tstart`) of the transport in seconds. -- @field #table conditionStart Start conditions. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. @@ -70,7 +72,7 @@ -- @field #OPSTRANSPORT OPSTRANSPORT = { ClassName = "OPSTRANSPORT", - verbose = 1, + verbose = 0, cargos = {}, carriers = {}, carrierTransportStatus = {}, @@ -108,12 +110,13 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.7" +OPSTRANSPORT.version="0.0.8" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Stop/abort transport. -- DONE: Add start conditions. -- DONE: Check carrier(s) dead. @@ -140,7 +143,6 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- Defaults. self.uid=_OPSTRANSPORTID - --self.status=OPSTRANSPORT.Status.PLANNING self.pickupzone=Pickupzone self.deployzone=Deployzone @@ -277,7 +279,8 @@ end -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetDisembarkCarriers(Carriers) - self:I(self.lid.."Setting transfer carriers!") + -- Debug info. + self:T(self.lid.."Setting transfer carriers!") -- Create table. self.disembarkCarriers=self.disembarkCarriers or {} @@ -326,7 +329,8 @@ end -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetRequiredCargos(Cargos) - self:I(self.lid.."Setting required cargos!") + -- Debug info. + self:T(self.lid.."Setting required cargos!") -- Create table. self.requiredCargos=self.requiredCargos or {} @@ -390,11 +394,11 @@ function OPSTRANSPORT:_DelCarrier(CarrierGroup) if self:IsCarrier(CarrierGroup) then - for i,_carrier in pairs(self.carriers) do - local carrier=_carrier --Ops.OpsGroup#OPSGROUP + for i=#self.carriers,1,-1 do + local carrier=self.carriers[i] --Ops.OpsGroup#OPSGROUP if carrier.groupname==CarrierGroup.groupname then - self:I(self.lid..string.format("Removing carrier %s", CarrierGroup.groupname)) - table.remove(self.carriers, i) + self:T(self.lid..string.format("Removing carrier %s", CarrierGroup.groupname)) + table.remove(self.carriers, i) end end @@ -704,29 +708,37 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) -- Current FSM state. local fsmstate=self:GetState() - -- Info text. - local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, #self.carriers,self.Ncarrier) + if self.verbose>=1 then - text=text..string.format("\nCargos:") - for _,_cargo in pairs(self.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - local carrier=cargo.opsgroup:_GetMyCarrierElement() - local name=carrier and carrier.name or "none" - local cstate=carrier and carrier.status or "N/A" - text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s]", cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name, cstate) + -- Info text. + local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, #self.carriers,self.Ncarrier) + + -- Info about cargo and carrier. + if self.verbose>=2 then + + text=text..string.format("\nCargos:") + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + local carrier=cargo.opsgroup:_GetMyCarrierElement() + local name=carrier and carrier.name or "none" + local cstate=carrier and carrier.status or "N/A" + text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s], delivered=%s", + cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name, cstate, tostring(cargo.delivered)) + end + + text=text..string.format("\nCarriers:") + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + text=text..string.format("\n- %s: %s [%s], Cargo Bay [current/reserved/total]=%d/%d/%d kg [free %d/%d/%d kg]", + carrier:GetName(), carrier.carrierStatus:upper(), carrier:GetState(), + carrier:GetWeightCargo(nil, false), carrier:GetWeightCargo(), carrier:GetWeightCargoMax(), + carrier:GetFreeCargobay(nil, false), carrier:GetFreeCargobay(), carrier:GetFreeCargobayMax()) + end + end + + self:I(self.lid..text) end - text=text..string.format("\nCarriers:") - for _,_carrier in pairs(self.carriers) do - local carrier=_carrier --Ops.OpsGroup#OPSGROUP - text=text..string.format("\n- %s: %s [%s], Cargo Bay [current/reserved/total]=%d/%d/%d kg [free %d/%d/%d kg]", - carrier:GetName(), carrier.carrierStatus:upper(), carrier:GetState(), - carrier:GetWeightCargo(nil, false), carrier:GetWeightCargo(), carrier:GetWeightCargoMax(), - carrier:GetFreeCargobay(nil, false), carrier:GetFreeCargobay(), carrier:GetFreeCargobayMax()) - end - - self:I(self.lid..text) - -- Check if all cargo was delivered (or is dead). self:_CheckDelivered() From 74bdeaf4f73224c7c813e925f476ce86fe667108 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 2 Jul 2021 23:11:51 +0200 Subject: [PATCH 046/141] AIRWING --- Moose Development/Moose/Functional/Warehouse.lua | 5 ++--- Moose Development/Moose/Ops/AirWing.lua | 11 +++++++---- Moose Development/Moose/Ops/Auftrag.lua | 7 +++++++ Moose Development/Moose/Ops/FlightGroup.lua | 6 +++++- Moose Development/Moose/Ops/OpsGroup.lua | 6 ++++++ Moose Development/Moose/Ops/Squadron.lua | 8 +++++--- 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 1d4e76d2e..cf02c728f 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -2631,9 +2631,8 @@ end --- Check parking ID. -- @param #WAREHOUSE self -- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot. --- @param Wrapper.Airbase#AIRBASE airbase The airbase. -- @return #boolean If true, parking is valid. -function WAREHOUSE:_CheckParkingValid(spot, airbase) +function WAREHOUSE:_CheckParkingValid(spot) if self.parkingIDs==nil then return true @@ -7841,7 +7840,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot -- Check correct terminal type for asset. We don't want helos in shelters etc. - if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingValid(parkingspot, airbase) and airbase:_CheckParkingLists(parkingspot.TerminalID) then + if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingValid(parkingspot) and airbase:_CheckParkingLists(parkingspot.TerminalID) then -- Coordinate of the parking spot. local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 62207fbb6..4b44b6b91 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1429,7 +1429,7 @@ function AIRWING:onafterMissionRequest(From, Event, To, Mission) asset.flightgroup:AddMission(Mission) -- Trigger event. - self:FlightOnMission(asset.flightgroup, Mission) + self:__FlightOnMission(5, asset.flightgroup, Mission) else self:E(self.lid.."ERROR: flight group for asset does NOT exist!") @@ -1662,6 +1662,8 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) local Tacan=squadron:FetchTacan() if Tacan then asset.tacan=Tacan + --flightgroup:SetDefaultTACAN(Tacan,Morse,UnitName,Band,OffSwitch) + flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) end -- Set radio frequency and modulation @@ -1689,19 +1691,19 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) if mission then if Tacan then - mission:SetTACAN(Tacan, Morse, UnitName, Band) + --mission:SetTACAN(Tacan, Morse, UnitName, Band) end -- Add mission to flightgroup queue. asset.flightgroup:AddMission(mission) -- Trigger event. - self:FlightOnMission(flightgroup, mission) + self:__FlightOnMission(5, flightgroup, mission) else if Tacan then - flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + --flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) end end @@ -1743,6 +1745,7 @@ end -- @param #string To To state. function AIRWING:onafterDestroyed(From, Event, To) + -- Debug message. self:I(self.lid.."Airwing warehouse destroyed!") -- Cancel all missions. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 7a952cba8..06183c53d 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1823,6 +1823,8 @@ function AUFTRAG:AssignSquadrons(Squadrons) end self.squadrons=Squadrons + + return self end --- Add a required payload for this mission. Only these payloads will be used for this mission. If they are not available, the mission cannot start. Only available for use with an AIRWING. @@ -1835,12 +1837,14 @@ function AUFTRAG:AddRequiredPayload(Payload) table.insert(self.payloads, Payload) + return self end --- Add a Ops group to the mission. -- @param #AUFTRAG self -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPSGROUP object. +-- @return #AUFTRAG self function AUFTRAG:AddOpsGroup(OpsGroup) self:T(self.lid..string.format("Adding Ops group %s", OpsGroup.groupname)) @@ -1853,11 +1857,13 @@ function AUFTRAG:AddOpsGroup(OpsGroup) self.groupdata[OpsGroup.groupname]=groupdata + return self end --- Remove an Ops group from the mission. -- @param #AUFTRAG self -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPSGROUP object. +-- @return #AUFTRAG self function AUFTRAG:DelOpsGroup(OpsGroup) self:T(self.lid..string.format("Removing OPS group %s", OpsGroup and OpsGroup.groupname or "nil (ERROR)!")) @@ -1870,6 +1876,7 @@ function AUFTRAG:DelOpsGroup(OpsGroup) end + return self end --- Check if mission is PLANNED. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 5534aa11e..7d487a335 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1020,6 +1020,8 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if self:IsAlive() and self.group:IsAirborne(true) then local fuelmin=self:GetFuelMin() + + self:I(self.lid..string.format("Fuel state=%d", fuelmin)) if fuelmin>=self.fuellowthresh then self.fuellow=false @@ -2751,8 +2753,10 @@ end -- @param #string To To state. function FLIGHTGROUP:onafterFuelLow(From, Event, To) + local fuel=self:GetFuelMin() or 0 + -- Debug message. - local text=string.format("Low fuel for flight group %s", self.groupname) + local text=string.format("Low fuel %d for flight group %s", fuel, self.groupname) self:I(self.lid..text) -- Set switch to true. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index fc0022178..29ee92ccd 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -7663,6 +7663,12 @@ function OPSGROUP:GetTACAN() return self.tacan.Channel, self.tacan.Morse, self.tacan.Band, self.tacan.On, self.tacan.BeaconName end +--- Get current TACAN parameters. +-- @param #OPSGROUP self +-- @return #OPSGROUP.Beacon TACAN beacon. +function OPSGROUP:GetBeaconTACAN() + return self.tacan +end --- Set default ICLS parameters. diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index c0f53d960..266fe7f4c 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -302,7 +302,7 @@ function SQUADRON:SetTakeoffType(TakeoffType) return self end ---- Set takeoff type cold (default). +--- Set takeoff type cold (default). All assets of this squadron will be spawned with engines off (cold). -- @param #SQUADRON self -- @return #SQUADRON self function SQUADRON:SetTakeoffCold() @@ -310,7 +310,7 @@ function SQUADRON:SetTakeoffCold() return self end ---- Set takeoff type hot. +--- Set takeoff type hot. All assets of this squadron will be spawned with engines on (hot). -- @param #SQUADRON self -- @return #SQUADRON self function SQUADRON:SetTakeoffHot() @@ -801,7 +801,8 @@ end -- @param #string To To state. function SQUADRON:onafterStop(From, Event, To) - self:I(self.lid.."STOPPING Squadron!") + -- Debug info. + self:I(self.lid.."STOPPING Squadron and removing all assets!") -- Remove all assets. for i=#self.assets,1,-1 do @@ -809,6 +810,7 @@ function SQUADRON:onafterStop(From, Event, To) self:DelAsset(asset) end + -- Clear call scheduler. self.CallScheduler:Clear() end From 464fde0ed269ecf42fb7220fa681cb0f46f74a3c Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 6 Jul 2021 21:13:50 +0200 Subject: [PATCH 047/141] OPS - MSRS - Callsign --- Moose Development/Moose/Ops/FlightGroup.lua | 3 - Moose Development/Moose/Ops/OpsGroup.lua | 101 +++++++++++++++++--- 2 files changed, 87 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 7d487a335..05fc533dd 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -53,8 +53,6 @@ -- @field #number Tparking Abs. mission time stamp when the group was spawned uncontrolled and is parking. -- @field #table menu F10 radio menu. -- @field #string controlstatus Flight control status. --- @field #number callsignName Callsign name. --- @field #number callsignNumber Callsign number. -- @field #boolean despawnAfterLanding If true, group is despawned after landed at an airbase. -- @field #number RTBRecallCount Number that counts RTB calls. -- @@ -2949,7 +2947,6 @@ function FLIGHTGROUP:_InitGroup() end self.callsign.NumberSquad=callsign[1] self.callsign.NumberGroup=callsign[2] - self.callsign.NumberElement=callsign[3] -- First element only self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) -- Set default formation. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 29ee92ccd..97fd17248 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -100,6 +100,8 @@ -- -- @field #OPSGROUP.Callsign callsign Current callsign settings. -- @field #OPSGROUP.Callsign callsignDefault Default callsign settings. +-- @field #string callsignName Callsign name. +-- @field #string callsignAlias Callsign alias. -- -- @field #OPSGROUP.Spot spot Laser and IR spot. -- @@ -118,6 +120,9 @@ -- @field #number cargocounter Running number of cargo UIDs. -- @field #OPSGROUP.CarrierLoader carrierLoader Carrier loader parameters. -- @field #OPSGROUP.CarrierLoader carrierUnloader Carrier unloader parameters. +-- +-- @field #boolean useSRS Use SRS for transmissions. +-- @field Sound.SRS#MSRS msrs MOOSE SRS wrapper. -- -- @extends Core.Fsm#FSM @@ -309,9 +314,7 @@ OPSGROUP.TaskType={ -- @type OPSGROUP.Callsign -- @field #number NumberSquad Squadron number corresponding to a name like "Uzi". -- @field #number NumberGroup Group number. First number after name, e.g. "Uzi-**1**-1". --- @field #number NumberElement Element number.Second number after name, e.g. "Uzi-1-**1**" -- @field #string NameSquad Name of the squad, e.g. "Uzi". --- @field #string NameElement Name of group element, e.g. Uzi 11. --- Option data. -- @type OPSGROUP.Option @@ -1473,6 +1476,53 @@ function OPSGROUP:SelfDestruction(Delay, ExplosionPower) return self end +--- Use SRS Simple-Text-To-Speech for transmissions. +-- @param #OPSGROUP self +-- @param #string PathToSRS Path to SRS directory. +-- @param #string Gender Gender: "male" or "female" (default). +-- @param #string Culture Culture, e.g. "en-GB" (default). +-- @param #string Voice Specific voice. Overrides `Gender` and `Culture`. +-- @param #number Port SRS port. Default 5002. +-- @return #OPSGROUP self +function OPSGROUP:SetSRS(PathToSRS, Gender, Culture, Voice, Port) + self.useSRS=true + self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation) + self.msrs:SetGender(Gender) + self.msrs:SetCulture(Culture) + self.msrs:SetVoice(Voice) + self.msrs:SetPort(Port) + self.msrs:SetCoalition(self:GetCoalition()) + return self +end + +--- Send a radio transmission via SRS Text-To-Speech. +-- @param #OPSGROUP self +-- @param #string Text Text of transmission. +-- @param #number Delay Delay in seconds before the transmission is started. +-- @return #OPSGROUP self +function OPSGROUP:RadioTransmission(Text, Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.RadioTransmission, self, Text, 0) + else + + if self.useSRS and self.msrs then + + local freq, modu, radioon=self:GetRadio() + + self.msrs:SetFrequencies(freq) + self.msrs:SetModulations(modu) + + self:I(self.lid..string.format("Radio transmission on %.3f MHz %s: %s", freq, UTILS.GetModulationName(modu), Text)) + + self.msrs:PlayText(Text) + end + + end + + return self +end + --- Set that this carrier is an all aspect loader. -- @param #OPSGROUP self -- @param #number Length Length of loading zone in meters. Default 50 m. @@ -3326,6 +3376,9 @@ function OPSGROUP:onafterMissionStart(From, Event, To, Mission) -- Set group mission status to STARTED. Mission:SetGroupStatus(self, AUFTRAG.GroupStatus.STARTED) + + -- + -- Set mission status to STARTED. Mission:__Started(3) @@ -7941,13 +7994,14 @@ end --- Set default callsign. -- @param #OPSGROUP self -- @param #number CallsignName Callsign name. --- @param #number CallsignNumber Callsign number. +-- @param #number CallsignNumber Callsign number. Default 1. -- @return #OPSGROUP self function OPSGROUP:SetDefaultCallsign(CallsignName, CallsignNumber) - self.callsignDefault={} + self.callsignDefault={} --#OPSGROUP.Callsign self.callsignDefault.NumberSquad=CallsignName self.callsignDefault.NumberGroup=CallsignNumber or 1 + self.callsignDefault.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) return self end @@ -7963,6 +8017,7 @@ function OPSGROUP:SwitchCallsign(CallsignName, CallsignNumber) -- Set default callsign. We switch to this when group is spawned. self:SetDefaultCallsign(CallsignName, CallsignNumber) + --self.callsign=UTILS.DeepCopy(self.callsignDefault) elseif self:IsAlive() then @@ -7978,9 +8033,21 @@ function OPSGROUP:SwitchCallsign(CallsignName, CallsignNumber) -- Give command to change the callsign. self.group:CommandSetCallsign(self.callsign.NumberSquad, self.callsign.NumberGroup) + + -- Callsign of the group, e.g. Colt-1 + self.callsignName=UTILS.GetCallsignName(self.callsign.NumberSquad).."-"..self.callsign.NumberGroup + self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) + + -- Set callsign of elements. + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if element.status~=OPSGROUP.ElementStatus.DEAD then + element.callsign=element.unit:GetCallsign() + end + end else - --TODO: Error + self:E(self.lid.."ERROR: Group is not alive and not in utero! Cannot switch callsign") end return self @@ -8815,10 +8882,10 @@ function OPSGROUP:_AddElementByName(unitname) if unit then - -- TODO: this is wrong when grouping is used! + -- Get unit template. local unittemplate=unit:GetTemplate() - + -- Element table. local element={} --#OPSGROUP.Element -- Name and status. @@ -8828,7 +8895,10 @@ function OPSGROUP:_AddElementByName(unitname) -- Unit and group. element.unit=unit element.DCSunit=Unit.getByName(unitname) + element.gid=element.DCSunit:getNumber() + element.uid=element.DCSunit:getID() element.group=unit:GetGroup() + element.opsgroup=self -- Skill etc. element.skill=unittemplate.skill or "Unknown" @@ -8841,11 +8911,10 @@ function OPSGROUP:_AddElementByName(unitname) -- Descriptors and type/category. element.descriptors=unit:GetDesc() - --self:I({desc=element.descriptors}) - element.category=unit:GetUnitCategory() element.categoryname=unit:GetCategoryName() element.typename=unit:GetTypeName() + --self:I({desc=element.descriptors}) -- Ammo. element.ammo0=self:GetAmmoUnit(unit, false) @@ -8880,6 +8949,7 @@ function OPSGROUP:_AddElementByName(unitname) unit:SetCargoBayWeightLimit() element.weightMaxCargo=unit.__.CargoBayWeightLimit + -- Cargo bay (empty). element.cargoBay={} -- FLIGHTGROUP specific. @@ -8891,6 +8961,14 @@ function OPSGROUP:_AddElementByName(unitname) element.fuelmass0=unittemplate.payload and unittemplate.payload.fuel or 0 element.fuelmass=element.fuelmass0 element.fuelrel=element.unit:GetFuel() + else + element.callsign="Peter-1-1" + element.modex="000" + element.payload={} + element.pylons={} + element.fuelmass0=99999 + element.fuelmass =99999 + element.fuelrel=1 end -- Debug text. @@ -8899,11 +8977,6 @@ function OPSGROUP:_AddElementByName(unitname) element.size, element.length, element.height, element.width, element.weight, element.weightMaxTotal, element.weightCargo, element.weightMaxCargo) self:T(self.lid..text) - -- Debug text. - --local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s", - --element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel*100, element.category, element.categoryname, element.callsign, tostring(element.ai)) - --self:T(self.lid..text) - -- Add element to table. table.insert(self.elements, element) From be2d1d78953c56e475cf2ec32d5cc85c98aeafce Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 7 Jul 2021 11:33:51 +0200 Subject: [PATCH 048/141] Update OpsTransport.lua --- Moose Development/Moose/Ops/OpsTransport.lua | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index c8d69a5ca..816813ac7 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -802,10 +802,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP OpsGroup OPSGROUP that was loaded into a carrier. --- @param Ops.OpsGroup#OPSGROUP.Element Carrier Carrier element. -function OPSTRANSPORT:onafterLoaded(From, Event, To, OpsGroup, Carrier) - self:I(self.lid..string.format("Loaded OPSGROUP %s into carrier %s", OpsGroup:GetName(), tostring(Carrier.name))) +-- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was loaded into a carrier. +-- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier OPSGROUP that was loaded into a carrier. +-- @param Ops.OpsGroup#OPSGROUP.Element CarrierElement Carrier element. +function OPSTRANSPORT:onafterLoaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier, CarrierElement) + self:I(self.lid..string.format("Loaded OPSGROUP %s into carrier %s", OpsGroupCargo:GetName(), tostring(CarrierElement.name))) end --- On after "Unloaded" event. @@ -813,9 +814,10 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP OpsGroup OPSGROUP that was unloaded from a carrier. -function OPSTRANSPORT:onafterUnloaded(From, Event, To, OpsGroup) - self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroup:GetName())) +-- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was unloaded from a carrier. +-- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. +function OPSTRANSPORT:onafterUnloaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier) + self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroupCargo:GetName())) end --- On after "DeadCarrierGroup" event. From fc1adf3b94ee8fc5cc455228f119f576dbe4dfd7 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 8 Jul 2021 12:21:10 +0200 Subject: [PATCH 049/141] OPS Transport --- Moose Development/Moose/Core/Spawn.lua | 21 ++--- Moose Development/Moose/Ops/NavyGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 71 ++++++++++++---- Moose Development/Moose/Ops/OpsTransport.lua | 88 ++++++++++++++++++-- 4 files changed, 144 insertions(+), 38 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index d25186b66..272dc386d 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -2423,8 +2423,7 @@ end -- @param #SPAWN self -- @param DCS#Vec3 Vec3 The Vec3 coordinates where to spawn the group. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) @@ -2493,8 +2492,7 @@ end -- @param #SPAWN self -- @param Core.Point#Coordinate Coordinate The Coordinate coordinates where to spawn the group. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) @@ -2510,8 +2508,7 @@ end -- @param #SPAWN self -- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 coordinates where to spawn the group. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage -- -- local SpawnPointVec3 = ZONE:New( ZoneName ):GetPointVec3( 2000 ) -- Get the center of the ZONE object at 2000 meters from the ground. @@ -2535,8 +2532,7 @@ end -- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. -- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage -- -- local SpawnVec2 = ZONE:New( ZoneName ):GetVec2() @@ -2569,8 +2565,7 @@ end -- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. -- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage -- -- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2() @@ -2626,8 +2621,7 @@ end -- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. -- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage -- -- local SpawnStatic = STATIC:FindByName( StaticName ) @@ -2658,8 +2652,7 @@ end -- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. -- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil when nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage -- -- local SpawnZone = ZONE:New( ZoneName ) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index d78b0a6a5..0310292ac 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1287,7 +1287,7 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx) local los=LoS(x) -- Debug message. - self:I(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los))) + self:T(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los))) if los and d<=eps then return x diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 97fd17248..65060db9e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4675,7 +4675,8 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) self:ScheduleOnce(Delay, OPSGROUP._Respawn, self, 0, Template, Reset) else - self:I(self.lid.."FF _Respawn") + -- Debug message. + self:T2(self.lid.."FF _Respawn") -- Given template or get old. Template=Template or UTILS.DeepCopy(self.template) @@ -5612,7 +5613,7 @@ end -- @param #OPSGROUP self -- @return #OPSGROUP self function OPSGROUP:_RemoveMyCarrier() - self:I(self.lid..string.format("Removing my carrier!")) + self:T(self.lid..string.format("Removing my carrier!")) self.mycarrier.group=nil self.mycarrier.element=nil self.mycarrier.reserved=nil @@ -5723,16 +5724,50 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Navy Group - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true + local cwp=self:GetWaypointCurrent() + local uid=cwp and cwp.uid or nil + -- Get a (random) pre-defined transport path. + local path=self.cargoTransport:_GetPathPickup() + + if path then + -- Loop over coordinates. + for i,coordinate in pairs(path) do + local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true + uid=waypoint.uid + --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) + end + end + + -- NAVYGROUP + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=true + + -- Give cruise command. self:__Cruise(-2) + elseif self.isArmygroup then -- Army Group - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true + local cwp=self:GetWaypointCurrent() + local uid=cwp and cwp.uid or nil + -- Get a (random) pre-defined transport path. + local path=self.cargoTransport:_GetPathPickup() + + if path then + -- Loop over coordinates. + for i,coordinate in pairs(path) do + local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true + uid=waypoint.uid + --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) + end + end + + -- ARMYGROUP + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=true + self:__Cruise(-2) end @@ -5883,7 +5918,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) -- Trigger "Loaded" event for current cargo transport. if self.cargoTransport then - self.cargoTransport:Loaded(CargoGroup, carrier) + self.cargoTransport:Loaded(CargoGroup, self, carrier) else self:E(self.lid..string.format("WARNING: Loaded cargo but no current OPSTRANSPORT assignment!")) end @@ -6034,15 +6069,18 @@ function OPSGROUP:onafterTransport(From, Event, To) elseif self.isArmygroup then + local cwp=self:GetWaypointCurrent() + local uid=cwp and cwp.uid or nil + local path=self.cargoTransport:_GetPathTransport() + if path then - - for _,_zone in pairs(path.zones:GetSet()) do - local zone=_zone --Core.Zone#ZONE - local coordinate=zone:GetRandomCoordinate(nil, nil, nil) --TODO: surface type land - local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.temp=true + -- Loop over coordinates. + for i,coordinate in pairs(path) do + local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true + uid=waypoint.uid + --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) end - end -- ARMYGROUP @@ -6060,12 +6098,11 @@ function OPSGROUP:onafterTransport(From, Event, To) local path=self.cargoTransport:_GetPathTransport() if path then - -- Loop over zones + -- Loop over coordinates. for i,coordinate in pairs(path) do - local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) - waypoint.temp=true + local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true uid=waypoint.uid - coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) + --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) end end @@ -6184,7 +6221,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) end -- Trigger "Unloaded" event for current cargo transport - self.cargoTransport:Unloaded(cargo.opsgroup) + self.cargoTransport:Unloaded(cargo.opsgroup, self) end @@ -7236,7 +7273,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Debug message. local text=string.format("Group passing waypoint uid=%d", uid) - opsgroup:I(opsgroup.lid..text) + opsgroup:T(opsgroup.lid..text) -- Trigger PassingWaypoint event. if waypoint.temp then diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 816813ac7..0ef309150 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -50,7 +50,8 @@ -- @field #number Ncargo Total number of cargo groups. -- @field #number Ncarrier Total number of assigned carriers. -- @field #number Ndelivered Total number of cargo groups delivered. --- @field #table pathsTransport Paths of `#OPSGROUP.Path`. +-- @field #table pathsTransport Transport paths of `#OPSGROUP.Path`. +-- @field #table pathsPickup Pickup paths of `#OPSGROUP.Path`. -- @extends Core.Fsm#FSM --- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill @@ -78,6 +79,7 @@ OPSTRANSPORT = { carrierTransportStatus = {}, conditionStart = {}, pathsTransport = {}, + pathsPickup = {}, requiredCargos = {}, } @@ -203,7 +205,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) -- We got a single GROUP or OPSGROUP object. local cargo=self:_CreateCargoGroupData(GroupSet) - if cargo then --and self:CanCargo(cargo.opsgroup) + if cargo then table.insert(self.cargos, cargo) self.Ncargo=self.Ncargo+1 end @@ -530,10 +532,11 @@ end --- Add path used for transportation from the pickup to the deploy zone. If multiple paths are defined, a random one is chosen. -- @param #OPSTRANSPORT self -- @param Wrapper.Group#GROUP PathGroup A (late activated) GROUP defining a transport path by their waypoints. +-- @param #boolean Reversed If `true`, add waypoints of group in reversed order. -- @param #number Radius Randomization radius in meters. Default 0 m. -- @param #number Altitude Altitude in feet AGL. Only for aircraft. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:AddPathTransport(PathGroup, Radius, Altitude) +function OPSTRANSPORT:AddPathTransport(PathGroup, Reversed, Radius, Altitude) local path={} --#OPSTRANSPORT.Path path.coords={} @@ -543,11 +546,21 @@ function OPSTRANSPORT:AddPathTransport(PathGroup, Radius, Altitude) -- Get route points. local waypoints=PathGroup:GetTaskRoute() - for _,wp in pairs(waypoints) do - local coord=COORDINATE:New(wp.x, wp.alt, wp.y) - table.insert(path.coords, coord) + if Reversed then + for i=#waypoints,1,-1 do + local wp=waypoints[i] + local coord=COORDINATE:New(wp.x, wp.alt, wp.y) + table.insert(path.coords, coord) + end + else + for i=1,#waypoints do + local wp=waypoints[i] + local coord=COORDINATE:New(wp.x, wp.alt, wp.y) + table.insert(path.coords, coord) + end end + -- Add path. table.insert(self.pathsTransport, path) @@ -580,6 +593,69 @@ function OPSTRANSPORT:_GetPathTransport() end +--- Add path used to go to the pickup zone. If multiple paths are defined, a random one is chosen. +-- @param #OPSTRANSPORT self +-- @param Wrapper.Group#GROUP PathGroup A (late activated) GROUP defining a transport path by their waypoints. +-- @param #boolean Reversed If `true`, add waypoints of group in reversed order. +-- @param #number Radius Randomization radius in meters. Default 0 m. +-- @param #number Altitude Altitude in feet AGL. Only for aircraft. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:AddPathPickup(PathGroup, Reversed, Radius, Altitude) + + local path={} --#OPSTRANSPORT.Path + path.coords={} + path.radius=Radius or 0 + path.altitude=Altitude + + -- Get route points. + local waypoints=PathGroup:GetTaskRoute() + + if Reversed then + for i=#waypoints,1,-1 do + local wp=waypoints[i] + local coord=COORDINATE:New(wp.x, wp.alt, wp.y) + table.insert(path.coords, coord) + end + else + for i=1,#waypoints do + local wp=waypoints[i] + local coord=COORDINATE:New(wp.x, wp.alt, wp.y) + table.insert(path.coords, coord) + end + end + + -- Add path. + table.insert(self.pathsPickup, path) + + return self +end + +--- Get a path for pickup. +-- @param #OPSTRANSPORT self +-- @return #table The path of COORDINATEs. +function OPSTRANSPORT:_GetPathPickup() + + if self.pathsPickup and #self.pathsPickup>0 then + + -- Get a random path for transport. + local path=self.pathsPickup[math.random(#self.pathsPickup)] --#OPSTRANSPORT.Path + + + local coordinates={} + for _,coord in ipairs(path.coords) do + + -- TODO: Add randomization. + + table.insert(coordinates, coord) + end + + return coordinates + end + + return nil +end + + --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. From 6dfd757ea1979b2090378937153b4c8fb6dec970 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 8 Jul 2021 22:59:29 +0200 Subject: [PATCH 050/141] OPS --- Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 2ca5c370a..205141623 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2396,7 +2396,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Set holding flag to 0=false. self.flaghold:Set(0) - local holdtime=5*60 + local holdtime=1*60 if fc or self.airboss then holdtime=nil end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 65060db9e..901bdebc0 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1713,24 +1713,28 @@ end -- @param #OPSGROUP self -- @return #boolean If true, group is not spawned yet. function OPSGROUP:IsInUtero() - return self:Is("InUtero") + local is=self:Is("InUtero") + return is end --- Check if group is in state spawned. -- @param #OPSGROUP self -- @return #boolean If true, group is spawned. function OPSGROUP:IsSpawned() - return self:Is("Spawned") + local is=self:Is("Spawned") + return is end --- Check if group is dead. -- @param #OPSGROUP self -- @return #boolean If true, all units/elements of the group are dead. function OPSGROUP:IsDead() + --env.info("FF IsDead") if self.isDead then return true else - return self:Is("Dead") + local is=self:Is("Dead") + return is end end @@ -1745,7 +1749,8 @@ end -- @param #OPSGROUP self -- @return #boolean If true, FSM state is stopped. function OPSGROUP:IsStopped() - return self:Is("Stopped") + local is=self:Is("Stopped") + return is end --- Check if this group is currently "uncontrolled" and needs to be "started" to begin its route. @@ -1781,14 +1786,16 @@ end -- @param #OPSGROUP self -- @return #boolean If true, group is retreating. function OPSGROUP:IsRetreating() - return self:is("Retreating") + local is=self:is("Retreating") + return is end --- Check if the group is engaging another unit or group. -- @param #OPSGROUP self -- @return #boolean If true, group is engaging. function OPSGROUP:IsEngaging() - return self:is("Engaging") + local is=self:is("Engaging") + return is end --- Check if the group is not a carrier yet. @@ -2358,7 +2365,7 @@ function OPSGROUP:OnEventBirth(EventData) self.destbase=self.homebase end - self:I(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned", unitname, self.homebase and self.homebase:GetName() or "unknown")) + self:I(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned", unitname, self.currbase and self.currbase:GetName() or "unknown")) else self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) end From 268eb1d60dbcbc891d8b4e0b68099b1db53cadce Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 11 Jul 2021 18:15:25 +0200 Subject: [PATCH 051/141] OPS --- Moose Development/Moose/Core/Database.lua | 6 +- Moose Development/Moose/Core/Point.lua | 6 +- Moose Development/Moose/Core/Set.lua | 2 +- Moose Development/Moose/Ops/Airboss.lua | 2 +- Moose Development/Moose/Ops/ArmyGroup.lua | 15 +- Moose Development/Moose/Ops/FlightGroup.lua | 210 +++++++++++++++---- Moose Development/Moose/Ops/NavyGroup.lua | 9 +- Moose Development/Moose/Ops/OpsGroup.lua | 188 ++++++++++------- Moose Development/Moose/Ops/OpsTransport.lua | 77 +++++-- 9 files changed, 358 insertions(+), 157 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 6b19d7ae3..df836f7dd 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1478,7 +1478,7 @@ end -- @param #DATABASE self -- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group added to the DB. function DATABASE:AddOpsGroup(opsgroup) - env.info("Adding OPSGROUP "..tostring(opsgroup.groupname)) + --env.info("Adding OPSGROUP "..tostring(opsgroup.groupname)) self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup end @@ -1494,7 +1494,7 @@ function DATABASE:GetOpsGroup(groupname) groupname=groupname:GetName() end - env.info("Getting OPSGROUP "..tostring(groupname)) + --env.info("Getting OPSGROUP "..tostring(groupname)) return self.FLIGHTGROUPS[groupname] end @@ -1510,7 +1510,7 @@ function DATABASE:FindOpsGroup(groupname) groupname=groupname:GetName() end - env.info("Getting OPSGROUP "..tostring(groupname)) + --env.info("Getting OPSGROUP "..tostring(groupname)) return self.FLIGHTGROUPS[groupname] end diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index b78ade334..1c28e0730 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -647,7 +647,8 @@ do -- COORDINATE local y=X*math.sin(phi)+Y*math.cos(phi) -- Coordinate assignment looks bit strange but is correct. - return COORDINATE:NewFromVec3({x=y, y=self.y, z=x}) + local coord=COORDINATE:NewFromVec3({x=y, y=self.y, z=x}) + return coord end --- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. @@ -690,7 +691,8 @@ do -- COORDINATE function COORDINATE:GetRandomCoordinateInRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) - return COORDINATE:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) + local coord=COORDINATE:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) + return coord end diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 7dfdfca49..c978d8f85 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -6112,7 +6112,7 @@ do -- SET_OPSGROUP -- @param Core.Base#BASE Object The object itself. -- @return Core.Base#BASE The added BASE Object. function SET_OPSGROUP:Add(ObjectName, Object) - self:I( { ObjectName = ObjectName, Object = Object } ) + self:T( { ObjectName = ObjectName, Object = Object } ) -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set if self.Set[ObjectName] then diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 71a6087fe..3133cd715 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -6134,7 +6134,7 @@ function AIRBOSS:_ScanCarrierZone() local flight=_DATABASE:GetOpsGroup(groupname) if flight and flight:IsInbound() and flight.destbase:GetName()==self.carrier:GetName() then - if flight.ishelo then + if flight.isHelo then else putintomarshal=true end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index f2953ede0..d8b506e64 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -38,7 +38,7 @@ -- @field Core.Set#SET_ZONE retreatZones Set of retreat zones. -- @extends Ops.OpsGroup#OPSGROUP ---- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge +--- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B Sledge -- -- === -- @@ -55,17 +55,6 @@ ARMYGROUP = { engage = {}, } ---- Army group element. --- @type ARMYGROUP.Element --- @field #string name Name of the element, i.e. the unit. --- @field Wrapper.Unit#UNIT unit The UNIT object. --- @field #string status The element status. --- @field #string typename Type name. --- @field #number length Length of element in meters. --- @field #number width Width of element in meters. --- @field #number height Height of element in meters. --- @extends Ops.OpsGroup#OPSGROUP.Element - --- Target -- @type ARMYGROUP.Target -- @field Ops.Target#TARGET Target The target. @@ -73,7 +62,7 @@ ARMYGROUP = { --- Army Group version. -- @field #string version -ARMYGROUP.version="0.4.0" +ARMYGROUP.version="0.7.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 205141623..fa3aec358 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -173,17 +173,11 @@ FLIGHTGROUP.Attribute = { --- Flight group element. -- @type FLIGHTGROUP.Element --- @field #string modex Tail number. --- @field Wrapper.Client#CLIENT client The client if element is occupied by a human player. --- @field #table pylons Table of pylons. --- @field #number fuelmass Mass of fuel in kg. --- @field #string callsign Call sign, e.g. "Uzi 1-1". --- @field Wrapper.Airbase#AIRBASE.ParkingSpot parking The parking spot table the element is parking on. -- @extends Ops.OpsGroup#OPSGROUP.Element --- FLIGHTGROUP class version. -- @field #string version -FLIGHTGROUP.version="0.6.1" +FLIGHTGROUP.version="0.7.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -278,6 +272,7 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "Taxiing", "Taxiing") -- The whole flight group is taxiing. self:AddTransition("*", "Takeoff", "Airborne") -- The whole flight group is airborne. self:AddTransition("*", "Airborne", "Airborne") -- The whole flight group is airborne. + self:AddTransition("*", "Cruise", "Cruising") -- The whole flight group is cruising. self:AddTransition("*", "Landing", "Landing") -- The whole flight group is landing. self:AddTransition("*", "Landed", "Landed") -- The whole flight group has landed. self:AddTransition("*", "Arrived", "Arrived") -- The whole flight group has arrived. @@ -600,11 +595,18 @@ function FLIGHTGROUP:IsTaxiing() return self:Is("Taxiing") end ---- Check if flight is airborne. +--- Check if flight is airborne or cruising. -- @param #FLIGHTGROUP self -- @return #boolean If true, flight is airborne. function FLIGHTGROUP:IsAirborne() - return self:Is("Airborne") + return self:Is("Airborne") or self:Is("Cruising") +end + +--- Check if flight is airborne or cruising. +-- @param #FLIGHTGROUP self +-- @return #boolean If true, flight is airborne. +function FLIGHTGROUP:IsCruising() + return self:Is("Cruising") end --- Check if flight is waiting after passing final waypoint. @@ -732,7 +734,7 @@ function FLIGHTGROUP:StartUncontrolled(delay) self:Activate() _delay=1 end - self:I(self.lid.."Starting uncontrolled group") + self:T(self.lid.."Starting uncontrolled group") self.group:StartUncontrolled(_delay) self.isUncontrolled=false else @@ -804,7 +806,7 @@ function FLIGHTGROUP:onbeforeStatus(From, Event, To) -- First we check if elements are still alive. Could be that they were despawned without notice, e.g. when landing on a too small airbase. for i,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element -- Check that element is not already dead or not yet alive. if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then @@ -881,7 +883,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- Check if flight began to taxi (if it was parking). if self:IsParking() then for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element if element.parking then -- Get distance to assigned parking spot. @@ -927,7 +929,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if self.verbose>=2 then local text="Elements:" for i,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element local name=element.name local status=element.status @@ -974,7 +976,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) local TmaxFuel=math.huge for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element -- Get relative fuel of element. local fuel=element.unit:GetFuel() or 0 @@ -1019,7 +1021,8 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) local fuelmin=self:GetFuelMin() - self:I(self.lid..string.format("Fuel state=%d", fuelmin)) + -- Debug info. + self:T2(self.lid..string.format("Fuel state=%d", fuelmin)) if fuelmin>=self.fuellowthresh then self.fuellow=false @@ -1365,8 +1368,10 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element) + + -- Debug info. self:T(self.lid..string.format("Element spawned %s", Element.name)) -- Set element status. @@ -1391,6 +1396,7 @@ function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element) self:__ElementParking(0.11, Element) end end + end --- On after "ElementParking" event. @@ -1398,9 +1404,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. -- @param Wrapper.Airbase#AIRBASE.ParkingSpot Spot Parking Spot. function FLIGHTGROUP:onafterElementParking(From, Event, To, Element, Spot) + + -- Debug info. self:T(self.lid..string.format("Element parking %s at spot %s", Element.name, Element.parking and tostring(Element.parking.TerminalID) or "N/A")) -- Set element status. @@ -1417,6 +1425,7 @@ function FLIGHTGROUP:onafterElementParking(From, Event, To, Element, Spot) elseif self:IsTakeoffRunway() then self:__ElementEngineOn(0.5, Element) end + end --- On after "ElementEngineOn" event. @@ -1424,7 +1433,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementEngineOn(From, Event, To, Element) -- Debug info. @@ -1432,6 +1441,7 @@ function FLIGHTGROUP:onafterElementEngineOn(From, Event, To, Element) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.ENGINEON) + end --- On after "ElementTaxiing" event. @@ -1439,7 +1449,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementTaxiing(From, Event, To, Element) -- Get terminal ID. @@ -1453,6 +1463,7 @@ function FLIGHTGROUP:onafterElementTaxiing(From, Event, To, Element) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.TAXIING) + end --- On after "ElementTakeoff" event. @@ -1460,7 +1471,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. -- @param Wrapper.Airbase#AIRBASE airbase The airbase if applicable or nil. function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase) self:T(self.lid..string.format("Element takeoff %s at %s airbase.", Element.name, airbase and airbase:GetName() or "unknown")) @@ -1475,6 +1486,7 @@ function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase) -- Trigger element airborne event. self:__ElementAirborne(0.1, Element) + end --- On after "ElementAirborne" event. @@ -1482,12 +1494,15 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementAirborne(From, Event, To, Element) + + -- Debug info. self:T2(self.lid..string.format("Element airborne %s", Element.name)) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.AIRBORNE) + end --- On after "ElementLanded" event. @@ -1495,9 +1510,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. -- @param Wrapper.Airbase#AIRBASE airbase The airbase if applicable or nil. function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) + + -- Debug info. self:T2(self.lid..string.format("Element landed %s at %s airbase", Element.name, airbase and airbase:GetName() or "unknown")) if self.despawnAfterLanding then @@ -1523,6 +1540,7 @@ function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) end end + end --- On after "ElementArrived" event. @@ -1530,7 +1548,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. -- @param Wrapper.Airbase#AIRBASE airbase The airbase, where the element arrived. -- @param Wrapper.Airbase#AIRBASE.ParkingSpot Parking The Parking spot the element has. function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element, airbase, Parking) @@ -1548,7 +1566,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementDestroyed(From, Event, To, Element) -- Call OPSGROUP function. @@ -1561,7 +1579,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) -- Call OPSGROUP function. @@ -1577,7 +1595,7 @@ function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) end ---- On after "Spawned" event. Sets the template, initializes the waypoints. +--- On after "Spawned" event. -- @param #FLIGHTGROUP self -- @param #string From From state. -- @param #string Event Event. @@ -1701,6 +1719,7 @@ function FLIGHTGROUP:onafterTaxiing(From, Event, To) else -- Human flights go to TAXI OUT queue. They will go to the ready for takeoff queue when they request it. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAXIOUT) + -- Update menu. self:_UpdateMenu() end @@ -1736,8 +1755,30 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) -- No current airbase any more. self.currbase=nil + + -- Cruising. + self:__Cruise(-0.05) + +end + +--- On after "Cruising" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function FLIGHTGROUP:onafterCruise(From, Event, To) + self:T(self.lid..string.format("Flight cruising")) + + -- Not waiting anymore. + self.Twaiting=nil + self.dTwait=nil if self.isAI then + + --- + -- AI + --- + if self:IsTransporting() then if self.cargoTransport and self.cargoTransport.deployzone and self.cargoTransport.deployzone:IsInstanceOf("ZONE_AIRBASE") then local airbase=self.cargoTransport.deployzone:GetAirbase() @@ -1749,11 +1790,19 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) self:LandAtAirbase(airbase) end else - self:_CheckGroupDone() + self:_CheckGroupDone(nil, 120) end + else + + --- + -- CLIENT + --- + self:_UpdateMenu() + end + end --- On after "Landing" event. @@ -1944,7 +1993,7 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) local allowed=true local trepeat=nil - if self:IsAlive() then -- and (self:IsAirborne() or self:IsWaiting() or self:IsInbound() or self:IsHolding()) then + if self:IsAlive() then -- Alive & Airborne ==> Update route possible. self:T3(self.lid.."Update route possible. Group is ALIVE") elseif self:IsDead() then @@ -2117,7 +2166,8 @@ end -- -- @param #FLIGHTGROUP self -- @param #number delay Delay in seconds. -function FLIGHTGROUP:_CheckGroupDone(delay) +-- @param #number waittime Time to wait if group is done. +function FLIGHTGROUP:_CheckGroupDone(delay, waittime) if self:IsAlive() and self.isAI then @@ -2127,7 +2177,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay) else -- Debug info. - self:T(self.lid.."Check group done?") + self:T(self.lid.."Check FLIGHTGROUP done?") -- First check if there is a paused mission that if self.missionpaused then @@ -2146,6 +2196,12 @@ function FLIGHTGROUP:_CheckGroupDone(delay) self:T(self.lid.."Landing at airbase! Group NOT done...") return end + + -- Group is waiting. + if self.Twaiting then + self:T(self.lid.."Waiting! Group NOT done...") + return + end -- Number of tasks remaining. local nTasks=self:CountRemainingTasks() @@ -2161,6 +2217,10 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Final waypoint passed? if self.passedfinalwp then + + --- + -- Final Waypoint PASSED + --- -- Got current mission or task? if self.currentmission==nil and self.taskcurrent==0 and self.cargoTransport==nil then @@ -2172,13 +2232,16 @@ function FLIGHTGROUP:_CheckGroupDone(delay) local destzone=self.destzone or self.homezone -- Send flight to destination. - if destbase then + if waittime then + self:T(self.lid..string.format("Passed Final WP and No current and/or future missions/tasks/transports. Waittime given ==> Waiting for %d sec!", waittime)) + self:Wait(waittime) + elseif destbase then self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") --self:RTB(destbase) self:__RTB(-0.1, destbase) elseif destzone then self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTZ!") - self:__RTZ(-3, destzone) + self:__RTZ(-0.1, destzone) else self:T(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!") self:__Wait(-1) @@ -2192,8 +2255,17 @@ function FLIGHTGROUP:_CheckGroupDone(delay) self:T(self.lid..string.format("Passed Final WP but still have current Task (#%s) or Mission (#%s) left to do", tostring(self.taskcurrent), tostring(self.currentmission))) end else + + --- + -- Final Waypoint NOT PASSED + --- + + -- Debug info. self:T(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route", self:GetState())) + + -- Update route. self:__UpdateRoute(-1) + end end @@ -2251,22 +2323,27 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) local Ntot,Nsched, Nwp=self:CountRemainingTasks() if self.taskcurrent>0 then - self:I(self.lid..string.format("WARNING: Got current task ==> RTB event is suspended for 10 sec.")) + self:I(self.lid..string.format("WARNING: Got current task ==> RTB event is suspended for 10 sec")) Tsuspend=-10 allowed=false end if Nsched>0 then - self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> RTB event is suspended for 10 sec.", Nsched)) + self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> RTB event is suspended for 10 sec", Nsched)) Tsuspend=-10 allowed=false end if Nwp>0 then - self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> RTB event is suspended for 10 sec.", Nwp)) + self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> RTB event is suspended for 10 sec", Nwp)) Tsuspend=-10 allowed=false end + + if self.Twaiting and self.dTwait then + self:I(self.lid..string.format("WARNING: Group is Waiting for a specific duration ==> RTB event is canceled", Nwp)) + allowed=false + end end @@ -2342,6 +2419,9 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Set current airbase. self.currbase=airbase + + -- Passed final waypoint! + self.passedfinalwp=true -- Defaults: SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise) @@ -2500,10 +2580,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. +-- @param #number Duration Duration how long the group will be waiting in seconds. Default `nil` (=forever). -- @param Core.Point#COORDINATE Coord Coordinate where to orbit. Default current position. -- @param #number Altitude Altitude in feet. Default 10000 ft. -- @param #number Speed Speed in knots. Default 250 kts. -function FLIGHTGROUP:onafterWait(From, Event, To, Coord, Altitude, Speed) +function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Coord, Altitude, Speed) Coord=Coord or self.group:GetCoordinate() Altitude=Altitude or (self.isHelo and 1000 or 10000) @@ -2517,9 +2598,34 @@ function FLIGHTGROUP:onafterWait(From, Event, To, Coord, Altitude, Speed) -- Orbit task. local TaskOrbit=self.group:TaskOrbit(Coord, UTILS.FeetToMeters(Altitude), UTILS.KnotsToMps(Speed)) + + -- Orbit task. + local TaskFunction=self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting", self) + local DCSTasks=self.group:TaskCombo({TaskOrbit, TaskFunction}) + + -- Orbit until flaghold=1 (true) but max 5 min if no FC is giving the landing clearance. + local TaskOrbit = self.group:TaskOrbit(Coord, UTILS.FeetToMeters(Altitude), UTILS.KnotsToMps(Speed)) + local TaskStop = self.group:TaskCondition(nil, nil, nil, nil, Duration) + local TaskCntr = self.group:TaskControlled(TaskOrbit, TaskStop) + local TaskOver = self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting", self) + + local DCSTasks + if Duration then + DCSTasks=self.group:TaskCombo({TaskCntr, TaskOver}) + else + DCSTasks=self.group:TaskCombo({TaskOrbit, TaskOver}) + end + + -- Set task. - self:SetTask(TaskOrbit) + self:SetTask(DCSTasks) + + -- Set time stamp. + self.Twaiting=timer.getAbsTime() + + -- Max waiting + self.dTwait=Duration end @@ -2885,6 +2991,20 @@ function FLIGHTGROUP._FinishedRefuelling(group, flightgroup) flightgroup:__Refueled(-1) end +--- Function called when flight finished waiting. +-- @param Wrapper.Group#GROUP group Group object. +-- @param #FLIGHTGROUP flightgroup Flight group object. +function FLIGHTGROUP._FinishedWaiting(group, flightgroup) + flightgroup:T(flightgroup.lid..string.format("Group finished waiting")) + + -- Not waiting any more. + flightgroup.Twaiting=nil + flightgroup.dTwait=nil + + -- Trigger Holding event. + flightgroup:_CheckGroupDone(1) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3034,14 +3154,14 @@ end --- Add an element to the flight group. -- @param #FLIGHTGROUP self -- @param #string unitname Name of unit. --- @return #FLIGHTGROUP.Element The element or nil. +-- @return Ops.OpsGroup#OPSGROUP.Element The element or nil. function FLIGHTGROUP:AddElementByName(unitname) local unit=UNIT:FindByName(unitname) if unit then - local element={} --#FLIGHTGROUP.Element + local element={} --Ops.OpsGroup#OPSGROUP.Element element.name=unitname element.status=OPSGROUP.ElementStatus.INUTERO @@ -3384,7 +3504,7 @@ function FLIGHTGROUP:InitWaypoints() end -- Debug info. - self:I(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown")) + self:T(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown")) -- Update route. if #self.waypoints>0 then @@ -3501,7 +3621,7 @@ end function FLIGHTGROUP:_IsElement(unitname) for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element if element.name==unitname then return true @@ -3516,7 +3636,7 @@ end --- Set parking spot of element. -- @param #FLIGHTGROUP self --- @param #FLIGHTGROUP.Element Element The element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The element. -- @param Wrapper.Airbase#AIRBASE.ParkingSpot Spot Parking Spot. function FLIGHTGROUP:_SetElementParkingAt(Element, Spot) @@ -3540,7 +3660,7 @@ end --- Set parking spot of element to free -- @param #FLIGHTGROUP self --- @param #FLIGHTGROUP.Element Element The element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The element. function FLIGHTGROUP:_SetElementParkingFree(Element) if Element.parking then @@ -3650,7 +3770,7 @@ end --- Returns the parking spot of the element. -- @param #FLIGHTGROUP self --- @param #FLIGHTGROUP.Element element Element of the flight group. +-- @param Ops.OpsGroup#OPSGROUP.Element element Element of the flight group. -- @param #number maxdist Distance threshold in meters. Default 5 m. -- @param Wrapper.Airbase#AIRBASE airbase (Optional) The airbase to check for parking. Default is closest airbase to the element. -- @return Wrapper.Airbase#AIRBASE.ParkingSpot Parking spot or nil if no spot is within distance threshold. @@ -3825,7 +3945,7 @@ function FLIGHTGROUP:GetParking(airbase) -- Loop over all units - each one needs a spot. for i,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element -- Loop over all parking spots. local gotit=false diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 0310292ac..9ce1b4210 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -65,12 +65,7 @@ NAVYGROUP = { pathCorridor = 400, } ---- Navy group element. --- @type NAVYGROUP.Element --- @field #string name Name of the element, i.e. the unit. --- @field #string typename Type name. - ---- Navy group element. +--- Turn into wind parameters. -- @type NAVYGROUP.IntoWind -- @field #number Tstart Time to start. -- @field #number Tstop Time to stop. @@ -87,7 +82,7 @@ NAVYGROUP = { --- NavyGroup version. -- @field #string version -NAVYGROUP.version="0.5.0" +NAVYGROUP.version="0.7.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 901bdebc0..9ed08f08a 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -117,7 +117,6 @@ -- @field #string cargoStatus Cargo status of this group acting as cargo. -- @field #number cargoTransportUID Unique ID of the transport assignment this cargo group is associated with. -- @field #string carrierStatus Carrier status of this group acting as cargo carrier. --- @field #number cargocounter Running number of cargo UIDs. -- @field #OPSGROUP.CarrierLoader carrierLoader Carrier loader parameters. -- @field #OPSGROUP.CarrierLoader carrierUnloader Carrier unloader parameters. -- @@ -134,7 +133,7 @@ -- -- # The OPSGROUP Concept -- --- The OPSGROUP class contains common functions used by other classes such as FLIGHGROUP, NAVYGROUP and ARMYGROUP. +-- The OPSGROUP class contains common functions used by other classes such as FLIGHTGROUP, NAVYGROUP and ARMYGROUP. -- Those classes inherit everything of this class and extend it with features specific to their unit category. -- -- This class is **NOT** meant to be used by the end user itself. @@ -180,7 +179,6 @@ OPSGROUP = { weaponData = {}, cargoqueue = {}, cargoBay = {}, - cargocounter = 1, mycarrier = {}, carrierLoader = {}, carrierUnloader = {}, @@ -225,6 +223,14 @@ OPSGROUP = { -- @field #number weightCargo Current cargo weight in kg. -- @field #number weight Current weight including cargo in kg. -- @field #table cargoBay Cargo bay. +-- +-- @field #string modex Tail number. +-- @field Wrapper.Client#CLIENT client The client if element is occupied by a human player. +-- @field #table pylons Table of pylons. +-- @field #number fuelmass Mass of fuel in kg. +-- @field #string callsign Call sign, e.g. "Uzi 1-1". +-- @field Wrapper.Airbase#AIRBASE.ParkingSpot parking The parking spot table the element is parking on. + --- Status of group element. -- @type OPSGROUP.ElementStatus @@ -446,7 +452,7 @@ OPSGROUP.CargoStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="0.7.3" +OPSGROUP.version="0.7.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -511,13 +517,13 @@ function OPSGROUP:New(group) self.spot.timer=TIMER:New(self._UpdateLaser, self) self.spot.Coordinate=COORDINATE:New(0, 0, 0) self:SetLaser(1688, true, false, 0.5) + + -- Cargo. self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER - self.cargocounter=1 self:SetCarrierLoaderAllAspect() self:SetCarrierUnloaderAllAspect() - -- Init task counter. self.taskcurrent=0 self.taskcounter=0 @@ -1513,6 +1519,7 @@ function OPSGROUP:RadioTransmission(Text, Delay) self.msrs:SetFrequencies(freq) self.msrs:SetModulations(modu) + -- Debug info. self:I(self.lid..string.format("Radio transmission on %.3f MHz %s: %s", freq, UTILS.GetModulationName(modu), Text)) self.msrs:PlayText(Text) @@ -2365,7 +2372,7 @@ function OPSGROUP:OnEventBirth(EventData) self.destbase=self.homebase end - self:I(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned", unitname, self.currbase and self.currbase:GetName() or "unknown")) + self:T(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned", unitname, self.currbase and self.currbase:GetName() or "unknown")) else self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) end @@ -4572,7 +4579,9 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Element The flight group element. function OPSGROUP:onafterElementDead(From, Event, To, Element) - self:I(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime())) + + -- Debug info. + self:T(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime())) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) @@ -4615,7 +4624,7 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) -- Clear cargo bay of element. --for _,_cargo in pairs(Element.cargoBay) do for i=#Element.cargoBay,1,-1 do - local cargo=Element.cargoBay[i] --#OPSGROUP.MyCargo --_cargo --#OPSGROUP.MyCargo + local cargo=Element.cargoBay[i] --#OPSGROUP.MyCargo -- Remove from cargo bay. self:_DelCargobay(cargo.group) @@ -4628,7 +4637,7 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) if cargo.reserved then -- This group was not loaded yet ==> Not cargo any more. - cargo.group.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + cargo.group:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) else @@ -4798,6 +4807,8 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterDead(From, Event, To) + + -- Debug info. self:I(self.lid..string.format("Group dead at t=%.3f", timer.getTime())) -- Is dead now. @@ -4897,7 +4908,7 @@ function OPSGROUP:onafterStop(From, Event, To) _DATABASE.FLIGHTGROUPS[self.groupname]=nil -- Debug output. - self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database") + self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -4983,7 +4994,7 @@ function OPSGROUP:_CheckCargoTransport() if self:IsNotCarrier() then -- Debug info. - self:I(self.lid.."Not carrier ==> pickup") + self:T(self.lid.."Not carrier ==> pickup") -- Initiate the cargo transport process. self:__Pickup(-1) @@ -5000,7 +5011,7 @@ function OPSGROUP:_CheckCargoTransport() self.Tloading=self.Tloading or Time -- Debug info. - self:I(self.lid.."Loading...") + self:T(self.lid.."Loading...") local boarding=false local gotcargo=false @@ -5018,15 +5029,10 @@ function OPSGROUP:_CheckCargoTransport() end end - - -- Debug. - --self:I(self.lid.."gotcargo="..tostring(gotcargo)) - --self:I(self.lid.."boarding="..tostring(boarding)) - --self:I(self.lid.."required="..tostring(self.cargoTransport:_CheckRequiredCargos())) -- Boarding finished ==> Transport cargo. if gotcargo and self.cargoTransport:_CheckRequiredCargos() and not boarding then - self:I(self.lid.."Boarding finished ==> Loaded") + self:T(self.lid.."Boarding finished ==> Loaded") self:Loaded() else -- No cargo and no one is boarding ==> check again if we can make anyone board. @@ -5046,7 +5052,7 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsUnloading() then -- Debug info. - self:I(self.lid.."Unloading ==> Checking if all cargo was delivered") + self:T(self.lid.."Unloading ==> Checking if all cargo was delivered") local delivered=true for _,_cargo in pairs(self.cargoTransport.cargos) do @@ -5064,7 +5070,7 @@ function OPSGROUP:_CheckCargoTransport() -- Unloading finished ==> pickup next batch or call it a day. if delivered then - self:I(self.lid.."Unloading finished ==> Unloaded") + self:T(self.lid.."Unloading finished ==> Unloaded") self:Unloaded() else self:Unloading() @@ -5468,6 +5474,23 @@ function OPSGROUP:GetWeightCargoMax(UnitName) return weight end +--- Get OPSGROUPs in the cargo bay. +-- @param #OPSGROUP self +-- @return #table Cargo OPSGROUPs. +function OPSGROUP:GetCargoOpsGroups() + + local opsgroups={} + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + table.insert(opsgroups, cargo.group) + end + end + + return opsgroups +end + --- Add weight to the internal cargo of an element of the group. -- @param #OPSGROUP self -- @param #string UnitName Name of the unit. Default is of the whole group. @@ -5635,11 +5658,9 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterPickup(From, Event, To) - -- Debug info. - self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.PICKUP)) -- Set carrier status. - self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.PICKUP) -- Pickup zone. local Zone=self.cargoTransport.pickupzone @@ -5790,11 +5811,9 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterLoading(From, Event, To) - -- Debug info. - self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.LOADING)) -- Set carrier status. - self.carrierStatus=OPSGROUP.CarrierStatus.LOADING + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.LOADING) -- Loading time stamp. self.Tloading=timer.getAbsTime() @@ -5824,7 +5843,7 @@ function OPSGROUP:onafterLoading(From, Event, To) if carrier then -- Set cargo status. - cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED + cargo.opsgroup:_NewCargoStatus(OPSGROUP.CargoStatus.ASSIGNED) -- Order cargo group to board the carrier. cargo.opsgroup:Board(self, carrier) @@ -5860,6 +5879,36 @@ function OPSGROUP:ClearWaypoints() self.waypoints={} end +--- Set (new) cargo status. +-- @param #OPSGROUP self +-- @param #string Status New status. +function OPSGROUP:_NewCargoStatus(Status) + + -- Debug info. + if self.verbose>=2 then + self:I(self.lid..string.format("New cargo status: %s --> %s", tostring(self.cargoStatus), tostring(Status))) + end + + -- Set cargo status. + self.cargoStatus=Status + +end + +--- Set (new) carrier status. +-- @param #OPSGROUP self +-- @param #string Status New status. +function OPSGROUP:_NewCarrierStatus(Status) + + -- Debug info. + if self.verbose>=2 then + self:I(self.lid..string.format("New carrier status: %s --> %s", tostring(self.carrierStatus), tostring(Status))) + end + + -- Set cargo status. + self.carrierStatus=Status + +end + --- Transfer cargo from to another carrier. -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup The cargo group to be transferred. @@ -5886,7 +5935,7 @@ end function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) -- Debug info. - self:I(self.lid..string.format("Loading group %s", tostring(CargoGroup.groupname))) + self:T(self.lid..string.format("Loading group %s", tostring(CargoGroup.groupname))) -- Carrier element. local carrier=Carrier or CargoGroup:_GetMyCarrierElement() --#OPSGROUP.Element @@ -5902,12 +5951,9 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) --- -- Embark Cargo --- - - -- Debug info. - CargoGroup:I(CargoGroup.lid..string.format("New cargo status: %s --> %s", CargoGroup.cargoStatus, OPSGROUP.CargoStatus.LOADED)) - - -- Set cargo status. - CargoGroup.cargoStatus=OPSGROUP.CargoStatus.LOADED + + -- New cargo status. + CargoGroup:_NewCargoStatus(OPSGROUP.CargoStatus.LOADED) -- Clear all waypoints. CargoGroup:ClearWaypoints() @@ -5927,7 +5973,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) if self.cargoTransport then self.cargoTransport:Loaded(CargoGroup, self, carrier) else - self:E(self.lid..string.format("WARNING: Loaded cargo but no current OPSTRANSPORT assignment!")) + self:T(self.lid..string.format("WARNING: Loaded cargo but no current OPSTRANSPORT assignment!")) end else @@ -5981,11 +6027,8 @@ end -- @param #string To To state. function OPSGROUP:onafterTransport(From, Event, To) - -- Debug info. - self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.TRANSPORTING)) - -- Set carrier status. - self.carrierStatus=OPSGROUP.CarrierStatus.TRANSPORTING + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.TRANSPORTING) --TODO: This is all very similar to the onafterPickup() function. Could make it general. @@ -6131,11 +6174,9 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterUnloading(From, Event, To) - -- Debug info. - self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.UNLOADING)) -- Set carrier status to UNLOADING. - self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.UNLOADING) -- Deploy zone. local zone=self.cargoTransport.disembarkzone or self.cargoTransport.deployzone --Core.Zone#ZONE @@ -6270,12 +6311,9 @@ end -- @param #boolean Activated If `true`, group is active. If `false`, group is spawned in late activated state. -- @param #number Heading (Optional) Heading of group in degrees. Default is random heading for each unit. function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated, Heading) - - -- Debug info. - OpsGroup:I(OpsGroup.lid..string.format("New cargo status: %s --> %s", OpsGroup.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) - - -- Set cargo status. - OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + + -- New cargo status. + OpsGroup:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) --TODO: Unload flightgroup. Find parking spot etc. @@ -6362,8 +6400,9 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterUnloaded(From, Event, To) + -- Debug info - self:I(self.lid.."Cargo unloaded..") + self:T(self.lid.."Cargo unloaded..") -- Cancel landedAt task. if self:IsFlightgroup() and self:IsLandedAt() then @@ -6400,9 +6439,9 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Check if this was the current transport. if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then - - -- Debug info. - self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.NOTCARRIER)) + + -- This is not a carrier anymore. + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.NOTCARRIER) -- Checks if self:IsPickingup() then @@ -6419,9 +6458,6 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Nothing to do? end - -- This is not a carrier anymore. - self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER - -- Startup uncontrolled aircraft to allow it to go back. if self:IsFlightgroup() then if self:IsUncontrolled() then @@ -6436,7 +6472,7 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) end -- Check group done. - self:I(self.lid.."All cargo delivered ==> check group done") + self:T(self.lid.."All cargo delivered ==> check group done") self:_CheckGroupDone(0.2) -- No current transport any more. @@ -6467,11 +6503,11 @@ function OPSGROUP:onbeforeBoard(From, Event, To, CarrierGroup, Carrier) return false elseif CarrierGroup:IsDead() then self:I(self.lid.."Carrier Group DEAD ==> Deny Board transition!") - self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + self:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) return false elseif Carrier.status==OPSGROUP.ElementStatus.DEAD then self:I(self.lid.."Carrier Element DEAD ==> Deny Board transition!") - self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + self:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) return false end @@ -6486,11 +6522,9 @@ end -- @param #OPSGROUP CarrierGroup The carrier group. -- @param #OPSGROUP.Element Carrier The OPSGROUP element function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) - -- Debug info. - self:I(self.lid..string.format("New cargo status: %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.BOARDING)) -- Set cargo status. - self.cargoStatus=OPSGROUP.CargoStatus.BOARDING + self:_NewCargoStatus(OPSGROUP.CargoStatus.BOARDING) -- Army or Navy group. local CarrierIsArmyOrNavy=CarrierGroup:IsArmygroup() or CarrierGroup:IsNavygroup() @@ -6510,7 +6544,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) if board then -- Debug info. - self:I(self.lid..string.format("Boarding group=%s [%s], carrier=%s", CarrierGroup:GetName(), CarrierGroup:GetState(), Carrier.name)) + self:T(self.lid..string.format("Boarding group=%s [%s], carrier=%s", CarrierGroup:GetName(), CarrierGroup:GetState(), Carrier.name)) -- TODO: Implement embarkzone. local Coordinate=Carrier.unit:GetCoordinate() @@ -6536,7 +6570,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) --- -- Debug info. - self:I(self.lid..string.format("Board with direct load to carrier %s", CarrierGroup:GetName())) + self:T(self.lid..string.format("Board with direct load to carrier %s", CarrierGroup:GetName())) local mycarriergroup=self:_GetMyCarrierGroup() @@ -6733,6 +6767,9 @@ function OPSGROUP:_CheckGroupDone(delay) self:ScheduleOnce(delay, self._CheckGroupDone, self) else + -- Debug info. + self:T(self.lid.."Check OPSGROUP done?") + -- Group is engaging something. if self:IsEngaging() then self:UpdateRoute() @@ -7523,7 +7560,8 @@ function OPSGROUP:SwitchROT(rot) self.group:OptionROT(self.option.ROT) - self:I(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) + -- Debug info. + self:T(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) end @@ -8326,7 +8364,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__Spawned(-0.5) + self:Spawned() end elseif newstatus==OPSGROUP.ElementStatus.PARKING then @@ -8335,7 +8373,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__Parking(-0.5) + self:Parking() end elseif newstatus==OPSGROUP.ElementStatus.ENGINEON then @@ -8351,7 +8389,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__Taxiing(-0.5) + self:Taxiing() end elseif newstatus==OPSGROUP.ElementStatus.TAKEOFF then @@ -8361,7 +8399,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) if self:_AllSimilarStatus(newstatus) then -- Trigger takeoff event. Also triggers airborne event. - self:__Takeoff(-0.5, airbase) + self:Takeoff(airbase) end elseif newstatus==OPSGROUP.ElementStatus.AIRBORNE then @@ -8370,7 +8408,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__Airborne(-0.1) + self:Airborne() end elseif newstatus==OPSGROUP.ElementStatus.LANDED then @@ -8463,13 +8501,19 @@ function OPSGROUP:GetElementZoneBoundingBox(UnitName) -- Create a new zone if necessary. element.zoneBoundingbox=element.zoneBoundingbox or ZONE_POLYGON_BASE:New(element.name.." Zone Bounding Box", {}) + -- Length in meters. local l=element.length + -- Width in meters. local w=element.width + -- Orientation vector. local X=self:GetOrientationX(element.name) + + -- Heading in degrees. local heading=math.deg(math.atan2(X.z, X.x)) - env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) + -- Debug info. + self:T(self.lid..string.format("Element %s bouding box: l=%d w=%d heading=%d", element.name, l, w, heading)) -- Set of edges facing "North" at the origin of the map. local b={} @@ -8559,7 +8603,7 @@ function OPSGROUP:_GetElementZoneLoader(Element, Zone, Loader) -- Heading in deg. local heading=math.deg(math.atan2(X.z, X.x)) - env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) + --env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) -- Bounding box at the origin of the map facing "North". local b={} diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 0ef309150..bea743fe7 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -69,6 +69,44 @@ -- * Cargo groups are **not** split and distributed into different carrier *units*. That means that the whole cargo group **must fit** into one of the carrier units. -- * Cargo groups must be inside the pickup zones to be considered for loading. Groups not inside the pickup zone will not get the command to board. -- +-- # Constructor +-- +-- A new cargo transport assignment is created with the @{#OPSTRANSPORT.New}() function +-- +-- local opstransport=OPSTRANSPORT:New(Cargo, PickupZone, DeployZone) +-- +-- Here `Cargo` is an object of the troops to be transported. This can be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object. +-- +-- `PickupZone` is the zone where the troops are picked up by the transport carriers. **Note** that troops *must* be inside this zone to be considered for loading! +-- +-- `DeployZone` is the zone where the troops are transported to. +-- +-- ## Assign to Carrier(s) +-- +-- A transport can be assigned to one or multiple carrier OPSGROUPS with this @{Ops.OpsGroup#OPSGROUP.AddOpsTransport}() function +-- +-- myopsgroup:AddOpsTransport(opstransport) +-- +-- There is no restriction to the type of the carrier. It can be a ground group (e.g. an APC), a helicopter, an airplane or even a ship. +-- +-- You can also mix carrier types. For instance, you can assign the same transport to APCs and helicopters. Or to helicopters and airplanes. +-- +-- # Examples +-- +-- A carrier group is assigned to transport infantry troops from zone "Zone Kobuleti X" to zone "Zone Alpha". +-- +-- -- Carrier group. +-- local carrier=ARMYGROUP:New("TPz Fuchs Group") +-- +-- -- Set of groups to transport. +-- local infantryset=SET_GROUP:New():FilterPrefixes("Infantry Platoon Alpha"):FilterOnce() +-- +-- -- Cargo transport assignment. +-- local opstransport=OPSTRANSPORT:New(infantryset, ZONE:New("Zone Kobuleti X"), ZONE:New("Zone Alpha")) +-- +-- -- Assign transport to carrier. +-- carrier:AddOpsTransport(opstransport) +-- -- -- @field #OPSTRANSPORT OPSTRANSPORT = { @@ -112,7 +150,7 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.8" +OPSTRANSPORT.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -227,7 +265,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) end -- Debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text=string.format("Added cargo groups:") local Weight=0 for _,_cargo in pairs(self.cargos) do @@ -432,20 +470,29 @@ end --- Get (all) cargo @{Ops.OpsGroup#OPSGROUP}s. Optionally, only delivered or undelivered groups can be returned. -- @param #OPSTRANSPORT self -- @param #boolean Delivered If `true`, only delivered groups are returned. If `false` only undelivered groups are returned. If `nil`, all groups are returned. --- @return #table Ops groups. +-- @return #table Cargo Ops groups. function OPSTRANSPORT:GetCargoOpsGroups(Delivered) local opsgroups={} for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup if Delivered==nil or cargo.delivered==Delivered then - table.insert(opsgroups, cargo.opsgroup) + if cargo.opsgroup and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then + table.insert(opsgroups, cargo.opsgroup) + end end end return opsgroups end +--- Get carrier @{Ops.OpsGroup#OPSGROUP}s. +-- @param #OPSTRANSPORT self +-- @return #table Carrier Ops groups. +function OPSTRANSPORT:GetCarrierOpsGroups() + return self.carriers +end + --- Set transport start and stop time. -- @param #OPSTRANSPORT self @@ -579,11 +626,13 @@ function OPSTRANSPORT:_GetPathTransport() local coordinates={} - for _,coord in ipairs(path.coords) do + for _,_coord in ipairs(path.coords) do + local coord=_coord --Core.Point#COORDINATE + + -- Get random coordinate. + local c=coord:GetRandomCoordinateInRadius(path.radius) - -- TODO: Add randomization. - - table.insert(coordinates, coord) + table.insert(coordinates, c) end return coordinates @@ -642,11 +691,13 @@ function OPSTRANSPORT:_GetPathPickup() local coordinates={} - for _,coord in ipairs(path.coords) do + for _,_coord in ipairs(path.coords) do + local coord=_coord --Core.Point#COORDINATE + + -- Get random coordinate. + local c=coord:GetRandomCoordinateInRadius(path.radius) - -- TODO: Add randomization. - - table.insert(coordinates, coord) + table.insert(coordinates, c) end return coordinates @@ -753,7 +804,7 @@ function OPSTRANSPORT:IsReadyToGo() -- Nope, not yet. if not startme then text=text..("No way, at least one start condition is not true!") - self:I(text) + self:T(text) return false end From 1b717e4683002fb12f9d9891e570ddd36c3a04c6 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 12 Jul 2021 15:27:08 +0200 Subject: [PATCH 052/141] AUFTRAG - Added push time --- Moose Development/Moose/Core/Set.lua | 11 ++- Moose Development/Moose/Ops/ArmyGroup.lua | 11 +++ Moose Development/Moose/Ops/Auftrag.lua | 25 ++++- Moose Development/Moose/Ops/FlightGroup.lua | 19 ++-- Moose Development/Moose/Ops/NavyGroup.lua | 11 +++ Moose Development/Moose/Ops/OpsGroup.lua | 103 +++++++++++++++++--- 6 files changed, 146 insertions(+), 34 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index c978d8f85..e6ab285ba 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -233,7 +233,9 @@ do -- SET_BASE -- @param Core.Base#BASE Object The object itself. -- @return Core.Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) - self:I( { ObjectName = ObjectName, Object = Object } ) + + -- Debug info. + self:T( { ObjectName = ObjectName, Object = Object } ) -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set if self.Set[ObjectName] then @@ -5908,12 +5910,13 @@ do -- SET_ZONE_GOAL -- @param Core.Event#EVENTDATA EventData function SET_ZONE_GOAL:OnEventNewZoneGoal( EventData ) - self:I( { "New Zone Capture Coalition", EventData } ) - self:I( { "Zone Capture Coalition", EventData.ZoneGoal } ) + -- Debug info. + self:T( { "New Zone Capture Coalition", EventData } ) + self:T( { "Zone Capture Coalition", EventData.ZoneGoal } ) if EventData.ZoneGoal then if EventData.ZoneGoal and self:IsIncludeObject( EventData.ZoneGoal ) then - self:I( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } ) + self:T( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } ) self:Add( EventData.ZoneGoal.ZoneName , EventData.ZoneGoal ) end end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index d8b506e64..301cd48f1 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -392,6 +392,17 @@ function ARMYGROUP:onafterStatus(From, Event, To) self:_UpdateEngageTarget() end + -- 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 + end + end + end if alive~=nil then diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 06183c53d..4c55f5119 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -39,9 +39,10 @@ -- @field #number prio Mission priority. -- @field #boolean urgent Mission is urgent. Running missions with lower prio might be cancelled. -- @field #number importance Importance. --- @field #number Tstart Mission start time in seconds. --- @field #number Tstop Mission stop time in seconds. +-- @field #number Tstart Mission start time in abs. seconds. +-- @field #number Tstop Mission stop time in abs. seconds. -- @field #number duration Mission duration in seconds. +-- @field #number Tpush Mission push/execute time in abs. seconds. -- @field Wrapper.Marker#MARKER marker F10 map marker. -- @field #boolean markerOn If true, display marker on F10 map with the AUFTRAG status. -- @field #number markerCoaliton Coalition to which the marker is dispayed. @@ -442,7 +443,7 @@ AUFTRAG.TargetType={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.6.0" +AUFTRAG.version="0.6.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1436,6 +1437,24 @@ function AUFTRAG:SetTime(ClockStart, ClockStop) return self end + +--- Set mission push time. This is the time the mission is executed. If the push time is not passed, the group will wait at the mission execution waypoint. +-- @param #AUFTRAG self +-- @param #string ClockPush Time the mission is executed, e.g. "05:00" for 5 am. Can also be given as a `#number`, where it is interpreted as relative push time in seconds. +-- @return #AUFTRAG self +function AUFTRAG:SetPushTime(ClockPush) + + if ClockPush then + if type(ClockPush)=="string" then + self.Tpush=UTILS.ClockToSeconds(ClockPush) + elseif type(ClockPush)=="number" then + self.Tpush=timer.getAbsTime()+ClockPush + end + end + + return self +end + --- Set mission priority and (optional) urgency. Urgent missions can cancel other running missions. -- @param #AUFTRAG self -- @param #number Prio Priority 1=high, 100=low. Default 50. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index fa3aec358..19d6d061d 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -609,13 +609,6 @@ function FLIGHTGROUP:IsCruising() return self:Is("Cruising") end ---- Check if flight is waiting after passing final waypoint. --- @param #FLIGHTGROUP self --- @return #boolean If true, flight is waiting. -function FLIGHTGROUP:IsWaiting() - return self:Is("Waiting") -end - --- Check if flight is landing. -- @param #FLIGHTGROUP self -- @return #boolean If true, flight is landing, i.e. on final approach. @@ -2556,15 +2549,15 @@ function FLIGHTGROUP:onbeforeWait(From, Event, To, Coord, Altitude, Speed) end if Nsched>0 then - self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> WAIT event is suspended for 10 sec.", Nsched)) - Tsuspend=-10 - allowed=false + --self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> WAIT event is suspended for 10 sec.", Nsched)) + --Tsuspend=-10 + --allowed=false end if Nwp>0 then - self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> WAIT event is suspended for 10 sec.", Nwp)) - Tsuspend=-10 - allowed=false + --self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> WAIT event is suspended for 10 sec.", Nwp)) + --Tsuspend=-10 + --allowed=false end if Tsuspend and not allowed then diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 9ce1b4210..5284cec36 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -522,6 +522,17 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check if group got stuck. self:_CheckStuck() + + -- 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 + end + end if self.verbose>=1 then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 9ed08f08a..0d686919d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -284,6 +284,7 @@ OPSGROUP.TaskType={ --- Task structure. -- @type OPSGROUP.Task -- @field #string type Type of task: either SCHEDULED or WAYPOINT. +-- @field #boolean ismission This is an AUFTRAG task. -- @field #number id Task ID. Running number to get the task. -- @field #number prio Priority. -- @field #number time Abs. mission time when to execute the task. @@ -2876,6 +2877,57 @@ function OPSGROUP:GetTaskByID(id, status) return nil end +--- On before "TaskExecute" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP.Task Task The task. +function OPSGROUP:onbeforeTaskExecute(From, Event, To, Task) + + -- Get mission of this task (if any). + local Mission=self:GetMissionByTaskID(Task.id) + + if Mission then + + if Mission.Tpush then + + local Tnow=timer.getAbsTime() + + -- Time to push + local dt=Mission.Tpush-Tnow + + -- Push time not reached. + if Tnow0 then for i,_task in pairs(tasks) do local task=_task --#OPSGROUP.Task text=text..string.format("\n[%d] %s", i, task.description) + if task.ismission then + missiontask=task + end end else text=text.." None" end self:T(self.lid..text) + + -- Check if there is mission task + if missiontask then + env.info("FF executing mission task") + self:TaskExecute(missiontask) + return 1 + end + -- TODO: maybe set waypoint enroute tasks? -- Tasks at this waypoints. local taskswp={} - -- TODO: maybe set waypoint enroute tasks? - for _,task in pairs(tasks) do local Task=task --Ops.OpsGroup#OPSGROUP.Task From 835041e5f6e225eef4ffc66d19bf86f3f842016e Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 14 Jul 2021 21:00:34 +0200 Subject: [PATCH 053/141] OPS --- Moose Development/Moose/Ops/ArmyGroup.lua | 25 ++-- Moose Development/Moose/Ops/FlightGroup.lua | 79 ++++------ Moose Development/Moose/Ops/NavyGroup.lua | 21 +-- Moose Development/Moose/Ops/OpsGroup.lua | 149 +++++++++++++------ Moose Development/Moose/Ops/OpsTransport.lua | 37 +++-- Moose Development/Moose/Wrapper/Group.lua | 10 +- 6 files changed, 190 insertions(+), 131 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 301cd48f1..9506a270b 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -345,10 +345,10 @@ end function ARMYGROUP:onbeforeStatus(From, Event, To) if self:IsDead() then - self:I(self.lid..string.format("Onbefore Status DEAD ==> false")) + self:T(self.lid..string.format("Onbefore Status DEAD ==> false")) return false elseif self:IsStopped() then - self:I(self.lid..string.format("Onbefore Status STOPPED ==> false")) + self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) return false end @@ -567,16 +567,19 @@ function ARMYGROUP:onafterSpawned(From, Event, To) if not self.option.Formation then self.option.Formation=self.optionDefault.Formation end + + -- Update route. + if #self.waypoints>1 then + self:Cruise(nil, self.option.Formation or self.optionDefault.Formation) + else + self:FullStop() + end + + -- Update status. + self:__Status(-0.1) end - -- Update route. - if #self.waypoints>1 then - self:Cruise(nil, self.option.Formation or self.optionDefault.Formation) - else - self:FullStop() - end - end --- On before "UpdateRoute" event. @@ -1117,7 +1120,7 @@ function ARMYGROUP:_InitGroup() -- First check if group was already initialized. if self.groupinitialized then - self:E(self.lid.."WARNING: Group was already initialized!") + self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") return end @@ -1177,7 +1180,7 @@ function ARMYGROUP:_InitGroup() self.actype=units[1]:GetTypeName() -- Debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text=string.format("Initialized Army Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 19d6d061d..5b15572ee 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2191,7 +2191,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) end -- Group is waiting. - if self.Twaiting then + if self:IsWaiting() then self:T(self.lid.."Waiting! Group NOT done...") return end @@ -2531,37 +2531,31 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Point#COORDINATE Coord Coordinate where to orbit. Default current position. --- @param #number Altitude Altitude in feet. Default 10000 ft. --- @param #number Speed Speed in knots. Default 250 kts. -function FLIGHTGROUP:onbeforeWait(From, Event, To, Coord, Altitude, Speed) +-- @param #number Duration Duration how long the group will be waiting in seconds. Default `nil` (=forever). +-- @param #number Altitude Altitude in feet. Default 10,000 ft for airplanes and 1,000 feet for helos. +-- @param #number Speed Speed in knots. Default 250 kts for airplanes and 20 kts for helos. +function FLIGHTGROUP:onbeforeWait(From, Event, To, Duration, Altitude, Speed) local allowed=true local Tsuspend=nil - -- Check if there are remaining tasks. - local Ntot,Nsched, Nwp=self:CountRemainingTasks() - + -- Check for a current task. if self.taskcurrent>0 then - self:I(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 10 sec.")) - Tsuspend=-10 + self:I(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!")) + Tsuspend=-30 allowed=false end - - if Nsched>0 then - --self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> WAIT event is suspended for 10 sec.", Nsched)) - --Tsuspend=-10 - --allowed=false - end - - if Nwp>0 then - --self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> WAIT event is suspended for 10 sec.", Nwp)) - --Tsuspend=-10 - --allowed=false + + -- Check for a current transport assignment. + if self.cargoTransport then + self:I(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) + Tsuspend=-30 + allowed=false end + -- Call wait again. if Tsuspend and not allowed then - self:__Wait(Tsuspend, Coord, Altitude, Speed) + self:__Wait(Tsuspend, Duration, Altitude, Speed) end return allowed @@ -2574,14 +2568,18 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #number Duration Duration how long the group will be waiting in seconds. Default `nil` (=forever). --- @param Core.Point#COORDINATE Coord Coordinate where to orbit. Default current position. --- @param #number Altitude Altitude in feet. Default 10000 ft. --- @param #number Speed Speed in knots. Default 250 kts. -function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Coord, Altitude, Speed) +-- @param #number Altitude Altitude in feet. Default 10,000 ft for airplanes and 1,000 feet for helos. +-- @param #number Speed Speed in knots. Default 250 kts for airplanes and 20 kts for helos. +function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Altitude, Speed) - Coord=Coord or self.group:GetCoordinate() + -- Group will orbit at its current position. + local Coord=self.group:GetCoordinate() + + -- Set altitude: 1000 ft for helos and 10,000 ft for panes. Altitude=Altitude or (self.isHelo and 1000 or 10000) - Speed=Speed or (self.isHelo and 80 or 250) + + -- Set speed. + Speed=Speed or (self.isHelo and 20 or 250) -- Debug message. local text=string.format("Flight group set to wait/orbit at altitude %d m and speed %.1f km/h", Altitude, Speed) @@ -3009,7 +3007,7 @@ function FLIGHTGROUP:_InitGroup() -- First check if group was already initialized. if self.groupinitialized then - self:E(self.lid.."WARNING: Group was already initialized!") + self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") return end @@ -3106,7 +3104,7 @@ function FLIGHTGROUP:_InitGroup() self.refueltype=select(2, unit:IsRefuelable()) -- Debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text=string.format("Initialized Flight Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) @@ -3606,27 +3604,6 @@ function FLIGHTGROUP:AddWaypointLanding(Airbase, Speed, AfterWaypointWithID, Alt end - ---- Check if a unit is an element of the flightgroup. --- @param #FLIGHTGROUP self --- @param #string unitname Name of unit. --- @return #boolean If true, unit is element of the flight group or false if otherwise. -function FLIGHTGROUP:_IsElement(unitname) - - for _,_element in pairs(self.elements) do - local element=_element --Ops.OpsGroup#OPSGROUP.Element - - if element.name==unitname then - return true - end - - end - - return false -end - - - --- Set parking spot of element. -- @param #FLIGHTGROUP self -- @param Ops.OpsGroup#OPSGROUP.Element Element The element. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 5284cec36..204792dcf 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -693,16 +693,19 @@ function NAVYGROUP:onafterSpawned(From, Event, To) else self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, false) end + + -- Update route. + if #self.waypoints>1 then + self:Cruise() + else + self:FullStop() + end + + -- Update status. + self:__Status(-0.1) end - -- Update route. - if #self.waypoints>1 then - self:Cruise() - else - self:FullStop() - end - end --- On before "UpdateRoute" event. @@ -1132,7 +1135,7 @@ function NAVYGROUP:_InitGroup() -- First check if group was already initialized. if self.groupinitialized then - self:E(self.lid.."WARNING: Group was already initialized!") + self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") return end @@ -1197,7 +1200,7 @@ function NAVYGROUP:_InitGroup() self.actype=units[1]:GetTypeName() -- Debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text=string.format("Initialized Navy Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 0d686919d..69b5c1d01 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -373,6 +373,7 @@ OPSGROUP.TaskType={ -- @field #number MissilesAS Amount of anti-ship missiles. -- @field #number MissilesCR Amount of cruise missiles. -- @field #number MissilesBM Amount of ballistic missiles. +-- @field #number MissilesSA Amount of surfe-to-air missiles. --- Waypoint data. -- @type OPSGROUP.Waypoint @@ -1695,6 +1696,12 @@ end -- @return #boolean If true, the group exists or false if the group does not exist. If nil, the DCS group could not be found. function OPSGROUP:IsActive() + if self.group then + local active=self.group:IsActive() + return active + end + + return nil end --- Check if group is alive. @@ -1737,7 +1744,6 @@ end -- @param #OPSGROUP self -- @return #boolean If true, all units/elements of the group are dead. function OPSGROUP:IsDead() - --env.info("FF IsDead") if self.isDead then return true else @@ -1806,6 +1812,16 @@ function OPSGROUP:IsEngaging() return is end +--- Check if group is currently waiting. +-- @param #OPSGROUP self +-- @return #boolean If true, group is currently waiting. +function OPSGROUP:IsWaiting() + if self.Twaiting then + return true + end + return false +end + --- Check if the group is not a carrier yet. -- @param #OPSGROUP self -- @return #boolean If true, group is not a carrier. @@ -1902,14 +1918,6 @@ function OPSGROUP:IsLoaded(CarrierGroupName) return self.cargoStatus==OPSGROUP.CargoStatus.LOADED end ---- Check if the group is cargo and waiting for a carrier to pick it up. --- @param #OPSGROUP self --- @return #boolean If true, group is waiting for a carrier. -function OPSGROUP:IsWaitingAsCargo() - return self.cargoStatus==OPSGROUP.CargoStatus.WAITING -end - - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3872,14 +3880,37 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Check if group is currently waiting. --- @param #OPSGROUP self --- @param #boolean If true, group is currently waiting. -function OPSGROUP:IsWaiting() - if self.Twaiting then - return true +--- On before "Wait" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number Duration Duration how long the group will be waiting in seconds. Default `nil` (=forever). +function OPSGROUP:onbeforeWait(From, Event, To, Duration) + + local allowed=true + local Tsuspend=nil + + -- Check for a current task. + if self.taskcurrent>0 then + self:I(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!")) + Tsuspend=-30 + allowed=false end - return false + + -- Check for a current transport assignment. + if self.cargoTransport then + self:I(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) + Tsuspend=-30 + allowed=false + end + + -- Call wait again. + if Tsuspend and not allowed then + self:__Wait(Tsuspend, Duration) + end + + return allowed end --- On after "Wait" event. @@ -3991,7 +4022,6 @@ function OPSGROUP:_SetWaypointTasks(Waypoint) -- Check if there is mission task if missiontask then - env.info("FF executing mission task") self:TaskExecute(missiontask) return 1 end @@ -4038,7 +4068,7 @@ function OPSGROUP:onafterGotoWaypoint(From, Event, To, UID) if n then - -- TODO: switch to re-enable waypoint tasks. + -- TODO: Switch to re-enable waypoint tasks? if false then local tasks=self:GetTasksWaypoint(n) @@ -4213,7 +4243,7 @@ function OPSGROUP:onbeforeLaserOn(From, Event, To, Target) self:LaserGotLOS() else -- Try to switch laser on again in 10 sec. - self:I(self.lid.."LASER got no LOS currently. Trying to switch the laser on again in 10 sec") + self:T(self.lid.."LASER got no LOS currently. Trying to switch the laser on again in 10 sec") self:__LaserOn(-10, Target) return false end @@ -4697,7 +4727,6 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) -- Clear cargo bay of element. - --for _,_cargo in pairs(Element.cargoBay) do for i=#Element.cargoBay,1,-1 do local cargo=Element.cargoBay[i] --#OPSGROUP.MyCargo @@ -4809,6 +4838,13 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Group is DESPAWNED --- + -- Ensure elements in utero. + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + self:ElementInUtero(element) + end + + --[[ -- Loop over template units. @@ -4845,7 +4881,10 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Not dead or destroyed any more. self.isDead=false self.isDestroyed=false - self.Ndestroyed=0 + self.Ndestroyed=0 + + self:InitWaypoints() + self:_InitGroup() -- Reset events. --self:ResetEvents() @@ -4919,10 +4958,17 @@ function OPSGROUP:onafterDead(From, Event, To) for i,_transport in pairs(self.cargoqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT transport:__DeadCarrierGroup(1, self) - end + end + + -- Cargo queue empty + self.cargoqueue={} + + -- No current cargo transport. + self.cargoTransport=nil + -- Stop in a sec. - self:__Stop(-5) + --self:__Stop(-5) end --- On before "Stop" event. @@ -5343,6 +5389,7 @@ function OPSGROUP:_CheckDelivered(CargoTransport) elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then -- This one is dead. else + --env.info(string.format()) done=false --Someone is not done! end @@ -5378,14 +5425,14 @@ end -- @param #OPSGROUP self -- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport Cargo transport do be deleted. -- @return #OPSGROUP self -function OPSGROUP:DelCargoTransport(CargoTransport) - - for i,_transport in pairs(self.cargoqueue) do - local transport=_transport --Ops.OpsTransport#OPSTRANSPORT +function OPSGROUP:DelOpsTransport(CargoTransport) + + for i=#self.cargoqueue,1,-1 do + local transport=self.cargoqueue[i] --Ops.OpsTransport#OPSTRANSPORT if transport.uid==CargoTransport.uid then table.remove(self.cargoqueue, i) return self - end + end end return self @@ -5574,7 +5621,7 @@ function OPSGROUP:AddWeightCargo(UnitName, Weight) local element=self:GetElementByName(UnitName) - if element and element.unit and element.unit:IsAlive() then + if element then --we do not check if the element is actually alive because we need to remove cargo from dead units -- Add weight. element.weightCargo=element.weightCargo+Weight @@ -5767,7 +5814,7 @@ function OPSGROUP:onafterPickup(From, Event, To) -- We are already in the pickup zone ==> wait and initiate loading. if (self:IsArmygroup() or self:IsNavygroup()) and not self:IsHolding() then - self:Wait() + self:FullStop() end -- Start loading. @@ -5992,7 +6039,7 @@ end function OPSGROUP:_TransferCargo(CargoGroup, CarrierGroup, CarrierElement) -- Debug info. - self:I(self.lid..string.format("Transferring cargo %s to new carrier group %s", CargoGroup:GetName(), CarrierGroup:GetName())) + self:T(self.lid..string.format("Transferring cargo %s to new carrier group %s", CargoGroup:GetName(), CarrierGroup:GetName())) -- Unload from this and directly load into the other carrier. self:Unload(CargoGroup) @@ -6136,7 +6183,7 @@ function OPSGROUP:onafterTransport(From, Event, To) -- We are already in the pickup zone ==> wait and initiate unloading. if (self:IsArmygroup() or self:IsNavygroup()) and not self:IsHolding() then - self:Wait() + self:FullStop() end -- Start loading. @@ -6440,10 +6487,12 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated -- Add current waypoint. These have been cleard on loading. if OpsGroup:IsNavygroup() then + OpsGroup:ClearWaypoints() OpsGroup.currentwp=1 OpsGroup.passedfinalwp=true NAVYGROUP.AddWaypoint(OpsGroup, Coordinate, nil, nil, nil, false) elseif OpsGroup:IsArmygroup() then + OpsGroup:ClearWaypoints() OpsGroup.currentwp=1 OpsGroup.passedfinalwp=true ARMYGROUP.AddWaypoint(OpsGroup, Coordinate, nil, nil, nil, false) @@ -6555,7 +6604,7 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) end -- Remove cargo transport from cargo queue. - self:DelCargoTransport(CargoTransport) + self:DelOpsTransport(CargoTransport) end @@ -6860,8 +6909,6 @@ function OPSGROUP:_CheckGroupDone(delay) -- Get current waypoint. local waypoint=self:GetWaypoint(self.currentwp) - --env.info("FF CheckGroupDone") - if waypoint then -- Number of tasks remaining for this waypoint. @@ -7437,7 +7484,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else -- Wait and load cargo. - opsgroup:Wait() + opsgroup:FullStop() opsgroup:__Loading(-5) end @@ -7450,7 +7497,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else -- Stop and unload. - opsgroup:Wait() + opsgroup:FullStop() opsgroup:Unloading() end @@ -8521,7 +8568,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__Dead(-1) + self:Dead() end end @@ -8678,8 +8725,6 @@ function OPSGROUP:_GetElementZoneLoader(Element, Zone, Loader) -- Heading in deg. local heading=math.deg(math.atan2(X.z, X.x)) - --env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) - -- Bounding box at the origin of the map facing "North". local b={} @@ -9035,6 +9080,24 @@ function OPSGROUP:_CoordinateFromObject(Object) return nil end +--- Check if a unit is an element of the flightgroup. +-- @param #OPSGROUP self +-- @param #string unitname Name of unit. +-- @return #boolean If true, unit is element of the flight group or false if otherwise. +function OPSGROUP:_IsElement(unitname) + + for _,_element in pairs(self.elements) do + local element=_element --Ops.OpsGroup#OPSGROUP.Element + + if element.name==unitname then + return true + end + + end + + return false +end + --- Add a unit/element to the OPS group. -- @param #OPSGROUP self -- @param #string unitname Name of unit. @@ -9095,7 +9158,7 @@ function OPSGROUP:_AddElementByName(unitname) element.weight=element.weightEmpty+element.weightCargo -- Looks like only aircraft have a massMax value in the descriptors. - element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+2*95 --If max mass is not given, we assume 10 soldiers. + element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+8*95 --If max mass is not given, we assume 8 soldiers. if self.isArmygroup then @@ -9141,7 +9204,9 @@ function OPSGROUP:_AddElementByName(unitname) self:T(self.lid..text) -- Add element to table. - table.insert(self.elements, element) + if not self:_IsElement(unitname) then + table.insert(self.elements, element) + end -- Trigger spawned event if alive. if unit:IsAlive() then diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index bea743fe7..61bd09885 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -208,8 +208,8 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- PLANNED --> SCHEDULED --> EXECUTING --> DELIVERED self:AddTransition("*", "Planned", OPSTRANSPORT.Status.PLANNED) -- Cargo transport was planned. self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. - self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. - self:AddTransition(OPSTRANSPORT.Status.EXECUTING, "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. + self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. + self:AddTransition("*", "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. self:AddTransition("*", "Status", "*") self:AddTransition("*", "Stop", "*") @@ -885,7 +885,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterPlanned(From, Event, To) - self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.PLANNED)) + self:T(self.lid..string.format("New status: %s-->%s", From, To)) end --- On after "Scheduled" event. @@ -894,7 +894,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterScheduled(From, Event, To) - self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.SCHEDULED)) + self:T(self.lid..string.format("New status: %s-->%s", From, To)) end --- On after "Executing" event. @@ -903,7 +903,22 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterExecuting(From, Event, To) - self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.EXECUTING)) + self:T(self.lid..string.format("New status: %s-->%s", From, To)) +end + +--- On before "Delivered" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onbeforeDelivered(From, Event, To) + + -- Check that we do not call delivered again. + if From==OPSTRANSPORT.Status.DELIVERED then + return false + end + + return true end --- On after "Delivered" event. @@ -912,7 +927,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterDelivered(From, Event, To) - self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.DELIVERED)) + self:T(self.lid..string.format("New status: %s-->%s", From, To)) -- Inform all assigned carriers that cargo was delivered. They can have this in the queue or are currently processing this transport. for _,_carrier in pairs(self.carriers) do @@ -941,7 +956,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was unloaded from a carrier. +-- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo Cargo OPSGROUP that was unloaded from a carrier. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. function OPSTRANSPORT:onafterUnloaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier) self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroupCargo:GetName())) @@ -966,6 +981,7 @@ end -- @param #string To To state. function OPSTRANSPORT:onafterDeadCarrierAll(From, Event, To) self:I(self.lid..string.format("ALL Carrier OPSGROUPs are dead! Setting stage to PLANNED if not all cargo was delivered.")) + self:_CheckDelivered() if not self:IsDelivered() then self:Planned() end @@ -997,7 +1013,6 @@ function OPSTRANSPORT:_CheckDelivered() -- This one was destroyed. elseif cargo.opsgroup:IsDead() then -- This one is dead. - dead=false elseif cargo.opsgroup:IsStopped() then -- This one is stopped. dead=false @@ -1089,7 +1104,7 @@ function OPSTRANSPORT:FindTransferCarrierForCargo(CargoGroup, Zone) local carrierGroup=_carrier --Ops.OpsGroup#OPSGROUP -- First check if carrier is alive and loading cargo. - if carrierGroup and carrierGroup:IsAlive() and carrierGroup:IsLoading() then + if carrierGroup and carrierGroup:IsAlive() and (carrierGroup:IsLoading() or self.deployzone:IsInstanceOf("ZONE_AIRBASE")) then -- Find an element of the group that has enough free space. carrier=carrierGroup:FindCarrierForCargo(CargoGroup) @@ -1098,10 +1113,10 @@ function OPSTRANSPORT:FindTransferCarrierForCargo(CargoGroup, Zone) if Zone==nil or Zone:IsCoordinateInZone(carrier.unit:GetCoordinate()) then return carrier, carrierGroup else - self:T3(self.lid.."Got transfer carrier but carrier not in zone (yet)!") + self:T2(self.lid.."Got transfer carrier but carrier not in zone (yet)!") end else - self:T3(self.lid.."No transfer carrier available!") + self:T2(self.lid.."No transfer carrier available!") end end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index a9c678ab5..f844a8ad2 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -310,8 +310,7 @@ end --- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Position The 3D position vectors of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. +-- @return DCS#Position The 3D position vectors of the POSITIONABLE or #nil if the groups not existing or alive. function GROUP:GetPositionVec3() -- Overridden from POSITIONABLE:GetPositionVec3() self:F2( self.PositionableName ) @@ -340,8 +339,7 @@ end -- -- @param #GROUP self -- @return #boolean true if the group is alive and active. --- @return #boolean false if the group is alive but inactive. --- @return #nil if the group does not exist anymore. +-- @return #boolean false if the group is alive but inactive or #nil if the group does not exist anymore. function GROUP:IsAlive() self:F2( self.GroupName ) @@ -363,8 +361,7 @@ end --- Returns if the group is activated. -- @param #GROUP self --- @return #boolean true if group is activated. --- @return #nil The group is not existing or alive. +-- @return #boolean true if group is activated or #nil The group is not existing or alive. function GROUP:IsActive() self:F2( self.GroupName ) @@ -412,7 +409,6 @@ function GROUP:Destroy( GenerateEvent, delay ) self:F2( self.GroupName ) if delay and delay>0 then - --SCHEDULER:New(nil, GROUP.Destroy, {self, GenerateEvent}, delay) self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent) else From 78b3e3c60b2adebb847cf37225dd48bccd81e9e4 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 15 Jul 2021 22:25:02 +0200 Subject: [PATCH 054/141] OPS Respawn --- Moose Development/Moose/Core/Fsm.lua | 18 +- Moose Development/Moose/Core/Point.lua | 79 ++++++-- Moose Development/Moose/Ops/ArmyGroup.lua | 9 +- Moose Development/Moose/Ops/FlightGroup.lua | 35 ++-- Moose Development/Moose/Ops/NavyGroup.lua | 13 +- Moose Development/Moose/Ops/OpsGroup.lua | 196 +++++++++++++++----- 6 files changed, 252 insertions(+), 98 deletions(-) diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 0ada59e71..68807904b 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -410,7 +410,7 @@ do -- FSM Transition.To = To -- Debug message. - self:T2( Transition ) + self:T3( Transition ) self._Transitions[Transition] = Transition self:_eventmap( self.Events, Transition ) @@ -432,7 +432,7 @@ do -- FSM -- @param #table ReturnEvents A table indicating for which returned events of the SubFSM which Event must be triggered in the FSM. -- @return Core.Fsm#FSM_PROCESS The SubFSM. function FSM:AddProcess( From, Event, Process, ReturnEvents ) - self:T( { From, Event } ) + self:T3( { From, Event } ) local Sub = {} Sub.From = From @@ -533,7 +533,7 @@ do -- FSM Process._Scores[State].ScoreText = ScoreText Process._Scores[State].Score = Score - self:T( Process._Scores ) + self:T3( Process._Scores ) return Process end @@ -576,7 +576,7 @@ do -- FSM self[__Event] = self[__Event] or self:_delayed_transition(Event) -- Debug message. - self:T2( "Added methods: " .. Event .. ", " .. __Event ) + self:T3( "Added methods: " .. Event .. ", " .. __Event ) Events[Event] = self.Events[Event] or { map = {} } self:_add_to_map( Events[Event].map, EventStructure ) @@ -825,7 +825,7 @@ do -- FSM end -- Debug. - self:T2( { CallID = CallID } ) + self:T3( { CallID = CallID } ) end end @@ -846,7 +846,7 @@ do -- FSM function FSM:_gosub( ParentFrom, ParentEvent ) local fsmtable = {} if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then - self:T( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } ) + self:T3( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } ) return self.subs[ParentFrom][ParentEvent] else return {} @@ -1150,7 +1150,7 @@ do -- FSM_PROCESS -- @param #FSM_PROCESS self -- @return #FSM_PROCESS function FSM_PROCESS:Copy( Controllable, Task ) - self:T( { self:GetClassNameAndID() } ) + self:T3( { self:GetClassNameAndID() } ) local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS @@ -1176,13 +1176,13 @@ do -- FSM_PROCESS -- Copy End States for EndStateID, EndState in pairs( self:GetEndStates() ) do - self:T( EndState ) + self:T3( EndState ) NewFsm:AddEndState( EndState ) end -- Copy the score tables for ScoreID, Score in pairs( self:GetScores() ) do - self:T( Score ) + self:T3( Score ) NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score ) end diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 1c28e0730..b450eebbf 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -26,19 +26,28 @@ -- -- ### Authors: -- --- * FlightControl : Design & Programming +-- * FlightControl (Design & Programming) -- -- ### Contributions: +-- +-- * funkyfranky +-- * Applevangelist +-- +-- === -- -- @module Core.Point -- @image Core_Coordinate.JPG - - do -- COORDINATE --- @type COORDINATE + -- @field #string ClassName Name of the class + -- @field #number x Component of the 3D vector. + -- @field #number y Component of the 3D vector. + -- @field #number z Component of the 3D vector. + -- @field #number Heading Heading in degrees. Needs to be set first. + -- @field #number Velocity Velocity in meters per second. Needs to be set first. -- @extends Core.Base#BASE @@ -201,13 +210,24 @@ do -- COORDINATE ClassName = "COORDINATE", } - --- @field COORDINATE.WaypointAltType + --- Waypoint altitude types. + -- @type COORDINATE.WaypointAltType + -- @field #string BARO Barometric altitude. + -- @field #string RADIO Radio altitude. COORDINATE.WaypointAltType = { BARO = "BARO", RADIO = "RADIO", } - --- @field COORDINATE.WaypointAction + --- Waypoint actions. + -- @type COORDINATE.WaypointAction + -- @field #string TurningPoint Turning point. + -- @field #string FlyoverPoint Fly over point. + -- @field #string FromParkingArea From parking area. + -- @field #string FromParkingAreaHot From parking area hot. + -- @field #string FromRunway From runway. + -- @field #string Landing Landing. + -- @field #string LandingReFuAr Landing and refuel and rearm. COORDINATE.WaypointAction = { TurningPoint = "Turning Point", FlyoverPoint = "Fly Over Point", @@ -218,7 +238,14 @@ do -- COORDINATE LandingReFuAr = "LandingReFuAr", } - --- @field COORDINATE.WaypointType + --- Waypoint types. + -- @type COORDINATE.WaypointType + -- @field #string TakeOffParking Take of parking. + -- @field #string TakeOffParkingHot Take of parking hot. + -- @field #string TakeOff Take off parking hot. + -- @field #string TurningPoint Turning point. + -- @field #string Land Landing point. + -- @field #string LandingReFuAr Landing and refuel and rearm. COORDINATE.WaypointType = { TakeOffParking = "TakeOffParking", TakeOffParkingHot = "TakeOffParkingHot", @@ -232,13 +259,13 @@ do -- COORDINATE --- COORDINATE constructor. -- @param #COORDINATE self -- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North. - -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to the Right. - -- @param DCS#Distance z The z coordinate of the Vec3 point, pointing to the Right. - -- @return #COORDINATE + -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to up. + -- @param DCS#Distance z The z coordinate of the Vec3 point, pointing to the right. + -- @return #COORDINATE self function COORDINATE:New( x, y, z ) - --env.info("FF COORDINATE New") - local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE + local self=BASE:Inherit(self, BASE:New()) -- #COORDINATE + self.x = x self.y = y self.z = z @@ -249,7 +276,7 @@ do -- COORDINATE --- COORDINATE constructor. -- @param #COORDINATE self -- @param #COORDINATE Coordinate. - -- @return #COORDINATE + -- @return #COORDINATE self function COORDINATE:NewFromCoordinate( Coordinate ) local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE @@ -263,8 +290,8 @@ do -- COORDINATE --- Create a new COORDINATE object from Vec2 coordinates. -- @param #COORDINATE self -- @param DCS#Vec2 Vec2 The Vec2 point. - -- @param DCS#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. - -- @return #COORDINATE + -- @param DCS#Distance LandHeightAdd (Optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. + -- @return #COORDINATE self function COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) local LandHeight = land.getHeight( Vec2 ) @@ -274,8 +301,6 @@ do -- COORDINATE local self = self:New( Vec2.x, LandHeight, Vec2.y ) -- #COORDINATE - self:F2( self ) - return self end @@ -283,7 +308,7 @@ do -- COORDINATE --- Create a new COORDINATE object from Vec3 coordinates. -- @param #COORDINATE self -- @param DCS#Vec3 Vec3 The Vec3 point. - -- @return #COORDINATE + -- @return #COORDINATE self function COORDINATE:NewFromVec3( Vec3 ) local self = self:New( Vec3.x, Vec3.y, Vec3.z ) -- #COORDINATE @@ -292,6 +317,22 @@ do -- COORDINATE return self end + + --- Create a new COORDINATE object from a waypoint. This uses the components + -- + -- * `waypoint.x` + -- * `waypoint.alt` + -- * `waypoint.y` + -- + -- @param #COORDINATE self + -- @param DCS#Waypoint Waypoint The waypoint. + -- @return #COORDINATE self + function COORDINATE:NewFromWaypoint(Waypoint) + + local self=self:New(Waypoint.x, Waypoint.alt, Waypoint.y) -- #COORDINATE + + return self + end --- Return the coordinates itself. Sounds stupid but can be useful for compatibility. -- @param #COORDINATE self @@ -1720,7 +1761,7 @@ do -- COORDINATE return self:GetSurfaceType()==land.SurfaceType.LAND end - --- Checks if the surface type is road. + --- Checks if the surface type is land. -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is land. function COORDINATE:IsSurfaceTypeLand() @@ -2082,7 +2123,7 @@ do -- COORDINATE --- Circle to all. -- Creates a circle on the map with a given radius, color, fill color, and outline. -- @param #COORDINATE self - -- @param #numberr Radius Radius in meters. Default 1000 m. + -- @param #number Radius Radius in meters. Default 1000 m. -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 9506a270b..b0c0b2c91 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -141,7 +141,7 @@ function ARMYGROUP:New(group) -- Init waypoints. - self:InitWaypoints() + self:_InitWaypoints() -- Initialize the group. self:_InitGroup() @@ -1115,8 +1115,9 @@ end --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. -- @param #ARMYGROUP self +-- @param #table Template Template used to init the group. Default is `self.template`. -- @return #ARMYGROUP self -function ARMYGROUP:_InitGroup() +function ARMYGROUP:_InitGroup(Template) -- First check if group was already initialized. if self.groupinitialized then @@ -1125,7 +1126,7 @@ function ARMYGROUP:_InitGroup() end -- Get template of group. - self.template=self.group:GetTemplate() + local template=Template or self:_GetTemplate() -- Define category. self.isAircraft=false @@ -1136,7 +1137,7 @@ function ARMYGROUP:_InitGroup() self.isAI=true -- Is (template) group late activated. - self.isLateActivated=self.template.lateActivation + self.isLateActivated=template.lateActivation -- Ground groups cannot be uncontrolled. self.isUncontrolled=false diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 5b15572ee..a10db29bf 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -306,7 +306,7 @@ function FLIGHTGROUP:New(group) self:HandleEvent(EVENTS.Kill, self.OnEventKill) -- Init waypoints. - self:InitWaypoints() + self:_InitWaypoints() -- Initialize group. self:_InitGroup() @@ -1478,7 +1478,7 @@ function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase) self:_UpdateStatus(Element, OPSGROUP.ElementStatus.TAKEOFF, airbase) -- Trigger element airborne event. - self:__ElementAirborne(0.1, Element) + self:__ElementAirborne(0.01, Element) end @@ -1750,7 +1750,7 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) self.currbase=nil -- Cruising. - self:__Cruise(-0.05) + self:__Cruise(-0.01) end @@ -1882,7 +1882,7 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) SpawnPoint.airdromeId = nil -- Airbase. - local airbase=self.isLandingAtAirbase + local airbase=self.isLandingAtAirbase --Wrapper.Airbase#AIRBASE -- Get airbase ID and category. local AirbaseID = airbase:GetID() @@ -1909,7 +1909,7 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) local unit=units[i] local element=self:GetElementByName(unit.name) if element and element.status~=OPSGROUP.ElementStatus.DEAD then - unit.parking=element.parking.TerminalID + unit.parking=element.parking and element.parking.TerminalID or nil unit.parking_id=nil local vec3=element.unit:GetVec3() local heading=element.unit:GetHeading() @@ -2415,6 +2415,10 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Passed final waypoint! self.passedfinalwp=true + + -- Not waiting any more. + self.Twaiting=nil + self.dTwait=nil -- Defaults: SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise) @@ -2469,7 +2473,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Set holding flag to 0=false. self.flaghold:Set(0) - local holdtime=1*60 + local holdtime=2*60 if fc or self.airboss then holdtime=nil end @@ -3002,8 +3006,9 @@ end --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. -- @param #FLIGHTGROUP self +-- @param #table Template Template used to init the group. Default is `self.template`. -- @return #FLIGHTGROUP self -function FLIGHTGROUP:_InitGroup() +function FLIGHTGROUP:_InitGroup(Template) -- First check if group was already initialized. if self.groupinitialized then @@ -3015,7 +3020,7 @@ function FLIGHTGROUP:_InitGroup() local group=self.group --Wrapper.Group#GROUP -- Get template of group. - self.template=group:GetTemplate() + local template=Template or self:_GetTemplate() -- Define category. self.isAircraft=true @@ -3026,10 +3031,10 @@ function FLIGHTGROUP:_InitGroup() self.isHelo=group:IsHelicopter() -- Is (template) group uncontrolled. - self.isUncontrolled=self.template.uncontrolled + self.isUncontrolled=template.uncontrolled -- Is (template) group late activated. - self.isLateActivated=self.template.lateActivation + self.isLateActivated=template.lateActivation -- Max speed in km/h. self.speedMax=group:GetSpeedMax() @@ -3044,12 +3049,12 @@ function FLIGHTGROUP:_InitGroup() self.ammo=self:GetAmmoTot() -- Radio parameters from template. Default is set on spawn if not modified by user. - self.radio.Freq=tonumber(self.template.frequency) - self.radio.Modu=tonumber(self.template.modulation) - self.radio.On=self.template.communication + self.radio.Freq=tonumber(template.frequency) + self.radio.Modu=tonumber(template.modulation) + self.radio.On=template.communication -- Set callsign. Default is set on spawn if not modified by user. - local callsign=self.template.units[1].callsign + local callsign=template.units[1].callsign if type(callsign)=="number" then -- Sometimes callsign is just "101". local cs=tostring(callsign) callsign={} @@ -3453,7 +3458,7 @@ function FLIGHTGROUP:IsLandingAirbase(wp) if wp then - if wp.action and wp.action==COORDINATE.WaypointAction.LANDING then + if wp.action and wp.action==COORDINATE.WaypointAction.Landing then return true else return false diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 204792dcf..a1209e0db 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -165,7 +165,7 @@ function NAVYGROUP:New(group) -- Init waypoints. - self:InitWaypoints() + self:_InitWaypoints() -- Initialize the group. self:_InitGroup() @@ -1130,8 +1130,9 @@ end --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. -- @param #NAVYGROUP self +-- @param #table Template Template used to init the group. Default is `self.template`. -- @return #NAVYGROUP self -function NAVYGROUP:_InitGroup() +function NAVYGROUP:_InitGroup(Template) -- First check if group was already initialized. if self.groupinitialized then @@ -1140,7 +1141,7 @@ function NAVYGROUP:_InitGroup() end -- Get template of group. - self.template=self.group:GetTemplate() + local template=Template or self:_GetTemplate() -- Define category. self.isAircraft=false @@ -1154,7 +1155,7 @@ function NAVYGROUP:_InitGroup() self.isAI=true -- Is (template) group late activated. - self.isLateActivated=self.template.lateActivation + self.isLateActivated=template.lateActivation -- Naval groups cannot be uncontrolled. self.isUncontrolled=false @@ -1170,8 +1171,8 @@ function NAVYGROUP:_InitGroup() -- Radio parameters from template. Default is set on spawn if not modified by the user. self.radio.On=true -- Radio is always on for ships. - self.radio.Freq=tonumber(self.template.units[1].frequency)/1000000 - self.radio.Modu=tonumber(self.template.units[1].modulation) + self.radio.Freq=tonumber(template.units[1].frequency)/1000000 + self.radio.Modu=tonumber(template.units[1].modulation) -- Set default formation. No really applicable for ships. self.optionDefault.Formation="Off Road" diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 69b5c1d01..6983f7dba 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -375,6 +375,12 @@ OPSGROUP.TaskType={ -- @field #number MissilesBM Amount of ballistic missiles. -- @field #number MissilesSA Amount of surfe-to-air missiles. +--- Spawn point data. +-- @type OPSGROUP.Spawnpoint +-- @field Core.Point#COORDINATE Coordinate Coordinate where to spawn +-- @field Wrapper.Airbase#AIRBASE Airport Airport where to spawn. +-- @field #table TerminalIDs Terminal IDs, where to spawn the group. It is a table of `#number`s because a group can consist of multiple units. + --- Waypoint data. -- @type OPSGROUP.Waypoint -- @field #number uid Waypoint's unit id, which is a running number. @@ -503,6 +509,9 @@ function OPSGROUP:New(group) return nil end end + + -- Set the template. + self:_SetTemplate() -- Init set of detected units. self.detectedunits=SET_UNIT:New() @@ -3881,7 +3890,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On before "Wait" event. --- @param #FLIGHTGROUP self +-- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. @@ -4787,7 +4796,7 @@ end -- @param #OPSGROUP self -- @param #number Delay Delay in seconds before respawn happens. Default 0. -- @param DCS#Template Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. --- @param #boolean Reset Reset positions if TRUE. +-- @param #boolean Reset Reset waypoints and reinit group if `true`. -- @return #OPSGROUP self function OPSGROUP:_Respawn(Delay, Template, Reset) @@ -4799,7 +4808,7 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) self:T2(self.lid.."FF _Respawn") -- Given template or get old. - Template=Template or UTILS.DeepCopy(self.template) + Template=Template or self:_GetTemplate(true) if self:IsAlive() then @@ -4807,6 +4816,8 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Group is ALIVE --- + --[[ + -- Get units. local units=self.group:GetUnits() @@ -4828,6 +4839,29 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) end end + + ]] + + local units=Template.units + + for i=#units,1,-1 do + local unit=units[i] + local element=self:GetElementByName(unit.name) + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + unit.parking=element.parking and element.parking.TerminalID or unit.parking + unit.parking_id=nil + local vec3=element.unit:GetVec3() + local heading=element.unit:GetHeading() + unit.x=vec3.x + unit.y=vec3.z + unit.alt=vec3.y + unit.heading=math.rad(heading) + unit.psi=-unit.heading + else + table.remove(units, i) + end + end + -- Despawn old group. Dont trigger any remove unit event since this is a respawn. self:Despawn(0, true) @@ -4843,28 +4877,6 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) local element=_element --#OPSGROUP.Element self:ElementInUtero(element) end - - - --[[ - - -- Loop over template units. - for UnitID, Unit in pairs(Template.units) do - - local element=self:GetElementByName(Unit.name) - - if element then - local vec3=element.vec3 - local heading=element.heading - Unit.x=vec3.x - Unit.y=vec3.z - Unit.alt=vec3.y - Unit.heading=math.rad(heading) - Unit.psi=-Unit.heading - end - - end - - ]] end @@ -4881,10 +4893,18 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Not dead or destroyed any more. self.isDead=false self.isDestroyed=false - self.Ndestroyed=0 - self:InitWaypoints() - self:_InitGroup() + + self.groupinitialized=false + self.Ndestroyed=0 + self.wpcounter=1 + self.currentwp=1 + + -- Init waypoints. + self:_InitWaypoints() + + -- Init Group. + self:_InitGroup() -- Reset events. --self:ResetEvents() @@ -5827,6 +5847,10 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Add waypoint. if self.isFlightgroup then + + --- + -- Flight Group + --- if airbasePickup then @@ -5872,7 +5896,9 @@ function OPSGROUP:onafterPickup(From, Event, To) elseif self.isNavygroup then + --- -- Navy Group + --- local cwp=self:GetWaypointCurrent() local uid=cwp and cwp.uid or nil @@ -5898,7 +5924,9 @@ function OPSGROUP:onafterPickup(From, Event, To) elseif self.isArmygroup then + --- -- Army Group + --- local cwp=self:GetWaypointCurrent() local uid=cwp and cwp.uid or nil @@ -5972,12 +6000,12 @@ function OPSGROUP:onafterLoading(From, Event, To) else -- Debug info. - self:T(self.lid.."Cannot board carrier!") + self:T(self.lid..string.format("Cannot board carrier! Group %s is NOT (yet) in zone %s", cargo.opsgroup:GetName(), self.cargoTransport.embarkzone:GetName())) end else -- Debug info. - self:T(self.lid.."Cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) + self:T(self.lid..string.format("Cargo %s NOT in embark zone %s", cargo.opsgroup:GetName(), self.cargoTransport.embarkzone:GetName())) end end @@ -7251,6 +7279,7 @@ function OPSGROUP:_CreateWaypoint(waypoint) waypoint.patrol=false waypoint.detour=false waypoint.astar=false + waypoint.temp=false -- Increase UID counter. self.wpcounter=self.wpcounter+1 @@ -7271,33 +7300,40 @@ function OPSGROUP:_AddWaypoint(waypoint, wpnumber) table.insert(self.waypoints, wpnumber, waypoint) -- Debug info. - self:T(self.lid..string.format("Adding waypoint at index=%d id=%d", wpnumber, waypoint.uid)) + self:T(self.lid..string.format("Adding waypoint at index=%d with UID=%d", wpnumber, waypoint.uid)) -- Now we obviously did not pass the final waypoint. - self.passedfinalwp=false - - -- Switch to cruise mode. - if self:IsHolding() then - -- Disable this for now. Cruise has to be commanded manually now. If group is ordered to hold, it will hold until told to move again. - --self:Cruise() + if self.currentwp and wpnumber>self.currentwp then + self.passedfinalwp=false end + end --- Initialize Mission Editor waypoints. -- @param #OPSGROUP self +-- @param #number WpIndexMin +-- @param #number WpIndexMax -- @return #OPSGROUP self -function OPSGROUP:InitWaypoints() +function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) -- Template waypoints. self.waypoints0=self.group:GetTemplateRoutePoints() - -- Waypoints + -- Waypoints empty! self.waypoints={} + + WpIndexMin=WpIndexMin or 1 + WpIndexMax=WpIndexMax or #self.waypoints0 + WpIndexMax=math.min(WpIndexMax, #self.waypoints0) --Ensure max is not out of bounce. - for index,wp in pairs(self.waypoints0) do + --for index,wp in pairs(self.waypoints0) do + + for i=WpIndexMin,WpIndexMax do + + local wp=self.waypoints0[i] --DCS#Waypoint -- Coordinate of the waypoint. - local coordinate=COORDINATE:New(wp.x, wp.alt, wp.y) + local coordinate=COORDINATE:NewFromWaypoint(wp) -- Strange! wp.speed=wp.speed or 0 @@ -7305,17 +7341,49 @@ function OPSGROUP:InitWaypoints() -- Speed at the waypoint. local speedknots=UTILS.MpsToKnots(wp.speed) - if index==1 then + if i==1 then self.speedWp=wp.speed end + + local waypoint=self:_CreateWaypoint(wp) + + self:_AddWaypoint(waypoint) -- Add waypoint. - self:AddWaypoint(coordinate, speedknots, index-1, nil, false) - + --[[ + if self:IsFlightgroup() then + FLIGHTGROUP.AddWaypoint(self, coordinate, speedknots, index-1, Altitude, false) + elseif self:IsArmygroup() then + ARMYGROUP.AddWaypoint(self, coordinate, speedknots, index-1, Formation, false) + elseif self:IsNavygroup() then + NAVYGROUP.AddWaypoint(self, coordinate, speedknots, index-1, Depth, false) + else + -- Should not happen! + self:AddWaypoint(coordinate, speedknots, index-1, nil, false) + end + ]] + end -- Debug info. self:T(self.lid..string.format("Initializing %d waypoints", #self.waypoints)) + + -- Flight group specific. + if self:IsFlightgroup() then + + -- Get home and destination airbases from waypoints. + self.homebase=self.homebase or self:GetHomebaseFromWaypoints() + self.destbase=self.destbase or self:GetDestinationFromWaypoints() + self.currbase=self:GetHomebaseFromWaypoints() + + -- Remove the landing waypoint. We use RTB for that. It makes adding new waypoints easier as we do not have to check if the last waypoint is the landing waypoint. + if self.destbase and #self.waypoints>1 then + table.remove(self.waypoints, #self.waypoints) + else + self.destbase=self.homebase + end + + end -- Update route. if #self.waypoints>0 then @@ -7324,7 +7392,9 @@ function OPSGROUP:InitWaypoints() if #self.waypoints==1 then self.passedfinalwp=true end - + + else + self:E(self.lid.."WARNING: No waypoints initialized. Number of waypoints is 0!") end return self @@ -9219,6 +9289,42 @@ function OPSGROUP:_AddElementByName(unitname) return nil end +--- Set the template of the group. +-- @param #OPSGROUP self +-- @param #table Template Template to set. Default is from the GROUP. +-- @return #OPSGROUP self +function OPSGROUP:_SetTemplate(Template) + + self.template=Template or self.group:GetTemplate() + + self:I(self.lid.."Setting group template") + + return self +end + +--- Get the template of the group. +-- @param #OPSGROUP self +-- @param #boolean Copy Get a deep copy of the template. +-- @return #table Template table. +function OPSGROUP:_GetTemplate(Copy) + + if self.template then + + if Copy then + local template=UTILS.DeepCopy(self.template) + return template + else + return self.template + end + + else + self:E(self.lid..string.format("ERROR: No template was set yet!")) + end + + return nil +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 65e852f341cd8c6826a60afba42c4bfa39012e9b Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 24 Jul 2021 01:06:44 +0200 Subject: [PATCH 055/141] OPS Transport --- Moose Development/Moose/Ops/Auftrag.lua | 7 ++- Moose Development/Moose/Ops/OpsGroup.lua | 78 ++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 4c55f5119..6dd08b4e6 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -3149,8 +3149,9 @@ end --- Get coordinate of target. First unit/group of the set is used. -- @param #AUFTRAG self -- @param Wrapper.Group#GROUP group Group. +-- @param #number randomradius Random radius in meters. -- @return Core.Point#COORDINATE Coordinate where the mission is executed. -function AUFTRAG:GetMissionWaypointCoord(group) +function AUFTRAG:GetMissionWaypointCoord(group, randomradius) -- Check if a coord has been explicitly set. if self.missionWaypointCoord then @@ -3166,7 +3167,9 @@ function AUFTRAG:GetMissionWaypointCoord(group) local alt=waypointcoord.y -- Add some randomization. - waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), 1000):GetRandomCoordinate():SetAltitude(alt, false) + if randomradius then + waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), randomradius):GetRandomCoordinate():SetAltitude(alt, false) + end -- Set altitude of mission waypoint. if self.missionAltitude then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 6983f7dba..a4ec949b2 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3009,6 +3009,7 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Parameters. local zone=Task.dcstask.params.zone --Core.Zone#ZONE local Coordinate=zone:GetRandomCoordinate() + Coordinate:MarkToAll("Random Patrol Zone Coordinate") local Speed=UTILS.KmphToKnots(Task.dcstask.params.speed or self.speedCruise) local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude) or nil @@ -3695,15 +3696,21 @@ function OPSGROUP:RouteToMission(mission, delay) self:ScheduleOnce(delay, OPSGROUP.RouteToMission, self, mission) else - if self:IsDead() then + if self:IsDead() or self:IsStopped() then return end -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid - + + -- Random radius. + local randomradius=1000 + if mission.type==AUFTRAG.Type.PATROLZONE then + randomradius=nil + end + -- Get coordinate where the mission is executed. - local waypointcoord=mission:GetMissionWaypointCoord(self.group) + local waypointcoord=mission:GetMissionWaypointCoord(self.group, randomradius) -- Add enroute tasks. for _,task in pairs(mission.enrouteTasks) do @@ -5423,6 +5430,39 @@ function OPSGROUP:_CheckDelivered(CargoTransport) return done end + +--- Check if all cargo of this transport assignment was delivered. +-- @param #OPSGROUP self +-- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport The next due cargo transport or `nil`. +-- @return #boolean If true, all cargo was delivered. +function OPSGROUP:_CheckGoPickup(CargoTransport) + + local done=true + for _,_cargo in pairs(CargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if self:CanCargo(cargo.opsgroup) then + + if cargo.delivered then + -- This one is delivered. + elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + -- This one is dead. + elseif cargo.opsgroup:IsLoaded() then + -- This one is loaded into a(nother) carrier. + else + done=false --Someone is not done! + end + + end + + end + + -- Debug info. + self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport:GetState(), tostring(done))) + + return done +end + --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The troop transport assignment. @@ -5517,6 +5557,32 @@ function OPSGROUP:GetFreeCargobay(UnitName, IncludeReserved) return Free end +--- Get relative free cargo bay in percent. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @param #boolean IncludeReserved If `false`, cargo weight that is only *reserved* is **not** counted. By default (`true` or `nil`), the reserved cargo is included. +-- @return #number Free cargo bay in percent. +function OPSGROUP:GetFreeCargobayRelative(UnitName, IncludeReserved) + + local free=self:GetFreeCargobay(UnitName, IncludeReserved) + + local total=self:GetWeightCargoMax(UnitName) + + local percent=free/total*100 + + return percent +end + +--- Get relative used (loaded) cargo bay in percent. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @param #boolean IncludeReserved If `false`, cargo weight that is only *reserved* is **not** counted. By default (`true` or `nil`), the reserved cargo is included. +-- @return #number Used cargo bay in percent. +function OPSGROUP:GetUsedCargobayRelative(UnitName, IncludeReserved) + local free=self:GetFreeCargobayRelative(UnitName, IncludeReserved) + return 100-free +end + --- Get max weight of cargo (group) this group can load. This is the largest free cargo bay of any (not dead) element of the group. -- Optionally, you can calculate the current max weight possible, which accounts for currently loaded cargo. -- @param #OPSGROUP self @@ -6563,7 +6629,7 @@ function OPSGROUP:onafterUnloaded(From, Event, To) end -- Check everything was delivered (or is dead). - local delivered=self:_CheckDelivered(self.cargoTransport) + local delivered=self:_CheckGoPickup(self.cargoTransport) if not delivered then @@ -9295,9 +9361,11 @@ end -- @return #OPSGROUP self function OPSGROUP:_SetTemplate(Template) + -- Set the template. self.template=Template or self.group:GetTemplate() - self:I(self.lid.."Setting group template") + -- Debug info. + self:T3(self.lid.."Setting group template") return self end From c718584755960bb7c312072b49466befaed51511 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 24 Jul 2021 23:25:26 +0200 Subject: [PATCH 056/141] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index a4ec949b2..b3c912dfa 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6087,12 +6087,18 @@ end --- Clear waypoints. -- @param #OPSGROUP self -function OPSGROUP:ClearWaypoints() +-- @param #number IndexMin Clear waypoints up to this min WP index. Default 1. +-- @param #number IndexMax Clear waypoints up to this max WP index. Default `#self.waypoints`. +function OPSGROUP:ClearWaypoints(IndexMin, IndexMax) + + IndexMin=IndexMin or 1 + IndexMax=IndexMax or #self.waypoints + -- Clear all waypoints. - for i=1,#self.waypoints do + for i=IndexMax,IndexMin,-1 do table.remove(self.waypoints, i) end - self.waypoints={} + --self.waypoints={} end --- Set (new) cargo status. @@ -6768,7 +6774,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) local Coordinate=Carrier.unit:GetCoordinate() -- Clear all waypoints. - self:ClearWaypoints() + self:ClearWaypoints(self.currentwp+1) if self.isArmygroup then local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=0 From 615a220acb19322624d3de1e35ccd3ece071f815 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 24 Jul 2021 23:56:27 +0200 Subject: [PATCH 057/141] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index b3c912dfa..78885d372 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -5447,7 +5447,7 @@ function OPSGROUP:_CheckGoPickup(CargoTransport) -- This one is delivered. elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then -- This one is dead. - elseif cargo.opsgroup:IsLoaded() then + elseif cargo.opsgroup:IsLoaded(CargoTransport:_GetCarrierNames()) then -- This one is loaded into a(nother) carrier. else done=false --Someone is not done! From d64de26ded20fa60375c21fca5ab8dc97f516237 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 29 Jul 2021 13:43:29 +0200 Subject: [PATCH 058/141] OPS - many fixes and improvements --- .../Moose/Functional/Warehouse.lua | 14 +- Moose Development/Moose/Ops/AirWing.lua | 4 +- Moose Development/Moose/Ops/Airboss.lua | 10 +- Moose Development/Moose/Ops/ArmyGroup.lua | 36 ++-- Moose Development/Moose/Ops/Auftrag.lua | 57 ++++-- Moose Development/Moose/Ops/FlightGroup.lua | 167 +++++++----------- Moose Development/Moose/Ops/NavyGroup.lua | 38 ++-- Moose Development/Moose/Ops/OpsGroup.lua | 143 +++++++++++---- Moose Development/Moose/Ops/OpsTransport.lua | 4 +- Moose Development/Moose/Ops/Squadron.lua | 17 +- 10 files changed, 288 insertions(+), 202 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index cf02c728f..1c571b93c 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -4882,6 +4882,13 @@ function WAREHOUSE:onbeforeArrived(From, Event, To, group) local asset=self:FindAssetInDB(group) if asset then + + if asset.flightgroup and not asset.arrived then + --env.info("FF asset has a flightgroup. arrival will be handled there!") + asset.arrived=true + return false + end + if asset.arrived==true then -- Asset already arrived (e.g. if multiple units trigger the event via landing). return false @@ -4889,6 +4896,7 @@ function WAREHOUSE:onbeforeArrived(From, Event, To, group) asset.arrived=true --ensure this is not called again from the same asset group. return true end + end end @@ -6042,7 +6050,7 @@ function WAREHOUSE:_RouteGround(group, request) end for n,wp in ipairs(Waypoints) do - env.info(n) + --env.info(n) local tf=self:_SimpleTaskFunctionWP("warehouse:_PassingWaypoint",group, n, #Waypoints) group:SetTaskWaypoint(wp, tf) end @@ -9046,11 +9054,11 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) -- Hot start. if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then - env.info("FF hot") + --env.info("FF hot") _type=COORDINATE.WaypointType.TakeOffParkingHot _action=COORDINATE.WaypointAction.FromParkingAreaHot else - env.info("FF cold") + --env.info("FF cold") end diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 4b44b6b91..5e702896b 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -612,9 +612,9 @@ function AIRWING:RemoveAssetFromSquadron(Asset) end end ---- Add mission to queue. +--- Add a mission for the airwing. The airwing will pick the best available assets for the mission and lauch it when ready. -- @param #AIRWING self --- @param Ops.Auftrag#AUFTRAG Mission for this group. +-- @param Ops.Auftrag#AUFTRAG Mission Mission for this airwing. -- @return #AIRWING self function AIRWING:AddMission(Mission) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index c3a45c8c5..e0cff612e 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -6118,6 +6118,11 @@ function AIRBOSS:_ScanCarrierZone() -- Get flight group if possible. local knownflight=self:_GetFlightFromGroupInQueue(group, self.flights) + + -- Unknown new AI flight. Create a new flight group. + if not knownflight and not self:_IsHuman(group) then + knownflight=self:_CreateFlightGroup(group) + end -- Get aircraft type name. local actype=group:GetTypeName() @@ -6175,10 +6180,7 @@ function AIRBOSS:_ScanCarrierZone() else - -- Unknown new AI flight. Create a new flight group. - if not self:_IsHuman(group) then - self:_CreateFlightGroup(group) - end + end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index b0c0b2c91..2bcef539a 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -538,6 +538,24 @@ end function ARMYGROUP:onafterSpawned(From, Event, To) self:T(self.lid..string.format("Group spawned!")) + -- Debug info. + if self.verbose>=1 then + local text=string.format("Initialized Army Group %s:\n", self.groupname) + text=text..string.format("Unit type = %s\n", self.actype) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) + text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) + text=text..string.format("Elements = %d\n", #self.elements) + text=text..string.format("Waypoints = %d\n", #self.waypoints) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) + text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles) + text=text..string.format("FSM state = %s\n", self:GetState()) + text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) + text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) + self:I(self.lid..text) + end + -- Update position. self:_UpdatePosition() @@ -1179,24 +1197,6 @@ function ARMYGROUP:_InitGroup(Template) -- Set type name. self.actype=units[1]:GetTypeName() - - -- Debug info. - if self.verbose>=1 then - local text=string.format("Initialized Army Group %s:\n", self.groupname) - text=text..string.format("Unit type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) - text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) - text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) - text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) - text=text..string.format("Elements = %d\n", #self.elements) - text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) - text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles) - text=text..string.format("FSM state = %s\n", self:GetState()) - text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) - text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) - self:I(self.lid..text) - end -- Init done. self.groupinitialized=true diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 6dd08b4e6..c9bd4ca98 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -7,7 +7,7 @@ -- * Set mission start/stop times -- * Set mission priority and urgency (can cancel running missions) -- * Specific mission options for ROE, ROT, formation, etc. --- * Compatible with FLIGHTGROUP, NAVYGROUP, ARMYGROUP, AIRWING, WINGCOMMANDER and CHIEF classes +-- * Compatible with OPS classes like FLIGHTGROUP, NAVYGROUP, ARMYGROUP, AIRWING, etc. -- * FSM events when a mission is done, successful or failed -- -- === @@ -106,6 +106,7 @@ -- @field #number missionFraction Mission coordiante fraction. Default is 0.5. -- @field #number missionRange Mission range in meters. Used in AIRWING class. -- @field Core.Point#COORDINATE missionWaypointCoord Mission waypoint coordinate. +-- @field Core.Point#COORDINATE missionEgressCoord Mission egress waypoint coordinate. -- -- @field #table enrouteTasks Mission enroute tasks. -- @@ -434,8 +435,9 @@ AUFTRAG.TargetType={ --- Group specific data. Each ops group subscribed to this mission has different data for this. -- @type AUFTRAG.GroupData -- @field Ops.OpsGroup#OPSGROUP opsgroup The OPS group. --- @field Core.Point#COORDINATE waypointcoordinate Waypoint coordinate. +-- @field Core.Point#COORDINATE waypointcoordinate Ingress waypoint coordinate. -- @field #number waypointindex Waypoint index. +-- @field Core.Point#COORDINATE wpegresscoordinate Egress waypoint coordinate. -- @field Ops.OpsGroup#OPSGROUP.Task waypointtask Waypoint task. -- @field #string status Group mission status. -- @field Ops.AirWing#AIRWING.SquadronAsset asset The squadron asset. @@ -443,7 +445,7 @@ AUFTRAG.TargetType={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.6.1" +AUFTRAG.version="0.7.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -938,7 +940,7 @@ end --- Create a STRIKE mission. Flight will attack the closest map object to the specified coordinate. -- @param #AUFTRAG self --- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT or STATIC object. +-- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC or TARGET object. -- @param #number Altitude Engage altitude in feet. Default 2000 ft. -- @return #AUFTRAG self function AUFTRAG:NewSTRIKE(Target, Altitude) @@ -966,7 +968,7 @@ end --- Create a BOMBING mission. Flight will drop bombs a specified coordinate. -- @param #AUFTRAG self --- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT or STATIC object. +-- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC or TARGET object. -- @param #number Altitude Engage altitude in feet. Default 25000 ft. -- @return #AUFTRAG self function AUFTRAG:NewBOMBING(Target, Altitude) @@ -1006,10 +1008,6 @@ function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude) if type(Airdrome)=="string" then Airdrome=AIRBASE:FindByName(Airdrome) end - - if Airdrome:IsInstanceOf("AIRBASE") then - - end local mission=AUFTRAG:New(AUFTRAG.Type.BOMBRUNWAY) @@ -1110,9 +1108,7 @@ end function AUFTRAG:NewRESCUEHELO(Carrier) local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) - - --mission.carrier=Carrier - + mission:_TargetFromObject(Carrier) -- Mission options: @@ -3138,12 +3134,45 @@ function AUFTRAG:GetMissionTypesText(MissionTypes) return text end ---- Set the mission waypoint coordinate where the mission is executed. +--- Set the mission waypoint coordinate where the mission is executed. Note that altitude is set via `:SetMissionAltitude`. -- @param #AUFTRAG self --- @return Core.Point#COORDINATE Coordinate where the mission is executed. +-- @param Core.Point#COORDINATE Coordinate Coordinate where the mission is executed. -- @return #AUFTRAG self function AUFTRAG:SetMissionWaypointCoord(Coordinate) + + -- Obviously a zone was passed. We get the coordinate. + if Coordinate:IsInstanceOf("ZONE_BASE") then + Coordinate=Coordinate:GetCoordinate() + end + self.missionWaypointCoord=Coordinate + return self +end + +--- Set the mission egress coordinate. This is the coordinate where the assigned group will go once the mission is finished. +-- @param #AUFTRAG self +-- @param Core.Point#COORDINATE Coordinate Egrees coordinate. +-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate. +-- @return #AUFTRAG self +function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude) + + -- Obviously a zone was passed. We get the coordinate. + if Coordinate:IsInstanceOf("ZONE_BASE") then + Coordinate=Coordinate:GetCoordinate() + end + + self.missionEgressCoord=Coordinate + + if Altitude then + self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude) + end +end + +--- Get the mission egress coordinate if this was defined. +-- @param #AUFTRAG self +-- @return Core.Point#COORDINATE Coordinate Coordinate or nil. +function AUFTRAG:GetMissionEgressCoord() + return self.missionEgressCoord end --- Get coordinate of target. First unit/group of the set is used. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index a10db29bf..c7a7e89c5 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -134,8 +134,8 @@ FLIGHTGROUP = { fuelcritical = nil, fuelcriticalthresh = nil, fuelcriticalrtb = false, - outofAAMrtb = true, - outofAGMrtb = true, + outofAAMrtb = false, + outofAGMrtb = false, squadron = nil, flightcontrol = nil, flaghold = nil, @@ -171,10 +171,6 @@ FLIGHTGROUP.Attribute = { OTHER="Other", } ---- Flight group element. --- @type FLIGHTGROUP.Element --- @extends Ops.OpsGroup#OPSGROUP.Element - --- FLIGHTGROUP class version. -- @field #string version FLIGHTGROUP.version="0.7.0" @@ -251,9 +247,9 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "FuelLow", "*") -- Fuel state of group is low. Default ~25%. self:AddTransition("*", "FuelCritical", "*") -- Fuel state of group is critical. Default ~10%. - self:AddTransition("*", "OutOfMissilesAA", "*") -- Group is out of A2A missiles. - self:AddTransition("*", "OutOfMissilesAG", "*") -- Group is out of A2G missiles. - self:AddTransition("*", "OutOfMissilesAS", "*") -- Group is out of A2S(ship) missiles. Not implemented yet! + self:AddTransition("*", "OutOfMissilesAA", "*") -- Group is out of A2A (air) missiles. + self:AddTransition("*", "OutOfMissilesAG", "*") -- Group is out of A2G (ground) missiles. + self:AddTransition("*", "OutOfMissilesAS", "*") -- Group is out of A2S (ship) missiles. self:AddTransition("Airborne", "EngageTarget", "Engaging") -- Engage targets. self:AddTransition("Engaging", "Disengage", "Airborne") -- Engagement over. @@ -1036,6 +1032,9 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) self:FuelCritical() end + -- This causes severe problems as OutOfMissiles is called over and over again leading to many RTB calls. + if false then + -- Out of AA Missiles? CAP, GCICAP, INTERCEPT local CurrIsCap = false -- Out of AG Missiles? BAI, SEAD, CAS, STRIKE @@ -1057,6 +1056,8 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if (not self:CanAirToGround(false)) and CurrIsA2G then self:OutOfMissilesAG() end + + end end @@ -1595,6 +1596,34 @@ end -- @param #string To To state. function FLIGHTGROUP:onafterSpawned(From, Event, To) self:T(self.lid..string.format("Flight spawned")) + + -- Debug info. + if self.verbose>=1 then + local text=string.format("Initialized Flight Group %s:\n", self.groupname) + text=text..string.format("Unit type = %s\n", self.actype) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) + text=text..string.format("Range max = %.1f km\n", self.rangemax/1000) + text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) + text=text..string.format("Tanker type = %s\n", tostring(self.tankertype)) + text=text..string.format("Refuel type = %s\n", tostring(self.refueltype)) + text=text..string.format("AI = %s\n", tostring(self.isAI)) + text=text..string.format("Helicopter = %s\n", tostring(self.group:IsHelicopter())) + text=text..string.format("Elements = %d\n", #self.elements) + text=text..string.format("Waypoints = %d\n", #self.waypoints) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) + text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) + text=text..string.format("FSM state = %s\n", self:GetState()) + text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) + text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) + text=text..string.format("Uncontrolled = %s\n", tostring(self:IsUncontrolled())) + text=text..string.format("Start Air = %s\n", tostring(self:IsTakeoffAir())) + text=text..string.format("Start Cold = %s\n", tostring(self:IsTakeoffCold())) + text=text..string.format("Start Hot = %s\n", tostring(self:IsTakeoffHot())) + text=text..string.format("Start Rwy = %s\n", tostring(self:IsTakeoffRunway())) + self:I(self.lid..text) + end -- Update position. self:_UpdatePosition() @@ -1858,9 +1887,10 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.ARRIVED) end - -- Despawn in 5 min. + -- Check what to do. if self.airwing then - -- Let airwing do its thing. + -- Add the asset back to the airwing. + self.airwing:AddAsset(self.group, 1) elseif self.isLandingAtAirbase then local Template=UTILS.DeepCopy(self.template) --DCS#Template @@ -1963,7 +1993,7 @@ function FLIGHTGROUP:onafterDead(From, Event, To) else if self.airwing then -- Not all assets were destroyed (despawn) ==> Add asset back to airwing. - self.airwing:AddAsset(self.group, 1) + --self.airwing:AddAsset(self.group, 1) end end @@ -2131,7 +2161,7 @@ function FLIGHTGROUP:onafterOutOfMissilesAA(From, Event, To) if self.outofAAMrtb then -- Back to destination or home. local airbase=self.destbase or self.homebase - self:__RTB(-5,airbase) + self:__RTB(-5, airbase) end end @@ -2145,7 +2175,7 @@ function FLIGHTGROUP:onafterOutOfMissilesAG(From, Event, To) if self.outofAGMrtb then -- Back to destination or home. local airbase=self.destbase or self.homebase - self:__RTB(-5,airbase) + self:__RTB(-5, airbase) end end @@ -2288,20 +2318,31 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) -- Check that coaliton is okay. We allow same (blue=blue, red=red) or landing on neutral bases. if airbase and airbase:GetCoalition()~=self.group:GetCoalition() and airbase:GetCoalition()>0 then - self:E(self.lid..string.format("ERROR: Wrong airbase coalition %d in RTB() call! We allow only same as group %d or neutral airbases 0.", airbase:GetCoalition(), self.group:GetCoalition())) - allowed=false + self:E(self.lid..string.format("ERROR: Wrong airbase coalition %d in RTB() call! We allow only same as group %d or neutral airbases 0", airbase:GetCoalition(), self.group:GetCoalition())) + return false + end + + if self.currbase and self.currbase:GetName()==airbase:GetName() then + self:E(self.lid.."WARNING: Currbase is already same as RTB airbase. RTB canceled!") + return false + end + + -- Check if the group has landed at an airbase. If so, we lost control and RTBing is not possible (only after a respawn). + if self:IsLanded() then + self:E(self.lid.."WARNING: Flight has already landed. RTB canceled!") + return false end if not self.group:IsAirborne(true) then -- this should really not happen, either the AUFTRAG is cancelled before the group was airborne or it is stuck at the ground for some reason - self:I(self.lid..string.format("WARNING: Group is not AIRBORNE ==> RTB event is suspended for 20 sec.")) + self:I(self.lid..string.format("WARNING: Group is not AIRBORNE ==> RTB event is suspended for 20 sec")) allowed=false Tsuspend=-20 local groupspeed = self.group:GetVelocityMPS() if groupspeed<=1 and not self:IsParking() then self.RTBRecallCount = self.RTBRecallCount+1 end - if self.RTBRecallCount > 6 then + if self.RTBRecallCount>6 then self:I(self.lid..string.format("WARNING: Group is not moving and was called RTB %d times. Assuming a problem and despawning!", self.RTBRecallCount)) self.RTBRecallCount=0 self:Despawn(5) @@ -3108,103 +3149,19 @@ function FLIGHTGROUP:_InitGroup(Template) self.tankertype=select(2, unit:IsTanker()) self.refueltype=select(2, unit:IsRefuelable()) - -- Debug info. - if self.verbose>=1 then - local text=string.format("Initialized Flight Group %s:\n", self.groupname) - text=text..string.format("Unit type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) - text=text..string.format("Range max = %.1f km\n", self.rangemax/1000) - text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) - text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) - text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) - text=text..string.format("Tanker type = %s\n", tostring(self.tankertype)) - text=text..string.format("Refuel type = %s\n", tostring(self.refueltype)) - text=text..string.format("AI = %s\n", tostring(self.isAI)) - text=text..string.format("Helicopter = %s\n", tostring(self.group:IsHelicopter())) - text=text..string.format("Elements = %d\n", #self.elements) - text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) - text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) - text=text..string.format("FSM state = %s\n", self:GetState()) - text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) - text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) - text=text..string.format("Uncontrolled = %s\n", tostring(self:IsUncontrolled())) - text=text..string.format("Start Air = %s\n", tostring(self:IsTakeoffAir())) - text=text..string.format("Start Cold = %s\n", tostring(self:IsTakeoffCold())) - text=text..string.format("Start Hot = %s\n", tostring(self:IsTakeoffHot())) - text=text..string.format("Start Rwy = %s\n", tostring(self:IsTakeoffRunway())) - self:I(self.lid..text) - end - --env.info("DCS Unit BOOM_AND_RECEPTACLE="..tostring(Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)) --env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE)) -- Init done. self.groupinitialized=true - + + else + self:E(self.lid.."ERROR: no unit in _InigGroup!") end return self end ---- Add an element to the flight group. --- @param #FLIGHTGROUP self --- @param #string unitname Name of unit. --- @return Ops.OpsGroup#OPSGROUP.Element The element or nil. -function FLIGHTGROUP:AddElementByName(unitname) - - local unit=UNIT:FindByName(unitname) - - if unit then - - local element={} --Ops.OpsGroup#OPSGROUP.Element - - element.name=unitname - element.status=OPSGROUP.ElementStatus.INUTERO - element.unit=unit - element.group=unit:GetGroup() - - - -- TODO: this is wrong when grouping is used! - local unittemplate=element.unit:GetTemplate() - - element.modex=unittemplate.onboard_num - element.skill=unittemplate.skill - element.payload=unittemplate.payload - element.pylons=unittemplate.payload and unittemplate.payload.pylons or nil --element.unit:GetTemplatePylons() - element.fuelmass0=unittemplate.payload and unittemplate.payload.fuel or 0 --element.unit:GetTemplatePayload().fuel - element.fuelmass=element.fuelmass0 - element.fuelrel=element.unit:GetFuel() - element.category=element.unit:GetUnitCategory() - element.categoryname=element.unit:GetCategoryName() - element.callsign=element.unit:GetCallsign() - element.size=element.unit:GetObjectSize() - - if element.skill=="Client" or element.skill=="Player" then - element.ai=false - element.client=CLIENT:FindByName(unitname) - else - element.ai=true - end - - -- Debug text. - local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s", - element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel*100, element.category, element.categoryname, element.callsign, tostring(element.ai)) - self:T(self.lid..text) - - -- Add element to table. - table.insert(self.elements, element) - - if unit:IsAlive() then - self:ElementSpawned(element) - end - - return element - end - - return nil -end - --- Check if a unit is and element of the flightgroup. -- @param #FLIGHTGROUP self diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index a1209e0db..4925626c2 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -648,7 +648,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #NAVYGROUP.Element Element The group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The group element. function NAVYGROUP:onafterElementSpawned(From, Event, To, Element) self:T(self.lid..string.format("Element spawned %s", Element.name)) @@ -665,6 +665,24 @@ end function NAVYGROUP:onafterSpawned(From, Event, To) self:T(self.lid..string.format("Group spawned!")) + -- Debug info. + if self.verbose>=1 then + local text=string.format("Initialized Navy Group %s:\n", self.groupname) + text=text..string.format("Unit type = %s\n", self.actype) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) + text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) + text=text..string.format("Elements = %d\n", #self.elements) + text=text..string.format("Waypoints = %d\n", #self.waypoints) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) + text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos) + text=text..string.format("FSM state = %s\n", self:GetState()) + text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) + text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) + self:I(self.lid..text) + end + -- Update position. self:_UpdatePosition() @@ -1199,24 +1217,6 @@ function NAVYGROUP:_InitGroup(Template) -- Set type name. self.actype=units[1]:GetTypeName() - - -- Debug info. - if self.verbose>=1 then - local text=string.format("Initialized Navy Group %s:\n", self.groupname) - text=text..string.format("Unit type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) - text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) - text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) - text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) - text=text..string.format("Elements = %d\n", #self.elements) - text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) - text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos) - text=text..string.format("FSM state = %s\n", self:GetState()) - text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) - text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) - self:I(self.lid..text) - end -- Init done. self.groupinitialized=true diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 78885d372..40d29583f 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -620,6 +620,7 @@ function OPSGROUP:New(group) self:AddTransition("*", "Unloading", "*") -- Carrier is unloading the cargo. self:AddTransition("*", "Unload", "*") -- Carrier unload a cargo group. self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its current cargo. + self:AddTransition("*", "UnloadingDone", "*") -- Carrier is unloading the cargo. self:AddTransition("*", "Delivered", "*") -- Carrier delivered ALL cargo of the transport assignment. ------------------------ @@ -1883,13 +1884,33 @@ end --- Check if the group is **not** cargo. -- @param #OPSGROUP self --- @param #boolean CheckTransport If true or nil, also check if cargo is associated with a transport assignment. If not, we consider it not cargo. +-- @param #boolean CheckTransport If `true` or `nil`, also check if cargo is associated with a transport assignment. If not, we consider it not cargo. -- @return #boolean If true, group is *not* cargo. function OPSGROUP:IsNotCargo(CheckTransport) local notcargo=self.cargoStatus==OPSGROUP.CargoStatus.NOTCARGO - if self.cargoTransportUID==nil then - --notcargo=true + + if notcargo then + -- Not cargo. + return true + else + -- Is cargo (e.g. loaded or boarding) + + if CheckTransport then + -- Check if transport UID was set. + if self.cargoTransportUID==nil then + return true + else + -- Some transport UID was assigned. + return false + end + else + -- Is cargo. + return false + end + end + + return notcargo end @@ -2397,9 +2418,13 @@ function OPSGROUP:OnEventBirth(EventData) -- Get element. local element=self:GetElementByName(unitname) + + if element then - -- Set element to spawned state. - self:ElementSpawned(element) + -- Set element to spawned state. + self:ElementSpawned(element) + + end end @@ -2412,7 +2437,7 @@ function OPSGROUP:OnEventDead(EventData) -- Check that this is the right group. if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - self:T(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName)) + self:T2(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName)) local unit=EventData.IniUnit local group=EventData.IniGroup @@ -2421,7 +2446,7 @@ function OPSGROUP:OnEventDead(EventData) -- Get element. local element=self:GetElementByName(unitname) - if element then + if element and element.status~=OPSGROUP.ElementStatus.DEAD then self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed", element.name)) self:ElementDestroyed(element) end @@ -2437,6 +2462,8 @@ function OPSGROUP:OnEventRemoveUnit(EventData) -- Check that this is the right group. if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + self:T2(self.lid..string.format("EVENT: Unit %s removed!", EventData.IniUnitName)) + local unit=EventData.IniUnit local group=EventData.IniGroup local unitname=EventData.IniUnitName @@ -2444,7 +2471,7 @@ function OPSGROUP:OnEventRemoveUnit(EventData) -- Get element. local element=self:GetElementByName(unitname) - if element then + if element and element.status~=OPSGROUP.ElementStatus.DEAD then self:T(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) self:ElementDead(element) end @@ -3026,7 +3053,7 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- If task is scheduled (not waypoint) set task. if Task.type==OPSGROUP.TaskType.SCHEDULED or Task.ismission then - + local DCStasks={} if Task.dcstask.id=='ComboTask' then -- Loop over all combo tasks. @@ -3053,7 +3080,12 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) local TaskFinal=self.group:TaskCombo({TaskControlled, TaskDone}) -- Set task for group. - self:SetTask(TaskFinal) + -- NOTE: I am pushing the task instead of setting it as it seems to keep the mission task alive. + -- There were issues that flights did not proceed to a later waypoint because the task did not finish until the fired missiles + -- impacted (took rather long). Then the flight flew to the nearest airbase and one lost completely the control over the group. + self:PushTask(TaskFinal) + --self:SetTask(TaskFinal) + elseif Task.type==OPSGROUP.TaskType.WAYPOINT then -- Waypoint tasks are executed elsewhere! @@ -3780,6 +3812,8 @@ function OPSGROUP:RouteToMission(mission, delay) if self.isGround and mission.optionFormation then formation=mission.optionFormation end + + --waypointcoord:MarkToAll(string.format("Mission %s alt=%d m", mission:GetName(), waypointcoord.y)) -- Add waypoint. local waypoint=self:AddWaypoint(waypointcoord, SpeedToMission, nil, formation, false) @@ -3793,6 +3827,11 @@ function OPSGROUP:RouteToMission(mission, delay) -- Set waypoint index. mission:SetGroupWaypointIndex(self, waypoint.uid) + + local egress=mission:GetMissionEgressCoord() + if egress then + local waypoint=self:AddWaypoint(egress, SpeedToMission, nil, formation, false) + end --- -- Mission Specific Settings @@ -4038,6 +4077,7 @@ function OPSGROUP:_SetWaypointTasks(Waypoint) -- Check if there is mission task if missiontask then + self:T(self.lid.."Executing mission task") self:TaskExecute(missiontask) return 1 end @@ -5218,8 +5258,8 @@ function OPSGROUP:_CheckCargoTransport() -- Unloading finished ==> pickup next batch or call it a day. if delivered then - self:T(self.lid.."Unloading finished ==> Unloaded") - self:Unloaded() + self:T(self.lid.."Unloading finished ==> UnloadingDone") + self:UnloadingDone() else self:Unloading() end @@ -5954,7 +5994,7 @@ function OPSGROUP:onafterPickup(From, Event, To) -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=true + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=1 else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") @@ -5982,7 +6022,7 @@ function OPSGROUP:onafterPickup(From, Event, To) end -- NAVYGROUP - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=true + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=1 -- Give cruise command. self:__Cruise(-2) @@ -6010,7 +6050,7 @@ function OPSGROUP:onafterPickup(From, Event, To) end -- ARMYGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=true + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=1 self:__Cruise(-2) @@ -6045,7 +6085,7 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Check that group is NOT cargo and NOT acting as carrier already -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() - if cargo.opsgroup:IsNotCargo() and not (cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading()) then + if cargo.opsgroup:IsNotCargo(true) and not (cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading()) then -- Check if cargo is in embark/pickup zone. local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) @@ -6333,7 +6373,7 @@ function OPSGROUP:onafterTransport(From, Event, To) -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=true + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=1 else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") @@ -6356,7 +6396,7 @@ function OPSGROUP:onafterTransport(From, Event, To) end -- ARMYGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=1 -- Give cruise command. self:Cruise() @@ -6379,7 +6419,7 @@ function OPSGROUP:onafterTransport(From, Event, To) end -- NAVYGROUP - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=true + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=1 -- Give cruise command. self:Cruise() @@ -6612,6 +6652,9 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated -- Trigger "Disembarked" event. OpsGroup:Disembarked(OpsGroup:_GetMyCarrierGroup(), OpsGroup:_GetMyCarrierElement()) + + -- Trigger "Unloaded" event. + self:Unloaded(OpsGroup) -- Remove my carrier. OpsGroup:_RemoveMyCarrier() @@ -6623,10 +6666,21 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function OPSGROUP:onafterUnloaded(From, Event, To) +-- @param #OPSGROUP OpsGroupCargo Cargo OPSGROUP that was unloaded from a carrier. +function OPSGROUP:onafterUnloaded(From, Event, To, OpsGroupCargo) + self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroupCargo:GetName())) +end + + +--- On after "UnloadingDone" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterUnloadingDone(From, Event, To) -- Debug info - self:T(self.lid.."Cargo unloaded..") + self:T(self.lid.."Cargo unloading done..") -- Cancel landedAt task. if self:IsFlightgroup() and self:IsLandedAt() then @@ -6777,10 +6831,10 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self:ClearWaypoints(self.currentwp+1) if self.isArmygroup then - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=0 + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=1 self:Cruise() else - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=0 + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=1 self:Cruise() end @@ -7216,7 +7270,7 @@ function OPSGROUP:_CheckAmmoStatus() -- Guns. if self.outofGuns and ammo.Guns>0 then - self.outoffGuns=false + self.outofGuns=false end if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then self.outofGuns=true @@ -7225,7 +7279,7 @@ function OPSGROUP:_CheckAmmoStatus() -- Rockets. if self.outofRockets and ammo.Rockets>0 then - self.outoffRockets=false + self.outofRockets=false end if ammo.Rockets==0 and self.ammo.Rockets>0 and not self.outofRockets then self.outofRockets=true @@ -7234,22 +7288,50 @@ function OPSGROUP:_CheckAmmoStatus() -- Bombs. if self.outofBombs and ammo.Bombs>0 then - self.outoffBombs=false + self.outofBombs=false end if ammo.Bombs==0 and self.ammo.Bombs>0 and not self.outofBombs then self.outofBombs=true self:OutOfBombs() end - -- Missiles. + -- Missiles (All). if self.outofMissiles and ammo.Missiles>0 then - self.outoffMissiles=false + self.outofMissiles=false end if ammo.Missiles==0 and self.ammo.Missiles>0 and not self.outofMissiles then self.outofMissiles=true self:OutOfMissiles() end + -- Missiles AA. + if self.outofMissilesAA and ammo.MissilesAA>0 then + self.outofMissilesAA=false + end + if ammo.MissilesAA and self.ammo.MissilesAA>0 and not self.outofMissilesAA then + self.outofMissilesAA=true + self:OutOfMissilesAA() + end + + -- Missiles AG. + if self.outofMissilesAG and ammo.MissilesAG>0 then + self.outofMissilesAG=false + end + if ammo.MissilesAG and self.ammo.MissilesAG>0 and not self.outofMissilesAG then + self.outofMissilesAG=true + self:OutOfMissilesAG() + end + + -- Missiles AS. + if self.outofMissilesAS and ammo.MissilesAS>0 then + self.outofMissilesAS=false + end + if ammo.MissilesAS and self.ammo.MissilesAS>0 and not self.outofMissilesAS then + self.outofMissilesAS=true + self:OutOfMissilesAS() + end + + -- Check if group is engaging. if self:IsEngaging() and ammo.Total==0 then self:Disengage() @@ -7725,7 +7807,7 @@ function OPSGROUP._TaskDone(group, opsgroup, task) -- Debug message. local text=string.format("_TaskDone %s", task.description) - opsgroup:T3(opsgroup.lid..text) + opsgroup:T(opsgroup.lid..text) -- Set current task to nil so that the next in line can be executed. if opsgroup then @@ -9352,7 +9434,8 @@ function OPSGROUP:_AddElementByName(unitname) -- Trigger spawned event if alive. if unit:IsAlive() then - self:ElementSpawned(element) + -- This needs to be slightly delayed (or moved elsewhere) or the first element will always trigger the group spawned event as it is not known that more elements are in the group. + self:__ElementSpawned(0.05, element) end return element diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 61bd09885..351669719 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -849,8 +849,8 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) local carrier=cargo.opsgroup:_GetMyCarrierElement() local name=carrier and carrier.name or "none" local cstate=carrier and carrier.status or "N/A" - text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s], delivered=%s", - cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name, cstate, tostring(cargo.delivered)) + text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s], delivered=%s [UID=%s]", + cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name, cstate, tostring(cargo.delivered), tostring(cargo.opsgroup.cargoTransportUID)) end text=text..string.format("\nCarriers:") diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 266fe7f4c..2a535bbe2 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -608,15 +608,22 @@ end -- @return #number TACAN channel or *nil* if no channel is free. function SQUADRON:FetchTacan() + -- Get the smallest free channel if there is one. + local freechannel=nil for channel,free in pairs(self.tacanChannel) do - if free then - self:T(self.lid..string.format("Checking out Tacan channel %d", channel)) - self.tacanChannel[channel]=false - return channel + if free then + if freechannel==nil or channel Date: Mon, 2 Aug 2021 11:57:45 +0200 Subject: [PATCH 059/141] OPS --- .../Moose/Functional/Warehouse.lua | 21 ++++++- Moose Development/Moose/Ops/AirWing.lua | 29 +++++++++- Moose Development/Moose/Ops/Airboss.lua | 43 ++++++++------- Moose Development/Moose/Ops/Auftrag.lua | 54 ++++++++++++++++-- Moose Development/Moose/Ops/FlightGroup.lua | 12 ++-- Moose Development/Moose/Ops/OpsGroup.lua | 55 +++++++++---------- Moose Development/Moose/Ops/OpsTransport.lua | 2 +- Moose Development/Moose/Ops/Squadron.lua | 39 ++++++++++--- Moose Development/Moose/Ops/Target.lua | 26 +++++++-- 9 files changed, 204 insertions(+), 77 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 1c571b93c..e352dce84 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -2647,6 +2647,25 @@ function WAREHOUSE:_CheckParkingValid(spot) return false end +--- Check parking ID for an asset. +-- @param #WAREHOUSE self +-- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot. +-- @return #boolean If true, parking is valid. +function WAREHOUSE:_CheckParkingAsset(spot, asset) + + if asset.parkingIDs==nil then + return true + end + + for _,id in pairs(asset.parkingIDs or {}) do + if spot.TerminalID==id then + return true + end + end + + return false +end + --- Enable auto save of warehouse assets at mission end event. -- @param #WAREHOUSE self @@ -7848,7 +7867,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot -- Check correct terminal type for asset. We don't want helos in shelters etc. - if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingValid(parkingspot) and airbase:_CheckParkingLists(parkingspot.TerminalID) then + if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingValid(parkingspot) and self:_CheckParkingAsset(parkingspot, asset) and airbase:_CheckParkingLists(parkingspot.TerminalID) then -- Coordinate of the parking spot. local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 5e702896b..58e8ebb95 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -155,7 +155,7 @@ AIRWING = { --- AIRWING class version. -- @field #string version -AIRWING.version="0.6.0" +AIRWING.version="0.7.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -745,6 +745,11 @@ end -- @return #AIRWING.PatrolData Patrol point table. function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) + -- Check if a zone was passed instead of a coordinate. + if Coordinate:IsInstanceOf("ZONE_BASE") then + Coordinate=Coordinate:GetCoordinate() + end + local patrolpoint={} --#AIRWING.PatrolData patrolpoint.type=Type or "Unknown" patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10, 15)), math.random(360)) @@ -994,7 +999,7 @@ function AIRWING:CheckTANKER() for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - if mission:IsNotOver() and mission.type==AUFTRAG.Type.TANKER then + if mission:IsNotOver() and mission.type==AUFTRAG.Type.TANKER and mission.patroldata then if mission.refuelSystem==Unit.RefuelingSystem.BOOM_AND_RECEPTACLE then Nboom=Nboom+1 elseif mission.refuelSystem==Unit.RefuelingSystem.PROBE_AND_DROGUE then @@ -1566,6 +1571,9 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) -- Set takeoff type. asset.takeoffType=squad.takeoffType + + -- Set parking IDs. + asset.parkingIDs=squad.parkingIDs -- Create callsign and modex (needs to be after grouping). squad:GetCallsign(asset) @@ -2032,6 +2040,23 @@ function AIRWING:CountAssets() return N end + +--- Count total number of assets that are in the warehouse stock (not spawned). +-- @param #AIRWING self +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @return #number Amount of asset groups in stock. +function AIRWING:CountAssetsInStock(MissionTypes) + + local N=0 + + for _,_squad in pairs(self.squadrons) do + local squad=_squad --Ops.Squadron#SQUADRON + N=N+squad:CountAssetsInStock(MissionTypes) + end + + return N +end + --- Count assets on mission. -- @param #AIRWING self -- @param #table MissionTypes Types on mission to be checked. Default all. diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index e0cff612e..d49d52b6f 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -3601,36 +3601,39 @@ function AIRBOSS:_CheckAIStatus() -- Unit local unit=element.unit - -- Get lineup and distance to carrier. - local lineup=self:_Lineup(unit, true) + if unit and unit:IsAlive() then - local unitcoord=unit:GetCoord() + -- Get lineup and distance to carrier. + local lineup=self:_Lineup(unit, true) - local dist=unitcoord:Get2DDistance(self:GetCoord()) + local unitcoord=unit:GetCoord() - -- Distance in NM. - local distance=UTILS.MetersToNM(dist) + local dist=unitcoord:Get2DDistance(self:GetCoord()) - -- Altitude in ft. - local alt=UTILS.MetersToFeet(unitcoord.y) + -- Distance in NM. + local distance=UTILS.MetersToNM(dist) - -- Check if parameters are right and flight is in the groove. - if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then + -- Altitude in ft. + local alt=UTILS.MetersToFeet(unitcoord.y) - -- Paddles: Call the ball! - self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true) + -- Check if parameters are right and flight is in the groove. + if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then - -- Pilot: "405, Hornet Ball, 3.2" - self:_LSOCallAircraftBall(element.onboard,self:_GetACNickname(unit:GetTypeName()), self:_GetFuelState(unit)/1000) + -- Paddles: Call the ball! + self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true) - -- Paddles: Roger ball after 0.5 seconds. - self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, nil, nil, 0.5, true) + -- Pilot: "405, Hornet Ball, 3.2" + self:_LSOCallAircraftBall(element.onboard,self:_GetACNickname(unit:GetTypeName()), self:_GetFuelState(unit)/1000) - -- Flight element called the ball. - element.ballcall=true + -- Paddles: Roger ball after 0.5 seconds. + self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, nil, nil, 0.5, true) - -- This is for the whole flight. Maybe we need it. - flight.ballcall=true + -- Flight element called the ball. + element.ballcall=true + + -- This is for the whole flight. Maybe we need it. + flight.ballcall=true + end end end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index c9bd4ca98..de8f0e82b 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -53,8 +53,9 @@ -- @field #number dTevaluate Time interval in seconds before the mission result is evaluated after mission is over. -- @field #number Tover Mission abs. time stamp, when mission was over. -- @field #table conditionStart Condition(s) that have to be true, before the mission will be started. --- @field #table conditionSuccess If all stop conditions are true, the mission is cancelled. --- @field #table conditionFailure If all stop conditions are true, the mission is cancelled. +-- @field #table conditionSuccess If all conditions are true, the mission is cancelled. +-- @field #table conditionFailure If all conditions are true, the mission is cancelled. +-- @field #table conditionPush If all conditions are true, the mission is executed. Before, the group(s) wait at the mission execution waypoint. -- -- @field #number orbitSpeed Orbit speed in m/s. -- @field #number orbitAltitude Orbit altitude in meters. @@ -288,6 +289,7 @@ AUFTRAG = { conditionStart = {}, conditionSuccess = {}, conditionFailure = {}, + conditionPush = {}, } --- Global mission counter. @@ -445,7 +447,7 @@ AUFTRAG.TargetType={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.7.0" +AUFTRAG.version="0.7.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1825,6 +1827,26 @@ function AUFTRAG:AddConditionFailure(ConditionFunction, ...) return self end +--- Add push condition. +-- @param #AUFTRAG self +-- @param #function ConditionFunction If this function returns `true`, the mission is executed. +-- @param ... Condition function arguments if any. +-- @return #AUFTRAG self +function AUFTRAG:AddConditionPush(ConditionFunction, ...) + + local condition={} --#AUFTRAG.Condition + + condition.func=ConditionFunction + condition.arg={} + if arg then + condition.arg=arg + end + + table.insert(self.conditionPush, condition) + + return self +end + --- Assign airwing squadron(s) to the mission. Only these squads will be considered for the job. -- @param #AUFTRAG self @@ -1983,12 +2005,12 @@ function AUFTRAG:IsReadyToGo() local Tnow=timer.getAbsTime() -- Start time did not pass yet. - if self.Tstart and Tnowself.Tstop or false then + if self.Tstop and Tnow>self.Tstop then return false end @@ -2014,7 +2036,7 @@ function AUFTRAG:IsReadyToCancel() local Tnow=timer.getAbsTime() -- Stop time already passed. - if self.Tstop and Tnow>self.Tstop then + if self.Tstop and Tnow>=self.Tstop then return true end @@ -2038,6 +2060,26 @@ function AUFTRAG:IsReadyToCancel() return false end +--- Check if mission is ready to be pushed. +-- * Mission push time already passed. +-- * All push conditions are true. +-- @param #AUFTRAG self +-- @return #boolean If true, mission groups can push. +function AUFTRAG:IsReadyToPush() + + local Tnow=timer.getAbsTime() + + -- Push time passed? + if self.Tpush and Tnow0) then + + if Mission:IsReadyToPush() then - if Mission.Tpush then + -- Not waiting any more. + self.Twaiting=nil + self.dTwait=nil - local Tnow=timer.getAbsTime() + else - -- Time to push - local dt=Mission.Tpush-Tnow - - -- Push time not reached. - if Tnow Date: Mon, 2 Aug 2021 18:41:33 +0200 Subject: [PATCH 060/141] Flightcontrol --- Moose Development/Moose/Modules.lua | 3 +- Moose Development/Moose/Ops/Army.lua | 434 ---- Moose Development/Moose/Ops/FlightControl.lua | 2209 +++++++++++++++++ Moose Development/Moose/Ops/Platoon.lua | 0 Moose Setup/Moose.files | 1 + 5 files changed, 2211 insertions(+), 436 deletions(-) delete mode 100644 Moose Development/Moose/Ops/Army.lua create mode 100644 Moose Development/Moose/Ops/FlightControl.lua delete mode 100644 Moose Development/Moose/Ops/Platoon.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 9ccb7ebb5..bac21d429 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -85,8 +85,7 @@ __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) __Moose.Include( 'Scripts/Moose/Ops/ChiefOfStaff.lua' ) -__Moose.Include( 'Scripts/Moose/Ops/Army.lua' ) -__Moose.Include( 'Scripts/Moose/Ops/Platoon.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/FlightControl.lua' ) __Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' ) __Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' ) __Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' ) diff --git a/Moose Development/Moose/Ops/Army.lua b/Moose Development/Moose/Ops/Army.lua deleted file mode 100644 index 884a6ed22..000000000 --- a/Moose Development/Moose/Ops/Army.lua +++ /dev/null @@ -1,434 +0,0 @@ ---- **Ops** - Army Warehouse. --- --- **Main Features:** --- --- * Manage platoons --- --- === --- --- ### Author: **funkyfranky** --- @module Ops.Army --- @image OPS_AirWing.png - - ---- ARMY class. --- @type ARMY --- @field #string ClassName Name of the class. --- @field #number verbose Verbosity of output. --- @field #string lid Class id string for output to DCS log file. --- @field #table menu Table of menu items. --- @field #table squadrons Table of squadrons. --- @field #table missionqueue Mission queue table. --- @field #table payloads Playloads for specific aircraft and mission types. --- @field #number payloadcounter Running index of payloads. --- @field Core.Set#SET_ZONE zonesetCAP Set of CAP zones. --- @field Core.Set#SET_ZONE zonesetTANKER Set of TANKER zones. --- @field Core.Set#SET_ZONE zonesetAWACS Set of AWACS zones. --- @field #number nflightsCAP Number of CAP flights constantly in the air. --- @field #number nflightsAWACS Number of AWACS flights constantly in the air. --- @field #number nflightsTANKERboom Number of TANKER flights with BOOM constantly in the air. --- @field #number nflightsTANKERprobe Number of TANKER flights with PROBE constantly in the air. --- @field #number nflightsRescueHelo Number of Rescue helo flights constantly in the air. --- @field #table pointsCAP Table of CAP points. --- @field #table pointsTANKER Table of Tanker points. --- @field #table pointsAWACS Table of AWACS points. --- @field Ops.WingCommander#WINGCOMMANDER wingcommander The wing commander responsible for this airwing. --- --- @field Ops.RescueHelo#RESCUEHELO rescuehelo The rescue helo. --- @field Ops.RecoveryTanker#RECOVERYTANKER recoverytanker The recoverytanker. --- --- @extends Functional.Warehouse#WAREHOUSE - ---- Be surprised! --- --- === --- --- ![Banner Image](..\Presentations\OPS\AirWing\_Main.png) --- --- # The ARMY Concept --- --- An ARMY consists of multiple SQUADRONS. These squadrons "live" in a WAREHOUSE, i.e. a physical structure that is connected to an airbase (airdrome, FRAP or ship). --- For an airwing to be operational, it needs airframes, weapons/fuel and an airbase. --- --- # Create an Army --- --- ## Constructing the Army --- --- airwing=ARMY:New("Warehouse Batumi", "8th Fighter Wing") --- airwing:Start() --- --- The first parameter specified the warehouse, i.e. the static building housing the airwing (or the name of the aircraft carrier). The second parameter is optional --- and sets an alias. --- --- ## Adding Squadrons --- --- At this point the airwing does not have any assets (aircraft). In order to add these, one needs to first define SQUADRONS. --- --- VFA151=SQUADRON:New("F-14 Group", 8, "VFA-151 (Vigilantes)") --- VFA151:AddMissionCapability({AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT}) --- --- airwing:AddSquadron(VFA151) --- --- This adds eight Tomcat groups beloning to VFA-151 to the airwing. This squadron has the ability to perform combat air patrols and intercepts. --- --- ## Adding Payloads --- --- Adding pure airframes is not enough. The aircraft also need weapons (and fuel) for certain missions. These must be given to the airwing from template groups --- defined in the Mission Editor. --- --- -- F-14 payloads for CAP and INTERCEPT. Phoenix are first, sparrows are second choice. --- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-54C"), 2, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.GCICAP}, 80) --- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-7M"), 20, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.GCICAP}) --- --- This will add two AIM-54C and 20 AIM-7M payloads. --- --- If the airwing gets an intercept or patrol mission assigned, it will first use the AIM-54s. Once these are consumed, the AIM-7s are attached to the aircraft. --- --- When an airwing does not have a payload for a certain mission type, the mission cannot be carried out. --- --- You can set the number of payloads to "unlimited" by setting its quantity to -1. --- --- # Adding Missions --- --- Various mission types can be added easily via the AUFTRAG class. --- --- Once you created an AUFTRAG you can add it to the ARMY with the :AddMission(mission) function. --- --- This mission will be put into the ARMY queue. Once the mission start time is reached and all resources (airframes and pylons) are available, the mission is started. --- If the mission stop time is over (and the mission is not finished), it will be cancelled and removed from the queue. This applies also to mission that were not even --- started. --- --- # Command an Army --- --- An airwing can receive missions from a WINGCOMMANDER. See docs of that class for details. --- --- However, you are still free to add missions at anytime. --- --- --- @field #ARMY -ARMY = { - ClassName = "ARMY", - verbose = 0, - lid = nil, - menu = nil, - squadrons = {}, - missionqueue = {}, - payloads = {}, - payloadcounter = 0, - pointsCAP = {}, - pointsTANKER = {}, - pointsAWACS = {}, - wingcommander = nil, -} - ---- Squadron asset. --- @type ARMY.SquadronAsset --- @field #ARMY.Payload payload The payload of the asset. --- @field Ops.FlightGroup#FLIGHTGROUP flightgroup The flightgroup object. --- @field #string squadname Name of the squadron this asset belongs to. --- @field #number Treturned Time stamp when asset returned to the airwing. --- @extends Functional.Warehouse#WAREHOUSE.Assetitem - ---- ARMY class version. --- @field #string version -ARMY.version="0.0.1" - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- ToDo list -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- TODO: A lot! - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Constructor -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Create a new ARMY class object. --- @param #ARMY self --- @param #string warehousename Name of the warehouse static or unit object representing the warehouse. --- @param #string airwingname Name of the air wing, e.g. "ARMY-8". --- @return #ARMY self -function ARMY:New(warehousename, airwingname) - - -- Inherit everything from WAREHOUSE class. - local self=BASE:Inherit(self, WAREHOUSE:New(warehousename, airwingname)) -- #ARMY - - -- Nil check. - if not self then - BASE:E(string.format("ERROR: Could not find warehouse %s!", warehousename)) - return nil - end - - -- Set some string id for output to DCS.log file. - self.lid=string.format("ARMY %s | ", self.alias) - - -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. - self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. - - ------------------------ - --- Pseudo Functions --- - ------------------------ - - --- Triggers the FSM event "Start". Starts the ARMY. Initializes parameters and starts event handlers. - -- @function [parent=#ARMY] Start - -- @param #ARMY self - - --- Triggers the FSM event "Start" after a delay. Starts the ARMY. Initializes parameters and starts event handlers. - -- @function [parent=#ARMY] __Start - -- @param #ARMY self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Stop". Stops the ARMY and all its event handlers. - -- @param #ARMY self - - --- Triggers the FSM event "Stop" after a delay. Stops the ARMY and all its event handlers. - -- @function [parent=#ARMY] __Stop - -- @param #ARMY self - -- @param #number delay Delay in seconds. - - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- User Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start & Status -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Start AIRWING FSM. --- @param #AIRWING self -function AIRWING:onafterStart(From, Event, To) - - -- Start parent Warehouse. - self:GetParent(self).onafterStart(self, From, Event, To) - - -- Info. - self:I(self.lid..string.format("Starting AIRWING v%s", AIRWING.version)) - -end - ---- Update status. --- @param #AIRWING self -function AIRWING:onafterStatus(From, Event, To) - - -- Status of parent Warehouse. - self:GetParent(self).onafterStatus(self, From, Event, To) - - local fsmstate=self:GetState() - - - -- General info: - if self.verbose>=1 then - - -- Count missions not over yet. - local Nmissions=self:CountMissionsInQueue() - - -- Assets tot - local Npq, Np, Nq=self:CountAssetsOnMission() - - local assets=string.format("%d (OnMission: Total=%d, Active=%d, Queued=%d)", self:CountAssets(), Npq, Np, Nq) - - -- Output. - local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d, Assets=%s", fsmstate, Nmissions, Npayloads, #self.payloads, #self.squadrons, assets) - self:I(self.lid..text) - end - - ------------------ - -- Mission Info -- - ------------------ - if self.verbose>=2 then - local text=string.format("Missions Total=%d:", #self.missionqueue) - for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end - local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.nassets) - local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage()) - - text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target) - end - self:I(self.lid..text) - end - - ------------------- - -- Squadron Info -- - ------------------- - if self.verbose>=3 then - local text="Squadrons:" - for i,_squadron in pairs(self.squadrons) do - local squadron=_squadron --Ops.Squadron#SQUADRON - - local callsign=squadron.callsignName and UTILS.GetCallsignName(squadron.callsignName) or "N/A" - local modex=squadron.modex and squadron.modex or -1 - local skill=squadron.skill and tostring(squadron.skill) or "N/A" - - -- Squadron text - text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, squadron:CountAssetsInStock(), #squadron.assets, callsign, modex, skill) - end - self:I(self.lid..text) - end - - -------------- - -- Mission --- - -------------- - - -- Check if any missions should be cancelled. - self:_CheckMissions() - - -- Get next mission. - local mission=self:_GetNextMission() - - -- Request mission execution. - if mission then - self:MissionRequest(mission) - end - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Stuff -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Check if mission is not over and ready to cancel. --- @param #AIRWING self -function AIRWING:_CheckMissions() - - -- Loop over missions in queue. - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - if mission:IsNotOver() and mission:IsReadyToCancel() then - mission:Cancel() - end - end - -end ---- Get next mission. --- @param #AIRWING self --- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. -function AIRWING:_GetNextMission() - - -- Number of missions. - local Nmissions=#self.missionqueue - - -- Treat special cases. - if Nmissions==0 then - return nil - end - - -- Sort results table wrt prio and start time. - local function _sort(a, b) - local taskA=a --Ops.Auftrag#AUFTRAG - local taskB=b --Ops.Auftrag#AUFTRAG - return (taskA.prio0 then - self:E(self.lid..string.format("ERROR: mission %s of type %s has already assets attached!", mission.name, mission.type)) - end - mission.assets={} - - -- Assign assets to mission. - for i=1,mission.nassets do - local asset=assets[i] --#AIRWING.SquadronAsset - - -- Should not happen as we just checked! - if not asset.payload then - self:E(self.lid.."ERROR: No payload for asset! This should not happen!") - end - - -- Add asset to mission. - mission:AddAsset(asset) - end - - -- Now return the remaining payloads. - for i=mission.nassets+1,#assets do - local asset=assets[i] --#AIRWING.SquadronAsset - for _,uid in pairs(gotpayload) do - if uid==asset.uid then - self:ReturnPayloadFromAsset(asset) - break - end - end - end - - return mission - end - - end -- mission due? - end -- mission loop - - return nil -end \ No newline at end of file diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua new file mode 100644 index 000000000..ded207415 --- /dev/null +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -0,0 +1,2209 @@ +--- **OPS** - Manage recovery of aircraft at airdromes. +-- +-- +-- +-- **Main Features:** +-- +-- * Manage aircraft recovery. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module OPS.FlightControl +-- @image OPS_FlightControl.png + + +--- FLIGHTCONTROL class. +-- @type FLIGHTCONTROL +-- @field #string ClassName Name of the class. +-- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #string theatre The DCS map used in the mission. +-- @field #string lid Class id string for output to DCS log file. +-- @field #string airbasename Name of airbase. +-- @field #number airbasetype Type of airbase. +-- @field Wrapper.Airbase#AIRBASE airbase Airbase object. +-- @field Core.Zone#ZONE zoneAirbase Zone around the airbase. +-- @field #table parking Parking spots table. +-- @field #table runways Runway table. +-- @field #table flights All flights table. +-- @field #table clients Table with all clients spawning at this airbase. +-- @field Ops.ATIS#ATIS atis ATIS object. +-- @field #number activerwyno Number of active runway. +-- @field #number atcfreq ATC radio frequency. +-- @field Core.RadioQueue#RADIOQUEUE atcradio ATC radio queue. +-- @field #number Nlanding Max number of aircraft groups in the landing pattern. +-- @field #number dTlanding Time interval in seconds between landing clearance. +-- @field #number Nparkingspots Total number of parking spots. +-- @field Core.Spawn#SPAWN parkingGuard Parking guard spawner. +-- @extends Core.Fsm#FSM + +--- **Ground Control**: Airliner X, Good news, you are clear to taxi to the active. +-- **Pilot**: Roger, What's the bad news? +-- **Ground Control**: No bad news at the moment, but you probably want to get gone before I find any. +-- +-- === +-- +-- ![Banner Image](..\Presentations\FLIGHTCONTROL\FlightControl_Main.jpg) +-- +-- # The FLIGHTCONTROL Concept +-- +-- +-- +-- @field #FLIGHTCONTROL +FLIGHTCONTROL = { + ClassName = "FLIGHTCONTROL", + Debug = false, + lid = nil, + theatre = nil, + airbasename = nil, + airbase = nil, + airbasetype = nil, + zoneAirbase = nil, + parking = {}, + runways = {}, + flights = {}, + clients = {}, + atis = nil, + activerwyno = 1, + atcfreq = nil, + atcradio = nil, + atcradiounitname = nil, + Nlanding = nil, + dTlanding = nil, + Nparkingspots = nil, +} + +--- Holding point +-- @type FLIGHTCONTROL.HoldingPoint +-- @field Core.Point#COORDINATE pos0 First poosition of racetrack holding point. +-- @field Core.Point#COORDINATE pos1 Second position of racetrack holding point. +-- @field #number angelsmin Smallest holding altitude in angels. +-- @field #number angelsmax Largest holding alitude in angels. + +--- Player menu data. +-- @type FLIGHTCONTROL.PlayerMenu +-- @field Core.Menu#MENU_GROUP root Root menu. +-- @field Core.Menu#MENU_GROUP_COMMAND RequestTaxi Request taxi. + +--- Parking spot data. +-- @type FLIGHTCONTROL.ParkingSpot +-- @field Wrapper.Group#GROUP ParkingGuard Parking guard for this spot. +-- @extends Wrapper.Airbase#AIRBASE.ParkingSpot + +--- Parking spot data. +-- @type FLIGHTCONTROL.FlightStatus +-- @field #string INBOUND Flight is inbound. +-- @field #string HOLDING Flight is holding. +-- @field #string LANDING Flight is landing. +-- @field #string TAXIINB Flight is taxiing to parking area. +-- @field #string ARRIVED Flight arrived at parking spot. +-- @field #string TAXIOUT Flight is taxiing to runway for takeoff. +-- @field #string READYTO Flight is ready for takeoff. +-- @field #string TAKEOFF Flight is taking off. +FLIGHTCONTROL.FlightStatus={ + INBOUND="Inbound", + HOLDING="Holding", + LANDING="Landing", + TAXIINB="Taxi Inbound", + ARRIVED="Arrived", + PARKING="Parking", + TAXIOUT="Taxi to runway", + READYTO="Ready For Takeoff", + TAKEOFF="Takeoff", +} + + +--- Runway data. +-- @type FLIGHTCONTROL.Runway +-- @field #number direction Direction of the runway. +-- @field #number length Length of runway in meters. +-- @field #number width Width of runway in meters. +-- @field Core.Point#COORDINATE position Position of runway start. + + +--- Sound file data. +-- @type FLIGHTCONTROL.Soundfile +-- @field #string filename Name of the file +-- @field #number duration Duration in seconds. + +--- Sound files. +-- @type FLIGHTCONTROL.Sound +-- @field #FLIGHTCONTROL.Soundfile ActiveRunway +FLIGHTCONTROL.Sound = { + ActiveRunway={filename="ActiveRunway.ogg", duration=0.99}, +} + +--- FlightControl class version. +-- @field #string version +FLIGHTCONTROL.version="0.4.0" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +-- +-- TODO: Define holding zone +-- TODO: +-- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- DONE: Add parking guard. +-- TODO: Accept and forbit parking spots. +-- NOGO: Add FARPS? +-- TODO: Add helos. +-- TODO: Talk me down option. +-- TODO: ATIS option. +-- TODO: ATC voice overs. +-- TODO: Check runways and clean up. +-- DONE: Interface with FLIGHTGROUP. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new FLIGHTCONTROL class object for an associated airbase. +-- @param #FLIGHTCONTROL self +-- @param #string airbasename Name of the airbase. +-- @return #FLIGHTCONTROL self +function FLIGHTCONTROL:New(airbasename) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #FLIGHTCONTROL + + -- Try to get the airbase. + self.airbase=AIRBASE:FindByName(airbasename) + + -- Name of the airbase. + self.airbasename=airbasename + + -- Set some string id for output to DCS.log file. + self.lid=string.format("FLIGHTCONTROL %s | ", airbasename) + + -- Check if the airbase exists. + if not self.airbase then + self:E(string.format("ERROR: Could not find airbase %s!", tostring(airbasename))) + return nil + end + -- Check if airbase is an airdrome. + if self.airbase:GetAirbaseCategory()~=Airbase.Category.AIRDROME then + self:E(string.format("ERROR: Airbase %s is not an AIRDROME! Script does not handle FARPS or ships.", tostring(airbasename))) + return nil + end + + + -- Airbase category airdrome, FARP, SHIP. + self.airbasetype=self.airbase:GetAirbaseCategory() + + -- Current map. + self.theatre=env.mission.theatre + + -- 5 NM zone around the airbase. + self.zoneAirbase=ZONE_RADIUS:New("FC", self:GetCoordinate():GetVec2(), UTILS.NMToMeters(5)) + + -- Defaults: + self:SetLandingMax() + self:SetLandingInterval() + + + -- Init runways. + self:_InitRunwayData() + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- Update status. + + -- Debug trace. + if false then + self.Debug=true + BASE:TraceOnOff(true) + BASE:TraceClass(self.ClassName) + BASE:TraceLevel(1) + end + + -- Add to data base. + _DATABASE:AddFlightControl(self) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User API Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set the number of aircraft groups, that are allowed to land simultaniously. +-- @param #FLIGHTCONTROL self +-- @param #number n Max number of aircraft landing simultaniously. Default 2. +-- @return #FLIGHTCONTROL self +function FLIGHTCONTROL:SetLandingMax(n) + + self.Nlanding=n or 2 + + return self +end + +--- Set time interval between landing clearance of groups. +-- @param #FLIGHTCONTROL self +-- @param #number dt Time interval in seconds. Default 180 sec (3 min). +-- @return #FLIGHTCONTROL self +function FLIGHTCONTROL:SetLandingInterval(dt) + + self.dTlanding=dt or 180 + + return self +end + + +--- Set runway. This clears all auto generated runways. +-- @param #FLIGHTCONTROL self +-- @param #FLIGHTCONTROL.Runway Runway. +-- @return #FLIGHTCONTROL self +function FLIGHTCONTROL:SetRunway(runway) + + -- Reset table. + self.runways={} + + -- Set runway. + table.insert(self.runways, runway) + + return self +end + +--- Add runway. +-- @param #FLIGHTCONTROL self +-- @param #FLIGHTCONTROL.Runway Runway. +-- @return #FLIGHTCONTROL self +function FLIGHTCONTROL:AddRunway(runway) + + -- Set runway. + table.insert(self.runways, runway) + + return self +end + +--- Set active runway number. Counting refers to the position in the table entry. +-- @param #FLIGHTCONTROL self +-- @param #number no Number in the runways table. +-- @return #FLIGHTCONTROL self +function FLIGHTCONTROL:SetActiveRunwayNumber(no) + self.activerwyno=no + return self +end + + +--- Set the parking guard group. +-- @param #FLIGHTCONTROL self +-- @param #string TemplateGroupName Name of the template group. +-- @return #FLIGHTCONTROL self +function FLIGHTCONTROL:SetParkingGuard(TemplateGroupName) + + local alias=string.format("Parking Guard %s", self.airbasename) + + -- Need spawn with alias for multiple FCs. + self.parkingGuard=SPAWN:NewWithAlias(TemplateGroupName, alias) + + --self.parkingGuard=SPAWNSTATIC:NewFromStatic("Parking Guard"):InitNamePrefix(alias) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Start FLIGHTCONTROL FSM. Handle events. +-- @param #FLIGHTCONTROL self +function FLIGHTCONTROL:onafterStart() + + -- Events are handled my MOOSE. + self:I(self.lid..string.format("Starting FLIGHTCONTROL v%s for airbase %s of type %d on map %s", FLIGHTCONTROL.version, self.airbasename, self.airbasetype, self.theatre)) + + -- Init parking spots. + self:_InitParkingSpots() + + -- Handle events. + self:HandleEvent(EVENTS.Birth) + self:HandleEvent(EVENTS.EngineStartup) + self:HandleEvent(EVENTS.Takeoff) + self:HandleEvent(EVENTS.Land) + self:HandleEvent(EVENTS.EngineShutdown) + self:HandleEvent(EVENTS.Crash) + + self.atcradio=RADIOQUEUE:New(self.atcfreq or 305, nil, string.format("FC %s", self.airbasename)) + self.atcradio:Start(1, 0.1) + + -- Init status updates. + self:__Status(-1) +end + +--- Update status. +-- @param #FLIGHTCONTROL self +function FLIGHTCONTROL:onafterStatus() + + -- Check status of all registered flights. + self:_CheckFlights() + + -- Check parking spots. + --self:_CheckParking() + + -- Check waiting and landing queue. + self:_CheckQueues() + + -- Get runway. + local runway=self:GetActiveRunway() + + local Nflights= self:CountFlights() + local NQparking=self:CountFlights(FLIGHTCONTROL.FlightStatus.PARKING) + local NQtaxiout=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIOUT) + local NQreadyto=self:CountFlights(FLIGHTCONTROL.FlightStatus.READYTO) + local NQtakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF) + local NQinbound=self:CountFlights(FLIGHTCONTROL.FlightStatus.INBOUND) + local NQholding=self:CountFlights(FLIGHTCONTROL.FlightStatus.HOLDING) + local NQlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) + local NQtaxiinb=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIINB) + local NQarrived=self:CountFlights(FLIGHTCONTROL.FlightStatus.ARRIVED) + -- ========================================================================================================= + local Nqueues = (NQparking+NQtaxiout+NQreadyto+NQtakeoff) + (NQinbound+NQholding+NQlanding+NQtaxiinb+NQarrived) + + -- Count free parking spots. + --TODO: get and substract number of reserved parking spots. + local nfree=self.Nparkingspots-NQarrived-NQparking + + local Nfree=self:CountParking(AIRBASE.SpotStatus.FREE) + local Noccu=self:CountParking(AIRBASE.SpotStatus.OCCUPIED) + local Nresv=self:CountParking(AIRBASE.SpotStatus.RESERVED) + + if Nfree+Noccu+Nresv~=self.Nparkingspots then + self:E(self.lid..string.format("WARNING: Number of parking spots does not match! Nfree=%d, Noccu=%d, Nreserved=%d != %d total", Nfree, Noccu, Nresv, self.Nparkingspots)) + end + + -- Info text. + local text=string.format("State %s - Runway %s - Parking F=%d/O=%d/R=%d of %d - Flights=%s: Qpark=%d Qtxout=%d Qready=%d Qto=%d | Qinbound=%d Qhold=%d Qland=%d Qtxinb=%d Qarr=%d", + self:GetState(), runway.idx, Nfree, Noccu, Nresv, self.Nparkingspots, Nflights, NQparking, NQtaxiout, NQreadyto, NQtakeoff, NQinbound, NQholding, NQlanding, NQtaxiinb, NQarrived) + self:I(self.lid..text) + + if Nflights==Nqueues then + --Check! + else + self:E(string.format("WARNING: Number of total flights %d!=%d number of flights in all queues!", Nflights, Nqueues)) + end + + -- Next status update in ~30 seconds. + self:__Status(-20) +end + +--- Start FLIGHTCONTROL FSM. Handle events. +-- @param #FLIGHTCONTROL self +function FLIGHTCONTROL:onafterStop() + + -- Handle events. + self:HandleEvent(EVENTS.Birth) + self:HandleEvent(EVENTS.EngineStartup) + self:HandleEvent(EVENTS.Takeoff) + self:HandleEvent(EVENTS.Land) + self:HandleEvent(EVENTS.EngineShutdown) + self:HandleEvent(EVENTS.Crash) + + self.atcradio:Stop() +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Event Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Event handler for event birth. +-- @param #FLIGHTCONTROL self +-- @param Core.Event#EVENTDATA EventData +function FLIGHTCONTROL:OnEventBirth(EventData) + self:F3({EvendData=EventData}) + + if EventData and EventData.IniGroupName and EventData.IniUnit then + + self:I(self.lid..string.format("BIRTH: unit = %s", tostring(EventData.IniUnitName))) + self:T2(self.lid..string.format("BIRTH: group = %s", tostring(EventData.IniGroupName))) + + -- Unit that was born. + local unit=EventData.IniUnit + + -- We delay this, to have all elements of the group in the game. + if unit:IsAir() then + + local bornhere=EventData.Place and EventData.Place:GetName()==self.airbasename or false + env.info("FF born here ".. tostring(bornhere)) + + -- We got a player? + local playerunit, playername=self:_GetPlayerUnitAndName(EventData.IniUnitName) + + if playername or bornhere then + + self:ScheduleOnce(0.5, self._CreateFlightGroup, self, EventData.IniGroup) + + end + + if bornhere then + self:SpawnParkingGuard(unit) + end + + end + + end + +end + +--- Event handler for event land. +-- @param #FLIGHTCONTROL self +-- @param Core.Event#EVENTDATA EventData +function FLIGHTCONTROL:OnEventLand(EventData) + self:F3({EvendData=EventData}) + + self:T2(self.lid..string.format("LAND: unit = %s", tostring(EventData.IniUnitName))) + self:T2(self.lid..string.format("LAND: group = %s", tostring(EventData.IniGroupName))) + +end + +--- Event handler for event takeoff. +-- @param #FLIGHTCONTROL self +-- @param Core.Event#EVENTDATA EventData +function FLIGHTCONTROL:OnEventTakeoff(EventData) + self:F3({EvendData=EventData}) + + self:T2(self.lid..string.format("TAKEOFF: unit = %s", tostring(EventData.IniUnitName))) + self:T2(self.lid..string.format("TAKEOFF: group = %s", tostring(EventData.IniGroupName))) + + -- This would be the closest airbase. + local airbase=EventData.Place + + -- Unit that took off. + local unit=EventData.IniUnit + + -- Nil check for airbase. Crashed as player gave me no airbase. + if not (airbase or unit) then + self:E(self.lid.."WARNING: Airbase or IniUnit is nil in takeoff event!") + return + end + +end + +--- Event handler for event engine startup. +-- @param #FLIGHTCONTROL self +-- @param Core.Event#EVENTDATA EventData +function FLIGHTCONTROL:OnEventEngineStartup(EventData) + self:F3({EvendData=EventData}) + + self:I(self.lid..string.format("ENGINESTARTUP: unit = %s", tostring(EventData.IniUnitName))) + self:T2(self.lid..string.format("ENGINESTARTUP: group = %s", tostring(EventData.IniGroupName))) + + -- Unit that took off. + local unit=EventData.IniUnit + + -- Nil check for unit. + if not unit then + return + end + +end + +--- Event handler for event engine shutdown. +-- @param #FLIGHTCONTROL self +-- @param Core.Event#EVENTDATA EventData +function FLIGHTCONTROL:OnEventEngineShutdown(EventData) + self:F3({EvendData=EventData}) + + self:I(self.lid..string.format("ENGINESHUTDOWN: unit = %s", tostring(EventData.IniUnitName))) + self:T2(self.lid..string.format("ENGINESHUTDOWN: group = %s", tostring(EventData.IniGroupName))) + + -- Unit that took off. + local unit=EventData.IniUnit + + -- Nil check for unit. + if not unit then + return + end + +end + +--- Event handler for event crash. +-- @param #FLIGHTCONTROL self +-- @param Core.Event#EVENTDATA EventData +function FLIGHTCONTROL:OnEventCrash(EventData) + self:F3({EvendData=EventData}) + + self:T2(self.lid..string.format("CRASH: unit = %s", tostring(EventData.IniUnitName))) + self:T2(self.lid..string.format("CRASH: group = %s", tostring(EventData.IniGroupName))) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Queue Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Scan airbase zone. +-- @param #FLIGHTCONTROL self +function FLIGHTCONTROL:_CheckQueues() + + -- Print queue. + if true then + self:_PrintQueue(self.flights, "All flights") + end + + -- Number of holding groups. + local nholding=self:CountFlights(FLIGHTCONTROL.FlightStatus.HOLDING) + + -- Number of groups landing. + local nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) + + -- Number of parking groups. + local nparking=self:CountFlights(FLIGHTCONTROL.FlightStatus.PARKING) + + -- Number of groups taking off. + local ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF) + + + -- Get next flight in line: either holding or parking. + local flight, isholding, parking=self:_GetNextFlight() + + + -- Check if somebody wants something. + if flight then + + if isholding then + + -------------------- + -- Holding flight -- + -------------------- + + -- No other flight is taking off and number of landing flights is below threshold. + if ntakeoff==0 and nlanding=self.dTlanding then + + -- Message. + local text=string.format("Flight %s, you are cleared to land.", flight.groupname) + MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() + + -- Give AI the landing signal. + -- TODO: Humans have to confirm via F10 menu. + if flight.ai then + self:_LandAI(flight, parking) + end + + -- Set time last flight got landing clearance. + self.Tlanding=timer.getAbsTime() + + end + else + self:I(self.lid..string.format("FYI: Landing clearance for flight %s denied as other flights are taking off (N=%d) or max. landing reached (N=%d/%d).", flight.groupname, ntakeoff, nlanding, self.Nlanding)) + end + + else + + -------------------- + -- Takeoff flight -- + -------------------- + + -- No other flight is taking off or landing. + if ntakeoff==0 and nlanding==0 then + + -- Check if flight is AI. Humans have to request taxi via F10 menu. + if flight.ai then + + --- + -- AI + --- + + -- Message. + local text=string.format("Flight %s, you are cleared to taxi to runway.", flight.groupname) + self:I(self.lid..text) + MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() + + -- Start uncontrolled aircraft. + if flight:IsUncontrolled() then + flight:StartUncontrolled() + end + + -- Add flight to takeoff queue. + self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.TAKEOFF) + + -- Remove parking guards. + for _,_element in pairs(flight.elements) do + local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element + if element and element.parking then + local spot=self:GetParkingSpotByID(element.parking.TerminalID) + self:RemoveParkingGuard(spot) + end + end + + else + + --- + -- PLAYER + --- + + local text=string.format("HUMAN Flight %s, you are cleared for takeoff.", flight.groupname) + self:I(self.lid..text) + MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() + + end + + else + self:I(self.lid..string.format("FYI: Take of for flight %s denied as other flights are taking off (N=%d) or landing (N=%d).", flight.groupname, ntakeoff, nlanding)) + end + end + else + self:I(self.lid..string.format("FYI: No flight in queue for takeoff or landing.")) + end + +end + +--- Get next flight in line, either waiting for landing or waiting for takeoff. +-- @param #FLIGHTCONTROL self +-- @return Ops.FlightGroup#FLIGHTGROUP Marshal flight next in line and ready to enter the pattern. Or nil if no flight is ready. +-- @return #boolean If true, flight is holding and waiting for landing, if false, flight is parking and waiting for takeoff. +-- @return #table Parking data for holding flights or nil. +function FLIGHTCONTROL:_GetNextFlight() + + local flightholding=self:_GetNextFightHolding() + local flightparking=self:_GetNextFightParking() + + -- If no flight is waiting for landing just return the takeoff flight or nil. + if not flightholding then + return flightparking, false, nil + end + + -- Get number of alive elements of the holding flight. + local nH=flightholding:GetNelements() + + -- Free parking spots. + local parking=flightholding:GetParking(self.airbase) + + + -- If no flight is waiting for takeoff return the holding flight or nil. + if not flightparking then + if parking then + return flightholding, true, parking + else + self:E(self.lid..string.format("WARNING: No flight parking but no parking spots! nP=%d nH=%d", #parking, nH)) + return nil, nil, nil + end + end + + + + -- We got flights waiting for landing and for takeoff. + if flightholding and flightparking then + + -- Return holding flight if fuel is low. + if flightholding.fuellow then + if parking then + -- Enough parking ==> land + return flightholding, true, parking + else + -- Not enough parking ==> take off + return flightparking, false, nil + end + end + + -- Return the flight which is waiting longer. NOTE that Tholding and Tparking are abs. mission time. So a smaller value means waiting longer. + if flightholding.Tholding0 then + + -- TODO: Could be sorted by distance to active runway! Take the runway spawn point for distance measure. + + -- First come, first serve. + return QreadyTO[1] + + end + + -- Get flights parking. + local Qparking=self:GetFlights(FLIGHTCONTROL.FlightStatus.PARKING) + + -- Check special cases where only up to one flight is waiting for takeoff. + if #Qparking==0 then + return nil + end + + -- Sort flights parking time. + local function _sortByTparking(a, b) + local flightA=a --Ops.FlightGroup#FLIGHTGROUP + local flightB=b --Ops.FlightGroup#FLIGHTGROUP + return flightA.Tparking=0 then + holding=UTILS.SecondsToClock(holding, true) + else + holding="X" + end + local parking=flight:GetParkingTime() + if parking>=0 then + parking=UTILS.SecondsToClock(parking, true) + else + parking="X" + end + + + local nunits=flight.nunits or 1 + + -- Main info. + text=text..string.format("\n[%d] %s (%s*%d): status=%s, ai=%s, fuel=%d, holding=%s, parking=%s", + i, flight.groupname, actype, nunits, flight:GetState(), ai, fuel, holding, parking) + + -- Elements info. + for j,_element in pairs(flight.elements) do + local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element + local life=element.unit:GetLife() + local life0=element.unit:GetLife0() + local park=element.parking and tostring(element.parking.TerminalID) or "N/A" + text=text..string.format("\n (%d) %s (%s): status=%s, ai=%s, airborne=%s life=%d/%d spot=%s", + j, tostring(element.modex), element.name, tostring(element.status), tostring(element.ai), tostring(element.unit:InAir()), life, life0, park) + end + end + end + + -- Display text. + self:I(self.lid..text) + + return text +end + +--- Remove a flight group from a queue. +-- @param #FLIGHTCONTROL self +-- @param #table queue The queue from which the group will be removed. +-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group that will be removed from queue. +-- @param #string queuename Name of the queue. +-- @return #boolean True, flight was in Queue and removed. False otherwise. +-- @return #number Table index of removed queue element or nil. +function FLIGHTCONTROL:_RemoveFlightFromQueue(queue, flight, queuename) + + queuename=queuename or "unknown" + + -- Loop over all flights in group. + for i,_flight in pairs(queue) do + local qflight=_flight --Ops.FlightGroup#FLIGHTGROUP + + -- Check for name. + if qflight.groupname==flight.groupname then + self:I(self.lid..string.format("Removing flight group %s from %s queue.", flight.groupname, queuename)) + table.remove(queue, i) + + if not flight.ai then + flight:_UpdateMenu() + end + + return true, i + end + end + + self:I(self.lid..string.format("Could NOT remove flight group %s from %s queue.", flight.groupname, queuename)) + return false, nil +end + + +--- Set flight status. +-- @param #FLIGHTCONTROL self +-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. +-- @param #string status New status. +function FLIGHTCONTROL:SetFlightStatus(flight, status) + + flight.controlstatus=status + +end + +--- Get flight status. +-- @param #FLIGHTCONTROL self +-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. +-- @return #string Flight status +function FLIGHTCONTROL:GetFlightStatus(flight) + + if flight then + return flight.controlstatus or "unkonwn" + end + + return "unknown" +end + +--- Check if FC has control over this flight. +-- @param #FLIGHTCONTROL self +-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. +-- @return #boolean +function FLIGHTCONTROL:IsControlling(flight) + + return flight.flightcontrol and flight.flightcontrol.airbasename==self.airbasename or false + +end + + + + +--- Check if a group is in a queue. +-- @param #FLIGHTCONTROL self +-- @param #table queue The queue to check. +-- @param Wrapper.Group#GROUP group The group to be checked. +-- @return #boolean If true, group is in the queue. False otherwise. +function FLIGHTCONTROL:_InQueue(queue, group) + local name=group:GetName() + + for _,_flight in pairs(queue) do + local flight=_flight --Ops.FlightGroup#FLIGHTGROUP + if name==flight.groupname then + return true + end + end + + return false +end + +--- Get flights. +-- @param #FLIGHTCONTROL self +-- @param #string Status Return only flights in this status. +-- @return #table Table of flights. +function FLIGHTCONTROL:GetFlights(Status) + + if Status then + + local flights={} + + for _,_flight in pairs(self.flights) do + local flight=_flight --Ops.FlightGroup#FLIGHTGROUP + + local status=self:GetFlightStatus(flight, Status) + + if status==Status then + table.insert(flights, flight) + end + + end + + return flights + else + return self.flights + end + +end + +--- Count flights in a given status. +-- @param #FLIGHTCONTROL self +-- @param #string Status Return only flights in this status. +-- @return #number +function FLIGHTCONTROL:CountFlights(Status) + + if Status then + + local flights=self:GetFlights(Status) + + return #flights + + else + return #self.flights + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Runway Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Initialize data of runways. +-- @param #FLIGHTCONTROL self +function FLIGHTCONTROL:_InitRunwayData() + self.runways=self.airbase:GetRunwayData() +end + +--- Get the active runway based on current wind direction. +-- @param #FLIGHTCONTROL self +-- @return Wrapper.Airbase#AIRBASE.Runway Active runway. +function FLIGHTCONTROL:GetActiveRunway() + return self.airbase:GetActiveRunway() +end + +--- Get the active runway based on current wind direction. +-- @param #FLIGHTCONTROL self +-- @return #string Runway text, e.g. "31L" or "09". +function FLIGHTCONTROL:GetActiveRunwayText() + local rwy="" + local rwyL + if self.atis then + rwy, rwyL=self.atis:GetActiveRunway() + if rwyL==true then + rwy=rwy.."L" + elseif rwyL==false then + rwy=rwy.."R" + end + else + rwy=self.airbase:GetActiveRunway().idx + end + return rwy +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Parking Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Init parking spots. +-- @param #FLIGHTCONTROL self +function FLIGHTCONTROL:_InitParkingSpots() + + -- Parking spots of airbase. + local parkingdata=self.airbase:GetParkingSpotsTable() + + -- Init parking spots table. + self.parking={} + + self.Nparkingspots=0 + for _,_spot in pairs(parkingdata) do + local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot + + + -- Mark position. + local text=string.format("Parking ID=%d, Terminal=%d: Free=%s, Client=%s, Dist=%.1f", spot.TerminalID, spot.TerminalType, tostring(spot.Free), tostring(spot.ClientSpot), spot.DistToRwy) + self:I(self.lid..text) + + -- Add to table. + self.parking[spot.TerminalID]=spot + + spot.Marker=MARKER:New(spot.Coordinate, "Spot"):ReadOnly() + spot.Marker.tocoaliton=true + spot.Marker.coalition=self:GetCoalition() + + -- Check if spot is initially free or occupied. + if spot.Free then + + -- Parking spot is free. + self:SetParkingFree(spot) + + else + + -- Scan for the unit sitting here. + local unit=spot.Coordinate:FindClosestUnit(20) + + + if unit then + + local unitname=unit and unit:GetName() or "unknown" + + local isalive=unit:IsAlive() + + env.info(string.format("FF parking spot %d is occupied by unit %s alive=%s", spot.TerminalID, unitname, tostring(isalive))) + + if isalive then + + + self:SetParkingOccupied(spot, unitname) + + self:SpawnParkingGuard(unit) + + else + + -- TODO + env.info(string.format("FF parking spot %d is occupied by NOT ALIVE unit %s", spot.TerminalID, unitname)) + + -- Parking spot is free. + self:SetParkingFree(spot) + + end + + else + self:I(self.lid..string.format("ERROR: Parking spot is NOT FREE but no unit could be found there!")) + end + end + + -- Increase counter + self.Nparkingspots=self.Nparkingspots+1 + end + +end + +--- Get parking spot by its Terminal ID. +-- @param #FLIGHTCONTROL self +-- @param #number TerminalID +-- @return #FLIGHTCONTROL.ParkingSpot Parking spot data table. +function FLIGHTCONTROL:GetParkingSpotByID(TerminalID) + return self.parking[TerminalID] +end + +--- Set parking spot to FREE and update F10 marker. +-- @param #FLIGHTCONTROL self +-- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot The parking spot data table. +function FLIGHTCONTROL:SetParkingFree(spot) + + local spot=self:GetParkingSpotByID(spot.TerminalID) + + spot.Status=AIRBASE.SpotStatus.FREE + spot.OccupiedBy=nil + spot.ReservedBy=nil + + self:UpdateParkingMarker(spot) + +end + +--- Set parking spot to RESERVED and update F10 marker. +-- @param #FLIGHTCONTROL self +-- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot The parking spot data table. +-- @param #string unitname Name of the unit occupying the spot. Default "unknown". +function FLIGHTCONTROL:SetParkingReserved(spot, unitname) + + local spot=self:GetParkingSpotByID(spot.TerminalID) + + spot.Status=AIRBASE.SpotStatus.RESERVED + spot.ReservedBy=unitname or "unknown" + + self:UpdateParkingMarker(spot) + +end + +--- Set parking spot to OCCUPIED and update F10 marker. +-- @param #FLIGHTCONTROL self +-- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot The parking spot data table. +-- @param #string unitname Name of the unit occupying the spot. Default "unknown". +function FLIGHTCONTROL:SetParkingOccupied(spot, unitname) + + local spot=self:GetParkingSpotByID(spot.TerminalID) + + spot.Status=AIRBASE.SpotStatus.OCCUPIED + spot.OccupiedBy=unitname or "unknown" + + self:UpdateParkingMarker(spot) + +end + +--- Get free parking spots. +-- @param #FLIGHTCONTROL self +-- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot The parking spot data table. +function FLIGHTCONTROL:UpdateParkingMarker(spot) + + local spot=self:GetParkingSpotByID(spot.TerminalID) + + env.info(string.format("FF updateing spot %d status=%s", spot.TerminalID, spot.Status)) + + -- Only mark OCCUPIED and RESERVED spots. + if spot.Status==AIRBASE.SpotStatus.FREE then + + if spot.Marker then + spot.Marker:Remove() + end + + else + + local text=string.format("Spot %d (type %d): %s", spot.TerminalID, spot.TerminalType, spot.Status:upper()) + if spot.OccupiedBy then + text=text..string.format("\nOccupied by %s", spot.OccupiedBy) + end + if spot.ReservedBy then + text=text..string.format("\nReserved for %s", spot.ReservedBy) + end + if spot.ClientSpot then + text=text..string.format("\nClient %s", tostring(spot.ClientSpot)) + end + + if spot.Marker then + + if text~=spot.Marker.text then + spot.Marker:UpdateText(text) + end + + else + + spot.Marker=MARKER:New(spot.Coordinate, text):ToAll() + + end + + end +end + +--- Check if parking spot is free. +-- @param #FLIGHTCONTROL self +-- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot data. +-- @return #boolean If true, parking spot is free. +function FLIGHTCONTROL:IsParkingFree(spot) + return spot.Status==AIRBASE.SpotStatus.FREE +end + +--- Check if a parking spot is reserved by a flight group. +-- @param #FLIGHTCONTROL self +-- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot to check. +-- @return #string Name of element or nil. +function FLIGHTCONTROL:IsParkingOccupied(spot) + + if spot.Status==AIRBASE.SpotStatus.OCCUPIED then + return tostring(spot.OccupiedBy) + else + return false + end +end + +--- Check if a parking spot is reserved by a flight group. +-- @param #FLIGHTCONTROL self +-- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot to check. +-- @return #string Name of element or *nil*. +function FLIGHTCONTROL:IsParkingReserved(spot) + + if spot.Status==AIRBASE.SpotStatus.RESERVED then + return tostring(spot.ReservedBy) + else + return false + end + + -- Init all elements as NOT parking anywhere. + for _,_flight in pairs(self.flights) do + local flight=_flight --Ops.FlightGroup#FLIGHTGROUP + -- Loop over all elements. + for _,_element in pairs(flight.elements) do + local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element + local parking=element.parking + if parking and parking.TerminalID==spot.TerminalID then + return element.name + end + end + end + + return nil +end + +--- Get free parking spots. +-- @param #FLIGHTCONTROL self +-- @param #number terminal Terminal type or nil. +-- @return #number Number of free spots. Total if terminal=nil or of the requested terminal type. +-- @return #table Table of free parking spots of data type #FLIGHCONTROL.ParkingSpot. +function FLIGHTCONTROL:_GetFreeParkingSpots(terminal) + + local freespots={} + + local n=0 + for _,_parking in pairs(self.parking) do + local parking=_parking --Wrapper.Airbase#AIRBASE.ParkingSpot + + if self:IsParkingFree(parking) then + if terminal==nil or terminal==parking.terminal then + n=n+1 + table.insert(freespots, parking) + end + end + end + + return n,freespots +end + +--- Get closest parking spot. +-- @param #FLIGHTCONTROL self +-- @param Core.Point#COORDINATE coordinate Reference coordinate. +-- @param #number terminaltype (Optional) Check only this terminal type. +-- @param #boolean free (Optional) If true, check only free spots. +-- @return #FLIGHTCONTROL.ParkingSpot Closest parking spot. +function FLIGHTCONTROL:GetClosestParkingSpot(coordinate, terminaltype, free) + + local distmin=math.huge + local spotmin=nil + + for TerminalID, Spot in pairs(self.parking) do + local spot=Spot --Wrapper.Airbase#AIRBASE.ParkingSpot + + if (not free) or (free==true and not (self:IsParkingReserved(spot) or self:IsParkingOccupied(spot))) then + if terminaltype==nil or terminaltype==spot.TerminalType then + + -- Get distance from coordinate to spot. + local dist=coordinate:Get2DDistance(spot.Coordinate) + + -- Check if distance is smaller. + if dist0 then + MESSAGE:New("Negative ghostrider, other flights are currently landing. Talk to you soon.", 5):ToAll() + self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.READYTO) + elseif Ntakeoff>0 then + MESSAGE:New("Negative ghostrider, other flights are ahead of you. Talk to you soon.", 5):ToAll() + self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.READYTO) + end + + else + MESSAGE:New(string.format("Negative, you must request TAXI before you can request TAKEOFF!"), 5):ToAll() + end + end + +end + +--- Player wants to abort takeoff. +-- @param #FLIGHTCONTROL self +-- @param #string groupname Name of the flight group. +function FLIGHTCONTROL:_PlayerAbortTakeoff(groupname) + + MESSAGE:New("Abort takeoff", 5):ToAll() + + local flight=_DATABASE:GetFlightGroup(groupname) + + if flight then + + if self:GetFlightStatus(flight)==FLIGHTCONTROL.FlightStatus.TAKEOFF then + + + MESSAGE:New("Afirm, You are removed from takeoff queue", 5):ToAll() + + --TODO: what now? taxi inbound? or just another later attempt to takeoff. + self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.READYTO) + + + else + MESSAGE:New("Negative, You are NOT in the takeoff queue", 5):ToAll() + end + + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Flight and Element Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new flight group. +-- @param #FLIGHTCONTROL self +-- @param Wrapper.Group#GROUP group Aircraft group. +-- @return Ops.FlightGroup#FLIGHTGROUP Flight group. +function FLIGHTCONTROL:_CreateFlightGroup(group) + + -- Check if not already in flights + if self:_InQueue(self.flights, group) then + self:E(self.lid..string.format("WARNING: Flight group %s does already exist!", group:GetName())) + return + end + + -- Debug info. + self:I(self.lid..string.format("Creating new flight for group %s of aircraft type %s.", group:GetName(), group:GetTypeName())) + + -- Get flightgroup from data base. + local flight=_DATABASE:GetFlightGroup(group:GetName()) + + -- If it does not exist yet, create one. + if not flight then + flight=FLIGHTGROUP:New(group:GetName()) + end + + --if flight.destination and flight.destination:GetName()==self.airbasename then + if flight.homebase and flight.homebase:GetName()==self.airbasename then + flight:SetFlightControl(self) + end + + return flight +end + +--- Remove flight from all queues. +-- @param #FLIGHTCONTROL self +-- @param Ops.FlightGroup#FLIGHTGROUP flight The flight to be removed. +function FLIGHTCONTROL:_RemoveFlight(flight) + + self:_RemoveFlightFromQueue(self.flights, flight, "flights") + +end + +--- Get flight from group. +-- @param #FLIGHTCONTROL self +-- @param Wrapper.Group#GROUP group Group that will be removed from queue. +-- @param #table queue The queue from which the group will be removed. +-- @return Ops.FlightGroup#FLIGHTGROUP Flight group or nil. +-- @return #number Queue index or nil. +function FLIGHTCONTROL:_GetFlightFromGroup(group) + + if group then + + -- Group name + local name=group:GetName() + + -- Loop over all flight groups in queue + for i,_flight in pairs(self.flights) do + local flight=_flight --Ops.FlightGroup#FLIGHTGROUP + + if flight.groupname==name then + return flight, i + end + end + + self:T2(self.lid..string.format("WARNING: Flight group %s could not be found in queue.", name)) + end + + self:T2(self.lid..string.format("WARNING: Flight group could not be found in queue. Group is nil!")) + return nil, nil +end + +--- Get element of flight from its unit name. +-- @param #FLIGHTCONTROL self +-- @param #string unitname Name of the unit. +-- @return #FLIGHTCONTROL.FlightElement Element of the flight or nil. +-- @return #number Element index or nil. +-- @return Ops.FlightGroup#FLIGHTGROUP The Flight group or nil. +function FLIGHTCONTROL:_GetFlightElement(unitname) + + -- Get the unit. + local unit=UNIT:FindByName(unitname) + + -- Check if unit exists. + if unit then + + -- Get flight element from all flights. + local flight=self:_GetFlightFromGroup(unit:GetGroup()) + + -- Check if fight exists. + if flight then + + -- Loop over all elements in flight group. + for i,_element in pairs(flight.elements) do + local element=_element --#FLIGHTCONTROL.FlightElement + + if element.unit:GetName()==unitname then + return element, i, flight + end + end + + self:T2(self.lid..string.format("WARNING: Flight element %s could not be found in flight group.", unitname, flight.groupname)) + end + end + + return nil, nil, nil +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Check Sanity Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check status of all registered flights and do some sanity checks. +-- @param #FLIGHTCONTROL self +function FLIGHTCONTROL:_CheckFlights() + + -- First remove all dead flights. + for i=#self.flights,1,-1 do + local flight=self.flights[i] --Ops.FlightGroup#FLIGHTGROUP + if flight:IsDead() then + self:I(self.lid..string.format("Removing DEAD flight %s", tostring(flight.groupname))) + self:_RemoveFlight(flight) + end + end + + --TODO: check parking? + +end + +--- Check status of all registered flights and do some sanity checks. +-- @param #FLIGHTCONTROL self +function FLIGHTCONTROL:_CheckParking() + + for TerminalID,_spot in pairs(self.parking) do + local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot + + if spot.Reserved then + if spot.MarkerID then + spot.Coordinate:RemoveMark(spot.MarkerID) + end + spot.MarkerID=spot.Coordinate:MarkToCoalition(string.format("Parking reserved for %s", tostring(spot.Reserved)), self:GetCoalition()) + end + + -- First remove all dead flights. + for i=1,#self.flights do + local flight=self.flights[i] --Ops.FlightGroup#FLIGHTGROUP + for _,_element in pairs(flight.elements) do + local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element + if element.parking and element.parking.TerminalID==TerminalID then + if spot.MarkerID then + spot.Coordinate:RemoveMark(spot.MarkerID) + end + spot.MarkerID=spot.Coordinate:MarkToCoalition(string.format("Parking spot occupied by %s", tostring(element.name)), self:GetCoalition()) + end + end + end + + end + + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Routing Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Tell AI to land at the airbase. Flight is added to the landing queue. +-- @param #FLIGHTCONTROL self +-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. +-- @param #table parking Free parking spots table. +function FLIGHTCONTROL:_LandAI(flight, parking) + + -- Debug info. + self:I(self.lid..string.format("Landing AI flight %s.", flight.groupname)) + + -- Set flight status to LANDING. + self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.LANDING) + + -- Flight is not holding any more. + flight.Tholding=nil + + + local respawn=false + + if respawn then + + -- Get group template. + local Template=flight.group:GetTemplate() + + -- TODO: get landing waypoints from flightgroup. + + -- Set route points. + Template.route.points=wp + + for i,unit in pairs(Template.units) do + local spot=parking[i] --Wrapper.Airbase#AIRBASE.ParkingSpot + + local element=flight:GetElementByName(unit.name) + if element then + + -- Set the parking spot at the destination airbase. + unit.parking_landing=spot.TerminalID + + local text=string.format("FF Reserving parking spot %d for unit %s", spot.TerminalID, tostring(unit.name)) + self:I(self.lid..text) + + -- Set parking to RESERVED. + self:SetParkingReserved(spot, element.name) + + else + env.info("FF error could not get element to assign parking!") + end + end + + -- Debug message. + MESSAGE:New(string.format("Respawning group %s", flight.groupname)):ToAll() + + --Respawn the group. + flight:Respawn(Template) + + else + + -- Give signal to land. + flight:ClearToLand() + + end + +end + +--- Get holding point. +-- @param #FLIGHTCONTROL self +-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. +-- @return #FLIGHTCONTROL.HoldingPoint Holding point. +function FLIGHTCONTROL:_GetHoldingpoint(flight) + + local holdingpoint={} --#FLIGHTCONTROL.HoldingPoint + + local runway=self:GetActiveRunway() + + local hdg=runway.heading+90 + local dx=UTILS.NMToMeters(5) + local dz=UTILS.NMToMeters(1) + + local angels=UTILS.FeetToMeters(math.random(6,10)*1000) + + holdingpoint.pos0=runway.position:Translate(dx, hdg):SetAltitude(angels) + holdingpoint.pos1=holdingpoint.pos0:Translate(dz, runway.heading):SetAltitude(angels) + + return holdingpoint +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Radio Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Transmission via RADIOQUEUE. +-- @param #FLIGHTCONTROL self +-- @param #FLIGHTCONTROL.Soundfile sound FLIGHTCONTROL sound object. +-- @param #number interval Interval in seconds after the last transmission finished. +-- @param #string subtitle Subtitle of the transmission. +-- @param #string path Path to sound file. Default self.soundpath. +function FLIGHTCONTROL:Transmission(sound, interval, subtitle, path) + self.radioqueue:NewTransmission(sound.filename, sound.duration, path or self.soundpath, nil, interval, subtitle, self.subduration) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Add parking guard in front of a parking aircraft. +-- @param #FLIGHTCONTROL self +-- @param Wrapper.Unit#UNIT unit The aircraft. +function FLIGHTCONTROL:SpawnParkingGuard(unit) + + if unit and self.parkingGuard then + + -- Position of the unit. + local coordinate=unit:GetCoordinate() + + -- Parking spot. + local spot=self:GetClosestParkingSpot(coordinate) + + -- Current heading of the unit. + local heading=unit:GetHeading() + + -- Length of the unit + 3 meters. + local size, x, y, z=unit:GetObjectSize() + + self:I(self.lid..string.format("Parking guard for %s: heading=%d, distance x=%.1f m", unit:GetName(), heading, x)) + + -- Coordinate for the guard. + local Coordinate=coordinate:Translate(0.75*x+3, heading) + + -- Let him face the aircraft. + local lookat=heading-180 + + -- Set heading and AI off to save resources. + self.parkingGuard:InitHeading(lookat):InitAIOff() + + -- Group that is spawned. + spot.ParkingGuard=self.parkingGuard:SpawnFromCoordinate(Coordinate) + --spot.ParkingGuard=self.parkingGuard:SpawnFromCoordinate(Coordinate, lookat) + + end + +end + +--- Remove parking guard. +-- @param #FLIGHTCONTROL self +-- @param #FLIGHTCONTROL.ParkingSpot spot +-- @param #number delay Delay in seconds. +function FLIGHTCONTROL:RemoveParkingGuard(spot, delay) + + if delay and delay>0 then + self:ScheduleOnce(delay, FLIGHTCONTROL.RemoveParkingGuard, self, spot) + else + + if spot.ParkingGuard then + spot.ParkingGuard:Destroy() + spot.ParkingGuard=nil + end + + end + +end + + +--- Get coordinate of the airbase. +-- @param #FLIGHTCONTROL self +-- @return Core.Point#COORDINATE Coordinate of the airbase. +function FLIGHTCONTROL:GetCoordinate() + return self.airbase:GetCoordinate() +end + +--- Get coalition of the airbase. +-- @param #FLIGHTCONTROL self +-- @return #number Coalition ID. +function FLIGHTCONTROL:GetCoalition() + return self.airbase:GetCoalition() +end + +--- Get country of the airbase. +-- @param #FLIGHTCONTROL self +-- @return #number Country ID. +function FLIGHTCONTROL:GetCountry() + return self.airbase:GetCountry() +end + +--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. +-- @param #FLIGHTCONTROL self +-- @param #string unitName Name of the player unit. +-- @return Wrapper.Unit#UNIT Unit of player or nil. +-- @return #string Name of the player or nil. +function FLIGHTCONTROL:_GetPlayerUnitAndName(unitName) + + if unitName then + + -- Get DCS unit from its name. + local DCSunit=Unit.getByName(unitName) + + if DCSunit then + + -- Get player name if any. + local playername=DCSunit:getPlayerName() + + -- Unit object. + local unit=UNIT:Find(DCSunit) + + -- Check if enverything is there. + if DCSunit and unit and playername then + self:T(self.lid..string.format("Found DCS unit %s with player %s", tostring(unitName), tostring(playername))) + return unit, playername + end + + end + + end + + -- Return nil if we could not find a player. + return nil,nil +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Platoon.lua b/Moose Development/Moose/Ops/Platoon.lua deleted file mode 100644 index e69de29bb..000000000 diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 13c6da66a..ae1a85fc9 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -83,6 +83,7 @@ Ops/AirWing.lua Ops/Intelligence.lua Ops/WingCommander.lua Ops/ChiefOfStaff.lua +Ops/FlightControl.lua Ops/CSAR.lua Ops/CTLD.lua From a5ad58e5162709bad29ba07eea3339781cd01838 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 2 Aug 2021 21:57:53 +0200 Subject: [PATCH 061/141] FC --- Moose Development/Moose/Ops/FlightControl.lua | 8 +++---- Moose Development/Moose/Wrapper/Airbase.lua | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index ded207415..58b7c11ae 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -1481,7 +1481,7 @@ end function FLIGHTCONTROL:_PlayerRequestParking(groupname) -- Get flight group. - local flight=_DATABASE:GetFlightGroup(groupname) + local flight=_DATABASE:GetOpsGroup(groupname) if flight then @@ -1535,7 +1535,7 @@ end function FLIGHTCONTROL:_PlayerRequestInfoATIS(groupname) -- Get flight group. - local flight=_DATABASE:GetFlightGroup(groupname) + local flight=_DATABASE:GetOpsGroup(groupname) if flight then @@ -1725,7 +1725,7 @@ function FLIGHTCONTROL:_PlayerRequestTaxi(groupname) MESSAGE:New("Request taxi to runway", 5):ToAll() - local flight=_DATABASE:GetFlightGroup(groupname) + local flight=_DATABASE:GetOpsGroup(groupname) if flight then @@ -1840,7 +1840,7 @@ function FLIGHTCONTROL:_CreateFlightGroup(group) self:I(self.lid..string.format("Creating new flight for group %s of aircraft type %s.", group:GetName(), group:GetTypeName())) -- Get flightgroup from data base. - local flight=_DATABASE:GetFlightGroup(group:GetName()) + local flight=_DATABASE:GetOpsGroup(group:GetName()) -- If it does not exist yet, create one. if not flight then diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 6887ab79d..8db1eacf0 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -473,6 +473,13 @@ AIRBASE.MarianaIslands={ -- @field #boolean Free This spot is currently free, i.e. there is no alive aircraft on it at the present moment. -- @field #number TerminalID0 Unknown what this means. If you know, please tell us! -- @field #number DistToRwy Distance to runway in meters. Currently bugged and giving the same number as the TerminalID. +-- @field #string AirbaseName Name of the airbase. +-- @field #number MarkerID Numerical ID of marker placed at parking spot. +-- @field Wrapper.Marker#MARKER Marker The marker on the F10 map. +-- @field #string ClientSpot Client unit sitting at this spot or *nil*. +-- @field #string Status Status of spot e.g. AIRBASE.SpotStatus.FREE. +-- @field #string OccupiedBy Name of the aircraft occupying the spot or "unknown". Can be *nil* if spot is not occupied. +-- @field #string ReservedBy Name of the aircraft for which this spot is reserved. Can be *nil* if spot is not reserved. --- Terminal Types of parking spots. See also https://wiki.hoggitworld.com/view/DCS_func_getParking -- @@ -507,6 +514,17 @@ AIRBASE.TerminalType = { FighterAircraft=244, } +--- Status of a parking spot. +-- @type AIRBASE.SpotStatus +-- @field #string FREE Spot is free. +-- @field #string OCCUPIED Spot is occupied. +-- @field #string RESERVED Spot is reserved. +AIRBASE.SpotStatus = { + FREE="Free", + OCCUPIED="Occupied", + RESERVED="Reserved", +} + --- Runway data. -- @type AIRBASE.Runway -- @field #number heading Heading of the runway in degrees. @@ -1029,6 +1047,8 @@ function AIRBASE:GetParkingSpotsTable(termtype) spot.Free=_isfree(_spot) -- updated spot.TOAC=_spot.TO_AC -- updated + spot.AirbaseName=self.AirbaseName + spot.ClientSpot=nil --TODO table.insert(spots, spot) @@ -1065,6 +1085,8 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC) spot.Free=true -- updated spot.TOAC=_spot.TO_AC -- updated + spot.AirbaseName=self.AirbaseName + spot.ClientSpot=nil --TODO table.insert(freespots, spot) From 69175e1b19a7502abcbabbed0f7fb8212429bd07 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 2 Aug 2021 23:34:42 +0200 Subject: [PATCH 062/141] FC --- Moose Development/Moose/Ops/FlightControl.lua | 10 +++++----- Moose Development/Moose/Ops/FlightGroup.lua | 11 +++++++---- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index 58b7c11ae..b97361749 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -590,7 +590,7 @@ function FLIGHTCONTROL:_CheckQueues() -- Give AI the landing signal. -- TODO: Humans have to confirm via F10 menu. - if flight.ai then + if flight.isAI then self:_LandAI(flight, parking) end @@ -612,7 +612,7 @@ function FLIGHTCONTROL:_CheckQueues() if ntakeoff==0 and nlanding==0 then -- Check if flight is AI. Humans have to request taxi via F10 menu. - if flight.ai then + if flight.isAI then --- -- AI @@ -822,7 +822,7 @@ function FLIGHTCONTROL:_GetNextFightParking() -- Get the first AI flight. for i,_flight in pairs(Qparking) do local flight=_flight --Ops.FlightGroup#FLIGHTGROUP - if flight.ai then + if flight.isAI then return flight end end @@ -851,7 +851,7 @@ function FLIGHTCONTROL:_PrintQueue(queue, name) -- Gather info. local fuel=flight.group:GetFuelMin()*100 - local ai=tostring(flight.ai) + local ai=tostring(flight.isAI) local actype=tostring(flight.actype) -- Holding and parking time. @@ -916,7 +916,7 @@ function FLIGHTCONTROL:_RemoveFlightFromQueue(queue, flight, queuename) self:I(self.lid..string.format("Removing flight group %s from %s queue.", flight.groupname, queuename)) table.remove(queue, i) - if not flight.ai then + if not flight.isAI then flight:_UpdateMenu() end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index aeed5a782..f65d1232c 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -902,11 +902,14 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() local nMissions=self:CountRemainingMissison() + local home=self.homebase and self.homebase:GetName() or "unknown" + local dest=self.destbase and self.destbase:GetName() or "unknown" + local fc=self.flightcontrol and self.flightcontrol.airbasename or "N/A" + local curr=self.currbase and self.currbase:GetName() or "N/A" - - local text=string.format("Status %s [%d/%d]: Tasks=%d (%d,%d) Curr=%d, Missions=%s, Waypoint=%d/%d, Detected=%d, Home=%s, Destination=%s", - fsmstate, #self.elements, #self.elements, nTaskTot, nTaskSched, nTaskWP, self.taskcurrent, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, - self.detectedunits:Count(), self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "unknown") + local text=string.format("Status %s [%d/%d]: Tasks=%d, Missions=%s, Waypoint=%d/%d, Detected=%d, Home=%s, Destination=%s, Current=%s, FC=%s", + fsmstate, #self.elements, #self.elements, nTaskTot, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, + self.detectedunits:Count(), home, dest, curr, fc) self:I(self.lid..text) end From 8c573c65d4ed75598f526363b75bd05cbfde634c Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 3 Aug 2021 22:37:27 +0200 Subject: [PATCH 063/141] Update FlightControl.lua --- Moose Development/Moose/Ops/FlightControl.lua | 108 +++++++++++------- 1 file changed, 65 insertions(+), 43 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index b97361749..97917bb7e 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -16,7 +16,7 @@ --- FLIGHTCONTROL class. -- @type FLIGHTCONTROL -- @field #string ClassName Name of the class. --- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #number verbose Verbosity level. -- @field #string theatre The DCS map used in the mission. -- @field #string lid Class id string for output to DCS log file. -- @field #string airbasename Name of airbase. @@ -52,7 +52,7 @@ -- @field #FLIGHTCONTROL FLIGHTCONTROL = { ClassName = "FLIGHTCONTROL", - Debug = false, + verbose = 3, lid = nil, theatre = nil, airbasename = nil, @@ -135,16 +135,14 @@ FLIGHTCONTROL.Sound = { --- FlightControl class version. -- @field #string version -FLIGHTCONTROL.version="0.4.0" +FLIGHTCONTROL.version="0.5.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list --- --- TODO: Define holding zone --- TODO: --- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - + +-- TODO: Runway destroyed. +-- TODO: Define holding zone -- DONE: Add parking guard. -- TODO: Accept and forbit parking spots. -- NOGO: Add FARPS? @@ -213,14 +211,6 @@ function FLIGHTCONTROL:New(airbasename) -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- Update status. - - -- Debug trace. - if false then - self.Debug=true - BASE:TraceOnOff(true) - BASE:TraceClass(self.ClassName) - BASE:TraceLevel(1) - end -- Add to data base. _DATABASE:AddFlightControl(self) @@ -379,15 +369,34 @@ function FLIGHTCONTROL:onafterStatus() end -- Info text. - local text=string.format("State %s - Runway %s - Parking F=%d/O=%d/R=%d of %d - Flights=%s: Qpark=%d Qtxout=%d Qready=%d Qto=%d | Qinbound=%d Qhold=%d Qland=%d Qtxinb=%d Qarr=%d", - self:GetState(), runway.idx, Nfree, Noccu, Nresv, self.Nparkingspots, Nflights, NQparking, NQtaxiout, NQreadyto, NQtakeoff, NQinbound, NQholding, NQlanding, NQtaxiinb, NQarrived) - self:I(self.lid..text) + if self.verbose>0 then + local text=string.format("State %s - Runway %s - Parking F=%d/O=%d/R=%d of %d - Flights=%s", self:GetState(), runway.idx, Nfree, Noccu, Nresv, self.Nparkingspots, Nflights) + self:I(self.lid..text) + end if Nflights==Nqueues then --Check! else self:E(string.format("WARNING: Number of total flights %d!=%d number of flights in all queues!", Nflights, Nqueues)) end + + if self.verbose>1 then + local text="Queue:" + text=text..string.format("\n- Flights = %d", Nflights) + text=text..string.format("\n---------------------------------------------") + text=text..string.format("\n- Parking = %d", NQparking) + text=text..string.format("\n- Taxi Out = %d", NQtaxiout) + text=text..string.format("\n- Ready TO = %d", NQreadyto) + text=text..string.format("\n- Take off = %d", NQtakeoff) + text=text..string.format("\n---------------------------------------------") + text=text..string.format("\n- Inbound = %d", NQinbound) + text=text..string.format("\n- Holding = %d", NQholding) + text=text..string.format("\n- Landing = %d", NQlanding) + text=text..string.format("\n- Taxi Inb = %d", NQtaxiinb) + text=text..string.format("\n- Arrived = %d", NQarrived) + text=text..string.format("\n---------------------------------------------") + self:I(self.lid..text) + end -- Next status update in ~30 seconds. self:__Status(-20) @@ -420,30 +429,30 @@ function FLIGHTCONTROL:OnEventBirth(EventData) if EventData and EventData.IniGroupName and EventData.IniUnit then - self:I(self.lid..string.format("BIRTH: unit = %s", tostring(EventData.IniUnitName))) + -- Debug + self:T2(self.lid..string.format("BIRTH: unit = %s", tostring(EventData.IniUnitName))) self:T2(self.lid..string.format("BIRTH: group = %s", tostring(EventData.IniGroupName))) -- Unit that was born. - local unit=EventData.IniUnit + local unit=EventData.IniUnit + + -- Check if birth took place at this airfield. + local bornhere=EventData.Place and EventData.Place:GetName()==self.airbasename or false -- We delay this, to have all elements of the group in the game. - if unit:IsAir() then - - local bornhere=EventData.Place and EventData.Place:GetName()==self.airbasename or false - env.info("FF born here ".. tostring(bornhere)) - + if unit:IsAir() and bornhere then + -- We got a player? local playerunit, playername=self:_GetPlayerUnitAndName(EventData.IniUnitName) - if playername or bornhere then - - self:ScheduleOnce(0.5, self._CreateFlightGroup, self, EventData.IniGroup) - - end - - if bornhere then - self:SpawnParkingGuard(unit) + -- Create flight group. + if playername then + --self:ScheduleOnce(0.5, self._CreateFlightGroup, self, EventData.IniGroup) end + self:ScheduleOnce(0.5, self._CreateFlightGroup, self, EventData.IniGroup) + + -- Spawn parking guard. + self:SpawnParkingGuard(unit) end @@ -933,10 +942,16 @@ end -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @param #string status New status. +-- @param #FLIGHTCONTROL self function FLIGHTCONTROL:SetFlightStatus(flight, status) + -- Debug info. + self:I(self.lid..string.format("New Flight Status for %s [%s]: %s-->%s", flight:GetName(), flight:GetState(), tostring(flight.controlstatus), status)) + + -- Set control status flight.controlstatus=status + return self end --- Get flight status. @@ -955,16 +970,12 @@ end --- Check if FC has control over this flight. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. --- @return #boolean +-- @return #boolean If true, this FC is controlling this flight group. function FLIGHTCONTROL:IsControlling(flight) - - return flight.flightcontrol and flight.flightcontrol.airbasename==self.airbasename or false - + return flight.flightcontrol and flight.flightcontrol.airbasename==self.airbasename or false end - - --- Check if a group is in a queue. -- @param #FLIGHTCONTROL self -- @param #table queue The queue to check. @@ -1158,6 +1169,9 @@ function FLIGHTCONTROL:SetParkingFree(spot) local spot=self:GetParkingSpotByID(spot.TerminalID) + -- Debug info. + self:I(self.lid..string.format("Parking spot %d: %s-->%s", spot.TerminalID, tostring(spot.Status), AIRBASE.SpotStatus.FREE)) + spot.Status=AIRBASE.SpotStatus.FREE spot.OccupiedBy=nil spot.ReservedBy=nil @@ -1174,6 +1188,9 @@ function FLIGHTCONTROL:SetParkingReserved(spot, unitname) local spot=self:GetParkingSpotByID(spot.TerminalID) + -- Debug info. + self:I(self.lid..string.format("Parking spot %d: %s-->%s", spot.TerminalID, tostring(spot.Status), AIRBASE.SpotStatus.RESERVED)) + spot.Status=AIRBASE.SpotStatus.RESERVED spot.ReservedBy=unitname or "unknown" @@ -1188,7 +1205,10 @@ end function FLIGHTCONTROL:SetParkingOccupied(spot, unitname) local spot=self:GetParkingSpotByID(spot.TerminalID) - + + -- Debug info. + self:I(self.lid..string.format("Parking spot %d: %s-->%s", spot.TerminalID, tostring(spot.Status), AIRBASE.SpotStatus.OCCUPIED)) + spot.Status=AIRBASE.SpotStatus.OCCUPIED spot.OccupiedBy=unitname or "unknown" @@ -1203,7 +1223,7 @@ function FLIGHTCONTROL:UpdateParkingMarker(spot) local spot=self:GetParkingSpotByID(spot.TerminalID) - env.info(string.format("FF updateing spot %d status=%s", spot.TerminalID, spot.Status)) + --env.info(string.format("FF updateing spot %d status=%s", spot.TerminalID, spot.Status)) -- Only mark OCCUPIED and RESERVED spots. if spot.Status==AIRBASE.SpotStatus.FREE then @@ -1851,7 +1871,9 @@ function FLIGHTCONTROL:_CreateFlightGroup(group) if flight.homebase and flight.homebase:GetName()==self.airbasename then flight:SetFlightControl(self) end - + + flight:SetVerbosity(2) + return flight end From f67cf994771f26ec7c76a8b8b4440c7b51250a79 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 5 Aug 2021 00:23:25 +0200 Subject: [PATCH 064/141] OPS and SCHEDULER --- Moose Development/Moose/Core/Base.lua | 19 ++++++---- Moose Development/Moose/Core/Fsm.lua | 2 +- .../Moose/Core/ScheduleDispatcher.lua | 2 +- Moose Development/Moose/Core/Scheduler.lua | 4 +-- .../Moose/Functional/Warehouse.lua | 20 ++++++++--- Moose Development/Moose/Ops/ArmyGroup.lua | 2 +- Moose Development/Moose/Ops/FlightGroup.lua | 1 + Moose Development/Moose/Ops/OpsGroup.lua | 35 ++++++++++++------- Moose Development/Moose/Wrapper/Group.lua | 5 ++- 9 files changed, 59 insertions(+), 31 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index e085f0081..6091eb5c8 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -873,7 +873,7 @@ do -- Scheduling -- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. -- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. -- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. - -- @return #number The ScheduleID of the planned schedule. + -- @return #string The Schedule ID of the planned schedule. function BASE:ScheduleOnce( Start, SchedulerFunction, ... ) self:F2( { Start } ) self:T3( { ... } ) @@ -887,6 +887,8 @@ do -- Scheduling self.Scheduler = SCHEDULER:New( self ) end + -- FF this was wrong! + --[[ local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( self, SchedulerFunction, @@ -896,6 +898,10 @@ do -- Scheduling nil, nil ) + ]] + + -- NOTE: MasterObject (first parameter) needs to be nil or it will be the first argument passed to the SchedulerFunction! + local ScheduleID = self.Scheduler:Schedule(nil, SchedulerFunction, {...}, Start, nil, nil, nil) self._.Schedules[#self._.Schedules+1] = ScheduleID @@ -910,7 +916,7 @@ do -- Scheduling -- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped. -- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. -- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. - -- @return #number The ScheduleID of the planned schedule. + -- @return #string The Schedule ID of the planned schedule. function BASE:ScheduleRepeat( Start, Repeat, RandomizeFactor, Stop, SchedulerFunction, ... ) self:F2( { Start } ) self:T3( { ... } ) @@ -924,6 +930,7 @@ do -- Scheduling self.Scheduler = SCHEDULER:New( self ) end + -- NOTE: MasterObject (first parameter) should(!) be nil as it will be the first argument passed to the SchedulerFunction! local ScheduleID = self.Scheduler:Schedule( self, SchedulerFunction, @@ -942,13 +949,13 @@ do -- Scheduling --- Stops the Schedule. -- @param #BASE self - -- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. - function BASE:ScheduleStop( SchedulerFunction ) - + -- @param #string SchedulerID (Optional) Scheduler ID to be stopped. If nil, all pending schedules are stopped. + function BASE:ScheduleStop( SchedulerID ) self:F3( { "ScheduleStop:" } ) if self.Scheduler then - _SCHEDULEDISPATCHER:Stop( self.Scheduler, self._.Schedules[SchedulerFunction] ) + --_SCHEDULEDISPATCHER:Stop( self.Scheduler, self._.Schedules[SchedulerFunction] ) + _SCHEDULEDISPATCHER:Stop(self.Scheduler, SchedulerID) end end diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 68807904b..1b7af9253 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -1431,7 +1431,7 @@ do -- FSM_SET -- @param #FSM_SET self -- @return Core.Set#SET_BASE function FSM_SET:Get() - return self.Controllable + return self.Set end function FSM_SET:_call_handler( step, trigger, params, EventName ) diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index db04a1500..c28891239 100644 --- a/Moose Development/Moose/Core/ScheduleDispatcher.lua +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -317,7 +317,7 @@ end --- Stop dispatcher. -- @param #SCHEDULEDISPATCHER self -- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object. --- @param #table CallID Call ID. +-- @param #string CallID (Optional) Scheduler Call ID. If nil, all pending schedules are stopped recursively. function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) self:F2( { Stop = CallID, Scheduler = Scheduler } ) diff --git a/Moose Development/Moose/Core/Scheduler.lua b/Moose Development/Moose/Core/Scheduler.lua index 781b90ebe..ee0950f22 100644 --- a/Moose Development/Moose/Core/Scheduler.lua +++ b/Moose Development/Moose/Core/Scheduler.lua @@ -238,7 +238,7 @@ end -- @param #number Stop Time interval in seconds after which the scheduler will be stoppe. -- @param #number TraceLevel Trace level [0,3]. Default 3. -- @param Core.Fsm#FSM Fsm Finite state model. --- @return #table The ScheduleID of the planned schedule. +-- @return #string The Schedule ID of the planned schedule. function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, TraceLevel, Fsm ) self:F2( { Start, Repeat, RandomizeFactor, Stop } ) self:T3( { SchedulerArguments } ) @@ -273,7 +273,7 @@ end --- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided. -- @param #SCHEDULER self --- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule. +-- @param #string ScheduleID (Optional) The Schedule ID of the planned (repeating) schedule. function SCHEDULER:Start( ScheduleID ) self:F3( { ScheduleID } ) self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID))) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index e352dce84..ae408b71e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -3989,9 +3989,21 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu -- Destroy group if it is alive. if group:IsAlive()==true then self:_DebugMessage(string.format("Removing group %s", group:GetName()), 5) - -- Setting parameter to false, i.e. creating NO dead or remove unit event, seems to not confuse the dispatcher logic. - -- TODO: It would be nice, however, to have the remove event. - group:Destroy() --(false) + + local opsgroup=_DATABASE:GetOpsGroup(group:GetName()) + if opsgroup then + opsgroup:Despawn(0) + opsgroup:__Stop(-0.01) + else + -- Setting parameter to false, i.e. creating NO dead or remove unit event, seems to not confuse the dispatcher logic. + -- TODO: It would be nice, however, to have the remove event. + group:Destroy() --(false) + end + else + local opsgroup=_DATABASE:GetOpsGroup(group:GetName()) + if opsgroup then + opsgroup:Stop() + end end else @@ -8766,7 +8778,7 @@ end -- @param #number duration Message display duration in seconds. Default 20 sec. If duration is zero, no message is displayed. function WAREHOUSE:_DebugMessage(text, duration) duration=duration or 20 - if duration>0 then + if self.Debug and duration>0 then MESSAGE:New(text, duration):ToAllIf(self.Debug) end self:T(self.lid..text) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 2bcef539a..90a843e17 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1178,7 +1178,7 @@ function ARMYGROUP:_InitGroup(Template) self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) -- Set default formation from first waypoint. - self.optionDefault.Formation=self:GetWaypoint(1).action + self.optionDefault.Formation=template.route.points[1].action --self:GetWaypoint(1).action -- Default TACAN off. self:SetDefaultTACAN(nil, nil, nil, nil, true) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index aeed5a782..ca132af8c 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1970,6 +1970,7 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) else -- Depawn after 5 min. Important to trigger dead events before DCS despawns on its own without any notification. + self:T(self.lid..string.format("Despawning group in 5 minutes after arrival!")) self:Despawn(5*60) end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index e5d7feb93..be7b577b1 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -546,7 +546,7 @@ function OPSGROUP:New(group) -- From State --> Event --> To State self:AddTransition("InUtero", "Spawned", "Spawned") -- The whole group was spawned. self:AddTransition("*", "Respawn", "InUtero") -- Respawn group. - self:AddTransition("*", "Dead", "Dead") -- The whole group is dead. + self:AddTransition("*", "Dead", "InUtero") -- The whole group is dead and goes back to mummy. self:AddTransition("*", "InUtero", "InUtero") -- Deactivated group goes back to mummy. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -1326,8 +1326,10 @@ end function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) if Delay and Delay>0 then - self:ScheduleOnce(Delay, OPSGROUP.Despawn, self, 0, NoEventRemoveUnit) + self.scheduleIDDespawn=self:ScheduleOnce(Delay, OPSGROUP.Despawn, self, 0, NoEventRemoveUnit) else + + self:I(self.lid..string.format("Despawning Group!")) local DCSGroup=self:GetDCSGroup() @@ -1422,8 +1424,8 @@ end function OPSGROUP:Activate(delay) if delay and delay>0 then - self:T2(self.lid..string.format("Activating late activated group in %d seconds", delay)) - self:ScheduleOnce(delay, OPSGROUP.Activate, self) + self:T2(self.lid..string.format("Activating late activated group in %d seconds", delay)) + self:ScheduleOnce(delay, OPSGROUP.Activate, self) else if self:IsAlive()==false then @@ -1750,16 +1752,11 @@ function OPSGROUP:IsSpawned() return is end ---- Check if group is dead. +--- Check if group is dead. Could be destroyed or despawned. FSM state of dead group is `InUtero` though. -- @param #OPSGROUP self -- @return #boolean If true, all units/elements of the group are dead. function OPSGROUP:IsDead() - if self.isDead then - return true - else - local is=self:Is("Dead") - return is - end + return self.isDead end --- Check if group was destroyed. @@ -4960,6 +4957,16 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) return self end +--- On after "InUtero" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterInUtero(From, Event, To) + self:T(self.lid..string.format("Group inutero at t=%.3f", timer.getTime())) + --TODO: set element status to inutero +end + --- On after "Destroyed" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -5031,8 +5038,7 @@ function OPSGROUP:onafterDead(From, Event, To) -- No current cargo transport. self.cargoTransport=nil - - + -- Stop in a sec. --self:__Stop(-5) end @@ -5083,6 +5089,9 @@ function OPSGROUP:onafterStop(From, Event, To) -- Stop FSM scheduler. self.CallScheduler:Clear() + if self.Scheduler then + self.Scheduler:Clear() + end if self:IsAlive() and not (self:IsDead() or self:IsStopped()) then local life, life0=self:GetLifePoints() diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index f844a8ad2..8eb360206 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -338,8 +338,7 @@ end -- If the first @{Wrapper.Unit} of the group is inactive, it will return false. -- -- @param #GROUP self --- @return #boolean true if the group is alive and active. --- @return #boolean false if the group is alive but inactive or #nil if the group does not exist anymore. +-- @return #boolean `true` if the group is alive *and* active, `false` if the group is alive but inactive or `#nil` if the group does not exist anymore. function GROUP:IsAlive() self:F2( self.GroupName ) @@ -361,7 +360,7 @@ end --- Returns if the group is activated. -- @param #GROUP self --- @return #boolean true if group is activated or #nil The group is not existing or alive. +-- @return #boolean `true` if group is activated or `#nil` The group is not existing or alive. function GROUP:IsActive() self:F2( self.GroupName ) From 4a56c7523da7cf116b99e2d9fda14b040ab39971 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 5 Aug 2021 12:44:43 +0200 Subject: [PATCH 065/141] Wingcommander --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/ChiefOfStaff.lua | 3 +-- Moose Development/Moose/Ops/WingCommander.lua | 26 +++++++++++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index bac21d429..4081249a0 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -84,6 +84,7 @@ __Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/WingCommander.lua' ) __Moose.Include( 'Scripts/Moose/Ops/ChiefOfStaff.lua' ) __Moose.Include( 'Scripts/Moose/Ops/FlightControl.lua' ) __Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' ) diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua index b3f56fced..69f644aa4 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -14,7 +14,7 @@ --- CHIEF class. -- @type CHIEF -- @field #string ClassName Name of the class. --- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field #table missionqueue Mission queue. -- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory. @@ -145,7 +145,6 @@ function CHIEF:New(AgentSet, Coalition) -- Debug trace. if false then - self.Debug=true BASE:TraceOnOff(true) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) diff --git a/Moose Development/Moose/Ops/WingCommander.lua b/Moose Development/Moose/Ops/WingCommander.lua index d3aaeceae..78e6179fd 100644 --- a/Moose Development/Moose/Ops/WingCommander.lua +++ b/Moose Development/Moose/Ops/WingCommander.lua @@ -112,12 +112,10 @@ function WINGCOMMANDER:New() -- Debug trace. if false then - self.Debug=true BASE:TraceOnOff(true) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) end - self.Debug=true return self end @@ -214,6 +212,30 @@ function WINGCOMMANDER:onafterStatus(From, Event, To) -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() + -- Status. + local text=string.format(self.lid.."Status %s: Airwings=%d, Missions=%d", fsmstate, #self.airwings, #self.missionqueue) + self:I(self.lid..text) + + -- Airwing Info + if #self.airwings>0 then + local text="Airwings:" + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + local Nassets=airwing:CountAssets() + local Nastock=airwing:CountAssetsInStock() + text=text..string.format("\n* %s [%s]: Assets=%s stock=%s", airwing.alias, airwing:GetState(), Nassets, Nastock) + for _,aname in pairs(AUFTRAG.Type) do + local na=airwing:CountAssetsInStock({aname}) + local np=airwing:CountPayloadsInStock({aname}) + local nm=airwing:CountAssetsOnMission({aname}) + if na>0 or np>0 then + text=text..string.format("\n - %s: assets=%d, payloads=%d, on mission=%d", aname, na, np, nm) + end + end + end + self:I(self.lid..text) + end + -- Mission queue. if #self.missionqueue>0 then From ed402e2f5fe761a44a66cf43e94ec975bc4f81bb Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 6 Aug 2021 10:01:23 +0200 Subject: [PATCH 066/141] OPS Chief --- Moose Development/Moose/Ops/AirWing.lua | 1 + Moose Development/Moose/Ops/Auftrag.lua | 47 +++++++---- Moose Development/Moose/Ops/ChiefOfStaff.lua | 84 ++++++++++++++++--- Moose Development/Moose/Ops/Intelligence.lua | 6 +- Moose Development/Moose/Ops/WingCommander.lua | 9 +- 5 files changed, 113 insertions(+), 34 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 58e8ebb95..65e01063b 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -642,6 +642,7 @@ function AIRWING:RemoveMission(Mission) local mission=_mission --Ops.Auftrag#AUFTRAG if mission.auftragsnummer==Mission.auftragsnummer then + mission.airwing=nil table.remove(self.missionqueue, i) break end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index de8f0e82b..faaab5881 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -91,6 +91,7 @@ -- @field #number artyRadius Radius in meters. -- @field #number artyShots Number of shots fired. -- +-- @field Ops.ChiefOfStaff#CHIEF chief The CHIEF managing this mission. -- @field Ops.WingCommander#WINGCOMMANDER wingcommander The WINGCOMMANDER managing this mission. -- @field Ops.AirWing#AIRWING airwing The assigned airwing. -- @field #table assets Airwing Assets assigned for this mission. @@ -2185,10 +2186,10 @@ function AUFTRAG:onafterStatus(From, Event, To) local targetname=self:GetTargetName() or "unknown" local airwing=self.airwing and self.airwing.alias or "N/A" - local commander=self.wingcommander and tostring(self.wingcommander.coalition) or "N/A" + local chief=self.chief and tostring(self.chief.coalition) or "N/A" -- Info message. - self:I(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, commander=%s", self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, airwing, commander)) + self:I(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, chief=%s", self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, airwing, chief)) end -- Group info. @@ -2204,11 +2205,6 @@ function AUFTRAG:onafterStatus(From, Event, To) -- Ready to evaluate mission outcome? local ready2evaluate=self.Tover and Tnow-self.Tover>=self.dTevaluate or false - - --env.info("FF Tover="..tostring(self.Tover)) - --if self.Tover then - -- env.info("FF Tnow-Tover="..tostring(Tnow-self.Tover)) - --end -- Check if mission is OVER (done or cancelled) and enough time passed to evaluate the result. if self:IsOver() and ready2evaluate then @@ -2714,11 +2710,17 @@ function AUFTRAG:onafterCancel(From, Event, To) -- Not necessary to delay the evaluaton?! self.dTevaluate=0 - if self.wingcommander then + if self.chief then + + self:T(self.lid..string.format("Chief will cancel the mission. Will wait for mission DONE before evaluation!")) + + self.chief:MissionCancel(self) + + elseif self.wingcommander then self:T(self.lid..string.format("Wingcommander will cancel the mission. Will wait for mission DONE before evaluation!")) - self.wingcommander:CancelMission(self) + self.wingcommander:MissionCancel(self) elseif self.airwing then @@ -2729,7 +2731,7 @@ function AUFTRAG:onafterCancel(From, Event, To) else - self:T(self.lid..string.format("No airwing or wingcommander. Attached flights will cancel the mission on their own. Will wait for mission DONE before evaluation!")) + self:T(self.lid..string.format("No airwing, wingcommander or chief. Attached flights will cancel the mission on their own. Will wait for mission DONE before evaluation!")) for _,_groupdata in pairs(self.groupdata) do local groupdata=_groupdata --#AUFTRAG.GroupData @@ -2831,8 +2833,16 @@ function AUFTRAG:onafterRepeat(From, Event, To) self.repeated=self.repeated+1 if self.chief then - - --TODO + + -- Remove mission from wingcommander because Cheif will assign it again. + if self.wingcommander then + self.wingcommander:RemoveMission(self) + end + + -- Remove mission from airwing because WC will assign it again but maybe to a different wing. + if self.airwing then + self.airwing:RemoveMission(self) + end elseif self.wingcommander then @@ -2849,6 +2859,7 @@ function AUFTRAG:onafterRepeat(From, Event, To) else self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, WINGCOMMANDER or AIRWING! Stopping AUFTRAG") self:Stop() + return end @@ -2882,19 +2893,27 @@ end -- @param #string To To state. function AUFTRAG:onafterStop(From, Event, To) + -- Debug info. self:I(self.lid..string.format("STOPPED mission in status=%s. Removing missions from queues. Stopping CallScheduler!", self.status)) - - -- TODO: remove missions from queues in WINGCOMMANDER, AIRWING and FLIGHGROUPS! + -- TODO: Mission should be OVER! we dont want to remove running missions from any queues. + -- Remove mission from CHIEF queue. + if self.chief then + self.chief:RemoveMission(self) + end + + -- Remove mission from WINGCOMMANDER queue. if self.wingcommander then self.wingcommander:RemoveMission(self) end + -- Remove mission from AIRWING queue. if self.airwing then self.airwing:RemoveMission(self) end + -- Remove mission from OPSGROUP queue for _,_groupdata in pairs(self.groupdata) do local groupdata=_groupdata --#AUFTRAG.GroupData groupdata.opsgroup:RemoveMission(self) diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua index 69f644aa4..fcb26abc6 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -17,6 +17,7 @@ -- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field #table missionqueue Mission queue. +-- @field #table targetqueue Target queue. -- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory. -- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected. -- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged. @@ -42,11 +43,12 @@ -- @field #CHIEF CHIEF = { ClassName = "CHIEF", - Debug = nil, + verbose = 0, lid = nil, wingcommander = nil, admiral = nil, general = nil, + targetqueue = {}, missionqueue = {}, borderzoneset = nil, yellowzoneset = nil, @@ -88,8 +90,6 @@ CHIEF.version="0.0.1" -- @return #CHIEF self function CHIEF:New(AgentSet, Coalition) - AgentSet=AgentSet or SET_GROUP:New() - -- Inherit everything from INTEL class. local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition)) --#CHIEF @@ -108,8 +108,11 @@ function CHIEF:New(AgentSet, Coalition) self:AddTransition("*", "AssignMissionAirforce", "*") -- Assign mission to a WINGCOMMANDER. self:AddTransition("*", "AssignMissionNavy", "*") -- Assign mission to an ADMIRAL. self:AddTransition("*", "AssignMissionArmy", "*") -- Assign mission to a GENERAL. - self:AddTransition("*", "CancelMission", "*") -- Cancel mission. + + self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + self:AddTransition("*", "Defcon", "*") -- Change defence condition. + self:AddTransition("*", "DeclareWar", "*") -- Declare War. ------------------------ @@ -253,6 +256,8 @@ end -- @return #CHIEF self function CHIEF:AddMission(Mission) + Mission.chief=self + table.insert(self.missionqueue, Mission) return self @@ -269,6 +274,7 @@ function CHIEF:RemoveMission(Mission) if mission.auftragsnummer==Mission.auftragsnummer then self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status)) + Mission.chief=nil table.remove(self.missionqueue, i) break end @@ -278,6 +284,18 @@ function CHIEF:RemoveMission(Mission) return self end +--- Add target. +-- @param #CHIEF self +-- @param Ops.Target#TARGET Target Target object to be added. +-- @return #CHIEF self +function CHIEF:AddTarget(Target) + + table.insert(self.targetqueue, Target) + + return self +end + + --- Set border zone set. -- @param #CHIEF self -- @param Core.Set#SET_ZONE BorderZoneSet Set of zones, defining our borders. @@ -371,7 +389,7 @@ function CHIEF:onafterStatus(From, Event, To) -- Clean up missions where the contact was lost. for _,_contact in pairs(self.ContactsLost) do - local contact=_contact --#INTEL.Contact + local contact=_contact --Ops.Intelligence#INTEL.Contact if contact.mission and contact.mission:IsNotOver() then @@ -389,7 +407,7 @@ function CHIEF:onafterStatus(From, Event, To) -- Create missions for all new contacts. local Nred=0 ; local Nyellow=0 ; local Nengage=0 for _,_contact in pairs(self.Contacts) do - local contact=_contact --#CHIEF.Contact + local contact=_contact --Ops.Intelligence#INTEL.Contact local group=contact.group --Wrapper.Group#GROUP local inred=self:CheckGroupInBorder(group) @@ -455,8 +473,25 @@ function CHIEF:onafterStatus(From, Event, To) -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() - local text=string.format("Defcon=%s Missions=%d Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred) + local text=string.format("Defcon=%s Missions=%d, Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred) self:I(self.lid..text) + + --- + -- Target Queue + --- + + for _,_target in pairs(self.targetqueue) do + local target=_target --Ops.Target#TARGET + + if target:IsAlive() then + + if self:CheckTargetInZones(target, self.borderzoneset) then + + end + + end + + end --- -- Contacts @@ -466,7 +501,7 @@ function CHIEF:onafterStatus(From, Event, To) if #self.Contacts>0 then local text="Contacts:" for i,_contact in pairs(self.Contacts) do - local contact=_contact --#CHIEF.Contact + local contact=_contact --Ops.Intelligence#INTEL.Contact local mtext="N/A" if contact.mission then mtext=string.format("Mission %s (%s) %s", contact.mission.name, contact.mission.type, contact.mission.status:upper()) @@ -512,13 +547,13 @@ function CHIEF:onafterAssignMissionAirforce(From, Event, To, Mission) end ---- On after "CancelMission" event. +--- On after "MissionCancel" 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:onafterCancelMission(From, Event, To, Mission) +function CHIEF:onafterMissionCancel(From, Event, To, Mission) self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status)) @@ -527,11 +562,16 @@ function CHIEF:onafterCancelMission(From, Event, To, Mission) -- Mission is still in planning stage. Should not have an airbase assigned ==> Just remove it form the queue. self:RemoveMission(Mission) + -- Remove Mission from WC queue. + if Mission.wingcommander then + Mission.wingcommander:RemoveMission(Mission) + end + else - -- Airwing will cancel mission. - if Mission.airwing then - Mission.airwing:MissionCancel(Mission) + -- Wingcommander will cancel mission. + if Mission.wingcommander then + Mission.wingcommander:MissionCancel(Mission) end end @@ -694,6 +734,24 @@ function CHIEF:CheckGroupInZones(group, zoneset) return false end +--- Check if group is inside a zone. +-- @param #CHIEF self +-- @param Ops.Target#TARGET target The target. +-- @param Core.Set#SET_ZONE zoneset Set of zones. +-- @return #boolean If true, group is in any zone. +function CHIEF:CheckTargetInZones(target, zoneset) + + for _,_zone in pairs(zoneset.Set or {}) do + local zone=_zone --Core.Zone#ZONE + + if zone:IsCoordinateInZone(target:GetCoordinate()) then + return true + end + end + + return false +end + --- Check resources. -- @param #CHIEF self -- @return #table diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index aee683f96..36ed8e635 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -95,9 +95,9 @@ INTEL = { ContactsUnknown = {}, Clusters = {}, clustercounter = 1, - clusterradius = 15, - clusteranalysis = true, - clustermarkers = false, + clusterradius = 15, + clusteranalysis = true, + clustermarkers = false, prediction = 300, } diff --git a/Moose Development/Moose/Ops/WingCommander.lua b/Moose Development/Moose/Ops/WingCommander.lua index 78e6179fd..5074de99d 100644 --- a/Moose Development/Moose/Ops/WingCommander.lua +++ b/Moose Development/Moose/Ops/WingCommander.lua @@ -77,7 +77,7 @@ function WINGCOMMANDER:New() self:AddTransition("*", "Stop", "Stopped") -- Stop WC. self:AddTransition("*", "AssignMission", "*") -- Mission was assigned to an AIRWING. - self:AddTransition("*", "CancelMission", "*") -- Cancel mission. + self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. ------------------------ --- Pseudo Functions --- @@ -162,6 +162,7 @@ function WINGCOMMANDER:RemoveMission(Mission) if mission.auftragsnummer==Mission.auftragsnummer then self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status)) + mission.wingcommander=nil table.remove(self.missionqueue, i) break end @@ -272,13 +273,13 @@ function WINGCOMMANDER:onafterAssignMission(From, Event, To, Airwing, Mission) end ---- On after "CancelMission" event. +--- On after "MissionCancel" event. -- @param #WINGCOMMANDER self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.Auftrag#AUFTRAG Mission The mission. -function WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission) +function WINGCOMMANDER:onafterMissionCancel(From, Event, To, Mission) self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status)) @@ -291,7 +292,7 @@ function WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission) -- Airwing will cancel mission. if Mission.airwing then - Mission.airwing:CancelMission(Mission) + Mission.airwing:MissionCancel(Mission) end end From 629c5e773937186b4ff32240bee3793efa5c103c Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 6 Aug 2021 23:16:48 +0200 Subject: [PATCH 067/141] AUFTRAG - Recon Mission --- Moose Development/Moose/Core/Set.lua | 12 +++ Moose Development/Moose/Ops/Auftrag.lua | 47 +++++++++- Moose Development/Moose/Ops/FlightGroup.lua | 4 +- Moose Development/Moose/Ops/OpsGroup.lua | 99 ++++++++++++++++++++- Moose Development/Moose/Ops/Target.lua | 17 ++++ 5 files changed, 172 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index e6ab285ba..17feaa492 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -265,6 +265,18 @@ do -- SET_BASE end + --- Sort the set by name. + -- @param #SET_BASE self + -- @return Core.Base#BASE The added BASE Object. + function SET_BASE:SortByName() + + local function sort(a, b) + return a0! Task description = %s", self.taskcurrent, tostring(taskname))) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index be7b577b1..2ba6a7322 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -556,7 +556,6 @@ function OPSGROUP:New(group) self:AddTransition("*", "Damaged", "*") -- Someone in the group took damage. self:AddTransition("*", "UpdateRoute", "*") -- Update route of group. Only if airborne. - self:AddTransition("*", "PassingWaypoint", "*") -- Passing waypoint. self:AddTransition("*", "Wait", "*") -- Group will wait for further orders. @@ -3031,8 +3030,13 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Parameters. local zone=Task.dcstask.params.zone --Core.Zone#ZONE + + -- Random coordinate in zone. local Coordinate=zone:GetRandomCoordinate() - Coordinate:MarkToAll("Random Patrol Zone Coordinate") + + --Coordinate:MarkToAll("Random Patrol Zone Coordinate") + + -- Speed and altitude. local Speed=UTILS.KmphToKnots(Task.dcstask.params.speed or self.speedCruise) local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude) or nil @@ -3045,6 +3049,37 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) end + elseif Task.dcstask.id=="ReconMission" then + + --- + -- Task recon. + --- + + -- Target + local target=Task.dcstask.params.target --Ops.Target#TARGET + Task.dcstask.params.lastindex=1 + + local object=target.targets[1] --Ops.Target#TARGET.Object + local zone=object.Object --Core.Zone#ZONE + + -- Random coordinate in zone. + local Coordinate=zone:GetRandomCoordinate() + + -- Speed and altitude. + local Speed=UTILS.KmphToKnots(Task.dcstask.params.speed or self.speedCruise) + local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude) or nil + + --Coordinate:MarkToAll("Next waypoint", ReadOnly,Text) + + -- New waypoint. + if self.isFlightgroup then + FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + elseif self.isNavygroup then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + elseif self.isArmygroup then + NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + end + else -- If task is scheduled (not waypoint) set task. @@ -3135,6 +3170,8 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) done=true elseif Task.dcstask.id=="PatrolZone" then done=true + elseif Task.dcstask.id=="ReconMission" then + done=true elseif stopflag==1 or (not self:IsAlive()) or self:IsDead() or self:IsStopped() then -- Manual call TaskDone if setting flag to one was not successful. done=true @@ -4000,8 +4037,13 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Remove old waypoint. self:RemoveWaypointByID(Waypoint.uid) + -- Zone object. local zone=task.dcstask.params.zone --Core.Zone#ZONE + + -- Random coordinate in zone. local Coordinate=zone:GetRandomCoordinate() + + -- Speed and altitude. local Speed=UTILS.KmphToKnots(task.dcstask.params.speed or self.speedCruise) local Altitude=task.dcstask.params.altitude and UTILS.MetersToFeet(task.dcstask.params.altitude) or nil @@ -4012,6 +4054,59 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) elseif self.isArmygroup then NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) end + + elseif task and task.dcstask.id=="ReconMission" then + + local target=task.dcstask.params.target --Ops.Target#TARGET + + local n=task.dcstask.params.lastindex+1 + + if n<=#target.targets then + + -- Zone object. + local object=target.targets[n] --Ops.Target#TARGET.Object + local zone=object.Object --Core.Zone#ZONE + + -- Random coordinate in zone. + local Coordinate=zone:GetRandomCoordinate() + + -- Speed and altitude. + local Speed=UTILS.KmphToKnots(task.dcstask.params.speed or self.speedCruise) + local Altitude=task.dcstask.params.altitude and UTILS.MetersToFeet(task.dcstask.params.altitude) or nil + + -- Debug. + --Coordinate:MarkToAll("Recon Waypoint n="..tostring(n)) + + if self.isFlightgroup then + FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + elseif self.isNavygroup then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + elseif self.isArmygroup then + NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + end + + -- Increase counter. + task.dcstask.params.lastindex=task.dcstask.params.lastindex+1 + + else + + -- Get waypoint index. + local wpindex=self:GetWaypointIndex(Waypoint.uid) + + -- Final waypoint reached? + if wpindex==nil or wpindex==#self.waypoints then + + -- Set switch to true. + if not self.adinfinitum or #self.waypoints<=1 then + self.passedfinalwp=true + end + + end + + -- Final zone reached ==> task done. + self:TaskDone(task) + + end else diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 756204b09..9d08774a6 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -260,6 +260,17 @@ function TARGET:AddObject(Object) self:AddObject(object) end + elseif Object:IsInstanceOf("SET_ZONE") then + + local set=Object --Core.Set#SET_ZONE + + set:SortByName() + + for index,ZoneName in pairs(set.Index) do + local zone=set.Set[ZoneName] --Core.Zone#ZONE + self:_AddObject(zone) + end + else --- @@ -971,6 +982,12 @@ function TARGET:GetTargetName(Target) local coord=Target.Object --Core.Point#COORDINATE return coord:ToStringMGRS() + + elseif Target.Type==TARGET.ObjectType.ZONE then + + local Zone=Target.Object --Core.Zone#ZONE + + return Zone:GetName() end From 86bb256bf133208031d5e283f0cf86ebcb0ae02e Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 10 Aug 2021 00:40:17 +0200 Subject: [PATCH 068/141] OPS - Added Cohort and Legion classes - Added Platoon and Brigade classes --- .../Moose/Functional/Warehouse.lua | 8 +- Moose Development/Moose/Modules.lua | 4 + Moose Development/Moose/Ops/AirWing.lua | 90 +- Moose Development/Moose/Ops/Auftrag.lua | 8 +- Moose Development/Moose/Ops/Brigade.lua | 273 ++++ Moose Development/Moose/Ops/ChiefOfStaff.lua | 70 +- Moose Development/Moose/Ops/Cohort.lua | 992 ++++++++++++ Moose Development/Moose/Ops/FlightGroup.lua | 14 +- Moose Development/Moose/Ops/Legion.lua | 1439 +++++++++++++++++ Moose Development/Moose/Ops/OpsGroup.lua | 9 + Moose Development/Moose/Ops/Platoon.lua | 155 ++ Moose Development/Moose/Ops/Squadron.lua | 41 +- Moose Development/Moose/Ops/WingCommander.lua | 20 +- 13 files changed, 3041 insertions(+), 82 deletions(-) create mode 100644 Moose Development/Moose/Ops/Brigade.lua create mode 100644 Moose Development/Moose/Ops/Cohort.lua create mode 100644 Moose Development/Moose/Ops/Legion.lua create mode 100644 Moose Development/Moose/Ops/Platoon.lua diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index ae408b71e..b09a22fc1 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1623,6 +1623,10 @@ WAREHOUSE = { -- @field #number rid The request ID of this asset. -- @field #boolean arrived If true, asset arrived at its destination. -- @field #number damage Damage of asset group in percent. +-- @field Ops.AirWing#AIRWING.Payload payload The payload of the asset. +-- @field Ops.FlightGroup#FLIGHTGROUP flightgroup The flightgroup object. +-- @field #string squadname Name of the squadron this asset belongs to. +-- @field #number Treturned Time stamp when asset returned to the airwing. --- Item of the warehouse queue table. -- @type WAREHOUSE.Queueitem @@ -1842,8 +1846,8 @@ WAREHOUSE.version="1.0.2" --- The WAREHOUSE constructor. Creates a new WAREHOUSE object from a static object. Parameters like the coalition and country are taken from the static object structure. -- @param #WAREHOUSE self --- @param Wrapper.Static#STATIC warehouse The physical structure representing the warehouse. --- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static +-- @param Wrapper.Static#STATIC warehouse The physical structure representing the warehouse. Can also be a @{Wrapper.Unit#UNIT}. +-- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static/unit representing the warehouse. -- @return #WAREHOUSE self function WAREHOUSE:New(warehouse, alias) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 4081249a0..b44f10f6b 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -81,8 +81,12 @@ __Moose.Include( 'Scripts/Moose/Ops/OpsGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/FlightGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/NavyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Cohort.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Platoon.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Legion.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Brigade.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) __Moose.Include( 'Scripts/Moose/Ops/WingCommander.lua' ) __Moose.Include( 'Scripts/Moose/Ops/ChiefOfStaff.lua' ) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 65e01063b..22737c5dc 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -125,10 +125,6 @@ AIRWING = { --- Squadron asset. -- @type AIRWING.SquadronAsset --- @field #AIRWING.Payload payload The payload of the asset. --- @field Ops.FlightGroup#FLIGHTGROUP flightgroup The flightgroup object. --- @field #string squadname Name of the squadron this asset belongs to. --- @field #number Treturned Time stamp when asset returned to the airwing. -- @extends Functional.Warehouse#WAREHOUSE.Assetitem --- Payload data. @@ -251,7 +247,7 @@ function AIRWING:New(warehousename, airwingname) -- @param #string Event Event. -- @param #string To To state. -- @param Ops.Squadron#SQUADRON Squadron The asset squadron. - -- @param #AIRWING.SquadronAsset Asset The asset that returned. + -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. return self end @@ -513,7 +509,7 @@ end --- Return payload from asset back to stock. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset asset The squadron asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The squadron asset. function AIRWING:ReturnPayloadFromAsset(asset) local payload=asset.payload @@ -596,7 +592,7 @@ end --- Get squadron of an asset. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset Asset The squadron asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The squadron asset. -- @return Ops.Squadron#SQUADRON The squadron object. function AIRWING:GetSquadronOfAsset(Asset) return self:GetSquadron(Asset.squadname) @@ -604,7 +600,7 @@ end --- Remove asset from squadron. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset Asset The squad asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The squad asset. function AIRWING:RemoveAssetFromSquadron(Asset) local squad=self:GetSquadronOfAsset(Asset) if squad then @@ -908,7 +904,7 @@ function AIRWING:onafterStatus(From, Event, To) local skill=squadron.skill and tostring(squadron.skill) or "N/A" -- Squadron text - text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, squadron:CountAssetsInStock(), #squadron.assets, callsign, modex, skill) + text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, squadron:CountAssets(true), #squadron.assets, callsign, modex, skill) end self:I(self.lid..text) end @@ -1105,7 +1101,7 @@ end --- Check how many AWACS missions are assigned and add number of missing missions. -- @param #AIRWING self -- @param Ops.FlightGroup#FLIGHTGROUP flightgroup The flightgroup. --- @return #AIRWING.SquadronAsset The tanker asset. +-- @return Functional.Warehouse#WAREHOUSE.Assetitem The tanker asset. function AIRWING:GetTankerForFlight(flightgroup) local tankers=self:GetAssetsOnMission(AUFTRAG.Type.TANKER) @@ -1114,7 +1110,7 @@ function AIRWING:GetTankerForFlight(flightgroup) local tankeropt={} for _,_tanker in pairs(tankers) do - local tanker=_tanker --#AIRWING.SquadronAsset + local tanker=_tanker --Functional.Warehouse#WAREHOUSE.Assetitem -- Check that donor and acceptor use the same refuelling system. if flightgroup.refueltype and flightgroup.refueltype==tanker.flightgroup.tankertype then @@ -1214,7 +1210,7 @@ function AIRWING:_GetNextMission() local remove={} local gotpayload={} for i=1,#assets do - local asset=assets[i] --#AIRWING.SquadronAsset + local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem -- Get payload for the asset. if not asset.payload then @@ -1231,7 +1227,7 @@ function AIRWING:_GetNextMission() -- Now remove assets for which we don't have a payload. for i=#assets,1,-1 do - local asset=assets[i] --#AIRWING.SquadronAsset + local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem for _,uid in pairs(remove) do if uid==asset.uid then table.remove(assets, i) @@ -1255,7 +1251,7 @@ function AIRWING:_GetNextMission() -- Assign assets to mission. for i=1,mission.nassets do - local asset=assets[i] --#AIRWING.SquadronAsset + local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem -- Should not happen as we just checked! if not asset.payload then @@ -1268,7 +1264,7 @@ function AIRWING:_GetNextMission() -- Now return the remaining payloads. for i=mission.nassets+1,#assets do - local asset=assets[i] --#AIRWING.SquadronAsset + local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem for _,uid in pairs(gotpayload) do if uid==asset.uid then self:ReturnPayloadFromAsset(asset) @@ -1288,7 +1284,7 @@ end --- Calculate the mission score of an asset. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset asset Asset +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset -- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. -- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. -- @return #number Mission score. @@ -1351,7 +1347,7 @@ function AIRWING:_OptimizeAssetSelection(assets, Mission, includePayload) local distmin=math.huge local distmax=0 for _,_asset in pairs(assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if asset.spawned then local group=GROUP:FindByName(asset.spawngroupname) @@ -1373,15 +1369,15 @@ function AIRWING:_OptimizeAssetSelection(assets, Mission, includePayload) -- Calculate the mission score of all assets. for _,_asset in pairs(assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem --self:I(string.format("FF asset %s has payload %s", asset.spawngroupname, asset.payload and "yes" or "no!")) asset.score=self:CalculateAssetMissionScore(asset, Mission, includePayload) end --- Sort assets wrt to their mission score. Higher is better. local function optimize(a, b) - local assetA=a --#AIRWING.SquadronAsset - local assetB=b --#AIRWING.SquadronAsset + local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem + local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem -- Higher score wins. If equal score ==> closer wins. -- TODO: Need to include the distance in a smarter way! @@ -1392,7 +1388,7 @@ function AIRWING:_OptimizeAssetSelection(assets, Mission, includePayload) -- Remove distance parameter. local text=string.format("Optimized assets for %s mission (payload=%s):", Mission.type, tostring(includePayload)) for i,Asset in pairs(assets) do - local asset=Asset --#AIRWING.SquadronAsset + local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem text=text..string.format("\n%s %s: score=%d, distance=%.1f km", asset.squadname, asset.spawngroupname, asset.score, asset.dist/1000) asset.dist=nil asset.score=nil @@ -1425,7 +1421,7 @@ function AIRWING:onafterMissionRequest(From, Event, To, Mission) local Assetlist={} for _,_asset in pairs(Mission.assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if asset.spawned then @@ -1452,7 +1448,7 @@ function AIRWING:onafterMissionRequest(From, Event, To, Mission) --local text=string.format("Requesting assets for mission %s:", Mission.name) for i,_asset in pairs(Assetlist) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem -- Set asset to requested! Important so that new requests do not use this asset! asset.requested=true @@ -1493,7 +1489,7 @@ function AIRWING:onafterMissionCancel(From, Event, To, Mission) else for _,_asset in pairs(Mission.assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem local flightgroup=asset.flightgroup @@ -1519,7 +1515,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #AIRWING.SquadronAsset asset The asset that has just been added. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that has just been added. -- @param #string assignment The (optional) assignment for the asset. function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) @@ -1604,7 +1600,7 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Ops.Squadron#SQUADRON Squadron The asset squadron. --- @param #AIRWING.SquadronAsset Asset The asset that returned. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. function AIRWING:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) -- Debug message. self:T(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Squadron.name, tostring(Asset.assignment))) @@ -1634,7 +1630,7 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Group#GROUP group The group spawned. --- @param #AIRWING.SquadronAsset asset The asset that was spawned. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that was spawned. -- @param Functional.Warehouse#WAREHOUSE.Pendingitem request The request of the dead asset. function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) @@ -1731,7 +1727,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #AIRWING.SquadronAsset asset The asset that is dead. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that is dead. -- @param Functional.Warehouse#WAREHOUSE.Pendingitem request The request of the dead asset. function AIRWING:onafterAssetDead(From, Event, To, asset, request) @@ -1793,7 +1789,7 @@ function AIRWING:onafterRequest(From, Event, To, Request) if Mission and assets then for _,_asset in pairs(assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem -- This would be the place to modify the asset table before the asset is spawned. end @@ -1820,7 +1816,7 @@ function AIRWING:onafterSelfRequest(From, Event, To, groupset, request) local mission=self:GetMissionByID(request.assignment) for _,_asset in pairs(request.assets) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem end for _,_group in pairs(groupset:GetSet()) do @@ -1835,7 +1831,7 @@ end --- Create a new flight group after an asset was spawned. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset asset The asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. -- @return Ops.FlightGroup#FLIGHTGROUP The created flightgroup object. function AIRWING:_CreateFlightGroup(asset) @@ -1857,7 +1853,7 @@ end --- Check if an asset is currently on a mission (STARTED or EXECUTING). -- @param #AIRWING self --- @param #AIRWING.SquadronAsset asset The asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. -- @param #table MissionTypes Types on mission to be checked. Default all. -- @return #boolean If true, asset has at least one mission of that type in the queue. function AIRWING:IsAssetOnMission(asset, MissionTypes) @@ -1900,7 +1896,7 @@ function AIRWING:IsAssetOnMission(asset, MissionTypes) if mission:IsNotOver() then for _,_asset in pairs(mission.assets) do - local sqasset=_asset --#AIRWING.SquadronAsset + local sqasset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if sqasset.uid==asset.uid then return true @@ -1917,7 +1913,7 @@ end --- Get the current mission of the asset. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset asset The asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. -- @return Ops.Auftrag#AUFTRAG Current mission or *nil*. function AIRWING:GetAssetCurrentMission(asset) @@ -2026,33 +2022,19 @@ function AIRWING:CountMissionsInQueue(MissionTypes) return N end ---- Count total number of assets. This is the sum of all squadron assets. --- @param #AIRWING self --- @return #number Amount of asset groups. -function AIRWING:CountAssets() - - local N=0 - - for _,_squad in pairs(self.squadrons) do - local squad=_squad --Ops.Squadron#SQUADRON - N=N+#squad.assets - end - - return N -end - - --- Count total number of assets that are in the warehouse stock (not spawned). -- @param #AIRWING self +-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. -- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. -- @return #number Amount of asset groups in stock. -function AIRWING:CountAssetsInStock(MissionTypes) +function AIRWING:CountAssets(InStock, MissionTypes, Attributes) local N=0 for _,_squad in pairs(self.squadrons) do local squad=_squad --Ops.Squadron#SQUADRON - N=N+squad:CountAssetsInStock(MissionTypes) + N=N+squad:CountAssets(InStock, MissionTypes, Attributes) end return N @@ -2077,7 +2059,7 @@ function AIRWING:CountAssetsOnMission(MissionTypes, Squadron) if self:CheckMissionType(mission.type, MissionTypes or AUFTRAG.Type) then for _,_asset in pairs(mission.assets or {}) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if Squadron==nil or Squadron.name==asset.squadname then @@ -2115,7 +2097,7 @@ function AIRWING:GetAssetsOnMission(MissionTypes) if self:CheckMissionType(mission.type, MissionTypes) then for _,_asset in pairs(mission.assets or {}) do - local asset=_asset --#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem table.insert(assets, asset) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 700bf2986..abda972be 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1224,8 +1224,10 @@ end -- @param Core.Set#SET_ZONE ZoneSet The recon zones. -- @param #number Speed Speed in knots. -- @param #number Altitude Altitude in feet. Only for airborne units. Default 2000 feet ASL. +-- @param #boolean Adinfinitum If true, the group will start over again after reaching the final zone. +-- @param #boolean Randomly If true, the group will select a random zone. -- @return #AUFTRAG self -function AUFTRAG:NewRECON(ZoneSet, Speed, Altitude) +function AUFTRAG:NewRECON(ZoneSet, Speed, Altitude, Adinfinitum, Randomly) local mission=AUFTRAG:New(AUFTRAG.Type.RECON) @@ -1238,8 +1240,10 @@ function AUFTRAG:NewRECON(ZoneSet, Speed, Altitude) mission.missionFraction=0.5 mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude) or UTILS.FeetToMeters(2000) - + mission.DCStask=mission:GetDCSMissionTask() + mission.DCStask.params.adinfitum=Adinfinitum + mission.DCStask.params.randomly=Randomly return mission end diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua new file mode 100644 index 000000000..65e65c6de --- /dev/null +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -0,0 +1,273 @@ +--- **Ops** - Brigade Warehouse. +-- +-- **Main Features:** +-- +-- * Manage platoons +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- @module Ops.Brigade +-- @image OPS_Brigade.png + + +--- BRIGADE class. +-- @type BRIGADE +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity of output. +-- @field Ops.General#GENERAL general The genral responsible for this brigade. +-- @extends Ops.Legion#LEGION + +--- Be surprised! +-- +-- === +-- +-- # The BRIGADE Concept +-- +-- An BRIGADE consists of multiple PLATOONS. These platoons "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed. +-- +-- +-- @field #BRIGADE +BRIGADE = { + ClassName = "BRIGADE", + verbose = 3, + genral = nil, +} + + +--- BRIGADE class version. +-- @field #string version +BRIGADE.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- ToDo list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot! + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new BRIGADE class object. +-- @param #BRIGADE self +-- @param #string WarehouseName Name of the warehouse STATIC or UNIT object representing the warehouse. +-- @param #string BrigadeName Name of the brigade. +-- @return #BRIGADE self +function BRIGADE:New(WarehouseName, BrigadeName) + + -- Inherit everything from LEGION class. + local self=BASE:Inherit(self, LEGION:New(WarehouseName, BrigadeName)) -- #BRIGADE + + -- Nil check. + if not self then + BASE:E(string.format("ERROR: Could not find warehouse %s!", WarehouseName)) + return nil + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("BRIGADE %s | ", self.alias) + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "PlatoonOnMission", "*") -- Add a (mission) request to the warehouse. + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Add a platoon to the brigade. +-- @param #BRIGADE self +-- @param Ops.Platoon#PLATOON Platoon The platoon object. +-- @return #BRIGADE self +function BRIGADE:AddPlatoon(Platoon) + + -- Add squadron to airwing. + table.insert(self.cohorts, Platoon) + + -- Add assets to squadron. + self:AddAssetToPlatoon(Platoon, Platoon.Ngroups) + + -- Set airwing to squadron. + Platoon:SetBrigade(self) + + -- Start squadron. + if Platoon:IsStopped() then + Platoon:Start() + end + + return self +end + + + +--- Add asset group(s) to platoon. +-- @param #BRIGADE self +-- @param Ops.Platoon#PLATOON Platoon The platoon object. +-- @param #number Nassets Number of asset groups to add. +-- @return #BRIGADE self +function BRIGADE:AddAssetToPlatoon(Platoon, Nassets) + + if Platoon then + + -- Get the template group of the squadron. + local Group=GROUP:FindByName(Platoon.templatename) + + if Group then + + -- Debug text. + local text=string.format("Adding asset %s to platoon %s", Group:GetName(), Platoon.name) + self:T(self.lid..text) + + -- Add assets to airwing warehouse. + self:AddAsset(Group, Nassets, nil, nil, nil, nil, Platoon.skill, Platoon.livery, Platoon.name) + + else + self:E(self.lid.."ERROR: Group does not exist!") + end + + else + self:E(self.lid.."ERROR: Squadron does not exit!") + end + + return self +end + +--- Get platoon by name. +-- @param #BRIGADE self +-- @param #string PlatoonName Name of the platoon. +-- @return Ops.Platoon#PLATOON The Platoon object. +function BRIGADE:GetPlatoon(PlatoonName) + + local platoon=self:_GetCohort(PlatoonName) + + return platoon +end + +--- Get platoon of an asset. +-- @param #BRIGADE self +-- @param Ops.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset. +-- @return Ops.Platoon#PLATOON The platoon object. +function BRIGADE:GetSquadronOfAsset(Asset) + local platoon=self:GetPlatoon(Asset.squadname) + return platoon +end + +--- Remove asset from squadron. +-- @param #BRIGADE self +-- @param #BRIGADE.SquadronAsset Asset The squad asset. +function BRIGADE:RemoveAssetFromSquadron(Asset) + local squad=self:GetSquadronOfAsset(Asset) + if squad then + squad:DelAsset(Asset) + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Start BRIGADE FSM. +-- @param #BRIGADE self +function BRIGADE:onafterStart(From, Event, To) + + -- Start parent Warehouse. + self:GetParent(self, BRIGADE).onafterStart(self, From, Event, To) + + -- Info. + self:I(self.lid..string.format("Starting BRIGADE v%s", BRIGADE.version)) + +end + +--- Update status. +-- @param #BRIGADE self +function BRIGADE:onafterStatus(From, Event, To) + + -- Status of parent Warehouse. + self:GetParent(self).onafterStatus(self, From, Event, To) + + local fsmstate=self:GetState() + + env.info("FF Brigade status "..fsmstate) + + -- General info: + if self.verbose>=1 then + + -- Count missions not over yet. + local Nmissions=self:CountMissionsInQueue() + + -- Assets tot + local Npq, Np, Nq=self:CountAssetsOnMission() + + local assets=string.format("%d (OnMission: Total=%d, Active=%d, Queued=%d)", self:CountAssets(), Npq, Np, Nq) + + -- Output. + local text=string.format("%s: Missions=%d, Squads=%d, Assets=%s", fsmstate, Nmissions, #self.cohorts, assets) + self:I(self.lid..text) + end + + ------------------ + -- Mission Info -- + ------------------ + if self.verbose>=2 then + local text=string.format("Missions Total=%d:", #self.missionqueue) + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end + local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.nassets) + local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage()) + + text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target) + end + self:I(self.lid..text) + end + + ------------------- + -- Squadron Info -- + ------------------- + if self.verbose>=3 then + local text="Platoons:" + for i,_squadron in pairs(self.cohorts) do + local squadron=_squadron --Ops.Squadron#SQUADRON + + local callsign=squadron.callsignName and UTILS.GetCallsignName(squadron.callsignName) or "N/A" + local modex=squadron.modex and squadron.modex or -1 + local skill=squadron.skill and tostring(squadron.skill) or "N/A" + + -- Squadron text + text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, squadron:CountAssets(true), #squadron.assets, callsign, modex, skill) + end + self:I(self.lid..text) + end + + -------------- + -- Mission --- + -------------- + + -- Check if any missions should be cancelled. + self:_CheckMissions() + + -- Get next mission. + local mission=self:_GetNextMission() + + -- Request mission execution. + if mission then + self:MissionRequest(mission) + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua index fcb26abc6..963886449 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -66,6 +66,19 @@ CHIEF.DEFCON = { RED="Red", } +--- Strategy. +-- @type CHIEF.Strategy +-- @field #string DEFENSIVE Only target in our own terretory are engaged. +-- @field #string OFFENSIVE Targets in own terretory and yellow zones are engaged. +-- @field #string AGGRESSIVE Targets in own terretroy, yellow zones and engage zones are engaged. +-- @field #string TOTALWAR Anything is engaged anywhere. +CHIEF.Strategy = { + DEFENSIVE="Defensive", + OFFENSIVE="Offensive", + AGGRESSIVE="Aggressive", + TOTALWAR="Total War" +} + --- CHIEF class version. -- @field #string version CHIEF.version="0.0.1" @@ -74,6 +87,10 @@ CHIEF.version="0.0.1" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Get list of own assets and capabilities. +-- TODO: Get list/overview of enemy assets etc. +-- TODO: Put all contacts into target list. Then make missions from them. +-- TODO: Set of interesting zones. -- TODO: Define A2A and A2G parameters. -- DONE: Add/remove spawned flightgroups to detection set. -- DONE: Borderzones. @@ -113,6 +130,8 @@ function CHIEF:New(AgentSet, Coalition) self:AddTransition("*", "Defcon", "*") -- Change defence condition. + self:AddTransition("*", "Stategy", "*") -- Change strategy condition. + self:AddTransition("*", "DeclareWar", "*") -- Declare War. ------------------------ @@ -393,6 +412,7 @@ function CHIEF:onafterStatus(From, Event, To) if contact.mission and contact.mission:IsNotOver() then + -- Debug info. local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.", contact.groupname, contact.mission.type:upper(), contact.mission.name) MESSAGE:New(text, 120, "CHIEF"):ToAll() self:I(self.lid..text) @@ -473,24 +493,58 @@ function CHIEF:onafterStatus(From, Event, To) -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() - local text=string.format("Defcon=%s Missions=%d, Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred) + --- + -- Mission Queue + --- + + local Nassets=self.wingcommander:CountAssets() + + local text=string.format("Defcon=%s Assets=%d, Missions=%d, Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, Nassets, #self.missionqueue, #self.Contacts, Nyellow, Nred) + self:I(self.lid..text) + + --- + -- Assets + --- + + local text="Assets:" + for _,missiontype in pairs(AUFTRAG.Type) do + local N=self.wingcommander:CountAssets(nil, missiontype) + if N>0 then + text=text..string.format("\n- %s %d", missiontype, N) + end + end + self:I(self.lid..text) + local text="Assets:" + for _,attribute in pairs(WAREHOUSE.Attribute) do + local N=self.wingcommander:CountAssets(nil, nil, attribute) + if N>0 or self.verbose>=10 then + text=text..string.format("\n- %s %d", attribute, N) + end + end self:I(self.lid..text) --- -- Target Queue --- - for _,_target in pairs(self.targetqueue) do - local target=_target --Ops.Target#TARGET - - if target:IsAlive() then - - if self:CheckTargetInZones(target, self.borderzoneset) then + if #self.targetqueue>0 then + local text="Targets:" + for i,_target in pairs(self.targetqueue) do + local target=_target --Ops.Target#TARGET + + text=text..string.format("\n[%d] %s: Category=%s, alive=%s [%.1f/%.1f]", i, target:GetName(), target.category, tostring(target:IsAlive()), target:GetLife(), target:GetLife0()) + + + if target:IsAlive() then + + if self:CheckTargetInZones(target, self.borderzoneset) then + + end end end - + self:I(self.lid..text) end --- diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua new file mode 100644 index 000000000..2d28c1a1b --- /dev/null +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -0,0 +1,992 @@ +--- **Ops** - Cohort encompassed all characteristics of SQUADRONs, PLATOONs and FLOTILLAs. +-- +-- **Main Features:** +-- +-- * Set parameters like livery, skill valid for all platoon members. +-- * Define modex and callsigns. +-- * Define mission types, this platoon can perform (see Ops.Auftrag#AUFTRAG). +-- * Pause/unpause platoon operations. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.Cohort +-- @image OPS_Cohort.png + + +--- COHORT class. +-- @type COHORT +-- @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 #string name Name of the platoon. +-- @field #string templatename Name of the template group. +-- @field #string aircrafttype Type of the airframe the platoon is using. +-- @field Wrapper.Group#GROUP templategroup Template group. +-- @field #table assets Cohort assets. +-- @field #table missiontypes Capabilities (mission types and performances) of the platoon. +-- @field #number maintenancetime Time in seconds needed for maintenance of a returned flight. +-- @field #number repairtime Time in seconds for each +-- @field #string livery Livery of the platoon. +-- @field #number skill Skill of platoon members. +-- @field Ops.Legion#LEGION legion The LEGION object the cohort belongs to. +-- @field #number Ngroups Number of asset flight groups this platoon has. +-- @field #number engageRange Mission range in meters. +-- @field #string attribute Generalized attribute of the platoon template group. +-- @field #table tacanChannel List of TACAN channels available to the platoon. +-- @field #number radioFreq Radio frequency in MHz the cohort uses. +-- @field #number radioModu Radio modulation the cohort uses. +-- @field #table tacanChannel List of TACAN channels available to the cohort. +-- @extends Core.Fsm#FSM + +--- *It is unbelievable what a platoon of twelve aircraft did to tip the balance.* -- Adolf Galland +-- +-- === +-- +-- ![Banner Image](..\Presentations\OPS\Cohort\_Main.png) +-- +-- # The COHORT Concept +-- +-- A COHORT is essential part of an BRIGADE and consists of **one** type of aircraft. +-- +-- +-- +-- @field #COHORT +COHORT = { + ClassName = "COHORT", + verbose = 0, + lid = nil, + name = nil, + templatename = nil, + assets = {}, + missiontypes = {}, + repairtime = 0, + maintenancetime= 0, + livery = nil, + skill = nil, + legion = nil, + Ngroups = nil, + engageRange = nil, + tacanChannel = {}, +} + +--- COHORT class version. +-- @field #string version +COHORT.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot! +-- TODO: Make general so that PLATOON and SQUADRON can inherit this class. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new COHORT object and start the FSM. +-- @param #COHORT self +-- @param #string TemplateGroupName Name of the template group. +-- @param #number Ngroups Number of asset groups of this platoon. Default 3. +-- @param #string CohortName Name of the platoon, e.g. "VFA-37". +-- @return #COHORT self +function COHORT:New(TemplateGroupName, Ngroups, CohortName) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #COHORT + + -- Name of the template group. + self.templatename=TemplateGroupName + + -- Cohort name. + self.name=tostring(CohortName or TemplateGroupName) + + -- Set some string id for output to DCS.log file. + self.lid=string.format("COHORT %s | ", self.name) + + -- Template group. + self.templategroup=GROUP:FindByName(self.templatename) + + -- Check if template group exists. + if not self.templategroup then + 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) + + -- Everyone can ORBIT. + --self:AddMissionCapability(AUFTRAG.Type.ORBIT) + + -- Generalized attribute. + self.attribute=self.templategroup:GetAttribute() + + -- Aircraft type. + self.aircrafttype=self.templategroup:GetTypeName() + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "OnDuty") -- Start FSM. + self:AddTransition("*", "Status", "*") -- Status update. + + self:AddTransition("OnDuty", "Pause", "Paused") -- Pause platoon. + self:AddTransition("Paused", "Unpause", "OnDuty") -- Unpause platoon. + + self:AddTransition("*", "Stop", "Stopped") -- Stop platoon. + + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the COHORT. Initializes parameters and starts event handlers. + -- @function [parent=#COHORT] Start + -- @param #COHORT self + + --- Triggers the FSM event "Start" after a delay. Starts the COHORT. Initializes parameters and starts event handlers. + -- @function [parent=#COHORT] __Start + -- @param #COHORT self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the COHORT and all its event handlers. + -- @param #COHORT self + + --- Triggers the FSM event "Stop" after a delay. Stops the COHORT and all its event handlers. + -- @function [parent=#COHORT] __Stop + -- @param #COHORT self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#COHORT] Status + -- @param #COHORT self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#COHORT] __Status + -- @param #COHORT self + -- @param #number delay Delay in seconds. + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set livery painted on all platoon aircraft. +-- Note that the livery name in general is different from the name shown in the mission editor. +-- +-- Valid names are the names of the **livery directories**. Check out the folder in your DCS installation for: +-- +-- * Full modules: `DCS World OpenBeta\CoreMods\aircraft\\Liveries\\` +-- * AI units: `DCS World OpenBeta\Bazar\Liveries\\` +-- +-- The folder name `` is the string you want. +-- +-- Or personal liveries you have installed somewhere in your saved games folder. +-- +-- @param #COHORT self +-- @param #string LiveryName Name of the livery. +-- @return #COHORT self +function COHORT:SetLivery(LiveryName) + self.livery=LiveryName + return self +end + +--- Set skill level of all platoon team members. +-- @param #COHORT self +-- @param #string Skill Skill of all flights. +-- @usage myplatoon:SetSkill(AI.Skill.EXCELLENT) +-- @return #COHORT self +function COHORT:SetSkill(Skill) + self.skill=Skill + return self +end + +--- Set verbosity level. +-- @param #COHORT self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #COHORT self +function COHORT:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + +--- Set turnover and repair time. If an asset returns from a mission, it will need some time until the asset is available for further missions. +-- @param #COHORT self +-- @param #number MaintenanceTime Time in minutes it takes until a flight is combat ready again. Default is 0 min. +-- @param #number RepairTime Time in minutes it takes to repair a flight for each life point taken. Default is 0 min. +-- @return #COHORT self +function COHORT:SetTurnoverTime(MaintenanceTime, RepairTime) + self.maintenancetime=MaintenanceTime and MaintenanceTime*60 or 0 + self.repairtime=RepairTime and RepairTime*60 or 0 + return self +end + +--- Set radio frequency and modulation the cohort uses. +-- @param #COHORT self +-- @param #number Frequency Radio frequency in MHz. Default 251 MHz. +-- @param #number Modulation Radio modulation. Default 0=AM. +-- @usage myplatoon:SetSkill(AI.Skill.EXCELLENT) +-- @return #COHORT self +function COHORT:SetRadio(Frequency, Modulation) + self.radioFreq=Frequency or 251 + self.radioModu=Modulation or radio.modulation.AM + return self +end + +--- Set number of units in groups. +-- @param #COHORT self +-- @param #number nunits Number of units. Must be >=1 and <=4. Default 2. +-- @return #COHORT self +function COHORT:SetGrouping(nunits) + self.ngrouping=nunits or 2 + if self.ngrouping<1 then self.ngrouping=1 end + if self.ngrouping>4 then self.ngrouping=4 end + return self +end + +--- Set mission types this platoon is able to perform. +-- @param #COHORT self +-- @param #table MissionTypes Table of mission types. Can also be passed as a #string if only one type. +-- @param #number Performance Performance describing how good this mission can be performed. Higher is better. Default 50. Max 100. +-- @return #COHORT self +function COHORT:AddMissionCapability(MissionTypes, Performance) + + -- Ensure Missiontypes is a table. + if MissionTypes and type(MissionTypes)~="table" then + MissionTypes={MissionTypes} + end + + -- Set table. + self.missiontypes=self.missiontypes or {} + + for _,missiontype in pairs(MissionTypes) do + + -- Check not to add the same twice. + if self:CheckMissionCapability(missiontype, self.missiontypes) then + self:E(self.lid.."WARNING: Mission capability already present! No need to add it twice.") + -- TODO: update performance. + else + + local capability={} --Ops.Auftrag#AUFTRAG.Capability + capability.MissionType=missiontype + capability.Performance=Performance or 50 + table.insert(self.missiontypes, capability) + + end + end + + -- Debug info. + self:T2(self.missiontypes) + + return self +end + +--- Get mission types this platoon is able to perform. +-- @param #COHORT self +-- @return #table Table of mission types. Could be empty {}. +function COHORT:GetMissionTypes() + + local missiontypes={} + + for _,Capability in pairs(self.missiontypes) do + local capability=Capability --Ops.Auftrag#AUFTRAG.Capability + table.insert(missiontypes, capability.MissionType) + end + + return missiontypes +end + +--- Get mission capabilities of this platoon. +-- @param #COHORT self +-- @return #table Table of mission capabilities. +function COHORT:GetMissionCapabilities() + return self.missiontypes +end + +--- Get mission performance for a given type of misson. +-- @param #COHORT self +-- @param #string MissionType Type of mission. +-- @return #number Performance or -1. +function COHORT:GetMissionPeformance(MissionType) + + for _,Capability in pairs(self.missiontypes) do + local capability=Capability --Ops.Auftrag#AUFTRAG.Capability + if capability.MissionType==MissionType then + return capability.Performance + end + end + + return -1 +end + +--- Set max mission range. Only missions in a circle of this radius around the platoon airbase are executed. +-- @param #COHORT self +-- @param #number Range Range in NM. Default 100 NM. +-- @return #COHORT self +function COHORT:SetMissionRange(Range) + self.engageRange=UTILS.NMToMeters(Range or 100) + return self +end + +--- Set call sign. +-- @param #COHORT self +-- @param #number Callsign Callsign from CALLSIGN.Aircraft, e.g. "Chevy" for CALLSIGN.Aircraft.CHEVY. +-- @param #number Index Callsign index, Chevy-**1**. +-- @return #COHORT self +function COHORT:SetCallsign(Callsign, Index) + self.callsignName=Callsign + self.callsignIndex=Index + return self +end + +--- Set modex. +-- @param #COHORT self +-- @param #number Modex A number like 100. +-- @param #string Prefix A prefix string, which is put before the `Modex` number. +-- @param #string Suffix A suffix string, which is put after the `Modex` number. +-- @return #COHORT self +function COHORT:SetModex(Modex, Prefix, Suffix) + self.modex=Modex + self.modexPrefix=Prefix + self.modexSuffix=Suffix + return self +end + +--- Set Legion. +-- @param #COHORT self +-- @param Ops.Legion#LEGION Legion The Legion. +-- @return #COHORT self +function COHORT:SetLegion(Legion) + self.legion=Legion + return self +end + +--- Add asset to cohort. +-- @param #COHORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The warehouse asset. +-- @return #COHORT self +function COHORT:AddAsset(Asset) + self:T(self.lid..string.format("Adding asset %s of type %s", Asset.spawngroupname, Asset.unittype)) + Asset.squadname=self.name + table.insert(self.assets, Asset) + return self +end + +--- Remove asset from chort. +-- @param #COHORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset. +-- @return #COHORT self +function COHORT:DelAsset(Asset) + for i,_asset in pairs(self.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + if Asset.uid==asset.uid then + self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname)) + table.remove(self.assets, i) + break + end + end + return self +end + +--- Remove asset group from cohort. +-- @param #COHORT self +-- @param #string GroupName Name of the asset group. +-- @return #COHORT self +function COHORT:DelGroup(GroupName) + for i,_asset in pairs(self.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + if GroupName==asset.spawngroupname then + self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname)) + table.remove(self.assets, i) + break + end + end + return self +end + +--- Get name of the platoon +-- @param #COHORT self +-- @return #string Name of the platoon. +function COHORT:GetName() + return self.name +end + +--- Get radio frequency and modulation. +-- @param #COHORT self +-- @return #number Radio frequency in MHz. +-- @return #number Radio Modulation (0=AM, 1=FM). +function COHORT:GetRadio() + return self.radioFreq, self.radioModu +end + +--- Create a callsign for the asset. +-- @param #COHORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The warehouse asset. +-- @return #COHORT self +function COHORT:GetCallsign(Asset) + + if self.callsignName then + + Asset.callsign={} + + for i=1,Asset.nunits do + + local callsign={} + + callsign[1]=self.callsignName + callsign[2]=math.floor(self.callsigncounter / 10) + callsign[3]=self.callsigncounter % 10 + if callsign[3]==0 then + callsign[3]=1 + self.callsigncounter=self.callsigncounter+2 + else + self.callsigncounter=self.callsigncounter+1 + end + + Asset.callsign[i]=callsign + + self:T3({callsign=callsign}) + + --TODO: there is also a table entry .name, which is a string. + end + + + end + +end + +--- Create a modex for the asset. +-- @param #COHORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The warehouse asset. +-- @return #COHORT self +function COHORT:GetModex(Asset) + + if self.modex then + + Asset.modex={} + + for i=1,Asset.nunits do + + Asset.modex[i]=string.format("%03d", self.modex+self.modexcounter) + + self.modexcounter=self.modexcounter+1 + + self:T3({modex=Asset.modex[i]}) + + end + + end + +end + + +--- Add TACAN channels to the platoon. Note that channels can only range from 1 to 126. +-- @param #COHORT self +-- @param #number ChannelMin Channel. +-- @param #number ChannelMax Channel. +-- @return #COHORT self +-- @usage mysquad:AddTacanChannel(64,69) -- adds channels 64, 65, 66, 67, 68, 69 +function COHORT:AddTacanChannel(ChannelMin, ChannelMax) + + ChannelMax=ChannelMax or ChannelMin + + if ChannelMin>126 then + self:E(self.lid.."ERROR: TACAN Channel must be <= 126! Will not add to available channels") + return self + end + if ChannelMax>126 then + self:E(self.lid.."WARNING: TACAN Channel must be <= 126! Adjusting ChannelMax to 126") + ChannelMax=126 + end + + for i=ChannelMin,ChannelMax do + self.tacanChannel[i]=true + end + + return self +end + +--- Get an unused TACAN channel. +-- @param #COHORT self +-- @return #number TACAN channel or *nil* if no channel is free. +function COHORT:FetchTacan() + + -- Get the smallest free channel if there is one. + local freechannel=nil + for channel,free in pairs(self.tacanChannel) do + if free then + if freechannel==nil or channel=2 and #self.assets>0 then + + local text="" + for j,_asset in pairs(self.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Text. + text=text..string.format("\n[%d] %s (%s*%d): ", j, asset.spawngroupname, asset.unittype, asset.nunits) + + if asset.spawned then + + --- + -- Spawned + --- + + -- Mission info. + local mission=self.legion and self.legion:GetAssetCurrentMission(asset) or false + if mission then + local distance=asset.flightgroup and UTILS.MetersToNM(mission:GetTargetDistance(asset.flightgroup.group:GetCoordinate())) or 0 + text=text..string.format("Mission %s - %s: Status=%s, Dist=%.1f NM", mission.name, mission.type, mission.status, distance) + else + text=text.."Mission None" + end + + -- Flight status. + text=text..", Flight: " + if asset.flightgroup and asset.flightgroup:IsAlive() then + local status=asset.flightgroup:GetState() + local fuelmin=asset.flightgroup:GetFuelMin() + local fuellow=asset.flightgroup:IsFuelLow() + local fuelcri=asset.flightgroup:IsFuelCritical() + + text=text..string.format("%s Fuel=%d", status, fuelmin) + if fuelcri then + text=text.." (Critical!)" + elseif fuellow then + text=text.." (Low)" + end + + local lifept, lifept0=asset.flightgroup:GetLifePoints() + text=text..string.format(", Life=%d/%d", lifept, lifept0) + + local ammo=asset.flightgroup:GetAmmoTot() + text=text..string.format(", Ammo=%d [G=%d, R=%d, B=%d, M=%d]", ammo.Total,ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles) + else + text=text.."N/A" + end + + -- Payload info. + local payload=asset.payload and table.concat(self.legion:GetPayloadMissionTypes(asset.payload), ", ") or "None" + text=text..", Payload={"..payload.."}" + + else + + --- + -- In Stock + --- + + text=text..string.format("In Stock") + + if self:IsRepaired(asset) then + text=text..", Combat Ready" + else + + text=text..string.format(", Repaired in %d sec", self:GetRepairTime(asset)) + + if asset.damage then + text=text..string.format(" (Damage=%.1f)", asset.damage) + end + end + + if asset.Treturned then + local T=timer.getAbsTime()-asset.Treturned + text=text..string.format(", Returned for %d sec", T) + end + + end + end + self:I(self.lid..text) + end + +end + +--- On after "Stop" event. +-- @param #COHORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function COHORT:onafterStop(From, Event, To) + + -- Debug info. + self:I(self.lid.."STOPPING Cohort and removing all assets!") + + -- Remove all assets. + for i=#self.assets,1,-1 do + local asset=self.assets[i] + self:DelAsset(asset) + end + + -- Clear call scheduler. + self.CallScheduler:Clear() + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check if there is a platoon that can execute a given mission. +-- We check the mission type, the refuelling system, engagement range +-- @param #COHORT self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return #boolean If true, Cohort can do that type of mission. +function COHORT:CanMission(Mission) + + local cando=true + + -- On duty?= + if not self:IsOnDuty() then + self:T(self.lid..string.format("Cohort in not OnDuty but in state %s. Cannot do mission %s with target %s", self:GetState(), Mission.name, Mission:GetTargetName())) + return false + end + + -- Check mission type. WARNING: This assumes that all assets of the squad can do the same mission types! + if not self:CheckMissionType(Mission.type, self:GetMissionTypes()) then + self:T(self.lid..string.format("INFO: Squad cannot do mission type %s (%s, %s)", Mission.type, Mission.name, Mission:GetTargetName())) + return false + end + + -- Check that tanker mission has the correct refuelling system. + if Mission.type==AUFTRAG.Type.TANKER then + + if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then + -- Correct refueling system. + else + self:T(self.lid..string.format("INFO: Wrong refueling system requested=%s != %s=available", tostring(Mission.refuelSystem), tostring(self.tankerSystem))) + return false + end + + end + + -- Distance to target. + local TargetDistance=Mission:GetTargetDistance(self.legion:GetCoordinate()) + + -- Max engage range. + local engagerange=Mission.engageRange and math.max(self.engageRange, Mission.engageRange) or self.engageRange + + -- Set range is valid. Mission engage distance can overrule the squad engage range. + if TargetDistance>engagerange then + self:I(self.lid..string.format("INFO: Squad is not in range. Target dist=%d > %d NM max mission Range", UTILS.MetersToNM(TargetDistance), UTILS.MetersToNM(engagerange))) + return false + end + + return true +end + +--- Count assets in legion warehouse stock. +-- @param #COHORT self +-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Number of assets. +function COHORT:CountAssets(InStock, MissionTypes, Attributes) + + local N=0 + for _,_asset in pairs(self.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + if MissionTypes==nil or self:CheckMissionCapability(MissionTypes, self.missiontypes) then + if Attributes==nil or self:CheckAttribute(Attributes) then + if asset.spawned then + if not InStock then + N=N+1 --Spawned but we also count the spawned ones. + end + else + N=N+1 --This is in stock. + end + end + end + end + + return N +end + +--- Get assets for a mission. +-- @param #COHORT self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @param #number Nplayloads Number of payloads available. +-- @return #table Assets that can do the required mission. +function COHORT:RecruitAssets(Mission, Npayloads) + + -- Number of payloads available. + Npayloads=Npayloads or self.legion:CountPayloadsInStock(Mission.type, self.aircrafttype, Mission.payloads) + + local assets={} + + -- Loop over assets. + for _,_asset in pairs(self.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + + -- Check if asset is currently on a mission (STARTED or QUEUED). + if self.legion:IsAssetOnMission(asset) then + + --- + -- Asset is already on a mission. + --- + + -- Check if this asset is currently on a GCICAP mission (STARTED or EXECUTING). + if self.legion:IsAssetOnMission(asset, AUFTRAG.Type.GCICAP) and Mission.type==AUFTRAG.Type.INTERCEPT then + + -- Check if the payload of this asset is compatible with the mission. + -- Note: we do not check the payload as an asset that is on a GCICAP mission should be able to do an INTERCEPT as well! + self:I(self.lid.."Adding asset on GCICAP mission for an INTERCEPT mission") + table.insert(assets, asset) + + end + + else + + --- + -- Asset as NO current mission + --- + + if asset.spawned then + + --- + -- Asset is already SPAWNED (could be uncontrolled on the airfield or inbound after another mission) + --- + + local flightgroup=asset.flightgroup + + -- Firstly, check if it has the right payload. + if self:CheckMissionCapability(Mission.type, asset.payload.capabilities) and flightgroup and flightgroup:IsAlive() then + + -- Assume we are ready and check if any condition tells us we are not. + local combatready=true + + if Mission.type==AUFTRAG.Type.INTERCEPT then + combatready=flightgroup:CanAirToAir() + else + local excludeguns=Mission.type==AUFTRAG.Type.BOMBING or Mission.type==AUFTRAG.Type.BOMBRUNWAY or Mission.type==AUFTRAG.Type.BOMBCARPET or Mission.type==AUFTRAG.Type.SEAD or Mission.type==AUFTRAG.Type.ANTISHIP + combatready=flightgroup:CanAirToGround(excludeguns) + end + + -- No more attacks if fuel is already low. Safety first! + if flightgroup:IsFuelLow() then + combatready=false + end + + -- Check if in a state where we really do not want to fight any more. + if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() or flightgroup:IsDead() or flightgroup:IsStopped() then + combatready=false + end + + -- This asset is "combatready". + if combatready then + self:I(self.lid.."Adding SPAWNED asset to ANOTHER mission as it is COMBATREADY") + table.insert(assets, asset) + end + + end + + else + + --- + -- Asset is still in STOCK + --- + + -- Check that asset is not already requested for another mission. + if Npayloads>0 and self:IsRepaired(asset) and (not asset.requested) then + + -- Add this asset to the selection. + table.insert(assets, asset) + + -- Reduce number of payloads so we only return the number of assets that could do the job. + Npayloads=Npayloads-1 + + end + + end + end + end -- loop over assets + + return assets +end + + +--- Get the time an asset needs to be repaired. +-- @param #COHORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset. +-- @return #number Time in seconds until asset is repaired. +function COHORT:GetRepairTime(Asset) + + if Asset.Treturned then + + local t=self.maintenancetime + t=t+Asset.damage*self.repairtime + + -- Seconds after returned. + local dt=timer.getAbsTime()-Asset.Treturned + + local T=t-dt + + return T + else + return 0 + end + +end + +--- Checks if a mission type is contained in a table of possible types. +-- @param #COHORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function COHORT:IsRepaired(Asset) + + if Asset.Treturned then + local Tnow=timer.getAbsTime() + local Trepaired=Asset.Treturned+self.maintenancetime + if Tnow>=Trepaired then + return true + else + return false + end + + else + return true + end + +end + + +--- Checks if a mission type is contained in a table of possible types. +-- @param #COHORT self +-- @param #string MissionType The requested mission type. +-- @param #table PossibleTypes A table with possible mission types. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function COHORT:CheckMissionType(MissionType, PossibleTypes) + + if type(PossibleTypes)=="string" then + PossibleTypes={PossibleTypes} + end + + for _,canmission in pairs(PossibleTypes) do + if canmission==MissionType then + return true + end + end + + return false +end + +--- Check if a mission type is contained in a list of possible capabilities. +-- @param #COHORT self +-- @param #table MissionTypes The requested mission type. Can also be passed as a single mission type `#string`. +-- @param #table Capabilities A table with possible capabilities. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function COHORT:CheckMissionCapability(MissionTypes, Capabilities) + + if type(MissionTypes)~="table" then + MissionTypes={MissionTypes} + end + + for _,cap in pairs(Capabilities) do + local capability=cap --Ops.Auftrag#AUFTRAG.Capability + for _,MissionType in pairs(MissionTypes) do + if capability.MissionType==MissionType then + return true + end + end + end + + return false +end + +--- Check if the platoon attribute matches the given attribute(s). +-- @param #COHORT self +-- @param #table Attributes The requested attributes. See `WAREHOUSE.Attribute` enum. Can also be passed as a single attribute `#string`. +-- @return #boolean If true, the squad has the requested attribute. +function COHORT:CheckAttribute(Attributes) + + if type(Attributes)~="table" then + Attributes={Attributes} + end + + for _,attribute in pairs(Attributes) do + if attribute==self.attribute then + return true + end + end + + return false +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 42a0da3ed..1fa0c414b 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -346,6 +346,13 @@ function FLIGHTGROUP:SetAirwing(airwing) return self end +--- Get airwing the flight group belongs to. +-- @param #FLIGHTGROUP self +-- @return Ops.AirWing#AIRWING The AIRWING object. +function FLIGHTGROUP:GetAirWing() + return self.airwing +end + --- Set if aircraft is VTOL capable. Unfortunately, there is no DCS way to determine this via scripting. -- @param #FLIGHTGROUP self -- @return #FLIGHTGROUP self @@ -354,13 +361,6 @@ function FLIGHTGROUP:SetVTOL() return self end ---- Get airwing the flight group belongs to. --- @param #FLIGHTGROUP self --- @return Ops.AirWing#AIRWING The AIRWING object. -function FLIGHTGROUP:GetAirWing() - return self.airwing -end - --- Set the FLIGHTCONTROL controlling this flight group. -- @param #FLIGHTGROUP self -- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol The FLIGHTCONTROL object. diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua new file mode 100644 index 000000000..825801bd3 --- /dev/null +++ b/Moose Development/Moose/Ops/Legion.lua @@ -0,0 +1,1439 @@ +--- **Ops** - Legion Warehouse. +-- +-- Parent class of Airwings and Brigades. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.Legion +-- @image OPS_Legion.png + + +--- LEGION class. +-- @type LEGION +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity of output. +-- @field #string lid Class id string for output to DCS log file. +-- @field #table missionqueue Mission queue table. +-- @field #table cohorts Cohorts of this legion. +-- @extends Functional.Warehouse#WAREHOUSE + +--- Be surprised! +-- +-- === +-- +-- # The LEGION Concept +-- +-- An LEGION consists of multiple COHORTs. These cohorts "live" in a WAREHOUSE, i.e. a physical structure that is connected to an airbase (airdrome, FRAP or ship). +-- +-- @field #LEGION +LEGION = { + ClassName = "LEGION", + verbose = 0, + lid = nil, + missionqueue = {}, + cohorts = {}, +} + +--- LEGION class version. +-- @field #string version +LEGION.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- ToDo list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot. +-- TODO: Make general so it can be inherited by AIRWING and BRIGADE classes. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new LEGION class object. +-- @param #LEGION self +-- @param #string WarehouseName Name of the warehouse STATIC or UNIT object representing the warehouse. +-- @param #string LegionName Name of the legion. +-- @return #LEGION self +function LEGION:New(WarehouseName, LegionName) + + -- Inherit everything from WAREHOUSE class. + local self=BASE:Inherit(self, WAREHOUSE:New(WarehouseName, LegionName)) -- #LEGION + + -- Nil check. + if not self then + BASE:E(string.format("ERROR: Could not find warehouse %s!", WarehouseName)) + return nil + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("LEGION %s | ", self.alias) + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. + self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + + self:AddTransition("*", "OpsOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). + self:AddTransition("*", "FlightOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). + self:AddTransition("*", "ArmyOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). + self:AddTransition("*", "NavyOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). + + -- Defaults: + -- TODO + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the LEGION. Initializes parameters and starts event handlers. + -- @function [parent=#LEGION] Start + -- @param #LEGION self + + --- Triggers the FSM event "Start" after a delay. Starts the LEGION. Initializes parameters and starts event handlers. + -- @function [parent=#LEGION] __Start + -- @param #LEGION self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the LEGION and all its event handlers. + -- @param #LEGION self + + --- Triggers the FSM event "Stop" after a delay. Stops the LEGION and all its event handlers. + -- @function [parent=#LEGION] __Stop + -- @param #LEGION self + -- @param #number delay Delay in seconds. + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set verbosity level. +-- @param #LEGION self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #LEGION self +function LEGION:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + +--- Add a mission for the airwing. The airwing will pick the best available assets for the mission and lauch it when ready. +-- @param #LEGION self +-- @param Ops.Auftrag#AUFTRAG Mission Mission for this airwing. +-- @return #LEGION self +function LEGION:AddMission(Mission) + + -- Set status to QUEUED. This also attaches the airwing to this mission. + Mission:Queued(self) + + -- Add mission to queue. + table.insert(self.missionqueue, Mission) + + -- Info text. + local text=string.format("Added mission %s (type=%s). Starting at %s. Stopping at %s", + tostring(Mission.name), tostring(Mission.type), UTILS.SecondsToClock(Mission.Tstart, true), Mission.Tstop and UTILS.SecondsToClock(Mission.Tstop, true) or "INF") + self:T(self.lid..text) + + return self +end + +--- Remove mission from queue. +-- @param #LEGION self +-- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed. +-- @return #LEGION self +function LEGION:RemoveMission(Mission) + + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission.auftragsnummer==Mission.auftragsnummer then + mission.airwing=nil + table.remove(self.missionqueue, i) + break + end + + end + + return self +end + +--- Get cohort by name. +-- @param #LEGION self +-- @param #string CohortName Name of the platoon. +-- @return Ops.Cohort#COHORT The Cohort object. +function LEGION:_GetCohort(CohortName) + + for _,_cohort in pairs(self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + if cohort.name==CohortName then + return cohort + end + + end + + return nil +end + +--- Get cohort of an asset. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The squadron asset. +-- @return Ops.Cohort#COHORT The Cohort object. +function LEGION:_GetCohortOfAsset(Asset) + local cohort=self:_GetCohort(Asset.squadname) + return cohort +end + + +--- Check if a BRIGADE class is calling. +-- @param #LEGION self +-- @return #boolean If true, this is a BRIGADE. +function LEGION:IsBrigade() + local is=self.ClassName==BRIGADE.ClassName + return is +end + +--- Check if the AIRWING class is calling. +-- @param #LEGION self +-- @return #boolean If true, this is an AIRWING. +function LEGION:IsAirwing() + local is=self.ClassName==AIRWING.ClassName + return is +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Start LEGION FSM. +-- @param #LEGION self +function LEGION:onafterStart(From, Event, To) + + -- Start parent Warehouse. + self:GetParent(self, LEGION).onafterStart(self, From, Event, To) + + -- Info. + self:I(self.lid..string.format("Starting LEGION v%s", LEGION.version)) + +end + + + +--- Check if mission is not over and ready to cancel. +-- @param #LEGION self +function LEGION:_CheckMissions() + + -- Loop over missions in queue. + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission:IsNotOver() and mission:IsReadyToCancel() then + mission:Cancel() + end + end + +end +--- Get next mission. +-- @param #LEGION self +-- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. +function LEGION:_GetNextMission() + + -- Number of missions. + local Nmissions=#self.missionqueue + + -- Treat special cases. + if Nmissions==0 then + return nil + end + + -- Sort results table wrt prio and start time. + local function _sort(a, b) + local taskA=a --Ops.Auftrag#AUFTRAG + local taskB=b --Ops.Auftrag#AUFTRAG + return (taskA.prio0 then + self:E(self.lid..string.format("ERROR: mission %s of type %s has already assets attached!", mission.name, mission.type)) + end + mission.assets={} + + -- Assign assets to mission. + for i=1,mission.nassets do + local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Should not happen as we just checked! + if self:IsAirwing() and not asset.payload then + self:E(self.lid.."ERROR: No payload for asset! This should not happen!") + end + + -- Add asset to mission. + mission:AddAsset(asset) + end + + -- Now return the remaining payloads. + if self:IsAirwing() then + for i=mission.nassets+1,#assets do + local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + for _,uid in pairs(gotpayload) do + if uid==asset.uid then + self:ReturnPayloadFromAsset(asset) + break + end + end + end + end + + return mission + end + + end -- mission due? + end -- mission loop + + return nil +end + +--- Calculate the mission score of an asset. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset +-- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. +-- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. +-- @return #number Mission score. +function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) + + local score=0 + + -- Prefer highly skilled assets. + if asset.skill==AI.Skill.AVERAGE then + score=score+0 + elseif asset.skill==AI.Skill.GOOD then + score=score+10 + elseif asset.skill==AI.Skill.HIGH then + score=score+20 + elseif asset.skill==AI.Skill.EXCELLENT then + score=score+30 + end + + -- Add mission performance to score. + local squad=self:_GetCohortOfAsset(asset) + local missionperformance=squad:GetMissionPeformance(Mission.type) + score=score+missionperformance + + -- Add payload performance to score. + if includePayload and asset.payload then + score=score+self:GetPayloadPeformance(asset.payload, Mission.type) + end + + -- Intercepts need to be carried out quickly. We prefer spawned assets. + if Mission.type==AUFTRAG.Type.INTERCEPT then + if asset.spawned then + self:T(self.lid.."Adding 25 to asset because it is spawned") + score=score+25 + end + end + + -- TODO: This could be vastly improved. Need to gather ideas during testing. + -- Calculate ETA? Assets on orbit missions should arrive faster even if they are further away. + -- Max speed of assets. + -- Fuel amount? + -- Range of assets? + + return score +end + +--- Optimize chosen assets for the mission at hand. +-- @param #LEGION self +-- @param #table assets Table of (unoptimized) assets. +-- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. +-- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. +function LEGION:_OptimizeAssetSelection(assets, Mission, includePayload) + + local TargetVec2=Mission:GetTargetVec2() + + local dStock=UTILS.VecDist2D(TargetVec2, self:GetVec2()) + + -- Calculate distance to mission target. + local distmin=math.huge + local distmax=0 + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + if asset.spawned then + local group=GROUP:FindByName(asset.spawngroupname) + asset.dist=UTILS.VecDist2D(group:GetVec2(), TargetVec2) + else + asset.dist=dStock + end + + if asset.distdistmax then + distmax=asset.dist + end + + end + + -- Calculate the mission score of all assets. + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.score=self:CalculateAssetMissionScore(asset, Mission, includePayload) + end + + --- Sort assets wrt to their mission score. Higher is better. + local function optimize(a, b) + local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem + local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Higher score wins. If equal score ==> closer wins. + -- TODO: Need to include the distance in a smarter way! + return (assetA.score>assetB.score) or (assetA.score==assetB.score and assetA.dist0 then + + --local text=string.format("Requesting assets for mission %s:", Mission.name) + for i,_asset in pairs(Assetlist) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Set asset to requested! Important so that new requests do not use this asset! + asset.requested=true + + if Mission.missionTask then + asset.missionTask=Mission.missionTask + end + + end + + -- Add request to airwing warehouse. + -- TODO: better Assignment string. + self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, Assetlist, #Assetlist, nil, nil, Mission.prio, tostring(Mission.auftragsnummer)) + + -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. + Mission.requestID=self.queueid + end + +end + +--- On after "MissionCancel" event. Cancels the missions of all flightgroups. Deletes request from warehouse queue. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission The mission to be cancelled. +function LEGION:onafterMissionCancel(From, Event, To, Mission) + + -- Info message. + self:I(self.lid..string.format("Cancel mission %s", Mission.name)) + + local Ngroups = Mission:CountOpsGroups() + + if Mission:IsPlanned() or Mission:IsQueued() or Mission:IsRequested() or Ngroups == 0 then + + Mission:Done() + + else + + for _,_asset in pairs(Mission.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + local flightgroup=asset.flightgroup + + if flightgroup then + flightgroup:MissionCancel(Mission) + end + + -- Not requested any more (if it was). + asset.requested=nil + end + + end + + -- Remove queued request (if any). + if Mission.requestID then + self:_DeleteQueueItemByID(Mission.requestID, self.queue) + end + +end + +--- On after "OpsOnMission". +-- @param #LEGION 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 LEGION:onafterOpsOnMission(From, Event, To, OpsGroup, Mission) + if self:IsAirwing() then + + else + + end + +end + +--- On after "NewAsset" event. Asset is added to the given squadron (asset assignment). +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that has just been added. +-- @param #string assignment The (optional) assignment for the asset. +function LEGION:onafterNewAsset(From, Event, To, asset, assignment) + + -- Call parent warehouse function first. + self:GetParent(self, LEGION).onafterNewAsset(self, From, Event, To, asset, assignment) + + -- Debug text. + local text=string.format("New asset %s with assignment %s and request assignment %s", asset.spawngroupname, tostring(asset.assignment), tostring(assignment)) + self:T3(self.lid..text) + + -- Get squadron. + --local squad=self:GetSquadron(asset.assignment) + local squad=self:_GetCohort(asset.assignment) + + -- Check if asset is already part of the squadron. If an asset returns, it will be added again! We check that asset.assignment is also assignment. + if squad then + + if asset.assignment==assignment then + + local nunits=#asset.template.units + + -- Debug text. + local text=string.format("Adding asset to squadron %s: assignment=%s, type=%s, attribute=%s, nunits=%d %s", squad.name, assignment, asset.unittype, asset.attribute, nunits, tostring(squad.ngrouping)) + self:T(self.lid..text) + + -- Adjust number of elements in the group. + if squad.ngrouping then + local template=asset.template + + local N=math.max(#template.units, squad.ngrouping) + + -- Handle units. + for i=1,N do + + -- Unit template. + local unit = template.units[i] + + -- If grouping is larger than units present, copy first unit. + if i>nunits then + table.insert(template.units, UTILS.DeepCopy(template.units[1])) + end + + -- Remove units if original template contains more than in grouping. + if squad.ngroupingnunits then + unit=nil + end + end + + asset.nunits=squad.ngrouping + end + + -- Set takeoff type. + asset.takeoffType=squad.takeoffType + + -- Set parking IDs. + asset.parkingIDs=squad.parkingIDs + + -- Create callsign and modex (needs to be after grouping). + squad:GetCallsign(asset) + squad:GetModex(asset) + + -- Set spawn group name. This has to include "AID-" for warehouse. + asset.spawngroupname=string.format("%s_AID-%d", squad.name, asset.uid) + + -- Add asset to squadron. + squad:AddAsset(asset) + + -- TODO + --asset.terminalType=AIRBASE.TerminalType.OpenBig + else + + --env.info("FF squad asset returned") + self:SquadAssetReturned(squad, asset) + + end + + end +end + +--- On after "AssetReturned" event. Triggered when an asset group returned to its airwing. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Squadron#SQUADRON Squadron The asset squadron. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. +function LEGION:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) + -- Debug message. + self:T(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Squadron.name, tostring(Asset.assignment))) + + -- Stop flightgroup. + if Asset.flightgroup and not Asset.flightgroup:IsStopped() then + Asset.flightgroup:Stop() + end + + -- Return payload. + self:ReturnPayloadFromAsset(Asset) + + -- Return tacan channel. + if Asset.tacan then + Squadron:ReturnTacan(Asset.tacan) + end + + -- Set timestamp. + Asset.Treturned=timer.getAbsTime() +end + + +--- On after "AssetSpawned" event triggered when an asset group is spawned into the cruel world. +-- Creates a new flightgroup element and adds the mission to the flightgroup queue. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Group#GROUP group The group spawned. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that was spawned. +-- @param Functional.Warehouse#WAREHOUSE.Pendingitem request The request of the dead asset. +function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) + + -- Call parent warehouse function first. + self:GetParent(self, LEGION).onafterAssetSpawned(self, From, Event, To, group, asset, request) + + -- Get the SQUADRON of the asset. + local squadron=self:_GetCohortOfAsset(asset) + + -- Check if we have a squadron or if this was some other request. + if squadron then + + -- Create a flight group. + local flightgroup=self:_CreateFlightGroup(asset) + + --- + -- Asset + --- + + -- Set asset flightgroup. + asset.flightgroup=flightgroup + + -- Not requested any more. + asset.requested=nil + + -- Did not return yet. + asset.Treturned=nil + + --- + -- Squadron + --- + + -- Get TACAN channel. + local Tacan=squadron:FetchTacan() + if Tacan then + asset.tacan=Tacan + --flightgroup:SetDefaultTACAN(Tacan,Morse,UnitName,Band,OffSwitch) + flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + end + + -- Set radio frequency and modulation + local radioFreq, radioModu=squadron:GetRadio() + if radioFreq then + flightgroup:SwitchRadio(radioFreq, radioModu) + end + + if squadron.fuellow then + flightgroup:SetFuelLowThreshold(squadron.fuellow) + end + + if squadron.fuellowRefuel then + flightgroup:SetFuelLowRefuel(squadron.fuellowRefuel) + end + + --- + -- Mission + --- + + -- Get Mission (if any). + local mission=self:GetMissionByID(request.assignment) + + -- Add mission to flightgroup queue. + if mission then + + if Tacan then + --mission:SetTACAN(Tacan, Morse, UnitName, Band) + end + + -- Add mission to flightgroup queue. + asset.flightgroup:AddMission(mission) + + -- Trigger event. + self:__OpsOnMission(5, flightgroup, mission) + + else + + if Tacan then + --flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + end + + end + + -- Add group to the detection set of the WINGCOMMANDER. + if self.wingcommander and self.wingcommander.chief then + self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) + end + + end + +end + +--- On after "AssetDead" event triggered when an asset group died. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that is dead. +-- @param Functional.Warehouse#WAREHOUSE.Pendingitem request The request of the dead asset. +function LEGION:onafterAssetDead(From, Event, To, asset, request) + + -- Call parent warehouse function first. + self:GetParent(self, LEGION).onafterAssetDead(self, From, Event, To, asset, request) + + -- Add group to the detection set of the WINGCOMMANDER. + if self.wingcommander and self.wingcommander.chief then + self.wingcommander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) + end + + -- Remove asset from mission is done via Mission:AssetDead() call from flightgroup onafterFlightDead function + -- Remove asset from squadron same +end + +--- On after "Destroyed" event. Remove assets from squadrons. Stop squadrons. Remove airwing from wingcommander. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function LEGION:onafterDestroyed(From, Event, To) + + -- Debug message. + self:I(self.lid.."Legion warehouse destroyed!") + + -- Cancel all missions. + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + mission:Cancel() + end + + -- Remove all squadron assets. + for _,_squadron in pairs(self.cohorts) do + local squadron=_squadron --Ops.Squadron#SQUADRON + -- Stop Squadron. This also removes all assets. + squadron:Stop() + end + + -- Call parent warehouse function first. + self:GetParent(self, LEGION).onafterDestroyed(self, From, Event, To) + +end + + +--- On after "Request" event. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Functional.Warehouse#WAREHOUSE.Queueitem Request Information table of the request. +function LEGION:onafterRequest(From, Event, To, Request) + + -- Assets + local assets=Request.cargoassets + + -- Get Mission + local Mission=self:GetMissionByID(Request.assignment) + + if Mission and assets then + + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + -- This would be the place to modify the asset table before the asset is spawned. + end + + end + + -- Call parent warehouse function after assets have been adjusted. + self:GetParent(self, LEGION).onafterRequest(self, From, Event, To, Request) + +end + +--- On after "SelfRequest" event. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Set#SET_GROUP groupset The set of asset groups that was delivered to the warehouse itself. +-- @param Functional.Warehouse#WAREHOUSE.Pendingitem request Pending self request. +function LEGION:onafterSelfRequest(From, Event, To, groupset, request) + + -- Call parent warehouse function first. + self:GetParent(self, LEGION).onafterSelfRequest(self, From, Event, To, groupset, request) + + -- Get Mission + local mission=self:GetMissionByID(request.assignment) + + for _,_asset in pairs(request.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + end + + for _,_group in pairs(groupset:GetSet()) do + local group=_group --Wrapper.Group#GROUP + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new flight group after an asset was spawned. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. +-- @return Ops.FlightGroup#FLIGHTGROUP The created flightgroup object. +function LEGION:_CreateFlightGroup(asset) + + -- Create flightgroup. + local flightgroup=nil --Ops.OpsGroup#OPSGROUP + if self:IsAirwing() then + flightgroup=FLIGHTGROUP:New(asset.spawngroupname) + elseif self:IsBrigade() then + flightgroup=ARMYGROUP:New(asset.spawngroupname) + else + self:E(self.lid.."ERROR: not airwing or brigade!") + end + + -- Set airwing. + flightgroup:_SetLegion(self) + + -- Set squadron. + flightgroup.squadron=self:_GetCohortOfAsset(asset) + + -- Set home base. + flightgroup.homebase=self.airbase + + return flightgroup +end + + +--- Check if an asset is currently on a mission (STARTED or EXECUTING). +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. +-- @param #table MissionTypes Types on mission to be checked. Default all. +-- @return #boolean If true, asset has at least one mission of that type in the queue. +function LEGION:IsAssetOnMission(asset, MissionTypes) + + if MissionTypes then + if type(MissionTypes)~="table" then + MissionTypes={MissionTypes} + end + else + -- Check all possible types. + MissionTypes=AUFTRAG.Type + end + + if asset.flightgroup and asset.flightgroup:IsAlive() then + + -- Loop over mission queue. + for _,_mission in pairs(asset.flightgroup.missionqueue or {}) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission:IsNotOver() then + + -- Get flight status. + local status=mission:GetGroupStatus(asset.flightgroup) + + -- Only if mission is started or executing. + if (status==AUFTRAG.GroupStatus.STARTED or status==AUFTRAG.GroupStatus.EXECUTING) and self:CheckMissionType(mission.type, MissionTypes) then + return true + end + + end + + end + + end + + -- Alternative: run over all missions and compare to mission assets. + --[[ + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission:IsNotOver() then + for _,_asset in pairs(mission.assets) do + local sqasset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + if sqasset.uid==asset.uid then + return true + end + + end + end + + end + ]] + + return false +end + +--- Get the current mission of the asset. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. +-- @return Ops.Auftrag#AUFTRAG Current mission or *nil*. +function LEGION:GetAssetCurrentMission(asset) + + if asset.flightgroup then + return asset.flightgroup:GetMissionCurrent() + end + + return nil +end + +--- Count payloads in stock. +-- @param #LEGION self +-- @param #table MissionTypes Types on mission to be checked. Default *all* possible types `AUFTRAG.Type`. +-- @param #table UnitTypes Types of units. +-- @param #table Payloads Specific payloads to be counted only. +-- @return #number Count of available payloads in stock. +function LEGION:CountPayloadsInStock(MissionTypes, UnitTypes, Payloads) + + if MissionTypes then + if type(MissionTypes)=="string" then + MissionTypes={MissionTypes} + end + end + + if UnitTypes then + if type(UnitTypes)=="string" then + UnitTypes={UnitTypes} + end + end + + local function _checkUnitTypes(payload) + if UnitTypes then + for _,unittype in pairs(UnitTypes) do + if unittype==payload.aircrafttype then + return true + end + end + else + -- Unit type was not specified. + return true + end + return false + end + + local function _checkPayloads(payload) + if Payloads then + for _,Payload in pairs(Payloads) do + if Payload.uid==payload.uid then + return true + end + end + else + -- Payload was not specified. + return nil + end + return false + end + + local n=0 + for _,_payload in pairs(self.payloads or {}) do + local payload=_payload --#LEGION.Payload + + for _,MissionType in pairs(MissionTypes) do + + local specialpayload=_checkPayloads(payload) + local compatible=self:CheckMissionCapability(MissionType, payload.capabilities) + + local goforit = specialpayload or (specialpayload==nil and compatible) + + if goforit and _checkUnitTypes(payload) then + + if payload.unlimited then + -- Payload is unlimited. Return a BIG number. + return 999 + else + n=n+payload.navail + end + + end + + end + end + + return n +end + +--- Count missions in mission queue. +-- @param #LEGION self +-- @param #table MissionTypes Types on mission to be checked. Default *all* possible types `AUFTRAG.Type`. +-- @return #number Number of missions that are not over yet. +function LEGION:CountMissionsInQueue(MissionTypes) + + MissionTypes=MissionTypes or AUFTRAG.Type + + local N=0 + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + -- Check if this mission type is requested. + if mission:IsNotOver() and self:CheckMissionType(mission.type, MissionTypes) then + N=N+1 + end + + end + + return N +end + +--- Count total number of assets that are in the warehouse stock (not spawned). +-- @param #LEGION self +-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Amount of asset groups in stock. +function LEGION:CountAssets(InStock, MissionTypes, Attributes) + + local N=0 + + for _,_squad in pairs(self.cohorts) do + local squad=_squad --Ops.Squadron#SQUADRON + N=N+squad:CountAssets(InStock, MissionTypes, Attributes) + end + + return N +end + +--- Count assets on mission. +-- @param #LEGION self +-- @param #table MissionTypes Types on mission to be checked. Default all. +-- @param Ops.Squadron#SQUADRON Squadron Only count assets of this squadron. Default count assets of all squadrons. +-- @return #number Number of pending and queued assets. +-- @return #number Number of pending assets. +-- @return #number Number of queued assets. +function LEGION:CountAssetsOnMission(MissionTypes, Squadron) + + local Nq=0 + local Np=0 + + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + -- Check if this mission type is requested. + if self:CheckMissionType(mission.type, MissionTypes or AUFTRAG.Type) then + + for _,_asset in pairs(mission.assets or {}) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + if Squadron==nil or Squadron.name==asset.squadname then + + local request, isqueued=self:GetRequestByID(mission.requestID) + + if isqueued then + Nq=Nq+1 + else + Np=Np+1 + end + + end + + end + end + end + + --env.info(string.format("FF N=%d Np=%d, Nq=%d", Np+Nq, Np, Nq)) + return Np+Nq, Np, Nq +end + +--- Count assets on mission. +-- @param #LEGION self +-- @param #table MissionTypes Types on mission to be checked. Default all. +-- @return #table Assets on pending requests. +function LEGION:GetAssetsOnMission(MissionTypes) + + local assets={} + local Np=0 + + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + -- Check if this mission type is requested. + if self:CheckMissionType(mission.type, MissionTypes) then + + for _,_asset in pairs(mission.assets or {}) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + table.insert(assets, asset) + + end + end + end + + return assets +end + +--- Get the aircraft types of this airwing. +-- @param #LEGION self +-- @param #boolean onlyactive Count only the active ones. +-- @param #table squadrons Table of squadrons. Default all. +-- @return #table Table of unit types. +function LEGION:GetAircraftTypes(onlyactive, squadrons) + + -- Get all unit types that can do the job. + local unittypes={} + + -- Loop over all squadrons. + for _,_squadron in pairs(squadrons or self.cohorts) do + local squadron=_squadron --Ops.Squadron#SQUADRON + + if (not onlyactive) or squadron:IsOnDuty() then + + local gotit=false + for _,unittype in pairs(unittypes) do + if squadron.aircrafttype==unittype then + gotit=true + break + end + end + if not gotit then + table.insert(unittypes, squadron.aircrafttype) + end + + end + end + + return unittypes +end + +--- Check if assets for a given mission type are available. +-- @param #LEGION self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return #boolean If true, enough assets are available. +-- @return #table Assets that can do the required mission. +function LEGION:CanMission(Mission) + + env.info("FF CanMission Classname "..self.ClassName) + + -- Assume we CAN and NO assets are available. + local Can=true + local Assets={} + + -- Squadrons for the job. If user assigned to mission or simply all. + local squadrons=Mission.squadrons or self.cohorts + + -- Get aircraft unit types for the job. + local unittypes=self:GetAircraftTypes(true, squadrons) + + -- Count all payloads in stock. + if self:IsAirwing() then + local Npayloads=self:CountPayloadsInStock(Mission.type, unittypes, Mission.payloads) + + if Npayloads#Assets then + self:T(self.lid..string.format("INFO: Not enough assets available! Got %d but need at least %d", #Assets, Mission.nassets)) + Can=false + end + + return Can, Assets +end + +--- Check if assets for a given mission type are available. +-- @param #LEGION self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return #table Assets that can do the required mission. +function LEGION:RecruitAssets(Mission) + +end + + +--- Check if a mission type is contained in a list of possible types. +-- @param #LEGION self +-- @param #string MissionType The requested mission type. +-- @param #table PossibleTypes A table with possible mission types. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function LEGION:CheckMissionType(MissionType, PossibleTypes) + + if type(PossibleTypes)=="string" then + PossibleTypes={PossibleTypes} + end + + for _,canmission in pairs(PossibleTypes) do + if canmission==MissionType then + return true + end + end + + return false +end + +--- Check if a mission type is contained in a list of possible capabilities. +-- @param #LEGION self +-- @param #string MissionType The requested mission type. +-- @param #table Capabilities A table with possible capabilities. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function LEGION:CheckMissionCapability(MissionType, Capabilities) + + for _,cap in pairs(Capabilities) do + local capability=cap --Ops.Auftrag#AUFTRAG.Capability + if capability.MissionType==MissionType then + return true + end + end + + return false +end + +--- Get payload performance for a given type of misson type. +-- @param #LEGION self +-- @param #LEGION.Payload Payload The payload table. +-- @param #string MissionType Type of mission. +-- @return #number Performance or -1. +function LEGION:GetPayloadPeformance(Payload, MissionType) + + if Payload then + + for _,Capability in pairs(Payload.capabilities) do + local capability=Capability --Ops.Auftrag#AUFTRAG.Capability + if capability.MissionType==MissionType then + return capability.Performance + end + end + + else + self:E(self.lid.."ERROR: Payload is nil!") + end + + return -1 +end + +--- Get mission types a payload can perform. +-- @param #LEGION self +-- @param #LEGION.Payload Payload The payload table. +-- @return #table Mission types. +function LEGION:GetPayloadMissionTypes(Payload) + + local missiontypes={} + + for _,Capability in pairs(Payload.capabilities) do + local capability=Capability --Ops.Auftrag#AUFTRAG.Capability + table.insert(missiontypes, capability.MissionType) + end + + return missiontypes +end + +--- Returns the mission for a given mission ID (Autragsnummer). +-- @param #LEGION self +-- @param #number mid Mission ID (Auftragsnummer). +-- @return Ops.Auftrag#AUFTRAG Mission table. +function LEGION:GetMissionByID(mid) + + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission.auftragsnummer==tonumber(mid) then + return mission + end + + end + + return nil +end + +--- Returns the mission for a given request ID. +-- @param #LEGION self +-- @param #number RequestID Unique ID of the request. +-- @return Ops.Auftrag#AUFTRAG Mission table or *nil*. +function LEGION:GetMissionFromRequestID(RequestID) + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + if mission.requestID and mission.requestID==RequestID then + return mission + end + end + return nil +end + +--- Returns the mission for a given request. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Queueitem Request The warehouse request. +-- @return Ops.Auftrag#AUFTRAG Mission table or *nil*. +function LEGION:GetMissionFromRequest(Request) + return self:GetMissionFromRequestID(Request.uid) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 2ba6a7322..e6922d07a 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -701,6 +701,15 @@ function OPSGROUP:SetVerbosity(VerbosityLevel) return self end +--- Set legion this ops group belongs to. +-- @param #OPSGROUP self +-- @param Ops.Legion#LEGION Legion The Legion. +-- @return #OPSGROUP self +function OPSGROUP:_SetLegion(Legion) + self.legion=Legion + return self +end + --- Set default cruise speed. -- @param #OPSGROUP self -- @param #number Speed Speed in knots. diff --git a/Moose Development/Moose/Ops/Platoon.lua b/Moose Development/Moose/Ops/Platoon.lua new file mode 100644 index 000000000..4753cc80d --- /dev/null +++ b/Moose Development/Moose/Ops/Platoon.lua @@ -0,0 +1,155 @@ +--- **Ops** - Brigade Platoon. +-- +-- **Main Features:** +-- +-- * Set parameters like livery, skill valid for all platoon members. +-- * Define modex and callsigns. +-- * Define mission types, this platoon can perform (see Ops.Auftrag#AUFTRAG). +-- * Pause/unpause platoon operations. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.Platoon +-- @image OPS_Platoon.png + + +--- PLATOON class. +-- @type PLATOON +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity level. +-- @extends Ops.Cohort#COHORT + +--- *Some cool cohort quote* -- Known Author +-- +-- === +-- +-- # The PLATOON Concept +-- +-- A PLATOON is essential part of an BRIGADE. +-- +-- +-- +-- @field #PLATOON +PLATOON = { + ClassName = "PLATOON", + verbose = 0, +} + +--- PLATOON class version. +-- @field #string version +PLATOON.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot! + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new PLATOON object and start the FSM. +-- @param #PLATOON self +-- @param #string TemplateGroupName Name of the template group. +-- @param #number Ngroups Number of asset groups of this platoon. Default 3. +-- @param #string PlatoonName Name of the platoon, e.g. "VFA-37". +-- @return #PLATOON self +function PLATOON:New(TemplateGroupName, Ngroups, PlatoonName) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, PlatoonName)) -- #PLATOON + + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + -- TODO: Platoon specific user functions. + +--- Set brigade of this platoon. +-- @param #PLATOON self +-- @param Ops.Brigade#BRIGADE Brigade The brigade. +-- @return #PLATOON self +function PLATOON:SetBrigade(Brigade) + self.legion=Brigade + return self +end + +--- Get brigade of this platoon. +-- @param #PLATOON self +-- @return Ops.Brigade#BRIGADE The brigade. +function PLATOON:GetBrigade() + return self.legion +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. +-- @param #PLATOON self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function PLATOON:onafterStart(From, Event, To) + + -- Short info. + local text=string.format("Starting PLATOON %s", self.name) + self:I(self.lid..text) + + -- Start the status monitoring. + self:__Status(-1) +end + +--- On after "Status" event. +-- @param #PLATOON self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function PLATOON:onafterStatus(From, Event, To) + + if self.verbose>=1 then + + -- FSM state. + local fsmstate=self:GetState() + + local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A" + local modex=self.modex and self.modex or -1 + local skill=self.skill and tostring(self.skill) or "N/A" + + local NassetsTot=#self.assets + local NassetsInS=self:CountAssets(true) + local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0 + if self.brigade then + NassetsQP, NassetsP, NassetsQ=self.brigade:CountAssetsOnMission(nil, self) + end + + -- Short info. + local text=string.format("%s [Type=%s, Call=%s, Modex=%d, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]", + fsmstate, self.aircrafttype, callsign, modex, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ) + self:I(self.lid..text) + + -- Check if group has detected any units. + self:_CheckAssetStatus() + + end + + if not self:IsStopped() then + self:__Status(-60) + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Misc functions. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index cb95b93fa..74cd5cc31 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -704,7 +704,7 @@ function SQUADRON:onafterStatus(From, Event, To) local skill=self.skill and tostring(self.skill) or "N/A" local NassetsTot=#self.assets - local NassetsInS=self:CountAssetsInStock() + local NassetsInS=self:CountAssets(true) local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0 if self.airwing then NassetsQP, NassetsP, NassetsQ=self.airwing:CountAssetsOnMission(nil, self) @@ -888,18 +888,25 @@ end --- Count assets in airwing (warehous) stock. -- @param #SQUADRON self +-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. -- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. --- @return #number Assets not spawned. -function SQUADRON:CountAssetsInStock(MissionTypes) +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Number of assets. +function SQUADRON:CountAssets(InStock, MissionTypes, Attributes) local N=0 for _,_asset in pairs(self.assets) do local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset - if asset.spawned then - else - if MissionTypes==nil or self:CheckMissionCapability(MissionTypes, self.missiontypes) then - N=N+1 + if MissionTypes==nil or self:CheckMissionCapability(MissionTypes, self.missiontypes) then + if Attributes==nil or self:CheckAttribute(Attributes) then + if asset.spawned then + if not InStock then + N=N+1 --Spawned but we also count the spawned ones. + end + else + N=N+1 --This is in stock. + end end end end @@ -1099,6 +1106,26 @@ function SQUADRON:CheckMissionCapability(MissionTypes, Capabilities) return false end +--- Check if the squadron attribute matches the given attribute(s). +-- @param #SQUADRON self +-- @param #table Attributes The requested attributes. See `WAREHOUSE.Attribute` enum. Can also be passed as a single attribute `#string`. +-- @return #boolean If true, the squad has the requested attribute. +function SQUADRON:CheckAttribute(Attributes) + + if type(Attributes)~="table" then + Attributes={Attributes} + end + + for _,attribute in pairs(Attributes) do + if attribute==self.attribute then + return true + end + end + + return false +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/WingCommander.lua b/Moose Development/Moose/Ops/WingCommander.lua index 5074de99d..144ae33c1 100644 --- a/Moose Development/Moose/Ops/WingCommander.lua +++ b/Moose Development/Moose/Ops/WingCommander.lua @@ -223,10 +223,10 @@ function WINGCOMMANDER:onafterStatus(From, Event, To) for _,_airwing in pairs(self.airwings) do local airwing=_airwing --Ops.AirWing#AIRWING local Nassets=airwing:CountAssets() - local Nastock=airwing:CountAssetsInStock() + local Nastock=airwing:CountAssets(true) text=text..string.format("\n* %s [%s]: Assets=%s stock=%s", airwing.alias, airwing:GetState(), Nassets, Nastock) for _,aname in pairs(AUFTRAG.Type) do - local na=airwing:CountAssetsInStock({aname}) + local na=airwing:CountAssets(true, {aname}) local np=airwing:CountPayloadsInStock({aname}) local nm=airwing:CountAssetsOnMission({aname}) if na>0 or np>0 then @@ -405,6 +405,22 @@ function WINGCOMMANDER:GetAirwingForMission(Mission) return nil end +--- Check mission queue and assign ONE planned mission. +-- @param #WINGCOMMANDER self +-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Amount of asset groups in stock. +function WINGCOMMANDER:CountAssets(InStock, MissionTypes, Attributes) + local N=0 + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + N=N+airwing:CountAssets(InStock, MissionTypes, Attributes) + end + + return N +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file From 88e59a273935a711e37dd5b62c1f44381c895f81 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 11 Aug 2021 10:40:16 +0200 Subject: [PATCH 069/141] OPS - Ground ops --- Moose Development/Moose/Ops/ArmyGroup.lua | 64 ++++++++++++- Moose Development/Moose/Ops/Brigade.lua | 20 ++++- Moose Development/Moose/Ops/Cohort.lua | 29 +++--- Moose Development/Moose/Ops/Legion.lua | 40 ++++++--- Moose Development/Moose/Ops/OpsGroup.lua | 105 ++++++++++++++++------ Moose Development/Moose/Ops/Platoon.lua | 27 +++++- 6 files changed, 230 insertions(+), 55 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 90a843e17..8143106e1 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -109,6 +109,9 @@ function ARMYGROUP:New(group) -- From State --> Event --> To State self:AddTransition("*", "FullStop", "Holding") -- Hold position. self:AddTransition("*", "Cruise", "Cruising") -- Cruise along the given route of waypoints. + + self:AddTransition("*", "RTZ", "Returning") -- Group is returning to (home) zone. + self:AddTransition("Returning", "Returned", "Returned") -- Group is returned to (home) zone. self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards. self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. @@ -626,7 +629,7 @@ end function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) -- Debug info. - local text=string.format("Update route n=%s, Speed=%s, Formation=%s", tostring(n), tostring(Speed), tostring(Formation)) + local text=string.format("Update route state=%s: n=%s, Speed=%s, Formation=%s", self:GetState(), tostring(n), tostring(Speed), tostring(Formation)) self:T(self.lid..text) -- Update route from this waypoint number onwards. @@ -823,6 +826,60 @@ function ARMYGROUP:onafterRearm(From, Event, To, Coordinate, Formation) end +--- On after "RTZ" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE Zone The zone to return to. +-- @param #number Formation Formation of the group. +function ARMYGROUP:onafterRTZ(From, Event, To, Zone, Formation) + + -- ID of current waypoint. + local uid=self:GetWaypointCurrent().uid + + -- Zone. + local zone=Zone or self.homezone + + if zone then + + -- Debug info. + self:I(self.lid..string.format("RTZ to Zone %s", zone:GetName())) + + local Coordinate=zone:GetRandomCoordinate() + + -- Add waypoint after current. + local wp=self:AddWaypoint(Coordinate, nil, uid, Formation, true) + + -- Set if we want to resume route after reaching the detour waypoint. + wp.detour=0 + + else + self:E(self.lid.."ERROR: No RTZ zone given!") + end + +end + +--- On after "Returned" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARMYGROUP:onafterReturned(From, Event, To) + + -- Debug info. + self:T(self.lid..string.format("Group returned")) + + if self.legion then + -- Debug info. + self:T(self.lid..string.format("Adding group back to warehouse stock")) + + -- Add asset back in 10 seconds. + self.legion:__AddAsset(10, self.group, 1) + end + +end + --- On after "Rearming" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -1048,6 +1105,8 @@ end -- @param #string To To state. function ARMYGROUP:onafterFullStop(From, Event, To) + self:I(self.lid..string.format("Full stop!")) + -- Get current position. local pos=self:GetCoordinate() @@ -1125,7 +1184,8 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation -- Update route. if Updateroute==nil or Updateroute==true then - self:_CheckGroupDone(1) + self:UpdateRoute() + --self:_CheckGroupDone(1) end return waypoint diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index 65e65c6de..98d611dcc 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -71,7 +71,7 @@ function BRIGADE:New(WarehouseName, BrigadeName) -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("*", "PlatoonOnMission", "*") -- Add a (mission) request to the warehouse. + self:AddTransition("*", "PlatoonAssetReturned", "*") -- An asset returned (from a mission) to the Brigade warehouse. return self end @@ -266,7 +266,23 @@ end -- FSM Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - +--- On after "ArmyOnMission". +-- @param #BRIGADE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup Ops army group on mission. +-- @param Ops.Auftrag#AUFTRAG Mission The requested mission. +function BRIGADE:onafterArmyOnMission(From, Event, To, ArmyGroup, Mission) + local armygroup=ArmyGroup --Ops.ArmyGroup#ARMYGROUP + local mission=Mission --Ops.Auftrag#AUFTRAG + + -- Debug info. + self:T(self.lid..string.format("Group %s on %s mission %s", armygroup:GetName(), mission:GetType(), mission:GetName())) + + + +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 2d28c1a1b..6651e14a0 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -579,8 +579,8 @@ end function COHORT:onafterStart(From, Event, To) -- Short info. - local text=string.format("Starting COHORT %s", self.name) - self:T(self.lid..text) + local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name) + self:I(self.lid..text) -- Start the status monitoring. self:__Status(-1) @@ -618,15 +618,18 @@ function COHORT:_CheckAssetStatus() text=text..", Flight: " if asset.flightgroup and asset.flightgroup:IsAlive() then local status=asset.flightgroup:GetState() - local fuelmin=asset.flightgroup:GetFuelMin() - local fuellow=asset.flightgroup:IsFuelLow() - local fuelcri=asset.flightgroup:IsFuelCritical() + text=text..string.format("%s", status) - text=text..string.format("%s Fuel=%d", status, fuelmin) - if fuelcri then - text=text.." (Critical!)" - elseif fuellow then - text=text.." (Low)" + if asset.flightgroup:IsFlightgroup() then + local fuelmin=asset.flightgroup:GetFuelMin() + local fuellow=asset.flightgroup:IsFuelLow() + local fuelcri=asset.flightgroup:IsFuelCritical() + text=text..string.format("Fuel=%d", fuelmin) + if fuelcri then + text=text.." (Critical!)" + elseif fuellow then + text=text.." (Low)" + end end local lifept, lifept0=asset.flightgroup:GetLifePoints() @@ -639,8 +642,10 @@ function COHORT:_CheckAssetStatus() end -- Payload info. - local payload=asset.payload and table.concat(self.legion:GetPayloadMissionTypes(asset.payload), ", ") or "None" - text=text..", Payload={"..payload.."}" + if asset.flightgroup:IsFlightgroup() then + local payload=asset.payload and table.concat(self.legion:GetPayloadMissionTypes(asset.payload), ", ") or "None" + text=text..", Payload={"..payload.."}" + end else diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 825801bd3..098aaecc9 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -23,8 +23,10 @@ -- === -- -- # The LEGION Concept --- --- An LEGION consists of multiple COHORTs. These cohorts "live" in a WAREHOUSE, i.e. a physical structure that is connected to an airbase (airdrome, FRAP or ship). +-- +-- The LEGION class contains all functions that are common for the AIRWING, BRIGADE and XXX classes, which inherit the LEGION class. +-- +-- An LEGION consists of multiple COHORTs. These cohorts "live" in a WAREHOUSE, i.e. a physical structure that can be destroyed or captured. -- -- @field #LEGION LEGION = { @@ -79,6 +81,8 @@ function LEGION:New(WarehouseName, LegionName) self:AddTransition("*", "ArmyOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). self:AddTransition("*", "NavyOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). + self:AddTransition("*", "AssetReturned", "*") -- An asset returned (from a mission) to the Legion warehouse. + -- Defaults: -- TODO @@ -598,10 +602,16 @@ end -- @param Ops.OpsGroup#OPSGROUP OpsGroup Ops group on mission -- @param Ops.Auftrag#AUFTRAG Mission The requested mission. function LEGION: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())) + if self:IsAirwing() then - + -- Trigger event for Airwings. + self:FlightOnMission(OpsGroup, Mission) + elseif self:IsBrigade() then + -- Trigger event for Brigades. + self:ArmyOnMission(OpsGroup, Mission) else - end end @@ -684,7 +694,7 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) else --env.info("FF squad asset returned") - self:SquadAssetReturned(squad, asset) + self:AssetReturned(squad, asset) end @@ -696,11 +706,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.Squadron#SQUADRON Squadron The asset squadron. +-- @param Ops.Cohort#COHORT Cohort The cohort the asset belongs to. -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. -function LEGION:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) +function LEGION:onafterAssetReturned(From, Event, To, Cohort, Asset) -- Debug message. - self:T(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Squadron.name, tostring(Asset.assignment))) + self:T(self.lid..string.format("Asset %s from Cohort %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Cohort.name, tostring(Asset.assignment))) -- Stop flightgroup. if Asset.flightgroup and not Asset.flightgroup:IsStopped() then @@ -708,15 +718,23 @@ function LEGION:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) end -- Return payload. - self:ReturnPayloadFromAsset(Asset) + if Asset.flightgroup:IsFlightgroup() then + self:ReturnPayloadFromAsset(Asset) + end -- Return tacan channel. if Asset.tacan then - Squadron:ReturnTacan(Asset.tacan) + Cohort:ReturnTacan(Asset.tacan) end -- Set timestamp. Asset.Treturned=timer.getAbsTime() + + if self:IsAirwing() then + self:SquadronAssetReturned(Cohort, Asset) + elseif self:IsBrigade() then + self:PlatoonAssetReturned(Cohort, Asset) + end end @@ -1252,8 +1270,6 @@ end -- @return #table Assets that can do the required mission. function LEGION:CanMission(Mission) - env.info("FF CanMission Classname "..self.ClassName) - -- Assume we CAN and NO assets are available. local Can=true local Assets={} diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index e6922d07a..8f489f430 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -70,6 +70,9 @@ -- @field Ops.Auftrag#AUFTRAG missionpaused Paused mission. -- @field #number Ndestroyed Number of destroyed units. -- @field #number Nkills Number kills of this groups. +-- +-- @field Ops.Legion#LEGION legion Legion the group belongs to. +-- @field Ops.Cohort#COHORT cohort Cohort the group belongs to. -- -- @field Core.Point#COORDINATE coordinate Current coordinate. -- @@ -1329,7 +1332,7 @@ end --- Despawn the group. The whole group is despawned and (optionally) a "Remove Unit" event is generated for all current units of the group. -- @param #OPSGROUP self -- @param #number Delay Delay in seconds before the group will be despawned. Default immediately. --- @param #boolean NoEventRemoveUnit If true, no event "Remove Unit" is generated. +-- @param #boolean NoEventRemoveUnit If `true`, **no** event "Remove Unit" is generated. -- @return #OPSGROUP self function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) @@ -1337,8 +1340,15 @@ function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) self.scheduleIDDespawn=self:ScheduleOnce(Delay, OPSGROUP.Despawn, self, 0, NoEventRemoveUnit) else + -- Debug info. self:I(self.lid..string.format("Despawning Group!")) + + if self.legion and not NoEventRemoveUnit then + -- Add asset back in 10 seconds. + self.legion:AddAsset(self.group, 1) + end + -- DCS group obejct. local DCSGroup=self:GetDCSGroup() if DCSGroup then @@ -1819,6 +1829,14 @@ function OPSGROUP:IsRetreating() return is end +--- Check if the group is currently returning to a zone. +-- @param #OPSGROUP self +-- @return #boolean If true, group is returning. +function OPSGROUP:IsReturning() + local is=self:is("Returning") + return is +end + --- Check if the group is engaging another unit or group. -- @param #OPSGROUP self -- @return #boolean If true, group is engaging. @@ -2424,7 +2442,7 @@ function OPSGROUP:OnEventBirth(EventData) -- Get element. local element=self:GetElementByName(unitname) - if element then + if element and element.status~=OPSGROUP.ElementStatus.SPAWNED then -- Set element to spawned state. self:ElementSpawned(element) @@ -2715,7 +2733,7 @@ function OPSGROUP:AddTaskWaypoint(task, Waypoint, description, prio, duration) self:T3({newtask=newtask}) -- Update route. - self:__UpdateRoute(-1) + --self:__UpdateRoute(-1) return newtask end @@ -3269,7 +3287,7 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) if Task.description=="Engage_Target" then self:Disengage() end - + self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec") self:_CheckGroupDone(1) end @@ -3429,7 +3447,7 @@ function OPSGROUP:_GetNextMission() for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - if mission:GetGroupStatus(self)==AUFTRAG.Status.SCHEDULED and (mission:IsReadyToGo() or self.airwing) and (mission.importance==nil or mission.importance<=vip) then + if mission:GetGroupStatus(self)==AUFTRAG.Status.SCHEDULED and (mission:IsReadyToGo() or self.legion) and (mission.importance==nil or mission.importance<=vip) then return mission end end @@ -3714,7 +3732,7 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) self:SwitchROE() end -- ROT to default - if Mission.optionROT then + if self:IsFlightgroup() and Mission.optionROT then self:SwitchROT() end -- Alarm state to default. @@ -3753,9 +3771,14 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) if Mission.icls then self:_SwitchICLS() end + + local delay=1 + if Mission.type==AUFTRAG.Type.ARTY then + delay=10 + end -- Check if group is done. - self:_CheckGroupDone(1) + self:_CheckGroupDone(delay) end @@ -3769,6 +3792,9 @@ function OPSGROUP:RouteToMission(mission, delay) -- Delayed call. self:ScheduleOnce(delay, OPSGROUP.RouteToMission, self, mission) else + + -- Debug info. + self:T(self.lid..string.format("Route To Mission")) if self:IsDead() or self:IsStopped() then return @@ -3907,6 +3933,12 @@ function OPSGROUP:RouteToMission(mission, delay) if mission.icls then self:SwitchICLS(mission.icls.Channel, mission.icls.Morse, mission.icls.UnitName) end + + if self:IsArmygroup() then + self:Cruise(mission.missionSpeed and UTILS.KmphToKnots(mission.missionSpeed) or self:GetSpeedCruise()) + elseif self:IsNavygroup() then + self:Cruise(mission.missionSpeed and UTILS.KmphToKnots(mission.missionSpeed) or self:GetSpeedCruise()) + end end end @@ -7165,6 +7197,11 @@ function OPSGROUP:_CheckGroupDone(delay) self:UpdateRoute() return end + + -- Group is returning + if self:IsReturning() then + return + end -- Group is waiting. We deny all updates. if self:IsWaiting() then @@ -7224,10 +7261,19 @@ function OPSGROUP:_CheckGroupDone(delay) -- Passed FINAL waypoint --- - -- No further waypoints. Command a full stop. - self:__FullStop(-1) + if self.legion then + + self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE, LEGION set ==> RTZ")) + self:RTZ(self.legion.spawnzone) + + else - self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE ==> Full Stop")) + -- No further waypoints. Command a full stop. + self:__FullStop(-1) + + self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE ==> Full Stop")) + + end else @@ -7237,7 +7283,6 @@ function OPSGROUP:_CheckGroupDone(delay) if #self.waypoints>0 then self:T(self.lid..string.format("NOT Passed final WP, #WP>0 ==> Update Route")) - --self:UpdateRoute() self:Cruise() else self:E(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop")) @@ -7810,6 +7855,10 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Trigger Retreated event. opsgroup:Retreated() + + elseif opsgroup:IsReturning() then + + opsgroup:Returned() elseif opsgroup:IsPickingup() then @@ -8008,23 +8057,27 @@ end -- @return #OPSGROUP self function OPSGROUP:SwitchROT(rot) - if self:IsAlive() or self:IsInUtero() then + if self:IsFlightgroup() then - self.option.ROT=rot or self.optionDefault.ROT - - if self:IsInUtero() then - self:T2(self.lid..string.format("Setting current ROT=%d when GROUP is SPAWNED", self.option.ROT)) + if self:IsAlive() or self:IsInUtero() then + + self.option.ROT=rot or self.optionDefault.ROT + + if self:IsInUtero() then + self:T2(self.lid..string.format("Setting current ROT=%d when GROUP is SPAWNED", self.option.ROT)) + else + + self.group:OptionROT(self.option.ROT) + + -- Debug info. + self:T(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) + end + + else - - self.group:OptionROT(self.option.ROT) - - -- Debug info. - self:T(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) + self:E(self.lid.."WARNING: Cannot switch ROT! Group is not alive") end - - - else - self:E(self.lid.."WARNING: Cannot switch ROT! Group is not alive") + end return self @@ -9545,7 +9598,7 @@ function OPSGROUP:_AddElementByName(unitname) end -- Trigger spawned event if alive. - if unit:IsAlive() then + if unit:IsAlive() and element.status~=OPSGROUP.ElementStatus.SPAWNED then -- This needs to be slightly delayed (or moved elsewhere) or the first element will always trigger the group spawned event as it is not known that more elements are in the group. self:__ElementSpawned(0.05, element) end diff --git a/Moose Development/Moose/Ops/Platoon.lua b/Moose Development/Moose/Ops/Platoon.lua index 4753cc80d..3c5e933d4 100644 --- a/Moose Development/Moose/Ops/Platoon.lua +++ b/Moose Development/Moose/Ops/Platoon.lua @@ -18,6 +18,7 @@ -- @type PLATOON -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity level. +-- @field Ops.OpsGroup#OPSGROUP.WeaponData weaponData Weapon data table with key=BitType. -- @extends Ops.Cohort#COHORT --- *Some cool cohort quote* -- Known Author @@ -34,6 +35,7 @@ PLATOON = { ClassName = "PLATOON", verbose = 0, + weaponData = {}, } --- PLATOON class version. @@ -87,6 +89,29 @@ function PLATOON:GetBrigade() return self.legion end +--- Add a weapon range for ARTY auftrag. +-- @param #PLATOON self +-- @param #number RangeMin Minimum range in nautical miles. Default 0 NM. +-- @param #number RangeMax Maximum range in nautical miles. Default 10 NM. +-- @param #number BitType Bit mask of weapon type for which the given min/max ranges apply. Default is `ENUMS.WeaponFlag.Auto`, i.e. for all weapon types. +-- @return #PLATOON self +function PLATOON:AddWeaponRange(RangeMin, RangeMax, BitType) + + RangeMin=UTILS.NMToMeters(RangeMin or 0) + RangeMax=UTILS.NMToMeters(RangeMax or 10) + + local weapon={} --Ops.OpsGroup#OPSGROUP.WeaponData + + weapon.BitType=BitType or ENUMS.WeaponFlag.Auto + weapon.RangeMax=RangeMax + weapon.RangeMin=RangeMin + + self.weaponData=self.weaponData or {} + self.weaponData[weapon.BitType]=weapon + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -99,7 +124,7 @@ end function PLATOON:onafterStart(From, Event, To) -- Short info. - local text=string.format("Starting PLATOON %s", self.name) + local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name) self:I(self.lid..text) -- Start the status monitoring. From 93f4b345c5a9f20673899238ef227b29b968775a Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 12 Aug 2021 00:17:55 +0200 Subject: [PATCH 070/141] OPS Generalization --- .../Moose/Functional/Warehouse.lua | 2 +- Moose Development/Moose/Ops/AirWing.lua | 1133 +---------------- Moose Development/Moose/Ops/Auftrag.lua | 18 +- Moose Development/Moose/Ops/Brigade.lua | 2 +- Moose Development/Moose/Ops/Cohort.lua | 13 +- Moose Development/Moose/Ops/FlightGroup.lua | 41 +- Moose Development/Moose/Ops/Legion.lua | 63 +- Moose Development/Moose/Ops/OpsGroup.lua | 36 +- Moose Development/Moose/Ops/Squadron.lua | 869 +------------ 9 files changed, 107 insertions(+), 2070 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index b09a22fc1..a2d317024 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -3996,7 +3996,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu local opsgroup=_DATABASE:GetOpsGroup(group:GetName()) if opsgroup then - opsgroup:Despawn(0) + opsgroup:Despawn(0, true) opsgroup:__Stop(-0.01) else -- Setting parameter to false, i.e. creating NO dead or remove unit event, seems to not confuse the dispatcher logic. diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 22737c5dc..169cd0bce 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -38,7 +38,7 @@ -- @field Ops.RescueHelo#RESCUEHELO rescuehelo The rescue helo. -- @field Ops.RecoveryTanker#RECOVERYTANKER recoverytanker The recoverytanker. -- --- @extends Functional.Warehouse#WAREHOUSE +-- @extends Ops.Legion#LEGION --- Be surprised! -- @@ -112,8 +112,6 @@ AIRWING = { verbose = 0, lid = nil, menu = nil, - squadrons = {}, - missionqueue = {}, payloads = {}, payloadcounter = 0, pointsCAP = {}, @@ -123,10 +121,6 @@ AIRWING = { markpoints = false, } ---- Squadron asset. --- @type AIRWING.SquadronAsset --- @extends Functional.Warehouse#WAREHOUSE.Assetitem - --- Payload data. -- @type AIRWING.Payload -- @field #number uid Unique payload ID. @@ -151,7 +145,7 @@ AIRWING = { --- AIRWING class version. -- @field #string version -AIRWING.version="0.7.0" +AIRWING.version="0.8.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -179,8 +173,8 @@ AIRWING.version="0.7.0" -- @return #AIRWING self function AIRWING:New(warehousename, airwingname) - -- Inherit everything from WAREHOUSE class. - local self=BASE:Inherit(self, WAREHOUSE:New(warehousename, airwingname)) -- #AIRWING + -- Inherit everything from LEGION class. + local self=BASE:Inherit(self, LEGION:New(warehousename, airwingname)) -- #AIRWING -- Nil check. if not self then @@ -192,13 +186,8 @@ function AIRWING:New(warehousename, airwingname) self.lid=string.format("AIRWING %s | ", self.alias) -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. - self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. - - self:AddTransition("*", "SquadAssetReturned", "*") -- Flight was spawned with a mission. - - self:AddTransition("*", "FlightOnMission", "*") -- Flight was spawned with a mission. + -- From State --> Event --> To State + self:AddTransition("*", "SquadronAssetReturned", "*") -- Flight was spawned with a mission. -- Defaults: --self:SetVerbosity(0) @@ -263,7 +252,7 @@ end function AIRWING:AddSquadron(Squadron) -- Add squadron to airwing. - table.insert(self.squadrons, Squadron) + table.insert(self.cohorts, Squadron) -- Add assets to squadron. self:AddAssetToSquadron(Squadron, Squadron.Ngroups) @@ -569,7 +558,7 @@ end -- @return Ops.Squadron#SQUADRON The squadron object. function AIRWING:GetSquadron(SquadronName) - for _,_squadron in pairs(self.squadrons) do + for _,_squadron in pairs(self.cohorts) do local squadron=_squadron --Ops.Squadron#SQUADRON if squadron.name==SquadronName then @@ -581,15 +570,6 @@ function AIRWING:GetSquadron(SquadronName) return nil end ---- Set verbosity level. --- @param #AIRWING self --- @param #number VerbosityLevel Level of output (higher=more). Default 0. --- @return #AIRWING self -function AIRWING:SetVerbosity(VerbosityLevel) - self.verbose=VerbosityLevel or 0 - return self -end - --- Get squadron of an asset. -- @param #AIRWING self -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The squadron asset. @@ -608,46 +588,6 @@ function AIRWING:RemoveAssetFromSquadron(Asset) end end ---- Add a mission for the airwing. The airwing will pick the best available assets for the mission and lauch it when ready. --- @param #AIRWING self --- @param Ops.Auftrag#AUFTRAG Mission Mission for this airwing. --- @return #AIRWING self -function AIRWING:AddMission(Mission) - - -- Set status to QUEUED. This also attaches the airwing to this mission. - Mission:Queued(self) - - -- Add mission to queue. - table.insert(self.missionqueue, Mission) - - -- Info text. - local text=string.format("Added mission %s (type=%s). Starting at %s. Stopping at %s", - tostring(Mission.name), tostring(Mission.type), UTILS.SecondsToClock(Mission.Tstart, true), Mission.Tstop and UTILS.SecondsToClock(Mission.Tstop, true) or "INF") - self:T(self.lid..text) - - return self -end - ---- Remove mission from queue. --- @param #AIRWING self --- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed. --- @return #AIRWING self -function AIRWING:RemoveMission(Mission) - - for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - if mission.auftragsnummer==Mission.auftragsnummer then - mission.airwing=nil - table.remove(self.missionqueue, i) - break - end - - end - - return self -end - --- Set number of CAP flights constantly carried out. -- @param #AIRWING self -- @param #number n Number of flights. Default 1. @@ -826,7 +766,7 @@ end function AIRWING:onafterStart(From, Event, To) -- Start parent Warehouse. - self:GetParent(self).onafterStart(self, From, Event, To) + self:GetParent(self, AIRWING).onafterStart(self, From, Event, To) -- Info. self:I(self.lid..string.format("Starting AIRWING v%s", AIRWING.version)) @@ -870,7 +810,7 @@ function AIRWING:onafterStatus(From, Event, To) local assets=string.format("%d (OnMission: Total=%d, Active=%d, Queued=%d)", self:CountAssets(), Npq, Np, Nq) -- Output. - local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d, Assets=%s", fsmstate, Nmissions, Npayloads, #self.payloads, #self.squadrons, assets) + local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d, Assets=%s", fsmstate, Nmissions, Npayloads, #self.payloads, #self.cohorts, assets) self:I(self.lid..text) end @@ -896,7 +836,7 @@ function AIRWING:onafterStatus(From, Event, To) ------------------- if self.verbose>=3 then local text="Squadrons:" - for i,_squadron in pairs(self.squadrons) do + for i,_squadron in pairs(self.cohorts) do local squadron=_squadron --Ops.Squadron#SQUADRON local callsign=squadron.callsignName and UTILS.GetCallsignName(squadron.callsignName) or "N/A" @@ -1142,459 +1082,11 @@ function AIRWING:GetTankerForFlight(flightgroup) return nil end - ---- Check if mission is not over and ready to cancel. --- @param #AIRWING self -function AIRWING:_CheckMissions() - - -- Loop over missions in queue. - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - if mission:IsNotOver() and mission:IsReadyToCancel() then - mission:Cancel() - end - end - -end ---- Get next mission. --- @param #AIRWING self --- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. -function AIRWING:_GetNextMission() - - -- Number of missions. - local Nmissions=#self.missionqueue - - -- Treat special cases. - if Nmissions==0 then - return nil - end - - -- Sort results table wrt prio and start time. - local function _sort(a, b) - local taskA=a --Ops.Auftrag#AUFTRAG - local taskB=b --Ops.Auftrag#AUFTRAG - return (taskA.prio0 then - self:E(self.lid..string.format("ERROR: mission %s of type %s has already assets attached!", mission.name, mission.type)) - end - mission.assets={} - - -- Assign assets to mission. - for i=1,mission.nassets do - local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Should not happen as we just checked! - if not asset.payload then - self:E(self.lid.."ERROR: No payload for asset! This should not happen!") - end - - -- Add asset to mission. - mission:AddAsset(asset) - end - - -- Now return the remaining payloads. - for i=mission.nassets+1,#assets do - local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - for _,uid in pairs(gotpayload) do - if uid==asset.uid then - self:ReturnPayloadFromAsset(asset) - break - end - end - end - - return mission - end - - end -- mission due? - end -- mission loop - - return nil -end - ---- Calculate the mission score of an asset. --- @param #AIRWING self --- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset --- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. --- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. --- @return #number Mission score. -function AIRWING:CalculateAssetMissionScore(asset, Mission, includePayload) - - local score=0 - - -- Prefer highly skilled assets. - if asset.skill==AI.Skill.AVERAGE then - score=score+0 - elseif asset.skill==AI.Skill.GOOD then - score=score+10 - elseif asset.skill==AI.Skill.HIGH then - score=score+20 - elseif asset.skill==AI.Skill.EXCELLENT then - score=score+30 - end - - -- Add mission performance to score. - local squad=self:GetSquadronOfAsset(asset) - local missionperformance=squad:GetMissionPeformance(Mission.type) - score=score+missionperformance - - -- Add payload performance to score. - if includePayload and asset.payload then - score=score+self:GetPayloadPeformance(asset.payload, Mission.type) - end - - -- Intercepts need to be carried out quickly. We prefer spawned assets. - if Mission.type==AUFTRAG.Type.INTERCEPT then - if asset.spawned then - self:T(self.lid.."Adding 25 to asset because it is spawned") - score=score+25 - end - end - - -- TODO: This could be vastly improved. Need to gather ideas during testing. - -- Calculate ETA? Assets on orbit missions should arrive faster even if they are further away. - -- Max speed of assets. - -- Fuel amount? - -- Range of assets? - - return score -end - ---- Optimize chosen assets for the mission at hand. --- @param #AIRWING self --- @param #table assets Table of (unoptimized) assets. --- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. --- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. -function AIRWING:_OptimizeAssetSelection(assets, Mission, includePayload) - - local TargetVec2=Mission:GetTargetVec2() - - --local dStock=self:GetCoordinate():Get2DDistance(TargetCoordinate) - - local dStock=UTILS.VecDist2D(TargetVec2, self:GetVec2()) - - -- Calculate distance to mission target. - local distmin=math.huge - local distmax=0 - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - if asset.spawned then - local group=GROUP:FindByName(asset.spawngroupname) - --asset.dist=group:GetCoordinate():Get2DDistance(TargetCoordinate) - asset.dist=UTILS.VecDist2D(group:GetVec2(), TargetVec2) - else - asset.dist=dStock - end - - if asset.distdistmax then - distmax=asset.dist - end - - end - - -- Calculate the mission score of all assets. - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - --self:I(string.format("FF asset %s has payload %s", asset.spawngroupname, asset.payload and "yes" or "no!")) - asset.score=self:CalculateAssetMissionScore(asset, Mission, includePayload) - end - - --- Sort assets wrt to their mission score. Higher is better. - local function optimize(a, b) - local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem - local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Higher score wins. If equal score ==> closer wins. - -- TODO: Need to include the distance in a smarter way! - return (assetA.score>assetB.score) or (assetA.score==assetB.score and assetA.dist0 then - - --local text=string.format("Requesting assets for mission %s:", Mission.name) - for i,_asset in pairs(Assetlist) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Set asset to requested! Important so that new requests do not use this asset! - asset.requested=true - - if Mission.missionTask then - asset.missionTask=Mission.missionTask - end - - end - - -- Add request to airwing warehouse. - -- TODO: better Assignment string. - self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, Assetlist, #Assetlist, nil, nil, Mission.prio, tostring(Mission.auftragsnummer)) - - -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. - Mission.requestID=self.queueid - end - -end - ---- On after "MissionCancel" event. Cancels the missions of all flightgroups. Deletes request from warehouse queue. --- @param #AIRWING self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.Auftrag#AUFTRAG Mission The mission to be cancelled. -function AIRWING:onafterMissionCancel(From, Event, To, Mission) - - -- Info message. - self:I(self.lid..string.format("Cancel mission %s", Mission.name)) - - local Ngroups = Mission:CountOpsGroups() - - if Mission:IsPlanned() or Mission:IsQueued() or Mission:IsRequested() or Ngroups == 0 then - - Mission:Done() - - else - - for _,_asset in pairs(Mission.assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - local flightgroup=asset.flightgroup - - if flightgroup then - flightgroup:MissionCancel(Mission) - end - - -- Not requested any more (if it was). - asset.requested=nil - end - - end - - -- Remove queued request (if any). - if Mission.requestID then - self:_DeleteQueueItemByID(Mission.requestID, self.queue) - end - -end - ---- On after "NewAsset" event. Asset is added to the given squadron (asset assignment). --- @param #AIRWING self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that has just been added. --- @param #string assignment The (optional) assignment for the asset. -function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) - - -- Call parent warehouse function first. - self:GetParent(self).onafterNewAsset(self, From, Event, To, asset, assignment) - - -- Debug text. - local text=string.format("New asset %s with assignment %s and request assignment %s", asset.spawngroupname, tostring(asset.assignment), tostring(assignment)) - self:T3(self.lid..text) - - -- Get squadron. - local squad=self:GetSquadron(asset.assignment) - - -- Check if asset is already part of the squadron. If an asset returns, it will be added again! We check that asset.assignment is also assignment. - if squad then - - if asset.assignment==assignment then - - local nunits=#asset.template.units - - -- Debug text. - local text=string.format("Adding asset to squadron %s: assignment=%s, type=%s, attribute=%s, nunits=%d %s", squad.name, assignment, asset.unittype, asset.attribute, nunits, tostring(squad.ngrouping)) - self:T(self.lid..text) - - -- Adjust number of elements in the group. - if squad.ngrouping then - local template=asset.template - - local N=math.max(#template.units, squad.ngrouping) - - -- Handle units. - for i=1,N do - - -- Unit template. - local unit = template.units[i] - - -- If grouping is larger than units present, copy first unit. - if i>nunits then - table.insert(template.units, UTILS.DeepCopy(template.units[1])) - end - - -- Remove units if original template contains more than in grouping. - if squad.ngroupingnunits then - unit=nil - end - end - - asset.nunits=squad.ngrouping - end - - -- Set takeoff type. - asset.takeoffType=squad.takeoffType - - -- Set parking IDs. - asset.parkingIDs=squad.parkingIDs - - -- Create callsign and modex (needs to be after grouping). - squad:GetCallsign(asset) - squad:GetModex(asset) - - -- Set spawn group name. This has to include "AID-" for warehouse. - asset.spawngroupname=string.format("%s_AID-%d", squad.name, asset.uid) - - -- Add asset to squadron. - squad:AddAsset(asset) - - -- TODO - --asset.terminalType=AIRBASE.TerminalType.OpenBig - else - - --env.info("FF squad asset returned") - self:SquadAssetReturned(squad, asset) - - end - - end -end - ---- On after "AssetReturned" event. Triggered when an asset group returned to its airwing. +--- On after "SquadAssetReturned" event. Triggered when an asset group returned to its airwing. -- @param #AIRWING self -- @param #string From From state. -- @param #string Event Event. @@ -1604,326 +1096,12 @@ end function AIRWING:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) -- Debug message. self:T(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Squadron.name, tostring(Asset.assignment))) - - -- Stop flightgroup. - if Asset.flightgroup and not Asset.flightgroup:IsStopped() then - Asset.flightgroup:Stop() - end - - -- Return payload. - self:ReturnPayloadFromAsset(Asset) - - -- Return tacan channel. - if Asset.tacan then - Squadron:ReturnTacan(Asset.tacan) - end - - -- Set timestamp. - Asset.Treturned=timer.getAbsTime() -end - - ---- On after "AssetSpawned" event triggered when an asset group is spawned into the cruel world. --- Creates a new flightgroup element and adds the mission to the flightgroup queue. --- @param #AIRWING self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Wrapper.Group#GROUP group The group spawned. --- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that was spawned. --- @param Functional.Warehouse#WAREHOUSE.Pendingitem request The request of the dead asset. -function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) - - -- Call parent warehouse function first. - self:GetParent(self).onafterAssetSpawned(self, From, Event, To, group, asset, request) - - -- Get the SQUADRON of the asset. - local squadron=self:GetSquadronOfAsset(asset) - - -- Check if we have a squadron or if this was some other request. - if squadron then - - -- Create a flight group. - local flightgroup=self:_CreateFlightGroup(asset) - - --- - -- Asset - --- - - -- Set asset flightgroup. - asset.flightgroup=flightgroup - - -- Not requested any more. - asset.requested=nil - - -- Did not return yet. - asset.Treturned=nil - - --- - -- Squadron - --- - - -- Get TACAN channel. - local Tacan=squadron:FetchTacan() - if Tacan then - asset.tacan=Tacan - --flightgroup:SetDefaultTACAN(Tacan,Morse,UnitName,Band,OffSwitch) - flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) - end - - -- Set radio frequency and modulation - local radioFreq, radioModu=squadron:GetRadio() - if radioFreq then - flightgroup:SwitchRadio(radioFreq, radioModu) - end - - if squadron.fuellow then - flightgroup:SetFuelLowThreshold(squadron.fuellow) - end - - if squadron.fuellowRefuel then - flightgroup:SetFuelLowRefuel(squadron.fuellowRefuel) - end - - --- - -- Mission - --- - - -- Get Mission (if any). - local mission=self:GetMissionByID(request.assignment) - - -- Add mission to flightgroup queue. - if mission then - - if Tacan then - --mission:SetTACAN(Tacan, Morse, UnitName, Band) - end - - -- Add mission to flightgroup queue. - asset.flightgroup:AddMission(mission) - - -- Trigger event. - self:__FlightOnMission(5, flightgroup, mission) - - else - - if Tacan then - --flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) - end - - end - - -- Add group to the detection set of the WINGCOMMANDER. - if self.wingcommander and self.wingcommander.chief then - self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) - end - - end - -end - ---- On after "AssetDead" event triggered when an asset group died. --- @param #AIRWING self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset that is dead. --- @param Functional.Warehouse#WAREHOUSE.Pendingitem request The request of the dead asset. -function AIRWING:onafterAssetDead(From, Event, To, asset, request) - - -- Call parent warehouse function first. - self:GetParent(self).onafterAssetDead(self, From, Event, To, asset, request) - - -- Add group to the detection set of the WINGCOMMANDER. - if self.wingcommander and self.wingcommander.chief then - self.wingcommander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) - end - - -- Remove asset from mission is done via Mission:AssetDead() call from flightgroup onafterFlightDead function - -- Remove asset from squadron same -end - ---- On after "Destroyed" event. Remove assets from squadrons. Stop squadrons. Remove airwing from wingcommander. --- @param #AIRWING self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function AIRWING:onafterDestroyed(From, Event, To) - - -- Debug message. - self:I(self.lid.."Airwing warehouse destroyed!") - - -- Cancel all missions. - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - mission:Cancel() - end - - -- Remove all squadron assets. - for _,_squadron in pairs(self.squadrons) do - local squadron=_squadron --Ops.Squadron#SQUADRON - -- Stop Squadron. This also removes all assets. - squadron:Stop() - end - - -- Call parent warehouse function first. - self:GetParent(self).onafterDestroyed(self, From, Event, To) - -end - - ---- On after "Request" event. --- @param #AIRWING self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Functional.Warehouse#WAREHOUSE.Queueitem Request Information table of the request. -function AIRWING:onafterRequest(From, Event, To, Request) - - -- Assets - local assets=Request.cargoassets - - -- Get Mission - local Mission=self:GetMissionByID(Request.assignment) - - if Mission and assets then - - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - -- This would be the place to modify the asset table before the asset is spawned. - end - - end - - -- Call parent warehouse function after assets have been adjusted. - self:GetParent(self).onafterRequest(self, From, Event, To, Request) - -end - ---- On after "SelfRequest" event. --- @param #AIRWING self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Set#SET_GROUP groupset The set of asset groups that was delivered to the warehouse itself. --- @param Functional.Warehouse#WAREHOUSE.Pendingitem request Pending self request. -function AIRWING:onafterSelfRequest(From, Event, To, groupset, request) - - -- Call parent warehouse function first. - self:GetParent(self).onafterSelfRequest(self, From, Event, To, groupset, request) - - -- Get Mission - local mission=self:GetMissionByID(request.assignment) - - for _,_asset in pairs(request.assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - end - - for _,_group in pairs(groupset:GetSet()) do - local group=_group --Wrapper.Group#GROUP - end - end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new flight group after an asset was spawned. --- @param #AIRWING self --- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. --- @return Ops.FlightGroup#FLIGHTGROUP The created flightgroup object. -function AIRWING:_CreateFlightGroup(asset) - - -- Create flightgroup. - local flightgroup=FLIGHTGROUP:New(asset.spawngroupname) - - -- Set airwing. - flightgroup:SetAirwing(self) - - -- Set squadron. - flightgroup.squadron=self:GetSquadronOfAsset(asset) - - -- Set home base. - flightgroup.homebase=self.airbase - - return flightgroup -end - - ---- Check if an asset is currently on a mission (STARTED or EXECUTING). --- @param #AIRWING self --- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. --- @param #table MissionTypes Types on mission to be checked. Default all. --- @return #boolean If true, asset has at least one mission of that type in the queue. -function AIRWING:IsAssetOnMission(asset, MissionTypes) - - if MissionTypes then - if type(MissionTypes)~="table" then - MissionTypes={MissionTypes} - end - else - -- Check all possible types. - MissionTypes=AUFTRAG.Type - end - - if asset.flightgroup and asset.flightgroup:IsAlive() then - - -- Loop over mission queue. - for _,_mission in pairs(asset.flightgroup.missionqueue or {}) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - if mission:IsNotOver() then - - -- Get flight status. - local status=mission:GetGroupStatus(asset.flightgroup) - - -- Only if mission is started or executing. - if (status==AUFTRAG.GroupStatus.STARTED or status==AUFTRAG.GroupStatus.EXECUTING) and self:CheckMissionType(mission.type, MissionTypes) then - return true - end - - end - - end - - end - - -- Alternative: run over all missions and compare to mission assets. - --[[ - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - if mission:IsNotOver() then - for _,_asset in pairs(mission.assets) do - local sqasset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - if sqasset.uid==asset.uid then - return true - end - - end - end - - end - ]] - - return false -end - ---- Get the current mission of the asset. --- @param #AIRWING self --- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset. --- @return Ops.Auftrag#AUFTRAG Current mission or *nil*. -function AIRWING:GetAssetCurrentMission(asset) - - if asset.flightgroup then - return asset.flightgroup:GetMissionCurrent() - end - - return nil -end - --- Count payloads in stock. -- @param #AIRWING self -- @param #table MissionTypes Types on mission to be checked. Default *all* possible types `AUFTRAG.Type`. @@ -2000,253 +1178,6 @@ function AIRWING:CountPayloadsInStock(MissionTypes, UnitTypes, Payloads) return n end ---- Count missions in mission queue. --- @param #AIRWING self --- @param #table MissionTypes Types on mission to be checked. Default *all* possible types `AUFTRAG.Type`. --- @return #number Number of missions that are not over yet. -function AIRWING:CountMissionsInQueue(MissionTypes) - - MissionTypes=MissionTypes or AUFTRAG.Type - - local N=0 - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - -- Check if this mission type is requested. - if mission:IsNotOver() and self:CheckMissionType(mission.type, MissionTypes) then - N=N+1 - end - - end - - return N -end - ---- Count total number of assets that are in the warehouse stock (not spawned). --- @param #AIRWING self --- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. --- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. --- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. --- @return #number Amount of asset groups in stock. -function AIRWING:CountAssets(InStock, MissionTypes, Attributes) - - local N=0 - - for _,_squad in pairs(self.squadrons) do - local squad=_squad --Ops.Squadron#SQUADRON - N=N+squad:CountAssets(InStock, MissionTypes, Attributes) - end - - return N -end - ---- Count assets on mission. --- @param #AIRWING self --- @param #table MissionTypes Types on mission to be checked. Default all. --- @param Ops.Squadron#SQUADRON Squadron Only count assets of this squadron. Default count assets of all squadrons. --- @return #number Number of pending and queued assets. --- @return #number Number of pending assets. --- @return #number Number of queued assets. -function AIRWING:CountAssetsOnMission(MissionTypes, Squadron) - - local Nq=0 - local Np=0 - - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - -- Check if this mission type is requested. - if self:CheckMissionType(mission.type, MissionTypes or AUFTRAG.Type) then - - for _,_asset in pairs(mission.assets or {}) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - if Squadron==nil or Squadron.name==asset.squadname then - - local request, isqueued=self:GetRequestByID(mission.requestID) - - if isqueued then - Nq=Nq+1 - else - Np=Np+1 - end - - end - - end - end - end - - --env.info(string.format("FF N=%d Np=%d, Nq=%d", Np+Nq, Np, Nq)) - return Np+Nq, Np, Nq -end - ---- Count assets on mission. --- @param #AIRWING self --- @param #table MissionTypes Types on mission to be checked. Default all. --- @return #table Assets on pending requests. -function AIRWING:GetAssetsOnMission(MissionTypes) - - local assets={} - local Np=0 - - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - -- Check if this mission type is requested. - if self:CheckMissionType(mission.type, MissionTypes) then - - for _,_asset in pairs(mission.assets or {}) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - table.insert(assets, asset) - - end - end - end - - return assets -end - ---- Get the aircraft types of this airwing. --- @param #AIRWING self --- @param #boolean onlyactive Count only the active ones. --- @param #table squadrons Table of squadrons. Default all. --- @return #table Table of unit types. -function AIRWING:GetAircraftTypes(onlyactive, squadrons) - - -- Get all unit types that can do the job. - local unittypes={} - - -- Loop over all squadrons. - for _,_squadron in pairs(squadrons or self.squadrons) do - local squadron=_squadron --Ops.Squadron#SQUADRON - - if (not onlyactive) or squadron:IsOnDuty() then - - local gotit=false - for _,unittype in pairs(unittypes) do - if squadron.aircrafttype==unittype then - gotit=true - break - end - end - if not gotit then - table.insert(unittypes, squadron.aircrafttype) - end - - end - end - - return unittypes -end - ---- Check if assets for a given mission type are available. --- @param #AIRWING self --- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @return #boolean If true, enough assets are available. --- @return #table Assets that can do the required mission. -function AIRWING:CanMission(Mission) - - -- Assume we CAN and NO assets are available. - local Can=true - local Assets={} - - -- Squadrons for the job. If user assigned to mission or simply all. - local squadrons=Mission.squadrons or self.squadrons - - -- Get aircraft unit types for the job. - local unittypes=self:GetAircraftTypes(true, squadrons) - - -- Count all payloads in stock. - local Npayloads=self:CountPayloadsInStock(Mission.type, unittypes, Mission.payloads) - - if Npayloads #Assets then - self:T(self.lid..string.format("INFO: Not enough assets available! Got %d but need at least %d", #Assets, Mission.nassets)) - Can=false - end - - return Can, Assets -end - ---- Check if assets for a given mission type are available. --- @param #AIRWING self --- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @return #table Assets that can do the required mission. -function AIRWING:RecruitAssets(Mission) - -end - - ---- Check if a mission type is contained in a list of possible types. --- @param #AIRWING self --- @param #string MissionType The requested mission type. --- @param #table PossibleTypes A table with possible mission types. --- @return #boolean If true, the requested mission type is part of the possible mission types. -function AIRWING:CheckMissionType(MissionType, PossibleTypes) - - if type(PossibleTypes)=="string" then - PossibleTypes={PossibleTypes} - end - - for _,canmission in pairs(PossibleTypes) do - if canmission==MissionType then - return true - end - end - - return false -end - ---- Check if a mission type is contained in a list of possible capabilities. --- @param #AIRWING self --- @param #string MissionType The requested mission type. --- @param #table Capabilities A table with possible capabilities. --- @return #boolean If true, the requested mission type is part of the possible mission types. -function AIRWING:CheckMissionCapability(MissionType, Capabilities) - - for _,cap in pairs(Capabilities) do - local capability=cap --Ops.Auftrag#AUFTRAG.Capability - if capability.MissionType==MissionType then - return true - end - end - - return false -end - --- Get payload performance for a given type of misson type. -- @param #AIRWING self -- @param #AIRWING.Payload Payload The payload table. @@ -2286,46 +1217,6 @@ function AIRWING:GetPayloadMissionTypes(Payload) return missiontypes end ---- Returns the mission for a given mission ID (Autragsnummer). --- @param #AIRWING self --- @param #number mid Mission ID (Auftragsnummer). --- @return Ops.Auftrag#AUFTRAG Mission table. -function AIRWING:GetMissionByID(mid) - - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - if mission.auftragsnummer==tonumber(mid) then - return mission - end - - end - - return nil -end - ---- Returns the mission for a given request ID. --- @param #AIRWING self --- @param #number RequestID Unique ID of the request. --- @return Ops.Auftrag#AUFTRAG Mission table or *nil*. -function AIRWING:GetMissionFromRequestID(RequestID) - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - if mission.requestID and mission.requestID==RequestID then - return mission - end - end - return nil -end - ---- Returns the mission for a given request. --- @param #AIRWING self --- @param Functional.Warehouse#WAREHOUSE.Queueitem Request The warehouse request. --- @return Ops.Auftrag#AUFTRAG Mission table or *nil*. -function AIRWING:GetMissionFromRequest(Request) - return self:GetMissionFromRequestID(Request.uid) -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index abda972be..2699cb668 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -443,7 +443,7 @@ AUFTRAG.TargetType={ -- @field Core.Point#COORDINATE wpegresscoordinate Egress waypoint coordinate. -- @field Ops.OpsGroup#OPSGROUP.Task waypointtask Waypoint task. -- @field #string status Group mission status. --- @field Ops.AirWing#AIRWING.SquadronAsset asset The squadron asset. +-- @field Functional.Warehouse#WAREHOUSE.Assetitem asset The warehouse asset. --- AUFTRAG class version. @@ -2689,7 +2689,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.AirWing#AIRWING.SquadronAsset Asset The asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset. function AUFTRAG:onafterAssetDead(From, Event, To, Asset) -- Number of groups alive. @@ -3147,7 +3147,7 @@ end --- Add asset to mission. -- @param #AUFTRAG self --- @param Ops.AirWing#AIRWING.SquadronAsset Asset The asset to be added to the mission. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset to be added to the mission. -- @return #AUFTRAG self function AUFTRAG:AddAsset(Asset) @@ -3160,12 +3160,12 @@ end --- Delete asset from mission. -- @param #AUFTRAG self --- @param Ops.AirWing#AIRWING.SquadronAsset Asset The asset to be removed. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset to be removed. -- @return #AUFTRAG self function AUFTRAG:DelAsset(Asset) for i,_asset in pairs(self.assets or {}) do - local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if asset.uid==Asset.uid then self:T(self.lid..string.format("Removing asset \"%s\" from mission", tostring(asset.spawngroupname))) @@ -3181,11 +3181,11 @@ end --- Get asset by its spawn group name. -- @param #AUFTRAG self -- @param #string Name Asset spawn group name. --- @return Ops.AirWing#AIRWING.SquadronAsset +-- @return Functional.Warehouse#WAREHOUSE.Assetitem Asset. function AUFTRAG:GetAssetByName(Name) for i,_asset in pairs(self.assets or {}) do - local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if asset.spawngroupname==Name then return asset @@ -3196,9 +3196,9 @@ function AUFTRAG:GetAssetByName(Name) return nil end ---- Count alive ops groups assigned for this mission. +--- Count alive OPS groups assigned for this mission. -- @param #AUFTRAG self --- @return #number Number of alive flight groups. +-- @return #number Number of alive OPS groups. function AUFTRAG:CountOpsGroups() local N=0 for _,_groupdata in pairs(self.groupdata) do diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index 98d611dcc..5a6c96c09 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -86,7 +86,7 @@ end -- @return #BRIGADE self function BRIGADE:AddPlatoon(Platoon) - -- Add squadron to airwing. + -- Add platoon to brigade. table.insert(self.cohorts, Platoon) -- Add assets to squadron. diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 6651e14a0..3b88595b1 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -119,9 +119,6 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) self:SetMissionRange() self:SetSkill(AI.Skill.GOOD) - -- Everyone can ORBIT. - --self:AddMissionCapability(AUFTRAG.Type.ORBIT) - -- Generalized attribute. self.attribute=self.templategroup:GetAttribute() @@ -718,9 +715,9 @@ function COHORT:CanMission(Mission) return false end - -- Check mission type. WARNING: This assumes that all assets of the squad can do the same mission types! + -- Check mission type. WARNING: This assumes that all assets of the cohort can do the same mission types! if not self:CheckMissionType(Mission.type, self:GetMissionTypes()) then - self:T(self.lid..string.format("INFO: Squad cannot do mission type %s (%s, %s)", Mission.type, Mission.name, Mission:GetTargetName())) + self:T(self.lid..string.format("INFO: Cohort cannot do mission type %s (%s, %s)", Mission.type, Mission.name, Mission:GetTargetName())) return false end @@ -742,9 +739,9 @@ function COHORT:CanMission(Mission) -- Max engage range. local engagerange=Mission.engageRange and math.max(self.engageRange, Mission.engageRange) or self.engageRange - -- Set range is valid. Mission engage distance can overrule the squad engage range. + -- Set range is valid. Mission engage distance can overrule the cohort engage range. if TargetDistance>engagerange then - self:I(self.lid..string.format("INFO: Squad is not in range. Target dist=%d > %d NM max mission Range", UTILS.MetersToNM(TargetDistance), UTILS.MetersToNM(engagerange))) + self:I(self.lid..string.format("INFO: Cohort is not in range. Target dist=%d > %d NM max mission Range", UTILS.MetersToNM(TargetDistance), UTILS.MetersToNM(engagerange))) return false end @@ -974,7 +971,7 @@ end --- Check if the platoon attribute matches the given attribute(s). -- @param #COHORT self -- @param #table Attributes The requested attributes. See `WAREHOUSE.Attribute` enum. Can also be passed as a single attribute `#string`. --- @return #boolean If true, the squad has the requested attribute. +-- @return #boolean If true, the cohort has the requested attribute. function COHORT:CheckAttribute(Attributes) if type(Attributes)~="table" then diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 1fa0c414b..2787b615a 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -44,8 +44,6 @@ -- @field #boolean fuelcritical Fuel critical switch. -- @field #number fuelcriticalthresh Critical fuel threshold in percent. -- @field #boolean fuelcriticalrtb RTB on critical fuel switch. --- @field Ops.Squadron#SQUADRON squadron The squadron of this flight group. --- @field Ops.AirWing#AIRWING airwing The airwing the flight group belongs to. -- @field Ops.FlightControl#FLIGHTCONTROL flightcontrol The flightcontrol handling this group. -- @field Ops.Airboss#AIRBOSS airboss The airboss handling this group. -- @field Core.UserFlag#USERFLAG flaghold Flag for holding. @@ -136,7 +134,6 @@ FLIGHTGROUP = { fuelcriticalrtb = false, outofAAMrtb = false, outofAGMrtb = false, - squadron = nil, flightcontrol = nil, flaghold = nil, Tholding = nil, @@ -336,21 +333,11 @@ function FLIGHTGROUP:AddTaskEnrouteEngageTargetsInZone(ZoneRadius, TargetTypes, self:AddTaskEnroute(Task) end ---- Set AIRWING the flight group belongs to. --- @param #FLIGHTGROUP self --- @param Ops.AirWing#AIRWING airwing The AIRWING object. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetAirwing(airwing) - self:T(self.lid..string.format("Add flight to AIRWING %s", airwing.alias)) - self.airwing=airwing - return self -end - --- Get airwing the flight group belongs to. -- @param #FLIGHTGROUP self -- @return Ops.AirWing#AIRWING The AIRWING object. function FLIGHTGROUP:GetAirWing() - return self.airwing + return self.legion end --- Set if aircraft is VTOL capable. Unfortunately, there is no DCS way to determine this via scripting. @@ -1891,11 +1878,15 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) -- Add flight to arrived queue. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.ARRIVED) end + + local airwing=self:GetAirWing() -- Check what to do. - if self.airwing then + if airwing then + -- Add the asset back to the airwing. - self.airwing:AddAsset(self.group, 1) + airwing:AddAsset(self.group, 1) + elseif self.isLandingAtAirbase then local Template=UTILS.DeepCopy(self.template) --DCS#Template @@ -1991,18 +1982,6 @@ function FLIGHTGROUP:onafterDead(From, Event, To) self.flightcontrol=nil end - if self.Ndestroyed==#self.elements then - if self.squadron then - -- All elements were destroyed ==> Asset group is gone. - self.squadron:DelGroup(self.groupname) - end - else - if self.airwing then - -- Not all assets were destroyed (despawn) ==> Add asset back to airwing. - --self.airwing:AddAsset(self.group, 1) - end - end - -- Call OPSGROUP function. self:GetParent(self).onafterDead(self, From, Event, To) @@ -2912,11 +2891,13 @@ function FLIGHTGROUP:onafterFuelLow(From, Event, To) -- Back to destination or home. local airbase=self.destbase or self.homebase + + local airwing=self:GetAirWing() - if self.airwing then + if airwing then -- Get closest tanker from airwing that can refuel this flight. - local tanker=self.airwing:GetTankerForFlight(self) + local tanker=airwing:GetTankerForFlight(self) if tanker and self.fuellowrefuel then diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 098aaecc9..b7f31dfbb 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -616,7 +616,7 @@ function LEGION:onafterOpsOnMission(From, Event, To, OpsGroup, Mission) end ---- On after "NewAsset" event. Asset is added to the given squadron (asset assignment). +--- On after "NewAsset" event. Asset is added to the given cohort (asset assignment). -- @param #LEGION self -- @param #string From From state. -- @param #string Event Event. @@ -632,26 +632,25 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) local text=string.format("New asset %s with assignment %s and request assignment %s", asset.spawngroupname, tostring(asset.assignment), tostring(assignment)) self:T3(self.lid..text) - -- Get squadron. - --local squad=self:GetSquadron(asset.assignment) - local squad=self:_GetCohort(asset.assignment) + -- Get cohort. + local cohort=self:_GetCohort(asset.assignment) -- Check if asset is already part of the squadron. If an asset returns, it will be added again! We check that asset.assignment is also assignment. - if squad then + if cohort then if asset.assignment==assignment then local nunits=#asset.template.units -- Debug text. - local text=string.format("Adding asset to squadron %s: assignment=%s, type=%s, attribute=%s, nunits=%d %s", squad.name, assignment, asset.unittype, asset.attribute, nunits, tostring(squad.ngrouping)) + local text=string.format("Adding asset to squadron %s: assignment=%s, type=%s, attribute=%s, nunits=%d %s", cohort.name, assignment, asset.unittype, asset.attribute, nunits, tostring(cohort.ngrouping)) self:T(self.lid..text) -- Adjust number of elements in the group. - if squad.ngrouping then + if cohort.ngrouping then local template=asset.template - local N=math.max(#template.units, squad.ngrouping) + local N=math.max(#template.units, cohort.ngrouping) -- Handle units. for i=1,N do @@ -665,36 +664,36 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) end -- Remove units if original template contains more than in grouping. - if squad.ngroupingnunits then + if cohort.ngroupingnunits then unit=nil end end - asset.nunits=squad.ngrouping + asset.nunits=cohort.ngrouping end -- Set takeoff type. - asset.takeoffType=squad.takeoffType + asset.takeoffType=cohort.takeoffType -- Set parking IDs. - asset.parkingIDs=squad.parkingIDs + asset.parkingIDs=cohort.parkingIDs -- Create callsign and modex (needs to be after grouping). - squad:GetCallsign(asset) - squad:GetModex(asset) + cohort:GetCallsign(asset) + cohort:GetModex(asset) -- Set spawn group name. This has to include "AID-" for warehouse. - asset.spawngroupname=string.format("%s_AID-%d", squad.name, asset.uid) + asset.spawngroupname=string.format("%s_AID-%d", cohort.name, asset.uid) -- Add asset to squadron. - squad:AddAsset(asset) + cohort:AddAsset(asset) -- TODO --asset.terminalType=AIRBASE.TerminalType.OpenBig else --env.info("FF squad asset returned") - self:AssetReturned(squad, asset) + self:AssetReturned(cohort, asset) end @@ -753,10 +752,10 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) self:GetParent(self, LEGION).onafterAssetSpawned(self, From, Event, To, group, asset, request) -- Get the SQUADRON of the asset. - local squadron=self:_GetCohortOfAsset(asset) + local cohort=self:_GetCohortOfAsset(asset) -- Check if we have a squadron or if this was some other request. - if squadron then + if cohort then -- Create a flight group. local flightgroup=self:_CreateFlightGroup(asset) @@ -779,7 +778,7 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) --- -- Get TACAN channel. - local Tacan=squadron:FetchTacan() + local Tacan=cohort:FetchTacan() if Tacan then asset.tacan=Tacan --flightgroup:SetDefaultTACAN(Tacan,Morse,UnitName,Band,OffSwitch) @@ -787,17 +786,17 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) end -- Set radio frequency and modulation - local radioFreq, radioModu=squadron:GetRadio() + local radioFreq, radioModu=cohort:GetRadio() if radioFreq then flightgroup:SwitchRadio(radioFreq, radioModu) end - if squadron.fuellow then - flightgroup:SetFuelLowThreshold(squadron.fuellow) + if cohort.fuellow then + flightgroup:SetFuelLowThreshold(cohort.fuellow) end - if squadron.fuellowRefuel then - flightgroup:SetFuelLowRefuel(squadron.fuellowRefuel) + if cohort.fuellowRefuel then + flightgroup:SetFuelLowRefuel(cohort.fuellowRefuel) end --- @@ -858,7 +857,7 @@ function LEGION:onafterAssetDead(From, Event, To, asset, request) -- Remove asset from squadron same end ---- On after "Destroyed" event. Remove assets from squadrons. Stop squadrons. Remove airwing from wingcommander. +--- On after "Destroyed" event. Remove assets from cohorts. Stop squadrons. -- @param #LEGION self -- @param #string From From state. -- @param #string Event Event. @@ -874,11 +873,11 @@ function LEGION:onafterDestroyed(From, Event, To) mission:Cancel() end - -- Remove all squadron assets. - for _,_squadron in pairs(self.cohorts) do - local squadron=_squadron --Ops.Squadron#SQUADRON - -- Stop Squadron. This also removes all assets. - squadron:Stop() + -- Remove all cohort assets. + for _,_cohort in pairs(self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + -- Stop Cohort. This also removes all assets. + cohort:Stop() end -- Call parent warehouse function first. @@ -964,7 +963,7 @@ function LEGION:_CreateFlightGroup(asset) flightgroup:_SetLegion(self) -- Set squadron. - flightgroup.squadron=self:_GetCohortOfAsset(asset) + flightgroup.cohort=self:_GetCohortOfAsset(asset) -- Set home base. flightgroup.homebase=self.airbase diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8f489f430..1a1e38ddd 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1329,7 +1329,9 @@ function OPSGROUP:DespawnElement(Element, Delay, NoEventRemoveUnit) return self end ---- Despawn the group. The whole group is despawned and (optionally) a "Remove Unit" event is generated for all current units of the group. +--- Despawn the group. The whole group is despawned and a "`Remove Unit`" event is generated for all current units of the group. +-- If no `Remove Unit` event should be generated, the second optional parameter needs to be set to `true`. +-- If this group belongs to an AIRWING, BRIGADE or FLEET, it will be added to the warehouse stock if the `NoEventRemoveUnit` parameter is `false` or `nil`. -- @param #OPSGROUP self -- @param #number Delay Delay in seconds before the group will be despawned. Default immediately. -- @param #boolean NoEventRemoveUnit If `true`, **no** event "Remove Unit" is generated. @@ -1339,15 +1341,17 @@ function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) if Delay and Delay>0 then self.scheduleIDDespawn=self:ScheduleOnce(Delay, OPSGROUP.Despawn, self, 0, NoEventRemoveUnit) else - - -- Debug info. - self:I(self.lid..string.format("Despawning Group!")) - + if self.legion and not NoEventRemoveUnit then -- Add asset back in 10 seconds. - self.legion:AddAsset(self.group, 1) + self:I(self.lid..string.format("Despawning Group by adding asset to LEGION!")) + self.legion:AddAsset(self.group, 1) + return end + -- Debug info. + self:I(self.lid..string.format("Despawning Group!")) + -- DCS group obejct. local DCSGroup=self:GetDCSGroup() @@ -3754,10 +3758,10 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) -- Switch to default. self:_SwitchTACAN() - -- Return Squadron TACAN channel. - local squadron=self.squadron --Ops.Squadron#SQUADRON - if squadron then - squadron:ReturnTacan(Mission.tacan.Channel) + -- Return Cohort's TACAN channel. + local cohort=self.cohort --Ops.Cohort#COHORT + if cohort then + cohort:ReturnTacan(Mission.tacan.Channel) end -- Set asset TACAN to nil. @@ -5174,6 +5178,18 @@ function OPSGROUP:onafterDead(From, Event, To) -- No current cargo transport. self.cargoTransport=nil + + if self.Ndestroyed==#self.elements then + if self.cohort then + -- All elements were destroyed ==> Asset group is gone. + self.cohort:DelGroup(self.groupname) + end + else + if self.airwing then + -- Not all assets were destroyed (despawn) ==> Add asset back to airwing. + --self.airwing:AddAsset(self.group, 1) + end + end -- Stop in a sec. --self:__Stop(-5) diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 74cd5cc31..a86d91f43 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -36,7 +36,6 @@ -- @field #number modexcounter Counter to incease modex number for assets. -- @field #string callsignName Callsign name. -- @field #number callsigncounter Counter to increase callsign names for new assets. --- @field Ops.AirWing#AIRWING airwing The AIRWING object the squadron belongs to. -- @field #number Ngroups Number of asset flight groups this squadron has. -- @field #number engageRange Mission range in meters. -- @field #string attribute Generalized attribute of the squadron template group. @@ -47,7 +46,7 @@ -- @field #number radioModu Radio modulation the squad uses. -- @field #number takeoffType Take of type. -- @field #table parkingIDs Parking IDs for this squadron. --- @extends Core.Fsm#FSM +-- @extends Ops.Cohort#COHORT --- *It is unbelievable what a squadron of twelve aircraft did to tip the balance.* -- Adolf Galland -- @@ -65,31 +64,17 @@ SQUADRON = { ClassName = "SQUADRON", verbose = 0, - lid = nil, - name = nil, - templatename = nil, - aircrafttype = nil, - assets = {}, - missiontypes = {}, - repairtime = 0, - maintenancetime= 0, - livery = nil, - skill = nil, modex = nil, modexcounter = 0, callsignName = nil, callsigncounter= 11, - airwing = nil, - Ngroups = nil, - engageRange = nil, tankerSystem = nil, refuelSystem = nil, - tacanChannel = {}, } --- SQUADRON class version. -- @field #string version -SQUADRON.version="0.7.0" +SQUADRON.version="0.8.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -113,37 +98,11 @@ SQUADRON.version="0.7.0" function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName) -- Inherit everything from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #SQUADRON + local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, SquadronName)) -- #SQUADRON - -- Name of the template group. - self.templatename=TemplateGroupName - - -- Squadron name. - self.name=tostring(SquadronName or TemplateGroupName) - - -- Set some string id for output to DCS.log file. - self.lid=string.format("SQUADRON %s | ", self.name) - - -- Template group. - self.templategroup=GROUP:FindByName(self.templatename) - - -- Check if template group exists. - if not self.templategroup then - 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) - -- Everyone can ORBIT. self:AddMissionCapability(AUFTRAG.Type.ORBIT) - -- Generalized attribute. - self.attribute=self.templategroup:GetAttribute() - -- Aircraft type. self.aircrafttype=self.templategroup:GetTypeName() @@ -151,58 +110,11 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName) self.refuelSystem=select(2, self.templategroup:GetUnit(1):IsRefuelable()) self.tankerSystem=select(2, self.templategroup:GetUnit(1):IsTanker()) - - -- Start State. - self:SetStartState("Stopped") - - -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("Stopped", "Start", "OnDuty") -- Start FSM. - self:AddTransition("*", "Status", "*") -- Status update. - - self:AddTransition("OnDuty", "Pause", "Paused") -- Pause squadron. - self:AddTransition("Paused", "Unpause", "OnDuty") -- Unpause squadron. - - self:AddTransition("*", "Stop", "Stopped") -- Stop squadron. - - ------------------------ --- Pseudo Functions --- ------------------------ - --- Triggers the FSM event "Start". Starts the SQUADRON. Initializes parameters and starts event handlers. - -- @function [parent=#SQUADRON] Start - -- @param #SQUADRON self - - --- Triggers the FSM event "Start" after a delay. Starts the SQUADRON. Initializes parameters and starts event handlers. - -- @function [parent=#SQUADRON] __Start - -- @param #SQUADRON self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Stop". Stops the SQUADRON and all its event handlers. - -- @param #SQUADRON self - - --- Triggers the FSM event "Stop" after a delay. Stops the SQUADRON and all its event handlers. - -- @function [parent=#SQUADRON] __Stop - -- @param #SQUADRON self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Status". - -- @function [parent=#SQUADRON] Status - -- @param #SQUADRON self - - --- Triggers the FSM event "Status" after a delay. - -- @function [parent=#SQUADRON] __Status - -- @param #SQUADRON self - -- @param #number delay Delay in seconds. - - - -- Debug trace. - if false then - BASE:TraceOnOff(true) - BASE:TraceClass(self.ClassName) - BASE:TraceLevel(1) - end + -- See COHORT class return self end @@ -211,68 +123,6 @@ end -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Set livery painted on all squadron aircraft. --- Note that the livery name in general is different from the name shown in the mission editor. --- --- Valid names are the names of the **livery directories**. Check out the folder in your DCS installation for: --- --- * Full modules: `DCS World OpenBeta\CoreMods\aircraft\\Liveries\\` --- * AI units: `DCS World OpenBeta\Bazar\Liveries\\` --- --- The folder name `` is the string you want. --- --- Or personal liveries you have installed somewhere in your saved games folder. --- --- @param #SQUADRON self --- @param #string LiveryName Name of the livery. --- @return #SQUADRON self -function SQUADRON:SetLivery(LiveryName) - self.livery=LiveryName - return self -end - ---- Set skill level of all squadron team members. --- @param #SQUADRON self --- @param #string Skill Skill of all flights. --- @usage mysquadron:SetSkill(AI.Skill.EXCELLENT) --- @return #SQUADRON self -function SQUADRON:SetSkill(Skill) - self.skill=Skill - return self -end - ---- Set verbosity level. --- @param #SQUADRON self --- @param #number VerbosityLevel Level of output (higher=more). Default 0. --- @return #SQUADRON self -function SQUADRON:SetVerbosity(VerbosityLevel) - self.verbose=VerbosityLevel or 0 - return self -end - ---- Set turnover and repair time. If an asset returns from a mission to the airwing, it will need some time until the asset is available for further missions. --- @param #SQUADRON self --- @param #number MaintenanceTime Time in minutes it takes until a flight is combat ready again. Default is 0 min. --- @param #number RepairTime Time in minutes it takes to repair a flight for each life point taken. Default is 0 min. --- @return #SQUADRON self -function SQUADRON:SetTurnoverTime(MaintenanceTime, RepairTime) - self.maintenancetime=MaintenanceTime and MaintenanceTime*60 or 0 - self.repairtime=RepairTime and RepairTime*60 or 0 - return self -end - ---- Set radio frequency and modulation the squad uses. --- @param #SQUADRON self --- @param #number Frequency Radio frequency in MHz. Default 251 MHz. --- @param #number Modulation Radio modulation. Default 0=AM. --- @usage mysquadron:SetSkill(AI.Skill.EXCELLENT) --- @return #SQUADRON self -function SQUADRON:SetRadio(Frequency, Modulation) - self.radioFreq=Frequency or 251 - self.radioModu=Modulation or radio.modulation.AM - return self -end - --- Set number of units in groups. -- @param #SQUADRON self -- @param #number nunits Number of units. Must be >=1 and <=4. Default 2. @@ -330,115 +180,6 @@ function SQUADRON:SetTakeoffHot() return self end - ---- Set mission types this squadron is able to perform. --- @param #SQUADRON self --- @param #table MissionTypes Table of mission types. Can also be passed as a #string if only one type. --- @param #number Performance Performance describing how good this mission can be performed. Higher is better. Default 50. Max 100. --- @return #SQUADRON self -function SQUADRON:AddMissionCapability(MissionTypes, Performance) - - -- Ensure Missiontypes is a table. - if MissionTypes and type(MissionTypes)~="table" then - MissionTypes={MissionTypes} - end - - -- Set table. - self.missiontypes=self.missiontypes or {} - - for _,missiontype in pairs(MissionTypes) do - - -- Check not to add the same twice. - if self:CheckMissionCapability(missiontype, self.missiontypes) then - self:E(self.lid.."WARNING: Mission capability already present! No need to add it twice.") - -- TODO: update performance. - else - - local capability={} --Ops.Auftrag#AUFTRAG.Capability - capability.MissionType=missiontype - capability.Performance=Performance or 50 - table.insert(self.missiontypes, capability) - - end - end - - -- Debug info. - self:T2(self.missiontypes) - - return self -end - ---- Get mission types this squadron is able to perform. --- @param #SQUADRON self --- @return #table Table of mission types. Could be empty {}. -function SQUADRON:GetMissionTypes() - - local missiontypes={} - - for _,Capability in pairs(self.missiontypes) do - local capability=Capability --Ops.Auftrag#AUFTRAG.Capability - table.insert(missiontypes, capability.MissionType) - end - - return missiontypes -end - ---- Get mission capabilities of this squadron. --- @param #SQUADRON self --- @return #table Table of mission capabilities. -function SQUADRON:GetMissionCapabilities() - return self.missiontypes -end - ---- Get mission performance for a given type of misson. --- @param #SQUADRON self --- @param #string MissionType Type of mission. --- @return #number Performance or -1. -function SQUADRON:GetMissionPeformance(MissionType) - - for _,Capability in pairs(self.missiontypes) do - local capability=Capability --Ops.Auftrag#AUFTRAG.Capability - if capability.MissionType==MissionType then - return capability.Performance - end - end - - return -1 -end - ---- Set max mission range. Only missions in a circle of this radius around the squadron airbase are executed. --- @param #SQUADRON self --- @param #number Range Range in NM. Default 100 NM. --- @return #SQUADRON self -function SQUADRON:SetMissionRange(Range) - self.engageRange=UTILS.NMToMeters(Range or 100) - return self -end - ---- Set call sign. --- @param #SQUADRON self --- @param #number Callsign Callsign from CALLSIGN.Aircraft, e.g. "Chevy" for CALLSIGN.Aircraft.CHEVY. --- @param #number Index Callsign index, Chevy-**1**. --- @return #SQUADRON self -function SQUADRON:SetCallsign(Callsign, Index) - self.callsignName=Callsign - self.callsignIndex=Index - return self -end - ---- Set modex. --- @param #SQUADRON self --- @param #number Modex A number like 100. --- @param #string Prefix A prefix string, which is put before the `Modex` number. --- @param #string Suffix A suffix string, which is put after the `Modex` number. --- @return #SQUADRON self -function SQUADRON:SetModex(Modex, Prefix, Suffix) - self.modex=Modex - self.modexPrefix=Prefix - self.modexSuffix=Suffix - return self -end - --- Set low fuel threshold. -- @param #SQUADRON self -- @param #number LowFuel Low fuel threshold in percent. Default 25. @@ -466,208 +207,17 @@ end -- @param Ops.AirWing#AIRWING Airwing The airwing. -- @return #SQUADRON self function SQUADRON:SetAirwing(Airwing) - self.airwing=Airwing + self.legion=Airwing return self end ---- Add airwing asset to squadron. +--- Get airwing. -- @param #SQUADRON self --- @param Ops.AirWing#AIRWING.SquadronAsset Asset The airwing asset. --- @return #SQUADRON self -function SQUADRON:AddAsset(Asset) - self:T(self.lid..string.format("Adding asset %s of type %s", Asset.spawngroupname, Asset.unittype)) - Asset.squadname=self.name - table.insert(self.assets, Asset) - return self +-- @return Ops.AirWing#AIRWING The airwing. +function SQUADRON:GetAirwing(Airwing) + return self.legion end ---- Remove airwing asset from squadron. --- @param #SQUADRON self --- @param Ops.AirWing#AIRWING.SquadronAsset Asset The airwing asset. --- @return #SQUADRON self -function SQUADRON:DelAsset(Asset) - for i,_asset in pairs(self.assets) do - local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset - if Asset.uid==asset.uid then - self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname)) - table.remove(self.assets, i) - break - end - end - return self -end - ---- Remove airwing asset group from squadron. --- @param #SQUADRON self --- @param #string GroupName Name of the asset group. --- @return #SQUADRON self -function SQUADRON:DelGroup(GroupName) - for i,_asset in pairs(self.assets) do - local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset - if GroupName==asset.spawngroupname then - self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname)) - table.remove(self.assets, i) - break - end - end - return self -end - ---- Get name of the squadron --- @param #SQUADRON self --- @return #string Name of the squadron. -function SQUADRON:GetName() - return self.name -end - ---- Get radio frequency and modulation. --- @param #SQUADRON self --- @return #number Radio frequency in MHz. --- @return #number Radio Modulation (0=AM, 1=FM). -function SQUADRON:GetRadio() - return self.radioFreq, self.radioModu -end - ---- Create a callsign for the asset. --- @param #SQUADRON self --- @param Ops.AirWing#AIRWING.SquadronAsset Asset The airwing asset. --- @return #SQUADRON self -function SQUADRON:GetCallsign(Asset) - - if self.callsignName then - - Asset.callsign={} - - for i=1,Asset.nunits do - - local callsign={} - - callsign[1]=self.callsignName - callsign[2]=math.floor(self.callsigncounter / 10) - callsign[3]=self.callsigncounter % 10 - if callsign[3]==0 then - callsign[3]=1 - self.callsigncounter=self.callsigncounter+2 - else - self.callsigncounter=self.callsigncounter+1 - end - - Asset.callsign[i]=callsign - - self:T3({callsign=callsign}) - - --TODO: there is also a table entry .name, which is a string. - end - - - end - -end - ---- Create a modex for the asset. --- @param #SQUADRON self --- @param Ops.AirWing#AIRWING.SquadronAsset Asset The airwing asset. --- @return #SQUADRON self -function SQUADRON:GetModex(Asset) - - if self.modex then - - Asset.modex={} - - for i=1,Asset.nunits do - - Asset.modex[i]=string.format("%03d", self.modex+self.modexcounter) - - self.modexcounter=self.modexcounter+1 - - self:T3({modex=Asset.modex[i]}) - - end - - end - -end - - ---- Add TACAN channels to the squadron. Note that channels can only range from 1 to 126. --- @param #SQUADRON self --- @param #number ChannelMin Channel. --- @param #number ChannelMax Channel. --- @return #SQUADRON self --- @usage mysquad:AddTacanChannel(64,69) -- adds channels 64, 65, 66, 67, 68, 69 -function SQUADRON:AddTacanChannel(ChannelMin, ChannelMax) - - ChannelMax=ChannelMax or ChannelMin - - if ChannelMin>126 then - self:E(self.lid.."ERROR: TACAN Channel must be <= 126! Will not add to available channels") - return self - end - if ChannelMax>126 then - self:E(self.lid.."WARNING: TACAN Channel must be <= 126! Adjusting ChannelMax to 126") - ChannelMax=126 - end - - for i=ChannelMin,ChannelMax do - self.tacanChannel[i]=true - end - - return self -end - ---- Get an unused TACAN channel. --- @param #SQUADRON self --- @return #number TACAN channel or *nil* if no channel is free. -function SQUADRON:FetchTacan() - - -- Get the smallest free channel if there is one. - local freechannel=nil - for channel,free in pairs(self.tacanChannel) do - if free then - if freechannel==nil or channel=2 and #self.assets>0 then - - local text="" - for j,_asset in pairs(self.assets) do - local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset - - -- Text. - text=text..string.format("\n[%d] %s (%s*%d): ", j, asset.spawngroupname, asset.unittype, asset.nunits) - - if asset.spawned then - - --- - -- Spawned - --- - - -- Mission info. - local mission=self.airwing and self.airwing:GetAssetCurrentMission(asset) or false - if mission then - local distance=asset.flightgroup and UTILS.MetersToNM(mission:GetTargetDistance(asset.flightgroup.group:GetCoordinate())) or 0 - text=text..string.format("Mission %s - %s: Status=%s, Dist=%.1f NM", mission.name, mission.type, mission.status, distance) - else - text=text.."Mission None" - end - - -- Flight status. - text=text..", Flight: " - if asset.flightgroup and asset.flightgroup:IsAlive() then - local status=asset.flightgroup:GetState() - local fuelmin=asset.flightgroup:GetFuelMin() - local fuellow=asset.flightgroup:IsFuelLow() - local fuelcri=asset.flightgroup:IsFuelCritical() - - text=text..string.format("%s Fuel=%d", status, fuelmin) - if fuelcri then - text=text.." (Critical!)" - elseif fuellow then - text=text.." (Low)" - end - - local lifept, lifept0=asset.flightgroup:GetLifePoints() - text=text..string.format(", Life=%d/%d", lifept, lifept0) - - local ammo=asset.flightgroup:GetAmmoTot() - text=text..string.format(", Ammo=%d [G=%d, R=%d, B=%d, M=%d]", ammo.Total,ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles) - else - text=text.."N/A" - end - - -- Payload info. - local payload=asset.payload and table.concat(self.airwing:GetPayloadMissionTypes(asset.payload), ", ") or "None" - text=text..", Payload={"..payload.."}" - - else - - --- - -- In Stock - --- - - text=text..string.format("In Stock") - - if self:IsRepaired(asset) then - text=text..", Combat Ready" - else - - text=text..string.format(", Repaired in %d sec", self:GetRepairTime(asset)) - - if asset.damage then - text=text..string.format(" (Damage=%.1f)", asset.damage) - end - end - - if asset.Treturned then - local T=timer.getAbsTime()-asset.Treturned - text=text..string.format(", Returned for %d sec", T) - end - - end - end - self:I(self.lid..text) - end - -end - ---- On after "Stop" event. --- @param #SQUADRON self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function SQUADRON:onafterStop(From, Event, To) - - -- Debug info. - self:I(self.lid.."STOPPING Squadron and removing all assets!") - - -- Remove all assets. - for i=#self.assets,1,-1 do - local asset=self.assets[i] - self:DelAsset(asset) - end - - -- Clear call scheduler. - self.CallScheduler:Clear() - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Check if there is a squadron that can execute a given mission. --- We check the mission type, the refuelling system, engagement range --- @param #SQUADRON self --- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @return #boolean If true, Squadron can do that type of mission. -function SQUADRON:CanMission(Mission) - - local cando=true - - -- On duty?= - if not self:IsOnDuty() then - self:T(self.lid..string.format("Squad in not OnDuty but in state %s. Cannot do mission %s with target %s", self:GetState(), Mission.name, Mission:GetTargetName())) - return false - end - - -- Check mission type. WARNING: This assumes that all assets of the squad can do the same mission types! - if not self:CheckMissionType(Mission.type, self:GetMissionTypes()) then - self:T(self.lid..string.format("INFO: Squad cannot do mission type %s (%s, %s)", Mission.type, Mission.name, Mission:GetTargetName())) - return false - end - - -- Check that tanker mission - if Mission.type==AUFTRAG.Type.TANKER then - - if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then - -- Correct refueling system. - else - self:T(self.lid..string.format("INFO: Wrong refueling system requested=%s != %s=available", tostring(Mission.refuelSystem), tostring(self.tankerSystem))) - return false - end - - end - - -- Distance to target. - local TargetDistance=Mission:GetTargetDistance(self.airwing:GetCoordinate()) - - -- Max engage range. - local engagerange=Mission.engageRange and math.max(self.engageRange, Mission.engageRange) or self.engageRange - - -- Set range is valid. Mission engage distance can overrule the squad engage range. - if TargetDistance>engagerange then - self:I(self.lid..string.format("INFO: Squad is not in range. Target dist=%d > %d NM max mission Range", UTILS.MetersToNM(TargetDistance), UTILS.MetersToNM(engagerange))) - return false - end - - return true -end - ---- Count assets in airwing (warehous) stock. --- @param #SQUADRON self --- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. --- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. --- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. --- @return #number Number of assets. -function SQUADRON:CountAssets(InStock, MissionTypes, Attributes) - - local N=0 - for _,_asset in pairs(self.assets) do - local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset - - if MissionTypes==nil or self:CheckMissionCapability(MissionTypes, self.missiontypes) then - if Attributes==nil or self:CheckAttribute(Attributes) then - if asset.spawned then - if not InStock then - N=N+1 --Spawned but we also count the spawned ones. - end - else - N=N+1 --This is in stock. - end - end - end - end - - return N -end - ---- Get assets for a mission. --- @param #SQUADRON self --- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @param #number Nplayloads Number of payloads available. --- @return #table Assets that can do the required mission. -function SQUADRON:RecruitAssets(Mission, Npayloads) - - -- Number of payloads available. - Npayloads=Npayloads or self.airwing:CountPayloadsInStock(Mission.type, self.aircrafttype, Mission.payloads) - - local assets={} - - -- Loop over assets. - for _,_asset in pairs(self.assets) do - local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset - - - -- Check if asset is currently on a mission (STARTED or QUEUED). - if self.airwing:IsAssetOnMission(asset) then - - --- - -- Asset is already on a mission. - --- - - -- Check if this asset is currently on a GCICAP mission (STARTED or EXECUTING). - if self.airwing:IsAssetOnMission(asset, AUFTRAG.Type.GCICAP) and Mission.type==AUFTRAG.Type.INTERCEPT then - - -- Check if the payload of this asset is compatible with the mission. - -- Note: we do not check the payload as an asset that is on a GCICAP mission should be able to do an INTERCEPT as well! - self:I(self.lid.."Adding asset on GCICAP mission for an INTERCEPT mission") - table.insert(assets, asset) - - end - - else - - --- - -- Asset as NO current mission - --- - - if asset.spawned then - - --- - -- Asset is already SPAWNED (could be uncontrolled on the airfield or inbound after another mission) - --- - - local flightgroup=asset.flightgroup - - -- Firstly, check if it has the right payload. - if self:CheckMissionCapability(Mission.type, asset.payload.capabilities) and flightgroup and flightgroup:IsAlive() then - - -- Assume we are ready and check if any condition tells us we are not. - local combatready=true - - if Mission.type==AUFTRAG.Type.INTERCEPT then - combatready=flightgroup:CanAirToAir() - else - local excludeguns=Mission.type==AUFTRAG.Type.BOMBING or Mission.type==AUFTRAG.Type.BOMBRUNWAY or Mission.type==AUFTRAG.Type.BOMBCARPET or Mission.type==AUFTRAG.Type.SEAD or Mission.type==AUFTRAG.Type.ANTISHIP - combatready=flightgroup:CanAirToGround(excludeguns) - end - - -- No more attacks if fuel is already low. Safety first! - if flightgroup:IsFuelLow() then - combatready=false - end - - -- Check if in a state where we really do not want to fight any more. - if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() or flightgroup:IsDead() or flightgroup:IsStopped() then - combatready=false - end - - -- This asset is "combatready". - if combatready then - self:I(self.lid.."Adding SPAWNED asset to ANOTHER mission as it is COMBATREADY") - table.insert(assets, asset) - end - - end - - else - - --- - -- Asset is still in STOCK - --- - - -- Check that asset is not already requested for another mission. - if Npayloads>0 and self:IsRepaired(asset) and (not asset.requested) then - - -- Add this asset to the selection. - table.insert(assets, asset) - - -- Reduce number of payloads so we only return the number of assets that could do the job. - Npayloads=Npayloads-1 - - end - - end - end - end -- loop over assets - - return assets -end - - ---- Get the time an asset needs to be repaired. --- @param #SQUADRON self --- @param Ops.AirWing#AIRWING.SquadronAsset Asset The asset. --- @return #number Time in seconds until asset is repaired. -function SQUADRON:GetRepairTime(Asset) - - if Asset.Treturned then - - local t=self.maintenancetime - t=t+Asset.damage*self.repairtime - - -- Seconds after returned. - local dt=timer.getAbsTime()-Asset.Treturned - - local T=t-dt - - return T - else - return 0 - end - -end - ---- Checks if a mission type is contained in a table of possible types. --- @param #SQUADRON self --- @param Ops.AirWing#AIRWING.SquadronAsset Asset The asset. --- @return #boolean If true, the requested mission type is part of the possible mission types. -function SQUADRON:IsRepaired(Asset) - - if Asset.Treturned then - local Tnow=timer.getAbsTime() - local Trepaired=Asset.Treturned+self.maintenancetime - if Tnow>=Trepaired then - return true - else - return false - end - - else - return true - end - -end - - ---- Checks if a mission type is contained in a table of possible types. --- @param #SQUADRON self --- @param #string MissionType The requested mission type. --- @param #table PossibleTypes A table with possible mission types. --- @return #boolean If true, the requested mission type is part of the possible mission types. -function SQUADRON:CheckMissionType(MissionType, PossibleTypes) - - if type(PossibleTypes)=="string" then - PossibleTypes={PossibleTypes} - end - - for _,canmission in pairs(PossibleTypes) do - if canmission==MissionType then - return true - end - end - - return false -end - ---- Check if a mission type is contained in a list of possible capabilities. --- @param #SQUADRON self --- @param #table MissionTypes The requested mission type. Can also be passed as a single mission type `#string`. --- @param #table Capabilities A table with possible capabilities. --- @return #boolean If true, the requested mission type is part of the possible mission types. -function SQUADRON:CheckMissionCapability(MissionTypes, Capabilities) - - if type(MissionTypes)~="table" then - MissionTypes={MissionTypes} - end - - for _,cap in pairs(Capabilities) do - local capability=cap --Ops.Auftrag#AUFTRAG.Capability - for _,MissionType in pairs(MissionTypes) do - if capability.MissionType==MissionType then - return true - end - end - end - - return false -end - ---- Check if the squadron attribute matches the given attribute(s). --- @param #SQUADRON self --- @param #table Attributes The requested attributes. See `WAREHOUSE.Attribute` enum. Can also be passed as a single attribute `#string`. --- @return #boolean If true, the squad has the requested attribute. -function SQUADRON:CheckAttribute(Attributes) - - if type(Attributes)~="table" then - Attributes={Attributes} - end - - for _,attribute in pairs(Attributes) do - if attribute==self.attribute then - return true - end - end - - return false -end - - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From b21c3ed4e93afe54be9f9e30f2e9e1e136df9c02 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 12 Aug 2021 11:07:28 +0200 Subject: [PATCH 071/141] OPS Cleanup --- Moose Development/Moose/Ops/AirWing.lua | 13 +---- Moose Development/Moose/Ops/Auftrag.lua | 24 +++++++-- Moose Development/Moose/Ops/Brigade.lua | 8 ++- Moose Development/Moose/Ops/Cohort.lua | 65 +++++++++++----------- Moose Development/Moose/Ops/Legion.lua | 68 ++++++++++++------------ Moose Development/Moose/Ops/Squadron.lua | 2 +- 6 files changed, 92 insertions(+), 88 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 169cd0bce..f61bf12d8 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -557,17 +557,8 @@ end -- @param #string SquadronName Name of the squadron, e.g. "VFA-37". -- @return Ops.Squadron#SQUADRON The squadron object. function AIRWING:GetSquadron(SquadronName) - - for _,_squadron in pairs(self.cohorts) do - local squadron=_squadron --Ops.Squadron#SQUADRON - - if squadron.name==SquadronName then - return squadron - end - - end - - return nil + local squad=self:_GetCohort(SquadronName) + return squad end --- Get squadron of an asset. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 2699cb668..8a6ba8098 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -454,12 +454,12 @@ AUFTRAG.version="0.7.1" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- DONE: Option to assign a specific payload for the mission (requires an AIRWING). -- TODO: Mission success options damaged, destroyed. --- TODO: Recon mission. What input? Set of coordinates? --- NOPE: Clone mission. How? Deepcopy? ==> Create a new auftrag. -- TODO: F10 marker to create new missions. -- TODO: Add recovery tanker mission for boat ops. +-- DONE: Option to assign a specific payload for the mission (requires an AIRWING). +-- NOPE: Clone mission. How? Deepcopy? ==> Create a new auftrag. +-- DONE: Recon mission. What input? Set of coordinates? -- DONE: Option to assign mission to specific squadrons (requires an AIRWING). -- DONE: Add mission start conditions. -- DONE: Add rescue helo mission for boat ops. @@ -1877,6 +1877,21 @@ function AUFTRAG:AddConditionPush(ConditionFunction, ...) return self end +--- Assign a legion cohort to the mission. Only these cohorts will be considered for the job. +-- @param #AUFTRAG self +-- @param Ops.Cohort#COHORT Cohort The cohort. +-- @return #AUFTRAG self +function AUFTRAG:_AssignCohort(Cohort) + + self.squadrons=self.squadrons or {} + + self:T3(self.lid..string.format("Assigning cohort %s", tostring(Cohort.name))) + table.insert(self.squadrons, Cohort) + + + return self +end + --- Assign airwing squadron(s) to the mission. Only these squads will be considered for the job. -- @param #AUFTRAG self @@ -1887,9 +1902,8 @@ function AUFTRAG:AssignSquadrons(Squadrons) for _,_squad in pairs(Squadrons) do local squadron=_squad --Ops.Squadron#SQUADRON self:I(self.lid..string.format("Assigning squadron %s", tostring(squadron.name))) + self:_AssignCohort(squadron) end - - self.squadrons=Squadrons return self end diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index 5a6c96c09..d12483cf6 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -142,9 +142,7 @@ end -- @param #string PlatoonName Name of the platoon. -- @return Ops.Platoon#PLATOON The Platoon object. function BRIGADE:GetPlatoon(PlatoonName) - local platoon=self:_GetCohort(PlatoonName) - return platoon end @@ -152,7 +150,7 @@ end -- @param #BRIGADE self -- @param Ops.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset. -- @return Ops.Platoon#PLATOON The platoon object. -function BRIGADE:GetSquadronOfAsset(Asset) +function BRIGADE:GetPlatoonOfAsset(Asset) local platoon=self:GetPlatoon(Asset.squadname) return platoon end @@ -160,8 +158,8 @@ end --- Remove asset from squadron. -- @param #BRIGADE self -- @param #BRIGADE.SquadronAsset Asset The squad asset. -function BRIGADE:RemoveAssetFromSquadron(Asset) - local squad=self:GetSquadronOfAsset(Asset) +function BRIGADE:RemoveAssetFromPlatoon(Asset) + local squad=self:GetPlatoonOfAsset(Asset) if squad then squad:DelAsset(Asset) end diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 3b88595b1..e6e04cc93 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -2,10 +2,10 @@ -- -- **Main Features:** -- --- * Set parameters like livery, skill valid for all platoon members. +-- * Set parameters like livery, skill valid for all cohort members. -- * Define modex and callsigns. --- * Define mission types, this platoon can perform (see Ops.Auftrag#AUFTRAG). --- * Pause/unpause platoon operations. +-- * Define mission types, this cohort can perform (see Ops.Auftrag#AUFTRAG). +-- * Pause/unpause cohort operations. -- -- === -- @@ -19,21 +19,21 @@ -- @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 #string name Name of the platoon. +-- @field #string name Name of the cohort. -- @field #string templatename Name of the template group. --- @field #string aircrafttype Type of the airframe the platoon is using. +-- @field #string aircrafttype Type of the units the cohort is using. -- @field Wrapper.Group#GROUP templategroup Template group. -- @field #table assets Cohort assets. --- @field #table missiontypes Capabilities (mission types and performances) of the platoon. +-- @field #table missiontypes Capabilities (mission types and performances) of the cohort. -- @field #number maintenancetime Time in seconds needed for maintenance of a returned flight. -- @field #number repairtime Time in seconds for each --- @field #string livery Livery of the platoon. --- @field #number skill Skill of platoon members. +-- @field #string livery Livery of the cohort. +-- @field #number skill Skill of cohort members. -- @field Ops.Legion#LEGION legion The LEGION object the cohort belongs to. --- @field #number Ngroups Number of asset flight groups this platoon has. +-- @field #number Ngroups Number of asset OPS groups this cohort has. -- @field #number engageRange Mission range in meters. --- @field #string attribute Generalized attribute of the platoon template group. --- @field #table tacanChannel List of TACAN channels available to the platoon. +-- @field #string attribute Generalized attribute of the cohort template group. +-- @field #table tacanChannel List of TACAN channels available to the cohort. -- @field #number radioFreq Radio frequency in MHz the cohort uses. -- @field #number radioModu Radio modulation the cohort uses. -- @field #table tacanChannel List of TACAN channels available to the cohort. @@ -72,7 +72,7 @@ COHORT = { --- COHORT class version. -- @field #string version -COHORT.version="0.0.1" +COHORT.version="0.0.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -88,8 +88,8 @@ COHORT.version="0.0.1" --- Create a new COHORT object and start the FSM. -- @param #COHORT self -- @param #string TemplateGroupName Name of the template group. --- @param #number Ngroups Number of asset groups of this platoon. Default 3. --- @param #string CohortName Name of the platoon, e.g. "VFA-37". +-- @param #number Ngroups Number of asset groups of this Cohort. Default 3. +-- @param #string CohortName Name of the cohort. -- @return #COHORT self function COHORT:New(TemplateGroupName, Ngroups, CohortName) @@ -133,10 +133,10 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) self:AddTransition("Stopped", "Start", "OnDuty") -- Start FSM. self:AddTransition("*", "Status", "*") -- Status update. - self:AddTransition("OnDuty", "Pause", "Paused") -- Pause platoon. - self:AddTransition("Paused", "Unpause", "OnDuty") -- Unpause platoon. + self:AddTransition("OnDuty", "Pause", "Paused") -- Pause cohort. + self:AddTransition("Paused", "Unpause", "OnDuty") -- Unpause cohort. - self:AddTransition("*", "Stop", "Stopped") -- Stop platoon. + self:AddTransition("*", "Stop", "Stopped") -- Stop cohort. ------------------------ @@ -176,7 +176,7 @@ end -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Set livery painted on all platoon aircraft. +--- Set livery painted on all cohort units. -- Note that the livery name in general is different from the name shown in the mission editor. -- -- Valid names are the names of the **livery directories**. Check out the folder in your DCS installation for: @@ -196,10 +196,10 @@ function COHORT:SetLivery(LiveryName) return self end ---- Set skill level of all platoon team members. +--- Set skill level of all cohort team members. -- @param #COHORT self -- @param #string Skill Skill of all flights. --- @usage myplatoon:SetSkill(AI.Skill.EXCELLENT) +-- @usage mycohort:SetSkill(AI.Skill.EXCELLENT) -- @return #COHORT self function COHORT:SetSkill(Skill) self.skill=Skill @@ -230,7 +230,6 @@ end -- @param #COHORT self -- @param #number Frequency Radio frequency in MHz. Default 251 MHz. -- @param #number Modulation Radio modulation. Default 0=AM. --- @usage myplatoon:SetSkill(AI.Skill.EXCELLENT) -- @return #COHORT self function COHORT:SetRadio(Frequency, Modulation) self.radioFreq=Frequency or 251 @@ -249,7 +248,7 @@ function COHORT:SetGrouping(nunits) return self end ---- Set mission types this platoon is able to perform. +--- Set mission types this cohort is able to perform. -- @param #COHORT self -- @param #table MissionTypes Table of mission types. Can also be passed as a #string if only one type. -- @param #number Performance Performance describing how good this mission can be performed. Higher is better. Default 50. Max 100. @@ -286,7 +285,7 @@ function COHORT:AddMissionCapability(MissionTypes, Performance) return self end ---- Get mission types this platoon is able to perform. +--- Get mission types this cohort is able to perform. -- @param #COHORT self -- @return #table Table of mission types. Could be empty {}. function COHORT:GetMissionTypes() @@ -301,7 +300,7 @@ function COHORT:GetMissionTypes() return missiontypes end ---- Get mission capabilities of this platoon. +--- Get mission capabilities of this cohort. -- @param #COHORT self -- @return #table Table of mission capabilities. function COHORT:GetMissionCapabilities() @@ -324,7 +323,7 @@ function COHORT:GetMissionPeformance(MissionType) return -1 end ---- Set max mission range. Only missions in a circle of this radius around the platoon airbase are executed. +--- 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. -- @return #COHORT self @@ -409,9 +408,9 @@ function COHORT:DelGroup(GroupName) return self end ---- Get name of the platoon +--- Get name of the cohort. -- @param #COHORT self --- @return #string Name of the platoon. +-- @return #string Name of the cohort. function COHORT:GetName() return self.name end @@ -485,7 +484,7 @@ function COHORT:GetModex(Asset) end ---- Add TACAN channels to the platoon. Note that channels can only range from 1 to 126. +--- Add TACAN channels to the cohort. Note that channels can only range from 1 to 126. -- @param #COHORT self -- @param #number ChannelMin Channel. -- @param #number ChannelMax Channel. @@ -542,21 +541,21 @@ function COHORT:ReturnTacan(channel) self.tacanChannel[channel]=true end ---- Check if platoon is "OnDuty". +--- Check if cohort is "OnDuty". -- @param #COHORT self -- @return #boolean If true, squdron is in state "OnDuty". function COHORT:IsOnDuty() return self:Is("OnDuty") end ---- Check if platoon is "Stopped". +--- Check if cohort is "Stopped". -- @param #COHORT self -- @return #boolean If true, squdron is in state "Stopped". function COHORT:IsStopped() return self:Is("Stopped") end ---- Check if platoon is "Paused". +--- Check if cohort is "Paused". -- @param #COHORT self -- @return #boolean If true, squdron is in state "Paused". function COHORT:IsPaused() @@ -700,7 +699,7 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Check if there is a platoon that can execute a given mission. +--- Check if there is a cohort that can execute a given mission. -- We check the mission type, the refuelling system, engagement range -- @param #COHORT self -- @param Ops.Auftrag#AUFTRAG Mission The mission. @@ -968,7 +967,7 @@ function COHORT:CheckMissionCapability(MissionTypes, Capabilities) return false end ---- Check if the platoon attribute matches the given attribute(s). +--- Check if the cohort attribute matches the given attribute(s). -- @param #COHORT self -- @param #table Attributes The requested attributes. See `WAREHOUSE.Attribute` enum. Can also be passed as a single attribute `#string`. -- @return #boolean If true, the cohort has the requested attribute. diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index b7f31dfbb..c626d1829 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -183,7 +183,7 @@ end --- Get cohort of an asset. -- @param #LEGION self --- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The squadron asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset. -- @return Ops.Cohort#COHORT The Cohort object. function LEGION:_GetCohortOfAsset(Asset) local cohort=self:_GetCohort(Asset.squadname) @@ -392,8 +392,8 @@ function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) end -- Add mission performance to score. - local squad=self:_GetCohortOfAsset(asset) - local missionperformance=squad:GetMissionPeformance(Mission.type) + local cohort=self:_GetCohortOfAsset(asset) + local missionperformance=cohort:GetMissionPeformance(Mission.type) score=score+missionperformance -- Add payload performance to score. @@ -685,14 +685,14 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) -- Set spawn group name. This has to include "AID-" for warehouse. asset.spawngroupname=string.format("%s_AID-%d", cohort.name, asset.uid) - -- Add asset to squadron. + -- Add asset to cohort. cohort:AddAsset(asset) -- TODO --asset.terminalType=AIRBASE.TerminalType.OpenBig else - --env.info("FF squad asset returned") + --env.info("FF cohort asset returned") self:AssetReturned(cohort, asset) end @@ -730,8 +730,10 @@ function LEGION:onafterAssetReturned(From, Event, To, Cohort, Asset) Asset.Treturned=timer.getAbsTime() if self:IsAirwing() then + -- Trigger airwing/squadron event. self:SquadronAssetReturned(Cohort, Asset) elseif self:IsBrigade() then + -- Trigger brigade/platoon event. self:PlatoonAssetReturned(Cohort, Asset) end end @@ -751,10 +753,10 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) -- Call parent warehouse function first. self:GetParent(self, LEGION).onafterAssetSpawned(self, From, Event, To, group, asset, request) - -- Get the SQUADRON of the asset. + -- Get the COHORT of the asset. local cohort=self:_GetCohortOfAsset(asset) - -- Check if we have a squadron or if this was some other request. + -- Check if we have a cohort or if this was some other request. if cohort then -- Create a flight group. @@ -774,7 +776,7 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) asset.Treturned=nil --- - -- Squadron + -- Cohort --- -- Get TACAN channel. @@ -857,7 +859,7 @@ function LEGION:onafterAssetDead(From, Event, To, asset, request) -- Remove asset from squadron same end ---- On after "Destroyed" event. Remove assets from cohorts. Stop squadrons. +--- On after "Destroyed" event. Remove assets from cohorts. Stop cohorts. -- @param #LEGION self -- @param #string From From state. -- @param #string Event Event. @@ -959,10 +961,10 @@ function LEGION:_CreateFlightGroup(asset) self:E(self.lid.."ERROR: not airwing or brigade!") end - -- Set airwing. + -- Set legion. flightgroup:_SetLegion(self) - -- Set squadron. + -- Set cohort. flightgroup.cohort=self:_GetCohortOfAsset(asset) -- Set home base. @@ -1153,9 +1155,9 @@ function LEGION:CountAssets(InStock, MissionTypes, Attributes) local N=0 - for _,_squad in pairs(self.cohorts) do - local squad=_squad --Ops.Squadron#SQUADRON - N=N+squad:CountAssets(InStock, MissionTypes, Attributes) + for _,_cohort in pairs(self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + N=N+cohort:CountAssets(InStock, MissionTypes, Attributes) end return N @@ -1164,11 +1166,11 @@ end --- Count assets on mission. -- @param #LEGION self -- @param #table MissionTypes Types on mission to be checked. Default all. --- @param Ops.Squadron#SQUADRON Squadron Only count assets of this squadron. Default count assets of all squadrons. +-- @param Ops.Cohort#COHORT Cohort Only count assets of this cohort. Default count assets of all cohorts. -- @return #number Number of pending and queued assets. -- @return #number Number of pending assets. -- @return #number Number of queued assets. -function LEGION:CountAssetsOnMission(MissionTypes, Squadron) +function LEGION:CountAssetsOnMission(MissionTypes, Cohort) local Nq=0 local Np=0 @@ -1182,7 +1184,7 @@ function LEGION:CountAssetsOnMission(MissionTypes, Squadron) for _,_asset in pairs(mission.assets or {}) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - if Squadron==nil or Squadron.name==asset.squadname then + if Cohort==nil or Cohort.name==asset.squadname then local request, isqueued=self:GetRequestByID(mission.requestID) @@ -1232,28 +1234,28 @@ end --- Get the aircraft types of this airwing. -- @param #LEGION self -- @param #boolean onlyactive Count only the active ones. --- @param #table squadrons Table of squadrons. Default all. +-- @param #table cohorts Table of cohorts. Default all. -- @return #table Table of unit types. -function LEGION:GetAircraftTypes(onlyactive, squadrons) +function LEGION:GetAircraftTypes(onlyactive, cohorts) -- Get all unit types that can do the job. local unittypes={} - -- Loop over all squadrons. - for _,_squadron in pairs(squadrons or self.cohorts) do - local squadron=_squadron --Ops.Squadron#SQUADRON + -- Loop over all cohorts. + for _,_cohort in pairs(cohorts or self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT - if (not onlyactive) or squadron:IsOnDuty() then + if (not onlyactive) or cohort:IsOnDuty() then local gotit=false for _,unittype in pairs(unittypes) do - if squadron.aircrafttype==unittype then + if cohort.aircrafttype==unittype then gotit=true break end end if not gotit then - table.insert(unittypes, squadron.aircrafttype) + table.insert(unittypes, cohort.aircrafttype) end end @@ -1274,10 +1276,10 @@ function LEGION:CanMission(Mission) local Assets={} -- Squadrons for the job. If user assigned to mission or simply all. - local squadrons=Mission.squadrons or self.cohorts + local cohorts=Mission.squadrons or self.cohorts -- Get aircraft unit types for the job. - local unittypes=self:GetAircraftTypes(true, squadrons) + local unittypes=self:GetAircraftTypes(true, cohorts) -- Count all payloads in stock. if self:IsAirwing() then @@ -1289,18 +1291,18 @@ function LEGION:CanMission(Mission) end end - for squadname,_squadron in pairs(squadrons) do - local squadron=_squadron --Ops.Cohort#COHORT + for cohortname,_cohort in pairs(cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT -- Check if this squadron can. - local can=squadron:CanMission(Mission) + local can=cohort:CanMission(Mission) if can then -- Number of payloads available. - local Npayloads=self:IsAirwing() and self:CountPayloadsInStock(Mission.type, squadron.aircrafttype, Mission.payloads) or 999 + local Npayloads=self:IsAirwing() and self:CountPayloadsInStock(Mission.type, cohort.aircrafttype, Mission.payloads) or 999 - local assets=squadron:RecruitAssets(Mission, Npayloads) + local assets=cohort:RecruitAssets(Mission, Npayloads) -- Total number. for _,asset in pairs(assets) do @@ -1308,7 +1310,7 @@ function LEGION:CanMission(Mission) end -- Debug output. - local text=string.format("Mission=%s, squadron=%s, payloads=%d, can=%s, assets=%d. Found %d/%d", Mission.type, squadron.name, Npayloads, tostring(can), #assets, #Assets, Mission.nassets) + local text=string.format("Mission=%s, cohort=%s, payloads=%d, can=%s, assets=%d. Found %d/%d", Mission.type, cohort.name, Npayloads, tostring(can), #assets, #Assets, Mission.nassets) self:T(self.lid..text) end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index a86d91f43..9aaba294b 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -80,7 +80,7 @@ SQUADRON.version="0.8.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Parking spots for squadrons? +-- DONE: Parking spots for squadrons? -- DONE: Engage radius. -- DONE: Modex. -- DONE: Call signs. From 77fc2c5cf10af1665c1e49cad2fb35b2978fe061 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 12 Aug 2021 11:41:30 +0200 Subject: [PATCH 072/141] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 1a1e38ddd..58fab32b5 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3942,6 +3942,8 @@ function OPSGROUP:RouteToMission(mission, delay) self:Cruise(mission.missionSpeed and UTILS.KmphToKnots(mission.missionSpeed) or self:GetSpeedCruise()) elseif self:IsNavygroup() then self:Cruise(mission.missionSpeed and UTILS.KmphToKnots(mission.missionSpeed) or self:GetSpeedCruise()) + elseif self:IsFlightgroup() then + self:UpdateRoute() end end From 3050fdb3c33fa2f80b6eecae1c04a5292fbde462 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 15 Aug 2021 00:28:03 +0200 Subject: [PATCH 073/141] OPS Zone --- Moose Development/Moose/DCS.lua | 4 + Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/ChiefOfStaff.lua | 146 ++++- Moose Development/Moose/Ops/Intelligence.lua | 5 +- Moose Development/Moose/Ops/OpsZone.lua | 592 +++++++++++++++++++ Moose Development/Moose/Ops/Target.lua | 20 + 6 files changed, 735 insertions(+), 33 deletions(-) create mode 100644 Moose Development/Moose/Ops/OpsZone.lua diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 8575e1f10..7c9328cc9 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -514,6 +514,10 @@ do -- Object --- @function [parent=#Object] isExist -- @param #Object self -- @return #boolean + + --- @function [parent=#Object] isActive + -- @param #Object self + -- @return #boolean --- @function [parent=#Object] destroy -- @param #Object self diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index b44f10f6b..64e9e701c 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -94,6 +94,7 @@ __Moose.Include( 'Scripts/Moose/Ops/FlightControl.lua' ) __Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' ) __Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' ) __Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/OpsZone.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua index 963886449..c54e69638 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -309,6 +309,9 @@ end -- @return #CHIEF self function CHIEF:AddTarget(Target) + Target:SetPriority() + Target:SetImportance() + table.insert(self.targetqueue, Target) return self @@ -424,7 +427,7 @@ function CHIEF:onafterStatus(From, Event, To) end - -- Create missions for all new contacts. + -- Create TARGETs for all new contacts. local Nred=0 ; local Nyellow=0 ; local Nengage=0 for _,_contact in pairs(self.Contacts) do local contact=_contact --Ops.Intelligence#INTEL.Contact @@ -448,7 +451,13 @@ function CHIEF:onafterStatus(From, Event, To) redalert=inred end - if redalert and threat and not contact.mission then + if redalert and threat and not contact.target then + + local Target=TARGET:New(contact.group) + + self:AddTarget(Target) + + --[[ -- Create a mission based on group category. local mission=AUFTRAG:NewAUTO(group) @@ -469,6 +478,8 @@ function CHIEF:onafterStatus(From, Event, To) self:AddMission(mission) end + ]] + end end @@ -485,25 +496,35 @@ function CHIEF:onafterStatus(From, Event, To) else self:SetDefcon(CHIEF.DEFCON.GREEN) end + + --- + -- Check Target Queue + --- + + -- Check target queue and assign missions to new targets. + self:CheckTargetQueue() + --- - -- Mission Queue + -- Check Mission Queue --- -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() + + --- - -- Mission Queue + -- Info General --- local Nassets=self.wingcommander:CountAssets() - local text=string.format("Defcon=%s Assets=%d, Missions=%d, Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, Nassets, #self.missionqueue, #self.Contacts, Nyellow, Nred) + local text=string.format("Defcon=%s Assets=%d, Contacts: Total=%d Yellow=%d Red=%d, Targets=%d, Missions=%d", self.Defcon, Nassets, #self.Contacts, Nyellow, Nred, #self.targetqueue, #self.missionqueue) self:I(self.lid..text) --- - -- Assets + -- Info Assets --- local text="Assets:" @@ -524,31 +545,7 @@ function CHIEF:onafterStatus(From, Event, To) self:I(self.lid..text) --- - -- Target Queue - --- - - if #self.targetqueue>0 then - local text="Targets:" - for i,_target in pairs(self.targetqueue) do - local target=_target --Ops.Target#TARGET - - text=text..string.format("\n[%d] %s: Category=%s, alive=%s [%.1f/%.1f]", i, target:GetName(), target.category, tostring(target:IsAlive()), target:GetLife(), target:GetLife0()) - - - if target:IsAlive() then - - if self:CheckTargetInZones(target, self.borderzoneset) then - - end - - end - - end - self:I(self.lid..text) - end - - --- - -- Contacts + -- Info Contacts --- -- Info about contacts. @@ -564,6 +561,26 @@ function CHIEF:onafterStatus(From, Event, To) end self:I(self.lid..text) end + + --- + -- Info Targets + --- + + if #self.targetqueue>0 then + local text="Targets:" + for i,_target in pairs(self.targetqueue) do + local target=_target --Ops.Target#TARGET + + text=text..string.format("\n[%d] %s: Category=%s, prio=%d, importance=%d, alive=%s [%.1f/%.1f]", + i, target:GetName(), target.category, target.prio, target.importance or -1, tostring(target:IsAlive()), target:GetLife(), target:GetLife0()) + + end + self:I(self.lid..text) + end + + --- + -- Info Missions + --- -- Mission queue. if #self.missionqueue>0 then @@ -688,6 +705,73 @@ function CHIEF:onafterDeclareWar(From, Event, To, Chief) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Target Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check mission queue and assign ONE planned mission. +-- @param #CHIEF self +function CHIEF:CheckTargetQueue() + + -- TODO: Sort mission queue. wrt what? Threat level? + + for _,_target in pairs(self.targetqueue) do + local target=_target --Ops.Target#TARGET + + if target:IsAlive() and not target.mission then + + -- TODO: stategry + self.strategy=CHIEF.Strategy.TOTALWAR + + local valid=false + if self.strategy==CHIEF.Strategy.DEFENSIVE then + + if self:CheckTargetInZones(target, self.borderzoneset) then + valid=true + end + + elseif self.strategy==CHIEF.Strategy.OFFENSIVE then + + if self:CheckTargetInZones(target, self.borderzoneset) or self:CheckTargetInZones(target, self.yellowzoneset) then + valid=true + end + + elseif self.strategy==CHIEF.Strategy.AGGRESSIVE then + + if self:CheckTargetInZones(target, self.borderzoneset) or self:CheckTargetInZones(target, self.yellowzoneset) or self:CheckTargetInZones(target, self.engagezoneset) then + valid=true + end + + elseif self.strategy==CHIEF.Strategy.TOTALWAR then + valid=true + end + + -- Valid target? + if valid then + + -- Create mission + local mission=AUFTRAG:NewTargetAir(target) + + if mission then + + -- Set target mission entry. + target.mission=mission + + -- Mission parameters. + mission.prio=target.prio + mission.importance=target.importance + + -- Add mission to queue. + self:AddMission(mission) + end + + end + + end + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Resources ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 36ed8e635..dac9f7298 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -117,8 +117,9 @@ INTEL = { -- @field #boolean isship -- @field #boolean ishelo -- @field #boolean isground --- @field Ops.Auftrag#AUFTRAG mission The current Auftrag attached to this contact --- @field #string recce The name of the recce unit that detected this contact +-- @field Ops.Auftrag#AUFTRAG mission The current Auftrag attached to this contact. +-- @field Ops.Target#TARGET target The Target attached to this contact. +-- @field #string recce The name of the recce unit that detected this contact. --- Cluster info. -- @type INTEL.Cluster diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua new file mode 100644 index 000000000..585a4da7b --- /dev/null +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -0,0 +1,592 @@ +--- **Ops** - Strategic Zone. +-- +-- **Main Features:** +-- +-- * Monitor if zone is captured. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- @module Ops.OpsZone +-- @image OPS_OpsZone.png + + +--- OPSZONE class. +-- @type OPSZONE +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity of output. +-- @field Core.Zone#ZONE zone The zone. +-- @field #string zoneName Name of the zone. +-- @field #number ownerCurrent Coalition of the current owner of the zone. +-- @field #number ownerPrevious Coalition of the previous owner of the zone. +-- @field Core.Timer#TIMER timerStatus Timer for calling the status update. +-- @extends Core.Fsm#FSM + +--- Be surprised! +-- +-- === +-- +-- # The OPSZONE Concept +-- +-- An OPSZONE is a strategically important area. +-- +-- +-- @field #OPSZONE +OPSZONE = { + ClassName = "OPSZONE", + verbose = 3, +} + + +--- OPSZONE class version. +-- @field #string version +OPSZONE.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- ToDo list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Can neutrals capture? +-- TODO: Can statics capture or hold a zone? +-- TODO: Differentiate between ground attack and boming by air or arty. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new OPSZONE class object. +-- @param #OPSZONE self +-- @param Core.Zone#ZONE Zone The zone. +-- @param #number CoalitionOwner Initial owner of the coaliton. Default `coalition.side.NEUTRAL`. +-- @return #OPSZONE self +function OPSZONE:New(Zone, CoalitionOwner) + + -- Inherit everything from LEGION class. + local self=BASE:Inherit(self, FSM:New()) -- #OPSZONE + + -- Check if zone name instead of ZONE object was passed. + if type(Zone)=="string" then + Zone=ZONE:New(Zone) + end + + -- Basic checks. + if not Zone then + self:E("ERROR: OPSZONE not found!") + return nil + elseif not Zone:IsInstanceOf("ZONE_RADIUS") then + self:E("ERROR: OPSZONE must be a SPHERICAL zone due to DCS restrictions!") + return nil + end + + self.zone=Zone + self.zoneName=Zone:GetName() + self.zoneRadius=Zone:GetRadius() + + self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL + self.ownerPrevious=CoalitionOwner or coalition.side.NEUTRAL + + -- Set some string id for output to DCS.log file. + self.lid=string.format("OPSZONE %s | ", Zone:GetName()) + + -- FMS start state is PLANNED. + self:SetStartState("Empty") + + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "Start", "*") -- Start FSM. + self:AddTransition("*", "Stop", "*") -- Start FSM. + + self:AddTransition("*", "Captured", "Guarded") -- Start FSM. + self:AddTransition("*", "Empty", "Empty") -- Start FSM. + + self:AddTransition("*", "Attacked", "Attacked") -- A guarded zone is under attack. + self:AddTransition("*", "Defeated", "Guarded") -- The owning coalition defeated an attack. + + + self.timerStatus=TIMER:New(OPSZONE.Status, self) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get current owner of the zone. +-- @param #OPSZONE self +-- @return #number Owner coalition. +function OPSZONE:GetOwner() + return self.ownerCurrent +end + +--- Get previous owner of the zone. +-- @param #OPSZONE self +-- @return #number Previous owner coalition. +function OPSZONE:GetPreviousOwner() + return self.ownerPrevious +end + + +--- Check if the red coalition is currently owning the zone. +-- @param #OPSZONE self +-- @return #boolean If `true`, zone is red. +function OPSZONE:IsRed() + local is=self.ownerCurrent==coalition.side.RED + return is +end + +--- Check if the blue coalition is currently owning the zone. +-- @param #OPSZONE self +-- @return #boolean If `true`, zone is blue. +function OPSZONE:IsBlue() + local is=self.ownerCurrent==coalition.side.BLUE + return is +end + +--- Check if the neutral coalition is currently owning the zone. +-- @param #OPSZONE self +-- @return #boolean If `true`, zone is neutral. +function OPSZONE:IsNeutral() + local is=self.ownerCurrent==coalition.side.NEUTRAL + return is +end + +--- Check if zone is guarded. +-- @param #OPSZONE self +-- @return #boolean If `true`, zone is guarded. +function OPSZONE:IsEmpty() + local is=self:is("Guarded") + return is +end + +--- Check if zone is empty. +-- @param #OPSZONE self +-- @return #boolean If `true`, zone is empty. +function OPSZONE:IsEmpty() + local is=self:is("Empty") + return is +end + +--- Check if zone is being attacked by the opposite coalition. +-- @param #OPSZONE self +-- @return #boolean If `true`, zone is being attacked. +function OPSZONE:IsAttacked() + local is=self:is("Attacked") + return is +end + +--- Check if zone is contested. Contested here means red *and* blue units are present in the zone. +-- @param #OPSZONE self +-- @return #boolean If `true`, zone is contested. +function OPSZONE:IsContested() + return self.isContested +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Start OPSZONE FSM. +-- @param #OPSZONE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSZONE:onafterStart(From, Event, To) + + -- Info. + self:I(self.lid..string.format("Starting OPSZONE v%s", OPSZONE.version)) + + -- Status update. + self.timerStatus:Start(1, 60) + +end + +--- Update status. +-- @param #OPSZONE self +function OPSZONE:Status() + + -- Current FSM state. + local fsmstate=self:GetState() + + -- Info message. + local text=string.format("State %s: Owner %d (previous %d), contested=%s, Nunits: red=%d, blue=%d, neutral=%d", fsmstate, self.ownerCurrent, self.ownerPrevious, tostring(self:IsContested()), 0, 0, 0) + self:I(self.lid..text) + + -- Scanning zone. + self:Scan() + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "Captured" event. +-- @param #OPSZONE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number NewOwnerCoalition Coalition of the new owner. +function OPSZONE:onafterCaptured(From, Event, To, NewOwnerCoalition) + + -- Debug info. + self:I(self.lid..string.format("Zone captured by %d coalition", NewOwnerCoalition)) + + self.ownerPrevious=self.ownerCurrent + self.ownerCurrent=NewOwnerCoalition + +end + +--- On after "Empty" event. +-- @param #OPSZONE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSZONE:onafterEmpty(From, Event, To) + + -- Debug info. + self:I(self.lid..string.format("Zone is empty now")) + +end + +--- On after "Attacked" event. +-- @param #OPSZONE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number AttackerCoalition Coalition of the attacking ground troops. +function OPSZONE:onafterAttacked(From, Event, To, AttackerCoalition) + + -- Debug info. + self:I(self.lid..string.format("Zone is being attacked by coalition %s!", tostring(AttackerCoalition))) + + -- Time stam when the attack started. + self.Tattacked=timer.getAbsTime() + +end + + +--- On after "Empty" event. +-- @param #OPSZONE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSZONE:onafterEmpty(From, Event, To) + + -- Debug info. + self:I(self.lid..string.format("Zone is empty now")) + +end + +--- On after "Defeated" event. +-- @param #OPSZONE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSZONE:onafterDefeated(From, Event, To) + + -- Debug info. + self:I(self.lid..string.format("Attack on zone has been defeated")) + + -- Not attacked any more. + self.Tattacked=nil + +end + +--- On enter "Guarded" state. +-- @param #OPSZONE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSZONE:onenterGuarded(From, Event, To) + + -- Debug info. + self:I(self.lid..string.format("Zone is guarded")) + + self.Tattacked=nil + +end + +--- On enter "Guarded" state. +-- @param #OPSZONE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSZONE:onenterAttacked(From, Event, To) + + -- Debug info. + self:I(self.lid..string.format("Zone is Attacked")) + + self.Tattacked=nil + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Scan Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Add a platoon to the brigade. +-- @param #OPSZONE self +-- @return #OPSZONE self +function OPSZONE:Scan() + + -- Debug info. + local text=string.format("Scanning zone %s R=%.1f m", self.zone:GetName(), self.zone:GetRadius()) + self:I(self.lid..text) + + -- Search. + local SphereSearch={id=world.VolumeType.SPHERE, params={point=self.zone:GetVec3(), radius=self.zone:GetRadius(),}} + + local ObjectCategories={Object.Category.UNIT, Object.Category.STATIC} + + local Nred=0 + local Nblu=0 + local Nnut=0 + + --- Function to evaluate the world search + local function EvaluateZone(_ZoneObject) + + local ZoneObject=_ZoneObject --DCS#Object + + if ZoneObject then + + -- Object category. + local ObjectCategory=ZoneObject:getCategory() + + if ObjectCategory==Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() then + + --- + -- UNIT + --- + + local DCSUnit=ZoneObject --DCS#Unit + + --TODO: only ground units! + + local Coalition=DCSUnit:getCoalition() + + if Coalition==coalition.side.RED then + Nred=Nred+1 + elseif Coalition==coalition.side.BLUE then + Nblu=Nblu+1 + elseif Coalition==coalition.side.NEUTRAL then + Nnut=Nnut+1 + end + + local unit=UNIT:Find(DCSUnit) + + env.info(string.format("FF found unit %s", unit:GetName())) + + + elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist() then + + --- + -- STATIC + --- + + local DCSStatic=ZoneObject --DCS#Static + + -- CAREFUL! Downed pilots break routine here without any error thrown. + local unit=STATIC:Find(DCSStatic) + + --env.info(string.format("FF found static %s", unit:GetName())) + + elseif ObjectCategory==Object.Category.SCENERY then + + --- + -- SCENERY + --- + + local SceneryType = ZoneObject:getTypeName() + local SceneryName = ZoneObject:getName() + + local Scenery=SCENERY:Register(SceneryName, ZoneObject) + + env.info(string.format("FF found scenery type=%s, name=%s", SceneryType, SceneryName)) + end + + end + + return true + end + + -- Search objects. + world.searchObjects(ObjectCategories, SphereSearch, EvaluateZone) + + -- Debug info. + local text=string.format("Scan result Nred=%d, Nblue=%d, Nneutrl=%d", Nred, Nblu, Nnut) + self:I(self.lid..text) + + if self:IsRed() then + + --- + -- RED zone + --- + + if Nred==0 then + + -- No red units in red zone any more. + + if Nblu>0 then + -- Blue captured red zone. + self:Captured(coalition.side.BLUE) + elseif Nnut>0 and self.neutralCanCapture then + -- Neutral captured red zone. + self:Captured(coalition.side.NEUTRAL) + else + -- Red zone is now empty (but will remain red). + self:Empty() + end + + else + + -- Still red units in red zone. + + if Nblu>0 then + + if not self:IsAttacked() then + self:Attacked(coalition.side.BLUE) + end + + elseif Nblu==0 then + + if self:IsAttacked() and self:IsContested() then + self:Defeated(coalition.side.BLUE) + end + + end + + end + + -- Contested by blue? + if Nblu==0 then + self.isContested=false + else + self.isContested=true + end + + elseif self:IsBlue() then + + --- + -- BLUE zone + --- + + if Nblu==0 then + + -- No blue units in blue zone any more. + + if Nred>0 then + -- Red captured blue zone. + self:Captured(coalition.side.RED) + elseif Nnut>0 and self.neutralCanCapture then + -- Neutral captured blue zone. + self:Captured(coalition.side.NEUTRAL) + else + -- Blue zone is empty now. + self:Empty() + end + + else + + -- Still blue units in blue zone. + + if Nred>0 then + + if not self:IsAttacked() then + -- Red is attacking blue zone. + self:Attacked(coalition.side.RED) + end + + elseif Nred==0 then + + if self:IsAttacked() and self:IsContested() then + -- Blue defeated read attack. + self:Defeated(coalition.side.RED) + end + + end + + end + + -- Contested by red? + if Nred==0 then + self.isContested=false + else + self.isContested=true + end + + elseif self:IsNeutral() then + + --- + -- NEUTRAL zone + --- + + -- Not checked as neutrals cant capture (for now). + --if Nnut==0 then + + -- No neutral units in neutral zone any more. + + if Nred>0 and Nblu>0 then + env.info("FF neutrals left neutral zone and red and blue are present! What to do?") + -- TODO Contested! + self:Attacked() + self.isContested=true + elseif Nred>0 then + -- Red captured neutral zone. + self:Captured(coalition.side.RED) + elseif Nblu>0 then + -- Blue captured neutral zone. + self:Captured(coalition.side.BLUE) + else + -- Neutral zone is empty now. + if not self:IsEmpty() then + self:Emtpy() + end + end + + --end + + else + env.info("FF error") + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- DCS Event Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Monitor hit events. +-- @param #OPSZONE self +-- @param Core.Event#EVENTDATA EventData The event data. +function OPSZONE:OnEventHit(EventData) + + if self.HitsOn then + + local UnitHit = EventData.TgtUnit + + -- Check if unit is inside the capture zone and that it is of the defending coalition. + if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.ownerCurrent then + + -- Update last hit time. + self.HitTimeLast=timer.getTime() + + -- Only trigger attacked event if not already in state "Attacked". + if not self:IsAttacked() then + self:T3(self.lid.."Hit ==> Attack") + self:Attacked() + end + + end + + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 9d08774a6..57993a85c 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -30,6 +30,8 @@ -- @field #number Ndead Number of target elements/units that are dead (destroyed or despawned). -- @field #table elements Table of target elements/units. -- @field #table casualties Table of dead element names. +-- @field #number prio Priority. +-- @field #number importance Importance -- @extends Core.Fsm#FSM --- **It is far more important to be able to hit the target than it is to haggle over who makes a weapon or who pulls a trigger** -- Dwight D. Eisenhower @@ -287,6 +289,24 @@ function TARGET:AddObject(Object) end +--- Set priority of the target. +-- @param #TARGET self +-- @param #number Priority Priority of the target. Default 50. +-- @return #TARGET self +function TARGET:SetPriority(Priority) + self.prio=Priority or 50 + return self +end + +--- Set importance of the target. +-- @param #TARGET self +-- @param #number Priority Priority of the target. Default `nil`. +-- @return #TARGET self +function TARGET:SetImportance(Importance) + self.importance=Importance + return self +end + --- Check if TARGET is alive. -- @param #TARGET self -- @return #boolean If true, target is alive. From 16964520df63d5b9e517ce129f16fba7a1e25075 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 18 Aug 2021 00:25:18 +0200 Subject: [PATCH 074/141] OPS - Lots of stuff to assign missions to multiple legions. --- Moose Development/Moose/Core/Base.lua | 6 +- .../Moose/Functional/Warehouse.lua | 2 + Moose Development/Moose/Modules.lua | 6 +- Moose Development/Moose/Ops/AirWing.lua | 4 +- Moose Development/Moose/Ops/Auftrag.lua | 405 +++++++++++++----- .../Moose/Ops/{ChiefOfStaff.lua => Chief.lua} | 104 ++--- Moose Development/Moose/Ops/Cohort.lua | 4 +- .../Ops/{WingCommander.lua => Commander.lua} | 309 ++++++++----- Moose Development/Moose/Ops/Legion.lua | 143 +++++-- Moose Development/Moose/Ops/OpsGroup.lua | 14 + Moose Setup/Moose.files | 8 +- 11 files changed, 679 insertions(+), 326 deletions(-) rename Moose Development/Moose/Ops/{ChiefOfStaff.lua => Chief.lua} (91%) rename Moose Development/Moose/Ops/{WingCommander.lua => Commander.lua} (57%) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 6091eb5c8..6c4d6a8eb 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -1099,7 +1099,7 @@ end --- Set tracing for a class -- @param #BASE self --- @param #string Class +-- @param #string Class Class name. function BASE:TraceClass( Class ) _TraceClass[Class] = true _TraceClassMethod[Class] = {} @@ -1108,8 +1108,8 @@ end --- Set tracing for a specific method of class -- @param #BASE self --- @param #string Class --- @param #string Method +-- @param #string Class Class name. +-- @param #string Method Method. function BASE:TraceClassMethod( Class, Method ) if not _TraceClassMethod[Class] then _TraceClassMethod[Class] = {} diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index a2d317024..a3afd7853 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1599,6 +1599,8 @@ WAREHOUSE = { --- Item of the warehouse stock table. -- @type WAREHOUSE.Assetitem -- @field #number uid Unique id of the asset. +-- @field #number wid ID of the warehouse this asset belongs to. +-- @field #number rid Request ID of this asset (if any). -- @field #string templatename Name of the template group. -- @field #table template The spawn template of the group. -- @field DCS#Group.Category category Category of the group. diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 64e9e701c..fdb043c2e 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -88,13 +88,13 @@ __Moose.Include( 'Scripts/Moose/Ops/Legion.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Brigade.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) -__Moose.Include( 'Scripts/Moose/Ops/WingCommander.lua' ) -__Moose.Include( 'Scripts/Moose/Ops/ChiefOfStaff.lua' ) -__Moose.Include( 'Scripts/Moose/Ops/FlightControl.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Commander.lua' ) __Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' ) __Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' ) __Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' ) __Moose.Include( 'Scripts/Moose/Ops/OpsZone.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Chief.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/FlightControl.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index f61bf12d8..96f171c07 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -33,7 +33,6 @@ -- @field #table pointsTANKER Table of Tanker points. -- @field #table pointsAWACS Table of AWACS points. -- @field #boolean markpoints Display markers on the F10 map. --- @field Ops.WingCommander#WINGCOMMANDER wingcommander The wing commander responsible for this airwing. -- -- @field Ops.RescueHelo#RESCUEHELO rescuehelo The rescue helo. -- @field Ops.RecoveryTanker#RECOVERYTANKER recoverytanker The recoverytanker. @@ -816,8 +815,9 @@ function AIRWING:onafterStatus(From, Event, To) local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.nassets) local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage()) + local mystatus=mission:GetLegionStatus(self) - text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target) + text=text..string.format("\n[%d] %s %s: Status=%s [%s], Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mystatus, mission.status, prio, assets, target) end self:I(self.lid..text) end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 8a6ba8098..d26c73f8e 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -28,12 +28,15 @@ --- AUFTRAG class. -- @type AUFTRAG -- @field #string ClassName Name of the class. --- @field #boolean Debug Debug mode. Messages to all about status. -- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field #number auftragsnummer Auftragsnummer. -- @field #string type Mission type. -- @field #string status Mission status. +-- @field #table legions Assigned legions. +-- @field #table statusLegion Mission status of all assigned LEGIONSs. +-- @field #string statusCommander Mission status of the COMMANDER. +-- @field #string statusChief Mission status of the CHIF. -- @field #table groupdata Group specific data. -- @field #string name Mission name. -- @field #number prio Mission priority. @@ -91,15 +94,15 @@ -- @field #number artyRadius Radius in meters. -- @field #number artyShots Number of shots fired. -- --- @field Ops.ChiefOfStaff#CHIEF chief The CHIEF managing this mission. --- @field Ops.WingCommander#WINGCOMMANDER wingcommander The WINGCOMMANDER managing this mission. --- @field Ops.AirWing#AIRWING airwing The assigned airwing. --- @field #table assets Airwing Assets assigned for this mission. --- @field #number nassets Number of required assets by the Airwing. --- @field #number requestID The ID of the queued warehouse request. Necessary to cancel the request if the mission was cancelled before the request is processed. --- @field #boolean cancelContactLost If true, cancel mission if the contact is lost. +-- @field Ops.Chief#CHIEF chief The CHIEF managing this mission. +-- @field Ops.Commander#COMMANDER commander The COMMANDER managing this mission. +-- @field #table assets Warehouse assets assigned for this mission. +-- @field #number nassets Number of required warehouse assets. +-- @field #table Nassets Number of required warehouse assets for each assigned legion. +-- @field #table requestID The ID of the queued warehouse request. Necessary to cancel the request if the mission was cancelled before the request is processed. -- @field #table squadrons User specified airwing squadrons assigned for this mission. Only these will be considered for the job! -- @field #table payloads User specified airwing payloads for this mission. Only these will be considered for the job! +-- @field #table mylegions User specified legions for this mission. Only these will be considered for the job! -- @field Ops.AirWing#AIRWING.PatrolData patroldata Patrol data. -- -- @field #string missionTask Mission task. See `ENUMS.MissionTask`. @@ -276,12 +279,15 @@ -- @field #AUFTRAG AUFTRAG = { ClassName = "AUFTRAG", - Debug = false, verbose = 0, lid = nil, auftragsnummer = nil, - groupdata = {}, + groupdata = {}, + legions = {}, + statusLegion = {}, + requestID = {}, assets = {}, + Nassets = {}, missionFraction = 0.5, enrouteTasks = {}, marker = nil, @@ -350,10 +356,10 @@ AUFTRAG.Type={ --- Mission status. -- @type AUFTRAG.Status --- @field #string PLANNED Mission is at the early planning stage. --- @field #string QUEUED Mission is queued at an airwing. +-- @field #string PLANNED Mission is at the early planning stage and has not been added to any queue. +-- @field #string QUEUED Mission is queued at a LEGION. -- @field #string REQUESTED Mission assets were requested from the warehouse. --- @field #string SCHEDULED Mission is scheduled in a FLIGHGROUP queue waiting to be started. +-- @field #string SCHEDULED Mission is scheduled in an OPSGROUP queue waiting to be started. -- @field #string STARTED Mission has started but is not executed yet. -- @field #string EXECUTING Mission is being executed. -- @field #string DONE Mission is over. @@ -454,6 +460,7 @@ AUFTRAG.version="0.7.1" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Missions can be assigned to multiple legions. -- TODO: Mission success options damaged, destroyed. -- TODO: F10 marker to create new missions. -- TODO: Add recovery tanker mission for boat ops. @@ -531,12 +538,12 @@ function AUFTRAG:New(Type) self:AddTransition(AUFTRAG.Status.PLANNED, "Scheduled", AUFTRAG.Status.SCHEDULED) -- From planned directly to scheduled. - self:AddTransition(AUFTRAG.Status.SCHEDULED, "Started", AUFTRAG.Status.STARTED) -- First asset has started the mission + self:AddTransition(AUFTRAG.Status.SCHEDULED, "Started", AUFTRAG.Status.STARTED) -- First asset has started the mission. self:AddTransition(AUFTRAG.Status.STARTED, "Executing", AUFTRAG.Status.EXECUTING) -- First asset is executing the mission. self:AddTransition("*", "Done", AUFTRAG.Status.DONE) -- All assets have reported that mission is done. - self:AddTransition("*", "Cancel", "*") -- Command to cancel the mission. + self:AddTransition("*", "Cancel", AUFTRAG.Status.CANCELLED) -- Command to cancel the mission. self:AddTransition("*", "Success", AUFTRAG.Status.SUCCESS) self:AddTransition("*", "Failed", AUFTRAG.Status.FAILED) @@ -1523,7 +1530,7 @@ function AUFTRAG:SetRepeatOnSuccess(Nrepeat) return self end ---- Define how many assets are required to do the job. Only valid if the mission is handled by an AIRWING or higher level. +--- Define how many assets are required to do the job. Only valid if the mission is handled by an AIRWING, BRIGADE etc or higher level. -- @param #AUFTRAG self -- @param #number Nassets Number of asset groups. Default 1. -- @return #AUFTRAG self @@ -1532,6 +1539,21 @@ function AUFTRAG:SetRequiredAssets(Nassets) return self end +--- Get number of required assets. +-- @param #AUFTRAG self +-- @param Ops.Legion#Legion Legion (Optional) Only get the required assets for a specific legion. +-- @param #number Number of required assets. +function AUFTRAG:GetRequiredAssets(Legion) + + local N=self.nassets + + if Legion then + N=self.Nassets[Legion.alias] or 0 + end + + return N +end + --- Set mission name. -- @param #AUFTRAG self -- @param #string Name Name of the mission. Default is "Auftrag Nr. X", where X is a running number, which is automatically increased. @@ -1967,35 +1989,45 @@ function AUFTRAG:IsPlanned() return self.status==AUFTRAG.Status.PLANNED end ---- Check if mission is QUEUED at an AIRWING mission queue. +--- Check if mission is QUEUED at a LEGION mission queue. -- @param #AUFTRAG self +-- @param Ops.Legion#LEGION Legion (Optional) Check if mission is queued at this legion. -- @return #boolean If true, mission is queued. -function AUFTRAG:IsQueued() - return self.status==AUFTRAG.Status.QUEUED +function AUFTRAG:IsQueued(Legion) + local is=self.status==AUFTRAG.Status.QUEUED + if Legion then + is=self:GetLegionStatus(Legion)==AUFTRAG.Status.QUEUED + end + return is end ---- Check if mission is REQUESTED, i.e. request for WAREHOUSE assets is done. +--- Check if mission is REQUESTED. The mission request out to the WAREHOUSE. -- @param #AUFTRAG self +-- @param Ops.Legion#LEGION Legion (Optional) Check if mission is requested at this legion. -- @return #boolean If true, mission is requested. -function AUFTRAG:IsRequested() - return self.status==AUFTRAG.Status.REQUESTED +function AUFTRAG:IsRequested(Legion) + local is=self.status==AUFTRAG.Status.REQUESTED + if Legion then + is=self:GetLegionStatus(Legion)==AUFTRAG.Status.REQUESTED + end + return is end ---- Check if mission is SCHEDULED, i.e. request for WAREHOUSE assets is done. +--- Check if mission is SCHEDULED. The first OPSGROUP has been assigned. -- @param #AUFTRAG self -- @return #boolean If true, mission is queued. function AUFTRAG:IsScheduled() return self.status==AUFTRAG.Status.SCHEDULED end ---- Check if mission is STARTED, i.e. group is on its way to the mission execution waypoint. +--- Check if mission is STARTED. The first OPSGROUP is on its way to the mission execution waypoint. -- @param #AUFTRAG self -- @return #boolean If true, mission is started. function AUFTRAG:IsStarted() return self.status==AUFTRAG.Status.STARTED end ---- Check if mission is executing. +--- Check if mission is EXECUTING. The first OPSGROUP has reached the mission execution waypoint and is not executing the mission task. -- @param #AUFTRAG self -- @return #boolean If true, mission is currently executing. function AUFTRAG:IsExecuting() @@ -2228,11 +2260,13 @@ function AUFTRAG:onafterStatus(From, Event, To) local targetname=self:GetTargetName() or "unknown" - local airwing=self.airwing and self.airwing.alias or "N/A" - local chief=self.chief and tostring(self.chief.coalition) or "N/A" + local Nlegions=#self.legions + local commander=self.commander and self.statusCommander or "N/A" + local chief=self.chief and self.statusChief or "N/A" -- Info message. - self:I(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, chief=%s", self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, airwing, chief)) + self:I(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, legions=%d, commander=%s, chief=%s", + self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, Nlegions, commander, chief)) end -- Group info. @@ -2407,8 +2441,11 @@ end -- @param #AUFTRAG self -- @param Ops.OpsGroup#OPSGROUP opsgroup The flight group. -- @param #string status New status. +-- @return #AUFTRAG self function AUFTRAG:SetGroupStatus(opsgroup, status) - self:T(self.lid..string.format("Setting flight %s to status %s", opsgroup and opsgroup.groupname or "nil", tostring(status))) + + -- Debug info. + self:T(self.lid..string.format("Setting OPSGROUP %s to status %s", opsgroup and opsgroup.groupname or "nil", tostring(status))) if self:GetGroupStatus(opsgroup)==AUFTRAG.GroupStatus.CANCELLED and status==AUFTRAG.GroupStatus.DONE then -- Do not overwrite a CANCELLED status with a DONE status. @@ -2432,11 +2469,13 @@ function AUFTRAG:SetGroupStatus(opsgroup, status) self:T3(self.lid.."Mission NOT DONE yet!") end + return self end --- Get ops group mission status. -- @param #AUFTRAG self --- @param Ops.OpsGroup#OPSGROUP opsgroup The flight group. +-- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group. +-- @return #string The group status. function AUFTRAG:GetGroupStatus(opsgroup) self:T3(self.lid..string.format("Trying to get Flight status for flight group %s", opsgroup and opsgroup.groupname or "nil")) @@ -2452,6 +2491,73 @@ function AUFTRAG:GetGroupStatus(opsgroup) end end +--- Add LEGION to mission. +-- @param #AUFTRAG self +-- @param Ops.Legion#LEGION Legion The legion. +-- @return #AUFTRAG self +function AUFTRAG:AddLegion(Legion) + + -- Debug info. + self:I(self.lid..string.format("Adding legion %s", Legion.alias)) + + -- Add legion to table. + table.insert(self.legions, Legion) + + return self +end + +--- Remove LEGION from mission. +-- @param #AUFTRAG self +-- @param Ops.Legion#LEGION Legion The legion. +-- @return #AUFTRAG self +function AUFTRAG:RemoveLegion(Legion) + + -- Loop over legions + for i=#self.legions,1,-1 do + local legion=self.legions[i] --Ops.Legion#LEGION + if legion.alias==Legion.alias then + -- Debug info. + self:I(self.lid..string.format("Removing legion %s", Legion.alias)) + table.remove(self.legions, i) + return self + end + end + + self:E(self.lid..string.format("ERROR: Legion %s not found and could not be removed!", Legion.alias)) + return self +end + +--- Set LEGION mission status. +-- @param #AUFTRAG self +-- @param Ops.Legion#LEGION Legion The legion. +-- @param #string Status New status. +-- @return #AUFTRAG self +function AUFTRAG:SetLegionStatus(Legion, Status) + + -- Old status + local status=self:GetLegionStatus(Legion) + + -- Debug info. + self:I(self.lid..string.format("Setting LEGION %s to status %s-->%s", Legion.alias, tostring(status), tostring(Status))) + + -- New status. + self.statusLegion[Legion.alias]=Status + + return self +end + +--- Get LEGION mission status. +-- @param #AUFTRAG self +-- @param Ops.Legion#LEGION Legion The legion. +-- @return #string status Current status. +function AUFTRAG:GetLegionStatus(Legion) + + -- New status. + local status=self.statusLegion[Legion.alias] or "unknown" + + return status +end + --- Set Ops group waypoint coordinate. -- @param #AUFTRAG self @@ -2527,7 +2633,42 @@ end -- @return #boolean If true, all flights are done with the mission. function AUFTRAG:CheckGroupsDone() - -- These are early stages, where we might not even have a opsgroup defined to be checked. + -- Check status of all OPS groups. + for groupname,data in pairs(self.groupdata) do + local groupdata=data --#AUFTRAG.GroupData + if groupdata then + if not (groupdata.status==AUFTRAG.GroupStatus.DONE or groupdata.status==AUFTRAG.GroupStatus.CANCELLED) then + -- At least this flight is not DONE or CANCELLED. + return false + end + end + end + + -- Check status of all LEGIONs. + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + local status=self:GetLegionStatus(legion) + if not status==AUFTRAG.Status.CANCELLED then + -- At least one LEGION has not CANCELLED. + return false + end + end + + -- Check commander status. + if self.commander then + if not self.statusCommander==AUFTRAG.Status.CANCELLED then + return false + end + end + + -- Check chief status. + if self.chief then + if not self.statusChief==AUFTRAG.Status.CANCELLED then + return false + end + end + + -- These are early stages, where we might not even have a opsgroup defined to be checked. If there were any groups, we checked above. if self:IsPlanned() or self:IsQueued() or self:IsRequested() then return false end @@ -2538,19 +2679,6 @@ function AUFTRAG:CheckGroupsDone() return true end - -- Check status of all flight groups. - for groupname,data in pairs(self.groupdata) do - local groupdata=data --#AUFTRAG.GroupData - if groupdata then - if groupdata.status==AUFTRAG.GroupStatus.DONE or groupdata.status==AUFTRAG.GroupStatus.CANCELLED then - -- This one is done or cancelled. - else - -- At least this flight is not DONE or CANCELLED. - return false - end - end - end - return true end @@ -2595,16 +2723,14 @@ function AUFTRAG:onafterPlanned(From, Event, To) self:T(self.lid..string.format("New mission status=%s", self.status)) end ---- On after "Queue" event. Mission is added to the mission queue of an AIRWING. +--- On after "Queue" event. Mission is added to the mission queue of a LEGION. -- @param #AUFTRAG self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.AirWing#AIRWING Airwing The airwing. function AUFTRAG:onafterQueued(From, Event, To, Airwing) self.status=AUFTRAG.Status.QUEUED - self.airwing=Airwing - self:T(self.lid..string.format("New mission status=%s at airwing %s", self.status, tostring(Airwing.alias))) + self:T(self.lid..string.format("New mission status=%s", self.status)) end @@ -2628,7 +2754,7 @@ function AUFTRAG:onafterAssign(From, Event, To) self:T(self.lid..string.format("New mission status=%s", self.status)) end ---- On after "Schedule" event. Mission is added to the mission queue of a FLIGHTGROUP. +--- On after "Schedule" event. Mission is added to the mission queue of an OPSGROUP. -- @param #AUFTRAG self -- @param #string From From state. -- @param #string Event Event. @@ -2658,20 +2784,6 @@ function AUFTRAG:onafterExecuting(From, Event, To) self:T(self.lid..string.format("New mission status=%s", self.status)) end ---- On after "Done" event. --- @param #AUFTRAG self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function AUFTRAG:onafterDone(From, Event, To) - self.status=AUFTRAG.Status.DONE - self:T(self.lid..string.format("New mission status=%s", self.status)) - - -- Set time stamp. - self.Tover=timer.getAbsTime() - -end - --- On after "ElementDestroyed" event. -- @param #AUFTRAG self -- @param #string From From state. @@ -2739,8 +2851,11 @@ end -- @param #string To To state. function AUFTRAG:onafterCancel(From, Event, To) + -- Number of OPSGROUPS assigned and alive. + local Ngroups = self:CountOpsGroups() + -- Debug info. - self:I(self.lid..string.format("CANCELLING mission in status %s. Will wait for groups to report mission DONE before evaluation", self.status)) + self:I(self.lid..string.format("CANCELLING mission in status %s. Will wait for %d groups to report mission DONE before evaluation", self.status, Ngroups)) -- Time stamp. self.Tover=timer.getAbsTime() @@ -2755,42 +2870,72 @@ function AUFTRAG:onafterCancel(From, Event, To) if self.chief then - self:T(self.lid..string.format("Chief will cancel the mission. Will wait for mission DONE before evaluation!")) + self:T(self.lid..string.format("CHIEF will cancel the mission. Will wait for mission DONE before evaluation!")) self.chief:MissionCancel(self) - - elseif self.wingcommander then - - self:T(self.lid..string.format("Wingcommander will cancel the mission. Will wait for mission DONE before evaluation!")) - self.wingcommander:MissionCancel(self) + end + + if self.commander then + + self:T(self.lid..string.format("COMMANDER will cancel the mission. Will wait for mission DONE before evaluation!")) + + self.commander:MissionCancel(self) + + end - elseif self.airwing then + if #self.legions>0 then + + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION - self:T(self.lid..string.format("Airwing %s will cancel the mission. Will wait for mission DONE before evaluation!", self.airwing.alias)) + self:T(self.lid..string.format("LEGION %s will cancel the mission. Will wait for mission DONE before evaluation!", legion.alias)) - -- Airwing will cancel all flight missions and remove queued request from warehouse queue. - self.airwing:MissionCancel(self) - - else - - self:T(self.lid..string.format("No airwing, wingcommander or chief. Attached flights will cancel the mission on their own. Will wait for mission DONE before evaluation!")) - - for _,_groupdata in pairs(self.groupdata) do - local groupdata=_groupdata --#AUFTRAG.GroupData - groupdata.opsgroup:MissionCancel(self) + -- Legion will cancel all flight missions and remove queued request from warehouse queue. + legion:MissionCancel(self) + end end + + self:T(self.lid..string.format("No legion, commander or chief. Attached flights will cancel the mission on their own. Will wait for mission DONE before evaluation!")) + + for _,_groupdata in pairs(self.groupdata or {}) do + local groupdata=_groupdata --#AUFTRAG.GroupData + groupdata.opsgroup:MissionCancel(self) + end + -- Special mission states. - if self.status==AUFTRAG.Status.PLANNED then - self:T(self.lid..string.format("Cancelled mission was in planned stage. Call it done!")) + if self:IsPlanned() or self:IsQueued() or self:IsRequested() or Ngroups==0 then + self:T(self.lid..string.format("Cancelled mission was in %s stage with %d groups assigned and alive. Call it done!", self.status, Ngroups)) self:Done() end end +--- On after "Done" event. +-- @param #AUFTRAG self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function AUFTRAG:onafterDone(From, Event, To) + self.status=AUFTRAG.Status.DONE + self:T(self.lid..string.format("New mission status=%s", self.status)) + + -- Set time stamp. + self.Tover=timer.getAbsTime() + + -- Set status for CHIEF, COMMANDER and LEGIONs + self.statusChief=AUFTRAG.Status.DONE + self.statusCommander=AUFTRAG.Status.DONE + for _,_legion in pairs(self.legions) do + local Legion=_legion --Ops.Legion#LEGION + self:SetLegionStatus(Legion, AUFTRAG.Status.DONE) + end + +end + --- On after "Success" event. -- @param #AUFTRAG self -- @param #string From From state. @@ -2801,6 +2946,14 @@ function AUFTRAG:onafterSuccess(From, Event, To) self.status=AUFTRAG.Status.SUCCESS self:T(self.lid..string.format("New mission status=%s", self.status)) + -- Set status for CHIEF, COMMANDER and LEGIONs + self.statusChief=self.status + self.statusCommander=self.status + for _,_legion in pairs(self.legions) do + local Legion=_legion --Ops.Legion#LEGION + self:SetLegionStatus(Legion, self.status) + end + local repeatme=self.repeatedSuccess0 then - -- Already at the airwing ==> Queued() - self:Queued(self.airwing) + -- Remove mission from airwing because WC will assign it again but maybe to a different wing. + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + legion:RemoveMission(self) + self:SetLegionStatus(legion, AUFTRAG.Status.PLANNED) + legion:AddMission(self) + end else self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, WINGCOMMANDER or AIRWING! Stopping AUFTRAG") @@ -2947,13 +3145,16 @@ function AUFTRAG:onafterStop(From, Event, To) end -- Remove mission from WINGCOMMANDER queue. - if self.wingcommander then - self.wingcommander:RemoveMission(self) + if self.commander then + self.commander:RemoveMission(self) end - -- Remove mission from AIRWING queue. - if self.airwing then - self.airwing:RemoveMission(self) + -- Remove mission from LEGION queues. + if #self.legions>0 then + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + legion:RemoveMission(self) + end end -- Remove mission from OPSGROUP queue diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/Chief.lua similarity index 91% rename from Moose Development/Moose/Ops/ChiefOfStaff.lua rename to Moose Development/Moose/Ops/Chief.lua index c54e69638..affe9112b 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -22,17 +22,13 @@ -- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected. -- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged. -- @field #string Defcon Defence condition. --- @field Ops.WingCommander#WINGCOMMANDER wingcommander Wing commander, commanding airborne forces. --- @field Ops.Admiral#ADMIRAL admiral Admiral commanding navy forces. --- @field Ops.General#GENERAL genaral General commanding army forces. +-- @field Ops.Commander#COMMANDER commander Commander of assigned legions. -- @extends Ops.Intelligence#INTEL --- Be surprised! -- -- === -- --- ![Banner Image](..\Presentations\WingCommander\CHIEF_Main.jpg) --- -- # The CHIEF Concept -- -- The Chief of staff gathers intel and assigns missions (AUFTRAG) the airforce (WINGCOMMANDER), army (GENERAL) or navy (ADMIRAL). @@ -45,9 +41,6 @@ CHIEF = { ClassName = "CHIEF", verbose = 0, lid = nil, - wingcommander = nil, - admiral = nil, - general = nil, targetqueue = {}, missionqueue = {}, borderzoneset = nil, @@ -87,6 +80,7 @@ CHIEF.version="0.0.1" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Capture OPSZONEs. -- TODO: Get list of own assets and capabilities. -- TODO: Get list/overview of enemy assets etc. -- TODO: Put all contacts into target list. Then make missions from them. @@ -138,23 +132,25 @@ function CHIEF:New(AgentSet, Coalition) --- Pseudo Functions --- ------------------------ - --- Triggers the FSM event "Start". Starts the CHIEF. Initializes parameters and starts event handlers. + --- Triggers the FSM event "Start". -- @function [parent=#CHIEF] Start -- @param #CHIEF self - --- Triggers the FSM event "Start" after a delay. Starts the CHIEF. Initializes parameters and starts event handlers. + --- Triggers the FSM event "Start" after a delay. -- @function [parent=#CHIEF] __Start -- @param #CHIEF self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Stop". Stops the CHIEF and all its event handlers. + + --- Triggers the FSM event "Stop". -- @param #CHIEF self - --- Triggers the FSM event "Stop" after a delay. Stops the CHIEF and all its event handlers. + --- Triggers the FSM event "Stop" after a delay. -- @function [parent=#CHIEF] __Stop -- @param #CHIEF self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Status". -- @function [parent=#CHIEF] Status -- @param #CHIEF self @@ -165,12 +161,18 @@ function CHIEF:New(AgentSet, Coalition) -- @param #number delay Delay in seconds. - -- Debug trace. - if false then - BASE:TraceOnOff(true) - BASE:TraceClass(self.ClassName) - BASE:TraceLevel(1) - end + --- Triggers the FSM event "MissionCancel". + -- @function [parent=#CHIEF] MissionCancel + -- @param #CHIEF self + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "MissionCancel" event. + -- @function [parent=#CHIEF] OnAfterMissionCancel + -- @param #CHIEF self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. return self end @@ -262,9 +264,9 @@ end -- @return #CHIEF self function CHIEF:SetWingCommander(WingCommander) - self.wingcommander=WingCommander + self.commander=WingCommander - self.wingcommander.chief=self + self.commander.chief=self return self end @@ -276,6 +278,8 @@ end function CHIEF:AddMission(Mission) Mission.chief=self + + Mission.statusChief=AUFTRAG.Status.QUEUED table.insert(self.missionqueue, Mission) @@ -385,10 +389,10 @@ function CHIEF:onafterStart(From, Event, To) -- Start parent INTEL. self:GetParent(self).onafterStart(self, From, Event, To) - -- Start wingcommander. - if self.wingcommander then - if self.wingcommander:GetState()=="NotReadyYet" then - self.wingcommander:Start() + -- Start commander. + if self.commander then + if self.commander:GetState()=="NotReadyYet" then + self.commander:Start() end end @@ -457,29 +461,6 @@ function CHIEF:onafterStatus(From, Event, To) self:AddTarget(Target) - --[[ - - -- Create a mission based on group category. - local mission=AUFTRAG:NewAUTO(group) - - -- Add mission to queue. - if mission then - - --TODO: Better amount of necessary assets. Count units in asset and in contact. Might need nassetMin/Max. - mission.nassets=1 - - -- Missons are repeated max 3 times on failure. - mission.NrepeatFailure=3 - - -- Set mission contact. - contact.mission=mission - - -- Add mission to queue. - self:AddMission(mission) - end - - ]] - end end @@ -518,9 +499,13 @@ function CHIEF:onafterStatus(From, Event, To) -- Info General --- - local Nassets=self.wingcommander:CountAssets() + local Nassets=self.commander:CountAssets() + local Ncontacts=#self.contacts + local Nmissions=#self.missionqueue + local Ntargets=#self.targetqueue - local text=string.format("Defcon=%s Assets=%d, Contacts: Total=%d Yellow=%d Red=%d, Targets=%d, Missions=%d", self.Defcon, Nassets, #self.Contacts, Nyellow, Nred, #self.targetqueue, #self.missionqueue) + -- Info message + local text=string.format("Defcon=%s Assets=%d, Contacts: Total=%d Yellow=%d Red=%d, Targets=%d, Missions=%d", self.Defcon, Nassets, Ncontacts, Nyellow, Nred, Ntargets, Nmissions) self:I(self.lid..text) --- @@ -529,15 +514,16 @@ function CHIEF:onafterStatus(From, Event, To) local text="Assets:" for _,missiontype in pairs(AUFTRAG.Type) do - local N=self.wingcommander:CountAssets(nil, missiontype) + local N=self.commander:CountAssets(nil, missiontype) if N>0 then text=text..string.format("\n- %s %d", missiontype, N) end end self:I(self.lid..text) + local text="Assets:" for _,attribute in pairs(WAREHOUSE.Attribute) do - local N=self.wingcommander:CountAssets(nil, nil, attribute) + 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) end @@ -609,9 +595,9 @@ end -- @param Ops.Auftrag#AUFTRAG Mission The mission. function CHIEF:onafterAssignMissionAirforce(From, Event, To, Mission) - if self.wingcommander then + if self.commander then self:I(self.lid..string.format("Assigning mission %s (%s) to WINGCOMMANDER", Mission.name, Mission.type)) - self.wingcommander:AddMission(Mission) + self.commander:AddMission(Mission) else self:E(self.lid..string.format("Mission cannot be assigned as no WINGCOMMANDER is defined.")) end @@ -626,6 +612,7 @@ end -- @param Ops.Auftrag#AUFTRAG Mission The mission. 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)) if Mission.status==AUFTRAG.Status.PLANNED then @@ -786,14 +773,14 @@ function CHIEF:CheckMissionQueue() local mission=_mission --Ops.Auftrag#AUFTRAG -- We look for PLANNED missions. - if mission.status==AUFTRAG.Status.PLANNED then + if mission:IsPlanned() then --- -- PLANNNED Mission --- -- Check if there is an airwing that can do the mission. - local airwing=self:GetAirwingForMission(mission) + local legions=self.commander:GetLegionsForMission(mission) if airwing then @@ -821,11 +808,12 @@ end --- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets. -- @param #CHIEF self -- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @return Ops.AirWing#AIRWING The airwing best for this mission. +-- @return #table The best LEGIONs for this mission or `nil`. function CHIEF:GetAirwingForMission(Mission) - if self.wingcommander then - return self.wingcommander:GetAirwingForMission(Mission) + if self.commander then + local legions=self.commander:GetLegionsForMission(Mission) + return legions end return nil diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index e6e04cc93..596c86b78 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -778,7 +778,7 @@ end --- Get assets for a mission. -- @param #COHORT self -- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @param #number Nplayloads Number of payloads available. +-- @param #number Npayloads Number of payloads available. -- @return #table Assets that can do the required mission. function COHORT:RecruitAssets(Mission, Npayloads) @@ -845,6 +845,8 @@ function COHORT:RecruitAssets(Mission, Npayloads) if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() or flightgroup:IsDead() or flightgroup:IsStopped() then combatready=false end + + --TODO: Check transport for combat readyness! -- This asset is "combatready". if combatready then diff --git a/Moose Development/Moose/Ops/WingCommander.lua b/Moose Development/Moose/Ops/Commander.lua similarity index 57% rename from Moose Development/Moose/Ops/WingCommander.lua rename to Moose Development/Moose/Ops/Commander.lua index 144ae33c1..240c5f522 100644 --- a/Moose Development/Moose/Ops/WingCommander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -1,9 +1,9 @@ ---- **Ops** - Commander Air Wing. +--- **Ops** - Commander of an Airwing, Brigade or Flotilla. -- -- **Main Features:** -- --- * Manages AIRWINGS --- * Handles missions (AUFTRAG) and finds the best airwing able to do the job +-- * Manages AIRWINGS, BRIGADEs and FLOTILLAs +-- * Handles missions (AUFTRAG) and finds the best airwing for the job -- -- === -- @@ -12,12 +12,12 @@ -- @image OPS_WingCommander.png ---- WINGCOMMANDER class. --- @type WINGCOMMANDER +--- COMMANDER class. +-- @type COMMANDER -- @field #string ClassName Name of the class. --- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. --- @field #table airwings Table of airwings which are commanded. +-- @field #table legions Table of legions which are commanded. -- @field #table missionqueue Mission queue. -- @field Ops.ChiefOfStaff#CHIEF chief Chief of staff. -- @extends Core.Fsm#FSM @@ -26,25 +26,23 @@ -- -- === -- --- ![Banner Image](..\Presentations\WingCommander\WINGCOMMANDER_Main.jpg) --- --- # The WINGCOMMANDER Concept +-- # The COMMANDER Concept -- --- A wing commander is the head of airwings. He will find the best AIRWING to perform an assigned AUFTRAG (mission). +-- A wing commander is the head of legions. He will find the best AIRWING to perform an assigned AUFTRAG (mission). -- -- --- @field #WINGCOMMANDER -WINGCOMMANDER = { - ClassName = "WINGCOMMANDER", +-- @field #COMMANDER +COMMANDER = { + ClassName = "COMMANDER", Debug = nil, lid = nil, - airwings = {}, + legions = {}, missionqueue = {}, } ---- WINGCOMMANDER class version. +--- COMMANDER class version. -- @field #string version -WINGCOMMANDER.version="0.1.0" +COMMANDER.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -57,65 +55,88 @@ WINGCOMMANDER.version="0.1.0" -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new WINGCOMMANDER object and start the FSM. --- @param #WINGCOMMANDER self --- @return #WINGCOMMANDER self -function WINGCOMMANDER:New() +--- Create a new COMMANDER object and start the FSM. +-- @param #COMMANDER self +-- @return #COMMANDER self +function COMMANDER:New() -- Inherit everything from INTEL class. - local self=BASE:Inherit(self, FSM:New()) --#WINGCOMMANDER + local self=BASE:Inherit(self, FSM:New()) --#COMMANDER - self.lid="WINGCOMMANDER | " + -- Log ID. + self.lid="COMMANDER | " -- Start state. self:SetStartState("NotReadyYet") -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("NotReadyYet", "Start", "OnDuty") -- Start WC. + self:AddTransition("NotReadyYet", "Start", "OnDuty") -- Start COMMANDER. self:AddTransition("*", "Status", "*") -- Status report. - self:AddTransition("*", "Stop", "Stopped") -- Stop WC. + self:AddTransition("*", "Stop", "Stopped") -- Stop COMMANDER. - self:AddTransition("*", "AssignMission", "*") -- Mission was assigned to an AIRWING. + self:AddTransition("*", "MissionAssign", "*") -- Mission was assigned to a LEGION. self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. ------------------------ --- Pseudo Functions --- ------------------------ - --- Triggers the FSM event "Start". Starts the WINGCOMMANDER. Initializes parameters and starts event handlers. - -- @function [parent=#WINGCOMMANDER] Start - -- @param #WINGCOMMANDER self + --- Triggers the FSM event "Start". Starts the COMMANDER. + -- @function [parent=#COMMANDER] Start + -- @param #COMMANDER self - --- Triggers the FSM event "Start" after a delay. Starts the WINGCOMMANDER. Initializes parameters and starts event handlers. - -- @function [parent=#WINGCOMMANDER] __Start - -- @param #WINGCOMMANDER self + --- Triggers the FSM event "Start" after a delay. Starts the COMMANDER. + -- @function [parent=#COMMANDER] __Start + -- @param #COMMANDER self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Stop". Stops the WINGCOMMANDER and all its event handlers. - -- @param #WINGCOMMANDER self + --- Triggers the FSM event "Stop". Stops the COMMANDER. + -- @param #COMMANDER self - --- Triggers the FSM event "Stop" after a delay. Stops the WINGCOMMANDER and all its event handlers. - -- @function [parent=#WINGCOMMANDER] __Stop - -- @param #WINGCOMMANDER self + --- Triggers the FSM event "Stop" after a delay. Stops the COMMANDER. + -- @function [parent=#COMMANDER] __Stop + -- @param #COMMANDER self -- @param #number delay Delay in seconds. --- Triggers the FSM event "Status". - -- @function [parent=#WINGCOMMANDER] Status - -- @param #WINGCOMMANDER self + -- @function [parent=#COMMANDER] Status + -- @param #COMMANDER self --- Triggers the FSM event "Status" after a delay. - -- @function [parent=#WINGCOMMANDER] __Status - -- @param #WINGCOMMANDER self + -- @function [parent=#COMMANDER] __Status + -- @param #COMMANDER self -- @param #number delay Delay in seconds. - -- Debug trace. - if false then - BASE:TraceOnOff(true) - BASE:TraceClass(self.ClassName) - BASE:TraceLevel(1) - end + --- Triggers the FSM event "MissionAssign". + -- @function [parent=#COMMANDER] MissionAssign + -- @param #COMMANDER self + -- @param Ops.Legion#LEGION Legion The Legion. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "MissionAssign" event. + -- @function [parent=#COMMANDER] OnAfterMissionAssign + -- @param #COMMANDER 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. + + + --- Triggers the FSM event "MissionCancel". + -- @function [parent=#COMMANDER] MissionCancel + -- @param #COMMANDER self + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "MissionCancel" event. + -- @function [parent=#COMMANDER] OnAfterMissionCancel + -- @param #COMMANDER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. return self end @@ -125,26 +146,28 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Add an airwing to the wingcommander. --- @param #WINGCOMMANDER self +-- @param #COMMANDER self -- @param Ops.AirWing#AIRWING Airwing The airwing to add. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:AddAirwing(Airwing) +-- @return #COMMANDER self +function COMMANDER:AddAirwing(Airwing) -- This airwing is managed by this wing commander. - Airwing.wingcommander=self + Airwing.commander=self - table.insert(self.airwings, Airwing) + table.insert(self.legions, Airwing) return self end --- Add mission to mission queue. --- @param #WINGCOMMANDER self +-- @param #COMMANDER self -- @param Ops.Auftrag#AUFTRAG Mission Mission to be added. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:AddMission(Mission) +-- @return #COMMANDER self +function COMMANDER:AddMission(Mission) - Mission.wingcommander=self + Mission.commander=self + + Mission.statusCommander=AUFTRAG.Status.PLANNED table.insert(self.missionqueue, Mission) @@ -152,17 +175,17 @@ function WINGCOMMANDER:AddMission(Mission) end --- Remove mission from queue. --- @param #WINGCOMMANDER self +-- @param #COMMANDER self -- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:RemoveMission(Mission) +-- @return #COMMANDER self +function COMMANDER:RemoveMission(Mission) for i,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG if mission.auftragsnummer==Mission.auftragsnummer then self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status)) - mission.wingcommander=nil + mission.commander=nil table.remove(self.missionqueue, i) break end @@ -177,22 +200,22 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. --- @param #WINGCOMMANDER self +-- @param #COMMANDER self -- @param Wrapper.Group#GROUP Group Flight group. -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function WINGCOMMANDER:onafterStart(From, Event, To) +function COMMANDER:onafterStart(From, Event, To) -- Short info. - local text=string.format("Starting Wing Commander") + local text=string.format("Starting Commander") self:I(self.lid..text) - -- Start attached airwings. - for _,_airwing in pairs(self.airwings) do - local airwing=_airwing --Ops.AirWing#AIRWING - if airwing:GetState()=="NotReadyYet" then - airwing:Start() + -- Start attached legions. + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + if legion:GetState()=="NotReadyYet" then + legion:Start() end end @@ -200,12 +223,12 @@ function WINGCOMMANDER:onafterStart(From, Event, To) end --- On after "Status" event. --- @param #WINGCOMMANDER self +-- @param #COMMANDER self -- @param Wrapper.Group#GROUP Group Flight group. -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function WINGCOMMANDER:onafterStatus(From, Event, To) +function COMMANDER:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() @@ -214,13 +237,13 @@ function WINGCOMMANDER:onafterStatus(From, Event, To) self:CheckMissionQueue() -- Status. - local text=string.format(self.lid.."Status %s: Airwings=%d, Missions=%d", fsmstate, #self.airwings, #self.missionqueue) + local text=string.format("Status %s: Airwings=%d, Missions=%d", fsmstate, #self.legions, #self.missionqueue) self:I(self.lid..text) -- Airwing Info - if #self.airwings>0 then + if #self.legions>0 then local text="Airwings:" - for _,_airwing in pairs(self.airwings) do + for _,_airwing in pairs(self.legions) do local airwing=_airwing --Ops.AirWing#AIRWING local Nassets=airwing:CountAssets() local Nastock=airwing:CountAssets(true) @@ -259,40 +282,57 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after "AssignMission" event. Mission is added to the AIRWING mission queue. --- @param #WINGCOMMANDER self +--- On after "MissionAssign" event. Mission is added to a LEGION mission queue. +-- @param #COMMANDER self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.AirWing#AIRWING Airwing The AIRWING. +-- @param Ops.Legion#LEGION Legion The LEGION. -- @param Ops.Auftrag#AUFTRAG Mission The mission. -function WINGCOMMANDER:onafterAssignMission(From, Event, To, Airwing, Mission) +function COMMANDER:onafterMissionAssign(From, Event, To, Legion, Mission) - self:I(self.lid..string.format("Assigning mission %s (%s) to airwing %s", Mission.name, Mission.type, Airwing.alias)) - Airwing:AddMission(Mission) + -- Debug info. + self:I(self.lid..string.format("Assigning mission %s (%s) to legion %s", Mission.name, Mission.type, Legion.alias)) + + -- Set mission commander status to QUEUED as it is now queued at a legion. + Mission.statusCommander=AUFTRAG.Status.QUEUED + + -- Add mission to legion. + Legion:AddMission(Mission) end --- On after "MissionCancel" event. --- @param #WINGCOMMANDER self +-- @param #COMMANDER self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.Auftrag#AUFTRAG Mission The mission. -function WINGCOMMANDER:onafterMissionCancel(From, Event, To, Mission) +function COMMANDER: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)) - if Mission.status==AUFTRAG.Status.PLANNED then + -- Set commander status. + Mission.statusCommander=AUFTRAG.Status.CANCELLED - -- Mission is still in planning stage. Should not have an airbase assigned ==> Just remove it form the queue. + if Mission:IsPlanned() then + + -- Mission is still in planning stage. Should not have a legion assigned ==> Just remove it form the queue. self:RemoveMission(Mission) else - -- Airwing will cancel mission. - if Mission.airwing then - Mission.airwing:MissionCancel(Mission) + -- Legion will cancel mission. + if #Mission.legions>0 then + for _,_legion in pairs(Mission.legions) do + local legion=_legion --Ops.Legion#LEGION + + -- TODO: Should check that this legions actually belongs to this commander. + + -- Legion will cancel the mission. + legion:MissionCancel(Mission) + end end end @@ -304,8 +344,8 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Check mission queue and assign ONE planned mission. --- @param #WINGCOMMANDER self -function WINGCOMMANDER:CheckMissionQueue() +-- @param #COMMANDER self +function COMMANDER:CheckMissionQueue() -- TODO: Sort mission queue. wrt what? Threat level? @@ -319,12 +359,16 @@ function WINGCOMMANDER:CheckMissionQueue() -- PLANNNED Mission --- - local airwing=self:GetAirwingForMission(mission) + local airwings=self:GetLegionsForMission(mission) - if airwing then + if airwings then - -- Add mission to airwing. - self:AssignMission(airwing, mission) + for _,airwing in pairs(airwings) do + + -- Add mission to airwing. + self:MissionAssign(airwing, mission) + + end return end @@ -341,24 +385,24 @@ function WINGCOMMANDER:CheckMissionQueue() end ---- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets. --- @param #WINGCOMMANDER self +--- Check all legions if they are able to do a specific mission type at a certain location with a given number of assets. +-- @param #COMMANDER self -- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @return Ops.AirWing#AIRWING The airwing best for this mission. -function WINGCOMMANDER:GetAirwingForMission(Mission) +-- @return #table Table of LEGIONs that can do the mission and have at least one asset available right now. +function COMMANDER:GetLegionsForMission(Mission) - -- Table of airwings that can do the mission. - local airwings={} + -- Table of legions that can do the mission. + local legions={} - -- Loop over all airwings. - for _,_airwing in pairs(self.airwings) do + -- Loop over all legions. + for _,_airwing in pairs(self.legions) do local airwing=_airwing --Ops.AirWing#AIRWING -- Check if airwing can do this mission. local can,assets=airwing:CanMission(Mission) - -- Can it? - if can then + -- Has it assets that can? + if #assets>0 then -- Get coordinate of the target. local coord=Mission:GetTargetCoordinate() @@ -366,10 +410,16 @@ function WINGCOMMANDER:GetAirwingForMission(Mission) if coord then -- Distance from airwing to target. - local dist=UTILS.MetersToNM(coord:Get2DDistance(airwing:GetCoordinate())) + local distance=UTILS.MetersToNM(coord:Get2DDistance(airwing:GetCoordinate())) + + -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 + local dist=UTILS.Round(distance/10, 0) + + -- Debug info. + self:I(self.lid..string.format("Got legion %s with Nassets=%d and dist=%.1f NM, rounded=%.1f", airwing.alias, #assets, distance, dist)) - -- Add airwing to table of airwings that can. - table.insert(airwings, {airwing=airwing, dist=dist, targetcoord=coord, nassets=#assets}) + -- Add airwing to table of legions that can. + table.insert(legions, {airwing=airwing, distance=distance, dist=dist, targetcoord=coord, nassets=#assets}) end @@ -378,7 +428,7 @@ function WINGCOMMANDER:GetAirwingForMission(Mission) end -- Can anyone? - if #airwings>0 then + if #legions>0 then --- Something like: -- * Closest airwing that can should be first prio. @@ -386,34 +436,61 @@ function WINGCOMMANDER:GetAirwingForMission(Mission) local function score(a) local d=math.round(a.dist/10) end + + env.info(self.lid.."FF #legions="..#legions) -- Sort table wrt distance and number of assets. -- Distances within 10 NM are equal and the airwing with more assets is preferred. local function sortdist(a,b) - local ad=math.round(a.dist/10) -- dist 55 NM ==> 5.5 ==> 6 - local bd=math.round(b.dist/10) -- dist 63 NM ==> 6.3 ==> 6 + local ad=a.dist + local bd=b.dist return adb.nassets) end - table.sort(airwings, sortdist) - - -- This is the closest airwing to the target. - local airwing=airwings[1].airwing --Ops.AirWing#AIRWING + table.sort(legions, sortdist) + - return airwing + -- Loops over all legions and stop if enough assets are summed up. + local selection={} ; local N=0 + for _,leg in ipairs(legions) do + local legion=leg.airwing --Ops.Legion#LEGION + + Mission.Nassets=Mission.Nassets or {} + Mission.Nassets[legion.alias]=leg.nassets + + table.insert(selection, legion) + + N=N+leg.nassets + + if N>=Mission.nassets then + self:I(self.lid..string.format("Found enough assets!")) + break + end + end + + if N>=Mission.nassets then + self:I(self.lid..string.format("Found %d legions that can do mission %s (%s) requiring %d assets", #selection, Mission:GetName(), Mission:GetType(), Mission.nassets)) + return selection + else + self:T(self.lid..string.format("Not enough LEGIONs found that could do the job :/")) + return nil + end + + else + self:T(self.lid..string.format("No LEGION found that could do the job :/")) end return nil end --- Check mission queue and assign ONE planned mission. --- @param #WINGCOMMANDER self +-- @param #COMMANDER self -- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. -- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. -- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. -- @return #number Amount of asset groups in stock. -function WINGCOMMANDER:CountAssets(InStock, MissionTypes, Attributes) +function COMMANDER:CountAssets(InStock, MissionTypes, Attributes) local N=0 - for _,_airwing in pairs(self.airwings) do + for _,_airwing in pairs(self.legions) do local airwing=_airwing --Ops.AirWing#AIRWING N=N+airwing:CountAssets(InStock, MissionTypes, Attributes) end diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index c626d1829..8c83911b0 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -107,6 +107,21 @@ function LEGION:New(WarehouseName, LegionName) -- @param #LEGION self -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "MissionCancel". + -- @function [parent=#LEGION] MissionCancel + -- @param #LEGION self + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "MissionCancel" event. + -- @function [parent=#LEGION] OnAfterMissionCancel + -- @param #LEGION self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + return self end @@ -129,8 +144,14 @@ end -- @return #LEGION self function LEGION:AddMission(Mission) - -- Set status to QUEUED. This also attaches the airwing to this mission. - Mission:Queued(self) + -- Set status to QUEUED. This event is only allowed for the first legion that calls it. + Mission:Queued() + + -- Set legion status. + Mission:SetLegionStatus(self, AUFTRAG.Status.QUEUED) + + -- Add legion to mission. + Mission:AddLegion(self) -- Add mission to queue. table.insert(self.missionqueue, Mission) @@ -153,7 +174,7 @@ function LEGION:RemoveMission(Mission) local mission=_mission --Ops.Auftrag#AUFTRAG if mission.auftragsnummer==Mission.auftragsnummer then - mission.airwing=nil + mission:RemoveLegion(self) table.remove(self.missionqueue, i) break end @@ -242,7 +263,7 @@ function LEGION:_CheckMissions() end --- Get next mission. -- @param #LEGION self --- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. +-- @return Ops.Auftrag#AUFTRAG Next mission or `#nil`. function LEGION:_GetNextMission() -- Number of missions. @@ -278,7 +299,7 @@ function LEGION:_GetNextMission() local mission=_mission --Ops.Auftrag#AUFTRAG -- Firstly, check if mission is due? - if mission:IsQueued() and mission:IsReadyToGo() and (mission.importance==nil or mission.importance<=vip) then + if mission:IsQueued(self) and mission:IsReadyToGo() and (mission.importance==nil or mission.importance<=vip) then -- Check if airwing can do the mission and gather required assets. local can, assets=self:CanMission(mission) @@ -333,10 +354,10 @@ function LEGION:_GetNextMission() if mission.assets and #mission.assets>0 then self:E(self.lid..string.format("ERROR: mission %s of type %s has already assets attached!", mission.name, mission.type)) end - mission.assets={} + --mission.assets={} -- Assign assets to mission. - for i=1,mission.nassets do + for i=1,mission.Nassets[self.alias] do local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem -- Should not happen as we just checked! @@ -350,7 +371,7 @@ function LEGION:_GetNextMission() -- Now return the remaining payloads. if self:IsAirwing() then - for i=mission.nassets+1,#assets do + for i=mission.Nassets[self.alias]+1,#assets do local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem for _,uid in pairs(gotpayload) do if uid==asset.uid then @@ -493,8 +514,11 @@ end -- @param Ops.Auftrag#AUFTRAG Mission The requested mission. function LEGION:onafterMissionRequest(From, Event, To, Mission) - -- Set mission status from QUEUED to REQUESTED. Ensures that it is not considered in the next selection. + -- Set mission status from QUEUED to REQUESTED. Mission:Requested() + + -- Set legion status. Ensures that it is not considered in the next selection. + Mission:SetLegionStatus(self, AUFTRAG.Status.REQUESTED) --- -- Some assets might already be spawned and even on a different mission (orbit). @@ -507,23 +531,28 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) for _,_asset in pairs(Mission.assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.spawned then - - if asset.flightgroup then - - -- Add new mission. - asset.flightgroup:AddMission(Mission) - - -- Trigger event. - self:__OpsOnMission(5, asset.flightgroup, Mission) + -- Check that this asset belongs to this Legion warehouse. + if asset.wid==self.uid then + if asset.spawned then + + if asset.flightgroup then + + -- Add new mission. + asset.flightgroup:AddMission(Mission) + + -- Trigger event. + self:__OpsOnMission(5, asset.flightgroup, Mission) + + else + self:E(self.lid.."ERROR: flight group for asset does NOT exist!") + end + else - self:E(self.lid.."ERROR: flight group for asset does NOT exist!") + -- These assets need to be requested and spawned. + table.insert(Assetlist, asset) end - - else - -- These assets need to be requested and spawned. - table.insert(Assetlist, asset) + end end @@ -548,7 +577,7 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, Assetlist, #Assetlist, nil, nil, Mission.prio, tostring(Mission.auftragsnummer)) -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. - Mission.requestID=self.queueid + Mission.requestID[self.alias]=self.queueid end end @@ -564,8 +593,31 @@ function LEGION:onafterMissionCancel(From, Event, To, Mission) -- Info message. self:I(self.lid..string.format("Cancel mission %s", Mission.name)) - local Ngroups = Mission:CountOpsGroups() + -- Set status to cancelled. + Mission:SetLegionStatus(self, AUFTRAG.Status.CANCELLED) + for _,_asset in pairs(Mission.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Asset should belong to this legion. + if asset.wid==self.uid then + + local opsgroup=asset.flightgroup + + if opsgroup then + opsgroup:MissionCancel(Mission) + end + + -- TODO: remove asset from mission + + -- Not requested any more (if it was). + asset.requested=nil + + end + end + + + --[[ if Mission:IsPlanned() or Mission:IsQueued() or Mission:IsRequested() or Ngroups == 0 then Mission:Done() @@ -574,22 +626,28 @@ function LEGION:onafterMissionCancel(From, Event, To, Mission) for _,_asset in pairs(Mission.assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Asset should belong to this legion. + if asset.wid==self.uid then - local flightgroup=asset.flightgroup - - if flightgroup then - flightgroup:MissionCancel(Mission) - end - - -- Not requested any more (if it was). - asset.requested=nil + local opsgroup=asset.flightgroup + + if opsgroup then + opsgroup:MissionCancel(Mission) + end + + -- Not requested any more (if it was). + asset.requested=nil + + end end end + ]] -- Remove queued request (if any). - if Mission.requestID then - self:_DeleteQueueItemByID(Mission.requestID, self.queue) + if Mission.requestID[self.alias] then + self:_DeleteQueueItemByID(Mission.requestID[self.alias], self.queue) end end @@ -1186,7 +1244,7 @@ function LEGION:CountAssetsOnMission(MissionTypes, Cohort) if Cohort==nil or Cohort.name==asset.squadname then - local request, isqueued=self:GetRequestByID(mission.requestID) + local request, isqueued=self:GetRequestByID(mission.requestID[self.alias]) if isqueued then Nq=Nq+1 @@ -1277,6 +1335,11 @@ function LEGION:CanMission(Mission) -- Squadrons for the job. If user assigned to mission or simply all. local cohorts=Mission.squadrons or self.cohorts + + local Nassets=Mission.nassets or 1 + if Mission.Nassets and Mission.Nassets[self.alias] then + Nassets=Mission.Nassets[self.alias] + end -- Get aircraft unit types for the job. local unittypes=self:GetAircraftTypes(true, cohorts) @@ -1285,7 +1348,7 @@ function LEGION:CanMission(Mission) if self:IsAirwing() then local Npayloads=self:CountPayloadsInStock(Mission.type, unittypes, Mission.payloads) - if Npayloads#Assets then + if Nassets>#Assets then self:T(self.lid..string.format("INFO: Not enough assets available! Got %d but need at least %d", #Assets, Mission.nassets)) Can=false end @@ -1436,7 +1500,8 @@ end function LEGION:GetMissionFromRequestID(RequestID) for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - if mission.requestID and mission.requestID==RequestID then + local mid=mission.requestID[self.alias] + if mid and mid==RequestID then return mission end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 58fab32b5..194dac649 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -646,6 +646,20 @@ function OPSGROUP:New(group) -- @param #OPSGROUP self -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "MissionCancel". + -- @function [parent=#OPSGROUP] MissionCancel + -- @param #CHIEF self + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "MissionCancel" event. + -- @function [parent=#OPSGROUP] OnAfterMissionCancel + -- @param #OPSGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + -- TODO: Add pseudo functions. return self diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index ae1a85fc9..113667df3 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -78,11 +78,15 @@ Ops/Auftrag.lua Ops/OpsGroup.lua Ops/FlightGroup.lua Ops/NavyGroup.lua +Ops/Cohort.lua Ops/Squadron.lua +Ops/Platoon.lua +Ops/Legion.lua Ops/AirWing.lua +Ops/Brigade.lua Ops/Intelligence.lua -Ops/WingCommander.lua -Ops/ChiefOfStaff.lua +Ops/Commander.lua +Ops/Chief.lua Ops/FlightControl.lua Ops/CSAR.lua Ops/CTLD.lua From d73ebaca76ad14843eef8fcb72d1c76a1a21eb12 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 21 Aug 2021 00:58:28 +0200 Subject: [PATCH 075/141] OPS - Lots of stuff --- Moose Development/Moose/Ops/ArmyGroup.lua | 31 +- Moose Development/Moose/Ops/Auftrag.lua | 119 +++++- Moose Development/Moose/Ops/Brigade.lua | 15 + Moose Development/Moose/Ops/Cohort.lua | 13 +- Moose Development/Moose/Ops/Commander.lua | 117 +++--- Moose Development/Moose/Ops/FlightGroup.lua | 92 ++--- Moose Development/Moose/Ops/Legion.lua | 404 ++++++++++++++----- Moose Development/Moose/Ops/NavyGroup.lua | 29 +- Moose Development/Moose/Ops/OpsGroup.lua | 167 ++++++-- Moose Development/Moose/Ops/OpsTransport.lua | 45 ++- 10 files changed, 731 insertions(+), 301 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 8143106e1..4dd1d2bd5 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -156,7 +156,7 @@ function ARMYGROUP:New(group) --self:HandleEvent(EVENTS.Hit, self.OnEventHit) -- Start the status monitoring. - self:__Status(-1) + self.timerStatus=TIMER:New(self.Status, self):Start(1, 30) -- Start queue update timer. self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(2, 5) @@ -343,30 +343,17 @@ end -- Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----- Update status. --- @param #ARMYGROUP self -function ARMYGROUP:onbeforeStatus(From, Event, To) - - if self:IsDead() then - self:T(self.lid..string.format("Onbefore Status DEAD ==> false")) - return false - elseif self:IsStopped() then - self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) - return false - end - - return true -end - --- Update status. -- @param #ARMYGROUP self -function ARMYGROUP:onafterStatus(From, Event, To) +function ARMYGROUP:Status() -- FSM state. local fsmstate=self:GetState() local alive=self:IsAlive() + env.info(self.lid.."FF status="..fsmstate) + if alive then --- @@ -490,9 +477,6 @@ function ARMYGROUP:onafterStatus(From, Event, To) self:_PrintTaskAndMissionStatus() - - -- Next status update. - self:__Status(-30) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -596,9 +580,6 @@ function ARMYGROUP:onafterSpawned(From, Event, To) self:FullStop() end - -- Update status. - self:__Status(-0.1) - end end @@ -652,7 +633,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) wp.speed=UTILS.KnotsToMps(Speed) else -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. - if self.adinfinitum and wp.speed<0.1 then + if wp.speed<0.1 then --self.adinfinitum and wp.speed=UTILS.KmphToMps(self.speedCruise) end end @@ -1156,7 +1137,7 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation -- Check if final waypoint is still passed. if wpnumber>self.currentwp then - self.passedfinalwp=false + self:_PassedFinalWaypoint(false, "ARMYGROUP.AddWaypoint: wpnumber>self.currentwp") end -- Speed in knots. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index d26c73f8e..bdc45fdad 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -90,6 +90,7 @@ -- @field Core.Set#SET_GROUP transportGroupSet Groups to be transported. -- @field Core.Point#COORDINATE transportPickup Coordinate where to pickup the cargo. -- @field Core.Point#COORDINATE transportDropoff Coordinate where to drop off the cargo. +-- @field Ops.OpsTransport#OPSTRANSPORT opstransport OPS transport assignment. -- -- @field #number artyRadius Radius in meters. -- @field #number artyShots Number of shots fired. @@ -328,6 +329,7 @@ _AUFTRAGSNR=0 -- @field #string TROOPTRANSPORT Troop transport mission. -- @field #string ARTY Fire at point. -- @field #string PATROLZONE Patrol a zone. +-- @field #string OPSTRANSPORT Ops transport. AUFTRAG.Type={ ANTISHIP="Anti Ship", AWACS="AWACS", @@ -352,6 +354,7 @@ AUFTRAG.Type={ TROOPTRANSPORT="Troop Transport", ARTY="Fire At Point", PATROLZONE="Patrol Zone", + OPSTRANSPORT="Ops Transport", } --- Mission status. @@ -1171,6 +1174,48 @@ function AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet, DropoffCoordinate, PickupC return mission end + +--- Create a OPS TRANSPORT mission. +-- @param #AUFTRAG self +-- @param Core.Set#SET_GROUP CargoGroupSet The set group(s) to be transported. +-- @param Core.Zone#ZONE PickupZone Pick up zone +-- @param Core.Zone#ZONE DeployZone Deploy zone +-- @return #AUFTRAG self +function AUFTRAG:NewOPSTRANSPORT(CargoGroupSet, PickupZone, DeployZone) + + local mission=AUFTRAG:New(AUFTRAG.Type.OPSTRANSPORT) + + mission.transportGroupSet=CargoGroupSet + + mission:_TargetFromObject(mission.transportGroupSet) + + --mission.transportPickup=PickupCoordinate or mission:GetTargetCoordinate() + --mission.transportDropoff=DropoffCoordinate + + -- Debug. + --mission.transportPickup:MarkToAll("Pickup") + --mission.transportDropoff:MarkToAll("Drop off") + + mission.opstransport=OPSTRANSPORT:New(CargoGroupSet, PickupZone, DeployZone) + + function mission.opstransport:OnAfterExecuting(From, Event, To) + mission:Executing() + end + + function mission.opstransport:OnAfterDelivered(From, Event, To) + mission:Done() + end + + -- TODO: what's the best ROE here? + mission.optionROE=ENUMS.ROE.ReturnFire + mission.optionROT=ENUMS.ROT.PassiveDefense + + mission.DCStask=mission:GetDCSMissionTask() + + return mission +end + + --- Create an ARTY mission. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Target Center of the firing solution. @@ -1211,6 +1256,11 @@ function AUFTRAG:NewPATROLZONE(Zone, Speed, Altitude) local mission=AUFTRAG:New(AUFTRAG.Type.PATROLZONE) + -- Ensure we got a ZONE and not just the zone name. + if type(Zone)=="string" then + Zone=ZONE:New(Zone) + end + mission:_TargetFromObject(Zone) mission.optionROE=ENUMS.ROE.OpenFire @@ -1541,14 +1591,14 @@ end --- Get number of required assets. -- @param #AUFTRAG self --- @param Ops.Legion#Legion Legion (Optional) Only get the required assets for a specific legion. --- @param #number Number of required assets. +-- @param Ops.Legion#Legion Legion (Optional) Only get the required assets for a specific legion. If required assets for this legion are not defined, the total number is returned. +-- @return #number Number of required assets. function AUFTRAG:GetRequiredAssets(Legion) local N=self.nassets - if Legion then - N=self.Nassets[Legion.alias] or 0 + if Legion and self.Nassets[Legion.alias] then + N=self.Nassets[Legion.alias] end return N @@ -1669,6 +1719,43 @@ function AUFTRAG:SetMissionRange(Range) return self end +--- Attach OPS transport to the mission. Mission assets will be transported before the mission is started at the OPSGROUP level. +-- @param #AUFTRAG self +-- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The OPS transport assignment attached to the mission. +-- @return #AUFTRAG self +function AUFTRAG:SetOpsTransport(OpsTransport) + self.opstransport=OpsTransport + return self +end + +--- Attach OPS transport to the mission. Mission assets will be transported before the mission is started at the OPSGROUP level. +-- @param #AUFTRAG self +-- @param Core.Zone#ZONE PickupZone Zone where assets are picked up. +-- @param Core.Zone#ZONE DeployZone Zone where assets are deployed. +-- @param Core.Set#SET_OPSGROUP Carriers Set of carriers. Can also be a single group. Can also be added via the AddTransportCarriers functions. +-- @return #AUFTRAG self +function AUFTRAG:SetTransportForAssets(PickupZone, DeployZone, Carriers) + + -- OPS transport from pickup to deploy zone. + self.opstransport=OPSTRANSPORT:New(nil, PickupZone, DeployZone) + + if Carriers then + if Carriers:IsInstanceOf("SET_OPSGROUP") then + + for _,_carrier in pairs(Carriers.Set) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + carrier:AddOpsTransport(self.opstransport) + end + + elseif Carriers:IsInstanceOf("OPSGROUP") then + Carriers:AddOpsTransport(self.opstransport) + end + + end + + return self +end + --- Set Rules of Engagement (ROE) for this mission. -- @param #AUFTRAG self -- @param #string roe Mission ROE. @@ -1959,6 +2046,11 @@ function AUFTRAG:AddOpsGroup(OpsGroup) groupdata.waypointtask=nil self.groupdata[OpsGroup.groupname]=groupdata + + -- Add ops transport to new group. + if self.opstransport then + self.opstransport:AddCargoGroups(OpsGroup) + end return self end @@ -2237,7 +2329,7 @@ function AUFTRAG:onafterStatus(From, Event, To) elseif (self.Tstop and Tnow>self.Tstop+10) or (Ntargets0>0 and Ntargets==0) then -- Cancel mission if stop time passed. - self:Cancel() + --self:Cancel() end @@ -3763,6 +3855,23 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) table.insert(DCStasks, TaskEmbark) table.insert(DCStasks, TaskDisEmbark) + elseif self.type==AUFTRAG.Type.OPSTRANSPORT then + + -------------------------- + -- OPSTRANSPORT Mission -- + -------------------------- + + local DCStask={} + + DCStask.id="OpsTransport" + + -- We create a "fake" DCS task and pass the parameters to the FLIGHTGROUP. + local param={} + DCStask.params=param + + table.insert(DCStasks, DCStask) + + elseif self.type==AUFTRAG.Type.RESCUEHELO then ------------------------- diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index d12483cf6..70d5505cd 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -243,6 +243,21 @@ function BRIGADE:onafterStatus(From, Event, To) self:I(self.lid..text) end + ---------------- + -- Transport --- + ---------------- + + -- Check if any transports should be cancelled. + --self:_CheckTransports() + + -- Get next mission. + local transport=self:_GetNextTransport() + + -- Request mission execution. + if transport then + self:TransportRequest(transport) + end + -------------- -- Mission --- -------------- diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 596c86b78..37d6ad914 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -842,10 +842,21 @@ function COHORT:RecruitAssets(Mission, Npayloads) end -- Check if in a state where we really do not want to fight any more. - if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() or flightgroup:IsDead() or flightgroup:IsStopped() then + if flightgroup:IsFlightgroup() then + if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() then + combatready=false + end + else + if flightgroup:IsRearming() or flightgroup:IsRetreating() or flightgroup:IsReturning() then + combatready=false + end + end + -- Applies to all opsgroups. + if flightgroup:IsDead() or flightgroup:IsStopped() then combatready=false end + --TODO: Check transport for combat readyness! -- This asset is "combatready". diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 240c5f522..9c5781277 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -1,15 +1,15 @@ ---- **Ops** - Commander of an Airwing, Brigade or Flotilla. +--- **Ops** - Commander of Airwings, Brigades and Flotillas. -- -- **Main Features:** -- -- * Manages AIRWINGS, BRIGADEs and FLOTILLAs --- * Handles missions (AUFTRAG) and finds the best airwing for the job +-- * Handles missions (AUFTRAG) and finds the best man for the job -- -- === -- -- ### Author: **funkyfranky** --- @module Ops.WingCommander --- @image OPS_WingCommander.png +-- @module Ops.Commander +-- @image OPS_Commander.png --- COMMANDER class. @@ -28,7 +28,7 @@ -- -- # The COMMANDER Concept -- --- A wing commander is the head of legions. He will find the best AIRWING to perform an assigned AUFTRAG (mission). +-- A commander is the head of legions. He will find the best LEGIONs to perform an assigned AUFTRAG (mission). -- -- -- @field #COMMANDER @@ -36,7 +36,7 @@ COMMANDER = { ClassName = "COMMANDER", Debug = nil, lid = nil, - legions = {}, + legions = {}, missionqueue = {}, } @@ -48,7 +48,8 @@ COMMANDER.version="0.1.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Improve airwing selection. Mostly done! +-- TODO: Improve legion selection. Mostly done! +-- TODO: Allow multiple Legions for one mission. -- NOGO: Maybe it's possible to preselect the assets for the mission. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -145,16 +146,29 @@ end -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Add an airwing to the wingcommander. +--- Add an AIRWING to the commander. -- @param #COMMANDER self -- @param Ops.AirWing#AIRWING Airwing The airwing to add. -- @return #COMMANDER self function COMMANDER:AddAirwing(Airwing) - -- This airwing is managed by this wing commander. - Airwing.commander=self + -- Add legion. + self:AddLegion(Airwing) + + return self +end - table.insert(self.legions, Airwing) +--- Add a LEGION to the commander. +-- @param #COMMANDER self +-- @param Ops.Legion#LEGION Legion The legion to add. +-- @return #COMMANDER self +function COMMANDER:AddLegion(Legion) + + -- This legion is managed by the commander. + Legion.commander=self + + -- Add to legions. + table.insert(self.legions, Legion) return self end @@ -237,21 +251,21 @@ function COMMANDER:onafterStatus(From, Event, To) self:CheckMissionQueue() -- Status. - local text=string.format("Status %s: Airwings=%d, Missions=%d", fsmstate, #self.legions, #self.missionqueue) + local text=string.format("Status %s: Legions=%d, Missions=%d", fsmstate, #self.legions, #self.missionqueue) self:I(self.lid..text) - -- Airwing Info + -- Legion info. if #self.legions>0 then - local text="Airwings:" - for _,_airwing in pairs(self.legions) do - local airwing=_airwing --Ops.AirWing#AIRWING - local Nassets=airwing:CountAssets() - local Nastock=airwing:CountAssets(true) - text=text..string.format("\n* %s [%s]: Assets=%s stock=%s", airwing.alias, airwing:GetState(), Nassets, Nastock) + local text="Legions:" + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + local Nassets=legion:CountAssets() + local Nastock=legion:CountAssets(true) + text=text..string.format("\n* %s [%s]: Assets=%s stock=%s", legion.alias, legion:GetState(), Nassets, Nastock) for _,aname in pairs(AUFTRAG.Type) do - local na=airwing:CountAssets(true, {aname}) - local np=airwing:CountPayloadsInStock({aname}) - local nm=airwing:CountAssetsOnMission({aname}) + local na=legion:CountAssets(true, {aname}) + local np=legion:CountPayloadsInStock({aname}) + local nm=legion:CountAssetsOnMission({aname}) if na>0 or np>0 then text=text..string.format("\n - %s: assets=%d, payloads=%d, on mission=%d", aname, na, np, nm) end @@ -353,23 +367,26 @@ function COMMANDER:CheckMissionQueue() local mission=_mission --Ops.Auftrag#AUFTRAG -- We look for PLANNED missions. - if mission.status==AUFTRAG.Status.PLANNED then + if mission:IsPlanned() then --- -- PLANNNED Mission --- - local airwings=self:GetLegionsForMission(mission) + -- Get legions for mission. + local legions=self:GetLegionsForMission(mission) - if airwings then + if legions then - for _,airwing in pairs(airwings) do + for _,_legion in pairs(legions) do + local legion=_legion --Ops.Legion#LEGION - -- Add mission to airwing. - self:MissionAssign(airwing, mission) + -- Add mission to legion. + self:MissionAssign(legion, mission) end + -- Only ONE mission is assigned. return end @@ -395,31 +412,36 @@ function COMMANDER:GetLegionsForMission(Mission) local legions={} -- Loop over all legions. - for _,_airwing in pairs(self.legions) do - local airwing=_airwing --Ops.AirWing#AIRWING + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION - -- Check if airwing can do this mission. - local can,assets=airwing:CanMission(Mission) + -- Count number of assets in stock. + local Nassets=0 + if legion:IsAirwing() then + Nassets=legion:CountAssetsWithPayloadsInStock(Mission.payloads, {Mission.type}, Attributes) + else + Nassets=legion:CountAssets(true, {Mission.type}, Attributes) --Could also specify the attribute if Air or Ground mission. + end -- Has it assets that can? - if #assets>0 then + if Nassets>0 then -- Get coordinate of the target. local coord=Mission:GetTargetCoordinate() if coord then - -- Distance from airwing to target. - local distance=UTILS.MetersToNM(coord:Get2DDistance(airwing:GetCoordinate())) + -- Distance from legion to target. + local distance=UTILS.MetersToNM(coord:Get2DDistance(legion:GetCoordinate())) -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 local dist=UTILS.Round(distance/10, 0) -- Debug info. - self:I(self.lid..string.format("Got legion %s with Nassets=%d and dist=%.1f NM, rounded=%.1f", airwing.alias, #assets, distance, dist)) + self:I(self.lid..string.format("Got legion %s with Nassets=%d and dist=%.1f NM, rounded=%.1f", legion.alias, Nassets, distance, dist)) - -- Add airwing to table of legions that can. - table.insert(legions, {airwing=airwing, distance=distance, dist=dist, targetcoord=coord, nassets=#assets}) + -- Add legion to table of legions that can. + table.insert(legions, {airwing=legion, distance=distance, dist=dist, targetcoord=coord, nassets=Nassets}) end @@ -431,8 +453,8 @@ function COMMANDER:GetLegionsForMission(Mission) if #legions>0 then --- Something like: - -- * Closest airwing that can should be first prio. - -- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the airwing with more resources should get the job. + -- * Closest legion that can should be first prio. + -- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the legion with more resources should get the job. local function score(a) local d=math.round(a.dist/10) end @@ -440,7 +462,7 @@ function COMMANDER:GetLegionsForMission(Mission) env.info(self.lid.."FF #legions="..#legions) -- Sort table wrt distance and number of assets. - -- Distances within 10 NM are equal and the airwing with more assets is preferred. + -- Distances within 10 NM are equal and the legion with more assets is preferred. local function sortdist(a,b) local ad=a.dist local bd=b.dist @@ -471,7 +493,7 @@ function COMMANDER:GetLegionsForMission(Mission) self:I(self.lid..string.format("Found %d legions that can do mission %s (%s) requiring %d assets", #selection, Mission:GetName(), Mission:GetType(), Mission.nassets)) return selection else - self:T(self.lid..string.format("Not enough LEGIONs found that could do the job :/")) + self:T(self.lid..string.format("Not enough LEGIONs found that could do the job :/ Number of assets avail %d < %d required for the mission", N, Mission.nassets)) return nil end @@ -482,17 +504,18 @@ function COMMANDER:GetLegionsForMission(Mission) return nil end ---- Check mission queue and assign ONE planned mission. +--- 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. -- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. -- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. --- @return #number Amount of asset groups in stock. +-- @return #number Amount of asset groups. function COMMANDER:CountAssets(InStock, MissionTypes, Attributes) + local N=0 - for _,_airwing in pairs(self.legions) do - local airwing=_airwing --Ops.AirWing#AIRWING - N=N+airwing:CountAssets(InStock, MissionTypes, Attributes) + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + N=N+legion:CountAssets(InStock, MissionTypes, Attributes) end return N diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 2787b615a..c7483e648 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -230,13 +230,13 @@ function FLIGHTGROUP:New(group) -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("*", "LandAtAirbase", "Inbound") -- Helo group is ordered to land at a specific point. - self:AddTransition("*", "RTB", "Inbound") -- Group is returning to destination base. + self:AddTransition("*", "LandAtAirbase", "Inbound") -- Group is ordered to land at an airbase. + self:AddTransition("*", "RTB", "Inbound") -- Group is returning to (home/destination) airbase. self:AddTransition("*", "RTZ", "Inbound") -- Group is returning to destination zone. Not implemented yet! self:AddTransition("Inbound", "Holding", "Holding") -- Group is in holding pattern. self:AddTransition("*", "Refuel", "Going4Fuel") -- Group is send to refuel at a tanker. - self:AddTransition("Going4Fuel", "Refueled", "Airborne") -- Group finished refueling. + self:AddTransition("Going4Fuel", "Refueled", "Cruising") -- Group finished refueling. self:AddTransition("*", "LandAt", "LandingAt") -- Helo group is ordered to land at a specific point. self:AddTransition("LandingAt", "LandedAt", "LandedAt") -- Helo group landed landed at a specific point. @@ -244,12 +244,8 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "FuelLow", "*") -- Fuel state of group is low. Default ~25%. self:AddTransition("*", "FuelCritical", "*") -- Fuel state of group is critical. Default ~10%. - self:AddTransition("*", "OutOfMissilesAA", "*") -- Group is out of A2A (air) missiles. - self:AddTransition("*", "OutOfMissilesAG", "*") -- Group is out of A2G (ground) missiles. - self:AddTransition("*", "OutOfMissilesAS", "*") -- Group is out of A2S (ship) missiles. - - self:AddTransition("Airborne", "EngageTarget", "Engaging") -- Engage targets. - self:AddTransition("Engaging", "Disengage", "Airborne") -- Engagement over. + self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage targets. + self:AddTransition("Engaging", "Disengage", "Cruising") -- Engagement over. self:AddTransition("*", "ElementParking", "*") -- An element is parking. self:AddTransition("*", "ElementEngineOn", "*") -- An element spooled up the engines. @@ -305,7 +301,7 @@ function FLIGHTGROUP:New(group) self:_InitGroup() -- Start the status monitoring. - self:__Status(-1) + self.timerStatus=TIMER:New(self.Status, self):Start(1, 30) -- Start queue update timer. self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(2, 5) @@ -662,6 +658,15 @@ function FLIGHTGROUP:IsFuelCritical() return self.fuelcritical end +--- Check if flight is good on fuel (not below low or even critical state). +-- @param #FLIGHTGROUP self +-- @return #boolean If true, flight is good on fuel. +function FLIGHTGROUP:IsFuelGood() + local isgood=not (self.fuellow or self.fuelcritical) + return isgood +end + + --- Check if flight can do air-to-ground tasks. -- @param #FLIGHTGROUP self -- @param #boolean ExcludeGuns If true, exclude gun @@ -830,15 +835,14 @@ function FLIGHTGROUP:onbeforeStatus(From, Event, To) return true end ---- On after "Status" event. +--- Status update. -- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function FLIGHTGROUP:onafterStatus(From, Event, To) +function FLIGHTGROUP:Status() -- FSM state. local fsmstate=self:GetState() + + env.info(self.lid.."FF status="..fsmstate) -- Update position. self:_UpdatePosition() @@ -894,8 +898,8 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) local fc=self.flightcontrol and self.flightcontrol.airbasename or "N/A" local curr=self.currbase and self.currbase:GetName() or "N/A" - local text=string.format("Status %s [%d/%d]: Tasks=%d, Missions=%s, Waypoint=%d/%d, Detected=%d, Home=%s, Destination=%s, Current=%s, FC=%s", - fsmstate, #self.elements, #self.elements, nTaskTot, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, + local text=string.format("Status %s [%d/%d]: Tasks=%d, Missions=%s, Waypoint=%d/%d [%s], Detected=%d, Home=%s, Destination=%s, Current=%s, FC=%s", + fsmstate, #self.elements, #self.elements, nTaskTot, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, tostring(self.passedfinalwp), self.detectedunits:Count(), home, dest, curr, fc) self:I(self.lid..text) @@ -1024,33 +1028,6 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) self:FuelCritical() end - -- This causes severe problems as OutOfMissiles is called over and over again leading to many RTB calls. - if false then - - -- Out of AA Missiles? CAP, GCICAP, INTERCEPT - local CurrIsCap = false - -- Out of AG Missiles? BAI, SEAD, CAS, STRIKE - local CurrIsA2G = false - -- Check AUFTRAG Type - local CurrAuftrag = self:GetMissionCurrent() - if CurrAuftrag then - local CurrAuftragType = CurrAuftrag:GetType() - if CurrAuftragType == "CAP" or CurrAuftragType == "GCICAP" or CurrAuftragType == "INTERCEPT" then CurrIsCap = true end - if CurrAuftragType == "BAI" or CurrAuftragType == "CAS" or CurrAuftragType == "SEAD" or CurrAuftragType == "STRIKE" then CurrIsA2G = true end - end - - -- Check A2A - if (not self:CanAirToAir(true)) and CurrIsCap then - self:OutOfMissilesAA() - end - - -- Check A2G - if (not self:CanAirToGround(false)) and CurrIsA2G then - self:OutOfMissilesAG() - end - - end - end --- @@ -1065,7 +1042,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) --- -- Engage Detected Targets --- - if self:IsAirborne() and self.detectionOn and self.engagedetectedOn and not (self.fuellow or self.fuelcritical) then + if self:IsAirborne() and self:IsFuelGood() and self.detectionOn and self.engagedetectedOn then -- Target. local targetgroup=nil --Wrapper.Group#GROUP @@ -1153,11 +1130,6 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) self:_CheckCargoTransport() - - -- Next check in ~30 seconds. - if not self:IsStopped() then - self:__Status(-30) - end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1658,9 +1630,6 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) self:GetGroup():SetOption(AI.Option.Air.id.RTB_ON_BINGO, false) --self.group:SetOption(AI.Option.Air.id.RADAR_USING, AI.Option.Air.val.RADAR_USING.FOR_CONTINUOUS_SEARCH) - -- Update status. - self:__Status(-0.1) - -- Update route. self:__UpdateRoute(-0.5) @@ -2338,7 +2307,7 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) end -- Only if fuel is not low or critical. - if not (self:IsFuelLow() or self:IsFuelCritical()) then + if self:IsFuelGood() then -- Check if there are remaining tasks. local Ntot,Nsched, Nwp=self:CountRemainingTasks() @@ -2442,7 +2411,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) self.currbase=airbase -- Passed final waypoint! - self.passedfinalwp=true + self:_PassedFinalWaypoint(true, "_LandAtAirbase") -- Not waiting any more. self.Twaiting=nil @@ -2477,7 +2446,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) p1=HoldingPoint.pos1 -- Debug marks. - if self.Debug then + if false then p0:MarkToAll("Holding point P0") p1:MarkToAll("Holding point P1") end @@ -3453,7 +3422,7 @@ function FLIGHTGROUP:InitWaypoints() -- Check if only 1 wp? if #self.waypoints==1 then - self.passedfinalwp=true + self:_PassedFinalWaypoint(true, "FLIGHTGROUP:InitWaypoints #self.waypoints==1") end end @@ -3475,7 +3444,7 @@ function FLIGHTGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Altitud local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) if wpnumber>self.currentwp then - self.passedfinalwp=false + self:_PassedFinalWaypoint(false, "FLIGHTGROUP:AddWaypoint wpnumber>self.currentwp") end -- Speed in knots. @@ -3520,7 +3489,7 @@ function FLIGHTGROUP:AddWaypointLanding(Airbase, Speed, AfterWaypointWithID, Alt local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) if wpnumber>self.currentwp then - self.passedfinalwp=false + self:_PassedFinalWaypoint(false, "AddWaypointLanding") end -- Speed in knots. @@ -3929,11 +3898,6 @@ function FLIGHTGROUP:GetParking(airbase) -- Debug output for occupied spots. self:T2(self.lid..string.format("Parking spot %d is occupied or not big enough!", parkingspot.TerminalID)) - --if self.Debug then - -- local coord=problem.coord --Core.Point#COORDINATE - -- local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist) - -- coord:MarkToAll(string.format(text)) - --end end diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 8c83911b0..06e63f37b 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -15,7 +15,9 @@ -- @field #number verbose Verbosity of output. -- @field #string lid Class id string for output to DCS log file. -- @field #table missionqueue Mission queue table. +-- @field #table transportqueue Transport queue. -- @field #table cohorts Cohorts of this legion. +-- @field Ops.Commander#COMMANDER commander Commander of this legion. -- @extends Functional.Warehouse#WAREHOUSE --- Be surprised! @@ -34,6 +36,7 @@ LEGION = { verbose = 0, lid = nil, missionqueue = {}, + transportqueue = {}, cohorts = {}, } @@ -76,6 +79,8 @@ function LEGION:New(WarehouseName, LegionName) self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + self:AddTransition("*", "TransportRequest", "*") -- Add a (mission) request to the warehouse. + self:AddTransition("*", "OpsOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). self:AddTransition("*", "FlightOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). self:AddTransition("*", "ArmyOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). @@ -138,9 +143,9 @@ function LEGION:SetVerbosity(VerbosityLevel) return self end ---- Add a mission for the airwing. The airwing will pick the best available assets for the mission and lauch it when ready. +--- Add a mission for the legion. It will pick the best available assets for the mission and lauch it when ready. -- @param #LEGION self --- @param Ops.Auftrag#AUFTRAG Mission Mission for this airwing. +-- @param Ops.Auftrag#AUFTRAG Mission Mission for this legion. -- @return #LEGION self function LEGION:AddMission(Mission) @@ -184,6 +189,27 @@ function LEGION:RemoveMission(Mission) return self end +--- Add transport assignment to queue. +-- @param #LEGION self +-- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport Transport assignment. +-- @return #LEGION self +function LEGION:AddOpsTransport(OpsTransport) + + -- Is not queued at a legion. + OpsTransport:Queued() + + -- Add mission to queue. + table.insert(self.transportqueue, OpsTransport) + + -- Info text. + local text=string.format("Added Transport %s. Starting at %s-%s", + tostring(OpsTransport.uid), UTILS.SecondsToClock(OpsTransport.Tstart, true), OpsTransport.Tstop and UTILS.SecondsToClock(OpsTransport.Tstop, true) or "INF") + self:T(self.lid..text) + + return self +end + + --- Get cohort by name. -- @param #LEGION self -- @param #string CohortName Name of the platoon. @@ -261,6 +287,7 @@ function LEGION:_CheckMissions() end end + --- Get next mission. -- @param #LEGION self -- @return Ops.Auftrag#AUFTRAG Next mission or `#nil`. @@ -301,7 +328,7 @@ function LEGION:_GetNextMission() -- Firstly, check if mission is due? if mission:IsQueued(self) and mission:IsReadyToGo() and (mission.importance==nil or mission.importance<=vip) then - -- Check if airwing can do the mission and gather required assets. + -- Check if legion can do the mission and gather required assets. local can, assets=self:CanMission(mission) -- Check that mission is still scheduled, time has passed and enough assets are available. @@ -391,6 +418,72 @@ function LEGION:_GetNextMission() return nil end +--- Get next transport. +-- @param #LEGION self +-- @return Ops.OpsTransport#OPSTRANSPORT Next transport or `#nil`. +function LEGION:_GetNextTransport() + + -- Number of missions. + local Ntransports=#self.transportqueue + + -- Treat special cases. + if Ntransports==0 then + return nil + end + + + local function getAssets(n) + local assets={} + + -- Loop over assets. + for _,_cohort in pairs(self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + if cohort:CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}, cohort.missiontypes) then + + for _,_asset in pairs(cohort.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Check if asset is currently on a mission (STARTED or QUEUED). + if not asset.spawned then + + -- Add to assets. + table.insert(assets, asset) + + if #assets==n then + return assets + end + end + + end + + end + end + end + + + -- Look for first task that is not accomplished. + for _,_transport in pairs(self.transportqueue) do + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + + -- Check if transport is still queued and ready. + if transport:IsQueued() and transport:IsReadyToGo() then + + local assets=getAssets(1) + + if #assets>0 then + transport.assets=assets + return transport + end + + end + + end + + + return nil +end + --- Calculate the mission score of an asset. -- @param #LEGION self -- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset @@ -518,7 +611,7 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) Mission:Requested() -- Set legion status. Ensures that it is not considered in the next selection. - Mission:SetLegionStatus(self, AUFTRAG.Status.REQUESTED) + Mission:SetLegionStatus(self, AUFTRAG.Status.REQUESTED) --- -- Some assets might already be spawned and even on a different mission (orbit). @@ -556,7 +649,7 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) end end - -- Add request to airwing warehouse. + -- Add request to legion warehouse. if #Assetlist>0 then --local text=string.format("Requesting assets for mission %s:", Mission.name) @@ -572,9 +665,11 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) end - -- Add request to airwing warehouse. - -- TODO: better Assignment string. - self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, Assetlist, #Assetlist, nil, nil, Mission.prio, tostring(Mission.auftragsnummer)) + -- TODO: Get/set functions for assignment string. + local assignment=string.format("Mission-%d", Mission.auftragsnummer) + + -- Add request to legion warehouse. + self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, Assetlist, #Assetlist, nil, nil, Mission.prio, assignment) -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. Mission.requestID[self.alias]=self.queueid @@ -582,6 +677,50 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) end +--- On after "MissionRequest" event. Performs a self request to the warehouse for the mission assets. Sets mission status to REQUESTED. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsTransport#OPSTRANSPORT Opstransport The requested mission. +function LEGION:onafterTransportRequest(From, Event, To, OpsTransport) + + -- Set mission status from QUEUED to REQUESTED. + OpsTransport:Requested() + + -- Set legion status. Ensures that it is not considered in the next selection. + --Mission:SetLegionStatus(self, AUFTRAG.Status.REQUESTED) + + -- Add request to legion warehouse. + if #OpsTransport.assets>0 then + + --local text=string.format("Requesting assets for mission %s:", Mission.name) + for i,_asset in pairs(OpsTransport.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Set asset to requested! Important so that new requests do not use this asset! + asset.requested=true + + -- Check max required transports. + if i==1 then + break + end + + end + + -- TODO: Get/set functions for assignment string. + local assignment=string.format("Transport-%d", OpsTransport.uid) + + -- Add request to legion warehouse. + self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, OpsTransport.assets, #OpsTransport.assets, nil, nil, OpsTransport.prio, assignment) + + -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. + OpsTransport.requestID=OpsTransport.requestID or {} + OpsTransport.requestID[self.alias]=self.queueid + end + +end + --- On after "MissionCancel" event. Cancels the missions of all flightgroups. Deletes request from warehouse queue. -- @param #LEGION self -- @param #string From From state. @@ -616,35 +755,6 @@ function LEGION:onafterMissionCancel(From, Event, To, Mission) end end - - --[[ - if Mission:IsPlanned() or Mission:IsQueued() or Mission:IsRequested() or Ngroups == 0 then - - Mission:Done() - - else - - for _,_asset in pairs(Mission.assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Asset should belong to this legion. - if asset.wid==self.uid then - - local opsgroup=asset.flightgroup - - if opsgroup then - opsgroup:MissionCancel(Mission) - end - - -- Not requested any more (if it was). - asset.requested=nil - - end - end - - end - ]] - -- Remove queued request (if any). if Mission.requestID[self.alias] then self:_DeleteQueueItemByID(Mission.requestID[self.alias], self.queue) @@ -683,7 +793,7 @@ end -- @param #string assignment The (optional) assignment for the asset. function LEGION:onafterNewAsset(From, Event, To, asset, assignment) - -- Call parent warehouse function first. + -- Call parent WAREHOUSE function first. self:GetParent(self, LEGION).onafterNewAsset(self, From, Event, To, asset, assignment) -- Debug text. @@ -697,6 +807,10 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) if cohort then if asset.assignment==assignment then + + --- + -- Asset is added to the COHORT for the first time + --- local nunits=#asset.template.units @@ -750,7 +864,11 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) --asset.terminalType=AIRBASE.TerminalType.OpenBig else - --env.info("FF cohort asset returned") + --- + -- Asset is returned to the COHORT + --- + + -- Trigger event. self:AssetReturned(cohort, asset) end @@ -758,7 +876,7 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) end end ---- On after "AssetReturned" event. Triggered when an asset group returned to its airwing. +--- On after "AssetReturned" event. Triggered when an asset group returned to its legion. -- @param #LEGION self -- @param #string From From state. -- @param #string Event Event. @@ -859,41 +977,66 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) flightgroup:SetFuelLowRefuel(cohort.fuellowRefuel) end - --- - -- Mission - --- - - -- Get Mission (if any). - local mission=self:GetMissionByID(request.assignment) - - -- Add mission to flightgroup queue. - if mission then - - if Tacan then - --mission:SetTACAN(Tacan, Morse, UnitName, Band) - end + -- Assignment. + local assignment=request.assignment + + if string.find(assignment, "Mission-") then + --- + -- Mission + --- + + local uid=UTILS.Split(assignment, "-")[2] + + -- Get Mission (if any). + local mission=self:GetMissionByID(uid) + -- Add mission to flightgroup queue. - asset.flightgroup:AddMission(mission) - - -- Trigger event. - self:__OpsOnMission(5, flightgroup, mission) - - else - - if Tacan then - --flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + if mission then + + if Tacan then + --mission:SetTACAN(Tacan, Morse, UnitName, Band) + end + + -- Add mission to flightgroup queue. + flightgroup:AddMission(mission) + + -- Trigger event. + self:__OpsOnMission(5, flightgroup, mission) + + else + + if Tacan then + --flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + end + end + + -- Add group to the detection set of the CHIEF (INTEL). + if self.commander and self.commander.chief then + self.commander.chief.detectionset:AddGroup(asset.flightgroup.group) + end + + elseif string.find(assignment, "Transport-") then + + --- + -- Transport + --- + + local uid=UTILS.Split(assignment, "-")[2] + -- Get Mission (if any). + local transport=self:GetTransportByID(uid) + + -- Add mission to flightgroup queue. + if transport then + flightgroup:AddOpsTransport(transport) + end + end - - -- Add group to the detection set of the WINGCOMMANDER. - if self.wingcommander and self.wingcommander.chief then - self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) - end - + end - + end --- On after "AssetDead" event triggered when an asset group died. @@ -907,11 +1050,11 @@ function LEGION:onafterAssetDead(From, Event, To, asset, request) -- Call parent warehouse function first. self:GetParent(self, LEGION).onafterAssetDead(self, From, Event, To, asset, request) - - -- Add group to the detection set of the WINGCOMMANDER. - if self.wingcommander and self.wingcommander.chief then - self.wingcommander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) - end + + -- Remove group from the detection set of the CHIEF (INTEL). + if self.commander and self.commander.chief then + self.commander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) + end -- Remove asset from mission is done via Mission:AssetDead() call from flightgroup onafterFlightDead function -- Remove asset from squadron same @@ -1203,7 +1346,7 @@ function LEGION:CountMissionsInQueue(MissionTypes) return N end ---- Count total number of assets that are in the warehouse stock (not spawned). +--- Count total number of assets of the legion. -- @param #LEGION self -- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. -- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. @@ -1221,6 +1364,54 @@ function LEGION:CountAssets(InStock, MissionTypes, Attributes) return N end +--- Count total number of assets in LEGION warehouse stock that also have a payload. +-- @param #LEGION self +-- @param #boolean Payloads (Optional) Specifc payloads to consider. Default all. +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Amount of asset groups in stock. +function LEGION:CountAssetsWithPayloadsInStock(Payloads, MissionTypes, Attributes) + + -- Total number counted. + local N=0 + + -- Number of payloads in stock per aircraft type. + local Npayloads={} + + -- First get payloads for aircraft types of squadrons. + for _,_cohort in pairs(self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + if Npayloads[cohort.aircrafttype]==nil then + Npayloads[cohort.aircrafttype]=self:CountPayloadsInStock(MissionTypes, cohort.aircrafttype, Payloads) + env.info(string.format("FF got Npayloads=%d for type=%s",Npayloads[cohort.aircrafttype], cohort.aircrafttype)) + end + end + + for _,_cohort in pairs(self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + -- Number of assets in stock. + local n=cohort:CountAssets(true, MissionTypes, Attributes) + + -- Number of payloads. + local p=Npayloads[cohort.aircrafttype] or 0 + + -- Only the smaller number of assets or paylods is really available. + local m=math.min(n, p) + + env.info("FF n="..n) + env.info("FF p="..p) + + -- Add up what we have. Could also be zero. + N=N+m + + -- Reduce number of available payloads. + Npayloads[cohort.aircrafttype]=Npayloads[cohort.aircrafttype]-m + end + + return N +end + --- Count assets on mission. -- @param #LEGION self -- @param #table MissionTypes Types on mission to be checked. Default all. @@ -1241,19 +1432,23 @@ function LEGION:CountAssetsOnMission(MissionTypes, Cohort) for _,_asset in pairs(mission.assets or {}) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Ensure asset belongs to this letion. + if asset.wid==self.uid then - if Cohort==nil or Cohort.name==asset.squadname then - - local request, isqueued=self:GetRequestByID(mission.requestID[self.alias]) - - if isqueued then - Nq=Nq+1 - else - Np=Np+1 + if Cohort==nil or Cohort.name==asset.squadname then + + local request, isqueued=self:GetRequestByID(mission.requestID[self.alias]) + + if isqueued then + Nq=Nq+1 + else + Np=Np+1 + end + end - + end - end end end @@ -1279,8 +1474,13 @@ function LEGION:GetAssetsOnMission(MissionTypes) for _,_asset in pairs(mission.assets or {}) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Ensure asset belongs to this legion. + if asset.wid==self.uid then - table.insert(assets, asset) + table.insert(assets, asset) + + end end end @@ -1289,7 +1489,7 @@ function LEGION:GetAssetsOnMission(MissionTypes) return assets end ---- Get the aircraft types of this airwing. +--- Get the unit types of this legion. These are the unit types of all assigned cohorts. -- @param #LEGION self -- @param #boolean onlyactive Count only the active ones. -- @param #table cohorts Table of cohorts. Default all. @@ -1332,24 +1532,24 @@ function LEGION:CanMission(Mission) -- Assume we CAN and NO assets are available. local Can=true local Assets={} - + -- Squadrons for the job. If user assigned to mission or simply all. local cohorts=Mission.squadrons or self.cohorts - local Nassets=Mission.nassets or 1 - if Mission.Nassets and Mission.Nassets[self.alias] then - Nassets=Mission.Nassets[self.alias] - end + -- Number of required assets. + local Nassets=Mission:GetRequiredAssets(self) -- Get aircraft unit types for the job. local unittypes=self:GetAircraftTypes(true, cohorts) -- Count all payloads in stock. if self:IsAirwing() then + + -- Number of payloads in stock. local Npayloads=self:CountPayloadsInStock(Mission.type, unittypes, Mission.payloads) if Npayloads false")) - return false - elseif self:IsStopped() then - self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) - return false - end - - return true -end - --- Update status. -- @param #NAVYGROUP self -function NAVYGROUP:onafterStatus(From, Event, To) +function NAVYGROUP:Status(From, Event, To) -- FSM state. local fsmstate=self:GetState() @@ -628,9 +614,6 @@ function NAVYGROUP:onafterStatus(From, Event, To) self:_PrintTaskAndMissionStatus() - - -- Next status update in 30 seconds. - self:__Status(-30) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -897,7 +880,7 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) IntoWind.waypoint=wptiw - if IntoWind.Uturn and self.Debug then + if IntoWind.Uturn and false then IntoWind.Coordinate:MarkToAll("Return coord") end @@ -1120,7 +1103,7 @@ function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Up -- Check if final waypoint is still passed. if wpnumber>self.currentwp then - self.passedfinalwp=false + self:_PassedFinalWaypoint(false, "NAVYGROUP:AddWaypoint wpnumber>self.currentwp") end -- Speed in knots. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 194dac649..ebbc98861 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -14,7 +14,6 @@ --- OPSGROUP class. -- @type OPSGROUP -- @field #string ClassName Name of the class. --- @field #boolean Debug Debug mode. Messages to all about status. -- @field #number verbose Verbosity level. 0=silent. -- @field #string lid Class id string for output to DCS log file. -- @field #string groupname Name of the group. @@ -60,9 +59,9 @@ -- @field #number speedWp Speed to the next waypoint in m/s. -- @field #boolean passedfinalwp Group has passed the final waypoint. -- @field #number wpcounter Running number counting waypoints. --- @field #boolean respawning Group is being respawned. -- @field Core.Set#SET_ZONE checkzones Set of zones. -- @field Core.Set#SET_ZONE inzones Set of zones in which the group is currently in. +-- @field Core.Timer#TIMER timerStatus Timer for status update. -- @field Core.Timer#TIMER timerCheckZone Timer for check zones. -- @field Core.Timer#TIMER timerQueueUpdate Timer for queue updates. -- @field #boolean groupinitialized If true, group parameters were initialized. @@ -145,7 +144,6 @@ -- @field #OPSGROUP OPSGROUP = { ClassName = "OPSGROUP", - Debug = false, verbose = 0, lid = nil, groupname = nil, @@ -169,7 +167,6 @@ OPSGROUP = { checkzones = nil, inzones = nil, groupinitialized = nil, - respawning = nil, wpcounter = 1, radio = {}, option = {}, @@ -553,7 +550,7 @@ function OPSGROUP:New(group) self:AddTransition("*", "InUtero", "InUtero") -- Deactivated group goes back to mummy. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - self:AddTransition("*", "Status", "*") -- Status update. + --self:AddTransition("*", "Status", "*") -- Status update. self:AddTransition("*", "Destroyed", "*") -- The whole group is dead. self:AddTransition("*", "Damaged", "*") -- Someone in the group took damage. @@ -580,6 +577,11 @@ function OPSGROUP:New(group) self:AddTransition("*", "OutOfRockets", "*") -- Group is out of rockets. self:AddTransition("*", "OutOfBombs", "*") -- Group is out of bombs. self:AddTransition("*", "OutOfMissiles", "*") -- Group is out of missiles. + self:AddTransition("*", "OutOfTorpedos", "*") -- Group is out of torpedos. + + self:AddTransition("*", "OutOfMissilesAA", "*") -- Group is out of A2A (air) missiles. + self:AddTransition("*", "OutOfMissilesAG", "*") -- Group is out of A2G (ground) missiles. + self:AddTransition("*", "OutOfMissilesAS", "*") -- Group is out of A2S (ship) missiles. self:AddTransition("*", "EnterZone", "*") -- Group entered a certain zone. self:AddTransition("*", "LeaveZone", "*") -- Group leaves a certain zone. @@ -2127,16 +2129,21 @@ end -- @return #number Next waypoint index. function OPSGROUP:GetWaypointIndexNext(cyclic, i) + -- If not specified, we take the adinititum value. if cyclic==nil then cyclic=self.adinfinitum end + -- Total number of waypoints. local N=#self.waypoints + -- Default is currentwp. i=i or self.currentwp + -- If no next waypoint exists, because the final waypoint was reached, we return the last waypoint. local n=math.min(i+1, N) + -- If last waypoint was reached, the first waypoint is the next in line. if cyclic and i==N then n=1 end @@ -2388,7 +2395,7 @@ function OPSGROUP:RemoveWaypoint(wpindex) -- TODO: patrol adinfinitum. if self.currentwp>=n then - self.passedfinalwp=true + self:_PassedFinalWaypoint(true, "Removed FUTURE waypoint") end self:_CheckGroupDone(1) @@ -3088,9 +3095,9 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- New waypoint. if self.isFlightgroup then FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) - elseif self.isNavygroup then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) elseif self.isArmygroup then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + elseif self.isNavygroup then NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) end @@ -3119,9 +3126,9 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- New waypoint. if self.isFlightgroup then FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) - elseif self.isNavygroup then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) elseif self.isArmygroup then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + elseif self.isNavygroup then NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) end @@ -3464,10 +3471,28 @@ function OPSGROUP:_GetNextMission() -- Look for first mission that is SCHEDULED. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG + + -- Local transport. + local transport=true + if mission.opstransport then + local cargos=mission.opstransport:GetCargoOpsGroups(false) or {} + for _,_opsgroup in pairs(cargos) do + local opscargo=_opsgroup --Ops.OpsGroup#OPSGROUP + if opscargo.groupname==self.groupname then + transport=false + break + end + end + end + + -- TODO: One could think of opsgroup specific start conditions. A legion also checks if "ready" but it can be other criteria for the group to actually start the mission. + -- Good example is the above transport. The legion should start the mission but the group should only start after the transport is finished. - if mission:GetGroupStatus(self)==AUFTRAG.Status.SCHEDULED and (mission:IsReadyToGo() or self.legion) and (mission.importance==nil or mission.importance<=vip) then + -- Check necessary conditions. + if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.SCHEDULED and (mission:IsReadyToGo() or self.legion) and (mission.importance==nil or mission.importance<=vip) and transport then return mission end + end return nil @@ -3817,6 +3842,11 @@ function OPSGROUP:RouteToMission(mission, delay) if self:IsDead() or self:IsStopped() then return end + + if mission.type==AUFTRAG.Type.OPSTRANSPORT then + self:AddOpsTransport(mission.opstransport) + return + end -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid @@ -4110,9 +4140,9 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) if self.isFlightgroup then FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) - elseif self.isNavygroup then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) elseif self.isArmygroup then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + elseif self.isNavygroup then NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) end @@ -4140,9 +4170,9 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) if self.isFlightgroup then FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) - elseif self.isNavygroup then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) elseif self.isArmygroup then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + elseif self.isNavygroup then NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) end @@ -4158,8 +4188,8 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) if wpindex==nil or wpindex==#self.waypoints then -- Set switch to true. - if not self.adinfinitum or #self.waypoints<=1 then - self.passedfinalwp=true + if not self.adinfinitum or #self.waypoints<=1 then + self:_PassedFinalWaypoint(true, "Passing waypoint and NOT adinfinitum and #self.waypoints<=1") end end @@ -4182,7 +4212,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Set switch to true. if not self.adinfinitum or #self.waypoints<=1 then - self.passedfinalwp=true + self:_PassedFinalWaypoint(true, "PassingWaypoint: wpindex=nil or wpindex=#self.waypoints") end end @@ -4846,6 +4876,22 @@ function OPSGROUP:_UpdateLaser() end +--- On before "ElementSpawned" event. Check that element is not in status spawned already. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. +function OPSGROUP:onbeforeElementSpawned(From, Event, To, Element) + + if Element and Element.status==OPSGROUP.ElementStatus.SPAWNED then + self:I(self.lid..string.format("FF element %s is already spawned", Element.name)) + return false + end + + return true +end + --- On after "ElementInUtero" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -5254,6 +5300,7 @@ function OPSGROUP:onafterStop(From, Event, To) -- Stop check timers. self.timerCheckZone:Stop() self.timerQueueUpdate:Stop() + self.timerStatus:Stop() -- Stop FSM scheduler. self.CallScheduler:Clear() @@ -7518,7 +7565,16 @@ function OPSGROUP:_CheckAmmoStatus() if ammo.MissilesAS and self.ammo.MissilesAS>0 and not self.outofMissilesAS then self.outofMissilesAS=true self:OutOfMissilesAS() - end + end + + -- Torpedos. + if self.outofTorpedos and ammo.Torpedos>0 then + self.outofTorpedos=false + end + if ammo.Torpedos==0 and self.ammo.Torpedos>0 and not self.outofTorpedos then + self.outofTorpedos=true + self:OutOfTorpedos() + end -- Check if group is engaging. @@ -7647,7 +7703,7 @@ function OPSGROUP:_AddWaypoint(waypoint, wpnumber) -- Now we obviously did not pass the final waypoint. if self.currentwp and wpnumber>self.currentwp then - self.passedfinalwp=false + self:_PassedFinalWaypoint(false, "_AddWaypoint self.currentwp and wpnumber>self.currentwp") end end @@ -7733,7 +7789,7 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) -- Check if only 1 wp? if #self.waypoints==1 then - self.passedfinalwp=true + self:_PassedFinalWaypoint(true, "_InitWaypoints: #self.waypoints==1") end else @@ -7825,41 +7881,62 @@ end --@param #number uid Waypoint UID. function OPSGROUP._PassingWaypoint(group, opsgroup, uid) + -- Debug message. + local text=string.format("Group passing waypoint uid=%d", uid) + opsgroup:T(opsgroup.lid..text) + -- Get waypoint data. local waypoint=opsgroup:GetWaypointByID(uid) if waypoint then + + -- Increase passing counter. + waypoint.npassed=waypoint.npassed+1 -- Current wp. local currentwp=opsgroup.currentwp -- Get the current waypoint index. opsgroup.currentwp=opsgroup:GetWaypointIndex(uid) + + local wpistemp=waypoint.temp or waypoint.detour or waypoint.astar + + -- Remove temp waypoints. + if wpistemp then + opsgroup:RemoveWaypointByID(uid) + end - -- Set expected speed and formation from the next WP. + -- Get next waypoint. Tricky part is that if local wpnext=opsgroup:GetWaypointNext() - if wpnext then + + if wpnext and (opsgroup.currentwp<#opsgroup.waypoints or opsgroup.adinfinitum or wpistemp) then + + opsgroup:I(opsgroup.lid..string.format("Next waypoint UID=%d index=%d", wpnext.uid, opsgroup:GetWaypointIndex(wpnext.uid))) -- Set formation. if opsgroup.isGround then opsgroup.formation=wpnext.action end - -- Set speed. + -- Set speed to next wp. opsgroup.speed=wpnext.speed - + + if opsgroup.speed<0.01 then + opsgroup.speed=UTILS.KmphToMps(opsgroup.speedCruise) + end + + else + + env.info(opsgroup.lid.."FF 300") + + -- Set passed final waypoint. + opsgroup:_PassedFinalWaypoint(true, "_PassingWaypoint No next Waypoint found") + end - -- Debug message. - local text=string.format("Group passing waypoint uid=%d", uid) - opsgroup:T(opsgroup.lid..text) - -- Trigger PassingWaypoint event. if waypoint.temp then - -- Remove temp waypoint. - opsgroup:RemoveWaypointByID(uid) - if opsgroup:IsNavygroup() or opsgroup:IsArmygroup() then --TODO: not sure if this works with FLIGHTGROUPS opsgroup:Cruise() @@ -7867,17 +7944,11 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif waypoint.astar then - -- Remove Astar waypoint. - opsgroup:RemoveWaypointByID(uid) - -- Cruise. opsgroup:Cruise() elseif waypoint.detour then - -- Remove detour waypoint. - opsgroup:RemoveWaypointByID(uid) - if opsgroup:IsRearming() then -- Trigger Rearming event. @@ -7890,6 +7961,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif opsgroup:IsReturning() then + -- Trigger Returned event. opsgroup:Returned() elseif opsgroup:IsPickingup() then @@ -7965,9 +8037,6 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) opsgroup.ispathfinding=false end - -- Increase passing counter. - waypoint.npassed=waypoint.npassed+1 - -- Call event function. opsgroup:PassingWaypoint(waypoint) end @@ -9398,7 +9467,7 @@ function OPSGROUP:GetAmmoUnit(unit, display) nmissilesAS=nmissilesAS+Nammo elseif MissileCategory==Weapon.MissileCategory.BM then nmissiles=nmissiles+Nammo - nmissilesAG=nmissilesAG+Nammo + nmissilesBM=nmissilesBM+Nammo elseif MissileCategory==Weapon.MissileCategory.CRUISE then nmissiles=nmissiles+Nammo nmissilesCR=nmissilesCR+Nammo @@ -9477,6 +9546,20 @@ function OPSGROUP:_MissileCategoryName(categorynumber) return cat end +--- Set passed final waypoint value. +-- @param #OPSGROUP self +-- @param #boolean final If `true`, final waypoint was passed. +-- @param #string comment Some comment as to why the final waypoint was passed. +function OPSGROUP:_PassedFinalWaypoint(final, comment) + + -- Debug info. + self:I(self.lid..string.format("Passed final waypoint=%s [from %s]: comment \"%s\"", tostring(final), tostring(self.passedfinalwp), tostring(comment))) + + -- Set value. + self.passedfinalwp=final +end + + --- Get coordinate from an object. -- @param #OPSGROUP self -- @param Wrapper.Object#OBJECT Object The object. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index b6a2e029c..6cdbf7430 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -119,16 +119,21 @@ OPSTRANSPORT = { pathsTransport = {}, pathsPickup = {}, requiredCargos = {}, + assets = {}, } --- Cargo transport status. -- @type OPSTRANSPORT.Status -- @field #string PLANNED Planning state. +-- @field #string QUEUED Queued state. +-- @field #string REQUESTED Requested state. -- @field #string SCHEDULED Transport is scheduled in the cargo queue. -- @field #string EXECUTING Transport is being executed. -- @field #string DELIVERED Transport was delivered. OPSTRANSPORT.Status={ PLANNED="planned", + QUEUED="queued", + REQUESTED="requested", SCHEDULED="scheduled", EXECUTING="executing", DELIVERED="delivered", @@ -207,7 +212,10 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- PLANNED --> SCHEDULED --> EXECUTING --> DELIVERED self:AddTransition("*", "Planned", OPSTRANSPORT.Status.PLANNED) -- Cargo transport was planned. - self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. + self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Queued", OPSTRANSPORT.Status.QUEUED) -- Cargo is queued at at least one carrier. + self:AddTransition(OPSTRANSPORT.Status.QUEUED, "Requested", OPSTRANSPORT.Status.REQUESTED) -- Transport assets have been requested from a warehouse. + self:AddTransition(OPSTRANSPORT.Status.QUEUED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. + self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. self:AddTransition("*", "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. @@ -814,6 +822,41 @@ function OPSTRANSPORT:IsReadyToGo() return true end +--- Check if state is PLANNED. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, status is PLANNED. +function OPSTRANSPORT:IsPlanned() + return self:is(OPSTRANSPORT.Status.PLANNED) +end + +--- Check if state is QUEUED. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, status is QUEUED. +function OPSTRANSPORT:IsQueued() + return self:is(OPSTRANSPORT.Status.QUEUED) +end + +--- Check if state is REQUESTED. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, status is REQUESTED. +function OPSTRANSPORT:IsRequested() + return self:is(OPSTRANSPORT.Status.REQUESTED) +end + +--- Check if state is SCHEDULED. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, status is SCHEDULED. +function OPSTRANSPORT:IsScheduled() + return self:is(OPSTRANSPORT.Status.SCHEDULED) +end + +--- Check if state is EXECUTING. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, status is EXECUTING. +function OPSTRANSPORT:IsExecuting() + return self:is(OPSTRANSPORT.Status.EXECUTING) +end + --- Check if all cargo was delivered (or is dead). -- @param #OPSTRANSPORT self -- @return #boolean If true, all possible cargo was delivered. From eba6e3f5f1c56cf9d8a973cda0f67fea47e6c4f1 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 22 Aug 2021 00:31:51 +0200 Subject: [PATCH 076/141] OPS Transport+Legion, Navy --- Moose Development/Moose/Ops/Auftrag.lua | 17 ++++++- Moose Development/Moose/Ops/Legion.lua | 20 +++++++-- Moose Development/Moose/Ops/NavyGroup.lua | 23 +++++----- Moose Development/Moose/Ops/OpsGroup.lua | 3 ++ Moose Development/Moose/Ops/OpsTransport.lua | 47 ++++++++++++++++---- 5 files changed, 87 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index bdc45fdad..a180d4d9d 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -533,7 +533,6 @@ function AUFTRAG:New(Type) self:SetStartState(self.status) -- PLANNED --> (QUEUED) --> (REQUESTED) --> SCHEDULED --> STARTED --> EXECUTING --> DONE - self:AddTransition("*", "Planned", AUFTRAG.Status.PLANNED) -- Mission is in planning stage. self:AddTransition(AUFTRAG.Status.PLANNED, "Queued", AUFTRAG.Status.QUEUED) -- Mission is in queue of an AIRWING. self:AddTransition(AUFTRAG.Status.QUEUED, "Requested", AUFTRAG.Status.REQUESTED) -- Mission assets have been requested from the warehouse. @@ -1728,6 +1727,13 @@ function AUFTRAG:SetOpsTransport(OpsTransport) return self end +--- Get the attach OPS transport of the mission. +-- @param #AUFTRAG self +-- @return Ops.OpsTransport#OPSTRANSPORT The OPS transport assignment attached to the mission. +function AUFTRAG:GetOpsTransport() + return self.opstransport +end + --- Attach OPS transport to the mission. Mission assets will be transported before the mission is started at the OPSGROUP level. -- @param #AUFTRAG self -- @param Core.Zone#ZONE PickupZone Zone where assets are picked up. @@ -2182,6 +2188,15 @@ function AUFTRAG:IsReadyToGo() return false end + -- Ops transport at + if self.opstransport then + if #self.legions>0 then + end + if self.opstransport:IsPlanned() or self.opstransport:IsQueued() or self.opstransport:IsRequested() then + return false + end + end + -- All start conditions true? local startme=self:EvalConditionsAll(self.conditionStart) diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 06e63f37b..4154ca1c7 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -157,6 +157,12 @@ function LEGION:AddMission(Mission) -- Add legion to mission. Mission:AddLegion(self) + + if Mission.opstransport then + Mission.opstransport:SetPickupZone(self.spawnzone) + Mission.opstransport:SetEmbarkZone(self.spawnzone) + self:AddOpsTransport(Mission.opstransport) + end -- Add mission to queue. table.insert(self.missionqueue, Mission) @@ -333,6 +339,9 @@ function LEGION:_GetNextMission() -- Check that mission is still scheduled, time has passed and enough assets are available. if can then + + -- Number of required assets. + local Nassets=mission:GetRequiredAssets(self) -- Optimize the asset selection. Most useful assets will come first. We do not include the payload as some assets have and some might not. self:_OptimizeAssetSelection(assets, mission, false) @@ -384,7 +393,7 @@ function LEGION:_GetNextMission() --mission.assets={} -- Assign assets to mission. - for i=1,mission.Nassets[self.alias] do + for i=1,Nassets do local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem -- Should not happen as we just checked! @@ -398,7 +407,7 @@ function LEGION:_GetNextMission() -- Now return the remaining payloads. if self:IsAirwing() then - for i=mission.Nassets[self.alias]+1,#assets do + for i=Nassets+1,#assets do local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem for _,uid in pairs(gotpayload) do if uid==asset.uid then @@ -997,9 +1006,14 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) if Tacan then --mission:SetTACAN(Tacan, Morse, UnitName, Band) end + + -- Transport for mission assets. + if mission.opstransport then + mission.opstransport:AddCargoGroups(self) + end -- Add mission to flightgroup queue. - flightgroup:AddMission(mission) + flightgroup:AddMission(mission) -- Trigger event. self:__OpsOnMission(5, flightgroup, mission) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 46202c0ed..2b8cec8ba 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -133,16 +133,16 @@ function NAVYGROUP:New(group) self:AddTransition("*", "FullStop", "Holding") -- Hold position. self:AddTransition("*", "Cruise", "Cruising") -- Hold position. - self:AddTransition("*", "TurnIntoWind", "IntoWind") -- Command the group to turn into the wind. - self:AddTransition("IntoWind", "TurnedIntoWind", "IntoWind") -- Group turned into wind. - self:AddTransition("IntoWind", "TurnIntoWindStop", "IntoWind") -- Stop a turn into wind. - self:AddTransition("IntoWind", "TurnIntoWindOver", "Cruising") -- Turn into wind is over. + self:AddTransition("*", "TurnIntoWind", "Cruising") -- Command the group to turn into the wind. + self:AddTransition("*", "TurnedIntoWind", "*") -- Group turned into wind. + self:AddTransition("*", "TurnIntoWindStop", "*") -- Stop a turn into wind. + self:AddTransition("*", "TurnIntoWindOver", "*") -- Turn into wind is over. self:AddTransition("*", "TurningStarted", "*") -- Group started turning. self:AddTransition("*", "TurningStopped", "*") -- Group stopped turning. - self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards. - self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. + self:AddTransition("*", "Detour", "Cruising") -- Make a detour to a coordinate and resume route afterwards. + self:AddTransition("*", "DetourReached", "*") -- Group reached the detour coordinate. self:AddTransition("*", "CollisionWarning", "*") -- Collision warning. self:AddTransition("*", "ClearAhead", "*") -- Clear ahead. @@ -701,9 +701,6 @@ function NAVYGROUP:onafterSpawned(From, Event, To) else self:FullStop() end - - -- Update status. - self:__Status(-0.1) end @@ -938,8 +935,12 @@ function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To, IntoWindData) -- Detour to where we left the route. self:T(self.lid.."FF Turn Into Wind Over ==> Uturn!") - self:Detour(self.intowind.Coordinate, self:GetSpeedCruise(), 0, true) - + + -- ID of current waypoint. + local uid=self:GetWaypointCurrent().uid + + self:AddWaypoint(self.intowind.Coordinate, self:GetSpeedCruise(), uid) + else --- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ebbc98861..8e45f7a07 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -5732,6 +5732,9 @@ end -- @return #OPSGROUP self function OPSGROUP:AddOpsTransport(OpsTransport) + -- Scheduled. + OpsTransport:Scheduled() + -- Add this group as carrier for the transport. OpsTransport:_AddCarrier(self) diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 6cdbf7430..0bd288cea 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -186,12 +186,13 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- Set some string id for output to DCS.log file. self.lid=string.format("OPSTRANSPORT [UID=%d] | ", _OPSTRANSPORTID) - -- Defaults. + -- UID of this transport. self.uid=_OPSTRANSPORTID - self.pickupzone=Pickupzone - self.deployzone=Deployzone - self.embarkzone=Pickupzone + -- Defaults. + self:SetPickupZone(Pickupzone) + self:SetDeployZone(Deployzone) + self:SetEmbarkZone() -- Default is pickup zone. self.cargos={} self.carriers={} self.Ncargo=0 @@ -214,7 +215,7 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self:AddTransition("*", "Planned", OPSTRANSPORT.Status.PLANNED) -- Cargo transport was planned. self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Queued", OPSTRANSPORT.Status.QUEUED) -- Cargo is queued at at least one carrier. self:AddTransition(OPSTRANSPORT.Status.QUEUED, "Requested", OPSTRANSPORT.Status.REQUESTED) -- Transport assets have been requested from a warehouse. - self:AddTransition(OPSTRANSPORT.Status.QUEUED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. + self:AddTransition(OPSTRANSPORT.Status.REQUESTED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. self:AddTransition("*", "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. @@ -290,6 +291,24 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) return self end +--- Set pickup zone. +-- @param #OPSTRANSPORT self +-- @param Core.Zone#ZONE PickupZone Zone where the troops are picked up. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetPickupZone(PickupZone) + self.pickupzone=PickupZone + return self +end + +--- Set deploy zone. +-- @param #OPSTRANSPORT self +-- @param Core.Zone#ZONE DeployZone Zone where the troops are deployed. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetDeployZone(DeployZone) + self.deployzone=DeployZone + return self +end + --- Set embark zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE EmbarkZone Zone where the troops are embarked. @@ -792,6 +811,16 @@ function OPSTRANSPORT:IsReadyToGo() -- Current abs time. local Tnow=timer.getAbsTime() + + -- Pickup and deploy zones must be set. + if not self.pickupzone then + text=text.."No, pickup zone not defined!" + return false + end + if not self.deployzone then + text=text.."No, deploy zone not defined!" + return false + end -- Start time did not pass yet. if self.Tstart and Tnow=1 then + if self.verbose>=1 then - -- Info text. - local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, #self.carriers,self.Ncarrier) + -- Info text. + local pickupname=self.pickupzone and self.pickupzone:GetName() or "Unknown" + local deployname=self.deployzone and self.deployzone:GetName() or "Unknown" + local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), pickupname, deployname, self.Ncargo, self.Ndelivered, #self.carriers, self.Ncarrier) -- Info about cargo and carrier. if self.verbose>=2 then From c6ebbc61222e49635fc3aaa9a9aa25a761fc5b44 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 23 Aug 2021 23:34:15 +0200 Subject: [PATCH 077/141] OPS Strange version buggy --- .../Moose/Functional/Warehouse.lua | 22 ++++-- Moose Development/Moose/Ops/AirWing.lua | 15 ++++ Moose Development/Moose/Ops/ArmyGroup.lua | 35 +++++++--- Moose Development/Moose/Ops/Cohort.lua | 4 +- Moose Development/Moose/Ops/FlightGroup.lua | 40 +++++++++-- Moose Development/Moose/Ops/Legion.lua | 9 +-- Moose Development/Moose/Ops/NavyGroup.lua | 55 +++++++++------ Moose Development/Moose/Ops/OpsGroup.lua | 68 +++++++++++-------- Moose Development/Moose/Ops/OpsTransport.lua | 4 +- 9 files changed, 172 insertions(+), 80 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index a3afd7853..836e053cd 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -2596,6 +2596,12 @@ function WAREHOUSE:SetSpawnZone(zone, maxdist) return self end +--- Get the spawn zone. +-- @param #WAREHOUSE self +-- @return Core.Zone#ZONE The spawn zone. +function WAREHOUSE:GetSpawnZone() + return self.spawnzone +end --- Set a warehouse zone. If this zone is captured, the warehouse and all its assets fall into the hands of the enemy. -- @param #WAREHOUSE self @@ -6331,9 +6337,17 @@ function WAREHOUSE:_OnEventBirth(EventData) -- Set born to true. request.born=true + + + if not asset.spawned then + asset.spawned=1 + else + asset.spawned=asset.spawned+1 + end + -- Birth is triggered for each unit. We need to make sure not to call this too often! - if not asset.spawned then + if asset.spawned==asset.nunits then -- Remove asset from stock. self:_DeleteStockItem(asset) @@ -6355,9 +6369,9 @@ function WAREHOUSE:_OnEventBirth(EventData) group:SetState(group, "WAREHOUSE", self) -- Asset spawned FSM function. - --self:__AssetSpawned(1, group, asset, request) - --env.info(string.format("FF asset spawned %s, %s", asset.spawngroupname, EventData.IniUnitName)) - self:AssetSpawned(group, asset, request) + -- This needs to be delayed a bit for all units to be present. Especially, since MOOSE needs a birth event for UNITs to be added to the _DATABASE. + self:__AssetSpawned(0.1, group, asset, request) + --self:AssetSpawned(group, asset, request) end diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 96f171c07..0efa1fdea 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -840,6 +840,21 @@ function AIRWING:onafterStatus(From, Event, To) self:I(self.lid..text) end + ---------------- + -- Transport --- + ---------------- + + -- Check if any transports should be cancelled. + --self:_CheckTransports() + + -- Get next mission. + local transport=self:_GetNextTransport() + + -- Request mission execution. + if transport then + self:TransportRequest(transport) + end + -------------- -- Mission --- -------------- diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 4dd1d2bd5..c3e957dfe 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -350,10 +350,9 @@ function ARMYGROUP:Status() -- FSM state. local fsmstate=self:GetState() + -- Is group alive? local alive=self:IsAlive() - - env.info(self.lid.."FF status="..fsmstate) - + if alive then --- @@ -1228,19 +1227,35 @@ function ARMYGROUP:_InitGroup(Template) -- Units of the group. local units=self.group:GetUnits() + -- DCS group. + local dcsgroup=Group.getByName(self.groupname) + local size0=dcsgroup:getInitialSize() + + -- Quick check. + if #units~=size0 then + self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0)) + end + -- Add elemets. for _,unit in pairs(units) do self:_AddElementByName(unit:GetName()) end - - -- Get Descriptors. - self.descriptors=units[1]:GetDesc() + + -- Get first unit. This is used to extract other parameters. + local unit=units[1] --Wrapper.Unit#UNIT - -- Set type name. - self.actype=units[1]:GetTypeName() + if unit then + + -- Get Descriptors. + self.descriptors=unit:GetDesc() - -- Init done. - self.groupinitialized=true + -- Set type name. + self.actype=unit:GetTypeName() + + -- Init done. + self.groupinitialized=true + + end return self end diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 37d6ad914..fdd4aa811 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -960,7 +960,7 @@ end --- Check if a mission type is contained in a list of possible capabilities. -- @param #COHORT self -- @param #table MissionTypes The requested mission type. Can also be passed as a single mission type `#string`. --- @param #table Capabilities A table with possible capabilities. +-- @param #table Capabilities (Optional) A table with possible capabilities `Ops.Auftrag#AUFTRAG.Capability`. Default is capabilities of the cohort. -- @return #boolean If true, the requested mission type is part of the possible mission types. function COHORT:CheckMissionCapability(MissionTypes, Capabilities) @@ -968,6 +968,8 @@ function COHORT:CheckMissionCapability(MissionTypes, Capabilities) MissionTypes={MissionTypes} end + Capabilities=Capabilities or self.missiontypes + for _,cap in pairs(Capabilities) do local capability=cap --Ops.Auftrag#AUFTRAG.Capability for _,MissionType in pairs(MissionTypes) do diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index c7483e648..18a804e6b 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1983,6 +1983,13 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) trepeat=-5 allowed=false end + + if self:IsUncontrolled() then + -- Not airborne yet. Try again in 5 sec. + self:T(self.lid.."Update route denied. Group is UNCONTROLLED ==> checking back in 5 sec") + trepeat=-5 + allowed=false + end if n and n<1 then self:E(self.lid.."Update route denied because waypoint n<1!") @@ -2065,13 +2072,16 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) -- Waypoint type. local waypointType=COORDINATE.WaypointType.TurningPoint + local waypointAction=COORDINATE.WaypointAction.TurningPoint if self:IsLanded() or self:IsLandedAt() or self:IsAirborne()==false then -- Had some issues with passing waypoint function of the next WP called too ealy when the type is TurningPoint. Setting it to TakeOff solved it! waypointType=COORDINATE.WaypointType.TakeOff + env.info("FF takeoff type waypoint") + --waypointAction=COORDINATE.WaypointAction.FromParkingArea end -- Set current waypoint or we get problem that the _PassingWaypoint function is triggered too early, i.e. right now and not when passing the next WP. - local current=self.group:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO, waypointType, COORDINATE.WaypointAction.TurningPoint, speed, true, nil, {}, "Current") + local current=self.group:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO, waypointType, waypointAction, speed, true, nil, {}, "Current") table.insert(wp, current) local Nwp=self.waypoints and #self.waypoints or 0 @@ -2084,7 +2094,13 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) -- Debug info. local hb=self.homebase and self.homebase:GetName() or "unknown" local db=self.destbase and self.destbase:GetName() or "unknown" - self:T(self.lid..string.format("Updating route for WP #%d-%d homebase=%s destination=%s", n, #wp, hb, db)) + self:T(self.lid..string.format("Updating route for WP #%d-%d [%s], homebase=%s destination=%s", n, #wp, self:GetState(), hb, db)) + + -- Print waypoints. + for i,w in pairs(wp) do + env.info("FF waypoint index="..i) + self:I(w) + end if #wp>1 then @@ -2243,7 +2259,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) self:T(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route", self:GetState())) -- Update route. - self:__UpdateRoute(-1) + self:__UpdateRoute(-0.01) end end @@ -3086,13 +3102,25 @@ function FLIGHTGROUP:_InitGroup(Template) self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") end + -- Units of the group. + local units=self.group:GetUnits() + + -- DCS group. + local dcsgroup=Group.getByName(self.groupname) + local size0=dcsgroup:getInitialSize() + + -- Quick check. + if #units~=size0 then + self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0)) + end + -- Add elemets. - for _,unit in pairs(self.group:GetUnits()) do + for _,unit in pairs(units) do self:_AddElementByName(unit:GetName()) end -- Get first unit. This is used to extract other parameters. - local unit=self.group:GetUnit(1) + local unit=units[1] --Wrapper.Unit#UNIT if unit then @@ -3469,7 +3497,7 @@ function FLIGHTGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Altitud -- Update route. if Updateroute==nil or Updateroute==true then - self:__UpdateRoute(-1) + self:__UpdateRoute(-0.01) end return waypoint diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 4154ca1c7..4e59e5151 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -158,11 +158,13 @@ function LEGION:AddMission(Mission) -- Add legion to mission. Mission:AddLegion(self) + --[[ if Mission.opstransport then Mission.opstransport:SetPickupZone(self.spawnzone) Mission.opstransport:SetEmbarkZone(self.spawnzone) self:AddOpsTransport(Mission.opstransport) end + ]] -- Add mission to queue. table.insert(self.missionqueue, Mission) @@ -1007,12 +1009,7 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) --mission:SetTACAN(Tacan, Morse, UnitName, Band) end - -- Transport for mission assets. - if mission.opstransport then - mission.opstransport:AddCargoGroups(self) - end - - -- Add mission to flightgroup queue. + -- Add mission to flightgroup queue. If mission has an OPSTRANSPORT attached, all added OPSGROUPS are added as CARGO for a transport. flightgroup:AddMission(mission) -- Trigger event. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 2b8cec8ba..f6b5f0058 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -461,8 +461,11 @@ function NAVYGROUP:Status(From, Event, To) -- FSM state. local fsmstate=self:GetState() + + -- Is group alive? + local alive=self:IsAlive() - if self:IsAlive() then + if alive then --- -- Detection @@ -518,7 +521,11 @@ function NAVYGROUP:Status(From, Event, To) self:Cruise() end end - end + end + + end + + if alive~=nil then if self.verbose>=1 then @@ -548,19 +555,7 @@ function NAVYGROUP:Status(From, Event, To) local text=string.format("%s [ROE=%d,AS=%d, T/M=%d/%d]: Wp=%d[%d]-->%d[%d] (of %d) Dist=%.1f NM ETA=%s - Speed=%.1f (%.1f) kts, Depth=%.1f m, Hdg=%03d, Turn=%s Collision=%d IntoWind=%s", fsmstate, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, #self.waypoints or 0, wpDist, wpETA, speed, speedExpected, alt, self.heading, turning, freepath, intowind) self:I(self.lid..text) - - if false then - local text="Waypoints:" - for i,wp in pairs(self.waypoints) do - local waypoint=wp --Ops.OpsGroup#OPSGROUP.Waypoint - text=text..string.format("\n%d. UID=%d", i, waypoint.uid) - if i==self.currentwp then - text=text.." current!" - end - end - env.info(text) - end - + end else @@ -575,7 +570,7 @@ function NAVYGROUP:Status(From, Event, To) -- Recovery Windows --- - if self.verbose>=2 and #self.Qintowind>0 then + if alive and self.verbose>=2 and #self.Qintowind>0 then -- Debug output: local text=string.format(self.lid.."Turn into wind time windows:") @@ -1190,20 +1185,36 @@ function NAVYGROUP:_InitGroup(Template) -- Get all units of the group. local units=self.group:GetUnits() + + -- DCS group. + local dcsgroup=Group.getByName(self.groupname) + local size0=dcsgroup:getInitialSize() + + -- Quick check. + if #units~=size0 then + self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0)) + end -- Add elemets. for _,unit in pairs(units) do self:_AddElementByName(unit:GetName()) end - -- Get Descriptors. - self.descriptors=units[1]:GetDesc() + -- Get first unit. This is used to extract other parameters. + local unit=units[1] --Wrapper.Unit#UNIT - -- Set type name. - self.actype=units[1]:GetTypeName() + if unit then - -- Init done. - self.groupinitialized=true + -- Get Descriptors. + self.descriptors=unit:GetDesc() + + -- Set type name. + self.actype=unit:GetTypeName() + + -- Init done. + self.groupinitialized=true + + end return self end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8e45f7a07..f396d4c29 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1414,7 +1414,7 @@ function OPSGROUP:DestroyUnit(UnitName, Delay) -- Create a "Unit Lost" event. local EventTime=timer.getTime() - if self.isAircraft then + if self:IsFlightgroup() then self:CreateEventUnitLost(EventTime, unit) else self:CreateEventDead(EventTime, unit) @@ -2469,8 +2469,8 @@ function OPSGROUP:OnEventBirth(EventData) if element and element.status~=OPSGROUP.ElementStatus.SPAWNED then - -- Set element to spawned state. - self:ElementSpawned(element) + -- Set element to spawned state. We need to delay this. + self:__ElementSpawned(0.05, element) end @@ -2646,12 +2646,11 @@ end --- Clear DCS tasks. -- @param #OPSGROUP self --- @param #table DCSTask DCS task structure. -- @return #OPSGROUP self function OPSGROUP:ClearTasks() if self:IsAlive() then - self.group:ClearTasks() self:I(self.lid..string.format("CLEARING Tasks")) + self.group:ClearTasks() end return self end @@ -3166,8 +3165,8 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- NOTE: I am pushing the task instead of setting it as it seems to keep the mission task alive. -- There were issues that flights did not proceed to a later waypoint because the task did not finish until the fired missiles -- impacted (took rather long). Then the flight flew to the nearest airbase and one lost completely the control over the group. - self:PushTask(TaskFinal) - --self:SetTask(TaskFinal) + --self:PushTask(TaskFinal) + self:SetTask(TaskFinal) elseif Task.type==OPSGROUP.TaskType.WAYPOINT then @@ -3307,14 +3306,16 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) else --Mission paused. Do nothing! end + else if Task.description=="Engage_Target" then self:Disengage() end - self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec") - self:_CheckGroupDone(1) + -- + self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 0 sec") + self:_CheckGroupDone() end end @@ -3575,7 +3576,7 @@ function OPSGROUP:onbeforeMissionStart(From, Event, To, Mission) end -- Startup group if it is uncontrolled. - if self.isAircraft and self:IsUncontrolled() then + if self:IsFlightgroup() and self:IsUncontrolled() then self:StartUncontrolled(delay) end @@ -3966,7 +3967,7 @@ function OPSGROUP:RouteToMission(mission, delay) self:SwitchAlarmstate(mission.optionAlarm) end -- Formation - if mission.optionFormation and self.isAircraft then + if mission.optionFormation and self:IsFlightgroup() then self:SwitchFormation(mission.optionFormation) end -- Radio frequency and modulation. @@ -4036,7 +4037,7 @@ function OPSGROUP:_QueueUpdate() local ready=true -- For aircraft check airborne. - if self.isAircraft then + if self:IsFlightgroup() then ready=self:IsAirborne() end @@ -4219,8 +4220,8 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Check if all tasks/mission are done? -- Note, we delay it for a second to let the OnAfterPassingwaypoint function to be executed in case someone wants to add another waypoint there. - if ntasks==0 then - self:_CheckGroupDone(0.1) + if ntasks==0 and self:HasPassedFinalWaypoint() then + self:_CheckGroupDone(0.01) end -- Debug info. @@ -5356,7 +5357,10 @@ function OPSGROUP:_CheckCargoTransport() local text="" for i,_transport in pairs(self.cargoqueue) do local transport=_transport --#Ops.OpsTransport#OPSTRANSPORT - text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport:GetState(), transport.pickupzone:GetName(), transport.deployzone:GetName()) + + local pickupname=transport.pickupzone and transport.pickupzone:GetName() or "unknown" + local deployname=transport.deployzone and transport.deployzone:GetName() or "unknown" + text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport:GetState(), pickupname, deployname) for j,_cargo in pairs(transport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup local state=cargo.opsgroup:GetState() @@ -5386,7 +5390,9 @@ function OPSGROUP:_CheckCargoTransport() -- Debug info. if self.verbose>=2 then - local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) + local pickupname=self.cargoTransport.pickupzone and self.cargoTransport.pickupzone:GetName() or "unknown" + local deployname=self.cargoTransport.deployzone and self.cargoTransport.deployzone:GetName() or "unknown" + local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, pickupname, deployname) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup local name=cargo.opsgroup:GetName() @@ -6483,7 +6489,7 @@ function OPSGROUP:onafterLoaded(From, Event, To) -- Cancel landedAt task. if self:IsFlightgroup() and self:IsLandedAt() then local Task=self:GetTaskCurrent() - self:TaskCancel(Task) + self:__TaskCancel(1, Task) end -- Order group to transport. @@ -6552,7 +6558,7 @@ function OPSGROUP:onafterTransport(From, Event, To) self:FullStop() end - -- Start loading. + -- Start unloading. self:__UnLoading(-5) else @@ -6598,8 +6604,9 @@ function OPSGROUP:onafterTransport(From, Event, To) --- -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. - Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=1 + Coordinate:MarkToAll("landing",ReadOnly,Text) + env.info("FF helo add waypoint detour") + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, 200) ; waypoint.detour=1 else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") @@ -7817,6 +7824,8 @@ function OPSGROUP:Route(waypoints, delay) -- DCS task combo. local Tasks={} + + self:ClearTasks(DCSTask) -- Route (Mission) task. local TaskRoute=self.group:TaskRoute(waypoints) @@ -7914,7 +7923,8 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) if wpnext and (opsgroup.currentwp<#opsgroup.waypoints or opsgroup.adinfinitum or wpistemp) then - opsgroup:I(opsgroup.lid..string.format("Next waypoint UID=%d index=%d", wpnext.uid, opsgroup:GetWaypointIndex(wpnext.uid))) + -- Debug info. + opsgroup:T(opsgroup.lid..string.format("Next waypoint UID=%d index=%d", wpnext.uid, opsgroup:GetWaypointIndex(wpnext.uid))) -- Set formation. if opsgroup.isGround then @@ -7930,8 +7940,6 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else - env.info(opsgroup.lid.."FF 300") - -- Set passed final waypoint. opsgroup:_PassedFinalWaypoint(true, "_PassingWaypoint No next Waypoint found") @@ -7972,6 +7980,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) if opsgroup.isFlightgroup then -- Land at current pos and wait for 60 min max. + env.info("FF LandAt for Pickup") opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) else @@ -7985,6 +7994,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) if opsgroup.isFlightgroup then -- Land at current pos and wait for 60 min max. + env.info("FF LandAt for Transporting") opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) else @@ -8271,7 +8281,7 @@ function OPSGROUP:SetDefaultTACAN(Channel, Morse, UnitName, Band, OffSwitch) self.tacanDefault.Morse=Morse or "XXX" self.tacanDefault.BeaconName=UnitName - if self.isAircraft then + if self:IsFlightgroup() then Band=Band or "Y" else Band=Band or "X" @@ -8356,7 +8366,7 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) -- System local System=BEACON.System.TACAN - if self.isAircraft then + if self:IsFlightgroup() then System=BEACON.System.TACAN_TANKER_Y end @@ -8592,7 +8602,7 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) Frequency=Frequency or self.radioDefault.Freq Modulation=Modulation or self.radioDefault.Modu - if self.isAircraft and not self.radio.On then + if self:IsFlightgroup() and not self.radio.On then self.group:SetOption(AI.Option.Air.id.SILENCE, false) end @@ -8621,7 +8631,7 @@ function OPSGROUP:TurnOffRadio() if self:IsAlive() then - if self.isAircraft then + if self:IsFlightgroup() then -- Set group to be silient. self.group:SetOption(AI.Option.Air.id.SILENCE, true) @@ -8662,7 +8672,7 @@ function OPSGROUP:SwitchFormation(Formation) Formation=Formation or self.optionDefault.Formation - if self.isAircraft then + if self:IsFlightgroup() then self.group:SetOption(AI.Option.Air.id.FORMATION, Formation) @@ -9556,7 +9566,7 @@ end function OPSGROUP:_PassedFinalWaypoint(final, comment) -- Debug info. - self:I(self.lid..string.format("Passed final waypoint=%s [from %s]: comment \"%s\"", tostring(final), tostring(self.passedfinalwp), tostring(comment))) + self:T(self.lid..string.format("Passed final waypoint=%s [from %s]: comment \"%s\"", tostring(final), tostring(self.passedfinalwp), tostring(comment))) -- Set value. self.passedfinalwp=final diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 0bd288cea..d82192cae 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -248,11 +248,11 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) -- Check type of GroupSet provided. if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then - + -- We got a single GROUP or OPSGROUP object. local cargo=self:_CreateCargoGroupData(GroupSet) - if cargo then + if cargo then table.insert(self.cargos, cargo) self.Ncargo=self.Ncargo+1 end From 259b201e65f14625b5411a3575c94a34b76c2ec6 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 24 Aug 2021 23:10:38 +0200 Subject: [PATCH 078/141] OPS Good version solving a few bugs and adding some new stuff. --- Moose Development/Moose/Core/Point.lua | 9 +- Moose Development/Moose/Ops/ArmyGroup.lua | 6 +- Moose Development/Moose/Ops/FlightGroup.lua | 53 +++-- Moose Development/Moose/Ops/NavyGroup.lua | 4 +- Moose Development/Moose/Ops/OpsGroup.lua | 207 +++++++++++++------- 5 files changed, 185 insertions(+), 94 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index b450eebbf..4245e9591 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -225,6 +225,8 @@ do -- COORDINATE -- @field #string FlyoverPoint Fly over point. -- @field #string FromParkingArea From parking area. -- @field #string FromParkingAreaHot From parking area hot. + -- @field #string FromGroundAreaHot From ground area hot. + -- @field #string FromGroundArea From ground area. -- @field #string FromRunway From runway. -- @field #string Landing Landing. -- @field #string LandingReFuAr Landing and refuel and rearm. @@ -233,6 +235,8 @@ do -- COORDINATE FlyoverPoint = "Fly Over Point", FromParkingArea = "From Parking Area", FromParkingAreaHot = "From Parking Area Hot", + FromGroundAreaHot = "From Ground Area Hot", + FromGroundArea = "From Ground Area", FromRunway = "From Runway", Landing = "Landing", LandingReFuAr = "LandingReFuAr", @@ -243,6 +247,7 @@ do -- COORDINATE -- @field #string TakeOffParking Take of parking. -- @field #string TakeOffParkingHot Take of parking hot. -- @field #string TakeOff Take off parking hot. + -- @field #string TakeOffGroundHot Take of from ground hot. -- @field #string TurningPoint Turning point. -- @field #string Land Landing point. -- @field #string LandingReFuAr Landing and refuel and rearm. @@ -250,9 +255,11 @@ do -- COORDINATE TakeOffParking = "TakeOffParking", TakeOffParkingHot = "TakeOffParkingHot", TakeOff = "TakeOffParkingHot", + TakeOffGroundHot = "TakeOffGroundHot", + TakeOffGround = "TakeOffGround", TurningPoint = "Turning Point", Land = "Land", - LandingReFuAr = "LandingReFuAr", + LandingReFuAr = "LandingReFuAr", } diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index c3e957dfe..3c5a9d193 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -83,7 +83,7 @@ ARMYGROUP.version="0.7.0" -- @param Wrapper.Group#GROUP group The GROUP object. Can also be given by its group name as `#string`. -- @return #ARMYGROUP self function ARMYGROUP:New(group) - + -- First check if we already have an OPS group for this group. local og=_DATABASE:GetOpsGroup(group) if og then @@ -91,6 +91,9 @@ function ARMYGROUP:New(group) return og end + -- First set ARMYGROUP. + self.isArmygroup=true + -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #ARMYGROUP @@ -98,7 +101,6 @@ function ARMYGROUP:New(group) self.lid=string.format("ARMYGROUP %s | ", self.groupname) -- Defaults - self.isArmygroup=true self:SetDefaultROE() self:SetDefaultAlarmstate() self:SetDetection() diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 18a804e6b..a01333e08 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -208,6 +208,9 @@ function FLIGHTGROUP:New(group) return og end + -- First set FLIGHTGROUP. + self.isFlightgroup=true + -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #FLIGHTGROUP @@ -215,7 +218,6 @@ function FLIGHTGROUP:New(group) self.lid=string.format("FLIGHTGROUP %s | ", self.groupname) -- Defaults - self.isFlightgroup=true self:SetFuelLowThreshold() self:SetFuelLowRTB() self:SetFuelCriticalThreshold() @@ -1853,6 +1855,9 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) -- Check what to do. if airwing then + -- Debug info. + self:T(self.lid..string.format("Airwing asset group %s arrived ==> Adding asset back to stock of airwing %s", self.groupname, airwing.alias)) + -- Add the asset back to the airwing. airwing:AddAsset(self.group, 1) @@ -2018,6 +2023,9 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) -- For patrol zone, we need to allow the update as we insert new waypoints. elseif task.dcstask.id=="ReconMission" then -- For recon missions, we need to allow the update as we insert new waypoints. + elseif task.description and task.description=="Task_Land_At" then + -- We allow this + env.info("FF allowing update route for Task_Land_At") else local taskname=task and task.description or "No description" self:E(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s", self.taskcurrent, tostring(taskname))) @@ -2075,9 +2083,9 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) local waypointAction=COORDINATE.WaypointAction.TurningPoint if self:IsLanded() or self:IsLandedAt() or self:IsAirborne()==false then -- Had some issues with passing waypoint function of the next WP called too ealy when the type is TurningPoint. Setting it to TakeOff solved it! - waypointType=COORDINATE.WaypointType.TakeOff - env.info("FF takeoff type waypoint") - --waypointAction=COORDINATE.WaypointAction.FromParkingArea + --waypointType=COORDINATE.WaypointType.TakeOff + waypointType=COORDINATE.WaypointType.TakeOffGroundHot + waypointAction=COORDINATE.WaypointAction.FromGroundAreaHot end -- Set current waypoint or we get problem that the _PassingWaypoint function is triggered too early, i.e. right now and not when passing the next WP. @@ -2097,10 +2105,12 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) self:T(self.lid..string.format("Updating route for WP #%d-%d [%s], homebase=%s destination=%s", n, #wp, self:GetState(), hb, db)) -- Print waypoints. + --[[ for i,w in pairs(wp) do env.info("FF waypoint index="..i) self:I(w) end + ]] if #wp>1 then @@ -2164,15 +2174,21 @@ end -- @param #number waittime Time to wait if group is done. function FLIGHTGROUP:_CheckGroupDone(delay, waittime) + -- FSM state. + local fsmstate=self:GetState() + if self:IsAlive() and self.isAI then if delay and delay>0 then + -- Debug info. + self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done in %.3f seconds...", fsmstate, delay)) + -- Delayed call. self:ScheduleOnce(delay, FLIGHTGROUP._CheckGroupDone, self) else -- Debug info. - self:T(self.lid.."Check FLIGHTGROUP done?") + self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done?", fsmstate)) -- First check if there is a paused mission that if self.missionpaused then @@ -2188,7 +2204,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) -- Group is ordered to land at an airbase. if self.isLandingAtAirbase then - self:T(self.lid.."Landing at airbase! Group NOT done...") + self:T(self.lid..string.format("Landing at airbase %s! Group NOT done...", self.isLandingAtAirbase:GetName())) return end @@ -2211,7 +2227,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) self:T(self.lid..string.format("Remaining (final=%s): missions=%d, tasks=%d, transports=%d", tostring(self.passedfinalwp), nMissions, nTasks, nTransports)) -- Final waypoint passed? - if self.passedfinalwp then + if self:HasPassedFinalWaypoint() then --- -- Final Waypoint PASSED @@ -2445,6 +2461,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) local text=string.format("Flight group set to hold at airbase %s. SpeedTo=%d, SpeedHold=%d, SpeedLand=%d", airbase:GetName(), SpeedTo, SpeedHold, SpeedLand) self:T(self.lid..text) + -- Holding altitude. local althold=self.isHelo and 1000+math.random(10)*100 or math.random(4,10)*1000 -- Holding points. @@ -2481,11 +2498,13 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) local h1=x1*math.tan(alpha) local h2=x2*math.tan(alpha) + -- Get active runway. local runway=airbase:GetActiveRunway() -- Set holding flag to 0=false. self.flaghold:Set(0) + -- Set holding time. local holdtime=2*60 if fc or self.airboss then holdtime=nil @@ -2557,14 +2576,14 @@ function FLIGHTGROUP:onbeforeWait(From, Event, To, Duration, Altitude, Speed) local Tsuspend=nil -- Check for a current task. - if self.taskcurrent>0 then + if self.taskcurrent>0 and not self:IsLandedAt() then self:I(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!")) Tsuspend=-30 allowed=false end -- Check for a current transport assignment. - if self.cargoTransport then + if self.cargoTransport and not self:IsLandedAt() then self:I(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) Tsuspend=-30 allowed=false @@ -2604,14 +2623,6 @@ function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Altitude, Speed) --TODO: set ROE passive. introduce roe event/state/variable. - -- Orbit task. - local TaskOrbit=self.group:TaskOrbit(Coord, UTILS.FeetToMeters(Altitude), UTILS.KnotsToMps(Speed)) - - -- Orbit task. - local TaskFunction=self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting", self) - local DCSTasks=self.group:TaskCombo({TaskOrbit, TaskFunction}) - - -- Orbit until flaghold=1 (true) but max 5 min if no FC is giving the landing clearance. local TaskOrbit = self.group:TaskOrbit(Coord, UTILS.FeetToMeters(Altitude), UTILS.KnotsToMps(Speed)) local TaskStop = self.group:TaskCondition(nil, nil, nil, nil, Duration) @@ -2842,11 +2853,11 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Point#COORDINATE Coordinate The coordinate where to land. Default is current position. --- @param #number Duration The duration in seconds to remain on ground. Default 600 sec (10 min). +-- @param #number Duration The duration in seconds to remain on ground. Default `nil` = forever. function FLIGHTGROUP:onafterLandAt(From, Event, To, Coordinate, Duration) -- Duration. - Duration=Duration or 600 + --Duration=Duration or 600 Coordinate=Coordinate or self:GetCoordinate() @@ -2855,7 +2866,7 @@ function FLIGHTGROUP:onafterLandAt(From, Event, To, Coordinate, Duration) local Task=self:NewTaskScheduled(DCStask, 1, "Task_Land_At", 0) self:TaskExecute(Task) - + end --- On after "FuelLow" event. @@ -3012,7 +3023,7 @@ function FLIGHTGROUP._FinishedWaiting(group, flightgroup) flightgroup.dTwait=nil -- Trigger Holding event. - flightgroup:_CheckGroupDone(1) + flightgroup:_CheckGroupDone(0.1) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index f6b5f0058..ca2230f8c 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -114,6 +114,9 @@ function NAVYGROUP:New(group) return og end + -- First set NAVYGROUP. + self.isNavygroup=true + -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #NAVYGROUP @@ -126,7 +129,6 @@ function NAVYGROUP:New(group) self:SetDefaultAlarmstate() self:SetPatrolAdInfinitum(true) self:SetPathfinding(false) - self.isNavygroup=true -- Add FSM transitions. -- From State --> Event --> To State diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index f396d4c29..1f31accd0 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -18,6 +18,7 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #string groupname Name of the group. -- @field Wrapper.Group#GROUP group Group object. +-- @field DCS#Controller controller The DCS controller of the group. -- @field DCS#Template template Template table of the group. -- @field #boolean isLateActivated Is the group late activated. -- @field #boolean isUncontrolled Is the group uncontrolled. @@ -513,6 +514,10 @@ function OPSGROUP:New(group) -- Set the template. self:_SetTemplate() + -- Set DCS group and controller. + self.dcsgroup=self:GetDCSGroup() + self.controller=self.dcsgroup:getController() + -- Init set of detected units. self.detectedunits=SET_UNIT:New() @@ -521,6 +526,9 @@ function OPSGROUP:New(group) -- Init inzone set. self.inzones=SET_ZONE:New() + + -- Set Default altitude. + self:SetDefaultAltitude() -- Laser. self.spot={} @@ -555,7 +563,11 @@ function OPSGROUP:New(group) self:AddTransition("*", "Destroyed", "*") -- The whole group is dead. self:AddTransition("*", "Damaged", "*") -- Someone in the group took damage. - self:AddTransition("*", "UpdateRoute", "*") -- Update route of group. Only if airborne. + self:AddTransition("*", "UpdateRoute", "*") -- Update route of group. + + self:AddTransition("*", "PassingWaypoint", "*") -- Group passed a waypoint. + self:AddTransition("*", "PassedFinalWaypoint", "*") -- Group passed the waypoint. + self:AddTransition("*", "GotoWaypoint", "*") -- Group switches to a specific waypoint. self:AddTransition("*", "Wait", "*") -- Group will wait for further orders. @@ -569,9 +581,6 @@ function OPSGROUP:New(group) self:AddTransition("*", "DetectedGroupKnown", "*") -- A known unit is still detected. self:AddTransition("*", "DetectedGroupLost", "*") -- Group lost a detected target group. - self:AddTransition("*", "PassingWaypoint", "*") -- Group passed a waypoint. - self:AddTransition("*", "GotoWaypoint", "*") -- Group switches to a specific waypoint. - self:AddTransition("*", "OutOfAmmo", "*") -- Group is completely out of ammo. self:AddTransition("*", "OutOfGuns", "*") -- Group is out of gun shells. self:AddTransition("*", "OutOfRockets", "*") -- Group is out of rockets. @@ -744,7 +753,37 @@ end -- @param #OPSGROUP self -- @return #number Cruise speed (>0) in knots. function OPSGROUP:GetSpeedCruise() - return UTILS.KmphToKnots(self.speedCruise or self.speedMax*0.7) + local speed=UTILS.KmphToKnots(self.speedCruise or self.speedMax*0.7) + return speed +end + +--- Set default cruise altitude. +-- @param #OPSGROUP self +-- @param #number Altitude Altitude in feet. Default is 10,000 ft for airplanes and 1,000 feet for helicopters. +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultAltitude(Altitude) + if Altitude then + self.altitudeCruise=UTILS.FeetToMeters(Altitude) + else + if self:IsFlightgroup() then + if self.isHelo then + self.altitudeCruise=UTILS.FeetToMeters(1000) + else + self.altitudeCruise=UTILS.FeetToMeters(10000) + end + else + self.altitudeCruise=0 + end + end + return self +end + +--- Get default cruise speed. +-- @param #OPSGROUP self +-- @return #number Cruise altitude in feet. +function OPSGROUP:GetCruiseAltitude() + local alt=UTILS.MetersToFeet(self.altitudeCruise) + return alt end --- Set detection on or off. @@ -2469,8 +2508,8 @@ function OPSGROUP:OnEventBirth(EventData) if element and element.status~=OPSGROUP.ElementStatus.SPAWNED then - -- Set element to spawned state. We need to delay this. - self:__ElementSpawned(0.05, element) + -- Set element to spawned state. + self:ElementSpawned(element) end @@ -2605,7 +2644,8 @@ function OPSGROUP:SetTask(DCSTask) end -- Set task. - self.group:SetTask(DCSTask) + --self.group:SetTask(DCSTask) + self.controller:setTask(DCSTask) -- Debug info. local text=string.format("SETTING Task %s", tostring(DCSTask.id)) @@ -2629,7 +2669,8 @@ function OPSGROUP:PushTask(DCSTask) if self:IsAlive() then -- Push task. - self.group:PushTask(DCSTask) + --self.group:PushTask(DCSTask) + self.controller:pushTask(DCSTask) -- Debug info. local text=string.format("PUSHING Task %s", tostring(DCSTask.id)) @@ -3165,8 +3206,8 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- NOTE: I am pushing the task instead of setting it as it seems to keep the mission task alive. -- There were issues that flights did not proceed to a later waypoint because the task did not finish until the fired missiles -- impacted (took rather long). Then the flight flew to the nearest airbase and one lost completely the control over the group. - --self:PushTask(TaskFinal) - self:SetTask(TaskFinal) + self:PushTask(TaskFinal) + --self:SetTask(TaskFinal) elseif Task.type==OPSGROUP.TaskType.WAYPOINT then @@ -3312,10 +3353,14 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) if Task.description=="Engage_Target" then self:Disengage() end + + if Task.description=="Task_Land_At" then + self:Wait(60, 100) + else + self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 0 sec") + self:_CheckGroupDone() + end - -- - self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 0 sec") - self:_CheckGroupDone() end end @@ -3427,6 +3472,11 @@ function OPSGROUP:CountRemainingTransports() N=N+1 end end + + -- In case we directly set the cargo transport (not in queue). + if N==0 and self.cargoTransport then + N=1 + end return N end @@ -4295,6 +4345,19 @@ function OPSGROUP:_SetWaypointTasks(Waypoint) return #taskswp end +--- On after "PassedFinalWaypoint" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterPassedFinalWaypoint(From, Event, To) + self:T(self.lid..string.format("Group passed final waypoint")) + + -- Check if group is done? No tasks mission running. + self:_CheckGroupDone() + +end + --- On after "GotoWaypoint" event. Group will got to the given waypoint and execute its route from there. -- @param #OPSGROUP self -- @param #string From From state. @@ -5989,7 +6052,7 @@ function OPSGROUP:AddWeightCargo(UnitName, Weight) -- For airborne units, we set the weight in game. if self.isFlightgroup then - trigger.action.setUnitInternalCargo(element.name, element.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + --trigger.action.setUnitInternalCargo(element.name, element.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo end end @@ -6202,8 +6265,8 @@ function OPSGROUP:onafterPickup(From, Event, To) if airbaseCurrent then -- Activate uncontrolled group. - if self:IsParking() then - self:StartUncontrolled() + if self:IsParking() and self:IsUncontrolled() then + self:StartUncontrolled(1) end else @@ -6220,14 +6283,13 @@ function OPSGROUP:onafterPickup(From, Event, To) --- -- Activate uncontrolled group. - if self:IsParking() then - self:StartUncontrolled() - end + if self:IsParking() and self:IsUncontrolled() then + self:StartUncontrolled(1) + end -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. - Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=1 - + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, 200, false) ; waypoint.detour=1 + else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") end @@ -6486,12 +6548,6 @@ function OPSGROUP:onafterLoaded(From, Event, To) -- Debug info. self:T(self.lid.."Carrier Loaded ==> Transport") - -- Cancel landedAt task. - if self:IsFlightgroup() and self:IsLandedAt() then - local Task=self:GetTaskCurrent() - self:__TaskCancel(1, Task) - end - -- Order group to transport. self:__Transport(1) @@ -6588,7 +6644,7 @@ function OPSGROUP:onafterTransport(From, Event, To) if airbaseCurrent then -- Activate uncontrolled group. - if self:IsParking() then + if self:IsParking() and self:IsUncontrolled() then self:StartUncontrolled() end @@ -6597,16 +6653,22 @@ function OPSGROUP:onafterTransport(From, Event, To) self:LandAtAirbase(airbaseDeploy) end - elseif self.isHelo or self.isVTOL then + elseif self.isHelo then --- - -- Helo or VTOL can also land in a zone + -- Helo can also land in a zone --- -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. - Coordinate:MarkToAll("landing",ReadOnly,Text) - env.info("FF helo add waypoint detour") - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, 200) ; waypoint.detour=1 + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, 200, false) ; waypoint.detour=1 + + -- Cancel landedAt task. This should trigger Cruise once airborne. + if self:IsFlightgroup() and self:IsLandedAt() then + local Task=self:GetTaskCurrent() + --if Task and Task.description=="Task_Landed_At" then + self:__TaskCancel(5, Task) + --end + end else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") @@ -7064,7 +7126,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self:ClearWaypoints(self.currentwp+1) if self.isArmygroup then - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=1 + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, nil, ENUMS.Formation.Vehicle.Diamond) ; waypoint.detour=1 self:Cruise() else local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=1 @@ -7344,7 +7406,7 @@ function OPSGROUP:_CheckGroupDone(delay) -- Finite Patrol --- - if self.passedfinalwp then + if self:HasPassedFinalWaypoint() then --- -- Passed FINAL waypoint @@ -7758,20 +7820,6 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) self:_AddWaypoint(waypoint) - -- Add waypoint. - --[[ - if self:IsFlightgroup() then - FLIGHTGROUP.AddWaypoint(self, coordinate, speedknots, index-1, Altitude, false) - elseif self:IsArmygroup() then - ARMYGROUP.AddWaypoint(self, coordinate, speedknots, index-1, Formation, false) - elseif self:IsNavygroup() then - NAVYGROUP.AddWaypoint(self, coordinate, speedknots, index-1, Depth, false) - else - -- Should not happen! - self:AddWaypoint(coordinate, speedknots, index-1, nil, false) - end - ]] - end -- Debug info. @@ -7822,25 +7870,15 @@ function OPSGROUP:Route(waypoints, delay) if self:IsAlive() then - -- DCS task combo. - local Tasks={} - - self:ClearTasks(DCSTask) + -- Clear all DCS tasks. + --self:ClearTasks() -- Route (Mission) task. local TaskRoute=self.group:TaskRoute(waypoints) - table.insert(Tasks, TaskRoute) - - -- TaskCombo of enroute and mission tasks. - local TaskCombo=self.group:TaskCombo(Tasks) - - -- Set tasks. - if #Tasks>1 then - self:SetTask(TaskCombo) - else - self:SetTask(TaskRoute) - end + -- Set mission task. + self:SetTask(TaskRoute) + else self:E(self.lid.."ERROR: Group is not alive! Cannot route group.") end @@ -7947,6 +7985,10 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Trigger PassingWaypoint event. if waypoint.temp then + + --- + -- Temporary Waypoint + --- if opsgroup:IsNavygroup() or opsgroup:IsArmygroup() then --TODO: not sure if this works with FLIGHTGROUPS @@ -7955,11 +7997,19 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif waypoint.astar then + --- + -- Pathfinding Waypoint + --- + -- Cruise. opsgroup:Cruise() elseif waypoint.detour then + --- + -- Detour Waypoint + --- + if opsgroup:IsRearming() then -- Trigger Rearming event. @@ -7977,16 +8027,19 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif opsgroup:IsPickingup() then - if opsgroup.isFlightgroup then + if opsgroup:IsFlightgroup() then -- Land at current pos and wait for 60 min max. env.info("FF LandAt for Pickup") - opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) + --opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) + opsgroup:LandAt(opsgroup.cargoTransport.pickupzone:GetCoordinate(), 60*60) else + -- Wait and load cargo. opsgroup:FullStop() opsgroup:__Loading(-5) + end elseif opsgroup:IsTransporting() then @@ -8044,6 +8097,10 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) end else + + --- + -- Normal Route Waypoint + --- -- Check if the group is still pathfinding. if opsgroup.ispathfinding then @@ -8858,7 +8915,15 @@ function OPSGROUP:_AllSimilarStatus(status) -- ALIVE ---------- - if status==OPSGROUP.ElementStatus.SPAWNED then + if status==OPSGROUP.ElementStatus.INUTERO then + + -- Element INUTERO: Check that ALL others are also INUTERO + if element.status~=status then + return false + end + + + elseif status==OPSGROUP.ElementStatus.SPAWNED then -- Element SPAWNED: Check that others are not still IN UTERO if element.status~=status and @@ -9568,6 +9633,10 @@ function OPSGROUP:_PassedFinalWaypoint(final, comment) -- Debug info. self:T(self.lid..string.format("Passed final waypoint=%s [from %s]: comment \"%s\"", tostring(final), tostring(self.passedfinalwp), tostring(comment))) + if final==true and not self.passedfinalwp then + self:PassedFinalWaypoint() + end + -- Set value. self.passedfinalwp=final end From 1e6899c40bfba849d310eeaadc8ff857726b2592 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 25 Aug 2021 17:20:17 +0200 Subject: [PATCH 079/141] OPS --- Moose Development/Moose/Ops/ArmyGroup.lua | 22 +-- Moose Development/Moose/Ops/Brigade.lua | 54 +++--- Moose Development/Moose/Ops/Cohort.lua | 180 +++++++++--------- Moose Development/Moose/Ops/FlightGroup.lua | 42 +--- Moose Development/Moose/Ops/Legion.lua | 43 ++++- Moose Development/Moose/Ops/NavyGroup.lua | 22 +-- Moose Development/Moose/Ops/OpsGroup.lua | 115 ++++++++--- Moose Development/Moose/Ops/Platoon.lua | 30 ++- .../Moose/Wrapper/Controllable.lua | 10 +- 9 files changed, 289 insertions(+), 229 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 3c5a9d193..f4d8194a2 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1187,11 +1187,6 @@ function ARMYGROUP:_InitGroup(Template) -- Get template of group. local template=Template or self:_GetTemplate() - - -- Define category. - self.isAircraft=false - self.isNaval=false - self.isGround=true -- Ground are always AI. self.isAI=true @@ -1243,21 +1238,8 @@ function ARMYGROUP:_InitGroup(Template) self:_AddElementByName(unit:GetName()) end - -- Get first unit. This is used to extract other parameters. - local unit=units[1] --Wrapper.Unit#UNIT - - if unit then - - -- Get Descriptors. - self.descriptors=unit:GetDesc() - - -- Set type name. - self.actype=unit:GetTypeName() - - -- Init done. - self.groupinitialized=true - - end + -- Init done. + self.groupinitialized=true return self end diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index 70d5505cd..77d0b2948 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -16,7 +16,6 @@ -- @type BRIGADE -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity of output. --- @field Ops.General#GENERAL general The genral responsible for this brigade. -- @extends Ops.Legion#LEGION --- Be surprised! @@ -31,8 +30,7 @@ -- @field #BRIGADE BRIGADE = { ClassName = "BRIGADE", - verbose = 3, - genral = nil, + verbose = 0, } @@ -44,7 +42,7 @@ BRIGADE.version="0.0.1" -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: A lot! +-- TODO: Add weapon range. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -89,13 +87,13 @@ function BRIGADE:AddPlatoon(Platoon) -- Add platoon to brigade. table.insert(self.cohorts, Platoon) - -- Add assets to squadron. + -- Add assets to platoon. self:AddAssetToPlatoon(Platoon, Platoon.Ngroups) - -- Set airwing to squadron. + -- Set brigade of platoon. Platoon:SetBrigade(self) - -- Start squadron. + -- Start platoon. if Platoon:IsStopped() then Platoon:Start() end @@ -114,7 +112,7 @@ function BRIGADE:AddAssetToPlatoon(Platoon, Nassets) if Platoon then - -- Get the template group of the squadron. + -- Get the template group of the platoon. local Group=GROUP:FindByName(Platoon.templatename) if Group then @@ -131,7 +129,7 @@ function BRIGADE:AddAssetToPlatoon(Platoon, Nassets) end else - self:E(self.lid.."ERROR: Squadron does not exit!") + self:E(self.lid.."ERROR: Platoon does not exit!") end return self @@ -148,20 +146,20 @@ end --- Get platoon of an asset. -- @param #BRIGADE self --- @param Ops.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset. -- @return Ops.Platoon#PLATOON The platoon object. function BRIGADE:GetPlatoonOfAsset(Asset) local platoon=self:GetPlatoon(Asset.squadname) return platoon end ---- Remove asset from squadron. +--- Remove asset from platoon. -- @param #BRIGADE self --- @param #BRIGADE.SquadronAsset Asset The squad asset. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset. function BRIGADE:RemoveAssetFromPlatoon(Asset) - local squad=self:GetPlatoonOfAsset(Asset) - if squad then - squad:DelAsset(Asset) + local platoon=self:GetPlatoonOfAsset(Asset) + if platoon then + platoon:DelAsset(Asset) end end @@ -188,9 +186,8 @@ function BRIGADE:onafterStatus(From, Event, To) -- Status of parent Warehouse. self:GetParent(self).onafterStatus(self, From, Event, To) + -- FSM state. local fsmstate=self:GetState() - - env.info("FF Brigade status "..fsmstate) -- General info: if self.verbose>=1 then @@ -198,13 +195,14 @@ function BRIGADE:onafterStatus(From, Event, To) -- Count missions not over yet. local Nmissions=self:CountMissionsInQueue() - -- Assets tot + -- Asset count. local Npq, Np, Nq=self:CountAssetsOnMission() - local assets=string.format("%d (OnMission: Total=%d, Active=%d, Queued=%d)", self:CountAssets(), Npq, Np, Nq) + -- Asset string. + local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]", self:CountAssets(), Npq, Np, Nq) -- Output. - local text=string.format("%s: Missions=%d, Squads=%d, Assets=%s", fsmstate, Nmissions, #self.cohorts, assets) + local text=string.format("%s: Missions=%d, Platoons=%d, Assets=%s", fsmstate, Nmissions, #self.cohorts, assets) self:I(self.lid..text) end @@ -226,19 +224,19 @@ function BRIGADE:onafterStatus(From, Event, To) end ------------------- - -- Squadron Info -- + -- Platoon Info -- ------------------- if self.verbose>=3 then local text="Platoons:" - for i,_squadron in pairs(self.cohorts) do - local squadron=_squadron --Ops.Squadron#SQUADRON + for i,_platoon in pairs(self.cohorts) do + local platoon=_platoon --Ops.Platoon#PLATOON - local callsign=squadron.callsignName and UTILS.GetCallsignName(squadron.callsignName) or "N/A" - local modex=squadron.modex and squadron.modex or -1 - local skill=squadron.skill and tostring(squadron.skill) or "N/A" + local callsign=platoon.callsignName and UTILS.GetCallsignName(platoon.callsignName) or "N/A" + local modex=platoon.modex and platoon.modex or -1 + local skill=platoon.skill and tostring(platoon.skill) or "N/A" - -- Squadron text - text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, squadron:CountAssets(true), #squadron.assets, callsign, modex, skill) + -- Platoon text. + text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", platoon.name, platoon:GetState(), platoon.aircrafttype, platoon:CountAssets(true), #platoon.assets, callsign, modex, skill) end self:I(self.lid..text) end diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index fdd4aa811..afc15c1ee 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -783,109 +783,115 @@ end function COHORT:RecruitAssets(Mission, Npayloads) -- Number of payloads available. - Npayloads=Npayloads or self.legion:CountPayloadsInStock(Mission.type, self.aircrafttype, Mission.payloads) + Npayloads=Npayloads or self.legion:CountPayloadsInStock(Mission.type, self.aircrafttype, Mission.payloads) + -- Recruited assets. local assets={} -- Loop over assets. for _,_asset in pairs(self.assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + -- First check that asset is not requested. This could happen if multiple requests are processed simultaniously. + if not asset.requested then - -- Check if asset is currently on a mission (STARTED or QUEUED). - if self.legion:IsAssetOnMission(asset) then - - --- - -- Asset is already on a mission. - --- - - -- Check if this asset is currently on a GCICAP mission (STARTED or EXECUTING). - if self.legion:IsAssetOnMission(asset, AUFTRAG.Type.GCICAP) and Mission.type==AUFTRAG.Type.INTERCEPT then - - -- Check if the payload of this asset is compatible with the mission. - -- Note: we do not check the payload as an asset that is on a GCICAP mission should be able to do an INTERCEPT as well! - self:I(self.lid.."Adding asset on GCICAP mission for an INTERCEPT mission") - table.insert(assets, asset) - - end - else - - --- - -- Asset as NO current mission - --- - - if asset.spawned then - - --- - -- Asset is already SPAWNED (could be uncontrolled on the airfield or inbound after another mission) - --- - - local flightgroup=asset.flightgroup - - -- Firstly, check if it has the right payload. - if self:CheckMissionCapability(Mission.type, asset.payload.capabilities) and flightgroup and flightgroup:IsAlive() then - - -- Assume we are ready and check if any condition tells us we are not. - local combatready=true + -- Check if asset is currently on a mission (STARTED or QUEUED). + if self.legion:IsAssetOnMission(asset) then - if Mission.type==AUFTRAG.Type.INTERCEPT then - combatready=flightgroup:CanAirToAir() - else - local excludeguns=Mission.type==AUFTRAG.Type.BOMBING or Mission.type==AUFTRAG.Type.BOMBRUNWAY or Mission.type==AUFTRAG.Type.BOMBCARPET or Mission.type==AUFTRAG.Type.SEAD or Mission.type==AUFTRAG.Type.ANTISHIP - combatready=flightgroup:CanAirToGround(excludeguns) - end + --- + -- Asset is already on a mission. + --- + + -- Check if this asset is currently on a GCICAP mission (STARTED or EXECUTING). + if self.legion:IsAssetOnMission(asset, AUFTRAG.Type.GCICAP) and Mission.type==AUFTRAG.Type.INTERCEPT then + + -- Check if the payload of this asset is compatible with the mission. + -- Note: we do not check the payload as an asset that is on a GCICAP mission should be able to do an INTERCEPT as well! + self:I(self.lid.."Adding asset on GCICAP mission for an INTERCEPT mission") + table.insert(assets, asset) - -- No more attacks if fuel is already low. Safety first! - if flightgroup:IsFuelLow() then - combatready=false - end - - -- Check if in a state where we really do not want to fight any more. - if flightgroup:IsFlightgroup() then - if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() then - combatready=false - end - else - if flightgroup:IsRearming() or flightgroup:IsRetreating() or flightgroup:IsReturning() then - combatready=false - end - end - -- Applies to all opsgroups. - if flightgroup:IsDead() or flightgroup:IsStopped() then - combatready=false - end - - - --TODO: Check transport for combat readyness! + end - -- This asset is "combatready". - if combatready then - self:I(self.lid.."Adding SPAWNED asset to ANOTHER mission as it is COMBATREADY") - table.insert(assets, asset) - end - - end - else --- - -- Asset is still in STOCK - --- - - -- Check that asset is not already requested for another mission. - if Npayloads>0 and self:IsRepaired(asset) and (not asset.requested) then - - -- Add this asset to the selection. - table.insert(assets, asset) - - -- Reduce number of payloads so we only return the number of assets that could do the job. - Npayloads=Npayloads-1 - - end + -- Asset as NO current mission + --- + + if asset.spawned then - end - end + --- + -- Asset is already SPAWNED (could be uncontrolled on the airfield or inbound after another mission) + --- + + local flightgroup=asset.flightgroup + + -- Firstly, check if it has the right payload. + if self:CheckMissionCapability(Mission.type, asset.payload.capabilities) and flightgroup and flightgroup:IsAlive() then + + -- Assume we are ready and check if any condition tells us we are not. + local combatready=true + + if Mission.type==AUFTRAG.Type.INTERCEPT then + combatready=flightgroup:CanAirToAir() + else + local excludeguns=Mission.type==AUFTRAG.Type.BOMBING or Mission.type==AUFTRAG.Type.BOMBRUNWAY or Mission.type==AUFTRAG.Type.BOMBCARPET or Mission.type==AUFTRAG.Type.SEAD or Mission.type==AUFTRAG.Type.ANTISHIP + combatready=flightgroup:CanAirToGround(excludeguns) + end + + -- No more attacks if fuel is already low. Safety first! + if flightgroup:IsFuelLow() then + combatready=false + end + + -- Check if in a state where we really do not want to fight any more. + if flightgroup:IsFlightgroup() then + if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() then + combatready=false + end + else + if flightgroup:IsRearming() or flightgroup:IsRetreating() or flightgroup:IsReturning() then + combatready=false + end + end + -- Applies to all opsgroups. + if flightgroup:IsDead() or flightgroup:IsStopped() then + combatready=false + end + + + --TODO: Check transport for combat readyness! + + -- This asset is "combatready". + if combatready then + self:I(self.lid.."Adding SPAWNED asset to ANOTHER mission as it is COMBATREADY") + table.insert(assets, asset) + end + + end + + else + + --- + -- Asset is still in STOCK + --- + + -- Check that we have payloads and asset is repaired. + if Npayloads>0 and self:IsRepaired(asset) then + + -- Add this asset to the selection. + table.insert(assets, asset) + + -- Reduce number of payloads so we only return the number of assets that could do the job. + Npayloads=Npayloads-1 + + end + + end + end + + end -- not requested check end -- loop over assets return assets diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index a01333e08..33d508e8e 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2612,7 +2612,11 @@ function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Altitude, Speed) local Coord=self.group:GetCoordinate() -- Set altitude: 1000 ft for helos and 10,000 ft for panes. - Altitude=Altitude or (self.isHelo and 1000 or 10000) + if Altitude then + Altitude=UTILS.FeetToMeters(Altitude) + else + Altitude=self.altitudeCruise + end -- Set speed. Speed=Speed or (self.isHelo and 20 or 250) @@ -2624,7 +2628,7 @@ function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Altitude, Speed) --TODO: set ROE passive. introduce roe event/state/variable. -- Orbit until flaghold=1 (true) but max 5 min if no FC is giving the landing clearance. - local TaskOrbit = self.group:TaskOrbit(Coord, UTILS.FeetToMeters(Altitude), UTILS.KnotsToMps(Speed)) + local TaskOrbit = self.group:TaskOrbit(Coord, Altitude, UTILS.KnotsToMps(Speed)) local TaskStop = self.group:TaskCondition(nil, nil, nil, nil, Duration) local TaskCntr = self.group:TaskControlled(TaskOrbit, TaskStop) local TaskOver = self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting", self) @@ -3022,7 +3026,7 @@ function FLIGHTGROUP._FinishedWaiting(group, flightgroup) flightgroup.Twaiting=nil flightgroup.dTwait=nil - -- Trigger Holding event. + -- Check group done. flightgroup:_CheckGroupDone(0.1) end @@ -3048,11 +3052,6 @@ function FLIGHTGROUP:_InitGroup(Template) -- Get template of group. local template=Template or self:_GetTemplate() - -- Define category. - self.isAircraft=true - self.isNaval=false - self.isGround=false - -- Helo group. self.isHelo=group:IsHelicopter() @@ -3130,32 +3129,9 @@ function FLIGHTGROUP:_InitGroup(Template) self:_AddElementByName(unit:GetName()) end - -- Get first unit. This is used to extract other parameters. - local unit=units[1] --Wrapper.Unit#UNIT - - if unit then - - self.rangemax=unit:GetRange() - - self.descriptors=unit:GetDesc() - - self.actype=unit:GetTypeName() - - self.ceiling=self.descriptors.Hmax - - self.tankertype=select(2, unit:IsTanker()) - self.refueltype=select(2, unit:IsRefuelable()) - - --env.info("DCS Unit BOOM_AND_RECEPTACLE="..tostring(Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)) - --env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE)) - - -- Init done. - self.groupinitialized=true + -- Init done. + self.groupinitialized=true - else - self:E(self.lid.."ERROR: no unit in _InigGroup!") - end - return self end diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 4e59e5151..762800ccf 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -1164,25 +1164,54 @@ end function LEGION:_CreateFlightGroup(asset) -- Create flightgroup. - local flightgroup=nil --Ops.OpsGroup#OPSGROUP + local opsgroup=nil --Ops.OpsGroup#OPSGROUP + if self:IsAirwing() then - flightgroup=FLIGHTGROUP:New(asset.spawngroupname) + + --- + -- FLIGHTGROUP + --- + + opsgroup=FLIGHTGROUP:New(asset.spawngroupname) + + elseif self:IsBrigade() then - flightgroup=ARMYGROUP:New(asset.spawngroupname) + + --- + -- ARMYGROUP + --- + + opsgroup=ARMYGROUP:New(asset.spawngroupname) + + + else self:E(self.lid.."ERROR: not airwing or brigade!") end -- Set legion. - flightgroup:_SetLegion(self) + opsgroup:_SetLegion(self) -- Set cohort. - flightgroup.cohort=self:_GetCohortOfAsset(asset) + opsgroup.cohort=self:_GetCohortOfAsset(asset) -- Set home base. - flightgroup.homebase=self.airbase + opsgroup.homebase=self.airbase + - return flightgroup + -- Set weapon data. + if opsgroup.cohort.weaponData then + local text="Weapon data for group:" + opsgroup.weaponData=opsgroup.weaponData or {} + for bittype,_weapondata in pairs(opsgroup.cohort.weaponData) do + local weapondata=_weapondata --Ops.OpsGroup#OPSGROUP.WeaponData + opsgroup.weaponData[bittype]=UTILS.DeepCopy(weapondata) -- Careful with the units. + text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km", bittype, weapondata.RangeMin/1000, weapondata.RangeMax/1000) + end + self:T3(self.lid..text) + end + + return opsgroup end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index ca2230f8c..e4414fd86 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1142,11 +1142,6 @@ function NAVYGROUP:_InitGroup(Template) -- Get template of group. local template=Template or self:_GetTemplate() - -- Define category. - self.isAircraft=false - self.isNaval=true - self.isGround=false - --TODO: Submarine check --self.isSubmarine=self.group:IsSubmarine() @@ -1202,21 +1197,8 @@ function NAVYGROUP:_InitGroup(Template) self:_AddElementByName(unit:GetName()) end - -- Get first unit. This is used to extract other parameters. - local unit=units[1] --Wrapper.Unit#UNIT - - if unit then - - -- Get Descriptors. - self.descriptors=unit:GetDesc() - - -- Set type name. - self.actype=unit:GetTypeName() - - -- Init done. - self.groupinitialized=true - - end + -- Init done. + self.groupinitialized=true return self end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 1f31accd0..010470b21 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -20,18 +20,16 @@ -- @field Wrapper.Group#GROUP group Group object. -- @field DCS#Controller controller The DCS controller of the group. -- @field DCS#Template template Template table of the group. +-- @field #table elements Table of elements, i.e. units of the group. -- @field #boolean isLateActivated Is the group late activated. -- @field #boolean isUncontrolled Is the group uncontrolled. -- @field #boolean isFlightgroup Is a FLIGHTGROUP. -- @field #boolean isArmygroup Is an ARMYGROUP. -- @field #boolean isNavygroup Is a NAVYGROUP. --- @field #boolean isHelo If true, the is a helicopter group. --- @field #boolean isVTOL If true, the is capable of Vertical TakeOff and Landing (VTOL). --- @field #table elements Table of elements, i.e. units of the group. +-- @field #boolean isHelo If true, this is a helicopter group. +-- @field #boolean isVTOL If true, this is capable of Vertical TakeOff and Landing (VTOL). +-- @field #boolean isSubmarine If true, this is a submarine group. -- @field #boolean isAI If true, group is purely AI. --- @field #boolean isAircraft If true, group is airplane or helicopter. --- @field #boolean isNaval If true, group is ships or submarine. --- @field #boolean isGround If true, group is some ground unit. -- @field #boolean isDestroyed If true, the whole group was destroyed. -- @field #boolean isDead If true, the whole group is dead. -- @field #table waypoints Table of waypoints. @@ -504,6 +502,7 @@ function OPSGROUP:New(group) -- Set some string id for output to DCS.log file. self.lid=string.format("OPSGROUP %s | ", tostring(self.groupname)) + -- Check if group exists. if self.group then if not self:IsExist() then self:E(self.lid.."ERROR: GROUP does not exist! Returning nil") @@ -517,6 +516,34 @@ function OPSGROUP:New(group) -- Set DCS group and controller. self.dcsgroup=self:GetDCSGroup() self.controller=self.dcsgroup:getController() + + local units=self.group:GetUnits() + if units then + local masterunit=units[1] --Wrapper.Unit#UNIT + + -- Get Descriptors. + self.descriptors=masterunit:GetDesc() + + -- Set type name. + self.actype=masterunit:GetTypeName() + + if self:IsFlightgroup() then + + self.rangemax=masterunit:GetRange() + + self.descriptors=masterunit:GetDesc() + + self.ceiling=self.descriptors.Hmax + + self.tankertype=select(2, masterunit:IsTanker()) + self.refueltype=select(2, masterunit:IsRefuelable()) + + --env.info("DCS Unit BOOM_AND_RECEPTACLE="..tostring(Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)) + --env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE)) + + end + + end -- Init set of detected units. self.detectedunits=SET_UNIT:New() @@ -876,7 +903,7 @@ function OPSGROUP:AddWeaponRange(RangeMin, RangeMax, BitType) weapon.RangeMin=RangeMin self.weaponData=self.weaponData or {} - self.weaponData[weapon.BitType]=weapon + self.weaponData[tostring(weapon.BitType)]=weapon return self end @@ -887,12 +914,12 @@ end -- @return #OPSGROUP.WeaponData Weapon range data. function OPSGROUP:GetWeaponData(BitType) - BitType=BitType or ENUMS.WeaponFlag.Auto + BitType=tostring(BitType or ENUMS.WeaponFlag.Auto) if self.weaponData[BitType] then return self.weaponData[BitType] else - return self.weaponData[ENUMS.WeaponFlag.Auto] + return self.weaponData[tostring(ENUMS.WeaponFlag.Auto)] end end @@ -3937,7 +3964,7 @@ function OPSGROUP:RouteToMission(mission, delay) end elseif mission.type==AUFTRAG.Type.ARTY then - + -- Get weapon range. local weapondata=self:GetWeaponData(mission.engageWeaponType) @@ -3959,8 +3986,9 @@ function OPSGROUP:RouteToMission(mission, delay) -- New waypoint coord. waypointcoord=self:GetCoordinate():Translate(d, heading) - - self:T(self.lid..string.format("Out of max range = %.1f km for weapon %d", weapondata.RangeMax/1000, mission.engageWeaponType)) + + -- Debug info. + self:T(self.lid..string.format("Out of max range = %.1f km for weapon %s", weapondata.RangeMax/1000, tostring(mission.engageWeaponType))) elseif dist Date: Wed, 25 Aug 2021 17:46:00 +0200 Subject: [PATCH 080/141] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 010470b21..b63a1106e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -7520,9 +7520,17 @@ function OPSGROUP:_CheckStuck() if holdtime>=10*60 then + -- Debug warning. self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec", speed, ExpectedSpeed, holdtime)) --TODO: Stuck event! + + -- Look for a current mission and cancel it as we do not seem to be able to perform it. + local mission=self:GetMissionCurrent() + if mission then + self:E(self.lid..string.format("WARNING: Cancelling mission %s [%s] due to being stuck", mission:GetName(), mission:GetType())) + self:MissionCancel(mission) + end end From 1b0ad13529a4cb81502b3e25579c5e805e7a21e3 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 26 Aug 2021 21:24:47 +0200 Subject: [PATCH 081/141] OPS Commander --- .../Moose/Functional/Warehouse.lua | 32 +- Moose Development/Moose/Ops/Cohort.lua | 4 +- Moose Development/Moose/Ops/Commander.lua | 288 ++++++++++++++---- Moose Development/Moose/Ops/Legion.lua | 57 +++- Moose Development/Moose/Ops/OpsTransport.lua | 2 + 5 files changed, 296 insertions(+), 87 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 836e053cd..c664f4f4b 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1622,13 +1622,17 @@ WAREHOUSE = { -- @field #boolean spawned If true, asset was spawned into the cruel world. If false, it is still in stock. -- @field #string spawngroupname Name of the spawned group. -- @field #boolean iscargo If true, asset is cargo. If false asset is transport. Nil if in stock. --- @field #number rid The request ID of this asset. -- @field #boolean arrived If true, asset arrived at its destination. +-- -- @field #number damage Damage of asset group in percent. -- @field Ops.AirWing#AIRWING.Payload payload The payload of the asset. --- @field Ops.FlightGroup#FLIGHTGROUP flightgroup The flightgroup object. +-- @field Ops.OpsGroup#OPSGROUP flightgroup The flightgroup object. +-- @field Ops.Cohort#COHORT cohort The cohort this asset belongs to. +-- @field Ops.Legion#LEGION legion The legion this asset belonts to. -- @field #string squadname Name of the squadron this asset belongs to. --- @field #number Treturned Time stamp when asset returned to the airwing. +-- @field #number Treturned Time stamp when asset returned to its legion (airwing, brigade). +-- @field #boolean requested If `true`, asset was requested and cannot be selected by another request. +-- @field #boolean isReserved If `true`, asset was reserved and cannot be selected by another request. --- Item of the warehouse queue table. -- @type WAREHOUSE.Queueitem @@ -3118,14 +3122,16 @@ end -- @param #WAREHOUSE self -- @return DCS#Vec3 The 3D vector of the warehouse. function WAREHOUSE:GetVec3() - return self.warehouse:GetVec3() + local vec3=self.warehouse:GetVec3() + return vec3 end --- Get 2D vector of warehouse static. -- @param #WAREHOUSE self -- @return DCS#Vec2 The 2D vector of the warehouse. function WAREHOUSE:GetVec2() - return self.warehouse:GetVec2() + local vec2=self.warehouse:GetVec2() + return vec2 end @@ -3194,18 +3200,6 @@ function WAREHOUSE:GetAssignment(request) return tostring(request.assignment) end ---[[ ---- Get warehouse unique ID from static warehouse object. This is the ID under which you find the @{#WAREHOUSE} object in the global data base. --- @param #WAREHOUSE self --- @param #string staticname Name of the warehouse static object. --- @return #number Warehouse unique ID. -function WAREHOUSE:GetWarehouseID(staticname) - local warehouse=STATIC:FindByName(staticname, true) - local uid=tonumber(warehouse:GetID()) - return uid -end -]] - --- Find a warehouse in the global warehouse data base. -- @param #WAREHOUSE self -- @param #number uid The unique ID of the warehouse. @@ -3951,6 +3945,8 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu -- Asset is not spawned. asset.spawned=false + asset.requested=false + asset.isReserved=false asset.iscargo=nil asset.arrived=nil @@ -4141,6 +4137,8 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay, asset.skill=skill asset.assignment=assignment asset.spawned=false + asset.requested=false + asset.isReserved=false asset.life0=group:GetLife0() asset.damage=0 asset.spawngroupname=string.format("%s_AID-%d", templategroupname, asset.uid) diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index afc15c1ee..5847acd73 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -372,6 +372,8 @@ end function COHORT:AddAsset(Asset) self:T(self.lid..string.format("Adding asset %s of type %s", Asset.spawngroupname, Asset.unittype)) Asset.squadname=self.name + Asset.legion=self.legion + Asset.cohort=self table.insert(self.assets, Asset) return self end @@ -700,7 +702,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Check if there is a cohort that can execute a given mission. --- We check the mission type, the refuelling system, engagement range +-- We check the mission type, the refuelling system, mission range. -- @param #COHORT self -- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @return #boolean If true, Cohort can do that type of mission. diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 9c5781277..f973f8311 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -28,15 +28,13 @@ -- -- # The COMMANDER Concept -- --- A commander is the head of legions. He will find the best LEGIONs to perform an assigned AUFTRAG (mission). +-- A commander is the head of legions. He/she will find the best LEGIONs to perform an assigned AUFTRAG (mission). -- -- -- @field #COMMANDER COMMANDER = { ClassName = "COMMANDER", - Debug = nil, - lid = nil, - legions = {}, + legions = {}, missionqueue = {}, } @@ -50,6 +48,8 @@ COMMANDER.version="0.1.0" -- TODO: Improve legion selection. Mostly done! -- TODO: Allow multiple Legions for one mission. +-- TODO: Add ops transports. +-- 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. -- NOGO: Maybe it's possible to preselect the assets for the mission. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -247,15 +247,19 @@ function COMMANDER:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() - -- Check mission queue and assign one PLANNED mission. - self:CheckMissionQueue() - -- Status. local text=string.format("Status %s: Legions=%d, Missions=%d", fsmstate, #self.legions, #self.missionqueue) self:I(self.lid..text) - - -- Legion info. + + -- Check mission queue and assign one PLANNED mission. + self:CheckMissionQueue() + + --- + -- LEGIONS + --- + if #self.legions>0 then + local text="Legions:" for _,_legion in pairs(self.legions) do local legion=_legion --Ops.Legion#LEGION @@ -272,8 +276,70 @@ function COMMANDER:onafterStatus(From, Event, To) end end self:I(self.lid..text) + + + local assets={} + + local Ntotal=0 + local Nspawned=0 + local Nrequested=0 + local Nreserved=0 + local Nstock=0 + + local text="===========================================\n" + text=text.."Assets:" + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + for _,_asset in pairs(cohort.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + table.insert(assets, asset) + + text=text..string.format("\n- %s [UID=%d] Legion=%s, Cohort=%s: Spawned=%s, Requested=%s [RID=%s], Reserved=%s", + asset.spawngroupname, asset.uid, legion.alias, cohort.name, tostring(asset.spawned), tostring(asset.requested), tostring(asset.rid), tostring(asset.isReserved)) + + if asset.spawned then + Nspawned=Nspawned+1 + end + + if asset.requested then + Nrequested=Nrequested+1 + end + + if asset.isReserved then + Nreserved=Nreserved+1 + end + + if not (asset.spawned or asset.requested or asset.isReserved) then + Nstock=Nstock+1 + end + + Ntotal=Ntotal+1 + + end + + end + + end + text=text.."\n-------------------------------------------" + text=text..string.format("\nNstock = %d", Nstock) + text=text..string.format("\nNreserved = %d", Nreserved) + text=text..string.format("\nNrequested = %d", Nrequested) + text=text..string.format("\nNspawned = %d", Nspawned) + text=text..string.format("\nNtotal = %d (=%d)", Ntotal, Nstock+Nspawned+Nrequested+Nreserved) + text=text.."\n===========================================" + self:I(self.lid..text) + end + --- + -- MISSIONS + --- + -- Mission queue. if #self.missionqueue>0 then @@ -313,6 +379,9 @@ function COMMANDER:onafterMissionAssign(From, Event, To, Legion, Mission) -- Add mission to legion. Legion:AddMission(Mission) + + -- Directly request the mission as the assets have already been selected. + Legion:MissionRequest(Mission) end @@ -362,24 +431,126 @@ end function COMMANDER:CheckMissionQueue() -- TODO: Sort mission queue. wrt what? Threat level? + -- Currently, we sort wrt to priority. So that should reflect the threat level of the mission target. + -- Number of missions. + local Nmissions=#self.missionqueue + + -- Treat special cases. + if Nmissions==0 then + return nil + end + + -- Sort results table wrt prio and start time. + local function _sort(a, b) + local taskA=a --Ops.Auftrag#AUFTRAG + local taskB=b --Ops.Auftrag#AUFTRAG + return (taskA.prio=mission.nassets then + + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, mission.type, mission.payloads) + asset.score=asset.legion:CalculateAssetMissionScore(asset, mission, true) + end + --- Sort assets wrt to their mission score. Higher is better. + local function optimize(assetA, assetB) + return (assetA.score>assetB.score) + end + table.sort(assets, optimize) + + -- Remove distance parameter. + local text=string.format("Optimized assets for %s mission:", mission.type) + for i,Asset in pairs(assets) do + local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Score text. + text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) + + -- Nillify score. + asset.score=nil + + -- Add assets to mission. + if i<=mission.nassets then + + -- Add asset to mission. + mission:AddAsset(Asset) + + -- Put into table. + legions[asset.legion.alias]=asset.legion + + -- Number of assets requested from this legion. + -- TODO: Check if this is really necessary as we do not go through the selection process. + mission.Nassets=mission.Nassets or {} + if mission.Nassets[asset.legion.alias] then + mission.Nassets[asset.legion.alias]=mission.Nassets[asset.legion.alias]+1 + else + mission.Nassets[asset.legion.alias]=1 + end + + else + + -- Return payload of asset (if any). + if asset.payload then + asset.legion:ReturnPayloadFromAsset(asset) + end + + end + end + self:T2(self.lid..text) + + else + self:T2(self.lid..string.format("Not enough assets available for mission")) + end + + --- + -- Assign Mission to Legions + --- + if legions then for _,_legion in pairs(legions) do local legion=_legion --Ops.Legion#LEGION + + -- Debug message. + self:I(self.lid..string.format("Assigning mission %s [%s] to legion %s", mission:GetName(), mission:GetType(), legion.alias)) -- Add mission to legion. self:MissionAssign(legion, mission) @@ -424,7 +595,7 @@ function COMMANDER:GetLegionsForMission(Mission) end -- Has it assets that can? - if Nassets>0 then + if Nassets>0 and false then -- Get coordinate of the target. local coord=Mission:GetTargetCoordinate() @@ -446,62 +617,15 @@ function COMMANDER:GetLegionsForMission(Mission) end end + + -- Add legion if it can provide at least 1 asset. + if Nassets>0 then + table.insert(legions, legion) + end end - -- Can anyone? - if #legions>0 then - - --- Something like: - -- * Closest legion that can should be first prio. - -- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the legion with more resources should get the job. - local function score(a) - local d=math.round(a.dist/10) - end - - env.info(self.lid.."FF #legions="..#legions) - - -- Sort table wrt distance and number of assets. - -- Distances within 10 NM are equal and the legion with more assets is preferred. - local function sortdist(a,b) - local ad=a.dist - local bd=b.dist - return adb.nassets) - end - table.sort(legions, sortdist) - - - -- Loops over all legions and stop if enough assets are summed up. - local selection={} ; local N=0 - for _,leg in ipairs(legions) do - local legion=leg.airwing --Ops.Legion#LEGION - - Mission.Nassets=Mission.Nassets or {} - Mission.Nassets[legion.alias]=leg.nassets - - table.insert(selection, legion) - - N=N+leg.nassets - - if N>=Mission.nassets then - self:I(self.lid..string.format("Found enough assets!")) - break - end - end - - if N>=Mission.nassets then - self:I(self.lid..string.format("Found %d legions that can do mission %s (%s) requiring %d assets", #selection, Mission:GetName(), Mission:GetType(), Mission.nassets)) - return selection - else - self:T(self.lid..string.format("Not enough LEGIONs found that could do the job :/ Number of assets avail %d < %d required for the mission", N, Mission.nassets)) - return nil - end - - else - self:T(self.lid..string.format("No LEGION found that could do the job :/")) - end - - return nil + return legions end --- Count assets of all assigned legions. @@ -521,6 +645,42 @@ function COMMANDER:CountAssets(InStock, MissionTypes, Attributes) return N 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. +-- @param #table Legions (Optional) Table of legions. Default is all legions. +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Amount of asset groups. +function COMMANDER:GetAssets(InStock, Legions, MissionTypes, Attributes) + + -- Selected assets. + local assets={} + + for _,_legion in pairs(Legions or self.legions) do + local legion=_legion --Ops.Legion#LEGION + + --TODO Check if legion is running and maybe if runway is operational if air assets are requested. + + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + for _,_asset in pairs(cohort.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- TODO: Check if repaired. + -- TODO: currently we take only unspawned assets. + if not (asset.spawned or asset.isReserved or asset.requested) then + table.insert(assets, asset) + end + + end + end + end + + return assets +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 762800ccf..1c8cb5802 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -118,6 +118,12 @@ function LEGION:New(WarehouseName, LegionName) -- @param #LEGION self -- @param Ops.Auftrag#AUFTRAG Mission The mission. + --- Triggers the FSM event "MissionCancel" after a delay. + -- @function [parent=#LEGION] __MissionCancel + -- @param #LEGION self + -- @param #number delay Delay in seconds. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + --- On after "MissionCancel" event. -- @function [parent=#LEGION] OnAfterMissionCancel -- @param #LEGION self @@ -446,12 +452,14 @@ function LEGION:_GetNextTransport() local function getAssets(n) local assets={} - -- Loop over assets. + -- Loop over cohorts. for _,_cohort in pairs(self.cohorts) do local cohort=_cohort --Ops.Cohort#COHORT + -- Check if chort can do a transport. if cohort:CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}, cohort.missiontypes) then + -- Loop over cohort assets. for _,_asset in pairs(cohort.assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem @@ -533,6 +541,23 @@ function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) score=score+25 end end + + -- Get coordinate of the target. + local coord=Mission:GetTargetCoordinate() + local dist=0 + if coord then + + -- Distance from legion to target. + local distance=UTILS.MetersToNM(coord:Get2DDistance(self:GetCoordinate())) + + -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 + dist=UTILS.Round(distance/10, 0) + + end + + -- Reduce score for legions that are futher away. + score=score-dist + -- TODO: This could be vastly improved. Need to gather ideas during testing. -- Calculate ETA? Assets on orbit missions should arrive faster even if they are further away. @@ -669,6 +694,7 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) -- Set asset to requested! Important so that new requests do not use this asset! asset.requested=true + asset.isReserved=false if Mission.missionTask then asset.missionTask=Mission.missionTask @@ -728,6 +754,7 @@ function LEGION:onafterTransportRequest(From, Event, To, OpsTransport) -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. OpsTransport.requestID=OpsTransport.requestID or {} OpsTransport.requestID[self.alias]=self.queueid + end end @@ -1174,7 +1201,6 @@ function LEGION:_CreateFlightGroup(asset) opsgroup=FLIGHTGROUP:New(asset.spawngroupname) - elseif self:IsBrigade() then --- @@ -1183,8 +1209,6 @@ function LEGION:_CreateFlightGroup(asset) opsgroup=ARMYGROUP:New(asset.spawngroupname) - - else self:E(self.lid.."ERROR: not airwing or brigade!") end @@ -1198,6 +1222,8 @@ function LEGION:_CreateFlightGroup(asset) -- Set home base. opsgroup.homebase=self.airbase + -- Set home zone. + opsgroup.homezone=self.spawnzone -- Set weapon data. if opsgroup.cohort.weaponData then @@ -1594,10 +1620,11 @@ function LEGION:CanMission(Mission) end end + -- Loop over cohorts and recruit assets. for cohortname,_cohort in pairs(cohorts) do local cohort=_cohort --Ops.Cohort#COHORT - -- Check if this squadron can. + -- Check if this cohort can. local can=cohort:CanMission(Mission) if can then @@ -1774,6 +1801,26 @@ function LEGION:GetMissionFromRequest(Request) return self:GetMissionFromRequestID(Request.uid) end +--- Fetch a payload from the airwing resources for a given unit and mission type. +-- The payload with the highest priority is preferred. +-- @param #LEGION self +-- @param #string UnitType The type of the unit. +-- @param #string MissionType The mission type. +-- @param #table Payloads Specific payloads only to be considered. +-- @return Ops.Airwing#AIRWING.Payload Payload table or *nil*. +function LEGION:FetchPayloadFromStock(UnitType, MissionType, Payloads) + -- Polymorphic. Will return something when called by airwing. + return nil +end + +--- Return payload from asset back to stock. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The squadron asset. +function LEGION:ReturnPayloadFromAsset(asset) + -- Polymorphic. + return nil +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index d82192cae..0dde8433c 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -52,6 +52,8 @@ -- @field #number Ndelivered Total number of cargo groups delivered. -- @field #table pathsTransport Transport paths of `#OPSGROUP.Path`. -- @field #table pathsPickup Pickup paths of `#OPSGROUP.Path`. +-- @field Ops.Auftrag#AUFTRAG mission The mission attached to this transport. +-- @field #table assets Warehouse assets assigned for this transport. -- @extends Core.Fsm#FSM --- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill From a8a8dcff3f49f0d96f018f95618405737d237db8 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 26 Aug 2021 23:24:11 +0200 Subject: [PATCH 082/141] OPS Chief - Removed mission queue. Now done via COMMANDER - MissionCancel is buggy --- Moose Development/Moose/Ops/Auftrag.lua | 42 ++-- Moose Development/Moose/Ops/Chief.lua | 217 +++++++------------- Moose Development/Moose/Ops/Commander.lua | 145 +++++++------ Moose Development/Moose/Ops/FlightGroup.lua | 11 +- Moose Development/Moose/Ops/Legion.lua | 4 +- 5 files changed, 193 insertions(+), 226 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index a180d4d9d..68552b007 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -110,7 +110,7 @@ -- @field #number missionAltitude Mission altitude in meters. -- @field #number missionSpeed Mission speed in km/h. -- @field #number missionFraction Mission coordiante fraction. Default is 0.5. --- @field #number missionRange Mission range in meters. Used in AIRWING class. +-- @field #number missionRange Mission range in meters. Used by LEGION classes (AIRWING, BRIGADE, ...). -- @field Core.Point#COORDINATE missionWaypointCoord Mission waypoint coordinate. -- @field Core.Point#COORDINATE missionEgressCoord Mission egress waypoint coordinate. -- @@ -463,6 +463,7 @@ AUFTRAG.version="0.7.1" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Replace engageRange by missionRange. Here and in other classes. CTRL+H is your friend! -- TODO: Missions can be assigned to multiple legions. -- TODO: Mission success options damaged, destroyed. -- TODO: F10 marker to create new missions. @@ -1552,7 +1553,7 @@ function AUFTRAG:SetPriority(Prio, Urgent, Importance) return self end ---- Set how many times the mission is repeated. Only valid if the mission is handled by an AIRWING or higher level. +--- Set how many times the mission is repeated. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, ...) or higher level. -- @param #AUFTRAG self -- @param #number Nrepeat Number of repeats. Default 0. -- @return #AUFTRAG self @@ -1561,7 +1562,7 @@ function AUFTRAG:SetRepeat(Nrepeat) return self end ---- Set how many times the mission is repeated if it fails. Only valid if the mission is handled by an AIRWING or higher level. +--- Set how many times the mission is repeated if it fails. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, ...) or higher level. -- @param #AUFTRAG self -- @param #number Nrepeat Number of repeats. Default 0. -- @return #AUFTRAG self @@ -1570,7 +1571,7 @@ function AUFTRAG:SetRepeatOnFailure(Nrepeat) return self end ---- Set how many times the mission is repeated if it was successful. Only valid if the mission is handled by an AIRWING or higher level. +--- Set how many times the mission is repeated if it was successful. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, ...) or higher level. -- @param #AUFTRAG self -- @param #number Nrepeat Number of repeats. Default 0. -- @return #AUFTRAG self @@ -1579,7 +1580,7 @@ function AUFTRAG:SetRepeatOnSuccess(Nrepeat) return self end ---- Define how many assets are required to do the job. Only valid if the mission is handled by an AIRWING, BRIGADE etc or higher level. +--- Define how many assets are required to do the job. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, ...) or higher level. -- @param #AUFTRAG self -- @param #number Nassets Number of asset groups. Default 1. -- @return #AUFTRAG self @@ -2977,25 +2978,27 @@ function AUFTRAG:onafterCancel(From, Event, To) if self.chief then + -- Debug info. self:T(self.lid..string.format("CHIEF will cancel the mission. Will wait for mission DONE before evaluation!")) + -- CHIEF will cancel the mission. self.chief:MissionCancel(self) - - end - if self.commander then + elseif self.commander then + -- Debug info. self:T(self.lid..string.format("COMMANDER will cancel the mission. Will wait for mission DONE before evaluation!")) + -- COMMANDER will cancel the mission. self.commander:MissionCancel(self) - - end - if #self.legions>0 then + elseif self.legions then - for _,_legion in pairs(self.legions) do + -- Loop over all LEGIONs. + for _,_legion in pairs(self.legions or {}) do local legion=_legion --Ops.Legion#LEGION + -- Debug info. self:T(self.lid..string.format("LEGION %s will cancel the mission. Will wait for mission DONE before evaluation!", legion.alias)) -- Legion will cancel all flight missions and remove queued request from warehouse queue. @@ -3003,14 +3006,17 @@ function AUFTRAG:onafterCancel(From, Event, To) end - end + else + -- Debug info. + self:T(self.lid..string.format("No legion, commander or chief. Attached flights will cancel the mission on their own. Will wait for mission DONE before evaluation!")) - self:T(self.lid..string.format("No legion, commander or chief. Attached flights will cancel the mission on their own. Will wait for mission DONE before evaluation!")) - - for _,_groupdata in pairs(self.groupdata or {}) do - local groupdata=_groupdata --#AUFTRAG.GroupData - groupdata.opsgroup:MissionCancel(self) + -- Loop over all groups. + for _,_groupdata in pairs(self.groupdata or {}) do + local groupdata=_groupdata --#AUFTRAG.GroupData + groupdata.opsgroup:MissionCancel(self) + end + end -- Special mission states. diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index affe9112b..93211d8f1 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -16,7 +16,6 @@ -- @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 #table missionqueue Mission queue. -- @field #table targetqueue Target queue. -- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory. -- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected. @@ -42,7 +41,6 @@ CHIEF = { verbose = 0, lid = nil, targetqueue = {}, - missionqueue = {}, borderzoneset = nil, yellowzoneset = nil, engagezoneset = nil, @@ -80,6 +78,7 @@ 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: Get list/overview of enemy assets etc. @@ -112,6 +111,9 @@ function CHIEF:New(AgentSet, Coalition) self:SetThreatLevelRange() + -- Create a new COMMANDER. + self.commander=COMMANDER:New() + self.Defcon=CHIEF.DEFCON.GREEN -- Add FSM transitions. @@ -258,20 +260,14 @@ function CHIEF:SetDefcon(Defcon) end ---- Set the wing commander for the airforce. +--- Get the commander. -- @param #CHIEF self --- @param Ops.WingCommander#WINGCOMMANDER WingCommander The WINGCOMMANDER object. --- @return #CHIEF self -function CHIEF:SetWingCommander(WingCommander) - - self.commander=WingCommander - - self.commander.chief=self - - return self +-- @return Ops.Commander#COMMANDER The commander. +function CHIEF:GetCommander() + return self.commander end ---- Add mission to mission queue. +--- Add mission to mission queue of the COMMANDER. -- @param #CHIEF self -- @param Ops.Auftrag#AUFTRAG Mission Mission to be added. -- @return #CHIEF self @@ -279,10 +275,8 @@ function CHIEF:AddMission(Mission) Mission.chief=self - Mission.statusChief=AUFTRAG.Status.QUEUED - - table.insert(self.missionqueue, Mission) - + self.commander:AddMission(Mission) + return self end @@ -292,17 +286,9 @@ end -- @return #CHIEF self function CHIEF:RemoveMission(Mission) - for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - if mission.auftragsnummer==Mission.auftragsnummer then - self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status)) - Mission.chief=nil - table.remove(self.missionqueue, i) - break - end - - end + Mission.chief=nil + + self.commander:RemoveMission(Mission) return self end @@ -412,7 +398,10 @@ function CHIEF:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() - + --- + -- CONTACTS: Mission Cleanup + --- + -- Clean up missions where the contact was lost. for _,_contact in pairs(self.ContactsLost) do local contact=_contact --Ops.Intelligence#INTEL.Contact @@ -426,10 +415,16 @@ function CHIEF:onafterStatus(From, Event, To) -- Cancel this mission. contact.mission:Cancel() + + -- TODO: contact.target end end + + --- + -- CONTACTS: Create new TARGETS + --- -- Create TARGETs for all new contacts. local Nred=0 ; local Nyellow=0 ; local Nengage=0 @@ -485,57 +480,28 @@ function CHIEF:onafterStatus(From, Event, To) -- Check target queue and assign missions to new targets. self:CheckTargetQueue() - - --- - -- Check Mission Queue - --- - - -- Check mission queue and assign one PLANNED mission. - self:CheckMissionQueue() - - - --- -- Info General --- - local Nassets=self.commander:CountAssets() - local Ncontacts=#self.contacts - local Nmissions=#self.missionqueue - local Ntargets=#self.targetqueue - - -- Info message - local text=string.format("Defcon=%s Assets=%d, Contacts: Total=%d Yellow=%d Red=%d, Targets=%d, Missions=%d", self.Defcon, Nassets, Ncontacts, Nyellow, Nred, Ntargets, Nmissions) - self:I(self.lid..text) - - --- - -- Info Assets - --- - - local text="Assets:" - 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) - end + if self.verbose>=1 then + local Nassets=self.commander:CountAssets() + local Ncontacts=#self.contacts + local Nmissions=#self.commander.missionqueue + local Ntargets=#self.targetqueue + + -- Info message + local text=string.format("Defcon=%s Assets=%d, Contacts: Total=%d Yellow=%d Red=%d, Targets=%d, Missions=%d", self.Defcon, Nassets, Ncontacts, Nyellow, Nred, Ntargets, Nmissions) + self:I(self.lid..text) + end - self:I(self.lid..text) - - local text="Assets:" - 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) - end - end - self:I(self.lid..text) --- -- Info Contacts --- -- Info about contacts. - if #self.Contacts>0 then + if self.verbose>=2 and #self.Contacts>0 then local text="Contacts:" for i,_contact in pairs(self.Contacts) do local contact=_contact --Ops.Intelligence#INTEL.Contact @@ -552,7 +518,7 @@ function CHIEF:onafterStatus(From, Event, To) -- Info Targets --- - if #self.targetqueue>0 then + if self.verbose>=3 and #self.targetqueue>0 then local text="Targets:" for i,_target in pairs(self.targetqueue) do local target=_target --Ops.Target#TARGET @@ -569,7 +535,7 @@ function CHIEF:onafterStatus(From, Event, To) --- -- Mission queue. - if #self.missionqueue>0 then + if self.verbose>=4 and #self.commander.missionqueue>0 then local text="Mission queue:" for i,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG @@ -581,6 +547,30 @@ function CHIEF:onafterStatus(From, Event, To) self:I(self.lid..text) end + --- + -- Info Assets + --- + + if self.verbose>=5 then + local text="Assets:" + 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) + end + end + self:I(self.lid..text) + + local text="Assets:" + 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) + end + end + self:I(self.lid..text) + end + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -615,21 +605,16 @@ 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)) - if Mission.status==AUFTRAG.Status.PLANNED then + if Mission:IsPlanned() then - -- Mission is still in planning stage. Should not have an airbase assigned ==> Just remove it form the queue. + -- Mission is still in planning stage. Should not have any LEGIONS assigned ==> Just remove it form the COMMANDER queue. self:RemoveMission(Mission) - -- Remove Mission from WC queue. - if Mission.wingcommander then - Mission.wingcommander:RemoveMission(Mission) - end - else - -- Wingcommander will cancel mission. - if Mission.wingcommander then - Mission.wingcommander:MissionCancel(Mission) + -- COMMANDER will cancel mission. + if Mission.commander then + Mission.commander:MissionCancel(Mission) end end @@ -658,7 +643,7 @@ function CHIEF:onbeforeDefcon(From, Event, To, Defcon) -- Defcon did not change. if Defcon==self.Defcon then - self:I(self.lid..string.format("Defcon %s unchanged. No processing transition.", tostring(Defcon))) + self:I(self.lid..string.format("Defcon %s unchanged. Not processing transition!", tostring(Defcon))) return false end @@ -735,8 +720,10 @@ function CHIEF:CheckTargetQueue() -- Valid target? if valid then + + --TODO: Create a good mission, which can be passed on to the COMMANDER. - -- Create mission + -- Create mission. local mission=AUFTRAG:NewTargetAir(target) if mission then @@ -748,7 +735,7 @@ function CHIEF:CheckTargetQueue() mission.prio=target.prio mission.importance=target.importance - -- Add mission to queue. + -- Add mission to COMMANDER queue. self:AddMission(mission) end @@ -763,62 +750,6 @@ end -- Resources ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Check mission queue and assign ONE planned mission. --- @param #CHIEF self -function CHIEF:CheckMissionQueue() - - -- TODO: Sort mission queue. wrt what? Threat level? - - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - -- We look for PLANNED missions. - if mission:IsPlanned() then - - --- - -- PLANNNED Mission - --- - - -- Check if there is an airwing that can do the mission. - local legions=self.commander:GetLegionsForMission(mission) - - if airwing then - - -- Add mission to airwing. - self:AssignMissionAirforce(mission) - - return - - else - self:T(self.lid.."NO airwing") - end - - else - - --- - -- Missions NOT in PLANNED state - --- - - end - - end - -end - ---- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets. --- @param #CHIEF self --- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @return #table The best LEGIONs for this mission or `nil`. -function CHIEF:GetAirwingForMission(Mission) - - if self.commander then - local legions=self.commander:GetLegionsForMission(Mission) - return legions - end - - return nil -end - --- Check if group is inside our border. -- @param #CHIEF self -- @param Wrapper.Group#GROUP group The group. @@ -878,11 +809,17 @@ function CHIEF:CheckTargetInZones(target, zoneset) return false end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Resources +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Check resources. -- @param #CHIEF self -- @return #table function CHIEF:CheckResources() + -- TODO: look at lower classes to do this! it's all there... + local capabilities={} for _,MissionType in pairs(AUFTRAG.Type) do diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index f973f8311..67086fa42 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -34,6 +34,7 @@ -- @field #COMMANDER COMMANDER = { ClassName = "COMMANDER", + verbose = 0, legions = {}, missionqueue = {}, } @@ -76,8 +77,8 @@ function COMMANDER:New() self:AddTransition("*", "Status", "*") -- Status report. self:AddTransition("*", "Stop", "Stopped") -- Stop COMMANDER. - self:AddTransition("*", "MissionAssign", "*") -- Mission was assigned to a LEGION. - self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + self:AddTransition("*", "MissionAssign", "*") -- Mission is assigned to a or multiple LEGIONs. + self:AddTransition("*", "MissionCancel", "*") -- COMMANDER cancels a mission. ------------------------ --- Pseudo Functions --- @@ -92,6 +93,7 @@ function COMMANDER:New() -- @param #COMMANDER self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Stop". Stops the COMMANDER. -- @param #COMMANDER self @@ -100,6 +102,7 @@ function COMMANDER:New() -- @param #COMMANDER self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Status". -- @function [parent=#COMMANDER] Status -- @param #COMMANDER self @@ -116,6 +119,13 @@ function COMMANDER:New() -- @param Ops.Legion#LEGION Legion The Legion. -- @param Ops.Auftrag#AUFTRAG Mission The mission. + --- Triggers the FSM event "MissionAssign" after a delay. + -- @function [parent=#COMMANDER] __MissionAssign + -- @param #COMMANDER self + -- @param #number delay Delay in seconds. + -- @param Ops.Legion#LEGION Legion The Legion. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + --- On after "MissionAssign" event. -- @function [parent=#COMMANDER] OnAfterMissionAssign -- @param #COMMANDER self @@ -131,6 +141,12 @@ function COMMANDER:New() -- @param #COMMANDER self -- @param Ops.Auftrag#AUFTRAG Mission The mission. + --- Triggers the FSM event "MissionCancel" after a delay. + -- @function [parent=#COMMANDER] __MissionCancel + -- @param #COMMANDER self + -- @param #number delay Delay in seconds. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + --- On after "MissionCancel" event. -- @function [parent=#COMMANDER] OnAfterMissionCancel -- @param #COMMANDER self @@ -146,6 +162,15 @@ end -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Set verbosity level. +-- @param #COMMANDER self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #COMMANDER self +function COMMANDER:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + --- Add an AIRWING to the commander. -- @param #COMMANDER self -- @param Ops.AirWing#AIRWING Airwing The airwing to add. @@ -248,8 +273,10 @@ function COMMANDER:onafterStatus(From, Event, To) local fsmstate=self:GetState() -- Status. - local text=string.format("Status %s: Legions=%d, Missions=%d", fsmstate, #self.legions, #self.missionqueue) - self:I(self.lid..text) + if self.verbose>=1 then + local text=string.format("Status %s: Legions=%d, Missions=%d", fsmstate, #self.legions, #self.missionqueue) + self:I(self.lid..text) + end -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() @@ -258,7 +285,7 @@ function COMMANDER:onafterStatus(From, Event, To) -- LEGIONS --- - if #self.legions>0 then + if self.verbose>=2 and #self.legions>0 then local text="Legions:" for _,_legion in pairs(self.legions) do @@ -278,61 +305,59 @@ function COMMANDER:onafterStatus(From, Event, To) self:I(self.lid..text) - local assets={} + if self.verbose>=3 then - local Ntotal=0 - local Nspawned=0 - local Nrequested=0 - local Nreserved=0 - local Nstock=0 - - local text="===========================================\n" - text=text.."Assets:" - for _,_legion in pairs(self.legions) do - local legion=_legion --Ops.Legion#LEGION - - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - - for _,_asset in pairs(cohort.assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + -- Count numbers + local Ntotal=0 + local Nspawned=0 + local Nrequested=0 + local Nreserved=0 + local Nstock=0 + + local text="\n===========================================\n" + text=text.."Assets:" + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT - table.insert(assets, asset) - - text=text..string.format("\n- %s [UID=%d] Legion=%s, Cohort=%s: Spawned=%s, Requested=%s [RID=%s], Reserved=%s", - asset.spawngroupname, asset.uid, legion.alias, cohort.name, tostring(asset.spawned), tostring(asset.requested), tostring(asset.rid), tostring(asset.isReserved)) - - if asset.spawned then - Nspawned=Nspawned+1 + for _,_asset in pairs(cohort.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Text. + text=text..string.format("\n- %s [UID=%d] Legion=%s, Cohort=%s: Spawned=%s, Requested=%s [RID=%s], Reserved=%s", + asset.spawngroupname, asset.uid, legion.alias, cohort.name, tostring(asset.spawned), tostring(asset.requested), tostring(asset.rid), tostring(asset.isReserved)) + + if asset.spawned then + Nspawned=Nspawned+1 + end + if asset.requested then + Nrequested=Nrequested+1 + end + if asset.isReserved then + Nreserved=Nreserved+1 + end + if not (asset.spawned or asset.requested or asset.isReserved) then + Nstock=Nstock+1 + end + + Ntotal=Ntotal+1 + end - if asset.requested then - Nrequested=Nrequested+1 - end - - if asset.isReserved then - Nreserved=Nreserved+1 - end - - if not (asset.spawned or asset.requested or asset.isReserved) then - Nstock=Nstock+1 - end - - Ntotal=Ntotal+1 - end - + end - + text=text.."\n-------------------------------------------" + text=text..string.format("\nNstock = %d", Nstock) + text=text..string.format("\nNreserved = %d", Nreserved) + text=text..string.format("\nNrequested = %d", Nrequested) + text=text..string.format("\nNspawned = %d", Nspawned) + text=text..string.format("\nNtotal = %d (=%d)", Ntotal, Nstock+Nspawned+Nrequested+Nreserved) + text=text.."\n===========================================" + self:I(self.lid..text) end - text=text.."\n-------------------------------------------" - text=text..string.format("\nNstock = %d", Nstock) - text=text..string.format("\nNreserved = %d", Nreserved) - text=text..string.format("\nNrequested = %d", Nrequested) - text=text..string.format("\nNspawned = %d", Nspawned) - text=text..string.format("\nNtotal = %d (=%d)", Ntotal, Nstock+Nspawned+Nrequested+Nreserved) - text=text.."\n===========================================" - self:I(self.lid..text) end @@ -341,19 +366,15 @@ function COMMANDER:onafterStatus(From, Event, To) --- -- Mission queue. - if #self.missionqueue>0 then - + if self.verbose>=2 and #self.missionqueue>0 then local text="Mission queue:" for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - local target=mission:GetTargetName() or "unknown" - + local mission=_mission --Ops.Auftrag#AUFTRAG + local target=mission:GetTargetName() or "unknown" text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target) end - self:I(self.lid..text) - - end + self:I(self.lid..text) + end self:__Status(-30) end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 33d508e8e..d039dcc08 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -843,8 +843,6 @@ function FLIGHTGROUP:Status() -- FSM state. local fsmstate=self:GetState() - - env.info(self.lid.."FF status="..fsmstate) -- Update position. self:_UpdatePosition() @@ -899,10 +897,13 @@ function FLIGHTGROUP:Status() local dest=self.destbase and self.destbase:GetName() or "unknown" local fc=self.flightcontrol and self.flightcontrol.airbasename or "N/A" local curr=self.currbase and self.currbase:GetName() or "N/A" + local nelem=self:CountElements() + local Nelem=#self.elements + local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "OFF" - local text=string.format("Status %s [%d/%d]: Tasks=%d, Missions=%s, Waypoint=%d/%d [%s], Detected=%d, Home=%s, Destination=%s, Current=%s, FC=%s", - fsmstate, #self.elements, #self.elements, nTaskTot, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, tostring(self.passedfinalwp), - self.detectedunits:Count(), home, dest, curr, fc) + local text=string.format("Status %s [%d/%d]: Tasks=%d, Missions=%s, Waypoint=%d/%d [%s], Detected=%s, Home=%s, Destination=%s, Current=%s, FC=%s", + fsmstate, nelem, Nelem, nTaskTot, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, tostring(self.passedfinalwp), + ndetected, home, dest, curr, fc) self:I(self.lid..text) end diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 1c8cb5802..f045192bc 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -785,10 +785,12 @@ function LEGION:onafterMissionCancel(From, Event, To, Mission) opsgroup:MissionCancel(Mission) end - -- TODO: remove asset from mission + -- Remove asset from mission. + Mission:DelAsset(asset) -- Not requested any more (if it was). asset.requested=nil + asset.isReserved=nil end end From f0167b3e88850581aeb0feadffaf4aac160dc904 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 27 Aug 2021 11:31:12 +0200 Subject: [PATCH 083/141] OPS - FSM pseudo function cleanup - Fixed bug when mission is cancelled --- Moose Development/Moose/Core/Fsm.lua | 28 ++-- Moose Development/Moose/Ops/AirWing.lua | 68 +++++---- Moose Development/Moose/Ops/Auftrag.lua | 32 ++-- Moose Development/Moose/Ops/Brigade.lua | 61 ++++++-- Moose Development/Moose/Ops/Cohort.lua | 2 + Moose Development/Moose/Ops/Intelligence.lua | 5 +- Moose Development/Moose/Ops/Legion.lua | 148 ++++++++++++++----- Moose Development/Moose/Ops/OpsTransport.lua | 2 + Moose Development/Moose/Ops/Platoon.lua | 4 +- Moose Development/Moose/Ops/Squadron.lua | 5 +- 10 files changed, 244 insertions(+), 111 deletions(-) diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 1b7af9253..3638153ef 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -410,7 +410,7 @@ do -- FSM Transition.To = To -- Debug message. - self:T3( Transition ) + --self:T3( Transition ) self._Transitions[Transition] = Transition self:_eventmap( self.Events, Transition ) @@ -432,7 +432,7 @@ do -- FSM -- @param #table ReturnEvents A table indicating for which returned events of the SubFSM which Event must be triggered in the FSM. -- @return Core.Fsm#FSM_PROCESS The SubFSM. function FSM:AddProcess( From, Event, Process, ReturnEvents ) - self:T3( { From, Event } ) + --self:T3( { From, Event } ) local Sub = {} Sub.From = From @@ -533,7 +533,7 @@ do -- FSM Process._Scores[State].ScoreText = ScoreText Process._Scores[State].Score = Score - self:T3( Process._Scores ) + --self:T3( Process._Scores ) return Process end @@ -576,7 +576,7 @@ do -- FSM self[__Event] = self[__Event] or self:_delayed_transition(Event) -- Debug message. - self:T3( "Added methods: " .. Event .. ", " .. __Event ) + --self:T3( "Added methods: " .. Event .. ", " .. __Event ) Events[Event] = self.Events[Event] or { map = {} } self:_add_to_map( Events[Event].map, EventStructure ) @@ -791,7 +791,7 @@ do -- FSM return function( self, DelaySeconds, ... ) -- Debug. - self:T2( "Delayed Event: " .. EventName ) + self:T3( "Delayed Event: " .. EventName ) local CallID = 0 if DelaySeconds ~= nil then @@ -809,23 +809,23 @@ do -- FSM self._EventSchedules[EventName] = CallID -- Debug output. - self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID))) + self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID))) else - self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds)) + self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds)) -- reschedule end else CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true ) - self:T2(string.format("Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID))) + self:T2(string.format("Event %s delayed by %.3f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID))) end else error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." ) end -- Debug. - self:T3( { CallID = CallID } ) + --self:T3( { CallID = CallID } ) end end @@ -846,7 +846,7 @@ do -- FSM function FSM:_gosub( ParentFrom, ParentEvent ) local fsmtable = {} if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then - self:T3( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } ) + --self:T3( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } ) return self.subs[ParentFrom][ParentEvent] else return {} @@ -893,7 +893,7 @@ do -- FSM end end - self:T3( { Map, Event } ) + --self:T3( { Map, Event } ) end --- Get current state. @@ -1150,7 +1150,7 @@ do -- FSM_PROCESS -- @param #FSM_PROCESS self -- @return #FSM_PROCESS function FSM_PROCESS:Copy( Controllable, Task ) - self:T3( { self:GetClassNameAndID() } ) + --self:T3( { self:GetClassNameAndID() } ) local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS @@ -1176,13 +1176,13 @@ do -- FSM_PROCESS -- Copy End States for EndStateID, EndState in pairs( self:GetEndStates() ) do - self:T3( EndState ) + --self:T3( EndState ) NewFsm:AddEndState( EndState ) end -- Copy the score tables for ScoreID, Score in pairs( self:GetScores() ) do - self:T3( Score ) + --self:T3( Score ) NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score ) end diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 0efa1fdea..a7f103147 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -94,7 +94,7 @@ -- -- Once you created an AUFTRAG you can add it to the AIRWING with the :AddMission(mission) function. -- --- This mission will be put into the AIRWING queue. Once the mission start time is reached and all resources (airframes and pylons) are available, the mission is started. +-- This mission will be put into the AIRWING queue. Once the mission start time is reached and all resources (airframes and payloads) are available, the mission is started. -- If the mission stop time is over (and the mission is not finished), it will be cancelled and removed from the queue. This applies also to mission that were not even -- started. -- @@ -116,7 +116,6 @@ AIRWING = { pointsCAP = {}, pointsTANKER = {}, pointsAWACS = {}, - wingcommander = nil, markpoints = false, } @@ -144,7 +143,7 @@ AIRWING = { --- AIRWING class version. -- @field #string version -AIRWING.version="0.8.0" +AIRWING.version="0.9.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -183,20 +182,19 @@ function AIRWING:New(warehousename, airwingname) -- Set some string id for output to DCS.log file. self.lid=string.format("AIRWING %s | ", self.alias) - - -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("*", "SquadronAssetReturned", "*") -- Flight was spawned with a mission. - + -- Defaults: - --self:SetVerbosity(0) self.nflightsCAP=0 self.nflightsAWACS=0 self.nflightsTANKERboom=0 self.nflightsTANKERprobe=0 self.nflightsRecoveryTanker=0 self.nflightsRescueHelo=0 - self.markpoints=false + self.markpoints=false + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "FlightOnMission", "*") -- A FLIGHTGROUP was send on a Mission (AUFTRAG). ------------------------ --- Pseudo Functions --- @@ -211,6 +209,7 @@ function AIRWING:New(warehousename, airwingname) -- @param #AIRWING self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Stop". Stops the AIRWING and all its event handlers. -- @param #AIRWING self @@ -219,23 +218,28 @@ function AIRWING:New(warehousename, airwingname) -- @param #AIRWING self -- @param #number delay Delay in seconds. - --- On after "FlightOnMission" event. Triggered when an asset group starts a mission. - -- @function [parent=#AIRWING] OnAfterFlightOnMission - -- @param #AIRWING self - -- @param #string From The From state - -- @param #string Event The Event called - -- @param #string To The To state - -- @param Ops.FlightGroup#FLIGHTGROUP Flightgroup The Flightgroup on mission - -- @param Ops.Auftrag#AUFTRAG Mission The Auftrag of the Flightgroup - --- On after "AssetReturned" event. Triggered when an asset group returned to its airwing. - -- @function [parent=#AIRWING] OnAfterAssetReturned - -- @param #AIRWING self - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Ops.Squadron#SQUADRON Squadron The asset squadron. - -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. + --- Triggers the FSM event "FlightOnMission". + -- @function [parent=#AIRWING] FlightOnMission + -- @param #AIRWING self + -- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FLIGHTGROUP on mission. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "FlightOnMission" after a delay. + -- @function [parent=#AIRWING] __FlightOnMission + -- @param #AIRWING self + -- @param #number delay Delay in seconds. + -- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FLIGHTGROUP on mission. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "FlightOnMission" event. + -- @function [parent=#AIRWING] OnAfterFlightOnMission + -- @param #AIRWING self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FLIGHTGROUP on mission. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. return self end @@ -1092,16 +1096,16 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after "SquadAssetReturned" event. Triggered when an asset group returned to its airwing. +--- On after "FlightOnMission". -- @param #AIRWING self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.Squadron#SQUADRON Squadron The asset squadron. --- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. -function AIRWING:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) - -- Debug message. - self:T(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Squadron.name, tostring(Asset.assignment))) +-- @param Ops.FlightGroup#FLIGHTGROUP ArmyGroup Ops army group on mission. +-- @param Ops.Auftrag#AUFTRAG Mission The requested mission. +function AIRWING:onafterFlightOnMission(From, Event, To, FlightGroup, Mission) + -- Debug info. + self:T(self.lid..string.format("Group %s on %s mission %s", FlightGroup:GetName(), Mission:GetType(), Mission:GetName())) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 68552b007..3c7f2e6f1 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -512,8 +512,7 @@ function AUFTRAG:New(Type) -- State is planned. self.status=AUFTRAG.Status.PLANNED - -- Defaults - --self:SetVerbosity(0) + -- Defaults self:SetName() self:SetPriority() self:SetTime() @@ -2552,10 +2551,13 @@ end -- @return #AUFTRAG self function AUFTRAG:SetGroupStatus(opsgroup, status) - -- Debug info. - self:T(self.lid..string.format("Setting OPSGROUP %s to status %s", opsgroup and opsgroup.groupname or "nil", tostring(status))) + -- Current status. + local oldstatus=self:GetGroupStatus(opsgroup) - if self:GetGroupStatus(opsgroup)==AUFTRAG.GroupStatus.CANCELLED and status==AUFTRAG.GroupStatus.DONE then + -- Debug info. + self:T(self.lid..string.format("Setting OPSGROUP %s to status %s-->%s", opsgroup and opsgroup.groupname or "nil", tostring(oldstatus), tostring(status))) + + if oldstatus==AUFTRAG.GroupStatus.CANCELLED and status==AUFTRAG.GroupStatus.DONE then -- Do not overwrite a CANCELLED status with a DONE status. else local groupdata=self:GetGroupData(opsgroup) @@ -2566,12 +2568,18 @@ function AUFTRAG:SetGroupStatus(opsgroup, status) end end + -- Check if mission is NOT over. + local isNotOver=self:IsNotOver() + + -- Check if all assigned groups are done. + local groupsDone=self:CheckGroupsDone() + -- Debug info. - self:T2(self.lid..string.format("Setting flight %s status to %s. IsNotOver=%s CheckGroupsDone=%s", opsgroup.groupname, self:GetGroupStatus(opsgroup), tostring(self:IsNotOver()), tostring(self:CheckGroupsDone()))) + self:T2(self.lid..string.format("Setting OPSGROUP %s status to %s. IsNotOver=%s CheckGroupsDone=%s", opsgroup.groupname, self:GetGroupStatus(opsgroup), tostring(self:IsNotOver()), tostring(self:CheckGroupsDone()))) -- Check if ALL flights are done with their mission. - if self:IsNotOver() and self:CheckGroupsDone() then - self:T3(self.lid.."All flights done ==> mission DONE!") + if isNotOver and groupsDone then + self:T3(self.lid.."All assigned OPSGROUPs done ==> mission DONE!") self:Done() else self:T3(self.lid.."Mission NOT DONE yet!") @@ -2747,6 +2755,7 @@ function AUFTRAG:CheckGroupsDone() if groupdata then if not (groupdata.status==AUFTRAG.GroupStatus.DONE or groupdata.status==AUFTRAG.GroupStatus.CANCELLED) then -- At least this flight is not DONE or CANCELLED. + self:T(self.lid..string.format("CheckGroupsDone: OPSGROUP %s is not DONE or CANCELLED but in state %s. Mission NOT DONE!", groupdata.opsgroup.groupname, groupdata.status)) return false end end @@ -2758,6 +2767,7 @@ function AUFTRAG:CheckGroupsDone() local status=self:GetLegionStatus(legion) if not status==AUFTRAG.Status.CANCELLED then -- At least one LEGION has not CANCELLED. + self:T(self.lid..string.format("CheckGroupsDone: LEGION %s is not CANCELLED but in state %s. Mission NOT DONE!", legion.alias, status)) return false end end @@ -2765,6 +2775,7 @@ function AUFTRAG:CheckGroupsDone() -- Check commander status. if self.commander then if not self.statusCommander==AUFTRAG.Status.CANCELLED then + self:T(self.lid..string.format("CheckGroupsDone: COMMANDER is not CANCELLED but in state %s. Mission NOT DONE!", self.statusCommander)) return false end end @@ -2772,18 +2783,21 @@ function AUFTRAG:CheckGroupsDone() -- Check chief status. if self.chief then if not self.statusChief==AUFTRAG.Status.CANCELLED then + self:T(self.lid..string.format("CheckGroupsDone: CHIEF is not CANCELLED but in state %s. Mission NOT DONE!", self.statusChief)) return false end end -- These are early stages, where we might not even have a opsgroup defined to be checked. If there were any groups, we checked above. - if self:IsPlanned() or self:IsQueued() or self:IsRequested() then + if self:IsPlanned() or self:IsQueued() or self:IsRequested() then + self:T(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] (PLANNED or QUEUED or REQUESTED). Mission NOT DONE!", self.status, self:GetState())) return false end -- It could be that all flights were destroyed on the way to the mission execution waypoint. -- TODO: would be better to check if everybody is dead by now. if self:IsStarted() and self:CountOpsGroups()==0 then + self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] but count of alive OPSGROUP is zero. Mission DONE!", self.status, self:GetState())) return true end diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index 77d0b2948..c94974e63 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -36,13 +36,13 @@ BRIGADE = { --- BRIGADE class version. -- @field #string version -BRIGADE.version="0.0.1" +BRIGADE.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Add weapon range. +-- DONE: Add weapon range. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -68,8 +68,53 @@ function BRIGADE:New(WarehouseName, BrigadeName) self.lid=string.format("BRIGADE %s | ", self.alias) -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("*", "PlatoonAssetReturned", "*") -- An asset returned (from a mission) to the Brigade warehouse. + -- From State --> Event --> To State + self:AddTransition("*", "ArmyOnMission", "*") -- An ARMYGROUP was send on a Mission (AUFTRAG). + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the BRIGADE. Initializes parameters and starts event handlers. + -- @function [parent=#BRIGADE] Start + -- @param #BRIGADE self + + --- Triggers the FSM event "Start" after a delay. Starts the BRIGADE. Initializes parameters and starts event handlers. + -- @function [parent=#BRIGADE] __Start + -- @param #BRIGADE self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Stop". Stops the BRIGADE and all its event handlers. + -- @param #BRIGADE self + + --- Triggers the FSM event "Stop" after a delay. Stops the BRIGADE and all its event handlers. + -- @function [parent=#BRIGADE] __Stop + -- @param #BRIGADE self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "ArmyOnMission". + -- @function [parent=#BRIGADE] ArmyOnMission + -- @param #BRIGADE self + -- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "ArmyOnMission" after a delay. + -- @function [parent=#BRIGADE] __ArmyOnMission + -- @param #BRIGADE self + -- @param #number delay Delay in seconds. + -- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "ArmyOnMission" event. + -- @function [parent=#BRIGADE] OnAfterArmyOnMission + -- @param #BRIGADE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. return self end @@ -285,14 +330,8 @@ end -- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup Ops army group on mission. -- @param Ops.Auftrag#AUFTRAG Mission The requested mission. function BRIGADE:onafterArmyOnMission(From, Event, To, ArmyGroup, Mission) - local armygroup=ArmyGroup --Ops.ArmyGroup#ARMYGROUP - local mission=Mission --Ops.Auftrag#AUFTRAG - -- Debug info. - self:T(self.lid..string.format("Group %s on %s mission %s", armygroup:GetName(), mission:GetType(), mission:GetName())) - - - + self:T(self.lid..string.format("Group %s on %s mission %s", ArmyGroup:GetName(), Mission:GetType(), Mission:GetName())) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 5847acd73..f19274936 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -152,6 +152,7 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) -- @param #COHORT self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Stop". Stops the COHORT and all its event handlers. -- @param #COHORT self @@ -160,6 +161,7 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) -- @param #COHORT self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Status". -- @function [parent=#COHORT] Status -- @param #COHORT self diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index dac9f7298..116a1582f 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -222,7 +222,8 @@ function INTEL:New(DetectionSet, Coalition, Alias) -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. - self:AddTransition("*", "Status", "*") -- INTEL status update + self:AddTransition("*", "Status", "*") -- INTEL status update. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Detect", "*") -- Start detection run. Not implemented yet! @@ -231,7 +232,7 @@ function INTEL:New(DetectionSet, Coalition, Alias) self:AddTransition("*", "NewCluster", "*") -- New cluster has been detected. self:AddTransition("*", "LostCluster", "*") -- Cluster could not be detected any more. - self:AddTransition("*", "Stop", "Stopped") + -- Defaults self:SetForgetTime() diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index f045192bc..94d690a71 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -42,14 +42,15 @@ LEGION = { --- LEGION class version. -- @field #string version -LEGION.version="0.0.1" +LEGION.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: A lot. --- TODO: Make general so it can be inherited by AIRWING and BRIGADE classes. +-- TODO: Create FLOTILLA class. +-- TODO: OPS transport. +-- DONE: Make general so it can be inherited by AIRWING and BRIGADE classes. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -73,24 +74,21 @@ function LEGION:New(WarehouseName, LegionName) -- Set some string id for output to DCS.log file. self.lid=string.format("LEGION %s | ", self.alias) - - -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. - self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. - - self:AddTransition("*", "TransportRequest", "*") -- Add a (mission) request to the warehouse. - - self:AddTransition("*", "OpsOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). - self:AddTransition("*", "FlightOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). - self:AddTransition("*", "ArmyOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). - self:AddTransition("*", "NavyOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). - - self:AddTransition("*", "AssetReturned", "*") -- An asset returned (from a mission) to the Legion warehouse. -- Defaults: - -- TODO + -- TODO: What? + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. + self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + + self:AddTransition("*", "TransportRequest", "*") -- Add a (mission) request to the warehouse. + + self:AddTransition("*", "OpsOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). + + self:AddTransition("*", "LegionAssetReturned", "*") -- An asset returned (from a mission) to the Legion warehouse. + ------------------------ --- Pseudo Functions --- ------------------------ @@ -133,6 +131,92 @@ function LEGION:New(WarehouseName, LegionName) -- @param Ops.Auftrag#AUFTRAG Mission The mission. + --- Triggers the FSM event "MissionRequest". + -- @function [parent=#LEGION] MissionRequest + -- @param #LEGION self + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "MissionRequest" after a delay. + -- @function [parent=#LEGION] __MissionRequest + -- @param #LEGION self + -- @param #number delay Delay in seconds. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "MissionRequest" event. + -- @function [parent=#LEGION] OnAfterMissionRequest + -- @param #LEGION self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + + --- Triggers the FSM event "TransportRequest". + -- @function [parent=#LEGION] TransportRequest + -- @param #LEGION self + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- Triggers the FSM event "TransportRequest" after a delay. + -- @function [parent=#LEGION] __TransportRequest + -- @param #LEGION self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- On after "TransportRequest" event. + -- @function [parent=#LEGION] OnAfterTransportRequest + -- @param #LEGION self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + + --- Triggers the FSM event "OpsOnMission". + -- @function [parent=#LEGION] OpsOnMission + -- @param #LEGION 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=#LEGION] __OpsOnMission + -- @param #LEGION 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=#LEGION] OnAfterOpsOnMission + -- @param #LEGION 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 "LegionAssetReturned". + -- @function [parent=#LEGION] LegionAssetReturned + -- @param #LEGION self + -- @param Ops.Cohort#COHORT Cohort The cohort the asset belongs to. + -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. + + --- Triggers the FSM event "LegionAssetReturned" after a delay. + -- @function [parent=#LEGION] __LegionAssetReturned + -- @param #LEGION self + -- @param #number delay Delay in seconds. + -- @param Ops.Cohort#COHORT Cohort The cohort the asset belongs to. + -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. + + --- On after "LegionAssetReturned" event. Triggered when an asset group returned to its Legion. + -- @function [parent=#LEGION] OnAfterLegionAssetReturned + -- @param #LEGION self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Cohort#COHORT Cohort The cohort the asset belongs to. + -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. + + return self end @@ -773,8 +857,8 @@ function LEGION:onafterMissionCancel(From, Event, To, Mission) -- Set status to cancelled. Mission:SetLegionStatus(self, AUFTRAG.Status.CANCELLED) - for _,_asset in pairs(Mission.assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + for i=#Mission.assets, 1, -1 do + local asset=Mission.assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem -- Asset should belong to this legion. if asset.wid==self.uid then @@ -909,21 +993,21 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) --- -- Trigger event. - self:AssetReturned(cohort, asset) + self:LegionAssetReturned(cohort, asset) end end end ---- On after "AssetReturned" event. Triggered when an asset group returned to its legion. +--- On after "LegionAssetReturned" event. Triggered when an asset group returned to its legion. -- @param #LEGION self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.Cohort#COHORT Cohort The cohort the asset belongs to. -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. -function LEGION:onafterAssetReturned(From, Event, To, Cohort, Asset) +function LEGION:onafterLegionAssetReturned(From, Event, To, Cohort, Asset) -- Debug message. self:T(self.lid..string.format("Asset %s from Cohort %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Cohort.name, tostring(Asset.assignment))) @@ -945,13 +1029,6 @@ function LEGION:onafterAssetReturned(From, Event, To, Cohort, Asset) -- Set timestamp. Asset.Treturned=timer.getAbsTime() - if self:IsAirwing() then - -- Trigger airwing/squadron event. - self:SquadronAssetReturned(Cohort, Asset) - elseif self:IsBrigade() then - -- Trigger brigade/platoon event. - self:PlatoonAssetReturned(Cohort, Asset) - end end @@ -1366,7 +1443,7 @@ function LEGION:CountPayloadsInStock(MissionTypes, UnitTypes, Payloads) local n=0 for _,_payload in pairs(self.payloads or {}) do - local payload=_payload --#LEGION.Payload + local payload=_payload --Ops.Airwing#AIRWING.Payload for _,MissionType in pairs(MissionTypes) do @@ -1451,7 +1528,7 @@ function LEGION:CountAssetsWithPayloadsInStock(Payloads, MissionTypes, Attribute local cohort=_cohort --Ops.Cohort#COHORT if Npayloads[cohort.aircrafttype]==nil then Npayloads[cohort.aircrafttype]=self:CountPayloadsInStock(MissionTypes, cohort.aircrafttype, Payloads) - env.info(string.format("FF got Npayloads=%d for type=%s",Npayloads[cohort.aircrafttype], cohort.aircrafttype)) + self:T3(self.lid..string.format("Got Npayloads=%d for type=%s",Npayloads[cohort.aircrafttype], cohort.aircrafttype)) end end @@ -1467,9 +1544,6 @@ function LEGION:CountAssetsWithPayloadsInStock(Payloads, MissionTypes, Attribute -- Only the smaller number of assets or paylods is really available. local m=math.min(n, p) - env.info("FF n="..n) - env.info("FF p="..p) - -- Add up what we have. Could also be zero. N=N+m @@ -1707,7 +1781,7 @@ end --- Get payload performance for a given type of misson type. -- @param #LEGION self --- @param #LEGION.Payload Payload The payload table. +-- @param Ops.Airwing#AIRWING.Payload Payload The payload table. -- @param #string MissionType Type of mission. -- @return #number Performance or -1. function LEGION:GetPayloadPeformance(Payload, MissionType) @@ -1730,7 +1804,7 @@ end --- Get mission types a payload can perform. -- @param #LEGION self --- @param #LEGION.Payload Payload The payload table. +-- @param Ops.Airwing#AIRWING.Payload Payload The payload table. -- @return #table Mission types. function LEGION:GetPayloadMissionTypes(Payload) diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 0dde8433c..ce6887c25 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -231,6 +231,8 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self:AddTransition("*", "DeadCarrierUnit", "*") self:AddTransition("*", "DeadCarrierGroup", "*") self:AddTransition("*", "DeadCarrierAll", "*") + + --TODO: Psydofunctions -- Call status update. self:__Status(-1) diff --git a/Moose Development/Moose/Ops/Platoon.lua b/Moose Development/Moose/Ops/Platoon.lua index 9b7df07d1..da63f6400 100644 --- a/Moose Development/Moose/Ops/Platoon.lua +++ b/Moose Development/Moose/Ops/Platoon.lua @@ -40,7 +40,7 @@ PLATOON = { --- PLATOON class version. -- @field #string version -PLATOON.version="0.0.1" +PLATOON.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -170,7 +170,7 @@ function PLATOON:onafterStatus(From, Event, To) self:I(self.lid..text) -- Weapon data info. - if self.weaponData then + if self.verbose>=3 and self.weaponData then local text="Weapon Data:" for bit,_weapondata in pairs(self.weaponData) do local weapondata=_weapondata --Ops.OpsGroup#OPSGROUP.WeaponData diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 9aaba294b..22cd1f964 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -102,10 +102,7 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName) -- Everyone can ORBIT. self:AddMissionCapability(AUFTRAG.Type.ORBIT) - - -- Aircraft type. - self.aircrafttype=self.templategroup:GetTypeName() - + -- Refueling system. self.refuelSystem=select(2, self.templategroup:GetUnit(1):IsRefuelable()) self.tankerSystem=select(2, self.templategroup:GetUnit(1):IsTanker()) From fe6826016c059c04628a1095095eee69d660cbd1 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 27 Aug 2021 11:59:34 +0200 Subject: [PATCH 084/141] Update Chief.lua --- Moose Development/Moose/Ops/Chief.lua | 40 +++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 93211d8f1..0e3c868b2 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -118,9 +118,9 @@ function CHIEF:New(AgentSet, Coalition) -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("*", "AssignMissionAirforce", "*") -- Assign mission to a WINGCOMMANDER. - self:AddTransition("*", "AssignMissionNavy", "*") -- Assign mission to an ADMIRAL. - self:AddTransition("*", "AssignMissionArmy", "*") -- Assign mission to a GENERAL. + self:AddTransition("*", "AssignMissionAirforce", "*") -- Assign mission to a COMMANDER but request only AIR assets. + self:AddTransition("*", "AssignMissionNavy", "*") -- Assign mission to a COMMANDER but request only NAVAL assets. + self:AddTransition("*", "AssignMissionArmy", "*") -- Assign mission to a COMMANDER but request only GROUND assets. self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. @@ -168,6 +168,12 @@ function CHIEF:New(AgentSet, Coalition) -- @param #CHIEF self -- @param Ops.Auftrag#AUFTRAG Mission The mission. + --- Triggers the FSM event "MissionCancel" after a delay. + -- @function [parent=#CHIEF] MissionCancel + -- @param #CHIEF self + -- @param #number delay Delay in seconds. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + --- On after "MissionCancel" event. -- @function [parent=#CHIEF] OnAfterMissionCancel -- @param #CHIEF self @@ -586,10 +592,29 @@ end function CHIEF:onafterAssignMissionAirforce(From, Event, To, Mission) if self.commander then - self:I(self.lid..string.format("Assigning mission %s (%s) to WINGCOMMANDER", Mission.name, Mission.type)) + 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 WINGCOMMANDER is defined.")) + self:E(self.lid..string.format("Mission cannot be assigned as no COMMANDER is defined!")) + end + +end + +--- On after "AssignMissionAssignArmy" event. +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function CHIEF:onafterAssignMissionArmy(From, Event, To, Mission) + + 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) + else + self:E(self.lid..string.format("Mission cannot be assigned as no COMMANDER is defined!")) end end @@ -681,7 +706,7 @@ end -- Target Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Check mission queue and assign ONE planned mission. +--- Check target queue and assign ONE valid target by adding it to the mission queue of the COMMANDER. -- @param #CHIEF self function CHIEF:CheckTargetQueue() @@ -737,6 +762,9 @@ function CHIEF:CheckTargetQueue() -- Add mission to COMMANDER queue. self:AddMission(mission) + + -- Only ONE target is assigned per check. + return end end From 28ddfa5243c08954b06b88405c4b4ebd258829b4 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 28 Aug 2021 00:17:16 +0200 Subject: [PATCH 085/141] OPSTRANSPORT - Fist working steps towards multiple pickup/deploy zone combos. --- Moose Development/Moose/Core/Zone.lua | 17 +++ Moose Development/Moose/Ops/OpsGroup.lua | 139 +++++++++++++----- Moose Development/Moose/Ops/OpsTransport.lua | 146 ++++++++++++++++--- 3 files changed, 244 insertions(+), 58 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 81d37269c..45419d5f9 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -288,6 +288,23 @@ function ZONE_BASE:GetCoordinate( Height ) --R2.1 return self.Coordinate end +--- Get 2D distance to a coordinate. +-- @param #ZONE_BASE self +-- @param Core.Point#COORDINATE Coordinate Reference coordinate. Can also be a DCS#Vec2 or DCS#Vec3 object. +-- @return #number Distance to the reference coordinate in meters. +function ZONE_BASE:Get2DDistance(Coordinate) + local a=self:GetVec2() + local b={} + if Coordinate.z then + b.x=Coordinate.x + b.y=Coordinate.z + else + b.x=Coordinate.x + b.y=Coordinate.y + end + local dist=UTILS.VecDist2D(a,b) + return dist +end --- Define a random @{DCS#Vec2} within the zone. -- @param #ZONE_BASE self diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index b63a1106e..b35e7cb83 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -115,6 +115,7 @@ -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #table cargoBay Table containing OPSGROUP loaded into this group. -- @field Ops.OpsTransport#OPSTRANSPORT cargoTransport Current cargo transport assignment. +-- @field Ops.OpsTransport#OPSTRANSPORT.TransportZone transportZone Transport zones (pickup, deploy etc.). -- @field #string cargoStatus Cargo status of this group acting as cargo. -- @field #number cargoTransportUID Unique ID of the transport assignment this cargo group is associated with. -- @field #string carrierStatus Carrier status of this group acting as cargo carrier. @@ -476,6 +477,7 @@ OPSGROUP.version="0.7.5" -- TODO: Damage? -- TODO: Shot events? -- TODO: Marks to add waypoints/tasks on-the-fly. +-- DONE: A lot. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -687,7 +689,13 @@ function OPSGROUP:New(group) --- Triggers the FSM event "MissionCancel". -- @function [parent=#OPSGROUP] MissionCancel - -- @param #CHIEF self + -- @param #OPSGROUP self + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "MissionCancel" after a delay + -- @function [parent=#OPSGROUP] MissionCancel + -- @param #OPSGROUP self + -- @param #number delay Delay in seconds. -- @param Ops.Auftrag#AUFTRAG Mission The mission. --- On after "MissionCancel" event. @@ -1769,6 +1777,36 @@ function OPSGROUP:SetCarrierUnloaderPort(Length, Width) return self end +--- Check if group is currently inside a zone. +-- @param #OPSGROUP self +-- @param Core.Zone#ZONE Zone The zone. +-- @return #boolean If true, group is in this zone +function OPSGROUP:IsInZone(Zone) + local vec2=self:GetVec2() + local is=Zone:IsVec2InZone(vec2) + return is +end + +--- Get 2D distance to a coordinate. +-- @param #OPSGROUP self +-- @param Core.Point#COORDINATE Coordinate. Can also be a DCS#Vec2 or DCS#Vec3. +-- @return #number Distance in meters. +function OPSGROUP:Get2DDistance(Coordinate) + + local a=self:GetVec2() + local b={} + if Coordinate.z then + b.x=Coordinate.x + b.y=Coordinate.z + else + b.x=Coordinate.x + b.y=Coordinate.y + end + + local dist=UTILS.VecDist2D(a, b) + + return dist +end --- Check if this is a FLIGHTGROUP. -- @param #OPSGROUP self @@ -3567,7 +3605,7 @@ function OPSGROUP:_GetNextMission() -- Good example is the above transport. The legion should start the mission but the group should only start after the transport is finished. -- Check necessary conditions. - if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.SCHEDULED and (mission:IsReadyToGo() or self.legion) and (mission.importance==nil or mission.importance<=vip) and transport then + if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.SCHEDULED and (mission:IsReadyToGo() or self.legion) and (mission.importance==nil or mission.importance<=vip) and transport then return mission end @@ -4972,11 +5010,11 @@ function OPSGROUP:_UpdateLaser() end --- On before "ElementSpawned" event. Check that element is not in status spawned already. --- @param #FLIGHTGROUP self +-- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. +-- @param #OPSGROUP.Element Element The flight group element. function OPSGROUP:onbeforeElementSpawned(From, Event, To, Element) if Element and Element.status==OPSGROUP.ElementStatus.SPAWNED then @@ -5505,10 +5543,19 @@ function OPSGROUP:_CheckCargoTransport() if self:IsNotCarrier() then -- Debug info. - self:T(self.lid.."Not carrier ==> pickup") + self:T(self.lid.."Not carrier ==> pickup?") + + -- Get transport zones. + local transportZone=self.cargoTransport:_GetPickupZone(self) + + if transportZone then - -- Initiate the cargo transport process. - self:__Pickup(-1) + -- Initiate the cargo transport process. + self:__Pickup(-1, transportZone) + + else + self:T(self.lid.."Not carrier ==> pickup") + end elseif self:IsPickingup() then @@ -5730,13 +5777,16 @@ function OPSGROUP:_GetNextCargoTransport() local function _sort(a, b) local transportA=a --Ops.OpsTransport#OPSTRANSPORT local transportB=b --Ops.OpsTransport#OPSTRANSPORT - local distA=transportA.pickupzone:GetCoordinate():Get2DDistance(coord) - local distB=transportB.pickupzone:GetCoordinate():Get2DDistance(coord) - return (transportA.prio Pickup") - self:Pickup(self.cargoTransport.pickupzone) + -- Pickup the next batch. + self:I(self.lid.."Unloaded: Still cargo left ==> Pickup") + self:Pickup(transportZone) + + else + env.info("FF error not implemented case!") + end else @@ -9920,6 +9967,22 @@ function OPSGROUP:_GetTemplate(Copy) return nil end +--- Clear waypoints. +-- @param #OPSGROUP self +-- @param #number IndexMin Clear waypoints up to this min WP index. Default 1. +-- @param #number IndexMax Clear waypoints up to this max WP index. Default `#self.waypoints`. +function OPSGROUP:ClearWaypoints(IndexMin, IndexMax) + + IndexMin=IndexMin or 1 + IndexMax=IndexMax or #self.waypoints + + -- Clear all waypoints. + for i=IndexMax,IndexMin,-1 do + table.remove(self.waypoints, i) + end + --self.waypoints={} +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index ce6887c25..5d0cd8b7e 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -38,11 +38,11 @@ -- @field #number Tstop Stop time in *abs.* seconds. Default `#nil` (never stops). -- @field #number duration Duration (`Tstop-Tstart`) of the transport in seconds. -- @field #table conditionStart Start conditions. +-- @field #table transportZones Table of transport zones. Each element of the table is of type `#OPSTRANSPORT.TransportZone`. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. --- @field Core.Zone#ZONE unboardzone (Optional) Zone where the cargo is going to after disembarkment. -- @field #boolean disembarkActivation Activation setting when group is disembared from carrier. -- @field #boolean disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. -- @field #table disembarkCarriers Table of carriers to which the cargo is disembared. This is a direct transfer from the old to the new carrier. @@ -117,6 +117,7 @@ OPSTRANSPORT = { cargos = {}, carriers = {}, carrierTransportStatus = {}, + transportZones = {}, conditionStart = {}, pathsTransport = {}, pathsPickup = {}, @@ -141,13 +142,22 @@ OPSTRANSPORT.Status={ DELIVERED="delivered", } ---- Path. +--- Pickup and deploy set. +-- @type OPSTRANSPORT.TransportZone +-- @field Core.Zone#ZONE PickupZone Pickup zone. +-- @field Core.Zone#ZONE DeployZone Deploy zone. +-- @field Core.Zone#ZONE EmbarkZone Embark zone if different from pickup zone. +-- @field #OPSTRANSPORT.Path PickupPath Path for pickup. +-- @field #OPSTRANSPORT.Path TransportPath Path for Transport. +-- @field #numberr Ncarriers Number of carrier groups using this transport zone. + +--- Path used for pickup or transport. -- @type OPSTRANSPORT.Path -- @field #table coords Table of coordinates. -- @field #number radius Radomization radius in meters. Default 0 m. -- @field #number altitude Altitude in feet AGL. Only for aircraft. ---- Generic mission condition. +--- Generic transport condition. -- @type OPSTRANSPORT.Condition -- @field #function func Callback function to check for a condition. Should return a #boolean. -- @field #table arg Optional arguments passed to the condition callback function. @@ -163,6 +173,7 @@ OPSTRANSPORT.version="0.3.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Allow multiple pickup/depoly zones. -- TODO: Stop/abort transport. -- DONE: Add start conditions. -- DONE: Check carrier(s) dead. @@ -295,6 +306,31 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) return self end +--- Add pickup and deploy zone combination. Optionally, embark and disembark zones can be specified. +-- +-- * The pickup zone is a zone where +-- * bla +-- +-- @param #OPSTRANSPORT self +-- @param Core.Zone#ZONE PickupZone Zone where the troops are picked up. +-- @return #OPSTRANSPORT.TransportZone Transport zone table. +function OPSTRANSPORT:AddTransportZones(PickupZone, DeployZone, EmbarkZone, DisembarkZone, PickupPath, TransportPath) + + local transport={} --#OPSTRANSPORT.TransportZone + + transport.PickupZone=PickupZone + transport.DeployZone=DeployZone + transport.EmbarkZone=EmbarkZone or PickupZone + transport.DisembarkZone=DisembarkZone + transport.PickupPath=PickupPath + transport.TransportPath=TransportPath + transport.Ncarriers=0 + + table.insert(self.transportZones, transport) + + return transport +end + --- Set pickup zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE PickupZone Zone where the troops are picked up. @@ -501,15 +537,18 @@ end --- Get (all) cargo @{Ops.OpsGroup#OPSGROUP}s. Optionally, only delivered or undelivered groups can be returned. -- @param #OPSTRANSPORT self -- @param #boolean Delivered If `true`, only delivered groups are returned. If `false` only undelivered groups are returned. If `nil`, all groups are returned. --- @return #table Cargo Ops groups. -function OPSTRANSPORT:GetCargoOpsGroups(Delivered) +-- @param Ops.OpsGroup#OPSGROUP Carrier (Optional) Only count cargo groups that fit into the given carrier group. Current cargo is not a factor. +-- @return #table Cargo Ops groups. Can be and empty table `{}`. +function OPSTRANSPORT:GetCargoOpsGroups(Delivered, Carrier) local opsgroups={} for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - if Delivered==nil or cargo.delivered==Delivered then + if Delivered==nil or cargo.delivered==Delivered then if cargo.opsgroup and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then - table.insert(opsgroups, cargo.opsgroup) + if Carrier==nil or Carrier:CanCargo(cargo.opsgroup) then + table.insert(opsgroups, cargo.opsgroup) + end end end end @@ -816,24 +855,27 @@ function OPSTRANSPORT:IsReadyToGo() -- Current abs time. local Tnow=timer.getAbsTime() - -- Pickup and deploy zones must be set. - if not self.pickupzone then - text=text.."No, pickup zone not defined!" - return false + -- Pickup AND deploy zones must be set. + local gotzones=false + for _,_tz in pairs(self.transportZones) do + local tz=_tz --#OPSTRANSPORT.TransportZone + if tz.PickupZone and tz.DeployZone then + gotzones=true + break + end end - if not self.deployzone then - text=text.."No, deploy zone not defined!" - return false + if not gotzones then + text=text.."No, pickup/deploy zone combo not yet defined!" end - + -- Start time did not pass yet. - if self.Tstart and Tnowself.Tstop or false then + if self.Tstop and Tnow>self.Tstop then text=text.."Nope, stop time already passed!" self:T(text) return false @@ -914,13 +956,21 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) if self.verbose>=1 then -- Info text. - local pickupname=self.pickupzone and self.pickupzone:GetName() or "Unknown" - local deployname=self.deployzone and self.deployzone:GetName() or "Unknown" - local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), pickupname, deployname, self.Ncargo, self.Ndelivered, #self.carriers, self.Ncarrier) + local text=string.format("%s: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), self.Ncargo, self.Ndelivered, #self.carriers, self.Ncarrier) -- Info about cargo and carrier. if self.verbose>=2 then + for i,_tz in pairs(self.transportZones) do + local tz=_tz --#OPSTRANSPORT.TransportZone + text=text..string.format("\n[%d] %s --> %s", i, tz.PickupZone and tz.PickupZone:GetName() or "Unknown", tz.DeployZone and tz.DeployZone and tz.DeployZone:GetName() or "Unknown", tz.Ncarriers) + end + + end + + -- Info about cargo and carrier. + if self.verbose>=3 then + text=text..string.format("\nCargos:") for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup @@ -1222,6 +1272,62 @@ function OPSTRANSPORT:_CreateCargoGroupData(group) return cargo end +--- Count how many cargo groups are inside a zone. +-- @param #OPSTRANSPORT self +-- @param Core.Zone#ZONE Zone The zone object. +-- @param #boolean Delivered If `true`, only delivered groups are returned. If `false` only undelivered groups are returned. If `nil`, all groups are returned. +-- @param Ops.OpsGroup#OPSGROUP Carrier (Optional) Only count cargo groups that fit into the given carrier group. Current cargo is not a factor. +-- @return #number Number of cargo groups. +function OPSTRANSPORT:_CountCargosInZone(Zone, Delivered, Carrier) + + local cargos=self:GetCargoOpsGroups(Delivered, Carrier) + + local N=0 + for _,_cargo in pairs(cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP + if cargo:IsInZone(Zone) then + N=N+1 + end + end + + return N +end + +--- Get a pickup zone for a carrier group. This will be a zone, where the most cargo groups are located that fit into the carrier. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP Carrier The carrier OPS group. +-- @return Core.Zone#ZONE Pickup zone or `#nil`. +function OPSTRANSPORT:_GetPickupZone(Carrier) + + env.info(string.format("FF GetPickupZone")) + local pickup=nil + + local distmin=nil + for i,_transportzone in pairs(self.transportZones) do + local tz=_transportzone --#OPSTRANSPORT.TransportZone + + -- Count cargos in pickup zone. + local ncargo=self:_CountCargosInZone(tz.PickupZone, false, Carrier) + + env.info(string.format("FF GetPickupZone i=%d, ncargo=%d", i, ncargo)) + + if ncargo>0 then + + local vec2=Carrier:GetVec2() + + local dist=tz.PickupZone:Get2DDistance(vec2) + + if distmin==nil or dist Date: Mon, 30 Aug 2021 23:45:40 +0200 Subject: [PATCH 086/141] OPSTRANSPORT v0.4 - Multiple transport zone combos (TZC) - Other issues solved. - Demo miz D-day landing is still not working correctly. --- .../Moose/Functional/Warehouse.lua | 4 + Moose Development/Moose/Ops/ArmyGroup.lua | 15 +- Moose Development/Moose/Ops/FlightGroup.lua | 30 +- Moose Development/Moose/Ops/NavyGroup.lua | 20 +- Moose Development/Moose/Ops/OpsGroup.lua | 339 +++++++----- Moose Development/Moose/Ops/OpsTransport.lua | 511 +++++++++++++----- 6 files changed, 625 insertions(+), 294 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index c664f4f4b..3fa15d19a 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -6329,6 +6329,10 @@ function WAREHOUSE:_OnEventBirth(EventData) local request=self:GetRequestByID(rid) if asset and request then + + if asset.spawned and type(asset.spawned)=="boolean" and asset.spawned==true then + return + end -- Debug message. self:T(self.lid..string.format("Warehouse %s captured event birth of request ID=%d, asset ID=%d, unit %s spawned=%s", self.alias, request.uid, asset.uid, EventData.IniUnitName, tostring(asset.spawned))) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index f4d8194a2..fc1761600 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -90,10 +90,7 @@ function ARMYGROUP:New(group) og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) return og end - - -- First set ARMYGROUP. - self.isArmygroup=true - + -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #ARMYGROUP @@ -595,6 +592,16 @@ end -- @param #number Formation Formation of the group. function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Formation) if self:IsWaiting() then + self:E(self.lid.."Update route denied. Group is WAIRING!") + return false + elseif self:IsInUtero() then + self:E(self.lid.."Update route denied. Group is INUTERO!") + return false + elseif self:IsDead() then + self:E(self.lid.."Update route denied. Group is DEAD!") + return false + elseif self:IsStopped() then + self:E(self.lid.."Update route denied. Group is STOPPED!") return false end return true diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index d039dcc08..ea0a73eea 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -32,7 +32,7 @@ --- FLIGHTGROUP class. -- @type FLIGHTGROUP -- @field #string actype Type name of the aircraft. --- @field #number rangemax Max range in km. +-- @field #number rangemax Max range in meters. -- @field #number ceiling Max altitude the aircraft can fly at in meters. -- @field #number tankertype The refueling system type (0=boom, 1=probe), if the group is a tanker. -- @field #number refueltype The refueling system type (0=boom, 1=probe), if the group can refuel from a tanker. @@ -208,9 +208,6 @@ function FLIGHTGROUP:New(group) return og end - -- First set FLIGHTGROUP. - self.isFlightgroup=true - -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #FLIGHTGROUP @@ -1766,14 +1763,12 @@ function FLIGHTGROUP:onafterCruise(From, Event, To) --- if self:IsTransporting() then - if self.cargoTransport and self.cargoTransport.deployzone and self.cargoTransport.deployzone:IsInstanceOf("ZONE_AIRBASE") then - local airbase=self.cargoTransport.deployzone:GetAirbase() - self:LandAtAirbase(airbase) + if self.cargoTransport and self.cargoTZC and self.cargoTZC.DeployAirbase then + self:LandAtAirbase(self.cargoTZC.DeployAirbase) end elseif self:IsPickingup() then - if self.cargoTransport and self.cargoTransport.pickupzone and self.cargoTransport.pickupzone:IsInstanceOf("ZONE_AIRBASE") then - local airbase=self.cargoTransport.pickupzone:GetAirbase() - self:LandAtAirbase(airbase) + if self.cargoTransport and self.cargoTZC and self.cargoTZC.PickupAirbase then + self:LandAtAirbase(self.cargoTZC.PickupAirbase) end else self:_CheckGroupDone(nil, 120) @@ -1983,6 +1978,9 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) -- Group is dead! No more updates. self:E(self.lid.."Update route denied. Group is DEAD!") allowed=false + elseif self:IsInUtero() then + self:E(self.lid.."Update route denied. Group is INUTERO!") + allowed=false else -- Not airborne yet. Try again in 5 sec. self:T(self.lid.."Update route denied ==> checking back in 5 sec") @@ -2084,9 +2082,9 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) local waypointAction=COORDINATE.WaypointAction.TurningPoint if self:IsLanded() or self:IsLandedAt() or self:IsAirborne()==false then -- Had some issues with passing waypoint function of the next WP called too ealy when the type is TurningPoint. Setting it to TakeOff solved it! - --waypointType=COORDINATE.WaypointType.TakeOff - waypointType=COORDINATE.WaypointType.TakeOffGroundHot - waypointAction=COORDINATE.WaypointAction.FromGroundAreaHot + waypointType=COORDINATE.WaypointType.TakeOff + --waypointType=COORDINATE.WaypointType.TakeOffGroundHot + --waypointAction=COORDINATE.WaypointAction.FromGroundAreaHot end -- Set current waypoint or we get problem that the _PassingWaypoint function is triggered too early, i.e. right now and not when passing the next WP. @@ -2585,9 +2583,9 @@ function FLIGHTGROUP:onbeforeWait(From, Event, To, Duration, Altitude, Speed) -- Check for a current transport assignment. if self.cargoTransport and not self:IsLandedAt() then - self:I(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) - Tsuspend=-30 - allowed=false + --self:I(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) + --Tsuspend=-30 + --allowed=false end -- Call wait again. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index e4414fd86..500642a4e 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -114,9 +114,6 @@ function NAVYGROUP:New(group) return og end - -- First set NAVYGROUP. - self.isNavygroup=true - -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #NAVYGROUP @@ -467,6 +464,9 @@ function NAVYGROUP:Status(From, Event, To) -- Is group alive? local alive=self:IsAlive() + -- + local freepath=0 + if alive then --- @@ -485,7 +485,7 @@ function NAVYGROUP:Status(From, Event, To) self:_CheckTurning() local disttoWP=math.min(self:GetDistanceToWaypoint(), UTILS.NMToMeters(10)) - local freepath=disttoWP + freepath=disttoWP -- Only check if not currently turning. if not self:IsTurning() then @@ -713,6 +713,16 @@ end -- @param #number Depth Depth in meters to the next waypoint. function NAVYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Depth) if self:IsWaiting() then + self:E(self.lid.."Update route denied. Group is WAIRING!") + return false + elseif self:IsInUtero() then + self:E(self.lid.."Update route denied. Group is INUTERO!") + return false + elseif self:IsDead() then + self:E(self.lid.."Update route denied. Group is DEAD!") + return false + elseif self:IsStopped() then + self:E(self.lid.."Update route denied. Group is STOPPED!") return false end return true @@ -771,7 +781,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) table.insert(waypoints, 1, current) - if not self.passedfinalwp then + if self:IsEngaging() or not self.passedfinalwp then -- Debug info. self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Depth=%d m", self.currentwp, n, #waypoints, #self.waypoints, UTILS.MpsToKnots(self.speedWp), wp.alt)) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index b35e7cb83..a1db9890f 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -115,7 +115,7 @@ -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #table cargoBay Table containing OPSGROUP loaded into this group. -- @field Ops.OpsTransport#OPSTRANSPORT cargoTransport Current cargo transport assignment. --- @field Ops.OpsTransport#OPSTRANSPORT.TransportZone transportZone Transport zones (pickup, deploy etc.). +-- @field Ops.OpsTransport#OPSTRANSPORT.TransportZoneCombo cargoTZC Transport zone combo (pickup, deploy etc.) currently used. -- @field #string cargoStatus Cargo status of this group acting as cargo. -- @field #number cargoTransportUID Unique ID of the transport assignment this cargo group is associated with. -- @field #string carrierStatus Carrier status of this group acting as cargo carrier. @@ -512,6 +512,8 @@ function OPSGROUP:New(group) end end + self.group:IsAir() + -- Set the template. self:_SetTemplate() @@ -519,7 +521,27 @@ function OPSGROUP:New(group) self.dcsgroup=self:GetDCSGroup() self.controller=self.dcsgroup:getController() + -- Category. + self.category=self.dcsgroup:getCategory() + if self.category==Group.Category.GROUND then + self.isArmygroup=true + elseif self.category==Group.Category.TRAIN then + self.isArmygroup=true + self.isTrain=true + elseif self.category==Group.Category.SHIP then + self.isNavygroup=true + -- TODO submarine + elseif self.category==Group.Category.AIRPLANE then + self.isFlightgroup=true + elseif self.category==Group.Category.HELICOPTER then + self.isFlightgroup=true + self.isHelo=true + else + + end + local units=self.group:GetUnits() + if units then local masterunit=units[1] --Wrapper.Unit#UNIT @@ -531,10 +553,8 @@ function OPSGROUP:New(group) if self:IsFlightgroup() then - self.rangemax=masterunit:GetRange() - - self.descriptors=masterunit:GetDesc() - + self.rangemax=self.descriptors.range and self.descriptors.range*1000 or 500*1000 + self.ceiling=self.descriptors.Hmax self.tankertype=select(2, masterunit:IsTanker()) @@ -1783,7 +1803,10 @@ end -- @return #boolean If true, group is in this zone function OPSGROUP:IsInZone(Zone) local vec2=self:GetVec2() - local is=Zone:IsVec2InZone(vec2) + local is=false + if vec2 then + is=Zone:IsVec2InZone(vec2) + end return is end @@ -2750,17 +2773,33 @@ function OPSGROUP:PushTask(DCSTask) return self end +--- Returns true if the DCS controller currently has a task. +-- @param #OPSGROUP self +-- @param #number Delay Delay in seconds. +-- @return #boolean True or false if the controller has a task. Nil if no controller. +function OPSGROUP:HasTaskController(Delay) + local hastask=nil + if self.controller then + hastask=self.controller:hasTask() + end + self:I(self.lid..string.format("Controller hasTask=%s", tostring(hastask))) + return hastask +end + --- Clear DCS tasks. -- @param #OPSGROUP self -- @return #OPSGROUP self function OPSGROUP:ClearTasks() - if self:IsAlive() then + local hastask=self:HasTaskController() + if self:IsAlive() and self.controller and self:HasTaskController() then self:I(self.lid..string.format("CLEARING Tasks")) - self.group:ClearTasks() + self.controller:resetTask() end return self end + + --- Add a *scheduled* task. -- @param #OPSGROUP self -- @param #table task DCS task table structure. @@ -5262,7 +5301,11 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) self:T({Template=Template}) -- Spawn new group. - _DATABASE:Spawn(Template) + self.group=_DATABASE:Spawn(Template) + + -- Set DCS group and controller. + self.dcsgroup=self:GetDCSGroup() + self.controller=self.dcsgroup:getController() -- Set activation and controlled state. self.isLateActivated=Template.lateActivation @@ -5373,6 +5416,7 @@ function OPSGROUP:onafterDead(From, Event, To) -- No current cargo transport. self.cargoTransport=nil + self.cargoTZC=nil if self.Ndestroyed==#self.elements then if self.cohort then @@ -5488,10 +5532,11 @@ function OPSGROUP:_CheckCargoTransport() if self.verbose>=3 then local text="" for i,_transport in pairs(self.cargoqueue) do - local transport=_transport --#Ops.OpsTransport#OPSTRANSPORT - - local pickupname=transport.pickupzone and transport.pickupzone:GetName() or "unknown" - local deployname=transport.deployzone and transport.deployzone:GetName() or "unknown" + local transport=_transport --#Ops.OpsTransport#OPSTRANSPORT + local pickupzone=transport:GetPickupZone() + local deployzone=transport:GetDeployZone() + local pickupname=pickupzone and pickupzone:GetName() or "unknown" + local deployname=deployzone and deployzone:GetName() or "unknown" text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport:GetState(), pickupname, deployname) for j,_cargo in pairs(transport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup @@ -5520,38 +5565,18 @@ function OPSGROUP:_CheckCargoTransport() -- Now handle the transport. if self.cargoTransport then - -- Debug info. - if self.verbose>=2 then - local pickupname=self.cargoTransport.pickupzone and self.cargoTransport.pickupzone:GetName() or "unknown" - local deployname=self.cargoTransport.deployzone and self.cargoTransport.deployzone:GetName() or "unknown" - local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, pickupname, deployname) - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - local name=cargo.opsgroup:GetName() - local gstatus=cargo.opsgroup:GetState() - local cstatus=cargo.opsgroup.cargoStatus - local weight=cargo.opsgroup:GetWeightTotal() - local carriergroup, carrierelement, reserved=cargo.opsgroup:_GetMyCarrier() - local carrierGroupname=carriergroup and carriergroup.groupname or "none" - local carrierElementname=carrierelement and carrierelement.name or "none" - text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s", name, weight, gstatus, cstatus, carrierElementname, carrierGroupname, tostring(cargo.delivered)) - end - self:I(self.lid..text) - end - - if self:IsNotCarrier() then -- Debug info. self:T(self.lid.."Not carrier ==> pickup?") - -- Get transport zones. - local transportZone=self.cargoTransport:_GetPickupZone(self) + -- Get transport zone combo (TZC). + self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self) - if transportZone then + if self.cargoTZC then -- Initiate the cargo transport process. - self:__Pickup(-1, transportZone) + self:__Pickup(-1) else self:T(self.lid.."Not carrier ==> pickup") @@ -5573,7 +5598,7 @@ function OPSGROUP:_CheckCargoTransport() local boarding=false local gotcargo=false - for _,_cargo in pairs(self.cargoTransport.cargos) do + for _,_cargo in pairs(self.cargoTZC.Cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup -- Check if anyone is still boarding. @@ -5589,7 +5614,7 @@ function OPSGROUP:_CheckCargoTransport() end -- Boarding finished ==> Transport cargo. - if gotcargo and self.cargoTransport:_CheckRequiredCargos() and not boarding then + if gotcargo and self.cargoTransport:_CheckRequiredCargos(self.cargoTZC) and not boarding then self:T(self.lid.."Boarding finished ==> Loaded") self:Loaded() else @@ -5613,7 +5638,7 @@ function OPSGROUP:_CheckCargoTransport() self:T(self.lid.."Unloading ==> Checking if all cargo was delivered") local delivered=true - for _,_cargo in pairs(self.cargoTransport.cargos) do + for _,_cargo in pairs(self.cargoTZC.Cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup local carrierGroup=cargo.opsgroup:_GetMyCarrierGroup() @@ -5629,12 +5654,33 @@ function OPSGROUP:_CheckCargoTransport() -- Unloading finished ==> pickup next batch or call it a day. if delivered then self:T(self.lid.."Unloading finished ==> UnloadingDone") - self:UnloadingDone() + self:__UnloadingDone(10) else self:Unloading() end end + + -- Debug info. (At this point, we might not have a current cargo transport ==> hence the check) + if self.verbose>=2 and self.cargoTransport then + local pickupzone=self.cargoTransport:GetPickupZone(self.cargoTZC) + local deployzone=self.cargoTransport:GetDeployZone(self.cargoTZC) + local pickupname=pickupzone and pickupzone:GetName() or "unknown" + local deployname=deployzone and deployzone:GetName() or "unknown" + local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, pickupname, deployname) + for _,_cargo in pairs(self.cargoTransport:GetCargos(self.cargoTZC)) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + local name=cargo.opsgroup:GetName() + local gstatus=cargo.opsgroup:GetState() + local cstatus=cargo.opsgroup.cargoStatus + local weight=cargo.opsgroup:GetWeightTotal() + local carriergroup, carrierelement, reserved=cargo.opsgroup:_GetMyCarrier() + local carrierGroupname=carriergroup and carriergroup.groupname or "none" + local carrierElementname=carrierelement and carrierelement.name or "none" + text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s", name, weight, gstatus, cstatus, carrierElementname, carrierGroupname, tostring(cargo.delivered)) + end + self:I(self.lid..text) + end end @@ -5851,28 +5897,33 @@ end function OPSGROUP:_CheckGoPickup(CargoTransport) local done=true - for _,_cargo in pairs(CargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - if self:CanCargo(cargo.opsgroup) then - - if cargo.delivered then - -- This one is delivered. - elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then - -- This one is dead. - elseif cargo.opsgroup:IsLoaded(CargoTransport:_GetCarrierNames()) then - -- This one is loaded into a(nother) carrier. - else - done=false --Someone is not done! + + if CargoTransport then + + for _,_cargo in pairs(CargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if self:CanCargo(cargo.opsgroup) then + + if cargo.delivered then + -- This one is delivered. + elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + -- This one is dead. + elseif cargo.opsgroup:IsLoaded(CargoTransport:_GetCarrierNames()) then + -- This one is loaded into a(nother) carrier. + else + done=false --Someone is not done! + end + end - + end - + + -- Debug info. + self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport:GetState(), tostring(done))) + end - -- Debug info. - self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport:GetState(), tostring(done))) - return done end @@ -6067,7 +6118,7 @@ function OPSGROUP:GetWeightCargo(UnitName, IncludeReserved) end -- Debug info. - self:T2(self.lid..string.format("Unit=%s (reserved=%s): weight=%d, gewicht=%d", tostring(UnitName), tostring(IncludeReserved), weight, gewicht)) + self:T3(self.lid..string.format("Unit=%s (reserved=%s): weight=%d, gewicht=%d", tostring(UnitName), tostring(IncludeReserved), weight, gewicht)) -- Quick check. if IncludeReserved==false and gewicht~=weight then @@ -6281,29 +6332,29 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsTransport#OPSTRANSPORT.TransportZone TransportZone The transport zones data table. -function OPSGROUP:onafterPickup(From, Event, To, TransportZone) +function OPSGROUP:onafterPickup(From, Event, To) -- Set carrier status. self:_NewCarrierStatus(OPSGROUP.CarrierStatus.PICKUP) - self.transportZone=TransportZone + local TZC=self.cargoTZC -- Pickup zone. - local Zone=TransportZone.PickupZone + local Zone=TZC.PickupZone -- Check if already in the pickup zone. local inzone=self:IsInZone(Zone) - local airbasePickup=nil --Wrapper.Airbase#AIRBASE - if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then - airbasePickup=Zone:GetAirbase() - end + -- Pickup at an airbase. + local airbasePickup=TZC.PickupAirbase --Wrapper.Airbase#AIRBASE -- Check if group is already ready for loading. local ready4loading=false if self:IsArmygroup() or self:IsNavygroup() then + + -- Army and Navy groups just need to be inside the zone. ready4loading=inzone + else -- Aircraft is already parking at the pickup airbase. @@ -6315,6 +6366,7 @@ function OPSGROUP:onafterPickup(From, Event, To, TransportZone) end end + -- Ready for loading? if ready4loading then -- We are already in the pickup zone ==> wait and initiate loading. @@ -6350,16 +6402,14 @@ function OPSGROUP:onafterPickup(From, Event, To, TransportZone) -- Activate uncontrolled group. if self:IsParking() and self:IsUncontrolled() then - self:StartUncontrolled(1) + self:StartUncontrolled() end - else - - -- Order group to land at an airbase. - self:LandAtAirbase(airbasePickup) - end + -- Order group to land at an airbase. + self:__LandAtAirbase(-0.5, airbasePickup) + elseif self.isHelo then --- @@ -6375,10 +6425,10 @@ function OPSGROUP:onafterPickup(From, Event, To, TransportZone) local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, self.altitudeCruise, false) ; waypoint.detour=1 else - self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") + self:E(self.lid.."ERROR: Transportcarrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") end - elseif self.isNavygroup then + elseif self:IsNavygroup() then --- -- Navy Group @@ -6388,7 +6438,7 @@ function OPSGROUP:onafterPickup(From, Event, To, TransportZone) local uid=cwp and cwp.uid or nil -- Get a (random) pre-defined transport path. - local path=self.cargoTransport:_GetPathPickup() + local path=self.cargoTransport:_GetPathPickup(self.cargoTZC) if path then -- Loop over coordinates. @@ -6406,7 +6456,7 @@ function OPSGROUP:onafterPickup(From, Event, To, TransportZone) self:__Cruise(-2) - elseif self.isArmygroup then + elseif self:IsArmygroup() then --- -- Army Group @@ -6416,14 +6466,13 @@ function OPSGROUP:onafterPickup(From, Event, To, TransportZone) local uid=cwp and cwp.uid or nil -- Get a (random) pre-defined transport path. - local path=self.cargoTransport:_GetPathPickup() + local path=self.cargoTransport:_GetPathPickup(self.cargoTZC) if path then -- Loop over coordinates. for i,coordinate in pairs(path) do local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true uid=waypoint.uid - --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) end end @@ -6455,8 +6504,11 @@ function OPSGROUP:onafterLoading(From, Event, To) --TODO: sort cargos wrt weight. + -- Cargo group table. + local cargos=self.cargoTZC.Cargos + -- Loop over all cargos. - for _,_cargo in pairs(self.cargoTransport.cargos) do + for _,_cargo in pairs(cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup -- Check that cargo weight is @@ -6467,8 +6519,7 @@ function OPSGROUP:onafterLoading(From, Event, To) if cargo.opsgroup:IsNotCargo(true) and not (cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading()) then -- Check if cargo is in embark/pickup zone. - --local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) - local inzone=cargo.opsgroup:IsInZone(self.transportZone.EmbarkZone) + local inzone=cargo.opsgroup:IsInZone(self.cargoTZC.EmbarkZone) -- Cargo MUST be inside zone or it will not be loaded! if inzone then @@ -6486,12 +6537,12 @@ function OPSGROUP:onafterLoading(From, Event, To) else -- Debug info. - self:T(self.lid..string.format("Cannot board carrier! Group %s is NOT (yet) in zone %s", cargo.opsgroup:GetName(), self.cargoTransport.embarkzone:GetName())) + self:T(self.lid..string.format("Cannot board carrier! Group %s is NOT (yet) in zone %s", cargo.opsgroup:GetName(), self.cargoTZC.EmbarkZone:GetName())) end else -- Debug info. - self:T(self.lid..string.format("Cargo %s NOT in embark zone %s", cargo.opsgroup:GetName(), self.cargoTransport.embarkzone:GetName())) + self:T(self.lid..string.format("Cargo %s NOT in embark zone %s", cargo.opsgroup:GetName(), self.cargoTZC.EmbarkZone:GetName())) end end @@ -6653,15 +6704,13 @@ function OPSGROUP:onafterTransport(From, Event, To) --TODO: This is all very similar to the onafterPickup() function. Could make it general. -- Deploy zone. - local Zone=self.transportZone.DeployZone + local Zone=self.cargoTZC.DeployZone -- Check if already in deploy zone. local inzone=self:IsInZone(Zone) --Zone:IsCoordinateInZone(self:GetCoordinate()) - local airbaseDeploy=nil --Wrapper.Airbase#AIRBASE - if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then - airbaseDeploy=Zone:GetAirbase() - end + -- Deploy airbase (if any). + local airbaseDeploy=self.cargoTZC.DeployAirbase --Wrapper.Airbase#AIRBASE -- Check if group is already ready for loading. local ready2deploy=false @@ -6679,7 +6728,7 @@ function OPSGROUP:onafterTransport(From, Event, To) if inzone then - -- We are already in the pickup zone ==> wait and initiate unloading. + -- We are already in the deploy zone ==> wait and initiate unloading. if (self:IsArmygroup() or self:IsNavygroup()) and not self:IsHolding() then self:FullStop() end @@ -6693,6 +6742,7 @@ function OPSGROUP:onafterTransport(From, Event, To) local Coordinate=nil --Core.Point#COORDINATE if self.cargoTransport.carrierGroup and self.cargoTransport.carrierGroup:IsLoading() then + --TODO: What the heck is self.cargoTransport.carrierGroup and where is this set?! -- Coordinate of the new carrier. Coordinate=self.cargoTransport.carrierGroup:GetCoordinate() else @@ -6701,14 +6751,14 @@ function OPSGROUP:onafterTransport(From, Event, To) end -- Add waypoint. - if self.isFlightgroup then + if self:IsFlightgroup() then + if airbaseDeploy then + --- -- Deploy at airbase --- - - if airbaseDeploy then - + local airbaseCurrent=self.currbase if airbaseCurrent then @@ -6718,10 +6768,10 @@ function OPSGROUP:onafterTransport(From, Event, To) self:StartUncontrolled() end - else - -- Order group to land at an airbase. - self:LandAtAirbase(airbaseDeploy) end + + -- Order group to land at an airbase. + self:__LandAtAirbase(-0.1, airbaseDeploy) elseif self.isHelo then @@ -6735,28 +6785,26 @@ function OPSGROUP:onafterTransport(From, Event, To) -- Cancel landedAt task. This should trigger Cruise once airborne. if self:IsFlightgroup() and self:IsLandedAt() then local Task=self:GetTaskCurrent() - --if Task and Task.description=="Task_Landed_At" then - self:__TaskCancel(5, Task) - --end + self:__TaskCancel(5, Task) end else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") end - elseif self.isArmygroup then + elseif self:IsArmygroup() then local cwp=self:GetWaypointCurrent() local uid=cwp and cwp.uid or nil - local path=self.cargoTransport:_GetPathTransport() + -- Get transport path. + local path=self.cargoTransport:_GetPathTransport(self.cargoTZC) if path then -- Loop over coordinates. for i,coordinate in pairs(path) do local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true uid=waypoint.uid - --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) end end @@ -6766,20 +6814,19 @@ function OPSGROUP:onafterTransport(From, Event, To) -- Give cruise command. self:Cruise() - elseif self.isNavygroup then + elseif self:IsNavygroup() then local cwp=self:GetWaypointCurrent() local uid=cwp and cwp.uid or nil -- Get a (random) pre-defined transport path. - local path=self.cargoTransport:_GetPathTransport() + local path=self.cargoTransport:_GetPathTransport(self.cargoTZC) if path then -- Loop over coordinates. for i,coordinate in pairs(path) do local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true uid=waypoint.uid - --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) end end @@ -6806,9 +6853,9 @@ function OPSGROUP:onafterUnloading(From, Event, To) self:_NewCarrierStatus(OPSGROUP.CarrierStatus.UNLOADING) -- Deploy zone. - local zone=self.cargoTransport.disembarkzone or self.cargoTransport.deployzone --Core.Zone#ZONE + local zone=self.cargoTZC.DisembarkZone or self.cargoTZC.DeployZone --Core.Zone#ZONE - for _,_cargo in pairs(self.cargoTransport.cargos) do + for _,_cargo in pairs(self.cargoTZC.Cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup -- Check that cargo is loaded into this group. @@ -6821,11 +6868,11 @@ function OPSGROUP:onafterUnloading(From, Event, To) local carrierGroup=nil - if self.cargoTransport.disembarkCarriers and #self.cargoTransport.disembarkCarriers>0 then + if self.cargoTZC.DisembarkCarriers and #self.cargoTZC.DisembarkCarriers>0 then needscarrier=true - carrier, carrierGroup=self.cargoTransport:FindTransferCarrierForCargo(cargo.opsgroup, zone) + carrier, carrierGroup=self.cargoTransport:FindTransferCarrierForCargo(cargo.opsgroup, zone, self.cargoTZC) --TODO: max unloading time if transfer carrier does not arrive in the zone. @@ -6863,7 +6910,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) -- Delivered to deploy zone --- - if self.cargoTransport.disembarkInUtero then + if self.cargoTransport:GetDisembarkInUtero(self.cargoTZC) then -- Unload but keep "in utero" (no coordinate provided). self:Unload(cargo.opsgroup) @@ -6872,10 +6919,10 @@ function OPSGROUP:onafterUnloading(From, Event, To) local Coordinate=nil - if self.cargoTransport.disembarkzone then + if self.cargoTransport:GetDisembarkZone(self.cargoTZC) then -- Random coordinate in disembark zone. - Coordinate=self.cargoTransport.disembarkzone:GetRandomCoordinate() + Coordinate=self.cargoTransport:GetDisembarkZone(self.cargoTZC):GetRandomCoordinate() else @@ -6891,7 +6938,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) local Heading=math.random(0,359) -- Unload to Coordinate. - self:Unload(cargo.opsgroup, Coordinate, self.cargoTransport.disembarkActivation, Heading) + self:Unload(cargo.opsgroup, Coordinate, self.cargoTransport:GetDisembarkActivation(self.cargoTZC), Heading) end @@ -7050,7 +7097,7 @@ function OPSGROUP:onafterUnloadingDone(From, Event, To) -- Cancel landedAt task. if self:IsFlightgroup() and self:IsLandedAt() then local Task=self:GetTaskCurrent() - self:TaskCancel(Task) + self:__TaskCancel(5, Task) end -- Check everything was delivered (or is dead). @@ -7058,16 +7105,22 @@ function OPSGROUP:onafterUnloadingDone(From, Event, To) if not delivered then - local transportZone=self.cargoTransport:_GetPathPickup() + self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self) - if transportZone then + if self.cargoTZC then -- Pickup the next batch. self:I(self.lid.."Unloaded: Still cargo left ==> Pickup") - self:Pickup(transportZone) + self:Pickup() else - env.info("FF error not implemented case!") + + -- Debug info. + self:I(self.lid..string.format("WARNING: Not all cargo was delivered but could not get a transport zone combo ==> setting carrier state to NOT CARRIER")) + + -- This is not a carrier anymore. + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.NOTCARRIER) + end else @@ -7128,6 +7181,7 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- No current transport any more. self.cargoTransport=nil + self.cargoTZC=nil end -- Remove cargo transport from cargo queue. @@ -7531,8 +7585,8 @@ end -- @param #OPSGROUP self function OPSGROUP:_CheckStuck() - -- Holding means we are not stuck. - if self:IsHolding() or self:Is("Rearming") then + -- Cases we are not stuck. + if self:IsHolding() or self:Is("Rearming") or self:IsWaiting() then return end @@ -7569,6 +7623,9 @@ function OPSGROUP:_CheckStuck() -- Debug warning. self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec", speed, ExpectedSpeed, holdtime)) + + -- Give cruise command again. + self:__Cruise(1) --TODO: Stuck event! @@ -7916,13 +7973,16 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) -- Get home and destination airbases from waypoints. self.homebase=self.homebase or self:GetHomebaseFromWaypoints() - self.destbase=self.destbase or self:GetDestinationFromWaypoints() + local destbase=self:GetDestinationFromWaypoints() + self.destbase=self.destbase or destbase self.currbase=self:GetHomebaseFromWaypoints() -- Remove the landing waypoint. We use RTB for that. It makes adding new waypoints easier as we do not have to check if the last waypoint is the landing waypoint. - if self.destbase and #self.waypoints>1 then + if destbase and #self.waypoints>1 then table.remove(self.waypoints, #self.waypoints) - else + end + + if self.destbase==nil then self.destbase=self.homebase end @@ -8051,7 +8111,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Get next waypoint. Tricky part is that if local wpnext=opsgroup:GetWaypointNext() - if wpnext and (opsgroup.currentwp<#opsgroup.waypoints or opsgroup.adinfinitum or wpistemp) then + if wpnext then --and (opsgroup.currentwp<#opsgroup.waypoints or opsgroup.adinfinitum or wpistemp) -- Debug info. opsgroup:T(opsgroup.lid..string.format("Next waypoint UID=%d index=%d", wpnext.uid, opsgroup:GetWaypointIndex(wpnext.uid))) @@ -8074,6 +8134,12 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) opsgroup:_PassedFinalWaypoint(true, "_PassingWaypoint No next Waypoint found") end + + -- Check if final waypoint was reached. + if opsgroup.currentwp==#opsgroup.waypoints and not opsgroup.adinfinitum then + -- Set passed final waypoint. + opsgroup:_PassedFinalWaypoint(true, "_PassingWaypoint currentwp==#waypoints and NOT adinfinitum") + end -- Trigger PassingWaypoint event. if waypoint.temp then @@ -8122,9 +8188,13 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) if opsgroup:IsFlightgroup() then -- Land at current pos and wait for 60 min max. - env.info("FF LandAt for Pickup") - --opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) - opsgroup:LandAt(opsgroup.cargoTransport.pickupzone:GetCoordinate(), 60*60) + local coordinate=nil + if opsgroup.cargoTZC then + coordinate=opsgroup.cargoTZC.PickupZone:GetCoordinate() + else + coordinate=opsgroup:GetCoordinate() + end + opsgroup:LandAt(coordinate, 60*60) else @@ -8136,11 +8206,16 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif opsgroup:IsTransporting() then - if opsgroup.isFlightgroup then + if opsgroup:IsFlightgroup() then -- Land at current pos and wait for 60 min max. - env.info("FF LandAt for Transporting") - opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) + local coordinate=nil + if opsgroup.cargoTZC then + coordinate=opsgroup.cargoTZC.DeployZone:GetCoordinate() + else + coordinate=opsgroup:GetCoordinate() + end + opsgroup:LandAt(coordinate, 60*60) else -- Stop and unload. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 5d0cd8b7e..cd9357650 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -29,8 +29,7 @@ -- @field #string lid Log ID. -- @field #number uid Unique ID of the transport. -- @field #number verbose Verbosity level. --- @field #table cargos Cargos. Each element is a @{Ops.OpsGroup#OPSGROUP.CargoGroup}. --- @field #table carriers Carriers assigned for this transport. +-- -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). -- @field #boolean urgent If true, transport is urgent. -- @field #number importance Importance of this transport. Smaller=higher. @@ -38,22 +37,21 @@ -- @field #number Tstop Stop time in *abs.* seconds. Default `#nil` (never stops). -- @field #number duration Duration (`Tstop-Tstart`) of the transport in seconds. -- @field #table conditionStart Start conditions. --- @field #table transportZones Table of transport zones. Each element of the table is of type `#OPSTRANSPORT.TransportZone`. --- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. --- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. --- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. --- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. --- @field #boolean disembarkActivation Activation setting when group is disembared from carrier. --- @field #boolean disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. --- @field #table disembarkCarriers Table of carriers to which the cargo is disembared. This is a direct transfer from the old to the new carrier. --- @field #table requiredCargos Table of cargo groups that must be loaded before the first transport is started. +-- +-- @field #table cargos Cargos. Each element is a @{Ops.OpsGroup#OPSGROUP.CargoGroup}. +-- @field #table carriers Carriers assigned for this transport. +-- +-- @field #table tzCombos Table of transport zone combos. Each element of the table is of type `#OPSTRANSPORT.TransportZoneCombo`. +-- @field #number tzcCounter Running number of added transport zone combos. +-- @field #OPSTRANSPORT.TransportZoneCombo tzcDefault Default transport zone combo. +-- -- @field #number Ncargo Total number of cargo groups. -- @field #number Ncarrier Total number of assigned carriers. -- @field #number Ndelivered Total number of cargo groups delivered. --- @field #table pathsTransport Transport paths of `#OPSGROUP.Path`. --- @field #table pathsPickup Pickup paths of `#OPSGROUP.Path`. +-- -- @field Ops.Auftrag#AUFTRAG mission The mission attached to this transport. -- @field #table assets Warehouse assets assigned for this transport. +-- -- @extends Core.Fsm#FSM --- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill @@ -113,15 +111,13 @@ -- @field #OPSTRANSPORT OPSTRANSPORT = { ClassName = "OPSTRANSPORT", - verbose = 0, - cargos = {}, - carriers = {}, + verbose = 0, + cargos = {}, + carriers = {}, carrierTransportStatus = {}, - transportZones = {}, + tzCombos = {}, + tzcCounter = 0, conditionStart = {}, - pathsTransport = {}, - pathsPickup = {}, - requiredCargos = {}, assets = {}, } @@ -143,13 +139,23 @@ OPSTRANSPORT.Status={ } --- Pickup and deploy set. --- @type OPSTRANSPORT.TransportZone +-- @type OPSTRANSPORT.TransportZoneCombo +-- @field #number uid Unique ID of the TZ combo. +-- @field #number Ncarriers Number of carrier groups using this transport zone. +-- @field #number Ncargo Number of cargos assigned. This is a running number and *not* decreased if cargo is delivered or dead. +-- @field #table Cargos Cargo groups of the TZ combo. Each element is of type `Ops.OpsGroup#OPSGROUP.CargoGroup`. -- @field Core.Zone#ZONE PickupZone Pickup zone. -- @field Core.Zone#ZONE DeployZone Deploy zone. -- @field Core.Zone#ZONE EmbarkZone Embark zone if different from pickup zone. --- @field #OPSTRANSPORT.Path PickupPath Path for pickup. --- @field #OPSTRANSPORT.Path TransportPath Path for Transport. --- @field #numberr Ncarriers Number of carrier groups using this transport zone. +-- @field Core.Zone#ZONE DisembarkZone Zone where the troops are disembared to. +-- @field Wrapper.Airbase#AIRBASE PickupAirbase Airbase for pickup. +-- @field Wrapper.Airbase#AIRBASE DeployAirbase Airbase for deploy. +-- @field #table PickupPaths Paths for pickup. +-- @field #table TransportPaths Path for Transport. Each elment of the table is of type `#OPSTRANSPORT.Path`. +-- @field #table RequiredCargos Required cargos. +-- @field #table DisembarkCarriers Carriers where the cargo is directly disembarked to. +-- @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". --- Path used for pickup or transport. -- @type OPSTRANSPORT.Path @@ -167,7 +173,7 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.3.0" +OPSTRANSPORT.version="0.4.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -184,11 +190,11 @@ OPSTRANSPORT.version="0.3.0" --- Create a new OPSTRANSPORT class object. Essential input are the troops that should be transported and the zones where the troops are picked up and deployed. -- @param #OPSTRANSPORT self --- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! --- @param Core.Zone#ZONE Deployzone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. +-- @param Core.Set#SET_GROUP CargoGroups Groups to be transported as cargo. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. +-- @param Core.Zone#ZONE PickupZone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! +-- @param Core.Zone#ZONE DeployZone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) +function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #OPSTRANSPORT @@ -201,26 +207,21 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- UID of this transport. self.uid=_OPSTRANSPORTID - + -- Defaults. - self:SetPickupZone(Pickupzone) - self:SetDeployZone(Deployzone) - self:SetEmbarkZone() -- Default is pickup zone. self.cargos={} self.carriers={} + self.Ncargo=0 self.Ncarrier=0 self.Ndelivered=0 self:SetPriority() self:SetTime() - - -- Add cargo groups (could also be added later). - if GroupSet then - self:AddCargoGroups(GroupSet) - end - + -- Set default TZC. + self.tzcDefault=self:AddTransportZoneCombo(PickupZone, DeployZone, CargoGroups) + -- FMS start state is PLANNED. self:SetStartState(OPSTRANSPORT.Status.PLANNED) @@ -255,11 +256,54 @@ end -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Add pickup and deploy zone combination. +-- @param #OPSTRANSPORT self +-- @param Core.Zone#ZONE PickupZone Zone where the troops are picked up. +-- @param Core.Zone#ZONE DeployZone Zone where the troops are picked up. +-- @param Core.Set#SET_GROUP CargoGroups Groups to be transported as cargo. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. +-- @return #OPSTRANSPORT.TransportZoneCombo Transport zone table. +function OPSTRANSPORT:AddTransportZoneCombo(PickupZone, DeployZone, CargoGroups) + + -- Increase counter. + self.tzcCounter=self.tzcCounter+1 + + local tzcombo={} --#OPSTRANSPORT.TransportZoneCombo + + -- Init. + tzcombo.uid=self.tzcCounter + tzcombo.Ncarriers=0 + tzcombo.Ncargo=0 + tzcombo.Cargos={} + tzcombo.RequiredCargos={} + tzcombo.DisembarkCarriers={} + tzcombo.PickupPaths={} + tzcombo.TransportPaths={} + + -- Set zones. + self:SetPickupZone(PickupZone, tzcombo) + self:SetDeployZone(DeployZone, tzcombo) + self:SetEmbarkZone(nil, tzcombo) + + -- Add cargo groups (could also be added later). + if CargoGroups then + self:AddCargoGroups(CargoGroups, tzcombo) + end + + -- Add to table. + table.insert(self.tzCombos, tzcombo) + + return tzcombo +end + --- Add cargo groups to be transported. -- @param #OPSTRANSPORT self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be passed as a single GROUP or OPSGROUP object. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:AddCargoGroups(GroupSet) +function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault -- Check type of GroupSet provided. if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then @@ -268,8 +312,13 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) local cargo=self:_CreateCargoGroupData(GroupSet) if cargo then + -- Add to main table. table.insert(self.cargos, cargo) self.Ncargo=self.Ncargo+1 + + -- Add to TZC table. + table.insert(TransportZoneCombo.Cargos, cargo) + TransportZoneCombo.Ncargo=TransportZoneCombo.Ncargo+1 end else @@ -281,8 +330,13 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) local cargo=self:_CreateCargoGroupData(group) if cargo then + -- Add to main table. table.insert(self.cargos, cargo) self.Ncargo=self.Ncargo+1 + + -- Add to TZC table. + table.insert(TransportZoneCombo.Cargos, cargo) + TransportZoneCombo.Ncargo=TransportZoneCombo.Ncargo+1 end end @@ -306,97 +360,174 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) return self end ---- Add pickup and deploy zone combination. Optionally, embark and disembark zones can be specified. --- --- * The pickup zone is a zone where --- * bla --- --- @param #OPSTRANSPORT self --- @param Core.Zone#ZONE PickupZone Zone where the troops are picked up. --- @return #OPSTRANSPORT.TransportZone Transport zone table. -function OPSTRANSPORT:AddTransportZones(PickupZone, DeployZone, EmbarkZone, DisembarkZone, PickupPath, TransportPath) - - local transport={} --#OPSTRANSPORT.TransportZone - - transport.PickupZone=PickupZone - transport.DeployZone=DeployZone - transport.EmbarkZone=EmbarkZone or PickupZone - transport.DisembarkZone=DisembarkZone - transport.PickupPath=PickupPath - transport.TransportPath=TransportPath - transport.Ncarriers=0 - - table.insert(self.transportZones, transport) - - return transport -end --- Set pickup zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE PickupZone Zone where the troops are picked up. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:SetPickupZone(PickupZone) - self.pickupzone=PickupZone +function OPSTRANSPORT:SetPickupZone(PickupZone, TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + TransportZoneCombo.PickupZone=PickupZone + + if PickupZone and PickupZone:IsInstanceOf("ZONE_AIRBASE") then + TransportZoneCombo.PickupAirbase=PickupZone._.ZoneAirbase + end + return self end +--- Get pickup zone. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return Core.Zone#ZONE Zone where the troops are picked up. +function OPSTRANSPORT:GetPickupZone(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + return TransportZoneCombo.PickupZone +end + --- Set deploy zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE DeployZone Zone where the troops are deployed. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:SetDeployZone(DeployZone) - self.deployzone=DeployZone +function OPSTRANSPORT:SetDeployZone(DeployZone, TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + -- Set deploy zone. + TransportZoneCombo.DeployZone=DeployZone + + -- Check if this is an airbase. + if DeployZone and DeployZone:IsInstanceOf("ZONE_AIRBASE") then + TransportZoneCombo.DeployAirbase=DeployZone._.ZoneAirbase + end + return self end +--- Get deploy zone. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return Core.Zone#ZONE Zone where the troops are deployed. +function OPSTRANSPORT:GetDeployZone(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + return TransportZoneCombo.DeployZone +end + --- Set embark zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE EmbarkZone Zone where the troops are embarked. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:SetEmbarkZone(EmbarkZone) - self.embarkzone=EmbarkZone or self.pickupzone +function OPSTRANSPORT:SetEmbarkZone(EmbarkZone, TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + TransportZoneCombo.EmbarkZone=EmbarkZone or TransportZoneCombo.PickupZone + return self end +--- Get embark zone. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return Core.Zone#ZONE Zone where the troops are embarked from. +function OPSTRANSPORT:GetEmbarkZone(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + return TransportZoneCombo.EmbarkZone +end + --- Set disembark zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE DisembarkZone Zone where the troops are disembarked. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:SetDisembarkZone(DisembarkZone) - self.disembarkzone=DisembarkZone +function OPSTRANSPORT:SetDisembarkZone(DisembarkZone, TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + TransportZoneCombo.DisembarkZone=DisembarkZone + return self end +--- Get disembark zone. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return Core.Zone#ZONE Zone where the troops are disembarked to. +function OPSTRANSPORT:GetDisembarkZone(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + return TransportZoneCombo.DisembarkZone +end + --- Set activation status of group when disembarked from transport carrier. -- @param #OPSTRANSPORT self -- @param #boolean Active If `true` or `nil`, group is activated when disembarked. If `false`, group is late activated and needs to be activated manually. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:SetDisembarkActivation(Active) +function OPSTRANSPORT:SetDisembarkActivation(Active, TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + if Active==true or Active==nil then - self.disembarkActivation=true + TransportZoneCombo.disembarkActivation=true else - self.disembarkActivation=false - end + TransportZoneCombo.disembarkActivation=false + end + return self end +--- Get disembark activation. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #boolean If `true`, groups are spawned in late activated state. +function OPSTRANSPORT:GetDisembarkActivation(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + return TransportZoneCombo.disembarkActivation +end + --- Set transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. -- @param #OPSTRANSPORT self -- @param Core.Set#SET_GROUP Carriers Carrier set. Can also be passed as a #GROUP, #OPSGROUP or #SET_OPSGROUP object. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:SetDisembarkCarriers(Carriers) +function OPSTRANSPORT:SetDisembarkCarriers(Carriers, TransportZoneCombo) -- Debug info. self:T(self.lid.."Setting transfer carriers!") - - -- Create table. - self.disembarkCarriers=self.disembarkCarriers or {} + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault if Carriers:IsInstanceOf("GROUP") or Carriers:IsInstanceOf("OPSGROUP") then local carrier=self:_GetOpsGroupFromObject(Carriers) if carrier then - table.insert(self.disembarkCarriers, carrier) + table.insert(TransportZoneCombo.DisembarkCarriers, carrier) end elseif Carriers:IsInstanceOf("SET_GROUP") or Carriers:IsInstanceOf("SET_OPSGROUP") then @@ -404,7 +535,7 @@ function OPSTRANSPORT:SetDisembarkCarriers(Carriers) for _,object in pairs(Carriers:GetSet()) do local carrier=self:_GetOpsGroupFromObject(object) if carrier then - table.insert(self.disembarkCarriers, carrier) + table.insert(TransportZoneCombo.DisembarkCarriers, carrier) end end @@ -415,38 +546,72 @@ function OPSTRANSPORT:SetDisembarkCarriers(Carriers) return self end +--- Get transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #table Table of carriers. +function OPSTRANSPORT:GetDisembarkCarriers(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + return TransportZoneCombo.DisembarkCarriers +end + --- Set if group remains *in utero* after disembarkment from carrier. Can be used to directly load the group into another carrier. Similar to disembark in late activated state. -- @param #OPSTRANSPORT self -- @param #boolean InUtero If `true` or `nil`, group remains *in utero* after disembarkment. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:SetDisembarkInUtero(InUtero) +function OPSTRANSPORT:SetDisembarkInUtero(InUtero, TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + if InUtero==true or InUtero==nil then - self.disembarkInUtero=true + TransportZoneCombo.disembarkInUtero=true else - self.disembarkInUtero=false - end + TransportZoneCombo.disembarkInUtero=false + end + return self end +--- Get disembark in utero. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #boolean If `true`, groups stay in utero after disembarkment. +function OPSTRANSPORT:GetDisembarkInUtero(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + return TransportZoneCombo.disembarkInUtero +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 -- @param Core.Set#SET_GROUP Cargos Required cargo set. Can also be passed as a #GROUP, #OPSGROUP or #SET_OPSGROUP object. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:SetRequiredCargos(Cargos) +function OPSTRANSPORT:SetRequiredCargos(Cargos, TransportZoneCombo) -- Debug info. self:T(self.lid.."Setting required cargos!") + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault -- Create table. - self.requiredCargos=self.requiredCargos or {} + TransportZoneCombo.RequiredCargos=TransportZoneCombo.RequiredCargos or {} if Cargos:IsInstanceOf("GROUP") or Cargos:IsInstanceOf("OPSGROUP") then local cargo=self:_GetOpsGroupFromObject(Cargos) if cargo then - table.insert(self.requiredCargos, cargo) + table.insert(TransportZoneCombo.RequiredCargos, cargo) end elseif Cargos:IsInstanceOf("SET_GROUP") or Cargos:IsInstanceOf("SET_OPSGROUP") then @@ -454,7 +619,7 @@ function OPSTRANSPORT:SetRequiredCargos(Cargos) for _,object in pairs(Cargos:GetSet()) do local cargo=self:_GetOpsGroupFromObject(object) if cargo then - table.insert(self.requiredCargos, cargo) + table.insert(TransportZoneCombo.RequiredCargos, cargo) end end @@ -465,6 +630,17 @@ function OPSTRANSPORT:SetRequiredCargos(Cargos) return self end +--- Get required cargos. This is a list of cargo groups that need to be loaded before the **first** transport will start. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #table Table of required cargo ops groups. +function OPSTRANSPORT:GetRequiredCargos(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + return TransportZoneCombo.RequiredCargos +end --- Add a carrier assigned for this transport. @@ -538,11 +714,14 @@ end -- @param #OPSTRANSPORT self -- @param #boolean Delivered If `true`, only delivered groups are returned. If `false` only undelivered groups are returned. If `nil`, all groups are returned. -- @param Ops.OpsGroup#OPSGROUP Carrier (Optional) Only count cargo groups that fit into the given carrier group. Current cargo is not a factor. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #table Cargo Ops groups. Can be and empty table `{}`. -function OPSTRANSPORT:GetCargoOpsGroups(Delivered, Carrier) +function OPSTRANSPORT:GetCargoOpsGroups(Delivered, Carrier, TransportZoneCombo) + + local cargos=self:GetCargos(TransportZoneCombo) local opsgroups={} - for _,_cargo in pairs(self.cargos) do + for _,_cargo in pairs(cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup if Delivered==nil or cargo.delivered==Delivered then if cargo.opsgroup and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then @@ -556,13 +735,26 @@ function OPSTRANSPORT:GetCargoOpsGroups(Delivered, Carrier) return opsgroups end ---- Get carrier @{Ops.OpsGroup#OPSGROUP}s. +--- Get carriers. -- @param #OPSTRANSPORT self -- @return #table Carrier Ops groups. -function OPSTRANSPORT:GetCarrierOpsGroups() +function OPSTRANSPORT:GetCarriers() return self.carriers end +--- Get cargos. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #table Cargos. +function OPSTRANSPORT:GetCargos(TransportZoneCombo) + + if TransportZoneCombo then + return TransportZoneCombo.Cargos + else + return self.cargos + end + +end --- Set transport start and stop time. -- @param #OPSTRANSPORT self @@ -652,8 +844,12 @@ end -- @param #boolean Reversed If `true`, add waypoints of group in reversed order. -- @param #number Radius Randomization radius in meters. Default 0 m. -- @param #number Altitude Altitude in feet AGL. Only for aircraft. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:AddPathTransport(PathGroup, Reversed, Radius, Altitude) +function OPSTRANSPORT:AddPathTransport(PathGroup, Reversed, Radius, Altitude, TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault local path={} --#OPSTRANSPORT.Path path.coords={} @@ -679,20 +875,26 @@ function OPSTRANSPORT:AddPathTransport(PathGroup, Reversed, Radius, Altitude) -- Add path. - table.insert(self.pathsTransport, path) + table.insert(TransportZoneCombo.TransportPaths, path) return self end --- Get a path for transportation. -- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. -- @return #table The path of COORDINATEs. -function OPSTRANSPORT:_GetPathTransport() +function OPSTRANSPORT:_GetPathTransport(TransportZoneCombo) - if self.pathsTransport and #self.pathsTransport>0 then + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + local pathsTransport=TransportZoneCombo.TransportPaths + + if pathsTransport and #pathsTransport>0 then -- Get a random path for transport. - local path=self.pathsTransport[math.random(#self.pathsTransport)] --#OPSTRANSPORT.Path + local path=pathsTransport[math.random(#pathsTransport)] --#OPSTRANSPORT.Path local coordinates={} @@ -718,8 +920,12 @@ end -- @param #boolean Reversed If `true`, add waypoints of group in reversed order. -- @param #number Radius Randomization radius in meters. Default 0 m. -- @param #number Altitude Altitude in feet AGL. Only for aircraft. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:AddPathPickup(PathGroup, Reversed, Radius, Altitude) +function OPSTRANSPORT:AddPathPickup(PathGroup, Reversed, Radius, Altitude, TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault local path={} --#OPSTRANSPORT.Path path.coords={} @@ -744,20 +950,26 @@ function OPSTRANSPORT:AddPathPickup(PathGroup, Reversed, Radius, Altitude) end -- Add path. - table.insert(self.pathsPickup, path) + table.insert(TransportZoneCombo.PickupPaths, path) return self end --- Get a path for pickup. -- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. -- @return #table The path of COORDINATEs. -function OPSTRANSPORT:_GetPathPickup() +function OPSTRANSPORT:_GetPathPickup(TransportZoneCombo) - if self.pathsPickup and #self.pathsPickup>0 then + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + local paths=TransportZoneCombo.PickupPaths + + if paths and #paths>0 then -- Get a random path for transport. - local path=self.pathsPickup[math.random(#self.pathsPickup)] --#OPSTRANSPORT.Path + local path=paths[math.random(#paths)] --#OPSTRANSPORT.Path local coordinates={} @@ -857,8 +1069,8 @@ function OPSTRANSPORT:IsReadyToGo() -- Pickup AND deploy zones must be set. local gotzones=false - for _,_tz in pairs(self.transportZones) do - local tz=_tz --#OPSTRANSPORT.TransportZone + for _,_tz in pairs(self.tzCombos) do + local tz=_tz --#OPSTRANSPORT.TransportZoneCombo if tz.PickupZone and tz.DeployZone then gotzones=true break @@ -961,8 +1173,8 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) -- Info about cargo and carrier. if self.verbose>=2 then - for i,_tz in pairs(self.transportZones) do - local tz=_tz --#OPSTRANSPORT.TransportZone + for i,_tz in pairs(self.tzCombos) do + local tz=_tz --#OPSTRANSPORT.TransportZoneCombo text=text..string.format("\n[%d] %s --> %s", i, tz.PickupZone and tz.PickupZone:GetName() or "Unknown", tz.DeployZone and tz.DeployZone and tz.DeployZone:GetName() or "Unknown", tz.Ncarriers) end @@ -1166,17 +1378,23 @@ end --- Check if all required cargos are loaded. -- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #boolean If true, all required cargos are loaded or there is no required cargo. -function OPSTRANSPORT:_CheckRequiredCargos() +function OPSTRANSPORT:_CheckRequiredCargos(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault - if self.requiredCargos==nil or #self.requiredCargos==0 then + local requiredCargos=TransportZoneCombo.RequiredCargos + + if requiredCargos==nil or #requiredCargos==0 then return true end local carrierNames=self:_GetCarrierNames() local gotit=true - for _,_cargo in pairs(self.requiredCargos) do + for _,_cargo in pairs(requiredCargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP @@ -1219,26 +1437,30 @@ end -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CargoGroup The cargo group that needs to be loaded into a carrier unit/element of the carrier group. -- @param Core.Zone#ZONE Zone (Optional) Zone where the carrier must be in. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return Ops.OpsGroup#OPSGROUP.Element New carrier element for cargo or nil. -- @return Ops.OpsGroup#OPSGROUP New carrier group for cargo or nil. -function OPSTRANSPORT:FindTransferCarrierForCargo(CargoGroup, Zone) +function OPSTRANSPORT:FindTransferCarrierForCargo(CargoGroup, Zone, TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault local carrier=nil --Ops.OpsGroup#OPSGROUP.Element local carrierGroup=nil --Ops.OpsGroup#OPSGROUP --TODO: maybe sort the carriers wrt to largest free cargo bay. Or better smallest free cargo bay that can take the cargo group weight. - for _,_carrier in pairs(self.disembarkCarriers or {}) do + for _,_carrier in pairs(TransportZoneCombo.DisembarkCarriers) do local carrierGroup=_carrier --Ops.OpsGroup#OPSGROUP -- First check if carrier is alive and loading cargo. - if carrierGroup and carrierGroup:IsAlive() and (carrierGroup:IsLoading() or self.deployzone:IsInstanceOf("ZONE_AIRBASE")) then + if carrierGroup and carrierGroup:IsAlive() and (carrierGroup:IsLoading() or TransportZoneCombo.DeployAirbase) then -- Find an element of the group that has enough free space. carrier=carrierGroup:FindCarrierForCargo(CargoGroup) if carrier then - if Zone==nil or Zone:IsCoordinateInZone(carrier.unit:GetCoordinate()) then + if Zone==nil or Zone:IsVec2InZone(carrier.unit:GetVec2()) then return carrier, carrierGroup else self:T2(self.lid.."Got transfer carrier but carrier not in zone (yet)!") @@ -1277,10 +1499,11 @@ end -- @param Core.Zone#ZONE Zone The zone object. -- @param #boolean Delivered If `true`, only delivered groups are returned. If `false` only undelivered groups are returned. If `nil`, all groups are returned. -- @param Ops.OpsGroup#OPSGROUP Carrier (Optional) Only count cargo groups that fit into the given carrier group. Current cargo is not a factor. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #number Number of cargo groups. -function OPSTRANSPORT:_CountCargosInZone(Zone, Delivered, Carrier) +function OPSTRANSPORT:_CountCargosInZone(Zone, Delivered, Carrier, TransportZoneCombo) - local cargos=self:GetCargoOpsGroups(Delivered, Carrier) + local cargos=self:GetCargoOpsGroups(Delivered, Carrier, TransportZoneCombo) local N=0 for _,_cargo in pairs(cargos) do @@ -1293,37 +1516,52 @@ function OPSTRANSPORT:_CountCargosInZone(Zone, Delivered, Carrier) return N end ---- Get a pickup zone for a carrier group. This will be a zone, where the most cargo groups are located that fit into the carrier. +--- Get a transport zone combination (TZC) for a carrier group. The pickup zone will be a zone, where the most cargo groups are located that fit into the carrier. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP Carrier The carrier OPS group. -- @return Core.Zone#ZONE Pickup zone or `#nil`. -function OPSTRANSPORT:_GetPickupZone(Carrier) +function OPSTRANSPORT:_GetTransportZoneCombo(Carrier) - env.info(string.format("FF GetPickupZone")) - local pickup=nil + --- Selection criteria + -- * Distance: pickup zone should be as close as possible. + -- * Ncargo: Number of cargo groups. Pickup, where there is most cargo. + -- * Ncarrier: Number of carriers already "working" on this TZC. Would be better if not all carriers work on the same combo while others are ignored. + -- Get carrier position. + local vec2=Carrier:GetVec2() + + local pickup=nil --#OPSTRANSPORT.TransportZoneCombo local distmin=nil - for i,_transportzone in pairs(self.transportZones) do - local tz=_transportzone --#OPSTRANSPORT.TransportZone + for i,_transportzone in pairs(self.tzCombos) do + local tz=_transportzone --#OPSTRANSPORT.TransportZoneCombo - -- Count cargos in pickup zone. - local ncargo=self:_CountCargosInZone(tz.PickupZone, false, Carrier) + -- Check that pickup and deploy zones were defined. + if tz.PickupZone and tz.DeployZone and tz.EmbarkZone then - env.info(string.format("FF GetPickupZone i=%d, ncargo=%d", i, ncargo)) - - if ncargo>0 then - - local vec2=Carrier:GetVec2() + -- Count undelivered cargos in embark(!) zone that fit into the carrier. + local ncargo=self:_CountCargosInZone(tz.EmbarkZone, false, Carrier, tz) - local dist=tz.PickupZone:Get2DDistance(vec2) - - if distmin==nil or dist0 then + + local dist=tz.PickupZone:Get2DDistance(vec2) + + if distmin==nil or dist Date: Wed, 1 Sep 2021 00:07:54 +0200 Subject: [PATCH 087/141] OPSTRANSPORT v0.4.1 - Fixed a couple of bugs - Lots of other fixes and improvements --- Moose Development/Moose/Core/Base.lua | 6 +- Moose Development/Moose/Core/Set.lua | 14 ++ Moose Development/Moose/Ops/ArmyGroup.lua | 14 +- Moose Development/Moose/Ops/FlightGroup.lua | 30 ++-- Moose Development/Moose/Ops/OpsGroup.lua | 51 ++++--- Moose Development/Moose/Ops/OpsTransport.lua | 144 ++++++++++++++++++- 6 files changed, 217 insertions(+), 42 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 6c4d6a8eb..2c492a949 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -875,12 +875,12 @@ do -- Scheduling -- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. -- @return #string The Schedule ID of the planned schedule. function BASE:ScheduleOnce( Start, SchedulerFunction, ... ) - self:F2( { Start } ) - self:T3( { ... } ) + -- Object name. local ObjectName = "-" ObjectName = self.ClassName .. self.ClassID + -- Debug info. self:F3( { "ScheduleOnce: ", ObjectName, Start } ) if not self.Scheduler then @@ -930,7 +930,7 @@ do -- Scheduling self.Scheduler = SCHEDULER:New( self ) end - -- NOTE: MasterObject (first parameter) should(!) be nil as it will be the first argument passed to the SchedulerFunction! + -- NOTE: MasterObject (first parameter) should(!) be nil as it will be the first argument passed to the SchedulerFunction!s local ScheduleID = self.Scheduler:Schedule( self, SchedulerFunction, diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 17feaa492..017df9cb1 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -278,6 +278,20 @@ do -- SET_BASE end + --- Add a SET to this set. + -- @param #SET_BASE self + -- @param Core.Set#SET_BASE SetToAdd Set to add. + -- @return #SET_BASE self + function SET_BASE:AddSet(SetToAdd) + + for _,ObjectB in pairs(SetToAdd.Set) do + self:AddObject(ObjectB) + end + + return self + end + + --- Get the *union* of two sets. -- @param #SET_BASE self -- @param Core.Set#SET_BASE SetB Set *B*. diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index fc1761600..3adde055d 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -715,7 +715,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) -- Passed final WP ==> Full Stop --- - self:E(self.lid..string.format("WARNING: Passed final WP ==> Full Stop!")) + self:E(self.lid..string.format("WARNING: Passed final WP when UpdateRoute() ==> Full Stop!")) self:FullStop() end @@ -991,6 +991,7 @@ end -- @param #string To To state. -- @param Wrapper.Group#GROUP Group the group to be engaged. function ARMYGROUP:onafterEngageTarget(From, Event, To, Target) + self:T(self.lid.."Engaging Target") if Target:IsInstanceOf("TARGET") then self.engage.Target=Target @@ -1062,6 +1063,8 @@ end -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterDisengage(From, Event, To) + self:T(self.lid.."Disengage Target") + -- TODO: Reset ROE and alarm state. self:_CheckGroupDone(1) end @@ -1072,9 +1075,11 @@ end -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterRearmed(From, Event, To) + self:I(self.lid.."Group rearmed") + -- Check group done. self:_CheckGroupDone(1) - + end --- On after "DetourReached" event. @@ -1083,7 +1088,7 @@ end -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterDetourReached(From, Event, To) - self:I(self.lid.."Group reached detour coordinate.") + self:T(self.lid.."Group reached detour coordinate") end @@ -1094,7 +1099,8 @@ end -- @param #string To To state. function ARMYGROUP:onafterFullStop(From, Event, To) - self:I(self.lid..string.format("Full stop!")) + -- Debug info. + self:T(self.lid..string.format("Full stop!")) -- Get current position. local pos=self:GetCoordinate() diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ea0a73eea..a8a4c19fe 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1179,6 +1179,7 @@ end -- @param #FLIGHTGROUP self -- @param Core.Event#EVENTDATA EventData Event data. function FLIGHTGROUP:OnEventTakeOff(EventData) + self:T3(self.lid.."EVENT: TakeOff") -- Check that this is the right group. if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then @@ -1190,7 +1191,7 @@ function FLIGHTGROUP:OnEventTakeOff(EventData) local element=self:GetElementByName(unitname) if element then - self:T3(self.lid..string.format("EVENT: Element %s took off ==> airborne", element.name)) + self:T2(self.lid..string.format("EVENT: Element %s took off ==> airborne", element.name)) self:ElementTakeoff(element, EventData.Place) end @@ -1812,6 +1813,10 @@ function FLIGHTGROUP:onafterLanded(From, Event, To, airbase) -- Add flight to taxiinb queue. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAXIINB) end + + if airbase and self.isHelo then + self:Arrived() + end end @@ -2020,19 +2025,21 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) if task then if task.dcstask.id=="PatrolZone" then -- For patrol zone, we need to allow the update as we insert new waypoints. + self:T2(self.lid.."Allowing update route for Task: PatrolZone") elseif task.dcstask.id=="ReconMission" then -- For recon missions, we need to allow the update as we insert new waypoints. + self:T2(self.lid.."Allowing update route for Task: ReconMission") elseif task.description and task.description=="Task_Land_At" then -- We allow this - env.info("FF allowing update route for Task_Land_At") + self:T2(self.lid.."Allowing update route for Task: Task_Land_At") else local taskname=task and task.description or "No description" self:E(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s", self.taskcurrent, tostring(taskname))) allowed=false end else - -- Now this can happen, if we directly use TaskExecute as the task is not in the task queue and cannot be removed. - self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d>0 but no task?!", self.taskcurrent)) + -- Now this can happen, if we directly use TaskExecute as the task is not in the task queue and cannot be removed. Therefore, also directly executed tasks should be added to the queue! + self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d (>0!) but no task?!", self.taskcurrent)) -- Anyhow, a task is running so we do not allow to update the route! allowed=false end @@ -2102,15 +2109,6 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) local hb=self.homebase and self.homebase:GetName() or "unknown" local db=self.destbase and self.destbase:GetName() or "unknown" self:T(self.lid..string.format("Updating route for WP #%d-%d [%s], homebase=%s destination=%s", n, #wp, self:GetState(), hb, db)) - - -- Print waypoints. - --[[ - for i,w in pairs(wp) do - env.info("FF waypoint index="..i) - self:I(w) - end - ]] - if #wp>1 then @@ -2489,6 +2487,10 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Add flight to inbound queue. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.INBOUND) end + + -- Some intermediate coordinate to climb to the default cruise alitude. + local c1=c0:GetIntermediateCoordinate(p0, 0.25):SetAltitude(self.altitudeCruise, true) + local c2=c0:GetIntermediateCoordinate(p0, 0.75):SetAltitude(self.altitudeCruise, true) -- Altitude above ground for a glide slope of 3 degrees. local x1=self.isHelo and UTILS.NMToMeters(5.0) or UTILS.NMToMeters(10) @@ -2521,6 +2523,8 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Waypoints from current position to holding point. local wp={} wp[#wp+1]=c0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Current Pos") + wp[#wp+1]=c1:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Climb") + wp[#wp+1]=c2:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Descent") wp[#wp+1]=p0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {TaskArrived, TaskHold, TaskKlar}, "Holding Point") -- Approach point: 10 NN in direction of runway. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index a1db9890f..ad94fc415 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -712,8 +712,8 @@ function OPSGROUP:New(group) -- @param #OPSGROUP self -- @param Ops.Auftrag#AUFTRAG Mission The mission. - --- Triggers the FSM event "MissionCancel" after a delay - -- @function [parent=#OPSGROUP] MissionCancel + --- Triggers the FSM event "MissionCancel" after a delay. + -- @function [parent=#OPSGROUP] __MissionCancel -- @param #OPSGROUP self -- @param #number delay Delay in seconds. -- @param Ops.Auftrag#AUFTRAG Mission The mission. @@ -1198,6 +1198,11 @@ function OPSGROUP:GetVec3(UnitName) end end + + -- Return last known position. + if self.position then + return self.position + end return nil end @@ -1806,6 +1811,8 @@ function OPSGROUP:IsInZone(Zone) local is=false if vec2 then is=Zone:IsVec2InZone(vec2) + else + env.info(self.lid.."FF cannot get vec2") end return is end @@ -1901,11 +1908,11 @@ function OPSGROUP:IsLateActivated() return self.isLateActivated end ---- Check if group is in state in utero. +--- Check if group is in state in utero. Note that dead groups are also in utero but will return `false` here. -- @param #OPSGROUP self -- @return #boolean If true, group is not spawned yet. function OPSGROUP:IsInUtero() - local is=self:Is("InUtero") + local is=self:Is("InUtero") and not self:IsDead() return is end @@ -2047,9 +2054,10 @@ end --- Check if the group is assigned as cargo. -- @param #OPSGROUP self +-- @param #boolean CheckTransport If `true` or `nil`, also check if cargo is associated with a transport assignment. If not, we consider it not cargo. -- @return #boolean If true, group is cargo. -function OPSGROUP:IsCargo() - return not self:IsNotCargo() +function OPSGROUP:IsCargo(CheckTransport) + return not self:IsNotCargo(CheckTransport) end --- Check if the group is **not** cargo. @@ -3310,8 +3318,8 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- NOTE: I am pushing the task instead of setting it as it seems to keep the mission task alive. -- There were issues that flights did not proceed to a later waypoint because the task did not finish until the fired missiles -- impacted (took rather long). Then the flight flew to the nearest airbase and one lost completely the control over the group. - self:PushTask(TaskFinal) - --self:SetTask(TaskFinal) + --self:PushTask(TaskFinal) + self:SetTask(TaskFinal) elseif Task.type==OPSGROUP.TaskType.WAYPOINT then @@ -5567,19 +5575,19 @@ function OPSGROUP:_CheckCargoTransport() if self:IsNotCarrier() then - -- Debug info. - self:T(self.lid.."Not carrier ==> pickup?") - -- Get transport zone combo (TZC). self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self) if self.cargoTZC then + + -- Found TZC + self:T(self.lid..string.format("Not carrier ==> pickup at %s [TZC UID=%d]", self.cargoTZC.PickupZone and self.cargoTZC.PickupZone:GetName() or "unknown", self.cargoTZC.uid)) -- Initiate the cargo transport process. self:__Pickup(-1) else - self:T(self.lid.."Not carrier ==> pickup") + self:T2(self.lid.."Not carrier ==> No TZC found") end elseif self:IsPickingup() then @@ -6422,7 +6430,7 @@ function OPSGROUP:onafterPickup(From, Event, To) end -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, self.altitudeCruise, false) ; waypoint.detour=1 + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, UTILS.MetersToFeet(self.altitudeCruise), false) ; waypoint.detour=1 else self:E(self.lid.."ERROR: Transportcarrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") @@ -6513,10 +6521,13 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Check that cargo weight is if self:CanCargo(cargo.opsgroup) and (not (cargo.delivered or cargo.opsgroup:IsDead())) then + + -- Check if cargo is currently acting as carrier. + local isCarrier=cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading() -- Check that group is NOT cargo and NOT acting as carrier already -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() - if cargo.opsgroup:IsNotCargo(true) and not (cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading()) then + if cargo.opsgroup:IsNotCargo(true) and not isCarrier then -- Check if cargo is in embark/pickup zone. local inzone=cargo.opsgroup:IsInZone(self.cargoTZC.EmbarkZone) @@ -6734,7 +6745,7 @@ function OPSGROUP:onafterTransport(From, Event, To) end -- Start unloading. - self:__UnLoading(-5) + self:__Unloading(-5) else @@ -6780,7 +6791,7 @@ function OPSGROUP:onafterTransport(From, Event, To) --- -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, self.altitudeCruise, false) ; waypoint.detour=1 + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, UTILS.MetersToFeet(self.altitudeCruise), false) ; waypoint.detour=1 -- Cancel landedAt task. This should trigger Cruise once airborne. if self:IsFlightgroup() and self:IsLandedAt() then @@ -6916,13 +6927,17 @@ function OPSGROUP:onafterUnloading(From, Event, To) self:Unload(cargo.opsgroup) else + + -- Get disembark zone of this TZC. + local DisembarkZone=self.cargoTransport:GetDisembarkZone(self.cargoTZC) local Coordinate=nil + - if self.cargoTransport:GetDisembarkZone(self.cargoTZC) then + if DisembarkZone then -- Random coordinate in disembark zone. - Coordinate=self.cargoTransport:GetDisembarkZone(self.cargoTZC):GetRandomCoordinate() + Coordinate=DisembarkZone:GetRandomCoordinate() else diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index cd9357650..48bbb0922 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -173,7 +173,7 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.4.0" +OPSTRANSPORT.version="0.4.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -243,6 +243,130 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) self:AddTransition("*", "DeadCarrierUnit", "*") self:AddTransition("*", "DeadCarrierGroup", "*") self:AddTransition("*", "DeadCarrierAll", "*") + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Status". + -- @function [parent=#OPSTRANSPORT] Status + -- @param #OPSTRANSPORT self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#OPSTRANSPORT] __Status + -- @param #OPSTRANSPORT self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Planned". + -- @function [parent=#OPSTRANSPORT] Planned + -- @param #OPSTRANSPORT self + + --- Triggers the FSM event "Planned" after a delay. + -- @function [parent=#OPSTRANSPORT] __Planned + -- @param #OPSTRANSPORT self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Queued". + -- @function [parent=#OPSTRANSPORT] Queued + -- @param #OPSTRANSPORT self + + --- Triggers the FSM event "Queued" after a delay. + -- @function [parent=#OPSTRANSPORT] __Queued + -- @param #OPSTRANSPORT self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Requested". + -- @function [parent=#OPSTRANSPORT] Requested + -- @param #OPSTRANSPORT self + + --- Triggers the FSM event "Requested" after a delay. + -- @function [parent=#OPSTRANSPORT] __Requested + -- @param #OPSTRANSPORT self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Scheduled". + -- @function [parent=#OPSTRANSPORT] Scheduled + -- @param #OPSTRANSPORT self + + --- Triggers the FSM event "Scheduled" after a delay. + -- @function [parent=#OPSTRANSPORT] __Scheduled + -- @param #OPSTRANSPORT self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Executing". + -- @function [parent=#OPSTRANSPORT] Executing + -- @param #OPSTRANSPORT self + + --- Triggers the FSM event "Executing" after a delay. + -- @function [parent=#OPSTRANSPORT] __Executing + -- @param #OPSTRANSPORT self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Delivered". + -- @function [parent=#OPSTRANSPORT] Delivered + -- @param #OPSTRANSPORT self + + --- Triggers the FSM event "Delivered" after a delay. + -- @function [parent=#OPSTRANSPORT] __Delivered + -- @param #OPSTRANSPORT self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Loaded". + -- @function [parent=#OPSTRANSPORT] Loaded + -- @param #OPSTRANSPORT self + -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was loaded into a carrier. + -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier OPSGROUP that was loaded into a carrier. + -- @param Ops.OpsGroup#OPSGROUP.Element CarrierElement Carrier element. + + --- Triggers the FSM event "Loaded" after a delay. + -- @function [parent=#OPSTRANSPORT] __Loaded + -- @param #OPSTRANSPORT self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was loaded into a carrier. + -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier OPSGROUP that was loaded into a carrier. + -- @param Ops.OpsGroup#OPSGROUP.Element CarrierElement Carrier element. + + --- On after "Loaded" event. + -- @function [parent=#OPSTRANSPORT] OnAfterLoaded + -- @param #OPSGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was loaded into a carrier. + -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier OPSGROUP that was loaded into a carrier. + -- @param Ops.OpsGroup#OPSGROUP.Element CarrierElement Carrier element. + + + --- Triggers the FSM event "Unloaded". + -- @function [parent=#OPSTRANSPORT] Unloaded + -- @param #OPSTRANSPORT self + -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo Cargo OPSGROUP that was unloaded from a carrier. + -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. + + --- Triggers the FSM event "Unloaded" after a delay. + -- @function [parent=#OPSTRANSPORT] __Unloaded + -- @param #OPSTRANSPORT self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo Cargo OPSGROUP that was unloaded from a carrier. + -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. + + + --- On after "Unloaded" event. + -- @function [parent=#OPSTRANSPORT] OnAfterUnloaded + -- @param #OPSGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo Cargo OPSGROUP that was unloaded from a carrier. + -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. + --TODO: Psydofunctions @@ -1146,9 +1270,18 @@ end --- Check if all cargo was delivered (or is dead). -- @param #OPSTRANSPORT self +-- @param #number Nmin Number of groups that must be actually delivered (and are not dead). Default 0. -- @return #boolean If true, all possible cargo was delivered. -function OPSTRANSPORT:IsDelivered() - return self:is(OPSTRANSPORT.Status.DELIVERED) +function OPSTRANSPORT:IsDelivered(Nmin) + local is=self:is(OPSTRANSPORT.Status.DELIVERED) + Nmin=Nmin or 0 + if Nmin>self.Ncargo then + Nmin=self.Ncargo + end + if self.Ndelivered Date: Thu, 2 Sep 2021 08:30:54 +0200 Subject: [PATCH 088/141] OPSTRANSPORT v0.4.2 - Some fixed. - All tested demo missions look nice --- Moose Development/Moose/Ops/FlightGroup.lua | 38 +++++++---- Moose Development/Moose/Ops/NavyGroup.lua | 4 +- Moose Development/Moose/Ops/OpsGroup.lua | 72 +++++++++++++------- Moose Development/Moose/Ops/OpsTransport.lua | 3 +- 4 files changed, 75 insertions(+), 42 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index a8a4c19fe..33431370a 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -888,19 +888,25 @@ function FLIGHTGROUP:Status() -- Short info. if self.verbose>=1 then + local nelem=self:CountElements() + local Nelem=#self.elements local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() local nMissions=self:CountRemainingMissison() + local currT=self.taskcurrent or "None" + local currM=self.currentmission or "None" + local currW=self.currentwp or 0 + local nWp=self.waypoints and #self.waypoints or 0 local home=self.homebase and self.homebase:GetName() or "unknown" local dest=self.destbase and self.destbase:GetName() or "unknown" local fc=self.flightcontrol and self.flightcontrol.airbasename or "N/A" local curr=self.currbase and self.currbase:GetName() or "N/A" - local nelem=self:CountElements() - local Nelem=#self.elements + local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "OFF" - local text=string.format("Status %s [%d/%d]: Tasks=%d, Missions=%s, Waypoint=%d/%d [%s], Detected=%s, Home=%s, Destination=%s, Current=%s, FC=%s", - fsmstate, nelem, Nelem, nTaskTot, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, tostring(self.passedfinalwp), - ndetected, home, dest, curr, fc) + local text=string.format("Status %s [%d/%d]: T/M=%d/%d [Current %s/%s] [%s], Waypoint=%d/%d [%s], Base=%s [%s-->%s]", + fsmstate, nelem, Nelem, + nTaskTot, nMissions, currT, currM, tostring(self:HasTaskController()), + currW, nWp, tostring(self.passedfinalwp), curr, home, dest) self:I(self.lid..text) end @@ -1813,10 +1819,6 @@ function FLIGHTGROUP:onafterLanded(From, Event, To, airbase) -- Add flight to taxiinb queue. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAXIINB) end - - if airbase and self.isHelo then - self:Arrived() - end end @@ -1993,7 +1995,7 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) allowed=false end - if self:IsUncontrolled() then + if allowed and self:IsUncontrolled() then -- Not airborne yet. Try again in 5 sec. self:T(self.lid.."Update route denied. Group is UNCONTROLLED ==> checking back in 5 sec") trepeat=-5 @@ -2244,9 +2246,13 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) self:T(self.lid..string.format("Passed Final WP and No current and/or future missions/tasks/transports. Waittime given ==> Waiting for %d sec!", waittime)) self:Wait(waittime) elseif destbase then - self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") - --self:RTB(destbase) - self:__RTB(-0.1, destbase) + if self.currbase and self.currbase.AirbaseName==destbase.AirbaseName and self:IsParking() then + self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports AND parking at destination airbase ==> Arrived!") + self:__Arrived(0.1) + else + self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") + self:__RTB(-0.1, destbase) + end elseif destzone then self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTZ!") self:__RTZ(-0.1, destzone) @@ -2493,8 +2499,8 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) local c2=c0:GetIntermediateCoordinate(p0, 0.75):SetAltitude(self.altitudeCruise, true) -- Altitude above ground for a glide slope of 3 degrees. - local x1=self.isHelo and UTILS.NMToMeters(5.0) or UTILS.NMToMeters(10) - local x2=self.isHelo and UTILS.NMToMeters(2.5) or UTILS.NMToMeters(5) + local x1=self.isHelo and UTILS.NMToMeters(2.0) or UTILS.NMToMeters(10) + local x2=self.isHelo and UTILS.NMToMeters(1.0) or UTILS.NMToMeters(5) local alpha=math.rad(3) local h1=x1*math.tan(alpha) local h2=x2*math.tan(alpha) @@ -2865,6 +2871,8 @@ function FLIGHTGROUP:onafterLandAt(From, Event, To, Coordinate, Duration) -- Duration. --Duration=Duration or 600 + + self:T(self.lid..string.format("Landing at Coordinate for %s seconds", tostring(Duration))) Coordinate=Coordinate or self:GetCoordinate() diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 500642a4e..65b32e2f3 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1460,7 +1460,9 @@ end -- @param #NAVYGROUP self -- @return #boolean If true, a path was found. function NAVYGROUP:_FindPathToNextWaypoint() - env.info("FF Path finding") + self:T3(self.lid.."Path finding") + + --TODO: Do not create a new ASTAR object each time this function is called but make it self.astar and reuse. Should be better for performance. -- Pathfinding A* local astar=ASTAR:New() diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ad94fc415..69455dd07 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -512,8 +512,6 @@ function OPSGROUP:New(group) end end - self.group:IsAir() - -- Set the template. self:_SetTemplate() @@ -1812,7 +1810,7 @@ function OPSGROUP:IsInZone(Zone) if vec2 then is=Zone:IsVec2InZone(vec2) else - env.info(self.lid.."FF cannot get vec2") + self:T3(self.lid.."WARNING: Cannot get vec2 at IsInZone()!") end return is end @@ -2783,14 +2781,13 @@ end --- Returns true if the DCS controller currently has a task. -- @param #OPSGROUP self --- @param #number Delay Delay in seconds. -- @return #boolean True or false if the controller has a task. Nil if no controller. -function OPSGROUP:HasTaskController(Delay) +function OPSGROUP:HasTaskController() local hastask=nil if self.controller then hastask=self.controller:hasTask() end - self:I(self.lid..string.format("Controller hasTask=%s", tostring(hastask))) + self:T3(self.lid..string.format("Controller hasTask=%s", tostring(hastask))) return hastask end @@ -3199,6 +3196,7 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Insert into task queue. Not sure any more, why I added this. But probably if a task is just executed without having been put into the queue. if self:GetTaskCurrent()==nil then + --env.info("FF adding current task to queue") table.insert(self.taskqueue, Task) end @@ -3318,8 +3316,8 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- NOTE: I am pushing the task instead of setting it as it seems to keep the mission task alive. -- There were issues that flights did not proceed to a later waypoint because the task did not finish until the fired missiles -- impacted (took rather long). Then the flight flew to the nearest airbase and one lost completely the control over the group. - --self:PushTask(TaskFinal) - self:SetTask(TaskFinal) + self:PushTask(TaskFinal) + --self:SetTask(TaskFinal) elseif Task.type==OPSGROUP.TaskType.WAYPOINT then @@ -3467,7 +3465,7 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) end if Task.description=="Task_Land_At" then - self:Wait(60, 100) + self:Wait(20, 100) else self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 0 sec") self:_CheckGroupDone() @@ -5065,7 +5063,7 @@ end function OPSGROUP:onbeforeElementSpawned(From, Event, To, Element) if Element and Element.status==OPSGROUP.ElementStatus.SPAWNED then - self:I(self.lid..string.format("FF element %s is already spawned", Element.name)) + self:T2(self.lid..string.format("Element %s is already spawned ==> Transition denied!", Element.name)) return false end @@ -5333,7 +5331,7 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) self:_InitWaypoints() -- Init Group. - self:_InitGroup() + self:_InitGroup(Template) -- Reset events. --self:ResetEvents() @@ -5883,7 +5881,6 @@ function OPSGROUP:_CheckDelivered(CargoTransport) elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then -- This one is dead. else - --env.info(string.format()) done=false --Someone is not done! end @@ -6416,7 +6413,7 @@ function OPSGROUP:onafterPickup(From, Event, To) end -- Order group to land at an airbase. - self:__LandAtAirbase(-0.5, airbasePickup) + self:__LandAtAirbase(-0.1, airbasePickup) elseif self.isHelo then @@ -6426,7 +6423,7 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Activate uncontrolled group. if self:IsParking() and self:IsUncontrolled() then - self:StartUncontrolled(1) + self:StartUncontrolled(0.5) end -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. @@ -6436,6 +6433,16 @@ function OPSGROUP:onafterPickup(From, Event, To) self:E(self.lid.."ERROR: Transportcarrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") end + -- Cancel landedAt task. This should trigger Cruise once airborne. + if self.isHelo and self:IsLandedAt() then + local Task=self:GetTaskCurrent() + if Task then + self:TaskCancel(Task) + else + self:E(self.lid.."ERROR: No current task but landed at?!") + end + end + elseif self:IsNavygroup() then --- @@ -6793,15 +6800,19 @@ function OPSGROUP:onafterTransport(From, Event, To) -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, UTILS.MetersToFeet(self.altitudeCruise), false) ; waypoint.detour=1 - -- Cancel landedAt task. This should trigger Cruise once airborne. - if self:IsFlightgroup() and self:IsLandedAt() then - local Task=self:GetTaskCurrent() - self:__TaskCancel(5, Task) - end - else - self:E(self.lid.."ERROR: Carrier aircraft cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") + self:E(self.lid.."ERROR: Aircraft (cargo carrier) cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") end + + -- Cancel landedAt task. This should trigger Cruise once airborne. + if self.isHelo and self:IsLandedAt() then + local Task=self:GetTaskCurrent() + if Task then + self:TaskCancel(Task) + else + self:E(self.lid.."ERROR: No current task but landed at?!") + end + end elseif self:IsArmygroup() then @@ -7179,9 +7190,22 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Startup uncontrolled aircraft to allow it to go back. if self:IsFlightgroup() then - if self:IsUncontrolled() then + + local function atbase(_airbase) + local airbase=_airbase --Wrapper.Airbase#AIRBASE + if airbase and self.currbase then + if airbase.AirbaseName==self.currbase.AirbaseName then + return true + end + end + return false + end + + -- Check if uncontrolled and NOT at destination. If so, start up uncontrolled and let flight return to whereever it wants to go. + if self:IsUncontrolled() and not atbase(self.destbase) then self:StartUncontrolled() - elseif self:IsLandedAt() then + end + if self:IsLandedAt() then local Task=self:GetTaskCurrent() self:TaskCancel(Task) end @@ -7191,7 +7215,7 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) end -- Check group done. - self:T(self.lid.."All cargo delivered ==> check group done") + self:T(self.lid.."All cargo delivered ==> check group done in 0.2 sec") self:_CheckGroupDone(0.2) -- No current transport any more. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 48bbb0922..5e123a191 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -173,7 +173,7 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.4.1" +OPSTRANSPORT.version="0.4.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -357,7 +357,6 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo Cargo OPSGROUP that was unloaded from a carrier. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. - --- On after "Unloaded" event. -- @function [parent=#OPSTRANSPORT] OnAfterUnloaded -- @param #OPSGROUP self From ad0b32c0ee1a8275a0e97a960af8587da612ebbd Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 3 Sep 2021 00:05:58 +0200 Subject: [PATCH 089/141] OPS - Found and fixed bugs for ARMY and NAVY groups, which caused only one waypoint to be processed - Added Duration for AUFTRAG - Fixed bug in auftrag if no legion was assigned and mission was canceled at opsgroup level - Trying (again) to include the whole route for ARMY and NAVY when UpdateRoute - Simpler task function for passing waypoint STILL a lot to do/check! --- Moose Development/Moose/Ops/ArmyGroup.lua | 104 ++++++++++++---------- Moose Development/Moose/Ops/Auftrag.lua | 30 ++++++- Moose Development/Moose/Ops/NavyGroup.lua | 72 ++++++++------- Moose Development/Moose/Ops/OpsGroup.lua | 40 +++++++-- 4 files changed, 157 insertions(+), 89 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 3adde055d..f30287d80 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -190,7 +190,8 @@ end -- @param #ARMYGROUP self -- @return Core.Point#COORDINATE Coordinate of a road closest to the group. function ARMYGROUP:GetClosestRoad() - return self:GetCoordinate():GetClosestPointToRoad() + local coord=self:GetCoordinate():GetClosestPointToRoad() + return coord end --- Get 2D distance to the closest road. @@ -568,12 +569,13 @@ function ARMYGROUP:onafterSpawned(From, Event, To) -- Formation if not self.option.Formation then - self.option.Formation=self.optionDefault.Formation + -- Will be set in update route. + --self.option.Formation=self.optionDefault.Formation end -- Update route. if #self.waypoints>1 then - self:Cruise(nil, self.option.Formation or self.optionDefault.Formation) + self:Cruise(nil, self.option.Formation) else self:FullStop() end @@ -624,72 +626,76 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) -- Update route from this waypoint number onwards. n=n or self:GetWaypointIndexNext(self.adinfinitum) - -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. - self:_UpdateWaypointTasks(n) - + -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. OBSOLETE! + --self:_UpdateWaypointTasks(n) + -- Waypoints. local waypoints={} - -- Next waypoint. - local wp=UTILS.DeepCopy(self.waypoints[n]) --Ops.OpsGroup#OPSGROUP.Waypoint + local formationlast=nil + for i=n, #self.waypoints do - -- Do we want to drive on road to the next wp? - local onroad=wp.action==ENUMS.Formation.Vehicle.OnRoad - - -- Speed. - if Speed then - wp.speed=UTILS.KnotsToMps(Speed) - else - -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. - if wp.speed<0.1 then --self.adinfinitum and - wp.speed=UTILS.KmphToMps(self.speedCruise) + -- Next waypoint. + local wp=UTILS.DeepCopy(self.waypoints[i]) --Ops.OpsGroup#OPSGROUP.Waypoint + + -- Speed. + if Speed then + wp.speed=UTILS.KnotsToMps(Speed) + else + -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. + if wp.speed<0.1 then --self.adinfinitum and + wp.speed=UTILS.KmphToMps(self.speedCruise) + end end + + -- Formation. + if self.formationPerma then + wp.action=self.formationPerma + elseif Formation then + wp.action=Formation + end + + -- Add waypoint in between because this waypoint is "On Road" but lies "Off Road". + if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>10 then + + -- The real waypoint is actually off road. + wp.action=ENUMS.Formation.Vehicle.OffRoad + + -- Add "On Road" waypoint in between. + local wproad=wp.roadcoord:WaypointGround(wp.speed, ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint + + -- Insert road waypoint. + table.insert(waypoints, wproad) + end + + -- Add waypoint. + table.insert(waypoints, wp) + + -- Last formation. + formationlast=wp.action end - -- Formation. - if self.formationPerma then - wp.action=self.formationPerma - elseif Formation then - wp.action=Formation - end + -- First (next wp). + local wp=waypoints[1] --Ops.OpsGroup#OPSGROUP.Waypoint -- Current set formation. self.option.Formation=wp.action - - -- Current set speed in m/s. - self.speedWp=wp.speed - - -- Add waypoint in between because this waypoint is "On Road" but lies "Off Road". - if onroad then - - -- The real waypoint is actually off road. - wp.action=ENUMS.Formation.Vehicle.OffRoad - - -- Add "On Road" waypoint in between. - local wproad=wp.roadcoord:WaypointGround(wp.speed, ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint - -- Insert road waypoint. - table.insert(waypoints, wproad) - end - - -- Add waypoint. - table.insert(waypoints, wp) + -- Current set speed in m/s. + self.speedWp=wp.speed - -- Apply formation at the current position or it will only be changed when reaching the next waypoint. - local formation=ENUMS.Formation.Vehicle.OffRoad - if wp.action~=ENUMS.Formation.Vehicle.OnRoad then - formation=wp.action - end + local formation0=wp.action==ENUMS.Formation.Vehicle.OnRoad and ENUMS.Formation.Vehicle.OffRoad or wp.action -- Current point. - local current=self:GetCoordinate():WaypointGround(UTILS.MpsToKmph(self.speedWp), formation) + local current=self:GetCoordinate():WaypointGround(UTILS.MpsToKmph(self.speedWp), formation0) table.insert(waypoints, 1, current) -- Insert a point on road. - if onroad then + if wp.action==ENUMS.Formation.Vehicle.OnRoad then local current=self:GetClosestRoad():WaypointGround(UTILS.MpsToKmph(self.speedWp), ENUMS.Formation.Vehicle.OnRoad) table.insert(waypoints, 2, current) end + -- Debug output. if false then diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 3c7f2e6f1..5ebec3140 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1521,6 +1521,15 @@ function AUFTRAG:SetTime(ClockStart, ClockStop) return self end +--- Set time how low the mission is executed. Once this time limit has passed, the mission is cancelled. +-- @param #AUFTRAG self +-- @param #number Duration Duration in seconds. +-- @return #AUFTRAG self +function AUFTRAG:SetDuration(Duration) + self.durationExe=Duration + return self +end + --- Set mission push time. This is the time the mission is executed. If the push time is not passed, the group will wait at the mission execution waypoint. -- @param #AUFTRAG self @@ -2341,10 +2350,21 @@ function AUFTRAG:onafterStatus(From, Event, To) -- All groups have reported MISSON DONE. self:Done() - elseif (self.Tstop and Tnow>self.Tstop+10) or (Ntargets0>0 and Ntargets==0) then - + elseif (self.Tstop and Tnow>self.Tstop+10) then + -- Cancel mission if stop time passed. - --self:Cancel() + self:Cancel() + + elseif self.durationExe and self.Texecuting and Tnow-self.Texecuting>self.durationExe then + + -- Cancel mission if stop time passed. + self:Cancel() + + elseif (Ntargets0>0 and Ntargets==0) then + + -- Cancel mission if mission targets are gone (if there were any in the beginning). + -- TODO: I commented this out for some reason but I forgot why... + self:Cancel() end @@ -2893,6 +2913,7 @@ end -- @param #string To To state. function AUFTRAG:onafterStarted(From, Event, To) self.status=AUFTRAG.Status.STARTED + self.Tstarted=timer.getAbsTime() self:T(self.lid..string.format("New mission status=%s", self.status)) end @@ -2903,6 +2924,7 @@ end -- @param #string To To state. function AUFTRAG:onafterExecuting(From, Event, To) self.status=AUFTRAG.Status.EXECUTING + self.Texecuting=timer.getAbsTime() self:T(self.lid..string.format("New mission status=%s", self.status)) end @@ -3006,7 +3028,7 @@ function AUFTRAG:onafterCancel(From, Event, To) -- COMMANDER will cancel the mission. self.commander:MissionCancel(self) - elseif self.legions then + elseif self.legions and #self.legions>0 then -- Loop over all LEGIONs. for _,_legion in pairs(self.legions or {}) do diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 65b32e2f3..739f54ac8 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -539,13 +539,15 @@ function NAVYGROUP:Status(From, Event, To) local turning=tostring(self:IsTurning()) local alt=self.position.y local speed=UTILS.MpsToKnots(self.velocity) - local speedExpected=UTILS.MpsToKnots(self:GetExpectedSpeed()) --UTILS.MpsToKnots(self.speedWp or 0) + local speedExpected=UTILS.MpsToKnots(self:GetExpectedSpeed()) -- Waypoint stuff. local wpidxCurr=self.currentwp local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr) or 0 local wpidxNext=self:GetWaypointIndexNext() or 0 local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext) or 0 + local wpN=#self.waypoints or 0 + local wpF=tostring(self.passedfinalwp) local wpDist=UTILS.MetersToNM(self:GetDistanceToWaypoint() or 0) local wpETA=UTILS.SecondsToClock(self:GetTimeToWaypoint() or 0, true) @@ -554,8 +556,8 @@ function NAVYGROUP:Status(From, Event, To) local als=self:GetAlarmstate() or 0 -- Info text. - local text=string.format("%s [ROE=%d,AS=%d, T/M=%d/%d]: Wp=%d[%d]-->%d[%d] (of %d) Dist=%.1f NM ETA=%s - Speed=%.1f (%.1f) kts, Depth=%.1f m, Hdg=%03d, Turn=%s Collision=%d IntoWind=%s", - fsmstate, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, #self.waypoints or 0, wpDist, wpETA, speed, speedExpected, alt, self.heading, turning, freepath, intowind) + local text=string.format("%s [ROE=%d,AS=%d, T/M=%d/%d]: Wp=%d[%d]-->%d[%d] /%d [%s] Dist=%.1f NM ETA=%s - Speed=%.1f (%.1f) kts, Depth=%.1f m, Hdg=%03d, Turn=%s Collision=%d IntoWind=%s", + fsmstate, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, wpN, wpF, wpDist, wpETA, speed, speedExpected, alt, self.heading, turning, freepath, intowind) self:I(self.lid..text) end @@ -742,49 +744,57 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) n=n or self:GetWaypointIndexNext() -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. - self:_UpdateWaypointTasks(n) + --self:_UpdateWaypointTasks(n) -- Waypoints. local waypoints={} - -- Waypoint. - local wp=UTILS.DeepCopy(self.waypoints[n]) --Ops.OpsGroup#OPSGROUP.Waypoint - - -- Speed. - if Speed then - -- Take speed specified. - wp.speed=UTILS.KnotsToMps(Speed) - else - -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. - if self.adinfinitum and wp.speed<0.1 then - wp.speed=UTILS.KmphToMps(self.speedCruise) + for i=n, #self.waypoints do + + -- Waypoint. + local wp=UTILS.DeepCopy(self.waypoints[i]) --Ops.OpsGroup#OPSGROUP.Waypoint + + -- Speed. + if Speed then + -- Take speed specified. + wp.speed=UTILS.KnotsToMps(Speed) + else + -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. + if self.adinfinitum and wp.speed<0.1 then + wp.speed=UTILS.KmphToMps(self.speedCruise) + end + end + + -- Depth. + if Depth then + wp.alt=-Depth + elseif self.depth then + wp.alt=-self.depth + else + -- Take default waypoint alt. + wp.alt=wp.alt or 0 + end + + -- Current set speed in m/s. + if i==n then + self.speedWp=wp.speed + self.altWp=wp.alt end - end - if Depth then - wp.alt=-Depth - elseif self.depth then - wp.alt=-self.depth - else - -- Take default waypoint alt. - wp.alt=wp.alt or 0 - end + -- Add waypoint. + table.insert(waypoints, wp) - -- Current set speed in m/s. - self.speedWp=wp.speed - - -- Add waypoint. - table.insert(waypoints, wp) + end -- Current waypoint. - local current=self:GetCoordinate():WaypointNaval(UTILS.MpsToKmph(self.speedWp), wp.alt) + local current=self:GetCoordinate():WaypointNaval(UTILS.MpsToKmph(self.speedWp), self.altWp) table.insert(waypoints, 1, current) if self:IsEngaging() or not self.passedfinalwp then -- Debug info. - self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Depth=%d m", self.currentwp, n, #waypoints, #self.waypoints, UTILS.MpsToKnots(self.speedWp), wp.alt)) + self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Depth=%d m", self.currentwp, n, #waypoints, #self.waypoints, UTILS.MpsToKnots(self.speedWp), self.altWp)) -- Route group to all defined waypoints remaining. self:Route(waypoints) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 69455dd07..aef995c88 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4097,7 +4097,7 @@ function OPSGROUP:RouteToMission(mission, delay) --waypointcoord:MarkToAll(string.format("Mission %s alt=%d m", mission:GetName(), waypointcoord.y)) -- Add waypoint. - local waypoint=self:AddWaypoint(waypointcoord, SpeedToMission, nil, formation, false) + local waypoint=self:AddWaypoint(waypointcoord, SpeedToMission, nil, formation, false) ; waypoint.ismission=true -- Add waypoint task. UpdateRoute is called inside. local waypointtask=self:AddTaskWaypoint(mission.DCStask, waypoint, mission.name, mission.prio, mission.duration) @@ -4111,7 +4111,7 @@ function OPSGROUP:RouteToMission(mission, delay) local egress=mission:GetMissionEgressCoord() if egress then - local waypoint=self:AddWaypoint(egress, SpeedToMission, nil, formation, false) + local waypoint=self:AddWaypoint(egress, SpeedToMission, nil, formation, false) ; waypoint.ismission=true end --- @@ -4384,7 +4384,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Check if all tasks/mission are done? -- Note, we delay it for a second to let the OnAfterPassingwaypoint function to be executed in case someone wants to add another waypoint there. - if ntasks==0 and self:HasPassedFinalWaypoint() then + if ntasks==0 and (self:HasPassedFinalWaypoint()) then-- or self:IsArmygroup() or self:IsNavygroup()) then self:_CheckGroupDone(0.01) end @@ -7910,6 +7910,26 @@ end -- Waypoints & Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Simple task function. Can be used to call a function which has the warehouse and the executing group as parameters. +-- @param #OPSGROUP self +-- @param #string Function The name of the function to call passed as string. +-- @param #number uid Waypoint UID. +function OPSGROUP:_SimpleTaskFunction(Function, uid) + + -- Task script. + local DCSScript = {} + + --_DATABASE:FindOpsGroup(groupname) + + DCSScript[#DCSScript+1] = string.format('local mygroup = _DATABASE:FindOpsGroup(\"%s\") ', self.groupname) -- The group that executes the task function. Very handy with the "...". + DCSScript[#DCSScript+1] = string.format('%s(mygroup, %d)', Function, uid) -- Call the function, e.g. myfunction.(warehouse,mygroup) + + -- Create task. + local DCSTask=CONTROLLABLE.TaskWrappedAction(self, CONTROLLABLE.CommandDoScript(self, table.concat(DCSScript))) + + return DCSTask +end + --- Enhance waypoint table. -- @param #OPSGROUP self -- @param #OPSGROUP.Waypoint Waypoint data. @@ -7933,6 +7953,17 @@ function OPSGROUP:_CreateWaypoint(waypoint) waypoint.detour=false waypoint.astar=false waypoint.temp=false + + -- Tasks of this waypoint + local taskswp={} + + -- At each waypoint report passing. + --local TaskPassingWaypoint=self.group:TaskFunction("OPSGROUP._PassingWaypoint", self, waypoint.uid) + local TaskPassingWaypoint=self:_SimpleTaskFunction("OPSGROUP._PassingWaypoint", waypoint.uid) + table.insert(taskswp, TaskPassingWaypoint) + + -- Waypoint task combo. + waypoint.task=self.group:TaskCombo(taskswp) -- Increase UID counter. self.wpcounter=self.wpcounter+1 @@ -8117,10 +8148,9 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Function called when a group is passing a waypoint. ---@param Wrapper.Group#GROUP group Group that passed the waypoint. --@param #OPSGROUP opsgroup Ops group object. --@param #number uid Waypoint UID. -function OPSGROUP._PassingWaypoint(group, opsgroup, uid) +function OPSGROUP._PassingWaypoint(opsgroup, uid) -- Debug message. local text=string.format("Group passing waypoint uid=%d", uid) From 538864519ea84793dafbc8cced8d5467f5ac4140 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 3 Sep 2021 18:20:28 +0200 Subject: [PATCH 090/141] OPSGROUP - Fixes that ARMY and NAVYGROUPS only go to first waypoint - Fixes for PATROL and RECON missions that waypoints are added at the end - Some other stuff - Still need to fix adinfinitum --- Moose Development/Moose/Ops/ArmyGroup.lua | 9 +- Moose Development/Moose/Ops/FlightGroup.lua | 11 +- Moose Development/Moose/Ops/NavyGroup.lua | 25 +--- Moose Development/Moose/Ops/OpsGroup.lua | 147 +++++++++++++++----- 4 files changed, 127 insertions(+), 65 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index f30287d80..bfed3b5f4 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1150,16 +1150,12 @@ end -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + -- Create coordinate. local coordinate=self:_CoordinateFromObject(Coordinate) -- Set waypoint index. local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) - -- Check if final waypoint is still passed. - if wpnumber>self.currentwp then - self:_PassedFinalWaypoint(false, "ARMYGROUP.AddWaypoint: wpnumber>self.currentwp") - end - -- Speed in knots. Speed=Speed or self:GetSpeedCruise() @@ -1185,8 +1181,7 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation -- Update route. if Updateroute==nil or Updateroute==true then - self:UpdateRoute() - --self:_CheckGroupDone(1) + self:__UpdateRoute(-0.01) end return waypoint diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 33431370a..5e68b039a 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2275,7 +2275,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) --- -- Debug info. - self:T(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route", self:GetState())) + self:T(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route in -0.01 sec", self:GetState())) -- Update route. self:__UpdateRoute(-0.01) @@ -3466,18 +3466,17 @@ end -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function FLIGHTGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) + -- Create coordinate. + local coordinate=self:_CoordinateFromObject(Coordinate) + -- Set waypoint index. local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) - if wpnumber>self.currentwp then - self:_PassedFinalWaypoint(false, "FLIGHTGROUP:AddWaypoint wpnumber>self.currentwp") - end - -- Speed in knots. Speed=Speed or self.speedCruise -- Create air waypoint. - local wp=Coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(Speed), true, nil, {}) + local wp=coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(Speed), true, nil, {}) -- Create waypoint data table. local waypoint=self:_CreateWaypoint(wp) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 739f54ac8..61fcb4db9 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -146,8 +146,8 @@ function NAVYGROUP:New(group) self:AddTransition("*", "CollisionWarning", "*") -- Collision warning. self:AddTransition("*", "ClearAhead", "*") -- Clear ahead. - self:AddTransition("*", "Dive", "Diving") -- Command a submarine to dive. - self:AddTransition("Diving", "Surface", "Cruising") -- Command a submarine to go to the surface. + self:AddTransition("*", "Dive", "*") -- Command a submarine to dive. + self:AddTransition("Diving", "Surface", "*") -- Command a submarine to go to the surface. ------------------------ --- Pseudo Functions --- @@ -1105,30 +1105,17 @@ end -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Updateroute) - -- Check if a coordinate was given or at least a positionable. - if not Coordinate:IsInstanceOf("COORDINATE") then - if Coordinate:IsInstanceOf("POSITIONABLE") or Coordinate:IsInstanceOf("ZONE_BASE") then - self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate") - Coordinate=Coordinate:GetCoordinate() - else - self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!") - return nil - end - end + -- Create coordinate. + local coordinate=self:_CoordinateFromObject(Coordinate) -- Set waypoint index. local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) - -- Check if final waypoint is still passed. - if wpnumber>self.currentwp then - self:_PassedFinalWaypoint(false, "NAVYGROUP:AddWaypoint wpnumber>self.currentwp") - end - -- Speed in knots. Speed=Speed or self:GetSpeedCruise() -- Create a Naval waypoint. - local wp=Coordinate:WaypointNaval(UTILS.KnotsToKmph(Speed), Depth) + local wp=coordinate:WaypointNaval(UTILS.KnotsToKmph(Speed), Depth) -- Create waypoint data table. local waypoint=self:_CreateWaypoint(wp) @@ -1141,7 +1128,7 @@ function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Up -- Update route. if Updateroute==nil or Updateroute==true then - self:_CheckGroupDone(1) + self:__UpdateRoute(-0.01) end return waypoint diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index aef995c88..9d511f1f5 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -402,6 +402,7 @@ OPSGROUP.TaskType={ -- @field #number roaddist Distance to closest point on road. -- @field Wrapper.Marker#MARKER marker Marker on the F10 map. -- @field #string formation Ground formation. Similar to action but on/off road. +-- @field #number missionUID Mission UID (Auftragsnr) this waypoint belongs to. --- Cargo Carrier status. -- @type OPSGROUP.CarrierStatus @@ -2502,6 +2503,18 @@ function OPSGROUP:RemoveWaypoint(wpindex) -- Number of waypoints before delete. local N=#self.waypoints + + -- Always keep at least one waypoint. + if N==1 then + self:E(self.lid..string.format("ERROR: Cannot remove waypoint with index=%d! It is the only waypoint and a group needs at least ONE waypoint", wpindex)) + return self + end + + -- Check that wpindex is not larger than the number of waypoints in the table. + if wpindex>N then + self:E(self.lid..string.format("ERROR: Cannot remove waypoint with index=%d as there are only N=%d waypoints!", wpindex, N)) + return self + end -- Remove waypoint marker. local wp=self:GetWaypoint(wpindex) @@ -2525,12 +2538,14 @@ function OPSGROUP:RemoveWaypoint(wpindex) -- Removed a FUTURE waypoint --- - -- TODO: patrol adinfinitum. + -- TODO: patrol adinfinitum. Not sure this is handled correctly. If patrol adinfinitum and we have now only one WP left, we should at least go back. - if self.currentwp>=n then - self:_PassedFinalWaypoint(true, "Removed FUTURE waypoint") + -- Could be that the waypoint we are currently moving to was the LAST waypoint. Then we now passed the final waypoint. + if self.currentwp>=n and not self.adinfinitum then + self:_PassedFinalWaypoint(true, "Removed FUTURE waypoint we are currently moving to and that was the LAST waypoint") end + -- Check if group is done. self:_CheckGroupDone(1) else @@ -3199,6 +3214,9 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) --env.info("FF adding current task to queue") table.insert(self.taskqueue, Task) end + + -- Get mission of this task (if any). + local Mission=self:GetMissionByTaskID(self.taskcurrent) if Task.dcstask.id=="Formation" then @@ -3241,15 +3259,21 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Speed and altitude. local Speed=UTILS.KmphToKnots(Task.dcstask.params.speed or self.speedCruise) local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude) or nil + + local currUID=self:GetWaypointCurrent().uid -- New waypoint. + local wp=nil --#OPSGROUP.Waypoint if self.isFlightgroup then - FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + wp=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, currUID, Altitude) elseif self.isArmygroup then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + wp=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, currUID, Formation) elseif self.isNavygroup then - NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + wp=NAVYGROUP.AddWaypoint(self, Coordinate, Speed, currUID, Altitude) end + + -- Set mission UID. + wp.missionUID=Mission and Mission.auftragsnummer or nil elseif Task.dcstask.id=="ReconMission" then @@ -3272,16 +3296,22 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude) or nil --Coordinate:MarkToAll("Next waypoint", ReadOnly,Text) + + local currUID=self:GetWaypointCurrent().uid -- New waypoint. + local wp=nil --#OPSGROUP.Waypoint if self.isFlightgroup then - FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + wp=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, currUID, Altitude) elseif self.isArmygroup then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + wp=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, currUID, Formation) elseif self.isNavygroup then - NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + wp=NAVYGROUP.AddWaypoint(self, Coordinate, Speed, currUID, Altitude) end - + + -- Set mission UID. + wp.missionUID=Mission and Mission.auftragsnummer or nil + else -- If task is scheduled (not waypoint) set task. @@ -3328,10 +3358,9 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) end - -- Get mission of this task (if any). - local Mission=self:GetMissionByTaskID(self.taskcurrent) + + -- Set AUFTRAG status. if Mission then - -- Set AUFTRAG status. self:MissionExecute(Mission) end @@ -3922,7 +3951,15 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) -- Remove mission waypoint. local wpidx=Mission:GetGroupWaypointIndex(self) if wpidx then - self:RemoveWaypointByID(wpidx) + --self:RemoveWaypointByID(wpidx) + end + + for i=#self.waypoints,1,-1 do + local wp=self.waypoints[i] --#OPSGROUP.Waypoint + if wp.missionUID==Mission.auftragsnummer then + --table.remove(self.waypoints, i) + self:RemoveWaypoint(i) + end end -- Decrease patrol data. @@ -4097,7 +4134,7 @@ function OPSGROUP:RouteToMission(mission, delay) --waypointcoord:MarkToAll(string.format("Mission %s alt=%d m", mission:GetName(), waypointcoord.y)) -- Add waypoint. - local waypoint=self:AddWaypoint(waypointcoord, SpeedToMission, nil, formation, false) ; waypoint.ismission=true + local waypoint=self:AddWaypoint(waypointcoord, SpeedToMission, uid, formation, false) ; waypoint.missionUID=mission.auftragsnummer -- Add waypoint task. UpdateRoute is called inside. local waypointtask=self:AddTaskWaypoint(mission.DCStask, waypoint, mission.name, mission.prio, mission.duration) @@ -4109,9 +4146,10 @@ function OPSGROUP:RouteToMission(mission, delay) -- Set waypoint index. mission:SetGroupWaypointIndex(self, waypoint.uid) + -- Add egress waypoint. local egress=mission:GetMissionEgressCoord() if egress then - local waypoint=self:AddWaypoint(egress, SpeedToMission, nil, formation, false) ; waypoint.ismission=true + local waypointEgress=self:AddWaypoint(egress, SpeedToMission, waypoint.uid, formation, false) ; waypointEgress.missionUID=mission.auftragsnummer end --- @@ -4287,6 +4325,12 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Get the current task. local task=self:GetTaskCurrent() + + -- Get the corresponding mission. + local mission=nil --Ops.Auftrag#AUFTRAG + if task then + mission=self:GetMissionByTaskID(task.id) + end if task and task.dcstask.id=="PatrolZone" then @@ -4302,14 +4346,18 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Speed and altitude. local Speed=UTILS.KmphToKnots(task.dcstask.params.speed or self.speedCruise) local Altitude=task.dcstask.params.altitude and UTILS.MetersToFeet(task.dcstask.params.altitude) or nil + + local currUID=self:GetWaypointCurrent().uid + local wp=nil --#OPSGROUP.Waypoint if self.isFlightgroup then - FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + wp=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, currUID, Altitude) elseif self.isArmygroup then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + wp=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, currUID, Formation) elseif self.isNavygroup then - NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + wp=NAVYGROUP.AddWaypoint(self, Coordinate, Speed, currUID, Altitude) end + wp.missionUID=mission and mission.auftragsnummer or nil elseif task and task.dcstask.id=="ReconMission" then @@ -4332,14 +4380,18 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Debug. --Coordinate:MarkToAll("Recon Waypoint n="..tostring(n)) + + local currUID=self:GetWaypointCurrent().uid + local wp=nil --#OPSGROUP.Waypoint if self.isFlightgroup then - FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + wp=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, currUID, Altitude) elseif self.isArmygroup then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + wp=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, currUID, Formation) elseif self.isNavygroup then - NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + wp=NAVYGROUP.AddWaypoint(self, Coordinate, Speed, currUID, Altitude) end + wp.missionUID=mission and mission.auftragsnummer or nil -- Increase counter. task.dcstask.params.lastindex=task.dcstask.params.lastindex+1 @@ -4365,26 +4417,43 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) end else + + --- + -- No special task active + --- -- Apply tasks of this waypoint. local ntasks=self:_SetWaypointTasks(Waypoint) -- Get waypoint index. - local wpindex=self:GetWaypointIndex(Waypoint.uid) + local wpindex=self:GetWaypointIndex(Waypoint.uid) -- Final waypoint reached? if wpindex==nil or wpindex==#self.waypoints then -- Set switch to true. - if not self.adinfinitum or #self.waypoints<=1 then + if self.adinfinitum then + if #self.waypoints<=1 then + self:_PassedFinalWaypoint(true, "PassingWaypoint: adinfinitum but only ONE WAYPOINT left") + else + local uid=self:GetWaypointID(1) + self:GotoWaypoint(uid) + end + else self:_PassedFinalWaypoint(true, "PassingWaypoint: wpindex=nil or wpindex=#self.waypoints") end end + + -- Passing mission waypoint? + if Waypoint.missionUID then + self:T(self.lid.."FF passing mission waypoint") + --self:RemoveWaypointByID(Waypoint.uid) + end -- Check if all tasks/mission are done? -- Note, we delay it for a second to let the OnAfterPassingwaypoint function to be executed in case someone wants to add another waypoint there. - if ntasks==0 and (self:HasPassedFinalWaypoint()) then-- or self:IsArmygroup() or self:IsNavygroup()) then + if ntasks==0 and self:HasPassedFinalWaypoint() then self:_CheckGroupDone(0.01) end @@ -7958,7 +8027,6 @@ function OPSGROUP:_CreateWaypoint(waypoint) local taskswp={} -- At each waypoint report passing. - --local TaskPassingWaypoint=self.group:TaskFunction("OPSGROUP._PassingWaypoint", self, waypoint.uid) local TaskPassingWaypoint=self:_SimpleTaskFunction("OPSGROUP._PassingWaypoint", waypoint.uid) table.insert(taskswp, TaskPassingWaypoint) @@ -7988,7 +8056,7 @@ function OPSGROUP:_AddWaypoint(waypoint, wpnumber) -- Now we obviously did not pass the final waypoint. if self.currentwp and wpnumber>self.currentwp then - self:_PassedFinalWaypoint(false, "_AddWaypoint self.currentwp and wpnumber>self.currentwp") + self:_PassedFinalWaypoint(false, string.format("_AddWaypoint: wpnumber/index %d>%d self.currentwp", wpnumber, self.currentwp)) end end @@ -8017,7 +8085,7 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) local wp=self.waypoints0[i] --DCS#Waypoint -- Coordinate of the waypoint. - local coordinate=COORDINATE:NewFromWaypoint(wp) + local Coordinate=COORDINATE:NewFromWaypoint(wp) -- Strange! wp.speed=wp.speed or 0 @@ -8025,14 +8093,25 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) -- Speed at the waypoint. local speedknots=UTILS.MpsToKnots(wp.speed) - if i==1 then + -- Expected speed to the first waypoint. + if i<=2 then self.speedWp=wp.speed end - local waypoint=self:_CreateWaypoint(wp) + -- Speed in knots. + local Speed=UTILS.MpsToKnots(wp.speed) + + --local waypoint=self:_CreateWaypoint(wp) + --self:_AddWaypoint(waypoint) + + if self:IsFlightgroup() then + FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, nil, Altitude, false) + elseif self:IsArmygroup() then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, nil, wp.action, false) + elseif self:IsNavygroup() then + NAVYGROUP.AddWaypoint(self, Coordinate, Speed, nil, Depth, false) + end - self:_AddWaypoint(waypoint) - end -- Debug info. @@ -8052,6 +8131,7 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) table.remove(self.waypoints, #self.waypoints) end + -- Set destination to homebase. if self.destbase==nil then self.destbase=self.homebase end @@ -9890,7 +9970,8 @@ function OPSGROUP:_CoordinateFromObject(Object) else if Object:IsInstanceOf("POSITIONABLE") or Object:IsInstanceOf("ZONE_BASE") then self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate") - return Object:GetCoordinate() + local coord=Object:GetCoordinate() + return coord else self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!") end From 0b5edfc21f1ae2d16dacf29aa66bb12aa67bd2bc Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 4 Sep 2021 23:51:17 +0200 Subject: [PATCH 091/141] OPSGROUP - Ad infinitum fixes - Turn into wind fixes --- Moose Development/Moose/Ops/ArmyGroup.lua | 34 +++------ Moose Development/Moose/Ops/FlightGroup.lua | 23 +++--- Moose Development/Moose/Ops/NavyGroup.lua | 31 ++++---- Moose Development/Moose/Ops/OpsGroup.lua | 84 ++++++++++++--------- 4 files changed, 89 insertions(+), 83 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index bfed3b5f4..74004faaa 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -589,10 +589,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #number n Waypoint number. Default is next waypoint. +-- @param #number n Next waypoint index. Default is the one coming after that one that has been passed last. +-- @param #number N Waypoint Max waypoint index to be included in the route. Default is the final waypoint. -- @param #number Speed Speed in knots. Default cruise speed. -- @param #number Formation Formation of the group. -function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Formation) +function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, N, Speed, Formation) if self:IsWaiting() then self:E(self.lid.."Update route denied. Group is WAIRING!") return false @@ -614,20 +615,22 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #number n Waypoint number. Default is next waypoint. +-- @param #number n Next waypoint index. Default is the one coming after that one that has been passed last. +-- @param #number N Waypoint Max waypoint index to be included in the route. Default is the final waypoint. -- @param #number Speed Speed in knots. Default cruise speed. -- @param #number Formation Formation of the group. -function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) +function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Debug info. - local text=string.format("Update route state=%s: n=%s, Speed=%s, Formation=%s", self:GetState(), tostring(n), tostring(Speed), tostring(Formation)) + local text=string.format("Update route state=%s: n=%s, N=%s, Speed=%s, Formation=%s", self:GetState(), tostring(n), tostring(N), tostring(Speed), tostring(Formation)) self:T(self.lid..text) -- Update route from this waypoint number onwards. n=n or self:GetWaypointIndexNext(self.adinfinitum) - -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. OBSOLETE! - --self:_UpdateWaypointTasks(n) + -- Max index. + N=N or #self.waypoints + N=math.min(N, #self.waypoints) -- Waypoints. local waypoints={} @@ -740,26 +743,13 @@ function ARMYGROUP:onafterGotoWaypoint(From, Event, To, UID, Speed, Formation) local n=self:GetWaypointIndex(UID) - --env.info(string.format("FF AG Goto waypoint UID=%s Index=%s, Speed=%s, Formation=%s", tostring(UID), tostring(n), tostring(Speed), tostring(Formation))) - if n then - - -- TODO: switch to re-enable waypoint tasks. - if false then - local tasks=self:GetTasksWaypoint(n) - - for _,_task in pairs(tasks) do - local task=_task --Ops.OpsGroup#OPSGROUP.Task - task.status=OPSGROUP.TaskStatus.SCHEDULED - end - - end -- Speed to waypoint. Speed=Speed or self:GetSpeedToWaypoint(n) -- Update the route. - self:UpdateRoute(n, Speed, Formation) + self:__UpdateRoute(-0.01, n, nil, Speed, Formation) end @@ -1132,7 +1122,7 @@ function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation) self.Twaiting=nil self.dTwait=nil - self:__UpdateRoute(-1, nil, Speed, Formation) + self:__UpdateRoute(-1, nil, nil, Speed, Formation) end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 5e68b039a..6baeee182 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1970,9 +1970,10 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #number n Waypoint number. +-- @param #number n Next waypoint index. Default is the one coming after that one that has been passed last. +-- @param #number N Waypoint Max waypoint index to be included in the route. Default is the final waypoint. -- @return #boolean Transision allowed? -function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) +function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n, N) -- Is transition allowed? We assume yes until proven otherwise. local allowed=true @@ -2012,8 +2013,8 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) allowed=false end - local N=n or self.currentwp+1 - if not N or N<1 then + local Nn=n or self.currentwp+1 + if not Nn or Nn<1 then self:E(self.lid.."Update route denied because N=nil or N<1") trepeat=-5 allowed=false @@ -2071,14 +2072,16 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #number n Waypoint number. Default is next waypoint. -function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) +-- @param #number n Next waypoint index. Default is the one coming after that one that has been passed last. +-- @param #number N Waypoint Max waypoint index to be included in the route. Default is the final waypoint. +function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n, N) -- Update route from this waypoint number onwards. n=n or self.currentwp+1 - -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. - self:_UpdateWaypointTasks(n) + -- Max index. + N=N or #self.waypoints + N=math.min(N, #self.waypoints) -- Waypoints. local wp={} @@ -2100,10 +2103,8 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) local current=self.group:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO, waypointType, waypointAction, speed, true, nil, {}, "Current") table.insert(wp, current) - local Nwp=self.waypoints and #self.waypoints or 0 - -- Add remaining waypoints to route. - for i=n, Nwp do + for i=n, N do table.insert(wp, self.waypoints[i]) end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 61fcb4db9..12e58abdb 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -710,12 +710,13 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #number n Waypoint number. Default is next waypoint. +-- @param #number n Next waypoint index. Default is the one coming after that one that has been passed last. +-- @param #number N Waypoint Max waypoint index to be included in the route. Default is the final waypoint. -- @param #number Speed Speed in knots to the next waypoint. -- @param #number Depth Depth in meters to the next waypoint. function NAVYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Depth) if self:IsWaiting() then - self:E(self.lid.."Update route denied. Group is WAIRING!") + self:E(self.lid.."Update route denied. Group is WAITING!") return false elseif self:IsInUtero() then self:E(self.lid.."Update route denied. Group is INUTERO!") @@ -735,21 +736,24 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #number n Waypoint number. Default is next waypoint. +-- @param #number n Next waypoint index. Default is the one coming after that one that has been passed last. +-- @param #number N Waypoint Max waypoint index to be included in the route. Default is the final waypoint. -- @param #number Speed Speed in knots to the next waypoint. -- @param #number Depth Depth in meters to the next waypoint. -function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) +function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Depth) -- Update route from this waypoint number onwards. n=n or self:GetWaypointIndexNext() - -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. - --self:_UpdateWaypointTasks(n) + -- Max index. + N=N or #self.waypoints + N=math.min(N, #self.waypoints) + -- Waypoints. local waypoints={} - for i=n, #self.waypoints do + for i=n, N do -- Waypoint. local wp=UTILS.DeepCopy(self.waypoints[i]) --Ops.OpsGroup#OPSGROUP.Waypoint @@ -956,7 +960,8 @@ function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To, IntoWindData) -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid - self:AddWaypoint(self.intowind.Coordinate, self:GetSpeedCruise(), uid) + -- Add temp waypoint. + local wp=self:AddWaypoint(self.intowind.Coordinate, self:GetSpeedCruise(), uid) ; wp.temp=true else @@ -966,11 +971,11 @@ function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To, IntoWindData) -- Next waypoint index and speed. local indx=self:GetWaypointIndexNext() - local speed=self:GetWaypointSpeed(indx) + local speed=self:GetSpeedToWaypoint(indx) -- Update route. self:T(self.lid..string.format("FF Turn Into Wind Over ==> Next WP Index=%d at %.1f knots via update route!", indx, speed)) - self:__UpdateRoute(-1, indx, speed) + self:__UpdateRoute(-1, indx, nil, speed) end @@ -1018,7 +1023,7 @@ function NAVYGROUP:onafterCruise(From, Event, To, Speed) -- No set depth. self.depth=nil - self:__UpdateRoute(-1, nil, Speed) + self:__UpdateRoute(-1, nil, nil, Speed) end @@ -1037,7 +1042,7 @@ function NAVYGROUP:onafterDive(From, Event, To, Depth, Speed) self.depth=Depth - self:__UpdateRoute(-1, nil, Speed) + self:__UpdateRoute(-1, nil, nil, Speed) end @@ -1051,7 +1056,7 @@ function NAVYGROUP:onafterSurface(From, Event, To, Speed) self.depth=0 - self:__UpdateRoute(-1, nil, Speed) + self:__UpdateRoute(-1, nil, nil, Speed) end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 9d511f1f5..8120f4ad8 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2411,7 +2411,7 @@ function OPSGROUP:GetSpeedToWaypoint(indx) local speed=self:GetWaypointSpeed(indx) - if speed<=0.1 then + if speed<=0.01 then speed=self:GetSpeedCruise() end @@ -4333,6 +4333,10 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) end if task and task.dcstask.id=="PatrolZone" then + + --- + -- SPECIAL TASK: Patrol Zone + --- -- Remove old waypoint. self:RemoveWaypointByID(Waypoint.uid) @@ -4360,6 +4364,10 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) wp.missionUID=mission and mission.auftragsnummer or nil elseif task and task.dcstask.id=="ReconMission" then + + --- + -- SPECIAL TASK: Recon Mission + --- local target=task.dcstask.params.target --Ops.Target#TARGET @@ -4412,7 +4420,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) end -- Final zone reached ==> task done. - self:TaskDone(task) + self:TaskDone(task) end @@ -4431,24 +4439,41 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Final waypoint reached? if wpindex==nil or wpindex==#self.waypoints then - -- Set switch to true. + -- Ad infinitum? if self.adinfinitum then + + --- + -- Ad Infinitum and last waypoint reached. + --- + if #self.waypoints<=1 then + -- Only one waypoint. Ad infinitum does not really make sense. However, another waypoint could be added later... self:_PassedFinalWaypoint(true, "PassingWaypoint: adinfinitum but only ONE WAYPOINT left") else - local uid=self:GetWaypointID(1) - self:GotoWaypoint(uid) - end - else - self:_PassedFinalWaypoint(true, "PassingWaypoint: wpindex=nil or wpindex=#self.waypoints") - end + + -- Looks like the passing waypoint function is triggered over and over again if the group is near the final waypoint. + -- So the only good solution is to guide the group away from that waypoint and then update the route. + -- Get first waypoint. + local wp1=self:GetWaypointByIndex(1) + + -- Get a waypoint + local Coordinate=Waypoint.coordinate:GetIntermediateCoordinate(wp1.coordinate, 0.1) + + -- Detour to the temp waypoint. When reached, the normal route is resumed. + self:Detour(Coordinate, self.speedCruise, nil, true) + + end + else + -- Final waypoint reached. + self:_PassedFinalWaypoint(true, "PassingWaypoint: wpindex=#self.waypoints (or wpindex=nil)") + end + end -- Passing mission waypoint? if Waypoint.missionUID then self:T(self.lid.."FF passing mission waypoint") - --self:RemoveWaypointByID(Waypoint.uid) end -- Check if all tasks/mission are done? @@ -4537,7 +4562,7 @@ function OPSGROUP:onafterPassedFinalWaypoint(From, Event, To) self:T(self.lid..string.format("Group passed final waypoint")) -- Check if group is done? No tasks mission running. - self:_CheckGroupDone() + --self:_CheckGroupDone() end @@ -4547,28 +4572,22 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #number UID The goto waypoint unique ID. -function OPSGROUP:onafterGotoWaypoint(From, Event, To, UID) +-- @param #number Speed (Optional) Speed to waypoint in knots. +function OPSGROUP:onafterGotoWaypoint(From, Event, To, UID, Speed) local n=self:GetWaypointIndex(UID) if n then - - -- TODO: Switch to re-enable waypoint tasks? - if false then - local tasks=self:GetTasksWaypoint(n) - - for _,_task in pairs(tasks) do - local task=_task --#OPSGROUP.Task - task.status=OPSGROUP.TaskStatus.SCHEDULED - end - - end - - local Speed=self:GetSpeedToWaypoint(n) + + -- Speed to waypoint. + Speed=Speed or self:GetSpeedToWaypoint(n) + + -- Debug message + self:T(self.lid..string.format("Goto Waypoint UID=%d index=%d from %d at speed %.1f knots", UID, n, self.currentwp, Speed)) -- Update the route. - self:__UpdateRoute(-1, n, Speed) - + self:__UpdateRoute(-0.01, n, nil, Speed) + end end @@ -6826,16 +6845,7 @@ function OPSGROUP:onafterTransport(From, Event, To) else -- Coord where the carrier goes to unload. - local Coordinate=nil --Core.Point#COORDINATE - - if self.cargoTransport.carrierGroup and self.cargoTransport.carrierGroup:IsLoading() then - --TODO: What the heck is self.cargoTransport.carrierGroup and where is this set?! - -- Coordinate of the new carrier. - Coordinate=self.cargoTransport.carrierGroup:GetCoordinate() - else - -- Get a random coordinate in the deploy zone and let the carrier go there. - Coordinate=Zone:GetRandomCoordinate() - end + local Coordinate=Zone:GetRandomCoordinate() --Core.Point#COORDINATE -- Add waypoint. if self:IsFlightgroup() then From 316ea910c28fdca02877cc486a111a7b31a56926 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 5 Sep 2021 00:23:12 +0200 Subject: [PATCH 092/141] Update Cohort.lua - Fixed some stuff for combat readiness check --- Moose Development/Moose/Ops/Cohort.lua | 52 +++++++++++++++++--------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index f19274936..f1c4fab58 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -830,35 +830,53 @@ function COHORT:RecruitAssets(Mission, Npayloads) --- local flightgroup=asset.flightgroup - - -- Firstly, check if it has the right payload. - if self:CheckMissionCapability(Mission.type, asset.payload.capabilities) and flightgroup and flightgroup:IsAlive() then + + + if flightgroup and flightgroup:IsAlive() then -- Assume we are ready and check if any condition tells us we are not. local combatready=true - - if Mission.type==AUFTRAG.Type.INTERCEPT then - combatready=flightgroup:CanAirToAir() - else - local excludeguns=Mission.type==AUFTRAG.Type.BOMBING or Mission.type==AUFTRAG.Type.BOMBRUNWAY or Mission.type==AUFTRAG.Type.BOMBCARPET or Mission.type==AUFTRAG.Type.SEAD or Mission.type==AUFTRAG.Type.ANTISHIP - combatready=flightgroup:CanAirToGround(excludeguns) - end - - -- No more attacks if fuel is already low. Safety first! - if flightgroup:IsFuelLow() then - combatready=false - end - + -- Check if in a state where we really do not want to fight any more. if flightgroup:IsFlightgroup() then + + --- + -- FLIGHTGROUP combat ready? + --- + + -- No more attacks if fuel is already low. Safety first! + if flightgroup:IsFuelLow() then + combatready=false + end + + if Mission.type==AUFTRAG.Type.INTERCEPT and not flightgroup:CanAirToAir() then + combatready=false + else + local excludeguns=Mission.type==AUFTRAG.Type.BOMBING or Mission.type==AUFTRAG.Type.BOMBRUNWAY or Mission.type==AUFTRAG.Type.BOMBCARPET or Mission.type==AUFTRAG.Type.SEAD or Mission.type==AUFTRAG.Type.ANTISHIP + if excludeguns and not flightgroup:CanAirToGround(excludeguns) then + combatready=false + end + end + if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() then combatready=false - end + end + if asset.payload and not self:CheckMissionCapability(Mission.type, asset.payload.capabilities) then + combatready=false + end + else + + --- + -- ARMY/NAVYGROUP combat ready? + --- + if flightgroup:IsRearming() or flightgroup:IsRetreating() or flightgroup:IsReturning() then combatready=false end + end + -- Applies to all opsgroups. if flightgroup:IsDead() or flightgroup:IsStopped() then combatready=false From 81120abfcb33f5981c58b9d535753167a58c16d9 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 6 Sep 2021 23:24:26 +0200 Subject: [PATCH 093/141] OPS - LEGION and COMMANDER: Recruit assets for mission --- Moose Development/Moose/Ops/Cohort.lua | 18 +- Moose Development/Moose/Ops/Commander.lua | 230 +++++++++++++++++++- Moose Development/Moose/Ops/FlightGroup.lua | 1 + Moose Development/Moose/Ops/Legion.lua | 161 +++++++++++++- Moose Development/Moose/Ops/OpsGroup.lua | 117 +++++++--- 5 files changed, 486 insertions(+), 41 deletions(-) diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index f1c4fab58..a62edb854 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -753,7 +753,7 @@ end --- Count assets in legion warehouse stock. -- @param #COHORT self --- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted. +-- @param #boolean InStock If `true`, only assets that are in the warehouse stock/inventory are counted. If `false`, only assets that are NOT in stock (i.e. spawned) are counted. If `nil`, all assets are counted. -- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. -- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. -- @return #number Number of assets. @@ -766,11 +766,13 @@ function COHORT:CountAssets(InStock, MissionTypes, Attributes) if MissionTypes==nil or self:CheckMissionCapability(MissionTypes, self.missiontypes) then if Attributes==nil or self:CheckAttribute(Attributes) then if asset.spawned then - if not InStock then + if InStock==true or InStock==nil then N=N+1 --Spawned but we also count the spawned ones. end else - N=N+1 --This is in stock. + if InStock==false or InStock==nil then + N=N+1 --This is in stock. + end end end end @@ -784,6 +786,7 @@ end -- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @param #number Npayloads Number of payloads available. -- @return #table Assets that can do the required mission. +-- @return #number Number of payloads still available after recruiting the assets. function COHORT:RecruitAssets(Mission, Npayloads) -- Number of payloads available. @@ -796,8 +799,8 @@ function COHORT:RecruitAssets(Mission, Npayloads) for _,_asset in pairs(self.assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - -- First check that asset is not requested. This could happen if multiple requests are processed simultaniously. - if not asset.requested then + -- First check that asset is not requested or reserved. This could happen if multiple requests are processed simultaniously. + if not (asset.requested or asset.isReserved) then -- Check if asset is currently on a mission (STARTED or QUEUED). @@ -815,7 +818,7 @@ function COHORT:RecruitAssets(Mission, Npayloads) self:I(self.lid.."Adding asset on GCICAP mission for an INTERCEPT mission") table.insert(assets, asset) - end + end else @@ -829,6 +832,7 @@ function COHORT:RecruitAssets(Mission, Npayloads) -- Asset is already SPAWNED (could be uncontrolled on the airfield or inbound after another mission) --- + -- Opsgroup. local flightgroup=asset.flightgroup @@ -916,7 +920,7 @@ function COHORT:RecruitAssets(Mission, Npayloads) end -- not requested check end -- loop over assets - return assets + return assets, Npayloads end diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 67086fa42..8025fdcf0 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -495,12 +495,35 @@ function COMMANDER:CheckMissionQueue() -- 1. Select best assets from legions --- + -- Recruite assets from legions. + local recruited, legions=self:RecruitAssets(mission) + + if recruited then + + for _,_legion in pairs(legions) do + local legion=_legion --Ops.Legion#LEGION + + -- Debug message. + self:I(self.lid..string.format("Assigning mission %s [%s] to legion %s", mission:GetName(), mission:GetType(), legion.alias)) + + -- Add mission to legion. + self:MissionAssign(legion, mission) + + end + + -- Only ONE mission is assigned. + return + end + + if false then + -- Get legions for mission. - local legions=self:GetLegionsForMission(mission) + local Legions=self:GetLegionsForMission(mission) -- Get ALL assets from pre-selected legions. local assets=self:GetAssets(InStock, legions, MissionTypes, Attributes) + -- Now we select the best assets from all legions. legions={} if #assets>=mission.nassets then @@ -582,6 +605,8 @@ function COMMANDER:CheckMissionQueue() return end + end -- if false then + else --- @@ -702,6 +727,209 @@ function COMMANDER:GetAssets(InStock, Legions, MissionTypes, Attributes) return assets end +--- Recruit assets for a given mission. +-- @param #COMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return #boolean If `true` enough assets could be recruited. +-- @return #table Legions that have recruited assets. +function COMMANDER:RecruitAssets(Mission) + + env.info("FF recruit assets") + + -- The recruited assets. + local Assets={} + + local legions=Mission.mylegions or self.legions + + local Legions={} + + for _,_legion in pairs(legions) do + local legion=_legion --Ops.Legion#LEGION + + -- Number of payloads in stock per aircraft type. + local Npayloads={} + + -- First get payloads for aircraft types of squadrons. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + if Npayloads[cohort.aircrafttype]==nil then + Npayloads[cohort.aircrafttype]=legion:IsAirwing() and legion:CountPayloadsInStock(Mission.type, cohort.aircrafttype, Mission.payloads) or 999 + self:I(self.lid..string.format("Got Npayloads=%d for type=%s", Npayloads[cohort.aircrafttype], cohort.aircrafttype)) + end + end + + -- Loops over cohorts. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + local npayloads=Npayloads[cohort.aircrafttype] + + if cohort:CanMission(Mission) and npayloads>0 then + + env.info("FF npayloads="..Npayloads[cohort.aircrafttype]) + + -- Recruit assets from squadron. + local assets, npayloads=cohort:RecruitAssets(Mission, npayloads) + + Npayloads[cohort.aircrafttype]=npayloads + + env.info("FF npayloads="..Npayloads[cohort.aircrafttype]) + + for _,asset in pairs(assets) do + table.insert(Assets, asset) + end + + end + + end + + end + + -- Now we have a long list with assets. + self:_OptimizeAssetSelection(Assets, Mission, false) + + for _,_asset in pairs(Assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + if asset.legion:IsAirwing() then + + -- Only assets that have no payload. Should be only spawned assets! + if not asset.payload then + + -- Fetch payload for asset. This can be nil! + asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, Mission.type, Mission.payloads) + + end + + end + + end + + -- Remove assets that dont have a payload. + for i=#Assets,1,-1 do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.payload then + table.remove(Assets, i) + end + end + + -- Now find the best asset for the given payloads. + self:_OptimizeAssetSelection(Assets, Mission, true) + + local Nassets=Mission:GetRequiredAssets(self) + + if #Assets>=Nassets then + + --- + -- Found enough assets + --- + + -- Add assets to mission. + for i=1,Nassets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + self:I(self.lid..string.format("Adding asset %s to mission %s [%s]", asset.spawngroupname, Mission.name, Mission.type)) + Mission:AddAsset(asset) + Legions[asset.legion.alias]=asset.legion + end + + + -- Return payloads of not needed assets. + for i=Nassets+1,#Assets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.spawned then + self:I(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + asset.legion:ReturnPayloadFromAsset(asset) + end + end + + -- Found enough assets. + return true, Legions + else + + --- + -- NOT enough assets + --- + + -- Return payloads of assets. + if self:IsAirwing() then + for i=1,#Assets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.spawned then + self:I(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + asset.legion:ReturnPayloadFromAsset(asset) + end + end + end + + -- Not enough assets found. + return false, {} + end + + return nil, {} +end + +--- Optimize chosen assets for the mission at hand. +-- @param #COMMANDER self +-- @param #table assets Table of (unoptimized) assets. +-- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. +-- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. +function COMMANDER:_OptimizeAssetSelection(assets, Mission, includePayload) + + -- Get target position. + local TargetVec2=Mission:GetTargetVec2() + + -- Calculate distance to mission target. + local distmin=math.huge + local distmax=0 + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + if asset.spawned then + local group=GROUP:FindByName(asset.spawngroupname) + asset.dist=UTILS.VecDist2D(group:GetVec2(), TargetVec2) + else + asset.dist=UTILS.VecDist2D(asset.legion:GetVec2(), TargetVec2) + end + + if asset.distdistmax then + distmax=asset.dist + end + + end + + -- Calculate the mission score of all assets. + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.score=asset.legion:CalculateAssetMissionScore(asset, Mission, includePayload) + end + + --- Sort assets wrt to their mission score. Higher is better. + local function optimize(a, b) + local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem + local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Higher score wins. If equal score ==> closer wins. + -- TODO: Need to include the distance in a smarter way! + return (assetA.score>assetB.score) or (assetA.score==assetB.score and assetA.dist0 then + + env.info("FF npayloads="..Npayloads[cohort.aircrafttype]) + + -- Recruit assets from squadron. + local assets, npayloads=cohort:RecruitAssets(Mission, npayloads) + + Npayloads[cohort.aircrafttype]=npayloads + + env.info("FF npayloads="..Npayloads[cohort.aircrafttype]) + + for _,asset in pairs(assets) do + table.insert(Assets, asset) + end + + end + + end + + -- Now we have a long list with assets. + self:_OptimizeAssetSelection(Assets, Mission, false) + + -- If airwing, get the best payload available. + if self:IsAirwing() then + + for _,_asset in pairs(Assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Only assets that have no payload. Should be only spawned assets! + if not asset.payload then + + -- Fetch payload for asset. This can be nil! + asset.payload=self:FetchPayloadFromStock(asset.unittype, Mission.type, Mission.payloads) + + end + + end + + -- Remove assets that dont have a payload. + for i=#Assets,1,-1 do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if not asset.payload then + table.remove(Assets, i) + end + end + + -- Now find the best asset for the given payloads. + self:_OptimizeAssetSelection(Assets, Mission, true) + + end + + local Nassets=Mission:GetRequiredAssets(self) + + if #Assets>=Nassets then + + --- + -- Found enough assets + --- + + -- Add assets to mission. + for i=1,Nassets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + self:I(self.lid..string.format("Adding asset %s to mission %s [%s]", asset.spawngroupname, Mission.name, Mission.type)) + Mission:AddAsset(asset) + end + + if self:IsAirwing() then + + -- Return payloads of not needed assets. + for i=Nassets+1,#Assets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if not asset.spawned then + self:I(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + self:ReturnPayloadFromAsset(asset) + end + end + + end + + -- Found enough assets. + return true + else + + --- + -- NOT enough assets + --- + + -- Return payloads of assets. + if self:IsAirwing() then + for i=1,#Assets do + local asset=Assets[i] + if not asset.spawned then + self:I(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + self:ReturnPayloadFromAsset(asset) + end + end + end + + -- Not enough assets found. + return false + end + end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8120f4ad8..f6999e6d3 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -706,6 +706,46 @@ function OPSGROUP:New(group) -- @param #number delay Delay in seconds. + --- Triggers the FSM event "MissionStart". + -- @function [parent=#OPSGROUP] MissionStart + -- @param #OPSGROUP self + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "MissionStart" after a delay. + -- @function [parent=#OPSGROUP] __MissionStart + -- @param #OPSGROUP self + -- @param #number delay Delay in seconds. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "MissionStart" event. + -- @function [parent=#OPSGROUP] OnAfterMissionStart + -- @param #OPSGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + + --- Triggers the FSM event "MissionExecute". + -- @function [parent=#OPSGROUP] MissionExecute + -- @param #OPSGROUP self + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "MissionExecute" after a delay. + -- @function [parent=#OPSGROUP] __MissionExecute + -- @param #OPSGROUP self + -- @param #number delay Delay in seconds. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "MissionExecute" event. + -- @function [parent=#OPSGROUP] OnAfterMissionExecute + -- @param #OPSGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "MissionCancel". -- @function [parent=#OPSGROUP] MissionCancel -- @param #OPSGROUP self @@ -725,6 +765,26 @@ function OPSGROUP:New(group) -- @param #string To To state. -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "MissionDone". + -- @function [parent=#OPSGROUP] MissionDone + -- @param #OPSGROUP self + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "MissionDone" after a delay. + -- @function [parent=#OPSGROUP] __MissionDone + -- @param #OPSGROUP self + -- @param #number delay Delay in seconds. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "MissionDone" event. + -- @function [parent=#OPSGROUP] OnAfterMissionDone + -- @param #OPSGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + -- TODO: Add pseudo functions. return self @@ -3295,7 +3355,7 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) local Speed=UTILS.KmphToKnots(Task.dcstask.params.speed or self.speedCruise) local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude) or nil - --Coordinate:MarkToAll("Next waypoint", ReadOnly,Text) + Coordinate:MarkToAll("Recon Waypoint Execute") local currUID=self:GetWaypointCurrent().uid @@ -3484,7 +3544,11 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) self:T(self.lid.."Task Done ==> Mission Done!") self:MissionDone(Mission) else - --Mission paused. Do nothing! + --Mission paused. Do nothing! Just set the current mission to nil so we can launch a new one. + if self.currentmission and self.currentmission==Mission.auftragsnummer then + self.currentmission=nil + end + self:_RemoveMissionWaypoints(Mission, false) end else @@ -3798,17 +3862,6 @@ function OPSGROUP:onafterMissionStart(From, Event, To, Mission) end ---- On before "MissionExecute" event. --- @param #OPSGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.Auftrag#AUFTRAG Mission The mission table. -function OPSGROUP:onbeforeMissionExecute(From, Event, To, Mission) - - return true -end - --- On after "MissionExecute" event. Mission execution began. -- @param #OPSGROUP self -- @param #string From From state. @@ -3928,12 +3981,31 @@ function OPSGROUP:onafterMissionCancel(From, Event, To, Mission) end +--- On after "MissionDone" event. +-- @param #OPSGROUP self +-- @param Ops.Auftrag#AUFTRAG Mission +-- @param #boolean Silently Remove waypoints by `table.remove()` and do not update the route. +function OPSGROUP:_RemoveMissionWaypoints(Mission, Silently) + + for i=#self.waypoints,1,-1 do + local wp=self.waypoints[i] --#OPSGROUP.Waypoint + if wp.missionUID==Mission.auftragsnummer then + if Silently then + table.remove(self.waypoints, i) + else + self:RemoveWaypoint(i) + end + end + end + +end + --- On after "MissionDone" event. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.Auftrag#AUFTRAG Mission +-- @param Ops.Auftrag#AUFTRAG Mission The mission that is done. function OPSGROUP:onafterMissionDone(From, Event, To, Mission) -- Debug info. @@ -3947,20 +4019,9 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) if self.currentmission and Mission.auftragsnummer==self.currentmission then self.currentmission=nil end - - -- Remove mission waypoint. - local wpidx=Mission:GetGroupWaypointIndex(self) - if wpidx then - --self:RemoveWaypointByID(wpidx) - end - for i=#self.waypoints,1,-1 do - local wp=self.waypoints[i] --#OPSGROUP.Waypoint - if wp.missionUID==Mission.auftragsnummer then - --table.remove(self.waypoints, i) - self:RemoveWaypoint(i) - end - end + -- Remove mission waypoints. + self:_RemoveMissionWaypoints(Mission) -- Decrease patrol data. if Mission.patroldata then @@ -4387,7 +4448,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) local Altitude=task.dcstask.params.altitude and UTILS.MetersToFeet(task.dcstask.params.altitude) or nil -- Debug. - --Coordinate:MarkToAll("Recon Waypoint n="..tostring(n)) + Coordinate:MarkToAll("Recon Waypoint n="..tostring(n)) local currUID=self:GetWaypointCurrent().uid From bdf13f29f7d030a8656dcfe61b27c7e824303ef6 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 6 Sep 2021 23:40:58 +0200 Subject: [PATCH 094/141] OPS - COMMANDER and LEGION clean up of obsolete stuff for recruiting assets. --- Moose Development/Moose/Ops/Commander.lua | 111 ++-------------------- Moose Development/Moose/Ops/Legion.lua | 103 +------------------- Moose Development/Moose/Ops/OpsGroup.lua | 4 +- 3 files changed, 12 insertions(+), 206 deletions(-) diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 8025fdcf0..e0ce6daab 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -489,10 +489,9 @@ function COMMANDER:CheckMissionQueue() --- -- PLANNNED Mission - --- - - --- + -- -- 1. Select best assets from legions + -- 2. Assign mission to legions that have the best assets. --- -- Recruite assets from legions. @@ -515,98 +514,6 @@ function COMMANDER:CheckMissionQueue() return end - if false then - - -- Get legions for mission. - local Legions=self:GetLegionsForMission(mission) - - -- Get ALL assets from pre-selected legions. - local assets=self:GetAssets(InStock, legions, MissionTypes, Attributes) - - - -- Now we select the best assets from all legions. - legions={} - if #assets>=mission.nassets then - - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, mission.type, mission.payloads) - asset.score=asset.legion:CalculateAssetMissionScore(asset, mission, true) - end - - --- Sort assets wrt to their mission score. Higher is better. - local function optimize(assetA, assetB) - return (assetA.score>assetB.score) - end - table.sort(assets, optimize) - - -- Remove distance parameter. - local text=string.format("Optimized assets for %s mission:", mission.type) - for i,Asset in pairs(assets) do - local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Score text. - text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) - - -- Nillify score. - asset.score=nil - - -- Add assets to mission. - if i<=mission.nassets then - - -- Add asset to mission. - mission:AddAsset(Asset) - - -- Put into table. - legions[asset.legion.alias]=asset.legion - - -- Number of assets requested from this legion. - -- TODO: Check if this is really necessary as we do not go through the selection process. - mission.Nassets=mission.Nassets or {} - if mission.Nassets[asset.legion.alias] then - mission.Nassets[asset.legion.alias]=mission.Nassets[asset.legion.alias]+1 - else - mission.Nassets[asset.legion.alias]=1 - end - - else - - -- Return payload of asset (if any). - if asset.payload then - asset.legion:ReturnPayloadFromAsset(asset) - end - - end - end - self:T2(self.lid..text) - - else - self:T2(self.lid..string.format("Not enough assets available for mission")) - end - - --- - -- Assign Mission to Legions - --- - - if legions then - - for _,_legion in pairs(legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Debug message. - self:I(self.lid..string.format("Assigning mission %s [%s] to legion %s", mission:GetName(), mission:GetType(), legion.alias)) - - -- Add mission to legion. - self:MissionAssign(legion, mission) - - end - - -- Only ONE mission is assigned. - return - end - - end -- if false then - else --- @@ -733,14 +640,14 @@ end -- @return #boolean If `true` enough assets could be recruited. -- @return #table Legions that have recruited assets. function COMMANDER:RecruitAssets(Mission) - - env.info("FF recruit assets") -- The recruited assets. local Assets={} + -- Legions we consider for selecting assets. local legions=Mission.mylegions or self.legions + -- Legions which have the best assets for the Mission. local Legions={} for _,_legion in pairs(legions) do @@ -766,15 +673,11 @@ function COMMANDER:RecruitAssets(Mission) if cohort:CanMission(Mission) and npayloads>0 then - env.info("FF npayloads="..Npayloads[cohort.aircrafttype]) - -- Recruit assets from squadron. local assets, npayloads=cohort:RecruitAssets(Mission, npayloads) Npayloads[cohort.aircrafttype]=npayloads - env.info("FF npayloads="..Npayloads[cohort.aircrafttype]) - for _,asset in pairs(assets) do table.insert(Assets, asset) end @@ -827,7 +730,7 @@ function COMMANDER:RecruitAssets(Mission) -- Add assets to mission. for i=1,Nassets do local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - self:I(self.lid..string.format("Adding asset %s to mission %s [%s]", asset.spawngroupname, Mission.name, Mission.type)) + self:T(self.lid..string.format("Adding asset %s to mission %s [%s]", asset.spawngroupname, Mission.name, Mission.type)) Mission:AddAsset(asset) Legions[asset.legion.alias]=asset.legion end @@ -837,7 +740,7 @@ function COMMANDER:RecruitAssets(Mission) for i=Nassets+1,#Assets do local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem if asset.legion:IsAirwing() and not asset.spawned then - self:I(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) asset.legion:ReturnPayloadFromAsset(asset) end end @@ -855,7 +758,7 @@ function COMMANDER:RecruitAssets(Mission) for i=1,#Assets do local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem if asset.legion:IsAirwing() and not asset.spawned then - self:I(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + self:T2(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) asset.legion:ReturnPayloadFromAsset(asset) end end diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 6cbb5d8f3..7ffce3b98 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -432,98 +432,7 @@ function LEGION:_GetNextMission() if recruited then return mission end - - -- OBSOLETE - if false then - -- Check if legion can do the mission and gather required assets. - local can, assets=self:CanMission(mission) - - -- Check that mission is still scheduled, time has passed and enough assets are available. - if can then - - -- Number of required assets. - local Nassets=mission:GetRequiredAssets(self) - - -- Optimize the asset selection. Most useful assets will come first. We do not include the payload as some assets have and some might not. - self:_OptimizeAssetSelection(assets, mission, false) - - -- Assign assets to mission. - local remove={} - local gotpayload={} - if self:IsAirwing() then - for i=1,#assets do - local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Get payload for the asset. - if not asset.payload then - local payload=self:FetchPayloadFromStock(asset.unittype, mission.type, mission.payloads) - if payload then - asset.payload=payload - table.insert(gotpayload, asset.uid) - else - table.insert(remove, asset.uid) - end - end - end - self:T(self.lid..string.format("Provided %d assets with payloads. Could not get payload for %d assets", #gotpayload, #remove)) - - -- Now remove assets for which we don't have a payload. - for i=#assets,1,-1 do - local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - for _,uid in pairs(remove) do - if uid==asset.uid then - table.remove(assets, i) - end - end - end - - -- Another check. - if #assets0 then - self:E(self.lid..string.format("ERROR: mission %s of type %s has already assets attached!", mission.name, mission.type)) - end - --mission.assets={} - - -- Assign assets to mission. - for i=1,Nassets do - local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Should not happen as we just checked! - if self:IsAirwing() and not asset.payload then - self:E(self.lid.."ERROR: No payload for asset! This should not happen!") - end - - -- Add asset to mission. - mission:AddAsset(asset) - end - - -- Now return the remaining payloads. - if self:IsAirwing() then - for i=Nassets+1,#assets do - local asset=assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - for _,uid in pairs(gotpayload) do - if uid==asset.uid then - self:ReturnPayloadFromAsset(asset) - break - end - end - end - end - - return mission - end - - end -- OBSOLETE end -- mission due? end -- mission loop @@ -1765,8 +1674,6 @@ end -- @return #boolean If `true` enough assets could be recruited. function LEGION:RecruitAssets(Mission) - env.info("FF recruit assets") - -- Number of payloads in stock per aircraft type. local Npayloads={} @@ -1790,15 +1697,11 @@ function LEGION:RecruitAssets(Mission) if cohort:CanMission(Mission) and npayloads>0 then - env.info("FF npayloads="..Npayloads[cohort.aircrafttype]) - -- Recruit assets from squadron. local assets, npayloads=cohort:RecruitAssets(Mission, npayloads) Npayloads[cohort.aircrafttype]=npayloads - env.info("FF npayloads="..Npayloads[cohort.aircrafttype]) - for _,asset in pairs(assets) do table.insert(Assets, asset) end @@ -1850,7 +1753,7 @@ function LEGION:RecruitAssets(Mission) -- Add assets to mission. for i=1,Nassets do local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - self:I(self.lid..string.format("Adding asset %s to mission %s [%s]", asset.spawngroupname, Mission.name, Mission.type)) + self:T(self.lid..string.format("Adding asset %s to mission %s [%s]", asset.spawngroupname, Mission.name, Mission.type)) Mission:AddAsset(asset) end @@ -1860,7 +1763,7 @@ function LEGION:RecruitAssets(Mission) for i=Nassets+1,#Assets do local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem if not asset.spawned then - self:I(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) self:ReturnPayloadFromAsset(asset) end end @@ -1880,7 +1783,7 @@ function LEGION:RecruitAssets(Mission) for i=1,#Assets do local asset=Assets[i] if not asset.spawned then - self:I(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) self:ReturnPayloadFromAsset(asset) end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index f6999e6d3..e8e51836f 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3355,7 +3355,7 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) local Speed=UTILS.KmphToKnots(Task.dcstask.params.speed or self.speedCruise) local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude) or nil - Coordinate:MarkToAll("Recon Waypoint Execute") + --Coordinate:MarkToAll("Recon Waypoint Execute") local currUID=self:GetWaypointCurrent().uid @@ -4448,7 +4448,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) local Altitude=task.dcstask.params.altitude and UTILS.MetersToFeet(task.dcstask.params.altitude) or nil -- Debug. - Coordinate:MarkToAll("Recon Waypoint n="..tostring(n)) + --Coordinate:MarkToAll("Recon Waypoint n="..tostring(n)) local currUID=self:GetWaypointCurrent().uid From aecb92ccd390cd412057454c2444b5801f3ef193 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 7 Sep 2021 11:17:20 +0200 Subject: [PATCH 095/141] OPSGROUP - Added EPLRS option - EPLRS is on if group has datalink capability --- Moose Development/Moose/Ops/ArmyGroup.lua | 5 ++ Moose Development/Moose/Ops/Auftrag.lua | 16 ++++ Moose Development/Moose/Ops/Commander.lua | 16 +++- Moose Development/Moose/Ops/FlightGroup.lua | 15 ++-- Moose Development/Moose/Ops/NavyGroup.lua | 10 ++- Moose Development/Moose/Ops/OpsGroup.lua | 82 +++++++++++++++++++-- 6 files changed, 128 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 74004faaa..a08bd3a43 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -100,6 +100,7 @@ function ARMYGROUP:New(group) -- Defaults self:SetDefaultROE() self:SetDefaultAlarmstate() + self:SetDefaultEPLRS(self.isEPLRS) self:SetDetection() self:SetPatrolAdInfinitum(false) self:SetRetreatZones() @@ -532,6 +533,7 @@ function ARMYGROUP:onafterSpawned(From, Event, To) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) + text=text..string.format("Has EPLRS = %s\n", tostring(self.isEPLRS)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) @@ -557,6 +559,9 @@ function ARMYGROUP:onafterSpawned(From, Event, To) -- Set default Alarm State. self:SwitchAlarmstate(self.option.Alarm) + -- Set default EPLRS. + self:SwitchEPLRS(self.option.EPLRS) + -- Set TACAN to default. self:_SwitchTACAN() diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 5ebec3140..4f19064fa 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -131,6 +131,7 @@ -- @field #number optionROT ROT. -- @field #number optionAlarm Alarm state. -- @field #number optionFormation Formation. +-- @field #boolean optionEPLRS EPLRS datalink. -- @field #number optionCM Counter measures. -- @field #number optionRTBammo RTB on out-of-ammo. -- @field #number optionRTBfuel RTB on out-of-fuel. @@ -1805,6 +1806,21 @@ function AUFTRAG:SetAlarmstate(Alarmstate) return self end +--- Set EPLRS datalink setting for this mission. +-- @param #AUFTRAG self +-- @param #boolean OnOffSwitch If `true` or `nil`, EPLRS is on. If `false`, EPLRS is off. +-- @return #AUFTRAG self +function AUFTRAG:SetEPLRS(OnOffSwitch) + + if OnOffSwitch==nil then + self.optionEPLRS=true + else + self.optionEPLRS=OnOffSwitch + end + + return self +end + --- Set formation for this mission. -- @param #AUFTRAG self -- @param #number Formation Formation. diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index e0ce6daab..23e2bbb66 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -48,9 +48,9 @@ COMMANDER.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Improve legion selection. Mostly done! --- TODO: Allow multiple Legions for one mission. --- TODO: Add ops transports. -- 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: Allow multiple Legions for one mission. -- NOGO: Maybe it's possible to preselect the assets for the mission. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -183,6 +183,18 @@ function COMMANDER:AddAirwing(Airwing) return self end +--- Add an BRIGADE to the commander. +-- @param #COMMANDER self +-- @param Ops.Brigade#BRIGADE Briagde The brigade to add. +-- @return #COMMANDER self +function COMMANDER:AddBrigade(Brigade) + + -- Add legion. + self:AddLegion(Brigade) + + return self +end + --- Add a LEGION to the commander. -- @param #COMMANDER self -- @param Ops.Legion#LEGION Legion The legion to add. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index d9738ae11..b71e5e711 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -215,13 +215,14 @@ function FLIGHTGROUP:New(group) self.lid=string.format("FLIGHTGROUP %s | ", self.groupname) -- Defaults + self:SetDefaultROE() + self:SetDefaultROT() + self:SetDefaultEPLRS(self.isEPLRS) + self:SetDetection() self:SetFuelLowThreshold() self:SetFuelLowRTB() self:SetFuelCriticalThreshold() - self:SetFuelCriticalRTB() - self:SetDefaultROE() - self:SetDefaultROT() - self:SetDetection() + self:SetFuelCriticalRTB() -- Holding flag. self.flaghold=USERFLAG:New(string.format("%s_FlagHold", self.groupname)) @@ -1580,7 +1581,8 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) text=text..string.format("Tanker type = %s\n", tostring(self.tankertype)) text=text..string.format("Refuel type = %s\n", tostring(self.refueltype)) text=text..string.format("AI = %s\n", tostring(self.isAI)) - text=text..string.format("Helicopter = %s\n", tostring(self.group:IsHelicopter())) + text=text..string.format("Has EPLRS = %s\n", tostring(self.isEPLRS)) + text=text..string.format("Helicopter = %s\n", tostring(self.isHelo)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) @@ -1611,6 +1613,9 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) -- Set ROT. self:SwitchROT(self.option.ROT) + -- Set default EPLRS. + self:SwitchEPLRS(self.option.EPLRS) + -- Set Formation self:SwitchFormation(self.option.Formation) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 12e58abdb..af093826d 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -121,9 +121,10 @@ function NAVYGROUP:New(group) self.lid=string.format("NAVYGROUP %s | ", self.groupname) -- Defaults - self:SetDetection() self:SetDefaultROE() self:SetDefaultAlarmstate() + self:SetDefaultEPLRS(self.isEPLRS) + self:SetDetection() self:SetPatrolAdInfinitum(true) self:SetPathfinding(false) @@ -654,7 +655,9 @@ function NAVYGROUP:onafterSpawned(From, Event, To) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) - text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) + text=text..string.format("Has EPLRS = %s\n", tostring(self.isEPLRS)) + text=text..string.format("Is Submarine = %s\n", tostring(self.isSubmarine)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) @@ -680,6 +683,9 @@ function NAVYGROUP:onafterSpawned(From, Event, To) -- Set default Alarm State. self:SwitchAlarmstate(self.option.Alarm) + -- Set default EPLRS. + self:SwitchEPLRS(self.option.EPLRS) + -- Set TACAN beacon. self:_SwitchTACAN() diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index e8e51836f..f1815e991 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -97,7 +97,7 @@ -- @field #OPSGROUP.Beacon iclsDefault Default ICLS settings. -- -- @field #OPSGROUP.Option option Current optional settings. --- @field #OPSGROUP.Option optionDefault Default option settings. +-- @field #OPSGROUP.Option optionDefault Default option settings. -- -- @field #OPSGROUP.Callsign callsign Current callsign settings. -- @field #OPSGROUP.Callsign callsignDefault Default callsign settings. @@ -297,11 +297,6 @@ OPSGROUP.TaskType={ -- @field Core.UserFlag#USERFLAG stopflag If flag is set to 1 (=true), the task is stopped. -- @field #number backupROE Rules of engagement that are restored once the task is over. ---- Enroute task. --- @type OPSGROUP.EnrouteTask --- @field DCS#Task DCStask DCS task structure table. --- @field #number WaypointIndex Waypoint number at which the enroute task is added. - --- Beacon data. -- @type OPSGROUP.Beacon -- @field #number Channel Channel. @@ -550,6 +545,12 @@ function OPSGROUP:New(group) -- Set type name. self.actype=masterunit:GetTypeName() + -- Is this a submarine. + self.isSubmarine=masterunit:HasAttribute("Submarines") + + -- Has this a datalink? + self.isEPLRS=masterunit:HasAttribute("Datalink") + if self:IsFlightgroup() then self.rangemax=self.descriptors.range and self.descriptors.range*1000 or 500*1000 @@ -4041,6 +4042,10 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) if Mission.optionAlarm then self:SwitchAlarmstate() end + -- Alarm state to default. + if Mission.optionEPLRS then + self:SwitchEPLRS() + end -- Formation to default. if Mission.optionFormation then self:SwitchFormation() @@ -4225,10 +4230,14 @@ function OPSGROUP:RouteToMission(mission, delay) if mission.optionROT then self:SwitchROT(mission.optionROT) end - -- Alarm state. + -- Alarm state if mission.optionAlarm then self:SwitchAlarmstate(mission.optionAlarm) end + -- EPLRS + if mission.optionEPLRS then + self:SwitchEPLRS(mission.optionEPLRS) + end -- Formation if mission.optionFormation and self:IsFlightgroup() then self:SwitchFormation(mission.optionFormation) @@ -8710,6 +8719,65 @@ function OPSGROUP:GetAlarmstate() return self.option.Alarm or self.optionDefault.Alarm end +--- Set the default Alarm State for the group. This is the state gets when the group is spawned or to which it defaults back after a mission. +-- @param #OPSGROUP self +-- @param #boolean OnOffSwitch If `true`, EPLRS is on by default. If `false` default EPLRS setting is off. If `nil`, default is on if group has EPLRS and off if it does not have a datalink. +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultEPLRS(OnOffSwitch) + + if OnOffSwitch==nil then + self.optionDefault.EPLRS=self.isEPLRS + else + self.optionDefault.EPLRS=OnOffSwitch + end + + return self +end + +--- Switch EPLRS datalink on or off. +-- @param #OPSGROUP self +-- @param #boolean OnOffSwitch If `true` or `nil`, switch EPLRS on. If `false` EPLRS switched off. +-- @return #OPSGROUP self +function OPSGROUP:SwitchEPLRS(OnOffSwitch) + + if self:IsAlive() or self:IsInUtero() then + + if OnOffSwitch==nil then + + self.option.EPLRS=self.optionDefault.EPLRS + + else + + self.option.EPLRS=OnOffSwitch + + end + + if self:IsInUtero() then + self:T2(self.lid..string.format("Setting current EPLRS=%s when GROUP is SPAWNED", tostring(self.option.EPLRS))) + else + + self.group:CommandEPLRS(self.option.EPLRS) + self:T(self.lid..string.format("Setting current EPLRS=%s", tostring(self.option.EPLRS))) + + end + else + self:E(self.lid.."WARNING: Cannot switch Alarm State! Group is not alive") + end + + return self +end + +--- Get current EPLRS state. +-- @param #OPSGROUP self +-- @return #boolean If `true`, EPLRS is on. +function OPSGROUP:GetEPLRS() + return self.option.EPLRS or self.optionDefault.EPLRS +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- SETTINGS FUNCTIONS +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Set default TACAN parameters. -- @param #OPSGROUP self -- @param #number Channel TACAN channel. Default is 74. From b0c2e5409a07d757e4bd25521beff826ec84fa5b Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 8 Sep 2021 11:49:27 +0200 Subject: [PATCH 096/141] OPS AUFTRAG - Added Alert 5 mission - Added Fuel supply mission - Added Ammo supply mission --- Moose Development/Moose/Ops/AirWing.lua | 2 - Moose Development/Moose/Ops/Auftrag.lua | 248 +++++++++++++++++--- Moose Development/Moose/Ops/Brigade.lua | 56 +++++ Moose Development/Moose/Ops/Cohort.lua | 6 + Moose Development/Moose/Ops/Commander.lua | 52 ++-- Moose Development/Moose/Ops/FlightGroup.lua | 19 +- Moose Development/Moose/Ops/Legion.lua | 118 +++++----- Moose Development/Moose/Ops/OpsGroup.lua | 127 +++++++--- 8 files changed, 456 insertions(+), 172 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index a7f103147..1555b3229 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -43,8 +43,6 @@ -- -- === -- --- ![Banner Image](..\Presentations\OPS\AirWing\_Main.png) --- -- # The AIRWING Concept -- -- An AIRWING consists of multiple SQUADRONS. These squadrons "live" in a WAREHOUSE, i.e. a physical structure that is connected to an airbase (airdrome, FRAP or ship). diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 4f19064fa..37de1edad 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -331,6 +331,10 @@ _AUFTRAGSNR=0 -- @field #string ARTY Fire at point. -- @field #string PATROLZONE Patrol a zone. -- @field #string OPSTRANSPORT Ops transport. +-- @field #string AMMOSUPPLY Ammo supply. +-- @field #string FUELSUPPLY Fuel supply. +-- @field #string ALERT5 Alert 5. +-- @field #string ONWATCH On watch. AUFTRAG.Type={ ANTISHIP="Anti Ship", AWACS="AWACS", @@ -356,6 +360,27 @@ AUFTRAG.Type={ ARTY="Fire At Point", PATROLZONE="Patrol Zone", OPSTRANSPORT="Ops Transport", + AMMOSUPPLY="Ammo Supply", + FUELSUPPLY="Fuel Supply", + ALERT5="Alert 5", + ONWATCH="On Watch", +} + +--- Mission status of an assigned group. +-- @type AUFTRAG.GroupStatus +-- @field #string PATROLZONE Patrol zone task. +-- @field #string RECON Recon task +-- @field #string AMMOSUPPLY Ammo Supply. +-- @field #string FUELSUPPLY Fuel Supply. +-- @field #string ALERT5 Alert 5 task. +-- @field #string ONWATCH On Watch +AUFTRAG.SpecialTask={ + PATROLZONE="PatrolZone", + RECON="ReconMission", + AMMOSUPPLY="Ammo Supply", + FUELSUPPLY="Fuel Supply", + ALERT5="Alert 5", + ONWATCH="On Watch", } --- Mission status. @@ -458,17 +483,17 @@ AUFTRAG.TargetType={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.7.1" +AUFTRAG.version="0.8.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Replace engageRange by missionRange. Here and in other classes. CTRL+H is your friend! --- TODO: Missions can be assigned to multiple legions. -- TODO: Mission success options damaged, destroyed. -- TODO: F10 marker to create new missions. -- TODO: Add recovery tanker mission for boat ops. +-- DONE: Missions can be assigned to multiple legions. -- DONE: Option to assign a specific payload for the mission (requires an AIRWING). -- NOPE: Clone mission. How? Deepcopy? ==> Create a new auftrag. -- DONE: Recon mission. What input? Set of coordinates? @@ -570,7 +595,7 @@ end -- Create Missions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create an ANTI-SHIP mission. +--- **[AIR]** Create an ANTI-SHIP mission. -- @param #AUFTRAG self -- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be passed as a @{Wrapper.Group#GROUP} or @{Wrapper.Unit#UNIT} object. -- @param #number Altitude Engage altitude in feet. Default 2000 ft. @@ -598,7 +623,7 @@ function AUFTRAG:NewANTISHIP(Target, Altitude) return mission end ---- Create an ORBIT mission, which can be either a circular orbit or a race-track pattern. +--- **[AIR]** Create an ORBIT mission, which can be either a circular orbit or a race-track pattern. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Where to orbit. -- @param #number Altitude Orbit altitude in feet. Default is y component of `Coordinate`. @@ -640,7 +665,7 @@ function AUFTRAG:NewORBIT(Coordinate, Altitude, Speed, Heading, Leg) return mission end ---- Create an ORBIT mission, where the aircraft will go in a circle around the specified coordinate. +--- **[AIR]** Create an ORBIT mission, where the aircraft will go in a circle around the specified coordinate. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Position where to orbit around. -- @param #number Altitude Orbit altitude in feet. Default is y component of `Coordinate`. @@ -653,7 +678,7 @@ function AUFTRAG:NewORBIT_CIRCLE(Coordinate, Altitude, Speed) return mission end ---- Create an ORBIT mission, where the aircraft will fly a race-track pattern. +--- **[AIR]** Create an ORBIT mission, where the aircraft will fly a race-track pattern. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Where to orbit. -- @param #number Altitude Orbit altitude in feet. Default is y component of `Coordinate`. @@ -671,7 +696,7 @@ function AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg) return mission end ---- Create a Ground Controlled CAP (GCICAP) mission. Flights with this task are considered for A2A INTERCEPT missions by the CHIEF class. They will perform a compat air patrol but not engage by +--- **[AIR]** Create a Ground Controlled CAP (GCICAP) mission. Flights with this task are considered for A2A INTERCEPT missions by the CHIEF class. They will perform a compat air patrol but not engage by -- themselfs. They wait for the CHIEF to tell them whom to engage. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Where to orbit. @@ -697,7 +722,7 @@ function AUFTRAG:NewGCICAP(Coordinate, Altitude, Speed, Heading, Leg) return mission end ---- Create a TANKER mission. +--- **[AIR]** Create a TANKER mission. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Where to orbit. -- @param #number Altitude Orbit altitude in feet. Default is y component of `Coordinate`. @@ -728,7 +753,7 @@ function AUFTRAG:NewTANKER(Coordinate, Altitude, Speed, Heading, Leg, RefuelSyst return mission end ---- Create a AWACS mission. +--- **[AIR]** Create a AWACS mission. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Where to orbit. Altitude is also taken from the coordinate. -- @param #number Altitude Orbit altitude in feet. Default is y component of `Coordinate`. @@ -758,7 +783,7 @@ end ---- Create an INTERCEPT mission. +--- **[AIR]** Create an INTERCEPT mission. -- @param #AUFTRAG self -- @param Wrapper.Positionable#POSITIONABLE Target The target to intercept. Can also be passed as simple @{Wrapper.Group#GROUP} or @{Wrapper.Unit#UNIT} object. -- @return #AUFTRAG self @@ -779,7 +804,7 @@ function AUFTRAG:NewINTERCEPT(Target) return mission end ---- Create a CAP mission. +--- **[AIR]** Create a CAP mission. -- @param #AUFTRAG self -- @param Core.Zone#ZONE_RADIUS ZoneCAP Circular CAP zone. Detected targets in this zone will be engaged. -- @param #number Altitude Altitude at which to orbit in feet. Default is 10,000 ft. @@ -819,7 +844,7 @@ function AUFTRAG:NewCAP(ZoneCAP, Altitude, Speed, Coordinate, Heading, Leg, Targ return mission end ---- Create a CAS mission. +--- **[AIR]** Create a CAS mission. -- @param #AUFTRAG self -- @param Core.Zone#ZONE_RADIUS ZoneCAS Circular CAS zone. Detected targets in this zone will be engaged. -- @param #number Altitude Altitude at which to orbit. Default is 10,000 ft. @@ -859,7 +884,7 @@ function AUFTRAG:NewCAS(ZoneCAS, Altitude, Speed, Coordinate, Heading, Leg, Targ return mission end ---- Create a FACA mission. +--- **[AIR]** Create a FACA mission. -- @param #AUFTRAG self -- @param Wrapper.Group#GROUP Target Target group. Must be a GROUP object. -- @param #string Designation Designation of target. See `AI.Task.Designation`. Default `AI.Task.Designation.AUTO`. @@ -894,7 +919,7 @@ function AUFTRAG:NewFACA(Target, Designation, DataLink, Frequency, Modulation) end ---- Create a BAI mission. +--- **[AIR]** Create a BAI mission. -- @param #AUFTRAG self -- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be a GROUP, UNIT or STATIC object. -- @param #number Altitude Engage altitude in feet. Default 2000 ft. @@ -922,7 +947,7 @@ function AUFTRAG:NewBAI(Target, Altitude) return mission end ---- Create a SEAD mission. +--- **[AIR]** Create a SEAD mission. -- @param #AUFTRAG self -- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be a GROUP or UNIT object. -- @param #number Altitude Engage altitude in feet. Default 2000 ft. @@ -951,7 +976,7 @@ function AUFTRAG:NewSEAD(Target, Altitude) return mission end ---- Create a STRIKE mission. Flight will attack the closest map object to the specified coordinate. +--- **[AIR]** Create a STRIKE mission. Flight will attack the closest map object to the specified coordinate. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC or TARGET object. -- @param #number Altitude Engage altitude in feet. Default 2000 ft. @@ -979,7 +1004,7 @@ function AUFTRAG:NewSTRIKE(Target, Altitude) return mission end ---- Create a BOMBING mission. Flight will drop bombs a specified coordinate. +--- **[AIR]** Create a BOMBING mission. Flight will drop bombs a specified coordinate. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC or TARGET object. -- @param #number Altitude Engage altitude in feet. Default 25000 ft. @@ -1011,7 +1036,7 @@ function AUFTRAG:NewBOMBING(Target, Altitude) return mission end ---- Create a BOMBRUNWAY mission. +--- **[AIR]** Create a BOMBRUNWAY mission. -- @param #AUFTRAG self -- @param Wrapper.Airbase#AIRBASE Airdrome The airbase to bomb. This must be an airdrome (not a FARP or ship) as these to not have a runway. -- @param #number Altitude Engage altitude in feet. Default 25000 ft. @@ -1047,7 +1072,7 @@ function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude) return mission end ---- Create a CARPET BOMBING mission. +--- **[AIR]** Create a CARPET BOMBING mission. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT or STATIC object. -- @param #number Altitude Engage altitude in feet. Default 25000 ft. @@ -1084,7 +1109,7 @@ function AUFTRAG:NewBOMBCARPET(Target, Altitude, CarpetLength) end ---- Create an ESCORT (or FOLLOW) mission. Flight will escort another group and automatically engage certain target types. +--- **[AIR]** Create an ESCORT (or FOLLOW) mission. Flight will escort another group and automatically engage certain target types. -- @param #AUFTRAG self -- @param Wrapper.Group#GROUP EscortGroup The group to escort. -- @param DCS#Vec3 OffsetVector A table with x, y and z components specifying the offset of the flight to the escorted group. Default {x=-100, y=0, z=200} for z=200 meters to the right, same alitude, x=100 meters behind. @@ -1114,7 +1139,7 @@ function AUFTRAG:NewESCORT(EscortGroup, OffsetVector, EngageMaxDistance, TargetT return mission end ---- Create a RESCUE HELO mission. +--- **[AIR ROTARY]** Create a RESCUE HELO mission. -- @param #AUFTRAG self -- @param Wrapper.Unit#UNIT Carrier The carrier unit. -- @return #AUFTRAG self @@ -1136,7 +1161,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier) end ---- Create a TROOP TRANSPORT mission. +--- **[AIR ROTARY]** Create a TROOP TRANSPORT mission. -- @param #AUFTRAG self -- @param Core.Set#SET_GROUP TransportGroupSet The set group(s) to be transported. -- @param Core.Point#COORDINATE DropoffCoordinate Coordinate where the helo will land drop off the the troops. @@ -1175,7 +1200,7 @@ function AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet, DropoffCoordinate, PickupC end ---- Create a OPS TRANSPORT mission. +--- **[AIR, GROUND, NAVAL]** Create a OPS TRANSPORT mission. -- @param #AUFTRAG self -- @param Core.Set#SET_GROUP CargoGroupSet The set group(s) to be transported. -- @param Core.Zone#ZONE PickupZone Pick up zone @@ -1189,13 +1214,6 @@ function AUFTRAG:NewOPSTRANSPORT(CargoGroupSet, PickupZone, DeployZone) mission:_TargetFromObject(mission.transportGroupSet) - --mission.transportPickup=PickupCoordinate or mission:GetTargetCoordinate() - --mission.transportDropoff=DropoffCoordinate - - -- Debug. - --mission.transportPickup:MarkToAll("Pickup") - --mission.transportDropoff:MarkToAll("Drop off") - mission.opstransport=OPSTRANSPORT:New(CargoGroupSet, PickupZone, DeployZone) function mission.opstransport:OnAfterExecuting(From, Event, To) @@ -1216,7 +1234,7 @@ function AUFTRAG:NewOPSTRANSPORT(CargoGroupSet, PickupZone, DeployZone) end ---- Create an ARTY mission. +--- **[GROUND, NAVAL]** Create an ARTY mission. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Target Center of the firing solution. -- @param #number Nshots Number of shots to be fired. Default 3. @@ -1246,7 +1264,7 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius) return mission end ---- Create a PATROLZONE mission. Group(s) will go to the zone and patrol it randomly. +--- **[AIR, GROUND, NAVAL]** Create a PATROLZONE mission. Group(s) will go to the zone and patrol it randomly. -- @param #AUFTRAG self -- @param Core.Zone#ZONE Zone The patrol zone. -- @param #number Speed Speed in knots. @@ -1276,7 +1294,7 @@ function AUFTRAG:NewPATROLZONE(Zone, Speed, Altitude) return mission end ---- Create a RECON mission. +--- **[AIR, GROUND, NAVAL]** Create a RECON mission. -- @param #AUFTRAG self -- @param Core.Set#SET_ZONE ZoneSet The recon zones. -- @param #number Speed Speed in knots. @@ -1305,6 +1323,70 @@ function AUFTRAG:NewRECON(ZoneSet, Speed, Altitude, Adinfinitum, Randomly) return mission end +--- **[GROUND]** Create a AMMO SUPPLY mission. +-- @param #AUFTRAG self +-- @param Core.Zone#ZONE Zone The zone, where supply units go. +-- @return #AUFTRAG self +function AUFTRAG:NewAMMOSUPPLY(Zone) + + local mission=AUFTRAG:New(AUFTRAG.Type.AMMOSUPPLY) + + mission:_TargetFromObject(Zone) + + mission.optionROE=ENUMS.ROE.WeaponHold + mission.optionAlarm=ENUMS.AlarmState.Auto + + mission.missionFraction=0.9 + + mission.DCStask=mission:GetDCSMissionTask() + + return mission +end + +--- **[GROUND]** Create a FUEL SUPPLY mission. +-- @param #AUFTRAG self +-- @param Core.Zone#ZONE Zone The zone, where supply units go. +-- @return #AUFTRAG self +function AUFTRAG:NewFUELSUPPLY(Zone) + + local mission=AUFTRAG:New(AUFTRAG.Type.FUELSUPPLY) + + mission:_TargetFromObject(Zone) + + mission.optionROE=ENUMS.ROE.WeaponHold + mission.optionAlarm=ENUMS.AlarmState.Auto + + mission.missionFraction=0.9 + + mission.DCStask=mission:GetDCSMissionTask() + + return mission +end + + +--- **[AIR]** Create an ALERT 5 mission. +-- @param #AUFTRAG self +-- @param #string MissionType Mission type `AUFTRAG.Type.XXX`. +-- @return #AUFTRAG self +function AUFTRAG:NewALERT5(MissionType) + + local mission=AUFTRAG:New(AUFTRAG.Type.ALERT5) + + --mission:_TargetFromObject(Coordinate) + + mission.missionTask=self:GetMissionTaskforMissionType(MissionType) + mission.optionROE=ENUMS.ROE.WeaponHold + mission.optionROT=ENUMS.ROT.NoReaction + + mission.alert5MissionType=MissionType + + mission.missionFraction=0.0 + + mission.DCStask=mission:GetDCSMissionTask() + + return mission +end + --- Create a mission to attack a group. Mission type is automatically chosen from the group category. -- @param #AUFTRAG self @@ -3996,6 +4078,59 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) DCStask.params=param table.insert(DCStasks, DCStask) + + elseif self.type==AUFTRAG.Type.AMMOSUPPLY then + + ------------------------- + -- AMMO SUPPLY Mission -- + ------------------------- + + local DCStask={} + + DCStask.id=AUFTRAG.SpecialTask.AMMOSUPPLY + + -- We create a "fake" DCS task and pass the parameters to the OPSGROUP. + local param={} + param.zone=self:GetObjective() + + DCStask.params=param + + table.insert(DCStasks, DCStask) + + elseif self.type==AUFTRAG.Type.FUELSUPPLY then + + ------------------------- + -- FUEL SUPPLY Mission -- + ------------------------- + + local DCStask={} + + DCStask.id=AUFTRAG.SpecialTask.FUELSUPPLY + + -- We create a "fake" DCS task and pass the parameters to the OPSGROUP. + local param={} + param.zone=self:GetObjective() + + DCStask.params=param + + table.insert(DCStasks, DCStask) + + elseif self.type==AUFTRAG.Type.ALERT5 then + + --------------------- + -- ALERT 5 Mission -- + --------------------- + + local DCStask={} + + DCStask.id=AUFTRAG.SpecialTask.ALERT5 + + -- We create a "fake" DCS task and pass the parameters to the OPSGROUP. + local param={} + + DCStask.params=param + + table.insert(DCStasks, DCStask) else self:E(self.lid..string.format("ERROR: Unknown mission task!")) @@ -4066,6 +4201,53 @@ function AUFTRAG:_GetDCSAttackTask(Target, DCStasks) return DCStasks end +--- Get DCS task table for an attack group or unit task. +-- @param #AUFTRAG self +-- @param #string MissionType Mission (AUFTAG) type. +-- @return #string DCS mission task for the auftrag type. +function AUFTRAG:GetMissionTaskforMissionType(MissionType) + + local mtask=ENUMS.MissionTask.NOTHING + + if MissionType==AUFTRAG.Type.ANTISHIP then + mtask=ENUMS.MissionTask.ANTISHIPSTRIKE + elseif MissionType==AUFTRAG.Type.AWACS then + mtask=ENUMS.MissionTask.AWACS + elseif MissionType==AUFTRAG.Type.BAI then + mtask=ENUMS.MissionTask.GROUNDATTACK + elseif MissionType==AUFTRAG.Type.BOMBCARPET then + mtask=ENUMS.MissionTask.GROUNDATTACK + elseif MissionType==AUFTRAG.Type.BOMBING then + mtask=ENUMS.MissionTask.GROUNDATTACK + elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then + mtask=ENUMS.MissionTask.RUNWAYATTACK + elseif MissionType==AUFTRAG.Type.CAP then + mtask=ENUMS.MissionTask.CAP + elseif MissionType==AUFTRAG.Type.CAS then + mtask=ENUMS.MissionTask.CAS + elseif MissionType==AUFTRAG.Type.ESCORT then + mtask=ENUMS.MissionTask.ESCORT + elseif MissionType==AUFTRAG.Type.FACA then + mtask=ENUMS.MissionTask.AFAC + elseif MissionType==AUFTRAG.Type.FERRY then + mtask=ENUMS.MissionTask.NOTHING + elseif MissionType==AUFTRAG.Type.INTERCEPT then + mtask=ENUMS.MissionTask.INTERCEPT + elseif MissionType==AUFTRAG.Type.RECON then + mtask=ENUMS.MissionTask.RECONNAISSANCE + elseif MissionType==AUFTRAG.Type.SEAD then + mtask=ENUMS.MissionTask.SEAD + elseif MissionType==AUFTRAG.Type.STRIKE then + mtask=ENUMS.MissionTask.GROUNDATTACK + elseif MissionType==AUFTRAG.Type.TANKER then + mtask=ENUMS.MissionTask.REFUELING + elseif MissionType==AUFTRAG.Type.TROOPTRANSPORT then + mtask=ENUMS.MissionTask.TRANSPORT + end + + return mtask +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index c94974e63..ce3a1a576 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -3,6 +3,8 @@ -- **Main Features:** -- -- * Manage platoons +-- * Carry out ARTY and PATROLZONE missions (AUFTRAG) +-- * Define rearming zones -- -- === -- @@ -16,6 +18,8 @@ -- @type BRIGADE -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity of output. +-- @field #table rearmingZones Rearming zones. Each element is of type `#BRIGADE.RearmingZone`. +-- @field Core.Set#SET_ZONE retreatZones Retreat zone set. -- @extends Ops.Legion#LEGION --- Be surprised! @@ -31,8 +35,14 @@ BRIGADE = { ClassName = "BRIGADE", verbose = 0, + rearmingZones = {}, } +--- Rearming Zone. +-- @type BRIGADE.RearmingZone +-- @field Core.Zone#ZONE zone The zone. +-- @field #boolean occupied If `true`, a rearming truck is present in the zone. +-- @field Wrapper.Marker#MARKER marker F10 marker. --- BRIGADE class version. -- @field #string version @@ -66,6 +76,9 @@ function BRIGADE:New(WarehouseName, BrigadeName) -- Set some string id for output to DCS.log file. self.lid=string.format("BRIGADE %s | ", self.alias) + + -- Defaults + self:SetRetreatZones() -- Add FSM transitions. -- From State --> Event --> To State @@ -180,6 +193,49 @@ function BRIGADE:AddAssetToPlatoon(Platoon, Nassets) return self end +--- Define a set of retreat zones. +-- @param #BRIGADE self +-- @param Core.Set#SET_ZONE RetreatZoneSet Set of retreat zones. +-- @return #BRIGADE self +function BRIGADE:SetRetreatZones(RetreatZoneSet) + self.retreatZones=RetreatZoneSet or SET_ZONE:New() + return self +end + +--- Add a retreat zone. +-- @param #BRIGADE self +-- @param Core.Zone#ZONE RetreatZone Retreat zone. +-- @return #BRIGADE self +function BRIGADE:AddRetreatZone(RetreatZone) + self.retreatZones:AddZone(RetreatZone) + return self +end + +--- Get retreat zones. +-- @param #BRIGADE self +-- @return Core.Set#SET_ZONE Set of retreat zones. +function BRIGADE:GetRetreatZones() + return self.retreatZones +end + +--- Add a patrol Point for CAP missions. +-- @param #BRIGADE self +-- @param Core.Zone#ZONE Rearming zone. +-- @return #AIRWING self +function BRIGADE:AddRearmingZone(RearmingZone) + + local rearmingzone={} --#BRIGADE.RearmingZone + + rearmingzone.zone=RearmingZone + rearmingzone.occupied=false + rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(), "Rearming Zone"):ToCoalition(self:GetCoalition()) + + table.insert(self.rearmingZones, rearmingzone) + + return self +end + + --- Get platoon by name. -- @param #BRIGADE self -- @param #string PlatoonName Name of the platoon. diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index a62edb854..969210f8c 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -818,6 +818,12 @@ function COHORT:RecruitAssets(Mission, Npayloads) self:I(self.lid.."Adding asset on GCICAP mission for an INTERCEPT mission") table.insert(assets, asset) + elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.ALERT5) and self:CheckMissionCapability(Mission.Type, asset.payload.capabilities) then + + -- Check if the payload of this asset is compatible with the mission. + self:I(self.lid.."Adding asset on ALERT 5 mission for XXX mission") + table.insert(assets, asset) + end else diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 23e2bbb66..b5ec7cb25 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -671,9 +671,13 @@ function COMMANDER:RecruitAssets(Mission) -- First get payloads for aircraft types of squadrons. for _,_cohort in pairs(legion.cohorts) do local cohort=_cohort --Ops.Cohort#COHORT - if Npayloads[cohort.aircrafttype]==nil then - Npayloads[cohort.aircrafttype]=legion:IsAirwing() and legion:CountPayloadsInStock(Mission.type, cohort.aircrafttype, Mission.payloads) or 999 - self:I(self.lid..string.format("Got Npayloads=%d for type=%s", Npayloads[cohort.aircrafttype], cohort.aircrafttype)) + if Npayloads[cohort.aircrafttype]==nil then + local MissionType=Mission.type + if MissionType==AUFTRAG.Type.ALERT5 then + MissionType=Mission.alert5MissionType + end + Npayloads[cohort.aircrafttype]=legion:IsAirwing() and legion:CountPayloadsInStock(MissionType, cohort.aircrafttype, Mission.payloads) or 999 + self:I(self.lid..string.format("Got N=%d payloads for mission type %s [%s]", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) end end @@ -711,8 +715,16 @@ function COMMANDER:RecruitAssets(Mission) -- Only assets that have no payload. Should be only spawned assets! if not asset.payload then + -- Set mission type. + local MissionType=Mission.Type + + -- Get a loadout for the actual mission this group is waiting for. + if Mission.type==AUFTRAG.Type.ALERT5 and Mission.alert5MissionType then + MissionType=Mission.alert5MissionType + end + -- Fetch payload for asset. This can be nil! - asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, Mission.type, Mission.payloads) + asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, MissionType, Mission.payloads) end @@ -790,32 +802,6 @@ end -- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. function COMMANDER:_OptimizeAssetSelection(assets, Mission, includePayload) - -- Get target position. - local TargetVec2=Mission:GetTargetVec2() - - -- Calculate distance to mission target. - local distmin=math.huge - local distmax=0 - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - if asset.spawned then - local group=GROUP:FindByName(asset.spawngroupname) - asset.dist=UTILS.VecDist2D(group:GetVec2(), TargetVec2) - else - asset.dist=UTILS.VecDist2D(asset.legion:GetVec2(), TargetVec2) - end - - if asset.distdistmax then - distmax=asset.dist - end - - end - -- Calculate the mission score of all assets. for _,_asset in pairs(assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem @@ -826,10 +812,8 @@ function COMMANDER:_OptimizeAssetSelection(assets, Mission, includePayload) local function optimize(a, b) local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem - -- Higher score wins. If equal score ==> closer wins. - -- TODO: Need to include the distance in a smarter way! - return (assetA.score>assetB.score) or (assetA.score==assetB.score and assetA.distassetB.score) end table.sort(assets, optimize) @@ -837,7 +821,7 @@ function COMMANDER:_OptimizeAssetSelection(assets, Mission, includePayload) local text=string.format("Optimized %d assets for %s mission (payload=%s):", #assets, Mission.type, tostring(includePayload)) for i,Asset in pairs(assets) do local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem - text=text..string.format("\n%s %s: score=%d, distance=%.1f km", asset.squadname, asset.spawngroupname, asset.score, asset.dist/1000) + text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) asset.dist=nil asset.score=nil end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index b71e5e711..a5e3b0036 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2001,18 +2001,25 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n, N) allowed=false end + -- Check if group is uncontrolled. If so, the mission task cannot be set yet! if allowed and self:IsUncontrolled() then - -- Not airborne yet. Try again in 5 sec. - self:T(self.lid.."Update route denied. Group is UNCONTROLLED ==> checking back in 5 sec") - trepeat=-5 + self:T(self.lid.."Update route denied. Group is UNCONTROLLED!") + local mission=self:GetMissionCurrent() + if mission and mission.type==AUFTRAG.Type.ALERT5 then + trepeat=nil --Alert 5 is just waiting for the real mission. No need to try to update the route. + else + trepeat=-5 + end allowed=false end + -- Requested waypoint index <1. Something is seriously wrong here! if n and n<1 then self:E(self.lid.."Update route denied because waypoint n<1!") allowed=false end + -- No current waypoint. Something is serously wrong! if not self.currentwp then self:E(self.lid.."Update route denied because self.currentwp=nil!") allowed=false @@ -2025,9 +2032,10 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n, N) allowed=false end + -- Check for a current task. if self.taskcurrent>0 then - --local task=self:GetTaskCurrent() + -- Get the current task. Must not be executing already. local task=self:GetTaskByID(self.taskcurrent) if task then @@ -2063,8 +2071,9 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n, N) end -- Debug info. - self:T2(self.lid..string.format("Onbefore Updateroute allowed=%s state=%s repeat in %s", tostring(allowed), self:GetState(), tostring(trepeat))) + self:T2(self.lid..string.format("Onbefore Updateroute in state %s: allowed=%s (repeat in %s)", self:GetState(), tostring(allowed), tostring(trepeat))) + -- Try again? if trepeat then self:__UpdateRoute(trepeat, n) end diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 7ffce3b98..2be104b6e 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -248,6 +248,11 @@ function LEGION:AddMission(Mission) -- Add legion to mission. Mission:AddLegion(self) + -- Set target for ALERT 5. + if Mission.type==AUFTRAG.Type.ALERT5 then + Mission:_TargetFromObject(self:GetCoordinate()) + end + --[[ if Mission.opstransport then Mission.opstransport:SetPickupZone(self.spawnzone) @@ -515,7 +520,8 @@ end -- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. -- @return #number Mission score. function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) - + + -- Mission score. local score=0 -- Prefer highly skilled assets. @@ -530,39 +536,38 @@ function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) end -- Add mission performance to score. - local cohort=self:_GetCohortOfAsset(asset) - local missionperformance=cohort:GetMissionPeformance(Mission.type) - score=score+missionperformance + score=score+asset.cohort:GetMissionPeformance(Mission.Type) -- Add payload performance to score. if includePayload and asset.payload then score=score+self:GetPayloadPeformance(asset.payload, Mission.type) end - + + -- Target position. + local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil --Mission:GetTargetVec2() + + -- Origin: We take the flightgroups position or the one of the legion. + local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2() or self:GetVec2() + + -- Distance factor. + local distance=0 + if TargetVec2 and OrigVec2 then + -- Distance in NM. + distance=UTILS.MetersToNM(UTILS.VecDist2D(OrigVec2, TargetVec2)) + -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 + distance=UTILS.Round(distance/10, 0) + end + + -- Reduce score for legions that are futher away. + score=score-distance + -- Intercepts need to be carried out quickly. We prefer spawned assets. - if Mission.type==AUFTRAG.Type.INTERCEPT then + if Mission.type==AUFTRAG.Type.INTERCEPT then if asset.spawned then self:T(self.lid.."Adding 25 to asset because it is spawned") score=score+25 end end - - -- Get coordinate of the target. - local coord=Mission:GetTargetCoordinate() - local dist=0 - if coord then - - -- Distance from legion to target. - local distance=UTILS.MetersToNM(coord:Get2DDistance(self:GetCoordinate())) - - -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 - dist=UTILS.Round(distance/10, 0) - - end - - -- Reduce score for legions that are futher away. - score=score-dist - -- TODO: This could be vastly improved. Need to gather ideas during testing. -- Calculate ETA? Assets on orbit missions should arrive faster even if they are further away. @@ -580,33 +585,6 @@ end -- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. function LEGION:_OptimizeAssetSelection(assets, Mission, includePayload) - local TargetVec2=Mission:GetTargetVec2() - - local dStock=UTILS.VecDist2D(TargetVec2, self:GetVec2()) - - -- Calculate distance to mission target. - local distmin=math.huge - local distmax=0 - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - if asset.spawned then - local group=GROUP:FindByName(asset.spawngroupname) - asset.dist=UTILS.VecDist2D(group:GetVec2(), TargetVec2) - else - asset.dist=dStock - end - - if asset.distdistmax then - distmax=asset.dist - end - - end - -- Calculate the mission score of all assets. for _,_asset in pairs(assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem @@ -617,10 +595,8 @@ function LEGION:_OptimizeAssetSelection(assets, Mission, includePayload) local function optimize(a, b) local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem - -- Higher score wins. If equal score ==> closer wins. - -- TODO: Need to include the distance in a smarter way! - return (assetA.score>assetB.score) or (assetA.score==assetB.score and assetA.distassetB.score) end table.sort(assets, optimize) @@ -628,7 +604,7 @@ function LEGION:_OptimizeAssetSelection(assets, Mission, includePayload) local text=string.format("Optimized %d assets for %s mission (payload=%s):", #assets, Mission.type, tostring(includePayload)) for i,Asset in pairs(assets) do local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem - text=text..string.format("\n%s %s: score=%d, distance=%.1f km", asset.squadname, asset.spawngroupname, asset.score, asset.dist/1000) + text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) asset.dist=nil asset.score=nil end @@ -679,15 +655,21 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) -- Special Missions --- + local currM=asset.flightgroup:GetMissionCurrent() + -- Check if mission is INTERCEPT and asset is currently on GCI mission. If so, GCI is paused. - if Mission.type==AUFTRAG.Type.INTERCEPT then - local currM=asset.flightgroup:GetMissionCurrent() - + if Mission.type==AUFTRAG.Type.INTERCEPT then if currM and currM.type==AUFTRAG.Type.GCICAP then self:I(self.lid..string.format("Pausing %s mission %s to send flight on intercept mission %s", currM.type, currM.name, Mission.name)) asset.flightgroup:PauseMission() - end + end end + + -- Cancel the current ALERT 5 mission. + if currM and currM.type==AUFTRAG.Type.ALERT5 then + asset.flightgroup:MissionCancel(currM) + end + -- Trigger event. self:__OpsOnMission(5, asset.flightgroup, Mission) @@ -1680,9 +1662,13 @@ function LEGION:RecruitAssets(Mission) -- First get payloads for aircraft types of squadrons. for _,_cohort in pairs(self.cohorts) do local cohort=_cohort --Ops.Cohort#COHORT - if Npayloads[cohort.aircrafttype]==nil then - Npayloads[cohort.aircrafttype]=self:IsAirwing() and self:CountPayloadsInStock(Mission.type, cohort.aircrafttype, Mission.payloads) or 999 - self:I(self.lid..string.format("Got Npayloads=%d for type=%s",Npayloads[cohort.aircrafttype], cohort.aircrafttype)) + if Npayloads[cohort.aircrafttype]==nil then + local MissionType=Mission.type + if MissionType==AUFTRAG.Type.ALERT5 then + MissionType=Mission.alert5MissionType + end + Npayloads[cohort.aircrafttype]=self:IsAirwing() and self:CountPayloadsInStock(MissionType, cohort.aircrafttype, Mission.payloads) or 999 + self:I(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) end end @@ -1722,8 +1708,16 @@ function LEGION:RecruitAssets(Mission) -- Only assets that have no payload. Should be only spawned assets! if not asset.payload then + -- Set mission type. + local MissionType=Mission.Type + + -- Get a loadout for the actual mission this group is waiting for. + if Mission.type==AUFTRAG.Type.ALERT5 and Mission.alert5MissionType then + MissionType=Mission.alert5MissionType + end + -- Fetch payload for asset. This can be nil! - asset.payload=self:FetchPayloadFromStock(asset.unittype, Mission.type, Mission.payloads) + asset.payload=self:FetchPayloadFromStock(asset.unittype, MissionType, Mission.payloads) end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index f1815e991..7af0acbe1 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3373,6 +3373,29 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Set mission UID. wp.missionUID=Mission and Mission.auftragsnummer or nil + elseif Task.dcstask.id==AUFTRAG.SpecialTask.AMMOSUPPLY or Task.dcstask.id==AUFTRAG.SpecialTask.FUELSUPPLY then + + --- + -- Task "Ammo Supply" or "Fuel Supply" mission. + --- + + -- Parameters. + local zone=Task.dcstask.params.zone --Core.Zone#ZONE + + -- Random coordinate in zone. + local Coordinate=zone:GetRandomCoordinate() + + -- Speed and altitude. + local Speed=UTILS.KmphToKnots(Task.dcstask.params.speed or self.speedCruise) + + elseif Task.dcstask.id==AUFTRAG.SpecialTask.ALERT5 then + + --- + -- Task Alert 5 mission. + --- + + -- Just stay put on the airfield and wait until something happens. + else -- If task is scheduled (not waypoint) set task. @@ -3463,7 +3486,13 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) elseif Task.dcstask.id=="PatrolZone" then done=true elseif Task.dcstask.id=="ReconMission" then - done=true + done=true + elseif Task.dcstask.id==AUFTRAG.SpecialTask.AMMOSUPPLY then + done=true + elseif Task.dcstask.id==AUFTRAG.SpecialTask.FUELSUPPLY then + done=true + elseif Task.dcstask.id==AUFTRAG.SpecialTask.ALERT5 then + done=true elseif stopflag==1 or (not self:IsAlive()) or self:IsDead() or self:IsStopped() then -- Manual call TaskDone if setting flag to one was not successful. done=true @@ -3829,8 +3858,8 @@ function OPSGROUP:onbeforeMissionStart(From, Event, To, Mission) end - -- Startup group if it is uncontrolled. - if self:IsFlightgroup() and self:IsUncontrolled() then + -- Startup group if it is uncontrolled. Alert 5 aircraft will not be started though! + if self:IsFlightgroup() and self:IsUncontrolled() and Mission.type~=AUFTRAG.Type.ALERT5 then self:StartUncontrolled(delay) end @@ -3950,6 +3979,12 @@ function OPSGROUP:onafterMissionCancel(From, Event, To, Mission) -- Current Mission --- + -- Alert 5 missoins dont have a task set, which could be cancelled. + if Mission.type==AUFTRAG.Type.ALERT5 then + self:MissionDone(Mission) + return + end + -- Get mission waypoint task. local Task=Mission:GetGroupWaypointTask(self) @@ -4103,14 +4138,22 @@ function OPSGROUP:RouteToMission(mission, delay) -- Debug info. self:T(self.lid..string.format("Route To Mission")) + -- Catch dead or stopped groups. if self:IsDead() or self:IsStopped() then return end + -- OPSTRANSPORT: Just add the ops transport to the queue. if mission.type==AUFTRAG.Type.OPSTRANSPORT then self:AddOpsTransport(mission.opstransport) return end + + -- ALERT 5: Just set the mission to executing. + if mission.type==AUFTRAG.Type.ALERT5 then + self:MissionExecute(mission) + return + end -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid @@ -4134,6 +4177,10 @@ function OPSGROUP:RouteToMission(mission, delay) -- Special for Troop transport. if mission.type==AUFTRAG.Type.TROOPTRANSPORT then + + --- + -- TROOP TRANSPORT + --- -- Refresh DCS task with the known controllable. mission.DCStask=mission:GetDCSMissionTask(self.group) @@ -4221,39 +4268,7 @@ function OPSGROUP:RouteToMission(mission, delay) --- -- Mission Specific Settings --- - - -- ROE - if mission.optionROE then - self:SwitchROE(mission.optionROE) - end - -- ROT - if mission.optionROT then - self:SwitchROT(mission.optionROT) - end - -- Alarm state - if mission.optionAlarm then - self:SwitchAlarmstate(mission.optionAlarm) - end - -- EPLRS - if mission.optionEPLRS then - self:SwitchEPLRS(mission.optionEPLRS) - end - -- Formation - if mission.optionFormation and self:IsFlightgroup() then - self:SwitchFormation(mission.optionFormation) - end - -- Radio frequency and modulation. - if mission.radio then - self:SwitchRadio(mission.radio.Freq, mission.radio.Modu) - end - -- TACAN settings. - if mission.tacan then - self:SwitchTACAN(mission.tacan.Channel, mission.tacan.Morse, mission.tacan.BeaconName, mission.tacan.Band) - end - -- ICLS settings. - if mission.icls then - self:SwitchICLS(mission.icls.Channel, mission.icls.Morse, mission.icls.UnitName) - end + self:_SetMissionOptions(mission) if self:IsArmygroup() then self:Cruise(mission.missionSpeed and UTILS.KmphToKnots(mission.missionSpeed) or self:GetSpeedCruise()) @@ -4266,6 +4281,46 @@ function OPSGROUP:RouteToMission(mission, delay) end end +--- Set mission specific options for ROE, Alarm state, etc. +-- @param #OPSGROUP self +-- @param Ops.Auftrag#AUFTRAG mission The mission table. +function OPSGROUP:_SetMissionOptions(mission) + + -- ROE + if mission.optionROE then + self:SwitchROE(mission.optionROE) + end + -- ROT + if mission.optionROT then + self:SwitchROT(mission.optionROT) + end + -- Alarm state + if mission.optionAlarm then + self:SwitchAlarmstate(mission.optionAlarm) + end + -- EPLRS + if mission.optionEPLRS then + self:SwitchEPLRS(mission.optionEPLRS) + end + -- Formation + if mission.optionFormation and self:IsFlightgroup() then + self:SwitchFormation(mission.optionFormation) + end + -- Radio frequency and modulation. + if mission.radio then + self:SwitchRadio(mission.radio.Freq, mission.radio.Modu) + end + -- TACAN settings. + if mission.tacan then + self:SwitchTACAN(mission.tacan.Channel, mission.tacan.Morse, mission.tacan.BeaconName, mission.tacan.Band) + end + -- ICLS settings. + if mission.icls then + self:SwitchICLS(mission.icls.Channel, mission.icls.Morse, mission.icls.UnitName) + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Queue Update: Missions & Tasks ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 6f126e6cd4e6b45a3db1d2ea1ddf596eef7cebed Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 9 Sep 2021 13:35:10 +0200 Subject: [PATCH 097/141] OPSCARGO - fixes for Legion assignment --- Moose Development/Moose/Ops/Auftrag.lua | 40 +++++++++--- Moose Development/Moose/Ops/Cohort.lua | 21 +++++- Moose Development/Moose/Ops/Legion.lua | 69 +++++++++++++++----- Moose Development/Moose/Ops/OpsGroup.lua | 64 ++++++++++++++---- Moose Development/Moose/Ops/OpsTransport.lua | 42 ++++++++++-- 5 files changed, 188 insertions(+), 48 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 37de1edad..8e68cb931 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1364,23 +1364,22 @@ function AUFTRAG:NewFUELSUPPLY(Zone) end ---- **[AIR]** Create an ALERT 5 mission. +--- **[AIR]** Create an ALERT 5 mission. Aircraft will be spawned uncontrolled and wait for an assignment. You must specify **one** mission type which is performed. +-- This determines the payload and the DCS mission task which are used when the aircraft is spawned. -- @param #AUFTRAG self --- @param #string MissionType Mission type `AUFTRAG.Type.XXX`. +-- @param #string MissionType Mission type `AUFTRAG.Type.XXX`. Determines payload and mission task (intercept, ground attack, etc.). -- @return #AUFTRAG self function AUFTRAG:NewALERT5(MissionType) local mission=AUFTRAG:New(AUFTRAG.Type.ALERT5) - --mission:_TargetFromObject(Coordinate) - mission.missionTask=self:GetMissionTaskforMissionType(MissionType) mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.NoReaction mission.alert5MissionType=MissionType - mission.missionFraction=0.0 + mission.missionFraction=1.0 mission.DCStask=mission:GetDCSMissionTask() @@ -1828,14 +1827,18 @@ end --- Attach OPS transport to the mission. Mission assets will be transported before the mission is started at the OPSGROUP level. -- @param #AUFTRAG self --- @param Core.Zone#ZONE PickupZone Zone where assets are picked up. -- @param Core.Zone#ZONE DeployZone Zone where assets are deployed. +-- @param Core.Zone#ZONE DisembarkZone Zone where assets are disembarked to. -- @param Core.Set#SET_OPSGROUP Carriers Set of carriers. Can also be a single group. Can also be added via the AddTransportCarriers functions. -- @return #AUFTRAG self -function AUFTRAG:SetTransportForAssets(PickupZone, DeployZone, Carriers) +function AUFTRAG:SetTransportForAssets(DeployZone, DisembarkZone, Carriers) -- OPS transport from pickup to deploy zone. - self.opstransport=OPSTRANSPORT:New(nil, PickupZone, DeployZone) + self.opstransport=OPSTRANSPORT:New(nil, nil, DeployZone) + + if DisembarkZone then + self.opstransport:SetDisembarkZone(DisembarkZone) + end if Carriers then if Carriers:IsInstanceOf("SET_OPSGROUP") then @@ -1854,6 +1857,18 @@ function AUFTRAG:SetTransportForAssets(PickupZone, DeployZone, Carriers) return self end +--- Add a transport Legion. This requires an OPSTRANSPORT to be set via `AUFTRAG:SetTransportForAssets`. +-- @param #AUFTRAG self +-- @param Ops.Legion#LEGION Legion The legion. +function AUFTRAG:AddTransportLegion(Legion) + + self.transportLegions=self.transportLegions or {} + + table.insert(self.transportLegions, Legion) + + return self +end + --- Set Rules of Engagement (ROE) for this mission. -- @param #AUFTRAG self -- @param #string roe Mission ROE. @@ -2162,7 +2177,12 @@ function AUFTRAG:AddOpsGroup(OpsGroup) -- Add ops transport to new group. if self.opstransport then - self.opstransport:AddCargoGroups(OpsGroup) + for _,_tzc in pairs(self.opstransport.tzCombos) do + local tzc=_tzc --Ops.OpsTransport#OPSTRANSPORT.TransportZoneCombo + if tzc.uid~=self.opstransport.tzcDefault.uid then + self.opstransport:AddCargoGroups(OpsGroup, tzc) + end + end end return self @@ -2300,7 +2320,7 @@ function AUFTRAG:IsReadyToGo() if #self.legions>0 then end if self.opstransport:IsPlanned() or self.opstransport:IsQueued() or self.opstransport:IsRequested() then - return false + --return false end end diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 969210f8c..6ae931404 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -37,6 +37,8 @@ -- @field #number radioFreq Radio frequency in MHz the cohort uses. -- @field #number radioModu Radio modulation the cohort uses. -- @field #table tacanChannel List of TACAN channels available to the cohort. +-- @field #number weightAsset Weight of one assets group in kg. +-- @field #number cargobayLimit Cargo bay capacity in kg. -- @extends Core.Fsm#FSM --- *It is unbelievable what a platoon of twelve aircraft did to tip the balance.* -- Adolf Galland @@ -67,7 +69,9 @@ COHORT = { legion = nil, Ngroups = nil, engageRange = nil, - tacanChannel = {}, + tacanChannel = {}, + weightAsset = 99999, + cargobayLimit = 0, } --- COHORT class version. @@ -124,7 +128,20 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) -- Aircraft type. self.aircrafttype=self.templategroup:GetTypeName() - + + local units=self.templategroup:GetUnits() + + -- Weight of the whole group. + self.weightAsset=0 + for i,_unit in pairs(units) do + local unit=_unit --Wrapper.Unit#UNIT + local desc=unit:GetDesc() + self.weightAsset=self.weightAsset + (desc.massMax or 666) + if i==1 then + self.cargobayLimit=unit:GetCargoBayFreeWeight() + end + end + -- Start State. self:SetStartState("Stopped") diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 2be104b6e..32344af38 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -253,13 +253,28 @@ function LEGION:AddMission(Mission) Mission:_TargetFromObject(self:GetCoordinate()) end - --[[ + -- Add ops transport to transport Legions. if Mission.opstransport then - Mission.opstransport:SetPickupZone(self.spawnzone) - Mission.opstransport:SetEmbarkZone(self.spawnzone) - self:AddOpsTransport(Mission.opstransport) + + + -- Add a new TZC: from pickup here to the deploy zone. + local tzc=Mission.opstransport:AddTransportZoneCombo(self.spawnzone, Mission.opstransport.tzcDefault.DeployZone) + + --TODO: Depending on "from where to where" the assets need to transported, we need to set ZONE_AIRBASE etc. + + --Mission.opstransport:SetPickupZone(self.spawnzone) + --Mission.opstransport:SetEmbarkZone(self.spawnzone) + + + -- Loop over all defined transport legions. + for _,_legion in pairs(Mission.transportLegions) do + local legion=_legion --Ops.Legion#LEGION + + -- Add ops transport to legion. + legion:AddOpsTransport(Mission.opstransport) + end + end - ]] -- Add mission to queue. table.insert(self.missionqueue, Mission) @@ -458,8 +473,10 @@ function LEGION:_GetNextTransport() return nil end - - local function getAssets(n) + --- Function to get carrier assets from all cohorts. + local function getAssets(n, weightGroup) + + -- Selected assets. local assets={} -- Loop over cohorts. @@ -467,14 +484,14 @@ function LEGION:_GetNextTransport() local cohort=_cohort --Ops.Cohort#COHORT -- Check if chort can do a transport. - if cohort:CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}, cohort.missiontypes) then + if cohort:CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}) and cohort.cargobayLimit>=weightGroup then -- Loop over cohort assets. for _,_asset in pairs(cohort.assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem -- Check if asset is currently on a mission (STARTED or QUEUED). - if not asset.spawned then + if not (asset.spawned or asset.isReserved or asset.requested) then -- Add to assets. table.insert(assets, asset) @@ -488,6 +505,8 @@ function LEGION:_GetNextTransport() end end + + return nil end @@ -498,11 +517,30 @@ function LEGION:_GetNextTransport() -- Check if transport is still queued and ready. if transport:IsQueued() and transport:IsReadyToGo() then - local assets=getAssets(1) - - if #assets>0 then - transport.assets=assets - return transport + -- Get all undelivered cargo ops groups. + local cargoOpsGroups=transport:GetCargoOpsGroups(false) + + -- At least one group should be spawned. + if #cargoOpsGroups>0 then + + -- Calculate the max weight so we know which cohorts can provide carriers. + local weightGroup=0 + for _,_opsgroup in pairs(cargoOpsGroups) do + local opsgroup=_opsgroup --Ops.OpsGroup#OPSGROUP + local weight=opsgroup:GetWeightTotal() + if weight>weightGroup then + weightGroup=weight + end + end + + -- Get assets. If not enough assets can be found, nil is returned. + local assets=getAssets(1, weightGroup) + + if assets then + transport.assets=assets + return transport + end + end end @@ -821,6 +859,7 @@ function LEGION:onafterOpsOnMission(From, Event, To, OpsGroup, Mission) -- Trigger event for Brigades. self:ArmyOnMission(OpsGroup, Mission) else + --TODO: Flotilla end end @@ -901,8 +940,6 @@ function LEGION:onafterNewAsset(From, Event, To, asset, assignment) -- Add asset to cohort. cohort:AddAsset(asset) - -- TODO - --asset.terminalType=AIRBASE.TerminalType.OpenBig else --- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 7af0acbe1..8ec6d644b 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -5751,13 +5751,13 @@ function OPSGROUP:_CheckCargoTransport() if self.verbose>=3 then local text="" for i,_transport in pairs(self.cargoqueue) do - local transport=_transport --#Ops.OpsTransport#OPSTRANSPORT + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT local pickupzone=transport:GetPickupZone() local deployzone=transport:GetDeployZone() local pickupname=pickupzone and pickupzone:GetName() or "unknown" local deployname=deployzone and deployzone:GetName() or "unknown" text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport:GetState(), pickupname, deployname) - for j,_cargo in pairs(transport.cargos) do + for j,_cargo in pairs(transport:GetCargos()) do local cargo=_cargo --#OPSGROUP.CargoGroup local state=cargo.opsgroup:GetState() local status=cargo.opsgroup.cargoStatus @@ -5785,6 +5785,12 @@ function OPSGROUP:_CheckCargoTransport() if self.cargoTransport then if self:IsNotCarrier() then + + -- Unset time stamps. + self.Tpickingup=nil + self.Tloading=nil + self.Ttransporting=nil + self.Tunloading=nil -- Get transport zone combo (TZC). self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self) @@ -5802,18 +5808,28 @@ function OPSGROUP:_CheckCargoTransport() end elseif self:IsPickingup() then + + -- Set time stamp. + self.Tpickingup=self.Tpickingup or Time + + -- Current pickup time. + local tpickingup=Time-self.Tpickingup -- Debug Info. - self:T(self.lid.."Picking up...") + self:T(self.lid..string.format("Picking up at %s [TZC UID=%d] for %s sec...", self.cargoTZC.PickupZone and self.cargoTZC.PickupZone:GetName() or "unknown", self.cargoTZC.uid, tpickingup)) elseif self:IsLoading() then -- Set loading time stamp. - --TODO: Check max loading time. If exceeded ==> abort transport. self.Tloading=self.Tloading or Time + + -- Current pickup time. + local tloading=Time-self.Tloading + + --TODO: Check max loading time. If exceeded ==> abort transport. -- Debug info. - self:T(self.lid.."Loading...") + self:T(self.lid..string.format("Loading at %s [TZC UID=%d] for %s sec...", self.cargoTZC.PickupZone and self.cargoTZC.PickupZone:GetName() or "unknown", self.cargoTZC.uid, tloading)) local boarding=false local gotcargo=false @@ -5847,11 +5863,23 @@ function OPSGROUP:_CheckCargoTransport() end elseif self:IsTransporting() then + + -- Set time stamp. + self.Ttransporting=self.Ttransporting or Time + + -- Current pickup time. + local ttransporting=Time-self.Ttransporting -- Debug info. self:T(self.lid.."Transporting (nothing to do)") elseif self:IsUnloading() then + + -- Set time stamp. + self.Tunloading=self.Tunloading or Time + + -- Current pickup time. + local tunloading=Time-self.Tunloading -- Debug info. self:T(self.lid.."Unloading ==> Checking if all cargo was delivered") @@ -6084,7 +6112,7 @@ end function OPSGROUP:_CheckDelivered(CargoTransport) local done=true - for _,_cargo in pairs(CargoTransport.cargos) do + for _,_cargo in pairs(CargoTransport:GetCargos()) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup if self:CanCargo(cargo.opsgroup) then @@ -6118,7 +6146,7 @@ function OPSGROUP:_CheckGoPickup(CargoTransport) if CargoTransport then - for _,_cargo in pairs(CargoTransport.cargos) do + for _,_cargo in pairs(CargoTransport:GetCargos()) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup if self:CanCargo(cargo.opsgroup) then @@ -8236,15 +8264,25 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) -- Speed in knots. local Speed=UTILS.MpsToKnots(wp.speed) - --local waypoint=self:_CreateWaypoint(wp) - --self:_AddWaypoint(waypoint) - + -- Add waypoint. + local Waypoint=nil if self:IsFlightgroup() then - FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, nil, Altitude, false) + Waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, nil, Altitude, false) elseif self:IsArmygroup() then - ARMYGROUP.AddWaypoint(self, Coordinate, Speed, nil, wp.action, false) + Waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, nil, wp.action, false) elseif self:IsNavygroup() then - NAVYGROUP.AddWaypoint(self, Coordinate, Speed, nil, Depth, false) + Waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, Speed, nil, Depth, false) + end + + -- Get DCS waypoint tasks set in the ME. EXPERIMENTAL! + local DCStasks=wp.task and wp.task.params.tasks or nil + if DCStasks then + for _,DCStask in pairs(DCStasks) do + -- Wrapped Actions are commands. We do not take those. + if DCStask.id and DCStask.id~="WrappedAction" then + self:AddTaskWaypoint(DCStask,Waypoint, "ME Task") + end + end end end diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 5e123a191..a77c7c4e9 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -432,9 +432,10 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then -- We got a single GROUP or OPSGROUP object. - local cargo=self:_CreateCargoGroupData(GroupSet) + local cargo=self:_CreateCargoGroupData(GroupSet, TransportZoneCombo) if cargo then + -- Add to main table. table.insert(self.cargos, cargo) self.Ncargo=self.Ncargo+1 @@ -442,6 +443,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) -- Add to TZC table. table.insert(TransportZoneCombo.Cargos, cargo) TransportZoneCombo.Ncargo=TransportZoneCombo.Ncargo+1 + end else @@ -450,6 +452,10 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) for _,group in pairs(GroupSet.Set) do + -- Call iteravely for each group. + self:AddCargoGroups(group, TransportZoneCombo) + + --[[ local cargo=self:_CreateCargoGroupData(group) if cargo then @@ -461,6 +467,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) table.insert(TransportZoneCombo.Cargos, cargo) TransportZoneCombo.Ncargo=TransportZoneCombo.Ncargo+1 end + ]] end end @@ -469,7 +476,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) if self.verbose>=1 then local text=string.format("Added cargo groups:") local Weight=0 - for _,_cargo in pairs(self.cargos) do + for _,_cargo in pairs(self:GetCargos()) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup local weight=cargo.opsgroup:GetWeightTotal() Weight=Weight+weight @@ -874,7 +881,14 @@ function OPSTRANSPORT:GetCargos(TransportZoneCombo) if TransportZoneCombo then return TransportZoneCombo.Cargos else - return self.cargos + local cargos={} + for _,_tzc in pairs(self.tzCombos) do + local tzc=_tzc --#OPSTRANSPORT.TransportZoneCombo + for _,cargo in pairs(tzc.Cargos) do + table.insert(cargos, cargo) + end + end + return cargos end end @@ -1316,7 +1330,7 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) if self.verbose>=3 then text=text..string.format("\nCargos:") - for _,_cargo in pairs(self.cargos) do + for _,_cargo in pairs(self:GetCargos()) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup local carrier=cargo.opsgroup:_GetMyCarrierElement() local name=carrier and carrier.name or "none" @@ -1472,7 +1486,7 @@ function OPSTRANSPORT:_CheckDelivered() local done=true local dead=true - for _,_cargo in pairs(self.cargos) do + for _,_cargo in pairs(self:GetCargos()) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup if cargo.delivered then @@ -1611,17 +1625,31 @@ end -- @param #OPSTRANSPORT self -- @param Wrapper.Group#GROUP group The GROUP or OPSGROUP object. -- @return Ops.OpsGroup#OPSGROUP.CargoGroup Cargo group data. -function OPSTRANSPORT:_CreateCargoGroupData(group) +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +function OPSTRANSPORT:_CreateCargoGroupData(group, TransportZoneCombo) + -- Get ops group. local opsgroup=self:_GetOpsGroupFromObject(group) + -- First check that this group is not already contained in this TZC. + for _,_cargo in pairs(TransportZoneCombo.Cargos or {}) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + if cargo.opsgroup.groupname==opsgroup.groupname then + -- Group is already contained. + return nil + end + end + + + -- Create a new data item. local cargo={} --Ops.OpsGroup#OPSGROUP.CargoGroup - + cargo.opsgroup=opsgroup cargo.delivered=false cargo.status="Unknown" cargo.disembarkCarrierElement=nil cargo.disembarkCarrierGroup=nil + cargo.tzcUID=TransportZoneCombo return cargo end From 884c51a69a34839f6b5d129765e71cf11469c15f Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 10 Sep 2021 00:32:15 +0200 Subject: [PATCH 098/141] OPSTRANSPORT - Improved assignment to multiple legions. --- Moose Development/Moose/Ops/Auftrag.lua | 48 ++++- Moose Development/Moose/Ops/Brigade.lua | 16 ++ Moose Development/Moose/Ops/Legion.lua | 94 +++++---- Moose Development/Moose/Ops/OpsTransport.lua | 191 +++++++++++++++++-- 4 files changed, 290 insertions(+), 59 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 8e68cb931..d45c51332 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -34,7 +34,7 @@ -- @field #string type Mission type. -- @field #string status Mission status. -- @field #table legions Assigned legions. --- @field #table statusLegion Mission status of all assigned LEGIONSs. +-- @field #table statusLegion Mission status of all assigned LEGIONs. -- @field #string statusCommander Mission status of the COMMANDER. -- @field #string statusChief Mission status of the CHIF. -- @field #table groupdata Group specific data. @@ -538,19 +538,22 @@ function AUFTRAG:New(Type) -- State is planned. self.status=AUFTRAG.Status.PLANNED - -- Defaults + -- Defaults . self:SetName() self:SetPriority() self:SetTime() + self:SetRequiredAssets() + self:SetRequiredCarriers() self.engageAsGroup=true + self.dTevaluate=5 + + -- Init counters and stuff. self.repeated=0 self.repeatedSuccess=0 self.repeatedFailure=0 self.Nrepeat=0 self.NrepeatFailure=0 self.NrepeatSuccess=0 - self.nassets=1 - self.dTevaluate=5 self.Ncasualties=0 self.Nkills=0 self.Nelements=0 @@ -1854,10 +1857,38 @@ function AUFTRAG:SetTransportForAssets(DeployZone, DisembarkZone, Carriers) end + -- Set min/max number of carriers to be assigned. + self.opstransport.nCarriersMin=self.nCarriersMin + self.opstransport.nCarriersMax=self.nCarriersMax + return self end ---- Add a transport Legion. This requires an OPSTRANSPORT to be set via `AUFTRAG:SetTransportForAssets`. +--- Set number of required carrier groups if an OPSTRANSPORT assignment is required. +-- @param #AUFTRAG self +-- @param #number NcarriersMin Number of carriers *at least* required. Default 1. +-- @param #number NcarriersMax Number of carriers *at most* used for transportation. Default is same as `NcarriersMin`. +-- @return #AUFTRAG self +function AUFTRAG:SetRequiredCarriers(NcarriersMin, NcarriersMax) + + self.nCarriersMin=NcarriersMin or 1 + + self.nCarriersMax=NcarriersMax or self.nCarriersMin + + -- Ensure that max is at least equal to min. + if self.nCarriersMax=2 then + local text=string.format("Transports Total=%d:", #self.transportqueue) + for i,_transport in pairs(self.transportqueue) do + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + + local prio=string.format("%d/%s", transport.prio, tostring(transport.importance)) ; if transport.urgent then prio=prio.." (!)" end + local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d", transport.Ncargo, transport.Ndelivered, transport.Ncarrier) + + text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s", i, transport.uid, transport:GetState(), prio, carriers) + end + self:I(self.lid..text) + end + ------------------- -- Platoon Info -- ------------------- diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 32344af38..84988cce6 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -256,7 +256,6 @@ function LEGION:AddMission(Mission) -- Add ops transport to transport Legions. if Mission.opstransport then - -- Add a new TZC: from pickup here to the deploy zone. local tzc=Mission.opstransport:AddTransportZoneCombo(self.spawnzone, Mission.opstransport.tzcDefault.DeployZone) @@ -315,9 +314,15 @@ function LEGION:AddOpsTransport(OpsTransport) -- Is not queued at a legion. OpsTransport:Queued() + + -- Set legion status. + OpsTransport:SetLegionStatus(self, AUFTRAG.Status.QUEUED) -- Add mission to queue. table.insert(self.transportqueue, OpsTransport) + + -- Add this legion to the transport. + OpsTransport:AddLegion(self) -- Info text. local text=string.format("Added Transport %s. Starting at %s-%s", @@ -427,7 +432,7 @@ function LEGION:_GetNextMission() end table.sort(self.missionqueue, _sort) - -- Look for first mission that is SCHEDULED. + -- Search min importance. local vip=math.huge for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG @@ -436,9 +441,6 @@ function LEGION:_GetNextMission() end end - -- Current time. - local time=timer.getAbsTime() - -- Look for first task that is not accomplished. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG @@ -453,7 +455,6 @@ function LEGION:_GetNextMission() return mission end - end -- mission due? end -- mission loop @@ -474,7 +475,7 @@ function LEGION:_GetNextTransport() end --- Function to get carrier assets from all cohorts. - local function getAssets(n, weightGroup) + local function getAssets(n, N , weightGroup) -- Selected assets. local assets={} @@ -496,7 +497,12 @@ function LEGION:_GetNextTransport() -- Add to assets. table.insert(assets, asset) - if #assets==n then + --TODO: Optimize Asset Selection! + + --TODO: Check if deploy and (any) pickup zone is an airbase, so airplanes can be used. + + -- Max number of assets reached. + if #assets==N then return assets end end @@ -506,16 +512,22 @@ function LEGION:_GetNextTransport() end end - return nil + -- At least min number reached? + if #assets>=n then + return assets + else + return nil + end end - + + --TODO: Sort transports wrt to prio and importance. See mission sorting! -- Look for first task that is not accomplished. for _,_transport in pairs(self.transportqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT -- Check if transport is still queued and ready. - if transport:IsQueued() and transport:IsReadyToGo() then + if transport:IsQueued(self) and transport:IsReadyToGo() then -- Get all undelivered cargo ops groups. local cargoOpsGroups=transport:GetCargoOpsGroups(false) @@ -534,10 +546,14 @@ function LEGION:_GetNextTransport() end -- Get assets. If not enough assets can be found, nil is returned. - local assets=getAssets(1, weightGroup) + local assets=getAssets(transport.nCarriersMin, transport.nCarriersMax, weightGroup) if assets then - transport.assets=assets + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.isReserved=true + transport:AddAsset(asset) + end return transport end @@ -547,7 +563,7 @@ function LEGION:_GetNextTransport() end - + -- No transport found. return nil end @@ -735,6 +751,7 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) asset.requested=true asset.isReserved=false + -- Set missin task so that the group is spawned with the right one. if Mission.missionTask then asset.missionTask=Mission.missionTask end @@ -760,38 +777,46 @@ end -- @param #string To To state. -- @param Ops.OpsTransport#OPSTRANSPORT Opstransport The requested mission. function LEGION:onafterTransportRequest(From, Event, To, OpsTransport) - - -- Set mission status from QUEUED to REQUESTED. - OpsTransport:Requested() - -- Set legion status. Ensures that it is not considered in the next selection. - --Mission:SetLegionStatus(self, AUFTRAG.Status.REQUESTED) + -- List of assets that will be requested. + local AssetList={} + + --TODO: Find spawned assets on ALERT 5 mission OPSTRANSPORT. - -- Add request to legion warehouse. - if #OpsTransport.assets>0 then - - --local text=string.format("Requesting assets for mission %s:", Mission.name) - for i,_asset in pairs(OpsTransport.assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + --local text=string.format("Requesting assets for mission %s:", Mission.name) + for i,_asset in pairs(OpsTransport.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Check that this asset belongs to this Legion warehouse. + if asset.wid==self.uid then -- Set asset to requested! Important so that new requests do not use this asset! asset.requested=true + asset.isReserved=false + + -- Set transport mission task. + asset.missionTask=ENUMS.MissionTask.TRANSPORT - -- Check max required transports. - if i==1 then - break - end - + -- Add asset to list. + table.insert(AssetList, asset) end + end + + if #AssetList>0 then + + -- Set mission status from QUEUED to REQUESTED. + OpsTransport:Requested() + + -- Set legion status. Ensures that it is not considered in the next selection. + OpsTransport:SetLegionStatus(self, OPSTRANSPORT.Status.REQUESTED) -- TODO: Get/set functions for assignment string. local assignment=string.format("Transport-%d", OpsTransport.uid) -- Add request to legion warehouse. - self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, OpsTransport.assets, #OpsTransport.assets, nil, nil, OpsTransport.prio, assignment) + self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, AssetList, #AssetList, nil, nil, OpsTransport.prio, assignment) -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. - OpsTransport.requestID=OpsTransport.requestID or {} OpsTransport.requestID[self.alias]=self.queueid end @@ -1619,11 +1644,14 @@ function LEGION:GetAircraftTypes(onlyactive, cohorts) end --- Check if assets for a given mission type are available. +-- +-- OBSOLETE and renamed to _CanMission (to see if it is still used somewhere) +-- -- @param #LEGION self -- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @return #boolean If true, enough assets are available. -- @return #table Assets that can do the required mission. -function LEGION:CanMission(Mission) +function LEGION:_CanMission(Mission) -- Assume we CAN and NO assets are available. local Can=true diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index a77c7c4e9..50b613719 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -2,10 +2,10 @@ -- -- ## Main Features: -- --- * Transport troops from A to B. +-- * Transport troops from A to B -- * Supports ground, naval and airborne (airplanes and helicopters) units as carriers --- * Use combined forces (ground, naval, air) to transport the troops. --- * Additional FSM events to hook into and customize your mission design. +-- * Use combined forces (ground, naval, air) to transport the troops +-- * Additional FSM events to hook into and customize your mission design -- -- === -- @@ -51,6 +51,9 @@ -- -- @field Ops.Auftrag#AUFTRAG mission The mission attached to this transport. -- @field #table assets Warehouse assets assigned for this transport. +-- @field #table legions Assigned legions. +-- @field #table statusLegion Transport status of all assigned LEGIONs. +-- @field #table requestID The ID of the queued warehouse request. Necessary to cancel the request if the transport was cancelled before the request is processed. -- -- @extends Core.Fsm#FSM @@ -58,8 +61,6 @@ -- -- === -- --- ![Banner Image](..\Presentations\OPS\Transport\_Main.png) --- -- # The OPSTRANSPORT Concept -- -- This class simulates troop transport using carriers such as APCs, ships, helicopters or airplanes. The carriers and transported groups need to be OPSGROUPS (see ARMYGROUP, NAVYGROUP and FLIGHTGROUP classes). @@ -119,6 +120,9 @@ OPSTRANSPORT = { tzcCounter = 0, conditionStart = {}, assets = {}, + legions = {}, + statusLegion = {}, + requestID = {}, } --- Cargo transport status. @@ -209,16 +213,17 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) self.uid=_OPSTRANSPORTID -- Defaults. - self.cargos={} - self.carriers={} + self:SetPriority() + self:SetTime() + self:SetRequiredCarriers() + -- Init arrays and counters. + self.cargos={} + self.carriers={} self.Ncargo=0 self.Ncarrier=0 self.Ndelivered=0 - self:SetPriority() - self:SetTime() - -- Set default TZC. self.tzcDefault=self:AddTransportZoneCombo(PickupZone, DeployZone, CargoGroups) @@ -772,6 +777,33 @@ function OPSTRANSPORT:GetRequiredCargos(TransportZoneCombo) return TransportZoneCombo.RequiredCargos end +--- Set number of required carrier groups for an OPSTRANSPORT assignment. Only used if transport is assigned at **LEGION** or higher level. +-- @param #OPSTRANSPORT self +-- @param #number NcarriersMin Number of carriers *at least* required. Default 1. +-- @param #number NcarriersMax Number of carriers *at most* used for transportation. Default is same as `NcarriersMin`. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetRequiredCarriers(NcarriersMin, NcarriersMax) + + self.nCarriersMin=NcarriersMin or 1 + + self.nCarriersMax=NcarriersMax or self.nCarriersMin + + -- Ensure that max is at least equal to min. + if self.nCarriersMax%s", Legion.alias, tostring(status), tostring(Status))) + + -- New status. + self.statusLegion[Legion.alias]=Status + + return self +end + +--- Get LEGION transport status. +-- @param #OPSTRANSPORT self +-- @param Ops.Legion#LEGION Legion The legion. +-- @return #string status Current status. +function OPSTRANSPORT:GetLegionStatus(Legion) + + -- Current status. + local status=self.statusLegion[Legion.alias] or "unknown" + + return status +end + --- Check if state is PLANNED. -- @param #OPSTRANSPORT self -- @return #boolean If true, status is PLANNED. function OPSTRANSPORT:IsPlanned() - return self:is(OPSTRANSPORT.Status.PLANNED) + local is=self:is(OPSTRANSPORT.Status.PLANNED) + return is end --- Check if state is QUEUED. -- @param #OPSTRANSPORT self +-- @param Ops.Legion#LEGION Legion (Optional) Check if transport is queued at this legion. -- @return #boolean If true, status is QUEUED. -function OPSTRANSPORT:IsQueued() - return self:is(OPSTRANSPORT.Status.QUEUED) +function OPSTRANSPORT:IsQueued(Legion) + local is=self:is(OPSTRANSPORT.Status.QUEUED) + if Legion then + is=self:GetLegionStatus(Legion)==OPSTRANSPORT.Status.QUEUED + end + return is end --- Check if state is REQUESTED. -- @param #OPSTRANSPORT self +-- @param Ops.Legion#LEGION Legion (Optional) Check if transport is queued at this legion. -- @return #boolean If true, status is REQUESTED. -function OPSTRANSPORT:IsRequested() - return self:is(OPSTRANSPORT.Status.REQUESTED) +function OPSTRANSPORT:IsRequested(Legion) + local is=self:is(OPSTRANSPORT.Status.REQUESTED) + if Legion then + is=self:GetLegionStatus(Legion)==OPSTRANSPORT.Status.REQUESTED + end + return is end --- Check if state is SCHEDULED. -- @param #OPSTRANSPORT self -- @return #boolean If true, status is SCHEDULED. function OPSTRANSPORT:IsScheduled() - return self:is(OPSTRANSPORT.Status.SCHEDULED) + local is=self:is(OPSTRANSPORT.Status.SCHEDULED) + return is end --- Check if state is EXECUTING. -- @param #OPSTRANSPORT self -- @return #boolean If true, status is EXECUTING. function OPSTRANSPORT:IsExecuting() - return self:is(OPSTRANSPORT.Status.EXECUTING) + local is=self:is(OPSTRANSPORT.Status.EXECUTING) + return is end --- Check if all cargo was delivered (or is dead). @@ -1314,7 +1463,7 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) if self.verbose>=1 then -- Info text. - local text=string.format("%s: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), self.Ncargo, self.Ndelivered, #self.carriers, self.Ncarrier) + local text=string.format("%s: Ncargo=%d/%d, Ncarrier=%d/%d, Nlegions=%d", fsmstate:upper(), self.Ncargo, self.Ndelivered, #self.carriers, self.Ncarrier, #self.legions) -- Info about cargo and carrier. if self.verbose>=2 then @@ -1701,13 +1850,17 @@ function OPSTRANSPORT:_GetTransportZoneCombo(Carrier) -- Check that pickup and deploy zones were defined. if tz.PickupZone and tz.DeployZone and tz.EmbarkZone then + --TODO: Check if Carrier is an aircraft and if so, check that pickup AND deploy zones are airbases (not ships, not farps). + -- Count undelivered cargos in embark(!) zone that fit into the carrier. local ncargo=self:_CountCargosInZone(tz.EmbarkZone, false, Carrier, tz) --env.info(string.format("FF GetPickupZone i=%d, ncargo=%d", i, ncargo)) - if ncargo>0 then + -- At least one group in the zone. + if ncargo>=1 then + -- Distance to the carrier in meters. local dist=tz.PickupZone:Get2DDistance(vec2) if distmin==nil or dist Date: Fri, 10 Sep 2021 11:37:53 +0200 Subject: [PATCH 099/141] OPS LEGION - Improved asset selection for transports --- Moose Development/Moose/Ops/Cohort.lua | 17 +- Moose Development/Moose/Ops/Commander.lua | 5 +- Moose Development/Moose/Ops/Legion.lua | 533 +++++++++++++++------- Moose Development/Moose/Ops/OpsGroup.lua | 1 + 4 files changed, 368 insertions(+), 188 deletions(-) diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 6ae931404..659736d62 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -800,14 +800,11 @@ end --- Get assets for a mission. -- @param #COHORT self --- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @param #string MissionType Mission type. -- @param #number Npayloads Number of payloads available. -- @return #table Assets that can do the required mission. -- @return #number Number of payloads still available after recruiting the assets. -function COHORT:RecruitAssets(Mission, Npayloads) - - -- Number of payloads available. - Npayloads=Npayloads or self.legion:CountPayloadsInStock(Mission.type, self.aircrafttype, Mission.payloads) +function COHORT:RecruitAssets(MissionType, Npayloads) -- Recruited assets. local assets={} @@ -828,14 +825,14 @@ function COHORT:RecruitAssets(Mission, Npayloads) --- -- Check if this asset is currently on a GCICAP mission (STARTED or EXECUTING). - if self.legion:IsAssetOnMission(asset, AUFTRAG.Type.GCICAP) and Mission.type==AUFTRAG.Type.INTERCEPT then + if self.legion:IsAssetOnMission(asset, AUFTRAG.Type.GCICAP) and MissionType==AUFTRAG.Type.INTERCEPT then -- Check if the payload of this asset is compatible with the mission. -- Note: we do not check the payload as an asset that is on a GCICAP mission should be able to do an INTERCEPT as well! self:I(self.lid.."Adding asset on GCICAP mission for an INTERCEPT mission") table.insert(assets, asset) - elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.ALERT5) and self:CheckMissionCapability(Mission.Type, asset.payload.capabilities) then + elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.ALERT5) and self:CheckMissionCapability(MissionType, asset.payload.capabilities) then -- Check if the payload of this asset is compatible with the mission. self:I(self.lid.."Adding asset on ALERT 5 mission for XXX mission") @@ -876,10 +873,10 @@ function COHORT:RecruitAssets(Mission, Npayloads) combatready=false end - if Mission.type==AUFTRAG.Type.INTERCEPT and not flightgroup:CanAirToAir() then + if MissionType==AUFTRAG.Type.INTERCEPT and not flightgroup:CanAirToAir() then combatready=false else - local excludeguns=Mission.type==AUFTRAG.Type.BOMBING or Mission.type==AUFTRAG.Type.BOMBRUNWAY or Mission.type==AUFTRAG.Type.BOMBCARPET or Mission.type==AUFTRAG.Type.SEAD or Mission.type==AUFTRAG.Type.ANTISHIP + local excludeguns=MissionType==AUFTRAG.Type.BOMBING or MissionType==AUFTRAG.Type.BOMBRUNWAY or MissionType==AUFTRAG.Type.BOMBCARPET or MissionType==AUFTRAG.Type.SEAD or MissionType==AUFTRAG.Type.ANTISHIP if excludeguns and not flightgroup:CanAirToGround(excludeguns) then combatready=false end @@ -888,7 +885,7 @@ function COHORT:RecruitAssets(Mission, Npayloads) if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() then combatready=false end - if asset.payload and not self:CheckMissionCapability(Mission.type, asset.payload.capabilities) then + if asset.payload and not self:CheckMissionCapability(MissionType, asset.payload.capabilities) then combatready=false end diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index b5ec7cb25..dc4f4508c 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -659,6 +659,8 @@ function COMMANDER:RecruitAssets(Mission) -- Legions we consider for selecting assets. local legions=Mission.mylegions or self.legions + --TODO: Setting of Mission.squadrons (cohorts) will not work here! + -- Legions which have the best assets for the Mission. local Legions={} @@ -690,7 +692,7 @@ function COMMANDER:RecruitAssets(Mission) if cohort:CanMission(Mission) and npayloads>0 then -- Recruit assets from squadron. - local assets, npayloads=cohort:RecruitAssets(Mission, npayloads) + local assets, npayloads=cohort:RecruitAssets(Mission.type, npayloads) Npayloads[cohort.aircrafttype]=npayloads @@ -743,6 +745,7 @@ function COMMANDER:RecruitAssets(Mission) -- Now find the best asset for the given payloads. self:_OptimizeAssetSelection(Assets, Mission, true) + -- Get number of required assets. local Nassets=Mission:GetRequiredAssets(self) if #Assets>=Nassets then diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 84988cce6..c7930bcad 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -451,6 +451,7 @@ function LEGION:_GetNextMission() -- Recruit best assets for the job. local recruited=self:RecruitAssets(mission) + -- Did we find enough assets? if recruited then return mission end @@ -473,52 +474,6 @@ function LEGION:_GetNextTransport() if Ntransports==0 then return nil end - - --- Function to get carrier assets from all cohorts. - local function getAssets(n, N , weightGroup) - - -- Selected assets. - local assets={} - - -- Loop over cohorts. - for _,_cohort in pairs(self.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - - -- Check if chort can do a transport. - if cohort:CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}) and cohort.cargobayLimit>=weightGroup then - - -- Loop over cohort assets. - for _,_asset in pairs(cohort.assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Check if asset is currently on a mission (STARTED or QUEUED). - if not (asset.spawned or asset.isReserved or asset.requested) then - - -- Add to assets. - table.insert(assets, asset) - - --TODO: Optimize Asset Selection! - - --TODO: Check if deploy and (any) pickup zone is an airbase, so airplanes can be used. - - -- Max number of assets reached. - if #assets==N then - return assets - end - end - - end - - end - end - - -- At least min number reached? - if #assets>=n then - return assets - else - return nil - end - end --TODO: Sort transports wrt to prio and importance. See mission sorting! @@ -529,142 +484,22 @@ function LEGION:_GetNextTransport() -- Check if transport is still queued and ready. if transport:IsQueued(self) and transport:IsReadyToGo() then - -- Get all undelivered cargo ops groups. - local cargoOpsGroups=transport:GetCargoOpsGroups(false) + -- Recruit assets for transport. + local recruited=self:RecruitAssetsForTransport(transport) - -- At least one group should be spawned. - if #cargoOpsGroups>0 then - - -- Calculate the max weight so we know which cohorts can provide carriers. - local weightGroup=0 - for _,_opsgroup in pairs(cargoOpsGroups) do - local opsgroup=_opsgroup --Ops.OpsGroup#OPSGROUP - local weight=opsgroup:GetWeightTotal() - if weight>weightGroup then - weightGroup=weight - end - end - - -- Get assets. If not enough assets can be found, nil is returned. - local assets=getAssets(transport.nCarriersMin, transport.nCarriersMax, weightGroup) - - if assets then - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.isReserved=true - transport:AddAsset(asset) - end - return transport - end - + -- Did we find enough assets? + if recruited then + return transport end - + end - end -- No transport found. return nil end ---- Calculate the mission score of an asset. --- @param #LEGION self --- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset --- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. --- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. --- @return #number Mission score. -function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) - - -- Mission score. - local score=0 - -- Prefer highly skilled assets. - if asset.skill==AI.Skill.AVERAGE then - score=score+0 - elseif asset.skill==AI.Skill.GOOD then - score=score+10 - elseif asset.skill==AI.Skill.HIGH then - score=score+20 - elseif asset.skill==AI.Skill.EXCELLENT then - score=score+30 - end - - -- Add mission performance to score. - score=score+asset.cohort:GetMissionPeformance(Mission.Type) - - -- Add payload performance to score. - if includePayload and asset.payload then - score=score+self:GetPayloadPeformance(asset.payload, Mission.type) - end - - -- Target position. - local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil --Mission:GetTargetVec2() - - -- Origin: We take the flightgroups position or the one of the legion. - local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2() or self:GetVec2() - - -- Distance factor. - local distance=0 - if TargetVec2 and OrigVec2 then - -- Distance in NM. - distance=UTILS.MetersToNM(UTILS.VecDist2D(OrigVec2, TargetVec2)) - -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 - distance=UTILS.Round(distance/10, 0) - end - - -- Reduce score for legions that are futher away. - score=score-distance - - -- Intercepts need to be carried out quickly. We prefer spawned assets. - if Mission.type==AUFTRAG.Type.INTERCEPT then - if asset.spawned then - self:T(self.lid.."Adding 25 to asset because it is spawned") - score=score+25 - end - end - - -- TODO: This could be vastly improved. Need to gather ideas during testing. - -- Calculate ETA? Assets on orbit missions should arrive faster even if they are further away. - -- Max speed of assets. - -- Fuel amount? - -- Range of assets? - - return score -end - ---- Optimize chosen assets for the mission at hand. --- @param #LEGION self --- @param #table assets Table of (unoptimized) assets. --- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. --- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. -function LEGION:_OptimizeAssetSelection(assets, Mission, includePayload) - - -- Calculate the mission score of all assets. - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.score=self:CalculateAssetMissionScore(asset, Mission, includePayload) - end - - --- Sort assets wrt to their mission score. Higher is better. - local function optimize(a, b) - local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem - local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem - -- Higher score wins. If equal score ==> closer wins. - return (assetA.score>assetB.score) - end - table.sort(assets, optimize) - - -- Remove distance parameter. - local text=string.format("Optimized %d assets for %s mission (payload=%s):", #assets, Mission.type, tostring(includePayload)) - for i,Asset in pairs(assets) do - local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem - text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) - asset.dist=nil - asset.score=nil - end - self:T2(self.lid..text) - -end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events @@ -1239,7 +1074,7 @@ function LEGION:onafterSelfRequest(From, Event, To, groupset, request) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Misc Functions +-- Mission Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create a new flight group after an asset was spawned. @@ -1691,7 +1526,7 @@ function LEGION:_CanMission(Mission) local Npayloads=self:IsAirwing() and self:CountPayloadsInStock(Mission.type, cohort.aircrafttype, Mission.payloads) or 999 -- Recruit assets. - local assets=cohort:RecruitAssets(Mission, Npayloads) + local assets=cohort:RecruitAssets(Mission.type, Npayloads) -- Total number. for _,asset in pairs(assets) do @@ -1723,9 +1558,12 @@ function LEGION:RecruitAssets(Mission) -- Number of payloads in stock per aircraft type. local Npayloads={} + + -- Squadrons for the job. If user assigned to mission or simply all. + local cohorts=Mission.squadrons or self.cohorts -- First get payloads for aircraft types of squadrons. - for _,_cohort in pairs(self.cohorts) do + for _,_cohort in pairs(cohorts) do local cohort=_cohort --Ops.Cohort#COHORT if Npayloads[cohort.aircrafttype]==nil then local MissionType=Mission.type @@ -1741,7 +1579,7 @@ function LEGION:RecruitAssets(Mission) local Assets={} -- Loops over cohorts. - for _,_cohort in pairs(self.cohorts) do + for _,_cohort in pairs(cohorts) do local cohort=_cohort --Ops.Cohort#COHORT local npayloads=Npayloads[cohort.aircrafttype] @@ -1749,7 +1587,7 @@ function LEGION:RecruitAssets(Mission) if cohort:CanMission(Mission) and npayloads>0 then -- Recruit assets from squadron. - local assets, npayloads=cohort:RecruitAssets(Mission, npayloads) + local assets, npayloads=cohort:RecruitAssets(Mission.type, npayloads) Npayloads[cohort.aircrafttype]=npayloads @@ -1801,6 +1639,7 @@ function LEGION:RecruitAssets(Mission) end + -- Get number of required assets. local Nassets=Mission:GetRequiredAssets(self) if #Assets>=Nassets then @@ -1854,6 +1693,346 @@ function LEGION:RecruitAssets(Mission) end +--- Calculate the mission score of an asset. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset +-- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. +-- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. +-- @return #number Mission score. +function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) + + -- Mission score. + local score=0 + + -- Prefer highly skilled assets. + if asset.skill==AI.Skill.AVERAGE then + score=score+0 + elseif asset.skill==AI.Skill.GOOD then + score=score+10 + elseif asset.skill==AI.Skill.HIGH then + score=score+20 + elseif asset.skill==AI.Skill.EXCELLENT then + score=score+30 + end + + -- Add mission performance to score. + score=score+asset.cohort:GetMissionPeformance(Mission.Type) + + -- Add payload performance to score. + if includePayload and asset.payload then + score=score+self:GetPayloadPeformance(asset.payload, Mission.type) + end + + -- Target position. + local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil --Mission:GetTargetVec2() + + -- Origin: We take the flightgroups position or the one of the legion. + local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2() or self:GetVec2() + + -- Distance factor. + local distance=0 + if TargetVec2 and OrigVec2 then + -- Distance in NM. + distance=UTILS.MetersToNM(UTILS.VecDist2D(OrigVec2, TargetVec2)) + -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 + distance=UTILS.Round(distance/10, 0) + end + + -- Reduce score for legions that are futher away. + score=score-distance + + -- Intercepts need to be carried out quickly. We prefer spawned assets. + if Mission.type==AUFTRAG.Type.INTERCEPT then + if asset.spawned then + self:T(self.lid.."Adding 25 to asset because it is spawned") + score=score+25 + end + end + + -- TODO: This could be vastly improved. Need to gather ideas during testing. + -- Calculate ETA? Assets on orbit missions should arrive faster even if they are further away. + -- Max speed of assets. + -- Fuel amount? + -- Range of assets? + + return score +end + +--- Optimize chosen assets for the mission at hand. +-- @param #LEGION self +-- @param #table assets Table of (unoptimized) assets. +-- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. +-- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. +function LEGION:_OptimizeAssetSelection(assets, Mission, includePayload) + + -- Calculate the mission score of all assets. + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.score=self:CalculateAssetMissionScore(asset, Mission, includePayload) + end + + --- Sort assets wrt to their mission score. Higher is better. + local function optimize(a, b) + local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem + local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem + -- Higher score wins. If equal score ==> closer wins. + return (assetA.score>assetB.score) + end + table.sort(assets, optimize) + + -- Remove distance parameter. + local text=string.format("Optimized %d assets for %s mission (payload=%s):", #assets, Mission.type, tostring(includePayload)) + for i,Asset in pairs(assets) do + local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem + text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) + asset.dist=nil + asset.score=nil + end + self:T2(self.lid..text) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Transport Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Recruit assets for a given OPS transport. +-- @param #LEGION self +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The OPS transport. +-- @return #boolean If `true`, enough assets could be recruited. +function LEGION:RecruitAssetsForTransport(Transport) + + -- Get all undelivered cargo ops groups. + local cargoOpsGroups=Transport:GetCargoOpsGroups(false) + + local weightGroup=0 + + -- At least one group should be spawned. + if #cargoOpsGroups>0 then + + -- Calculate the max weight so we know which cohorts can provide carriers. + for _,_opsgroup in pairs(cargoOpsGroups) do + local opsgroup=_opsgroup --Ops.OpsGroup#OPSGROUP + local weight=opsgroup:GetWeightTotal() + if weight>weightGroup then + weightGroup=weight + end + end + end + + + -- Number of payloads in stock per aircraft type. + local Npayloads={} + + -- First get payloads for aircraft types of squadrons. + for _,_cohort in pairs(self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + if Npayloads[cohort.aircrafttype]==nil then + Npayloads[cohort.aircrafttype]=self:IsAirwing() and self:CountPayloadsInStock(AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype) or 999 + self:I(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype)) + end + end + + -- The recruited assets. + local Assets={} + + -- Loops over cohorts. + for _,_cohort in pairs(self.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + local npayloads=Npayloads[cohort.aircrafttype] + + if cohort:IsOnDuty() and npayloads>0 and cohort:CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}) and cohort.cargobayLimit>=weightGroup then + + -- Recruit assets from squadron. + local assets, npayloads=cohort:RecruitAssets(AUFTRAG.Type.OPSTRANSPORT, npayloads) + + Npayloads[cohort.aircrafttype]=npayloads + + for _,asset in pairs(assets) do + table.insert(Assets, asset) + end + + end + + end + + -- Sort asset list. Best ones come first. + self:_OptimizeAssetSelectionForTransport(Assets, Transport, false) + + -- If airwing, get the best payload available. + if self:IsAirwing() then + + for _,_asset in pairs(Assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Only assets that have no payload. Should be only spawned assets! + if not asset.payload then + + -- Fetch payload for asset. This can be nil! + asset.payload=self:FetchPayloadFromStock(asset.unittype, AUFTRAG.Type.OPSTRANSPORT) + + end + + end + + -- Remove assets that dont have a payload. + for i=#Assets,1,-1 do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if not asset.payload then + table.remove(Assets, i) + end + end + + end + + -- Number of required carriers. + local NreqMin,NreqMax=Transport:GetRequiredCarriers() + + -- Number of assets. At most NreqMax. + local Nassets=math.min(#Assets, NreqMax) + + if Nassets>=NreqMin then + + --- + -- Found enough assets + --- + + -- Add assets to mission. + for i=1,Nassets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + asset.isReserved=true + Transport:AddAsset(asset) + end + + if self:IsAirwing() then + + -- Return payloads of not needed assets. + for i=Nassets+1,#Assets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if not asset.spawned then + self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + self:ReturnPayloadFromAsset(asset) + end + end + + end + + -- Found enough assets. + return true + else + + --- + -- NOT enough assets + --- + + -- Return payloads of assets. + if self:IsAirwing() then + for i=1,#Assets do + local asset=Assets[i] + if not asset.spawned then + self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + self:ReturnPayloadFromAsset(asset) + end + end + end + + -- Not enough assets found. + return false + end + +end + + +--- Optimize chosen assets for the mission at hand. +-- @param #LEGION self +-- @param #table assets Table of (unoptimized) assets. +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. +function LEGION:_OptimizeAssetSelectionForTransport(assets, Transport) + + -- Calculate the mission score of all assets. + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.score=self:CalculateAssetTransportScore(asset, Transport) + end + + --- Sort assets wrt to their mission score. Higher is better. + local function optimize(a, b) + local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem + local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem + -- Higher score wins. If equal score ==> closer wins. + return (assetA.score>assetB.score) + end + table.sort(assets, optimize) + + -- Remove distance parameter. + local text=string.format("Optimized %d assets for transport:", #assets) + for i,Asset in pairs(assets) do + local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem + text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) + asset.dist=nil + asset.score=nil + end + self:T2(self.lid..text) + +end + +--- Calculate the mission score of an asset. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. +-- @return #number Mission score. +function LEGION:CalculateAssetTransportScore(asset, Transport) + + -- Mission score. + local score=0 + + -- Prefer highly skilled assets. + if asset.skill==AI.Skill.AVERAGE then + score=score+0 + elseif asset.skill==AI.Skill.GOOD then + score=score+10 + elseif asset.skill==AI.Skill.HIGH then + score=score+20 + elseif asset.skill==AI.Skill.EXCELLENT then + score=score+30 + end + + -- Add mission performance to score. + score=score+asset.cohort:GetMissionPeformance(AUFTRAG.Type.OPSTRANSPORT) + + -- Target position. + local TargetVec2=Transport:GetDeployZone():GetVec2() + + -- Origin: We take the flightgroups position or the one of the legion. + local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2() or self:GetVec2() + + -- Distance factor. + local distance=0 + if TargetVec2 and OrigVec2 then + -- Distance in NM. + distance=UTILS.MetersToNM(UTILS.VecDist2D(OrigVec2, TargetVec2)) + -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 + distance=UTILS.Round(distance/10, 0) + end + + -- Reduce score for legions that are futher away. + score=score-distance + + --TODO: Check cargo bay capacity. + + --TODO: Check ALERT 5 for Transports. + if asset.spawned then + self:T(self.lid.."Adding 25 to asset because it is spawned") + score=score+25 + end + + return score +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Check if a mission type is contained in a list of possible types. -- @param #LEGION self diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8ec6d644b..7e72e1a32 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4114,6 +4114,7 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) self:_SwitchICLS() end + -- We add a 10 sec delay for ARTY. Found that they need some time to readjust the barrel of their gun. Not sure if necessary for all. Needs some more testing! local delay=1 if Mission.type==AUFTRAG.Type.ARTY then delay=10 From 1d0eb9806dcb6049f9c0dfda74c8ed351f446443 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 13 Sep 2021 08:31:00 +0200 Subject: [PATCH 100/141] COMMANDER - Added OPS transport (untested) --- Moose Development/Moose/Core/Zone.lua | 14 + .../Moose/Functional/Warehouse.lua | 137 ++-- Moose Development/Moose/Ops/Chief.lua | 26 + Moose Development/Moose/Ops/Commander.lua | 615 ++++++++++++++---- Moose Development/Moose/Ops/FlightGroup.lua | 18 +- Moose Development/Moose/Ops/Legion.lua | 29 +- Moose Development/Moose/Ops/OpsGroup.lua | 50 +- Moose Development/Moose/Ops/OpsTransport.lua | 26 +- Moose Development/Moose/Ops/Platoon.lua | 14 +- 9 files changed, 672 insertions(+), 257 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 45419d5f9..813d74e1f 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -2223,6 +2223,20 @@ do -- ZONE_AIRBASE self._.ZoneAirbase = Airbase self._.ZoneVec2Cache = self._.ZoneAirbase:GetVec2() + + if Airbase:IsShip() then + self.isShip=true + self.isHelipad=false + self.isAirdrome=false + elseif Airbase:IsHelipad() then + self.isShip=false + self.isHelipad=true + self.isAirdrome=false + elseif Airbase:IsAirdrome() then + self.isShip=false + self.isHelipad=false + self.isAirdrome=true + end -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 3fa15d19a..e67257aa5 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1866,6 +1866,9 @@ function WAREHOUSE:New(warehouse, alias) self.isunit=false else self.isunit=true + if warehouse:IsShip() then + self.isShip=true + end end end @@ -1909,8 +1912,14 @@ function WAREHOUSE:New(warehouse, alias) end -- Define warehouse and default spawn zone. - self.zone=ZONE_RADIUS:New(string.format("Warehouse zone %s", self.warehouse:GetName()), warehouse:GetVec2(), 500) - self.spawnzone=ZONE_RADIUS:New(string.format("Warehouse %s spawn zone", self.warehouse:GetName()), warehouse:GetVec2(), 250) + if self.isShip then + self.zone=ZONE_AIRBASE:New(self.warehouse:GetName(), 1000) + self.spawnzone=ZONE_AIRBASE:New(self.warehouse:GetName(), 1000) + else + self.zone=ZONE_RADIUS:New(string.format("Warehouse zone %s", self.warehouse:GetName()), warehouse:GetVec2(), 500) + self.spawnzone=ZONE_RADIUS:New(string.format("Warehouse %s spawn zone", self.warehouse:GetName()), warehouse:GetVec2(), 250) + end + -- Defaults self:SetMarker(true) @@ -4507,6 +4516,11 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) return end + -- Trigger event. + if spawngroup then + self:__AssetSpawned(0.01, spawngroup, _assetitem, Request) + end + end -- Init problem table. @@ -5336,24 +5350,6 @@ function WAREHOUSE:onafterRunwayRepaired(From, Event, To) end ---- On before "AssetSpawned" event. Checks whether the asset was already set to "spawned" for groups with multiple units. --- @param #WAREHOUSE self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Wrapper.Group#GROUP group The group spawned. --- @param #WAREHOUSE.Assetitem asset The asset that is dead. --- @param #WAREHOUSE.Pendingitem request The request of the dead asset. -function WAREHOUSE:onbeforeAssetSpawned(From, Event, To, group, asset, request) - if asset.spawned then - --return false - else - --return true - end - - return true -end - --- On after "AssetSpawned" event triggered when an asset group is spawned into the cruel world. -- @param #WAREHOUSE self -- @param #string From From state. @@ -5368,6 +5364,24 @@ function WAREHOUSE:onafterAssetSpawned(From, Event, To, group, asset, request) -- Sete asset state to spawned. asset.spawned=true + + -- Set spawn group name. + asset.spawngroupname=group:GetName() + + -- Remove asset from stock. + self:_DeleteStockItem(asset) + + -- Add group. + if asset.iscargo==true then + request.cargogroupset=request.cargogroupset or SET_GROUP:New() + request.cargogroupset:AddGroup(group) + else + request.transportgroupset=request.transportgroupset or SET_GROUP:New() + request.transportgroupset:AddGroup(group) + end + + -- Set warehouse state. + group:SetState(group, "WAREHOUSE", self) -- Check if all assets groups are spawned and trigger events. local n=0 @@ -5718,15 +5732,15 @@ function WAREHOUSE:_SpawnAssetRequest(Request) if asset.category==Group.Category.GROUND then -- Spawn ground troops. - _group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.spawnzone) + _group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.spawnzone, Request.lateActivation) elseif asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then -- Spawn air units. if Parking[asset.uid] then - _group=self:_SpawnAssetAircraft(_alias, asset, Request, Parking[asset.uid], UnControlled) + _group=self:_SpawnAssetAircraft(_alias, asset, Request, Parking[asset.uid], UnControlled, Request.lateActivation) else - _group=self:_SpawnAssetAircraft(_alias, asset, Request, nil, UnControlled) + _group=self:_SpawnAssetAircraft(_alias, asset, Request, nil, UnControlled, Request.lateActivation) end elseif asset.category==Group.Category.TRAIN then @@ -5736,7 +5750,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) --TODO: Rail should only get one asset because they would spawn on top! -- Spawn naval assets. - _group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.spawnzone) + _group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.spawnzone, Request.lateActivation) end --self:E(self.lid.."ERROR: Spawning of TRAIN assets not possible yet!") @@ -5744,11 +5758,16 @@ function WAREHOUSE:_SpawnAssetRequest(Request) elseif asset.category==Group.Category.SHIP then -- Spawn naval assets. - _group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.portzone) + _group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.portzone, Request.lateActivation) else self:E(self.lid.."ERROR: Unknown asset category!") end + + -- Trigger event. + if _group then + self:__AssetSpawned(0.01, _group, asset, Request) + end end @@ -5761,9 +5780,9 @@ end -- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. -- @param Core.Zone#ZONE spawnzone Zone where the assets should be spawned. --- @param #boolean aioff If true, AI of ground units are set to off. +-- @param #boolean lateactivated If true, groups are spawned late activated. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, aioff) +function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, lateactivated) if asset and (asset.category==Group.Category.GROUND or asset.category==Group.Category.SHIP or asset.category==Group.Category.TRAIN) then @@ -5806,6 +5825,11 @@ function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, aiof end end + + -- Late activation. + template.lateActivation=lateactivated + + env.info("FF lateActivation="..tostring(template.lateActivation)) template.route.points[1].x = coord.x template.route.points[1].y = coord.z @@ -5817,14 +5841,6 @@ function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, aiof -- Spawn group. local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP - -- Activate group. Should only be necessary for late activated groups. - --group:Activate() - - -- Switch AI off if desired. This works only for ground and naval groups. - if aioff then - group:SetAIOff() - end - return group end @@ -5838,8 +5854,9 @@ end -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. -- @param #table parking Parking data for this asset. -- @param #boolean uncontrolled Spawn aircraft in uncontrolled state. +-- @param #boolean lateactivated If true, groups are spawned late activated. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled) +function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled, lateactivated) if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then @@ -6329,54 +6346,13 @@ function WAREHOUSE:_OnEventBirth(EventData) local request=self:GetRequestByID(rid) if asset and request then - - if asset.spawned and type(asset.spawned)=="boolean" and asset.spawned==true then - return - end - + -- Debug message. self:T(self.lid..string.format("Warehouse %s captured event birth of request ID=%d, asset ID=%d, unit %s spawned=%s", self.alias, request.uid, asset.uid, EventData.IniUnitName, tostring(asset.spawned))) -- Set born to true. request.born=true - - if not asset.spawned then - asset.spawned=1 - else - asset.spawned=asset.spawned+1 - end - - - -- Birth is triggered for each unit. We need to make sure not to call this too often! - if asset.spawned==asset.nunits then - - -- Remove asset from stock. - self:_DeleteStockItem(asset) - - -- Set spawned switch. - asset.spawned=true - asset.spawngroupname=group:GetName() - - -- Add group. - if asset.iscargo==true then - request.cargogroupset=request.cargogroupset or SET_GROUP:New() - request.cargogroupset:AddGroup(group) - else - request.transportgroupset=request.transportgroupset or SET_GROUP:New() - request.transportgroupset:AddGroup(group) - end - - -- Set warehouse state. - group:SetState(group, "WAREHOUSE", self) - - -- Asset spawned FSM function. - -- This needs to be delayed a bit for all units to be present. Especially, since MOOSE needs a birth event for UNITs to be added to the _DATABASE. - self:__AssetSpawned(0.1, group, asset, request) - --self:AssetSpawned(group, asset, request) - - end - else self:E(self.lid..string.format("ERROR: Either asset AID=%s or request RID=%s are nil in event birth of unit %s", tostring(aid), tostring(rid), tostring(EventData.IniUnitName))) end @@ -7076,10 +7052,9 @@ function WAREHOUSE:_CheckRequestValid(request) -- Check that both spawn zones are not in water. local inwater=self.spawnzone:GetCoordinate():IsSurfaceTypeWater() or request.warehouse.spawnzone:GetCoordinate():IsSurfaceTypeWater() - if inwater then + if inwater and not request.lateActivation then self:E("ERROR: Incorrect request. Ground asset requested but at least one spawn zone is in water!") - --valid=false - valid=false + return false end -- No ground assets directly to or from ships. diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 0e3c868b2..255cb9f4f 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -299,6 +299,32 @@ function CHIEF:RemoveMission(Mission) return self end +--- Add transport to transport queue of the COMMANDER. +-- @param #CHIEF self +-- @param Ops.OpsTransport#OPSTRANSPORT Transport Transport to be added. +-- @return #CHIEF self +function CHIEF:AddOpsTransport(Transport) + + Transport.chief=self + + self.commander:AddOpsTransport(Transport) + + return self +end + +--- Remove transport from queue. +-- @param #CHIEF self +-- @param Ops.OpsTransport#OPSTRANSPORT Transport Transport to be removed. +-- @return #CHIEF self +function CHIEF:RemoveTransport(Transport) + + Transport.chief=nil + + self.commander:RemoveTransport(Transport) + + return self +end + --- Add target. -- @param #CHIEF self -- @param Ops.Target#TARGET Target Target object to be added. diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index dc4f4508c..f8776fa98 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -19,6 +19,7 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #table legions Table of legions which are commanded. -- @field #table missionqueue Mission queue. +-- @field #table transportqueue Transport queue. -- @field Ops.ChiefOfStaff#CHIEF chief Chief of staff. -- @extends Core.Fsm#FSM @@ -37,6 +38,7 @@ COMMANDER = { verbose = 0, legions = {}, missionqueue = {}, + transportqueue = {}, } --- COMMANDER class version. @@ -80,6 +82,9 @@ function COMMANDER:New() self:AddTransition("*", "MissionAssign", "*") -- Mission is assigned to a or multiple LEGIONs. self:AddTransition("*", "MissionCancel", "*") -- COMMANDER cancels a mission. + self:AddTransition("*", "TransportAssign", "*") -- Transport is assigned to a or multiple LEGIONs. + self:AddTransition("*", "TransportCancel", "*") -- COMMANDER cancels a Transport. + ------------------------ --- Pseudo Functions --- ------------------------ @@ -155,6 +160,43 @@ function COMMANDER:New() -- @param #string To To state. -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "TransportAssign" after a delay. + -- @function [parent=#COMMANDER] __TransportAssign + -- @param #COMMANDER self + -- @param #number delay Delay in seconds. + -- @param Ops.Legion#LEGION Legion The Legion. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- On after "TransportAssign" event. + -- @function [parent=#COMMANDER] OnAfterTransportAssign + -- @param #COMMANDER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Legion#LEGION Legion The Legion. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + + --- Triggers the FSM event "TransportCancel". + -- @function [parent=#COMMANDER] TransportCancel + -- @param #COMMANDER self + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- Triggers the FSM event "TransportCancel" after a delay. + -- @function [parent=#COMMANDER] __TransportCancel + -- @param #COMMANDER self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- On after "TransportCancel" event. + -- @function [parent=#COMMANDER] OnAfterTransportCancel + -- @param #COMMANDER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + return self end @@ -225,6 +267,21 @@ function COMMANDER:AddMission(Mission) return self end +--- Add transport to queue. +-- @param #COMMANDER self +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The OPS transport to be added. +-- @return #COMMANDER self +function COMMANDER:AddOpsTransport(Transport) + + Transport.commander=self + + Transport.statusCommander=TRANSPORT.Status.PLANNED + + table.insert(self.transportqueue, Transport) + + return self +end + --- Remove mission from queue. -- @param #COMMANDER self -- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed. @@ -246,6 +303,27 @@ function COMMANDER:RemoveMission(Mission) return self end +--- Remove transport from queue. +-- @param #COMMANDER self +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The OPS transport to be removed. +-- @return #COMMANDER self +function COMMANDER:RemoveTransport(Transport) + + for i,_transport in pairs(self.transportqueue) do + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + + if transport.uid==Transport.uid then + self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", transport.uid, transport:GetState())) + transport.commander=nil + table.remove(self.transportqueue, i) + break + end + + end + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -292,6 +370,8 @@ function COMMANDER:onafterStatus(From, Event, To) -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() + + -- Check mission queue and assign one PLANNED mission --- -- LEGIONS @@ -387,6 +467,20 @@ function COMMANDER:onafterStatus(From, Event, To) end self:I(self.lid..text) end + + --- + -- TRANSPORTS + --- + + -- Transport queue. + if self.verbose>=2 and #self.transportqueue>0 then + local text="Transport queue:" + for i,_transport in pairs(self.transportqueue) do + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + text=text..string.format("\n[%d] UID=%d: status=%s", i, transport.uid, transport:GetState()) + end + self:I(self.lid..text) + end self:__Status(-30) end @@ -455,8 +549,31 @@ function COMMANDER:onafterMissionCancel(From, Event, To, Mission) end +--- On after "MissionAssign" event. Mission is added to a LEGION mission queue. +-- @param #COMMANDER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Legion#LEGION Legion The LEGION. +-- @param Ops.OpsTransport#OPSTRANSPORT +function COMMANDER:onafterTransportAssign(From, Event, To, Legion, Transport) + + -- Debug info. + self:I(self.lid..string.format("Assigning transport %d to legion %s", Transport.uid, Legion.alias)) + + -- Set mission commander status to QUEUED as it is now queued at a legion. + Transport.statusCommander=OPSTRANSPORT.Status.QUEUED + + -- Add mission to legion. + Legion:AddOpsTransport(Transport) + + -- Directly request the mission as the assets have already been selected. + Legion:TransportRequest(Transport) + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Resources +-- Mission Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Check mission queue and assign ONE planned mission. @@ -538,113 +655,6 @@ function COMMANDER:CheckMissionQueue() end ---- Check all legions if they are able to do a specific mission type at a certain location with a given number of assets. --- @param #COMMANDER self --- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @return #table Table of LEGIONs that can do the mission and have at least one asset available right now. -function COMMANDER:GetLegionsForMission(Mission) - - -- Table of legions that can do the mission. - local legions={} - - -- Loop over all legions. - for _,_legion in pairs(self.legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Count number of assets in stock. - local Nassets=0 - if legion:IsAirwing() then - Nassets=legion:CountAssetsWithPayloadsInStock(Mission.payloads, {Mission.type}, Attributes) - else - Nassets=legion:CountAssets(true, {Mission.type}, Attributes) --Could also specify the attribute if Air or Ground mission. - end - - -- Has it assets that can? - if Nassets>0 and false then - - -- Get coordinate of the target. - local coord=Mission:GetTargetCoordinate() - - if coord then - - -- Distance from legion to target. - local distance=UTILS.MetersToNM(coord:Get2DDistance(legion:GetCoordinate())) - - -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 - local dist=UTILS.Round(distance/10, 0) - - -- Debug info. - self:I(self.lid..string.format("Got legion %s with Nassets=%d and dist=%.1f NM, rounded=%.1f", legion.alias, Nassets, distance, dist)) - - -- Add legion to table of legions that can. - table.insert(legions, {airwing=legion, distance=distance, dist=dist, targetcoord=coord, nassets=Nassets}) - - end - - end - - -- Add legion if it can provide at least 1 asset. - if Nassets>0 then - table.insert(legions, legion) - end - - end - - return legions -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. --- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. --- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. --- @return #number Amount of asset groups. -function COMMANDER:CountAssets(InStock, MissionTypes, Attributes) - - local N=0 - for _,_legion in pairs(self.legions) do - local legion=_legion --Ops.Legion#LEGION - N=N+legion:CountAssets(InStock, MissionTypes, Attributes) - end - - return N -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. --- @param #table Legions (Optional) Table of legions. Default is all legions. --- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. --- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. --- @return #number Amount of asset groups. -function COMMANDER:GetAssets(InStock, Legions, MissionTypes, Attributes) - - -- Selected assets. - local assets={} - - for _,_legion in pairs(Legions or self.legions) do - local legion=_legion --Ops.Legion#LEGION - - --TODO Check if legion is running and maybe if runway is operational if air assets are requested. - - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - - for _,_asset in pairs(cohort.assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - -- TODO: Check if repaired. - -- TODO: currently we take only unspawned assets. - if not (asset.spawned or asset.isReserved or asset.requested) then - table.insert(assets, asset) - end - - end - end - end - - return assets -end --- Recruit assets for a given mission. -- @param #COMMANDER self @@ -757,7 +767,7 @@ function COMMANDER:RecruitAssets(Mission) -- Add assets to mission. for i=1,Nassets do local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - self:T(self.lid..string.format("Adding asset %s to mission %s [%s]", asset.spawngroupname, Mission.name, Mission.type)) + asset.isReserved=true Mission:AddAsset(asset) Legions[asset.legion.alias]=asset.legion end @@ -832,6 +842,393 @@ function COMMANDER:_OptimizeAssetSelection(assets, Mission, includePayload) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Transport Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check transport queue and assign ONE planned transport. +-- @param #COMMANDER self +function COMMANDER:CheckTransportQueue() + + -- Number of missions. + local Ntransports=#self.transportqueue + + -- Treat special cases. + if Ntransports==0 then + return nil + end + + -- Sort results table wrt prio and start time. + local function _sort(a, b) + local taskA=a --Ops.Auftrag#AUFTRAG + local taskB=b --Ops.Auftrag#AUFTRAG + return (taskA.prio0 then + + -- Calculate the max weight so we know which cohorts can provide carriers. + for _,_opsgroup in pairs(cargoOpsGroups) do + local opsgroup=_opsgroup --Ops.OpsGroup#OPSGROUP + local weight=opsgroup:GetWeightTotal() + if weight>weightGroup then + weightGroup=weight + end + end + + else + -- No cargo groups! + return false, {} + end + + -- The recruited assets. + local Assets={} + + -- Legions we consider for selecting assets. + local legions=self.legions + + --TODO: Setting of Mission.squadrons (cohorts) will not work here! + + -- Legions which have the best assets for the Mission. + local Legions={} + + for _,_legion in pairs(legions) do + local legion=_legion --Ops.Legion#LEGION + + -- Number of payloads in stock per aircraft type. + local Npayloads={} + + -- First get payloads for aircraft types of squadrons. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + if Npayloads[cohort.aircrafttype]==nil then + Npayloads[cohort.aircrafttype]=legion:IsAirwing() and legion:CountPayloadsInStock(AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype) or 999 + self:I(self.lid..string.format("Got N=%d payloads for mission type %s [%s]", Npayloads[cohort.aircrafttype], AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype)) + end + end + + -- Loops over cohorts. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + local npayloads=Npayloads[cohort.aircrafttype] + + if cohort:IsOnDuty() and npayloads>0 and cohort:CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}) and cohort.cargobayLimit>=weightGroup then + + -- Recruit assets from squadron. + local assets, npayloads=cohort:RecruitAssets(AUFTRAG.Type.OPSTRANSPORT, npayloads) + + Npayloads[cohort.aircrafttype]=npayloads + + for _,asset in pairs(assets) do + table.insert(Assets, asset) + end + + end + + end + + end + + -- Now we have a long list with assets. + self:_OptimizeAssetSelectionForTransport(Assets, Transport) + + for _,_asset in pairs(Assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + if asset.legion:IsAirwing() then + + -- Only assets that have no payload. Should be only spawned assets! + if not asset.payload then + + -- Fetch payload for asset. This can be nil! + asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, AUFTRAG.Type.OPSTRANSPORT) + + end + + end + + end + + -- Remove assets that dont have a payload. + for i=#Assets,1,-1 do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.payload then + table.remove(Assets, i) + end + end + + + -- Number of required carriers. + local NreqMin,NreqMax=Transport:GetRequiredCarriers() + + -- Number of assets. At most NreqMax. + local Nassets=math.min(#Assets, NreqMax) + + if Nassets>=NreqMin then + + --- + -- Found enough assets + --- + + -- Add assets to transport. + for i=1,Nassets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + asset.isReserved=true + Transport:AddAsset(asset) + Legions[asset.legion.alias]=asset.legion + end + + + -- Return payloads of not needed assets. + for i=Nassets+1,#Assets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.spawned then + self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + asset.legion:ReturnPayloadFromAsset(asset) + end + end + + -- Found enough assets. + return true, Legions + else + + --- + -- NOT enough assets + --- + + -- Return payloads of assets. + if self:IsAirwing() then + for i=1,#Assets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.spawned then + self:T2(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + asset.legion:ReturnPayloadFromAsset(asset) + end + end + end + + -- Not enough assets found. + return false, {} + end + + return nil, {} +end + +--- Optimize chosen assets for the given transport. +-- @param #COMMANDER self +-- @param #table assets Table of (unoptimized) assets. +-- @param Ops.OpsTransport#OPSTRANSPORT Transport Transport assignment. +function COMMANDER:_OptimizeAssetSelectionForTransport(assets, Transport) + + -- Calculate the mission score of all assets. + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.score=asset.legion:CalculateAssetTransportScore(asset, Transport) + end + + --- Sort assets wrt to their mission score. Higher is better. + local function optimize(a, b) + local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem + local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem + -- Higher score wins. If equal score ==> closer wins. + return (assetA.score>assetB.score) + end + table.sort(assets, optimize) + + -- Remove distance parameter. + local text=string.format("Optimized %d assets for %s mission (payload=%s):", #assets, Mission.type, tostring(includePayload)) + for i,Asset in pairs(assets) do + local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem + text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) + asset.dist=nil + asset.score=nil + end + self:T2(self.lid..text) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Resources +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- 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. +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Amount of asset groups. +function COMMANDER:CountAssets(InStock, MissionTypes, Attributes) + + local N=0 + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + N=N+legion:CountAssets(InStock, MissionTypes, Attributes) + end + + return N +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. +-- @param #table Legions (Optional) Table of legions. Default is all legions. +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return #number Amount of asset groups. +function COMMANDER:GetAssets(InStock, Legions, MissionTypes, Attributes) + + -- Selected assets. + local assets={} + + for _,_legion in pairs(Legions or self.legions) do + local legion=_legion --Ops.Legion#LEGION + + --TODO Check if legion is running and maybe if runway is operational if air assets are requested. + + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + for _,_asset in pairs(cohort.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- TODO: Check if repaired. + -- TODO: currently we take only unspawned assets. + if not (asset.spawned or asset.isReserved or asset.requested) then + table.insert(assets, asset) + end + + end + end + end + + return assets +end + +--- Check all legions if they are able to do a specific mission type at a certain location with a given number of assets. +-- @param #COMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return #table Table of LEGIONs that can do the mission and have at least one asset available right now. +function COMMANDER:GetLegionsForMission(Mission) + + -- Table of legions that can do the mission. + local legions={} + + -- Loop over all legions. + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + + -- Count number of assets in stock. + local Nassets=0 + if legion:IsAirwing() then + Nassets=legion:CountAssetsWithPayloadsInStock(Mission.payloads, {Mission.type}, Attributes) + else + Nassets=legion:CountAssets(true, {Mission.type}, Attributes) --Could also specify the attribute if Air or Ground mission. + end + + -- Has it assets that can? + if Nassets>0 and false then + + -- Get coordinate of the target. + local coord=Mission:GetTargetCoordinate() + + if coord then + + -- Distance from legion to target. + local distance=UTILS.MetersToNM(coord:Get2DDistance(legion:GetCoordinate())) + + -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 + local dist=UTILS.Round(distance/10, 0) + + -- Debug info. + self:I(self.lid..string.format("Got legion %s with Nassets=%d and dist=%.1f NM, rounded=%.1f", legion.alias, Nassets, distance, dist)) + + -- Add legion to table of legions that can. + table.insert(legions, {airwing=legion, distance=distance, dist=dist, targetcoord=coord, nassets=Nassets}) + + end + + end + + -- Add legion if it can provide at least 1 asset. + if Nassets>0 then + table.insert(legions, legion) + end + + end + + return legions +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index a5e3b0036..abd32593c 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1374,16 +1374,17 @@ end -- @param Wrapper.Airbase#AIRBASE.ParkingSpot Spot Parking Spot. function FLIGHTGROUP:onafterElementParking(From, Event, To, Element, Spot) + -- Set parking spot. + if Spot then + self:_SetElementParkingAt(Element, Spot) + end + -- Debug info. self:T(self.lid..string.format("Element parking %s at spot %s", Element.name, Element.parking and tostring(Element.parking.TerminalID) or "N/A")) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.PARKING) - if Spot then - self:_SetElementParkingAt(Element, Spot) - end - if self:IsTakeoffCold() then -- Wait for engine startup event. elseif self:IsTakeoffHot() then @@ -1660,11 +1661,16 @@ end -- @param #string Event Event. -- @param #string To To state. function FLIGHTGROUP:onafterParking(From, Event, To) - self:T(self.lid..string.format("Flight is parking")) + -- Get closest airbase local airbase=self:GetClosestAirbase() --self.group:GetCoordinate():GetClosestAirbase() - local airbasename=airbase:GetName() or "unknown" + + -- Debug info + self:T(self.lid..string.format("Flight is parking at airbase %s", airbasename)) + + -- Set current airbase. + self.currbase=airbase -- Parking time stamp. self.Tparking=timer.getAbsTime() diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index c7930bcad..96de22712 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -256,8 +256,11 @@ function LEGION:AddMission(Mission) -- Add ops transport to transport Legions. if Mission.opstransport then + local PickupZone=self.spawnzone + local DeployZone=Mission.opstransport.tzcDefault.DeployZone + -- Add a new TZC: from pickup here to the deploy zone. - local tzc=Mission.opstransport:AddTransportZoneCombo(self.spawnzone, Mission.opstransport.tzcDefault.DeployZone) + local tzc=Mission.opstransport:AddTransportZoneCombo(PickupZone, DeployZone) --TODO: Depending on "from where to where" the assets need to transported, we need to set ZONE_AIRBASE etc. @@ -601,6 +604,17 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. Mission.requestID[self.alias]=self.queueid + + -- Get request. + local request=self:GetRequestByID(self.queueid) + + if request then + if self.isShip then + self:T(self.lid.."FF request late activated") + request.lateActivation=true + end + end + end end @@ -653,7 +667,6 @@ function LEGION:onafterTransportRequest(From, Event, To, OpsTransport) -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. OpsTransport.requestID[self.alias]=self.queueid - end end @@ -1571,7 +1584,7 @@ function LEGION:RecruitAssets(Mission) MissionType=Mission.alert5MissionType end Npayloads[cohort.aircrafttype]=self:IsAirwing() and self:CountPayloadsInStock(MissionType, cohort.aircrafttype, Mission.payloads) or 999 - self:I(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) + self:T2(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) end end @@ -1818,6 +1831,9 @@ function LEGION:RecruitAssetsForTransport(Transport) weightGroup=weight end end + else + -- No cargo groups! + return false end @@ -1829,7 +1845,7 @@ function LEGION:RecruitAssetsForTransport(Transport) local cohort=_cohort --Ops.Cohort#COHORT if Npayloads[cohort.aircrafttype]==nil then Npayloads[cohort.aircrafttype]=self:IsAirwing() and self:CountPayloadsInStock(AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype) or 999 - self:I(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype)) + self:T2(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype)) end end @@ -1858,7 +1874,7 @@ function LEGION:RecruitAssetsForTransport(Transport) end -- Sort asset list. Best ones come first. - self:_OptimizeAssetSelectionForTransport(Assets, Transport, false) + self:_OptimizeAssetSelectionForTransport(Assets, Transport) -- If airwing, get the best payload available. if self:IsAirwing() then @@ -2019,7 +2035,8 @@ function LEGION:CalculateAssetTransportScore(asset, Transport) -- Reduce score for legions that are futher away. score=score-distance - --TODO: Check cargo bay capacity. + -- Add 1 score point for each 10 kg of cargo bay. + score=score+UTILS.Round(asset.cargobaymax/10, 0) --TODO: Check ALERT 5 for Transports. if asset.spawned then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 7e72e1a32..3fcc7f0eb 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -467,12 +467,12 @@ OPSGROUP.version="0.7.5" -- TODO: Invisible/immortal. -- TODO: F10 menu. -- TODO: Add pseudo function. --- TODO: Options EPLRS -- 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. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3344,8 +3344,9 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Target local target=Task.dcstask.params.target --Ops.Target#TARGET - Task.dcstask.params.lastindex=1 + self.lastindex=1 + -- Target object and zone. local object=target.targets[1] --Ops.Target#TARGET.Object local zone=object.Object --Core.Zone#ZONE @@ -4495,9 +4496,10 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- SPECIAL TASK: Recon Mission --- + -- TARGET. local target=task.dcstask.params.target --Ops.Target#TARGET - local n=task.dcstask.params.lastindex+1 + local n=self.lastindex+1 if n<=#target.targets then @@ -4528,7 +4530,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) wp.missionUID=mission and mission.auftragsnummer or nil -- Increase counter. - task.dcstask.params.lastindex=task.dcstask.params.lastindex+1 + self.lastindex=self.lastindex+1 else @@ -4599,7 +4601,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Passing mission waypoint? if Waypoint.missionUID then - self:T(self.lid.."FF passing mission waypoint") + self:T2(self.lid..string.format("Passing mission waypoint")) end -- Check if all tasks/mission are done? @@ -6636,24 +6638,17 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Flight Group --- + -- Activate uncontrolled group. + if self:IsParking() and self:IsUncontrolled() then + self:StartUncontrolled() + end + if airbasePickup then --- -- Pickup at airbase --- - -- Current airbase. - local airbaseCurrent=self.currbase - - if airbaseCurrent then - - -- Activate uncontrolled group. - if self:IsParking() and self:IsUncontrolled() then - self:StartUncontrolled() - end - - end - -- Order group to land at an airbase. self:__LandAtAirbase(-0.1, airbasePickup) @@ -6662,11 +6657,6 @@ function OPSGROUP:onafterPickup(From, Event, To) --- -- Helo can also land in a zone (NOTE: currently VTOL cannot!) --- - - -- Activate uncontrolled group. - if self:IsParking() and self:IsUncontrolled() then - self:StartUncontrolled(0.5) - end -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, UTILS.MetersToFeet(self.altitudeCruise), false) ; waypoint.detour=1 @@ -7004,22 +6994,16 @@ function OPSGROUP:onafterTransport(From, Event, To) -- Add waypoint. if self:IsFlightgroup() then + -- Activate uncontrolled group. + if self:IsParking() and self:IsUncontrolled() then + self:StartUncontrolled() + end + if airbaseDeploy then --- -- Deploy at airbase --- - - local airbaseCurrent=self.currbase - - if airbaseCurrent then - - -- Activate uncontrolled group. - if self:IsParking() and self:IsUncontrolled() then - self:StartUncontrolled() - end - - end -- Order group to land at an airbase. self:__LandAtAirbase(-0.1, airbaseDeploy) diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 50b613719..d33453ad9 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -53,6 +53,8 @@ -- @field #table assets Warehouse assets assigned for this transport. -- @field #table legions Assigned legions. -- @field #table statusLegion Transport status of all assigned LEGIONs. +-- @field #string statusCommander Staus of the COMMANDER. +-- @field Ops.Commander#COMMANDER commander Commander of the transport. -- @field #table requestID The ID of the queued warehouse request. Necessary to cancel the request if the transport was cancelled before the request is processed. -- -- @extends Core.Fsm#FSM @@ -133,6 +135,9 @@ OPSTRANSPORT = { -- @field #string SCHEDULED Transport is scheduled in the cargo queue. -- @field #string EXECUTING Transport is being executed. -- @field #string DELIVERED Transport was delivered. +-- @field #string CANCELLED Transport was cancelled. +-- @field #string SUCCESS Transport was a success. +-- @field #string FAILED Transport failed. OPSTRANSPORT.Status={ PLANNED="planned", QUEUED="queued", @@ -140,6 +145,9 @@ OPSTRANSPORT.Status={ SCHEDULED="scheduled", EXECUTING="executing", DELIVERED="delivered", + CANCELLED="cancelled", + SUCCESS="success", + FAILED="failed", } --- Pickup and deploy set. @@ -177,14 +185,14 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.4.2" +OPSTRANSPORT.version="0.5.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Allow multiple pickup/depoly zones. -- TODO: Stop/abort transport. +-- DONE: Allow multiple pickup/depoly zones. -- DONE: Add start conditions. -- DONE: Check carrier(s) dead. @@ -459,20 +467,6 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) -- Call iteravely for each group. self:AddCargoGroups(group, TransportZoneCombo) - - --[[ - local cargo=self:_CreateCargoGroupData(group) - - if cargo then - -- Add to main table. - table.insert(self.cargos, cargo) - self.Ncargo=self.Ncargo+1 - - -- Add to TZC table. - table.insert(TransportZoneCombo.Cargos, cargo) - TransportZoneCombo.Ncargo=TransportZoneCombo.Ncargo+1 - end - ]] end end diff --git a/Moose Development/Moose/Ops/Platoon.lua b/Moose Development/Moose/Ops/Platoon.lua index da63f6400..cbaa8748f 100644 --- a/Moose Development/Moose/Ops/Platoon.lua +++ b/Moose Development/Moose/Ops/Platoon.lua @@ -110,14 +110,16 @@ function PLATOON:AddWeaponRange(RangeMin, RangeMax, BitType) self.weaponData[tostring(weapon.BitType)]=weapon -- Debug info. - env.info(string.format("FF Adding weapon data: Bit=%s, Rmin=%d m, Rmax=%d m", tostring(weapon.BitType), weapon.RangeMin, weapon.RangeMax)) + self:T(self.lid..string.format("Adding weapon data: Bit=%s, Rmin=%d m, Rmax=%d m", tostring(weapon.BitType), weapon.RangeMin, weapon.RangeMax)) - local text="Weapon data:" - for _,_weapondata in pairs(self.weaponData) do - local weapondata=_weapondata - text=text..string.format("\n- Bit=%s, Rmin=%d m, Rmax=%d m", tostring(weapondata.BitType), weapondata.RangeMin, weapondata.RangeMax) + if self.verbose>=2 then + local text="Weapon data:" + for _,_weapondata in pairs(self.weaponData) do + local weapondata=_weapondata + text=text..string.format("\n- Bit=%s, Rmin=%d m, Rmax=%d m", tostring(weapondata.BitType), weapondata.RangeMin, weapondata.RangeMax) + end + self:I(self.lid..text) end - self:I(self.lid..text) return self end From f038564b1ba62862ad5a9dc65f62e6ccfefdb8c8 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 13 Sep 2021 16:04:16 +0200 Subject: [PATCH 101/141] OPSTRANSPORT - Added Cancel feature. First steps... - Fixed a couple of other little bugs. --- Moose Development/Moose/Ops/AirWing.lua | 6 +- Moose Development/Moose/Ops/Auftrag.lua | 3 + Moose Development/Moose/Ops/Chief.lua | 47 +++++++ Moose Development/Moose/Ops/Commander.lua | 63 +++++++-- Moose Development/Moose/Ops/Legion.lua | 69 +++++++++- Moose Development/Moose/Ops/OpsGroup.lua | 138 ++++++++++++++++++- Moose Development/Moose/Ops/OpsTransport.lua | 99 ++++++++++++- 7 files changed, 400 insertions(+), 25 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 1555b3229..e1eb895ac 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -463,12 +463,12 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) -- Debug. if self.verbose>=4 then - self:I(self.lid..string.format("Sorted payloads for mission type X and aircraft type=Y:")) + self:I(self.lid..string.format("Sorted payloads for mission type %s and aircraft type=%s:", MissionType, UnitType)) for _,_payload in ipairs(self.payloads) do local payload=_payload --#AIRWING.Payload if payload.aircrafttype==UnitType and self:CheckMissionCapability(MissionType, payload.capabilities) then local performace=self:GetPayloadPeformance(payload, MissionType) - self:I(self.lid..string.format("FF %s payload for %s: avail=%d performace=%d", MissionType, payload.aircrafttype, payload.navail, performace)) + self:I(self.lid..string.format("- %s payload for %s: avail=%d performace=%d", MissionType, payload.aircrafttype, payload.navail, performace)) end end end @@ -476,7 +476,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) -- Cases: if #payloads==0 then -- No payload available. - self:T(self.lid.."Warning could not find a payload for airframe X mission type Y!") + self:T(self.lid..string.format("WARNING: Could not find a payload for airframe %s mission type %s!", UnitType, MissionType)) return nil elseif #payloads==1 then -- Only one payload anyway. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index d45c51332..84cfec085 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -3420,6 +3420,9 @@ function AUFTRAG:onafterRepeat(From, Event, To) self.Ncasualties=0 self.Nelements=0 + -- Update DCS mission task. Could be that the initial task (e.g. for bombing) was destroyed. Then we need to update the coordinate. + self.DCStask=self:GetDCSMissionTask() + -- Call status again. self:__Status(-30) diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 255cb9f4f..008e71b85 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -182,6 +182,26 @@ function CHIEF:New(AgentSet, Coalition) -- @param #string To To state. -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "TransportCancel". + -- @function [parent=#CHIEF] TransportCancel + -- @param #CHIEF self + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- Triggers the FSM event "TransportCancel" after a delay. + -- @function [parent=#CHIEF] TransportCancel + -- @param #CHIEF self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- On after "TransportCancel" event. + -- @function [parent=#CHIEF] OnAfterTransportCancel + -- @param #CHIEF self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + return self end @@ -672,6 +692,33 @@ function CHIEF:onafterMissionCancel(From, Event, To, Mission) end +--- On after "TransportCancel" event. +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. +function CHIEF:onafterTransportCancel(From, Event, To, Transport) + + -- Debug info. + self:I(self.lid..string.format("Cancelling transport UID=%d in status %s", Transport.uid, Transport:GetState())) + + if Transport:IsPlanned() then + + -- Mission is still in planning stage. Should not have any LEGIONS assigned ==> Just remove it form the COMMANDER queue. + self:RemoveTransport(Transport) + + else + + -- COMMANDER will cancel mission. + if Transport.commander then + Transport.commander:TransportCancel(Transport) + end + + end + +end + --- On before "Defcon" event. -- @param #CHIEF self -- @param #string From From state. diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index f8776fa98..a739f045c 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -572,6 +572,43 @@ function COMMANDER:onafterTransportAssign(From, Event, To, Legion, Transport) end +--- On after "TransportCancel" event. +-- @param #COMMANDER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. +function COMMANDER:onafterTransportCancel(From, Event, To, Transport) + + -- Debug info. + self:I(self.lid..string.format("Cancelling Transport UID=%d in status %s", Transport.uid, Transport:GetState())) + + -- Set commander status. + Transport.statusCommander=OPSTRANSPORT.Status.CANCELLED + + if Transport:IsPlanned() then + + -- Transport is still in planning stage. Should not have a legion assigned ==> Just remove it form the queue. + self:RemoveTransport(Transport) + + else + + -- Legion will cancel mission. + if #Transport.legions>0 then + for _,_legion in pairs(Transport.legions) do + local legion=_legion --Ops.Legion#LEGION + + -- TODO: Should check that this legions actually belongs to this commander. + + -- Legion will cancel the mission. + legion:TransportCancel(Transport) + end + end + + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Mission Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -689,7 +726,7 @@ function COMMANDER:RecruitAssets(Mission) MissionType=Mission.alert5MissionType end Npayloads[cohort.aircrafttype]=legion:IsAirwing() and legion:CountPayloadsInStock(MissionType, cohort.aircrafttype, Mission.payloads) or 999 - self:I(self.lid..string.format("Got N=%d payloads for mission type %s [%s]", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) + self:T2(self.lid..string.format("Got N=%d payloads for mission type %s [%s]", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) end end @@ -728,16 +765,16 @@ function COMMANDER:RecruitAssets(Mission) if not asset.payload then -- Set mission type. - local MissionType=Mission.Type + local MissionType=Mission.type -- Get a loadout for the actual mission this group is waiting for. if Mission.type==AUFTRAG.Type.ALERT5 and Mission.alert5MissionType then MissionType=Mission.alert5MissionType - end - + end + -- Fetch payload for asset. This can be nil! asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, MissionType, Mission.payloads) - + end end @@ -748,6 +785,7 @@ function COMMANDER:RecruitAssets(Mission) for i=#Assets,1,-1 do local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem if asset.legion:IsAirwing() and not asset.payload then + self:T3(self.lid..string.format("Remove asset %s with no payload", tostring(asset.spawngroupname))) table.remove(Assets, i) end end @@ -791,13 +829,12 @@ function COMMANDER:RecruitAssets(Mission) --- -- Return payloads of assets. - if self:IsAirwing() then - for i=1,#Assets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.legion:IsAirwing() and not asset.spawned then - self:T2(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) - asset.legion:ReturnPayloadFromAsset(asset) - end + + for i=1,#Assets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.spawned then + self:T2(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + asset.legion:ReturnPayloadFromAsset(asset) end end @@ -972,7 +1009,7 @@ function COMMANDER:RecruitAssetsForTransport(Transport) local cohort=_cohort --Ops.Cohort#COHORT if Npayloads[cohort.aircrafttype]==nil then Npayloads[cohort.aircrafttype]=legion:IsAirwing() and legion:CountPayloadsInStock(AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype) or 999 - self:I(self.lid..string.format("Got N=%d payloads for mission type %s [%s]", Npayloads[cohort.aircrafttype], AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype)) + self:T2(self.lid..string.format("Got N=%d payloads for mission type %s [%s]", Npayloads[cohort.aircrafttype], AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype)) end end diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 96de22712..be466cc7c 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -84,6 +84,7 @@ function LEGION:New(WarehouseName, LegionName) self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. self:AddTransition("*", "TransportRequest", "*") -- Add a (mission) request to the warehouse. + self:AddTransition("*", "TransportCancel", "*") -- Cancel transport. self:AddTransition("*", "OpsOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). @@ -171,6 +172,26 @@ function LEGION:New(WarehouseName, LegionName) -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + --- Triggers the FSM event "TransportCancel". + -- @function [parent=#LEGION] TransportCancel + -- @param #LEGION self + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- Triggers the FSM event "TransportCancel" after a delay. + -- @function [parent=#LEGION] __TransportCancel + -- @param #LEGION self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- On after "TransportCancel" event. + -- @function [parent=#LEGION] OnAfterTransportCancel + -- @param #LEGION self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- Triggers the FSM event "OpsOnMission". -- @function [parent=#LEGION] OpsOnMission -- @param #LEGION self @@ -671,6 +692,50 @@ function LEGION:onafterTransportRequest(From, Event, To, OpsTransport) end +--- On after "TransportCancel" event. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport to be cancelled. +function LEGION:onafterTransportCancel(From, Event, To, Transport) + + -- Info message. + self:I(self.lid..string.format("Cancel transport UID=%d", Transport.uid)) + + -- Set status to cancelled. + Transport:SetLegionStatus(self, OPSTRANSPORT.Status.CANCELLED) + + for i=#Transport.assets, 1, -1 do + local asset=Transport.assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Asset should belong to this legion. + if asset.wid==self.uid then + + local opsgroup=asset.flightgroup + + if opsgroup then + opsgroup:TransportCancel(Transport) + end + + -- Remove asset from mission. + Transport:DelAsset(asset) + + -- Not requested any more (if it was). + asset.requested=nil + asset.isReserved=nil + + end + end + + -- Remove queued request (if any). + if Transport.requestID[self.alias] then + self:_DeleteQueueItemByID(Transport.requestID[self.alias], self.queue) + end + +end + + --- On after "MissionCancel" event. Cancels the missions of all flightgroups. Deletes request from warehouse queue. -- @param #LEGION self -- @param #string From From state. @@ -1625,7 +1690,7 @@ function LEGION:RecruitAssets(Mission) if not asset.payload then -- Set mission type. - local MissionType=Mission.Type + local MissionType=Mission.type -- Get a loadout for the actual mission this group is waiting for. if Mission.type==AUFTRAG.Type.ALERT5 and Mission.alert5MissionType then @@ -1729,7 +1794,7 @@ function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) end -- Add mission performance to score. - score=score+asset.cohort:GetMissionPeformance(Mission.Type) + score=score+asset.cohort:GetMissionPeformance(Mission.type) -- Add payload performance to score. if includePayload and asset.payload then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 3fcc7f0eb..ee5501cf9 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -659,7 +659,7 @@ function OPSGROUP:New(group) self:AddTransition("*", "MissionStart", "*") -- Mission is started. self:AddTransition("*", "MissionExecute", "*") -- Mission execution began. - self:AddTransition("*", "MissionCancel", "*") -- Cancel current mission. + self:AddTransition("*", "MissionCancel", "*") -- Cancel current mission. self:AddTransition("*", "PauseMission", "*") -- Pause the current mission. self:AddTransition("*", "UnpauseMission", "*") -- Unpause the the paused mission. self:AddTransition("*", "MissionDone", "*") -- Mission is over. @@ -684,6 +684,8 @@ function OPSGROUP:New(group) self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its current cargo. self:AddTransition("*", "UnloadingDone", "*") -- Carrier is unloading the cargo. self:AddTransition("*", "Delivered", "*") -- Carrier delivered ALL cargo of the transport assignment. + + self:AddTransition("*", "TransportCancel", "*") -- Cancel (current) transport. ------------------------ --- Pseudo Functions --- @@ -786,6 +788,26 @@ function OPSGROUP:New(group) -- @param #string To To state. -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "TransportCancel". + -- @function [parent=#OPSGROUP] TransportCancel + -- @param #OPSGROUP self + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- Triggers the FSM event "TransportCancel" after a delay. + -- @function [parent=#OPSGROUP] __TransportCancel + -- @param #OPSGROUP self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- On after "TransportCancel" event. + -- @function [parent=#OPSGROUP] OnAfterTransportCancel + -- @param #OPSGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + -- TODO: Add pseudo functions. return self @@ -6206,11 +6228,17 @@ function OPSGROUP:DelOpsTransport(CargoTransport) for i=#self.cargoqueue,1,-1 do local transport=self.cargoqueue[i] --Ops.OpsTransport#OPSTRANSPORT if transport.uid==CargoTransport.uid then + + -- Remove from queue. table.remove(self.cargoqueue, i) + + -- Remove carrier from ops transport. + CargoTransport:_DelCarrier(self) + return self end end - + return self end @@ -7445,6 +7473,112 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) end +--- On after "TransportCancel" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsTransport#OPSTRANSPORT The transport to be cancelled. +function OPSGROUP:onafterTransportCancel(From, Event, To, Transport) + + if self.cargoTransport and self.cargoTransport.uid==Transport.uid then + + --- + -- Current Transport + --- + + -- Debug info. + self:T(self.lid..string.format("Cancel current transport %d", Transport.uid)) + + -- Call delivered= + local calldelivered=false + + if self:IsPickingup() then + + -- On its way to the pickup zone. Remove waypoint. Will be done in delivered. + calldelivered=true + + elseif self:IsLoading() then + + -- Handle cargo groups. + local cargos=Transport:GetCargoOpsGroups(false) + + for _,_opsgroup in pairs(cargos) do + local opsgroup=_opsgroup --#OPSGROUP + + if opsgroup:IsBoarding(self.groupname) then + + -- Remove boarding waypoint. + opsgroup:RemoveWaypoint(self.currentwp+1) + + -- Remove from cargo bay (reserved), remove mycarrier, set cargo status. + self:_DelCargobay(opsgroup) + opsgroup:_RemoveMyCarrier() + opsgroup:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) + + elseif opsgroup:IsLoaded(self.groupname) then + + -- Get random point in disembark zone. + local zoneCarrier=self:GetElementZoneUnload(opsgroup:_GetMyCarrierElement().name) + + -- Random coordinate/heading in the zone. + local Coordinate=zoneCarrier and zoneCarrier:GetRandomCoordinate() or self.cargoTransport:GetEmbarkZone(self.cargoTZC):GetRandomCoordinate() + + -- Random heading of the group. + local Heading=math.random(0,359) + + -- Unload to Coordinate. + self:Unload(opsgroup, Coordinate, self.cargoTransport:GetDisembarkActivation(self.cargoTZC), Heading) + + -- Trigger "Unloaded" event for current cargo transport + self.cargoTransport:Unloaded(opsgroup, self) + + end + + end + + -- Call delivered. + calldelivered=true + + elseif self:IsTransporting() then + + -- Well, we cannot just unload the cargo anywhere. + + -- TODO: Best would be to bring the cargo back to the pickup zone! + + elseif self:IsUnloading() then + -- Unloading anyway... delivered will be called when done. + else + + end + + -- Transport delivered. + if calldelivered then + self:Delivered(Transport) + end + + else + + --- + -- NOT the current transport + --- + + -- Set mission group status. + --Transport:SetGroupStatus(self, AUFTRAG.GroupStatus.CANCELLED) + + -- Remove transport from queue. + self:DelOpsTransport(Transport) + + -- Remove carrier. + Transport:_DelCarrier(self) + + -- Send group RTB or WAIT if nothing left to do. + self:_CheckGroupDone(1) + + end + +end + --- -- Cargo Group Functions diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index d33453ad9..87352904b 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -250,6 +250,8 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) self:AddTransition("*", "Status", "*") self:AddTransition("*", "Stop", "*") + self:AddTransition("*", "Cancel", OPSTRANSPORT.Status.CANCELLED) -- Command to cancel the transport. + self:AddTransition("*", "Loaded", "*") self:AddTransition("*", "Unloaded", "*") @@ -331,6 +333,26 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Cancel". + -- @function [parent=#OPSTRANSPORT] Cancel + -- @param #OPSTRANSPORT self + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- Triggers the FSM event "Cancel" after a delay. + -- @function [parent=#OPSTRANSPORT] __Cancel + -- @param #OPSTRANSPORT self + -- @param #number delay Delay in seconds. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- On after "Cancel" event. + -- @function [parent=#OPSTRANSPORT] OnAfterCancel + -- @param #OPSTRANSPORT self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- Triggers the FSM event "Loaded". -- @function [parent=#OPSTRANSPORT] Loaded -- @param #OPSTRANSPORT self @@ -348,7 +370,7 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) --- On after "Loaded" event. -- @function [parent=#OPSTRANSPORT] OnAfterLoaded - -- @param #OPSGROUP self + -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. @@ -372,7 +394,7 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) --- On after "Unloaded" event. -- @function [parent=#OPSTRANSPORT] OnAfterUnloaded - -- @param #OPSGROUP self + -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. @@ -1616,6 +1638,74 @@ function OPSTRANSPORT:onafterDeadCarrierAll(From, Event, To) end end +--- On after "Cancel" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterCancel(From, Event, To) + + -- Number of OPSGROUPS assigned and alive. + local Ngroups = #self.carriers + + -- Debug info. + self:I(self.lid..string.format("CANCELLING mission in status %s. Will wait for %d groups to report mission DONE before evaluation", self.status, Ngroups)) + + -- Time stamp. + self.Tover=timer.getAbsTime() + + + if self.chief then + + -- Debug info. + self:T(self.lid..string.format("CHIEF will cancel the transport. Will wait for mission DONE before evaluation!")) + + -- CHIEF will cancel the transport. + self.chief:TransportCancel(self) + + elseif self.commander then + + -- Debug info. + self:T(self.lid..string.format("COMMANDER will cancel the transport. Will wait for transport DELIVERED before evaluation!")) + + -- COMMANDER will cancel the transport. + self.commander:TransportCancel(self) + + elseif self.legions and #self.legions>0 then + + -- Loop over all LEGIONs. + for _,_legion in pairs(self.legions or {}) do + local legion=_legion --Ops.Legion#LEGION + + -- Debug info. + self:T(self.lid..string.format("LEGION %s will cancel the transport. Will wait for transport DELIVERED before evaluation!", legion.alias)) + + -- Legion will cancel all flight missions and remove queued request from warehouse queue. + legion:TransportCancel(self) + + end + + else + + -- Debug info. + self:T(self.lid..string.format("No legion, commander or chief. Attached OPS groups will cancel the transport on their own. Will wait for transport DELIVERED before evaluation!")) + + -- Loop over all carrier groups. + for _,_carrier in pairs(self:GetCarriers()) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + carrier:TransportCancel(self) + end + + end + + -- Special mission states. + if self:IsPlanned() or self:IsQueued() or self:IsRequested() or Ngroups==0 then + self:T(self.lid..string.format("Cancelled transport was in %s stage with %d carrier groups assigned and alive. Call it DELIVERED!", self.status, Ngroups)) + self:Delivered() + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1653,11 +1743,10 @@ function OPSTRANSPORT:_CheckDelivered() end if dead then - --self:CargoDead() - self:I(self.lid.."All cargo DEAD!") + self:I(self.lid.."All cargo DEAD ==> Delivered!") self:Delivered() elseif done then - self:I(self.lid.."All cargo delivered") + self:I(self.lid.."All cargo DONE ==> Delivered!") self:Delivered() end From 35b50e1a9d51daf40c265f3c0743ff2856fb1732 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 14 Sep 2021 23:51:03 +0200 Subject: [PATCH 102/141] OPSZONE and CHIEF --- Moose Development/Moose/Ops/Auftrag.lua | 35 +- Moose Development/Moose/Ops/Chief.lua | 363 +++++++++++++++++--- Moose Development/Moose/Ops/Cohort.lua | 6 +- Moose Development/Moose/Ops/Commander.lua | 2 +- Moose Development/Moose/Ops/OpsZone.lua | 383 ++++++++++++++++++---- Moose Development/Moose/Ops/Target.lua | 36 +- 6 files changed, 688 insertions(+), 137 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 84cfec085..c270afb98 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1390,24 +1390,37 @@ function AUFTRAG:NewALERT5(MissionType) end ---- Create a mission to attack a group. Mission type is automatically chosen from the group category. +--- Create a mission to attack a TARGET object. -- @param #AUFTRAG self -- @param Ops.Target#TARGET Target The target. +-- @param #string MissionType The mission type. -- @return #AUFTRAG self -function AUFTRAG:NewTargetAir(Target) +function AUFTRAG:NewFromTarget(Target, MissionType) local mission=nil --#AUFTRAG - self.engageTarget=Target - - local target=self.engageTarget:GetObject() - - local mission=self:NewAUTO(target) - - if mission then - mission:SetPriority(10, true) + if MissionType==AUFTRAG.Type.ANTISHIP then + mission=self:NewANTISHIP(Target, Altitude) + elseif MissionType==AUFTRAG.Type.ARTY then + mission=self:NewARTY(Target, Nshots, Radius) + elseif MissionType==AUFTRAG.Type.BAI then + mission=self:NewBAI(Target, Altitude) + elseif MissionType==AUFTRAG.Type.BOMBCARPET then + mission=self:NewBOMBCARPET(Target, Altitude, CarpetLength) + elseif MissionType==AUFTRAG.Type.BOMBING then + mission=self:NewBOMBING(Target, Altitude) + elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then + mission=self:NewBOMBRUNWAY(Target, Altitude) + elseif MissionType==AUFTRAG.Type.INTERCEPT then + mission=self:NewINTERCEPT(Target) + elseif MissionType==AUFTRAG.Type.SEAD then + mission=self:NewSEAD(Target, Altitude) + elseif MissionType==AUFTRAG.Type.STRIKE then + mission=self:NewSTRIKE(Target, Altitude) + else + return nil end - + return mission end diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 008e71b85..bbe3941bc 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -17,6 +17,7 @@ -- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field #table targetqueue Target queue. +-- @field #table zonequeue Strategic zone queue. -- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory. -- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected. -- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged. @@ -30,9 +31,7 @@ -- -- # The CHIEF Concept -- --- The Chief of staff gathers intel and assigns missions (AUFTRAG) the airforce (WINGCOMMANDER), army (GENERAL) or navy (ADMIRAL). --- --- **Note** that currently only assignments to airborne forces (WINGCOMMANDER) are implemented. +-- The Chief of staff gathers INTEL and assigns missions (AUFTRAG) the airforce, army and/or navy. -- -- -- @field #CHIEF @@ -41,6 +40,7 @@ CHIEF = { verbose = 0, lid = nil, targetqueue = {}, + zonequeue = {}, borderzoneset = nil, yellowzoneset = nil, engagezoneset = nil, @@ -70,6 +70,18 @@ CHIEF.Strategy = { TOTALWAR="Total War" } +--- Strategy. +-- @type CHIEF.MissionTypePerformance +-- @field #string MissionType Mission Type. +-- @field #number Performance Performance: a number between 0 and 100, where 100 is best performance. + + +--- Strategy. +-- @type CHIEF.MissionTypeMapping +-- @field #string Attribute Generalized attibute +-- @field #table MissionTypes + + --- CHIEF class version. -- @field #string version CHIEF.version="0.0.1" @@ -97,15 +109,17 @@ CHIEF.version="0.0.1" -- @param #CHIEF self -- @param Core.Set#SET_GROUP AgentSet Set of agents (groups) providing intel. Default is an empty set. -- @param #number Coalition Coalition side, e.g. `coaliton.side.BLUE`. Can also be passed as a string "red", "blue" or "neutral". +-- @param #string Alias An *optional* alias how this object is called in the logs etc. -- @return #CHIEF self -function CHIEF:New(AgentSet, Coalition) +function CHIEF:New(AgentSet, Coalition, Alias) + + -- Set alias. + Alias=Alias or "CHIEF" -- Inherit everything from INTEL class. - local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition)) --#CHIEF - - -- Set some string id for output to DCS.log file. - --self.lid=string.format("CHIEF | ") + local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition, Alias)) --#CHIEF + -- Define zones. self:SetBorderZones() self:SetYellowZones() @@ -114,21 +128,25 @@ function CHIEF:New(AgentSet, Coalition) -- Create a new COMMANDER. self.commander=COMMANDER:New() + -- Init DEFCON. self.Defcon=CHIEF.DEFCON.GREEN -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("*", "AssignMissionAirforce", "*") -- Assign mission to a COMMANDER but request only AIR assets. - self:AddTransition("*", "AssignMissionNavy", "*") -- Assign mission to a COMMANDER but request only NAVAL assets. - self:AddTransition("*", "AssignMissionArmy", "*") -- Assign mission to a COMMANDER but request only GROUND assets. + -- From State --> Event --> To State + self:AddTransition("*", "MissionAssignToAny", "*") -- Assign mission to a COMMANDER. - self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + 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("*", "Defcon", "*") -- Change defence condition. + self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + self:AddTransition("*", "TransportCancel", "*") -- Cancel transport. - self:AddTransition("*", "Stategy", "*") -- Change strategy condition. + self:AddTransition("*", "Defcon", "*") -- Change defence condition. - self:AddTransition("*", "DeclareWar", "*") -- Declare War. + self:AddTransition("*", "Stategy", "*") -- Change strategy condition. + + self:AddTransition("*", "DeclareWar", "*") -- Declare War. ------------------------ --- Pseudo Functions --- @@ -163,13 +181,33 @@ function CHIEF:New(AgentSet, Coalition) -- @param #number delay Delay in seconds. + --- Triggers the FSM event "MissionAssignToAny". + -- @function [parent=#CHIEF] MissionAssignToAny + -- @param #CHIEF self + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "MissionAssignToAny" after a delay. + -- @function [parent=#CHIEF] __MissionAssignToAny + -- @param #CHIEF self + -- @param #number delay Delay in seconds. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "MissionAssignToAny" event. + -- @function [parent=#CHIEF] OnAfterMissionAssignToAny + -- @param #CHIEF self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- Triggers the FSM event "MissionCancel". -- @function [parent=#CHIEF] MissionCancel -- @param #CHIEF self -- @param Ops.Auftrag#AUFTRAG Mission The mission. --- Triggers the FSM event "MissionCancel" after a delay. - -- @function [parent=#CHIEF] MissionCancel + -- @function [parent=#CHIEF] __MissionCancel -- @param #CHIEF self -- @param #number delay Delay in seconds. -- @param Ops.Auftrag#AUFTRAG Mission The mission. @@ -189,7 +227,7 @@ function CHIEF:New(AgentSet, Coalition) -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. --- Triggers the FSM event "TransportCancel" after a delay. - -- @function [parent=#CHIEF] TransportCancel + -- @function [parent=#CHIEF] __TransportCancel -- @param #CHIEF self -- @param #number delay Delay in seconds. -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. @@ -359,6 +397,19 @@ function CHIEF:AddTarget(Target) return self end +--- Add strategically important zone. +-- @param #CHIEF self +-- @param Core.Zone#ZONE_RADIUS Zone Strategic zone. +-- @return #CHIEF self +function CHIEF:AddStrateticZone(Zone) + + local opszone=OPSZONE:New(Zone, CoalitionOwner) + + table.insert(self.zonequeue, opszone) + + return self +end + --- Set border zone set. -- @param #CHIEF self @@ -538,7 +589,7 @@ function CHIEF:onafterStatus(From, Event, To) if self.verbose>=1 then local Nassets=self.commander:CountAssets() - local Ncontacts=#self.contacts + local Ncontacts=#self.Contacts local Nmissions=#self.commander.missionqueue local Ntargets=#self.targetqueue @@ -589,7 +640,7 @@ function CHIEF:onafterStatus(From, Event, To) -- Mission queue. if self.verbose>=4 and #self.commander.missionqueue>0 then local text="Mission queue:" - for i,_mission in pairs(self.missionqueue) do + for i,_mission in pairs(self.commander.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG local target=mission:GetTargetName() or "unknown" @@ -629,13 +680,13 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after "AssignMissionAssignAirforce" event. +--- On after "MissionAssignToAny" event. -- @param #CHIEF self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.Auftrag#AUFTRAG Mission The mission. -function CHIEF:onafterAssignMissionAirforce(From, Event, To, Mission) +function CHIEF:onafterMissionAssignToAny(From, Event, To, Mission) if self.commander then self:I(self.lid..string.format("Assigning mission %s (%s) to COMMANDER", Mission.name, Mission.type)) @@ -647,13 +698,31 @@ function CHIEF:onafterAssignMissionAirforce(From, Event, To, Mission) end ---- On after "AssignMissionAssignArmy" event. +--- On after "MissionAssignToAirforce" event. -- @param #CHIEF self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.Auftrag#AUFTRAG Mission The mission. -function CHIEF:onafterAssignMissionArmy(From, Event, To, Mission) +function CHIEF:onafterMissionAssignToAirforce(From, Event, To, Mission) + + if self.commander then + self:I(self.lid..string.format("Assigning mission %s (%s) to COMMANDER", Mission.name, Mission.type)) + --TODO: Request only air assets. + self.commander:AddMission(Mission) + else + self:E(self.lid..string.format("Mission cannot be assigned as no COMMANDER is defined!")) + end + +end + +--- On after "MissionAssignToArmy" event. +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function CHIEF:onafterMissionAssignToArmy(From, Event, To, Mission) if self.commander then self:I(self.lid..string.format("Assigning mission %s (%s) to COMMANDER", Mission.name, Mission.type)) @@ -819,10 +888,33 @@ function CHIEF:CheckTargetQueue() -- Valid target? if valid then - --TODO: Create a good mission, which can be passed on to the COMMANDER. - - -- Create mission. - local mission=AUFTRAG:NewTargetAir(target) + env.info("FF got valid target "..target:GetName()) + + -- Get mission performances for the given target. + local MissionPerformances=self:_GetMissionPerformanceFromTarget(target) + + -- Mission. + local mission=nil --Ops.Auftrag#AUFTRAG + + if #MissionPerformances>0 then + + env.info(string.format("FF found mission performance N=%d", #MissionPerformances)) + + -- Mission Type. + local MissionType=nil + + for _,_mp in pairs(MissionPerformances) do + local mp=_mp --#CHIEF.MissionTypePerformance + local n=self.commander:CountAssets(true, {mp.MissionType}) + env.info(string.format("FF Found N=%d assets in stock for mission type %s [Performance=%d]", n, mp.MissionType, mp.Performance)) + if n>0 then + mission=AUFTRAG:NewFromTarget(target, mp.MissionType) + break + end + end + + + end if mission then @@ -847,6 +939,7 @@ function CHIEF:CheckTargetQueue() end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Resources ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -916,29 +1009,209 @@ end --- Check resources. -- @param #CHIEF self +-- @param Ops.Target#TARGET Target Target. -- @return #table -function CHIEF:CheckResources() +function CHIEF:CheckResources(Target) - -- TODO: look at lower classes to do this! it's all there... - - local capabilities={} - - for _,MissionType in pairs(AUFTRAG.Type) do - capabilities[MissionType]=0 + local objective=Target:GetObjective() - for _,_airwing in pairs(self.airwings) do - local airwing=_airwing --Ops.AirWing#AIRWING - - -- Get Number of assets that can do this type of missions. - local _,assets=airwing:CanMission(MissionType) - - -- Add up airwing resources. - capabilities[MissionType]=capabilities[MissionType]+#assets - end + local missionperformances={} + + for _,missionperformance in pairs(missionperformances) do + local mp=missionperformance --#CHIEF.MissionTypePerformance + end - return capabilities + +end + +--- Create a mission performance table. +-- @param #CHIEF self +-- @param #string MissionType Mission type. +-- @param #number Performance Performance. +-- @return #CHIEF.MissionPerformance Mission performance. +function CHIEF:_CreateMissionPerformance(MissionType, Performance) + local mp={} --#CHIEF.MissionTypePerformance + mp.MissionType=MissionType + mp.Performance=Performance + return mp +end + +--- Create a mission to attack a group. Mission type is automatically chosen from the group category. +-- @param #CHIEF self +-- @param Ops.Target#TARGET Target +-- @return #table Mission performances of type #CHIEF.MissionPerformance +function CHIEF:_GetMissionPerformanceFromTarget(Target) + + local group=nil --Wrapper.Group#GROUP + local airbase=nil --Wrapper.Airbase#AIRBASE + local scenery=nil --Wrapper.Scenery#SCENERY + local coordinate=nil --Core.Point#COORDINATE + + -- Get target objective. + local target=Target:GetObject() + + if target:IsInstanceOf("GROUP") then + group=target --Target is already a group. + elseif target:IsInstanceOf("UNIT") then + group=target:GetGroup() + elseif target:IsInstanceOf("AIRBASE") then + airbase=target + elseif target:IsInstanceOf("SCENERY") then + scenery=target + end + + local TargetCategory=Target:GetCategory() + + local missionperf={} --#CHIEF.MissionTypePerformance + + if group then + + local category=group:GetCategory() + local attribute=group:GetAttribute() + + if category==Group.Category.AIRPLANE or category==Group.Category.HELICOPTER then + + --- + -- A2A: Intercept + --- + + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.INTERCEPT, 100)) + + elseif category==Group.Category.GROUND or category==Group.Category.TRAIN then + + --- + -- GROUND + --- + + if attribute==GROUP.Attribute.GROUND_SAM then + + -- SEAD/DEAD + + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.SEAD, 100)) + + elseif attribute==GROUP.Attribute.GROUND_AAA then + + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100)) + + elseif attribute==GROUP.Attribute.GROUND_ARTILLERY then + + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100)) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING, 70)) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30)) + + elseif attribute==GROUP.Attribute.GROUND_INFANTRY then + + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100)) + + else + + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100)) + + end + + + elseif category==Group.Category.SHIP then + + --- + -- NAVAL + --- + + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ANTISHIP, 100)) + + else + self:E(self.lid.."ERROR: Unknown Group category!") + end + + elseif airbase then + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBRUNWAY, 100)) + elseif scenery then + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.STRIKE, 100)) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING, 70)) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30)) + elseif coordinate then + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING, 100)) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30)) + end + + return missionperf +end + +--- Check if group is inside our border. +-- @param #CHIEF self +-- @param #string Attribute Group attibute. +-- @return #table Mission types +function CHIEF:_GetMissionTypeForGroupAttribute(Attribute) + + local missiontypes={} + + if Attribute==GROUP.Attribute.AIR_ATTACKHELO then + + local mt={} --#CHIEF.MissionTypePerformance + mt.MissionType=AUFTRAG.Type.INTERCEPT + mt.Performance=100 + table.insert(missiontypes, mt) + + elseif Attribute==GROUP.Attribute.GROUND_AAA then + + local mt={} --#CHIEF.MissionTypePerformance + mt.MissionType=AUFTRAG.Type.BAI + mt.Performance=100 + table.insert(missiontypes, mt) + + local mt={} --#CHIEF.MissionTypePerformance + mt.MissionType=AUFTRAG.Type.BOMBING + mt.Performance=70 + table.insert(missiontypes, mt) + + local mt={} --#CHIEF.MissionTypePerformance + mt.MissionType=AUFTRAG.Type.BOMBCARPET + mt.Performance=70 + table.insert(missiontypes, mt) + + local mt={} --#CHIEF.MissionTypePerformance + mt.MissionType=AUFTRAG.Type.ARTY + mt.Performance=30 + table.insert(missiontypes, mt) + + elseif Attribute==GROUP.Attribute.GROUND_SAM then + + local mt={} --#CHIEF.MissionTypePerformance + mt.MissionType=AUFTRAG.Type.SEAD + mt.Performance=100 + table.insert(missiontypes, mt) + + local mt={} --#CHIEF.MissionTypePerformance + mt.MissionType=AUFTRAG.Type.BAI + mt.Performance=100 + table.insert(missiontypes, mt) + + local mt={} --#CHIEF.MissionTypePerformance + mt.MissionType=AUFTRAG.Type.ARTY + mt.Performance=50 + table.insert(missiontypes, mt) + + elseif Attribute==GROUP.Attribute.GROUND_EWR then + + local mt={} --#CHIEF.MissionTypePerformance + mt.MissionType=AUFTRAG.Type.SEAD + mt.Performance=100 + table.insert(missiontypes, mt) + + local mt={} --#CHIEF.MissionTypePerformance + mt.MissionType=AUFTRAG.Type.BAI + mt.Performance=100 + table.insert(missiontypes, mt) + + local mt={} --#CHIEF.MissionTypePerformance + mt.MissionType=AUFTRAG.Type.ARTY + mt.Performance=50 + table.insert(missiontypes, mt) + + + end + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 659736d62..62d543db7 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -294,7 +294,7 @@ function COHORT:AddMissionCapability(MissionTypes, Performance) capability.MissionType=missiontype capability.Performance=Performance or 50 table.insert(self.missiontypes, capability) - + env.info("FF adding mission capability "..tostring(capability.MissionType)) end end @@ -783,11 +783,11 @@ function COHORT:CountAssets(InStock, MissionTypes, Attributes) if MissionTypes==nil or self:CheckMissionCapability(MissionTypes, self.missiontypes) then if Attributes==nil or self:CheckAttribute(Attributes) then if asset.spawned then - if InStock==true or InStock==nil then + if InStock==false or InStock==nil then N=N+1 --Spawned but we also count the spawned ones. end else - if InStock==false or InStock==nil then + if InStock==true or InStock==nil then N=N+1 --This is in stock. end end diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index a739f045c..7433f3413 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -549,7 +549,7 @@ function COMMANDER:onafterMissionCancel(From, Event, To, Mission) end ---- On after "MissionAssign" event. Mission is added to a LEGION mission queue. +--- On after "TransportAssign" event. Transport is added to a LEGION mission queue. -- @param #COMMANDER self -- @param #string From From state. -- @param #string Event Event. diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index 585a4da7b..d4ca460b1 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -18,9 +18,18 @@ -- @field #number verbose Verbosity of output. -- @field Core.Zone#ZONE zone The zone. -- @field #string zoneName Name of the zone. +-- @field #number zoneRadius Radius of the zone in meters. -- @field #number ownerCurrent Coalition of the current owner of the zone. -- @field #number ownerPrevious Coalition of the previous owner of the zone. -- @field Core.Timer#TIMER timerStatus Timer for calling the status update. +-- @field #number Nred Number of red units in the zone. +-- @field #number Nblu Number of blue units in the zone. +-- @field #number Nnut Number of neutral units in the zone. +-- @field #table ObjectCategories Object categories for the scan. +-- @field #table UnitCategories Unit categories for the scan. +-- @field #number Tattacked Abs. mission time stamp when an attack was started. +-- @field #number dTCapture Time interval in seconds until a zone is captured. +-- @field #boolean neutralCanCapture Neutral units can capture. Default `false`. -- @extends Core.Fsm#FSM --- Be surprised! @@ -31,24 +40,32 @@ -- -- An OPSZONE is a strategically important area. -- +-- **Restrictions** +-- +-- * Since we are using a DCS routine that scans a zone for units or other objects present in the zone and this DCS routine is limited to cicular zones, only those can be used. -- -- @field #OPSZONE OPSZONE = { ClassName = "OPSZONE", - verbose = 3, + verbose = 0, + Nred = 0, + Nblu = 0, + Nnut = 0, } --- OPSZONE class version. -- @field #string version -OPSZONE.version="0.0.1" +OPSZONE.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Can neutrals capture? --- TODO: Can statics capture or hold a zone? +-- TODO: Pause/unpause evaluations. +-- TODO: Capture time, i.e. time how long a single coalition has to be inside the zone to capture it. +-- TODO: Can neutrals capture? No, since they are _neutral_! +-- TODO: Can statics capture or hold a zone? No, unless explicitly requested by mission designer. -- TODO: Differentiate between ground attack and boming by air or arty. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -78,34 +95,139 @@ function OPSZONE:New(Zone, CoalitionOwner) self:E("ERROR: OPSZONE must be a SPHERICAL zone due to DCS restrictions!") return nil end - - self.zone=Zone - self.zoneName=Zone:GetName() - self.zoneRadius=Zone:GetRadius() - - self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL - self.ownerPrevious=CoalitionOwner or coalition.side.NEUTRAL -- Set some string id for output to DCS.log file. self.lid=string.format("OPSZONE %s | ", Zone:GetName()) - -- FMS start state is PLANNED. + -- Set some values. + self.zone=Zone + self.zoneName=Zone:GetName() + self.zoneRadius=Zone:GetRadius() + + -- Current and previous owners. + self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL + self.ownerPrevious=CoalitionOwner or coalition.side.NEUTRAL + + -- Set object categories. + self:SetObjectCategories() + self:SetUnitCategories() + + -- Status timer. + self.timerStatus=TIMER:New(OPSZONE.Status, self) + + + -- FMS start state is EMPTY. self:SetStartState("Empty") - -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("*", "Start", "*") -- Start FSM. - self:AddTransition("*", "Stop", "*") -- Start FSM. + self:AddTransition("*", "Stop", "*") -- Stop FSM. - self:AddTransition("*", "Captured", "Guarded") -- Start FSM. - self:AddTransition("*", "Empty", "Empty") -- Start FSM. + self:AddTransition("*", "Captured", "Guarded") -- Zone was captured. + self:AddTransition("*", "Empty", "Empty") -- No red or blue units inside the zone. self:AddTransition("*", "Attacked", "Attacked") -- A guarded zone is under attack. self:AddTransition("*", "Defeated", "Guarded") -- The owning coalition defeated an attack. + ------------------------ + --- Pseudo Functions --- + ------------------------ - self.timerStatus=TIMER:New(OPSZONE.Status, self) + --- Triggers the FSM event "Start". + -- @function [parent=#OPSZONE] Start + -- @param #OPSZONE self + + --- Triggers the FSM event "Start" after a delay. + -- @function [parent=#OPSZONE] __Start + -- @param #OPSZONE self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Stop". + -- @param #OPSZONE self + + --- Triggers the FSM event "Stop" after a delay. + -- @function [parent=#OPSZONE] __Stop + -- @param #OPSZONE self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Captured". + -- @function [parent=#OPSZONE] Captured + -- @param #OPSZONE self + -- @param #number Coalition Coalition side that captured the zone. + + --- Triggers the FSM event "Captured" after a delay. + -- @function [parent=#OPSZONE] __Captured + -- @param #OPSZONE self + -- @param #number delay Delay in seconds. + -- @param #number Coalition Coalition side that captured the zone. + + --- On after "Captured" event. + -- @function [parent=#OPSZONE] OnAfterCaptured + -- @param #OPSZONE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number Coalition Coalition side that captured the zone. + + + --- Triggers the FSM event "Empty". + -- @function [parent=#OPSZONE] Empty + -- @param #OPSZONE self + + --- Triggers the FSM event "Empty" after a delay. + -- @function [parent=#OPSZONE] __Empty + -- @param #OPSZONE self + -- @param #number delay Delay in seconds. + + --- On after "Empty" event. + -- @function [parent=#OPSZONE] OnAfterEmpty + -- @param #OPSZONE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Attacked". + -- @function [parent=#OPSZONE] Attacked + -- @param #OPSZONE self + -- @param #number AttackerCoalition Coalition side that is attacking the zone. + + --- Triggers the FSM event "Attacked" after a delay. + -- @function [parent=#OPSZONE] __Attacked + -- @param #OPSZONE self + -- @param #number delay Delay in seconds. + -- @param #number AttackerCoalition Coalition side that is attacking the zone. + + --- On after "Attacked" event. + -- @function [parent=#OPSZONE] OnAfterAttacked + -- @param #OPSZONE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number AttackerCoalition Coalition side that is attacking the zone. + + + --- Triggers the FSM event "Defeated". + -- @function [parent=#OPSZONE] Defeated + -- @param #OPSZONE self + -- @param #number DefeatedCoalition Coalition side that was defeated. + + --- Triggers the FSM event "Defeated" after a delay. + -- @function [parent=#OPSZONE] __Defeated + -- @param #OPSZONE self + -- @param #number delay Delay in seconds. + -- @param #number DefeatedCoalition Coalition side that was defeated. + + --- On after "Defeated" event. + -- @function [parent=#OPSZONE] OnAfterDefeated + -- @param #OPSZONE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number DefeatedCoalition Coalition side that was defeated. return self end @@ -114,6 +236,58 @@ end -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Set verbosity level. +-- @param #OPSZONE self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #OPSZONE self +function OPSZONE:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + +--- Set categories of objects that can capture or hold the zone. +-- @param #OPSZONE self +-- @param #table Categories Object categories. Default is `{Object.Category.UNIT, Object.Category.STATIC}`, i.e. UNITs and STATICs. +-- @return #OPSZONE self +function OPSZONE:SetObjectCategories(Categories) + + -- Ensure table if something was passed. + if Categories and type(Categories)~="table" then + Categories={Categories} + end + + -- Set categories. + self.ObjectCategories=Categories or {Object.Category.UNIT, Object.Category.STATIC} + + return self +end + +--- Set categories of units that can capture or hold the zone. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). +-- @param #OPSZONE self +-- @param #table Categories Table of unit categories. Default `{Unit.Category.GROUND_UNIT}`. +-- @return #OPSZONE +function OPSZONE:SetUnitCategories(Categories) + + -- Ensure table. + if Categories and type(Categories)~="table" then + Categories={Categories} + end + + -- Set categories. + self.UnitCategories=Categories or {Unit.Category.GROUND_UNIT} + + return self +end + +--- Set whether *neutral* units can capture the zone. +-- @param #OPSZONE self +-- @param #boolean CanCapture If `true`, neutral units can. +-- @return #OPSZONE self +function OPSZONE:SetNeutralCanCapture(CanCapture) + self.neutralCanCapture=CanCapture + return self +end + --- Get current owner of the zone. -- @param #OPSZONE self -- @return #number Owner coalition. @@ -128,6 +302,19 @@ function OPSZONE:GetPreviousOwner() return self.ownerPrevious end +--- Get duration of the current attack. +-- @param #OPSZONE self +-- @return #number Duration in seconds since when the last attack began. Is `nil` if the zone is not under attack currently. +function OPSZONE:GetAttackDuration() + if self:IsAttacked() and self.Tattacked then + + local dT=timer.getAbsTime()-self.Tattacked + return dT + end + + return nil +end + --- Check if the red coalition is currently owning the zone. -- @param #OPSZONE self @@ -156,7 +343,7 @@ end --- Check if zone is guarded. -- @param #OPSZONE self -- @return #boolean If `true`, zone is guarded. -function OPSZONE:IsEmpty() +function OPSZONE:IsGuarded() local is=self:is("Guarded") return is end @@ -186,7 +373,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- FSM Functions +-- Start/Stop and Status Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start OPSZONE FSM. @@ -199,6 +386,9 @@ function OPSZONE:onafterStart(From, Event, To) -- Info. self:I(self.lid..string.format("Starting OPSZONE v%s", OPSZONE.version)) + -- Reinit the timer. + self.timerStatus=self.timerStatus or TIMER:New(OPSZONE.Status, self) + -- Status update. self.timerStatus:Start(1, 60) @@ -210,10 +400,15 @@ function OPSZONE:Status() -- Current FSM state. local fsmstate=self:GetState() + + -- Get contested. + local contested=tostring(self:IsContested()) -- Info message. - local text=string.format("State %s: Owner %d (previous %d), contested=%s, Nunits: red=%d, blue=%d, neutral=%d", fsmstate, self.ownerCurrent, self.ownerPrevious, tostring(self:IsContested()), 0, 0, 0) - self:I(self.lid..text) + if self.verbose>=1 then + local text=string.format("State %s: Owner %d (previous %d), contested=%s, Nunits: red=%d, blue=%d, neutral=%d", fsmstate, self.ownerCurrent, self.ownerPrevious, contested, self.Nred, self.Nblu, self.Nnut) + self:I(self.lid..text) + end -- Scanning zone. self:Scan() @@ -233,8 +428,9 @@ end function OPSZONE:onafterCaptured(From, Event, To, NewOwnerCoalition) -- Debug info. - self:I(self.lid..string.format("Zone captured by %d coalition", NewOwnerCoalition)) + self:T(self.lid..string.format("Zone captured by coalition=%d", NewOwnerCoalition)) + -- Set owners. self.ownerPrevious=self.ownerCurrent self.ownerCurrent=NewOwnerCoalition @@ -248,7 +444,7 @@ end function OPSZONE:onafterEmpty(From, Event, To) -- Debug info. - self:I(self.lid..string.format("Zone is empty now")) + self:T(self.lid..string.format("Zone is empty now")) end @@ -261,10 +457,7 @@ end function OPSZONE:onafterAttacked(From, Event, To, AttackerCoalition) -- Debug info. - self:I(self.lid..string.format("Zone is being attacked by coalition %s!", tostring(AttackerCoalition))) - - -- Time stam when the attack started. - self.Tattacked=timer.getAbsTime() + self:T(self.lid..string.format("Zone is being attacked by coalition=%s!", tostring(AttackerCoalition))) end @@ -277,7 +470,7 @@ end function OPSZONE:onafterEmpty(From, Event, To) -- Debug info. - self:I(self.lid..string.format("Zone is empty now")) + self:T(self.lid..string.format("Zone is empty now")) end @@ -286,10 +479,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function OPSZONE:onafterDefeated(From, Event, To) +-- @param #number DefeatedCoalition Coalition side that was defeated. +function OPSZONE:onafterDefeated(From, Event, To, DefeatedCoalition) -- Debug info. - self:I(self.lid..string.format("Attack on zone has been defeated")) + self:T(self.lid..string.format("Defeated attack on zone by coalition=%d", DefeatedCoalition)) -- Not attacked any more. self.Tattacked=nil @@ -304,8 +498,9 @@ end function OPSZONE:onenterGuarded(From, Event, To) -- Debug info. - self:I(self.lid..string.format("Zone is guarded")) + self:T(self.lid..string.format("Zone is guarded")) + -- Not attacked any more. self.Tattacked=nil end @@ -318,9 +513,10 @@ end function OPSZONE:onenterAttacked(From, Event, To) -- Debug info. - self:I(self.lid..string.format("Zone is Attacked")) + self:T(self.lid..string.format("Zone is Attacked")) - self.Tattacked=nil + -- Time stamp when the attack started. + self.Tattacked=timer.getAbsTime() end @@ -334,14 +530,15 @@ end function OPSZONE:Scan() -- Debug info. - local text=string.format("Scanning zone %s R=%.1f m", self.zone:GetName(), self.zone:GetRadius()) - self:I(self.lid..text) + if self.verbose>=3 then + local text=string.format("Scanning zone %s R=%.1f m", self.zoneName, self.zoneRadius) + self:I(self.lid..text) + end -- Search. - local SphereSearch={id=world.VolumeType.SPHERE, params={point=self.zone:GetVec3(), radius=self.zone:GetRadius(),}} + local SphereSearch={id=world.VolumeType.SPHERE, params={point=self.zone:GetVec3(), radius=self.zoneRadius}} - local ObjectCategories={Object.Category.UNIT, Object.Category.STATIC} - + -- Init number of red, blue and neutral units. local Nred=0 local Nblu=0 local Nnut=0 @@ -362,12 +559,67 @@ function OPSZONE:Scan() -- UNIT --- + -- This is a DCS unit object. local DCSUnit=ZoneObject --DCS#Unit - --TODO: only ground units! + --- Function to check if unit category is included. + local function Included() + + if not self.UnitCategories then + -- Any unit is included. + return true + else + -- Check if found object is in specified categories. + local CategoryDCSUnit = ZoneObject:getDesc().category + + for _,UnitCategory in pairs(self.UnitCategories) do + if UnitCategory==CategoryDCSUnit then + return true + end + end + + end + + return false + end - local Coalition=DCSUnit:getCoalition() + + if Included() then + -- Get Coalition. + local Coalition=DCSUnit:getCoalition() + + -- Increase counter. + if Coalition==coalition.side.RED then + Nred=Nred+1 + elseif Coalition==coalition.side.BLUE then + Nblu=Nblu+1 + elseif Coalition==coalition.side.NEUTRAL then + Nnut=Nnut+1 + end + + -- Debug info. + if self.verbose>=4 then + self:I(self.lid..string.format("Found unit %s (coalition=%d)", DCSUnit:getName(), Coalition)) + end + end + + elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist() then + + --- + -- STATIC + --- + + -- This is a DCS static object. + local DCSStatic=ZoneObject --DCS#Static + + -- Get coalition. + local Coalition=DCSStatic:getCoalition() + + -- CAREFUL! Downed pilots break routine here without any error thrown. + --local unit=STATIC:Find(DCSStatic) + + -- Increase counter. if Coalition==coalition.side.RED then Nred=Nred+1 elseif Coalition==coalition.side.BLUE then @@ -376,23 +628,10 @@ function OPSZONE:Scan() Nnut=Nnut+1 end - local unit=UNIT:Find(DCSUnit) - - env.info(string.format("FF found unit %s", unit:GetName())) - - - elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist() then - - --- - -- STATIC - --- - - local DCSStatic=ZoneObject --DCS#Static - - -- CAREFUL! Downed pilots break routine here without any error thrown. - local unit=STATIC:Find(DCSStatic) - - --env.info(string.format("FF found static %s", unit:GetName())) + -- Debug info + if self.verbose>=4 then + self:I(self.lid..string.format("Found static %s (coalition=%d)", DCSStatic:getName(), Coalition)) + end elseif ObjectCategory==Object.Category.SCENERY then @@ -403,9 +642,8 @@ function OPSZONE:Scan() local SceneryType = ZoneObject:getTypeName() local SceneryName = ZoneObject:getName() - local Scenery=SCENERY:Register(SceneryName, ZoneObject) - - env.info(string.format("FF found scenery type=%s, name=%s", SceneryType, SceneryName)) + -- Debug info. + self:T2(self.lid..string.format("Found scenery type=%s, name=%s", SceneryType, SceneryName)) end end @@ -414,11 +652,18 @@ function OPSZONE:Scan() end -- Search objects. - world.searchObjects(ObjectCategories, SphereSearch, EvaluateZone) + world.searchObjects(self.ObjectCategories, SphereSearch, EvaluateZone) -- Debug info. - local text=string.format("Scan result Nred=%d, Nblue=%d, Nneutrl=%d", Nred, Nblu, Nnut) - self:I(self.lid..text) + if self.verbose>=3 then + local text=string.format("Scan result Nred=%d, Nblue=%d, Nneutral=%d", Nred, Nblu, Nnut) + self:I(self.lid..text) + end + + -- Set values. + self.Nred=Nred + self.Nblu=Nblu + self.Nnut=Nnut if self:IsRed() then @@ -438,7 +683,9 @@ function OPSZONE:Scan() self:Captured(coalition.side.NEUTRAL) else -- Red zone is now empty (but will remain red). - self:Empty() + if not self:IsEmpty() then + self:Empty() + end end else @@ -486,7 +733,9 @@ function OPSZONE:Scan() self:Captured(coalition.side.NEUTRAL) else -- Blue zone is empty now. - self:Empty() + if not self:IsEmpty() then + self:Empty() + end end else @@ -530,7 +779,7 @@ function OPSZONE:Scan() -- No neutral units in neutral zone any more. if Nred>0 and Nblu>0 then - env.info("FF neutrals left neutral zone and red and blue are present! What to do?") + env.info(self.lid.."FF neutrals left neutral zone and red and blue are present! What to do?") -- TODO Contested! self:Attacked() self.isContested=true @@ -550,7 +799,7 @@ function OPSZONE:Scan() --end else - env.info("FF error") + self:E(self.lid.."ERROR!") end return self diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 57993a85c..35aa98cda 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -31,15 +31,14 @@ -- @field #table elements Table of target elements/units. -- @field #table casualties Table of dead element names. -- @field #number prio Priority. --- @field #number importance Importance +-- @field #number importance Importance. +-- @field #boolean isDestroyed If true, target objects were destroyed. -- @extends Core.Fsm#FSM --- **It is far more important to be able to hit the target than it is to haggle over who makes a weapon or who pulls a trigger** -- Dwight D. Eisenhower -- -- === -- --- ![Banner Image](..\Presentations\OPS\Target\_Main.pngs) --- -- # The TARGET Concept -- -- Define a target of your mission and monitor its status. Events are triggered when the target is damaged or destroyed. @@ -130,13 +129,13 @@ _TARGETID=0 --- TARGET class version. -- @field #string version -TARGET.version="0.5.0" +TARGET.version="0.5.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: A lot. +-- TODO: Add pseudo functions. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -189,7 +188,7 @@ function TARGET:New(TargetObject) self:AddTransition("*", "Damaged", "*") -- Target was damaged. self:AddTransition("*", "Destroyed", "Dead") -- Target was completely destroyed. - self:AddTransition("*", "Dead", "Dead") -- Target was completely destroyed. + self:AddTransition("*", "Dead", "Dead") -- Target is dead. Could be destroyed or despawned. ------------------------ --- Pseudo Functions --- @@ -222,7 +221,6 @@ function TARGET:New(TargetObject) -- @param #number delay Delay in seconds. - -- Start. self:__Start(-1) @@ -300,7 +298,7 @@ end --- Set importance of the target. -- @param #TARGET self --- @param #number Priority Priority of the target. Default `nil`. +-- @param #number Importance Importance of the target. Default `nil`. -- @return #TARGET self function TARGET:SetImportance(Importance) self.importance=Importance @@ -311,14 +309,24 @@ end -- @param #TARGET self -- @return #boolean If true, target is alive. function TARGET:IsAlive() - return self:Is("Alive") + local is=self:Is("Alive") + return is end +--- Check if TARGET is destroyed. +-- @param #TARGET self +-- @return #boolean If true, target is destroyed. +function TARGET:IsDestroyed() + return self.isDestroyed +end + + --- Check if TARGET is dead. -- @param #TARGET self -- @return #boolean If true, target is dead. function TARGET:IsDead() - return self:Is("Dead") + local is=self:Is("Dead") + return is end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -469,6 +477,8 @@ function TARGET:onafterObjectDead(From, Event, To, Target) if self.Ndestroyed==self.Ntargets0 then + self.isDestroyed=true + self:Destroyed() else @@ -1041,6 +1051,12 @@ function TARGET:GetCoordinate() return nil end +--- Get category. +-- @param #TARGET self +-- @return #string Target category. See `TARGET.Category.X`, where `X=AIRCRAFT, GROUND`. +function TARGET:GetCategory() + return self.category +end --- Get target category. -- @param #TARGET self From 589ebd5bcabb402a815b7e6943ca971c709ee7b0 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 16 Sep 2021 11:02:49 +0200 Subject: [PATCH 103/141] CHIEF - Improved asset selection --- Moose Development/Moose/Core/Timer.lua | 15 +- Moose Development/Moose/Ops/Auftrag.lua | 1 - Moose Development/Moose/Ops/Chief.lua | 560 +++++++++++++++---- Moose Development/Moose/Ops/Commander.lua | 33 +- Moose Development/Moose/Ops/FlightGroup.lua | 11 +- Moose Development/Moose/Ops/Intelligence.lua | 8 +- Moose Development/Moose/Ops/Legion.lua | 13 +- Moose Development/Moose/Ops/OpsZone.lua | 1 + Moose Development/Moose/Ops/Target.lua | 65 ++- 9 files changed, 557 insertions(+), 150 deletions(-) diff --git a/Moose Development/Moose/Core/Timer.lua b/Moose Development/Moose/Core/Timer.lua index 258996d68..6bda7b66b 100644 --- a/Moose Development/Moose/Core/Timer.lua +++ b/Moose Development/Moose/Core/Timer.lua @@ -34,8 +34,6 @@ -- -- === -- --- ![Banner Image](..\Presentations\Timer\TIMER_Main.jpg) --- -- # The TIMER Concept -- -- The TIMER class is the little sister of the @{Core.Scheduler#SCHEDULER} class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases. @@ -107,9 +105,6 @@ TIMER = { --- Timer ID. _TIMERID=0 ---- Timer data base. ---_TIMERDB={} - --- TIMER class version. -- @field #string version TIMER.version="0.1.1" @@ -118,7 +113,7 @@ TIMER.version="0.1.1" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: A lot. +-- TODO: Pause/unpause. -- TODO: Write docs. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -156,9 +151,6 @@ function TIMER:New(Function, ...) -- Log id. self.lid=string.format("TIMER UID=%d | ", self.uid) - -- Add to DB. - --_TIMERDB[self.uid]=self - return self end @@ -219,10 +211,7 @@ function TIMER:Stop(Delay) -- Not running any more. self.isrunning=false - - -- Remove DB entry. - --_TIMERDB[self.uid]=nil - + end end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index c270afb98..936f61750 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2169,7 +2169,6 @@ function AUFTRAG:_AssignCohort(Cohort) self:T3(self.lid..string.format("Assigning cohort %s", tostring(Cohort.name))) table.insert(self.squadrons, Cohort) - return self end diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index bbe3941bc..8b2f918df 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -22,6 +22,7 @@ -- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected. -- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged. -- @field #string Defcon Defence condition. +-- @field #string strategy Strategy of the CHIEF. -- @field Ops.Commander#COMMANDER commander Commander of assigned legions. -- @extends Ops.Intelligence#INTEL @@ -70,18 +71,11 @@ CHIEF.Strategy = { TOTALWAR="Total War" } ---- Strategy. --- @type CHIEF.MissionTypePerformance +--- Mission performance. +-- @type CHIEF.MissionPerformance -- @field #string MissionType Mission Type. -- @field #number Performance Performance: a number between 0 and 100, where 100 is best performance. - ---- Strategy. --- @type CHIEF.MissionTypeMapping --- @field #string Attribute Generalized attibute --- @field #table MissionTypes - - --- CHIEF class version. -- @field #string version CHIEF.version="0.0.1" @@ -93,8 +87,8 @@ CHIEF.version="0.0.1" -- 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: Get list/overview of enemy assets etc. --- TODO: Put all contacts into target list. Then make missions from them. +-- 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: Add/remove spawned flightgroups to detection set. @@ -119,17 +113,17 @@ function CHIEF:New(AgentSet, Coalition, Alias) -- Inherit everything from INTEL class. local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition, Alias)) --#CHIEF - -- Define zones. + -- Defaults. self:SetBorderZones() self:SetYellowZones() - self:SetThreatLevelRange() - -- Create a new COMMANDER. - self.commander=COMMANDER:New() - - -- Init DEFCON. + -- Init stuff. self.Defcon=CHIEF.DEFCON.GREEN + self.strategy=CHIEF.Strategy.DEFENSIVE + + -- Create a new COMMANDER. + self.commander=COMMANDER:New() -- Add FSM transitions. -- From State --> Event --> To State @@ -142,11 +136,11 @@ function CHIEF:New(AgentSet, Coalition, Alias) self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. self:AddTransition("*", "TransportCancel", "*") -- Cancel transport. - self:AddTransition("*", "Defcon", "*") -- Change defence condition. + self:AddTransition("*", "DefconChange", "*") -- Change defence condition. - self:AddTransition("*", "Stategy", "*") -- Change strategy condition. + self:AddTransition("*", "StategyChange", "*") -- Change strategy condition. - self:AddTransition("*", "DeclareWar", "*") -- Declare War. + self:AddTransition("*", "DeclareWar", "*") -- Declare War. Not implemented. ------------------------ --- Pseudo Functions --- @@ -181,6 +175,46 @@ function CHIEF:New(AgentSet, Coalition, Alias) -- @param #number delay Delay in seconds. + --- Triggers the FSM event "DefconChange". + -- @function [parent=#CHIEF] DefconChange + -- @param #CHIEF self + -- @param #string Defcon New Defence Condition. + + --- Triggers the FSM event "DefconChange" after a delay. + -- @function [parent=#CHIEF] __DefconChange + -- @param #CHIEF self + -- @param #number delay Delay in seconds. + -- @param #string Defcon New Defence Condition. + + --- On after "DefconChange" event. + -- @function [parent=#CHIEF] OnAfterDefconChange + -- @param #CHIEF self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Defcon New Defence Condition. + + + --- Triggers the FSM event "StrategyChange". + -- @function [parent=#CHIEF] StrategyChange + -- @param #CHIEF self + -- @param #string Strategy New strategy. + + --- Triggers the FSM event "StrategyChange" after a delay. + -- @function [parent=#CHIEF] __StrategyChange + -- @param #CHIEF self + -- @param #number delay Delay in seconds. + -- @param #string Strategy New strategy. + + --- On after "StrategyChange" event. + -- @function [parent=#CHIEF] OnAfterStrategyChange + -- @param #CHIEF self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Strategy New stragegy. + + --- Triggers the FSM event "MissionAssignToAny". -- @function [parent=#CHIEF] MissionAssignToAny -- @param #CHIEF self @@ -317,12 +351,60 @@ end -- @return #CHIEF self function CHIEF:SetDefcon(Defcon) + -- Check if valid string was passed. + local gotit=false + for _,defcon in pairs(CHIEF.DEFCON) do + if defcon==Defcon then + gotit=true + end + end + if not gotit then + self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s", tostring(Defcon))) + return self + end + + -- Trigger event if defcon changed. + if Defcon~=self.Defcon then + self:DefconChange(Defcon) + end + + -- Set new DEFCON. self.Defcon=Defcon - --self:Defcon(Defcon) return self end +--- Get defence condition. +-- @param #CHIEF self +-- @param #string Current Defence condition. See @{#CHIEF.DEFCON}, e.g. `CHIEF.DEFCON.RED`. +function CHIEF:GetDefcon(Defcon) + return self.Defcon +end + +--- Set stragegy. +-- @param #CHIEF self +-- @param #string Strategy Strategy. See @{#CHIEF.Stragegy}, e.g. `CHIEF.Strategy.DEFENSIVE` (default). +-- @return #CHIEF self +function CHIEF:SetStragety(Strategy) + + -- Trigger event if Strategy changed. + if Strategy~=self.strategy then + self:StrategyChange(Strategy) + end + + -- Set new Strategy. + self.strategy=Strategy + + return self +end + +--- Get defence condition. +-- @param #CHIEF self +-- @param #string Current Defence condition. See @{#CHIEF.DEFCON}, e.g. `CHIEF.DEFCON.RED`. +function CHIEF:GetDefcon(Defcon) + return self.Defcon +end + --- Get the commander. -- @param #CHIEF self @@ -389,14 +471,31 @@ end -- @return #CHIEF self function CHIEF:AddTarget(Target) - Target:SetPriority() - Target:SetImportance() - table.insert(self.targetqueue, Target) return self end +--- Remove target from queue. +-- @param #CHIEF self +-- @param Ops.Target#TARGET Target The target. +-- @return #CHIEF self +function CHIEF:RemoveTarget(Target) + + for i,_target in pairs(self.targetqueue) do + local target=_target --Ops.Target#TARGET + + if target.uid==Target.uid then + self:I(self.lid..string.format("Removing target %s from queue", Target.name)) + table.remove(self.targetqueue, i) + break + end + + end + + return self +end + --- Add strategically important zone. -- @param #CHIEF self -- @param Core.Zone#ZONE_RADIUS Zone Strategic zone. @@ -519,7 +618,8 @@ function CHIEF:onafterStatus(From, Event, To) -- Cancel this mission. contact.mission:Cancel() - -- TODO: contact.target + -- Remove a target from the queue. + self:RemoveTarget(contact.target) end @@ -535,19 +635,39 @@ function CHIEF:onafterStatus(From, Event, To) local contact=_contact --Ops.Intelligence#INTEL.Contact local group=contact.group --Wrapper.Group#GROUP + -- Check if contact inside of out border. local inred=self:CheckGroupInBorder(group) if inred then Nred=Nred+1 end + -- Check if contact is in the yellow zones. local inyellow=self:CheckGroupInYellow(group) if inyellow then Nyellow=Nyellow+1 end + + -- Check if this is not already a target. + if not contact.target then + + -- Create a new TARGET of the contact group. + local Target=TARGET:New(contact.group) + + -- Set to contact. + contact.target=Target + + -- Set contact to target. Might be handy. + Target.contact=contact + + -- Add target to queue. + self:AddTarget(Target) + + end -- Is this a threat? local threat=contact.threatlevel>=self.threatLevelMin and contact.threatlevel<=self.threatLevelMax + --[[ local redalert=true if self.borderzoneset:Count()>0 then redalert=inred @@ -555,11 +675,17 @@ function CHIEF:onafterStatus(From, Event, To) if redalert and threat and not contact.target then + -- Create a new TARGET of the contact group. local Target=TARGET:New(contact.group) + -- Set to contact. + contact.target=Target + + -- Add target to queue. self:AddTarget(Target) end + ]] end @@ -594,7 +720,7 @@ function CHIEF:onafterStatus(From, Event, To) local Ntargets=#self.targetqueue -- Info message - local text=string.format("Defcon=%s Assets=%d, Contacts: Total=%d Yellow=%d Red=%d, Targets=%d, Missions=%d", self.Defcon, Nassets, Ncontacts, Nyellow, Nred, Ntargets, Nmissions) + local text=string.format("Defcon=%s: Assets=%d, Contacts=%d [Yellow=%d Red=%d], Targets=%d, Missions=%d", self.Defcon, Nassets, Ncontacts, Nyellow, Nred, Ntargets, Nmissions) self:I(self.lid..text) end @@ -788,48 +914,27 @@ function CHIEF:onafterTransportCancel(From, Event, To, Transport) end ---- On before "Defcon" event. +--- On after "DefconChange" event. -- @param #CHIEF self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #string Defcon New defence condition. -function CHIEF:onbeforeDefcon(From, Event, To, Defcon) - - local gotit=false - for _,defcon in pairs(CHIEF.DEFCON) do - if defcon==Defcon then - gotit=true - end - end - - if not gotit then - self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s", tostring(Defcon))) - return false - end - - -- Defcon did not change. - if Defcon==self.Defcon then - self:I(self.lid..string.format("Defcon %s unchanged. Not processing transition!", tostring(Defcon))) - return false - end - - return true -end - ---- On after "Defcon" event. --- @param #CHIEF self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #string Defcon New defence condition. -function CHIEF:onafterDefcon(From, Event, To, Defcon) +function CHIEF:onafterDefconChange(From, Event, To, Defcon) self:I(self.lid..string.format("Changing Defcon from %s --> %s", self.Defcon, Defcon)) - - -- Set new defcon. - self.Defcon=Defcon end +--- On after "StrategyChange" event. +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string Strategy +function CHIEF:onafterDefconChange(From, Event, To, Strategy) + self:I(self.lid..string.format("Changing Strategy from %s --> %s", self.strategy, Strategy)) +end + + --- On after "DeclareWar" event. -- @param #CHIEF self -- @param #string From From state. @@ -852,71 +957,134 @@ end -- @param #CHIEF self function CHIEF:CheckTargetQueue() - -- TODO: Sort mission queue. wrt what? Threat level? + -- Number of missions. + local Ntargets=#self.targetqueue + -- Treat special cases. + if Ntargets==0 then + return nil + end + + -- Sort results table wrt prio and threatlevel. + local function _sort(a, b) + local taskA=a --Ops.Target#TARGET + local taskB=b --Ops.Target#TARGET + return (taskA.priotaskB.threatlevel0) + end + table.sort(self.targetqueue, _sort) + + -- Get the lowest importance value (lower means more important). + -- If a target with importance 1 exists, targets with importance 2 will not be assigned. Targets with no importance (nil) can still be selected. + local vip=math.huge + for _,_target in pairs(self.targetqueue) do + local target=_target --Ops.Target#TARGET + if target.importance and target.importance0 then - - env.info(string.format("FF found mission performance N=%d", #MissionPerformances)) - - -- Mission Type. - local MissionType=nil + + --TODO: Number of required assets. How many do we want? Should depend on: + -- * number of enemy units + -- * target threatlevel + -- * how many assets are still in stock + -- * is it inside of our border + local NassetsMin=1 + local NassetsMax=3 for _,_mp in pairs(MissionPerformances) do - local mp=_mp --#CHIEF.MissionTypePerformance - local n=self.commander:CountAssets(true, {mp.MissionType}) - env.info(string.format("FF Found N=%d assets in stock for mission type %s [Performance=%d]", n, mp.MissionType, mp.Performance)) - if n>0 then - mission=AUFTRAG:NewFromTarget(target, mp.MissionType) - break + local mp=_mp --#CHIEF.MissionPerformance + + -- Debug info. + self:I(self.lid..string.format("Recruiting assets for mission type %s [performance=%d] of target %s", mp.MissionType, mp.Performance, target:GetName())) + + -- Recruit assets. + local recruited, legions, assets=self:RecruitAssetsForTarget(target, mp.MissionType, NassetsMin, NassetsMax) + + if recruited then + + -- Create a mission. + mission=AUFTRAG:NewFromTarget(target, mp.MissionType) + + -- Add asset to mission. + if mission then + for _,asset in pairs(assets) do + mission:AddAsset(asset) + end + Legions=legions + + -- We got what we wanted ==> leave loop. + break + end + end end - - end - if mission then + -- Check if mission could be defined. + if mission and Legions then -- Set target mission entry. target.mission=mission @@ -924,9 +1092,11 @@ function CHIEF:CheckTargetQueue() -- Mission parameters. mission.prio=target.prio mission.importance=target.importance - - -- Add mission to COMMANDER queue. - self:AddMission(mission) + + -- Assign mission to legions. + for _,Legion in pairs(Legions) do + self.commander:MissionAssign(Legion, mission) + end -- Only ONE target is assigned per check. return @@ -941,7 +1111,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Resources +-- Zone Check Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Check if group is inside our border. @@ -1007,32 +1177,13 @@ end -- Resources ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Check resources. --- @param #CHIEF self --- @param Ops.Target#TARGET Target Target. --- @return #table -function CHIEF:CheckResources(Target) - - local objective=Target:GetObjective() - - local missionperformances={} - - for _,missionperformance in pairs(missionperformances) do - local mp=missionperformance --#CHIEF.MissionTypePerformance - - - end - - -end - --- Create a mission performance table. -- @param #CHIEF self -- @param #string MissionType Mission type. -- @param #number Performance Performance. -- @return #CHIEF.MissionPerformance Mission performance. function CHIEF:_CreateMissionPerformance(MissionType, Performance) - local mp={} --#CHIEF.MissionTypePerformance + local mp={} --#CHIEF.MissionPerformance mp.MissionType=MissionType mp.Performance=Performance return mp @@ -1064,7 +1215,7 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target) local TargetCategory=Target:GetCategory() - local missionperf={} --#CHIEF.MissionTypePerformance + local missionperf={} --#CHIEF.MissionPerformance if group then @@ -1148,63 +1299,63 @@ function CHIEF:_GetMissionTypeForGroupAttribute(Attribute) if Attribute==GROUP.Attribute.AIR_ATTACKHELO then - local mt={} --#CHIEF.MissionTypePerformance + local mt={} --#CHIEF.MissionPerformance mt.MissionType=AUFTRAG.Type.INTERCEPT mt.Performance=100 table.insert(missiontypes, mt) elseif Attribute==GROUP.Attribute.GROUND_AAA then - local mt={} --#CHIEF.MissionTypePerformance + local mt={} --#CHIEF.MissionPerformance mt.MissionType=AUFTRAG.Type.BAI mt.Performance=100 table.insert(missiontypes, mt) - local mt={} --#CHIEF.MissionTypePerformance + local mt={} --#CHIEF.MissionPerformance mt.MissionType=AUFTRAG.Type.BOMBING mt.Performance=70 table.insert(missiontypes, mt) - local mt={} --#CHIEF.MissionTypePerformance + local mt={} --#CHIEF.MissionPerformance mt.MissionType=AUFTRAG.Type.BOMBCARPET mt.Performance=70 table.insert(missiontypes, mt) - local mt={} --#CHIEF.MissionTypePerformance + local mt={} --#CHIEF.MissionPerformance mt.MissionType=AUFTRAG.Type.ARTY mt.Performance=30 table.insert(missiontypes, mt) elseif Attribute==GROUP.Attribute.GROUND_SAM then - local mt={} --#CHIEF.MissionTypePerformance + local mt={} --#CHIEF.MissionPerformance mt.MissionType=AUFTRAG.Type.SEAD mt.Performance=100 table.insert(missiontypes, mt) - local mt={} --#CHIEF.MissionTypePerformance + local mt={} --#CHIEF.MissionPerformance mt.MissionType=AUFTRAG.Type.BAI mt.Performance=100 table.insert(missiontypes, mt) - local mt={} --#CHIEF.MissionTypePerformance + local mt={} --#CHIEF.MissionPerformance mt.MissionType=AUFTRAG.Type.ARTY mt.Performance=50 table.insert(missiontypes, mt) elseif Attribute==GROUP.Attribute.GROUND_EWR then - local mt={} --#CHIEF.MissionTypePerformance + local mt={} --#CHIEF.MissionPerformance mt.MissionType=AUFTRAG.Type.SEAD mt.Performance=100 table.insert(missiontypes, mt) - local mt={} --#CHIEF.MissionTypePerformance + local mt={} --#CHIEF.MissionPerformance mt.MissionType=AUFTRAG.Type.BAI mt.Performance=100 table.insert(missiontypes, mt) - local mt={} --#CHIEF.MissionTypePerformance + local mt={} --#CHIEF.MissionPerformance mt.MissionType=AUFTRAG.Type.ARTY mt.Performance=50 table.insert(missiontypes, mt) @@ -1214,6 +1365,183 @@ function CHIEF:_GetMissionTypeForGroupAttribute(Attribute) end +--- Recruit assets for a given mission. +-- @param #CHIEF self +-- @param Ops.Target#TARGET Target The target. +-- @param #string MissionType Mission Type. +-- @return #boolean If `true` enough assets could be recruited. +-- @return #table Legions that have recruited assets. +-- @return #table Assets that have been recruited from all legions. +function CHIEF:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMax) + + -- The recruited assets. + local Assets={} + + -- Legions which have the best assets for the Mission. + local Legions={} + + -- Target vector. + local TargetVec2=Target:GetVec2() + + for _,_legion in pairs(self.commander.legions) do + local legion=_legion --Ops.Legion#LEGION + + -- Distance to target. + local TargetDistance=Target:GetCoordinate():Get2DDistance(legion:GetCoordinate()) + + -- Number of payloads in stock per aircraft type. + local Npayloads={} + + -- First get payloads for aircraft types of squadrons. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + if Npayloads[cohort.aircrafttype]==nil then + Npayloads[cohort.aircrafttype]=legion:IsAirwing() and legion:CountPayloadsInStock(MissionType, cohort.aircrafttype) or 999 + self:T2(self.lid..string.format("Got N=%d payloads for mission type %s [%s]", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) + end + end + + -- Loops over cohorts. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + local npayloads=Npayloads[cohort.aircrafttype] + + if cohort:IsOnDuty() and cohort:CheckMissionCapability({MissionType}) and cohort.engageRange>=TargetDistance and npayloads>0 then + + -- Recruit assets from squadron. + local assets, npayloads=cohort:RecruitAssets(MissionType, npayloads) + + Npayloads[cohort.aircrafttype]=npayloads + + for _,asset in pairs(assets) do + table.insert(Assets, asset) + end + + end + + end + + end + + -- Now we have a long list with assets. + self:_OptimizeAssetSelection(Assets, MissionType, TargetVec2, false) + + for _,_asset in pairs(Assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + if asset.legion:IsAirwing() then + + -- Only assets that have no payload. Should be only spawned assets! + if not asset.payload then + + -- Fetch payload for asset. This can be nil! + asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, MissionType) + + end + + end + + end + + -- Remove assets that dont have a payload. + for i=#Assets,1,-1 do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.payload then + self:T3(self.lid..string.format("Remove asset %s with no payload", tostring(asset.spawngroupname))) + table.remove(Assets, i) + end + end + + -- Now find the best asset for the given payloads. + self:_OptimizeAssetSelection(Assets, MissionType, TargetVec2, true) + + -- Number of assets. At most NreqMax. + local Nassets=math.min(#Assets, NassetsMax) + + if #Assets>=Nassets then + + --- + -- Found enough assets + --- + + -- Get Legions of assets and put into table. + for i=1,Nassets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + Legions[asset.legion.alias]=asset.legion + end + + + -- Return payloads and remove not needed assets. + for i=#Assets,Nassets+1,-1 do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.spawned then + self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + asset.legion:ReturnPayloadFromAsset(asset) + end + table.remove(Assets, i) + end + + -- Found enough assets. + return true, Legions, Assets + else + + --- + -- NOT enough assets + --- + + -- Return payloads of assets. + + for i=1,#Assets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.spawned then + self:T2(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + asset.legion:ReturnPayloadFromAsset(asset) + end + end + + -- Not enough assets found. + return false, {}, {} + end + + return nil, {}, {} +end + +--- Optimize chosen assets for the mission at hand. +-- @param #CHIEF self +-- @param #table assets Table of (unoptimized) assets. +-- @param #string MissionType MissionType for which the best assets are desired. +-- @param DCS#Vec2 TargetVec2 Target 2D vector. +-- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. +function CHIEF:_OptimizeAssetSelection(assets, MissionType, TargetVec2, includePayload) + + -- Calculate the mission score of all assets. + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.score=asset.legion:CalculateAssetMissionScore(asset, MissionType, TargetVec2, includePayload) + end + + --- Sort assets wrt to their mission score. Higher is better. + local function optimize(a, b) + local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem + local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem + -- Higher score wins. If equal score ==> closer wins. + return (assetA.score>assetB.score) + end + table.sort(assets, optimize) + + -- Remove distance parameter. + local text=string.format("Optimized %d assets for %s mission (payload=%s):", #assets, MissionType, tostring(includePayload)) + for i,Asset in pairs(assets) do + local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem + text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) + asset.score=nil + end + self:T2(self.lid..text) + +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 7433f3413..460709388 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -416,10 +416,32 @@ function COMMANDER:onafterStatus(From, Event, To) for _,_asset in pairs(cohort.assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - + + local state="In Stock" + if asset.flightgroup then + state=asset.flightgroup:GetState() + local mission=legion:GetAssetCurrentMission(asset) + if mission then + state=state..string.format("Mission %s [%s]", mission:GetName(), mission:GetType()) + end + else + if asset.spawned then + env.info("FF ERROR: asset has opsgroup but is NOT spawned!") + end + if asset.requested and asset.isReserved then + env.info("FF ERROR: asset is requested and reserved. Should not be both!") + state="Reserved+Requested!" + elseif asset.isReserved then + state="Reserved" + elseif asset.requested then + state="Requested" + end + end + -- Text. - text=text..string.format("\n- %s [UID=%d] Legion=%s, Cohort=%s: Spawned=%s, Requested=%s [RID=%s], Reserved=%s", - asset.spawngroupname, asset.uid, legion.alias, cohort.name, tostring(asset.spawned), tostring(asset.requested), tostring(asset.rid), tostring(asset.isReserved)) + text=text..string.format("\n[UID=%03d] %s Legion=%s [%s]: State=%s [RID=%s]", + asset.uid, asset.spawngroupname, legion.alias, cohort.name, state, tostring(asset.rid)) + if asset.spawned then Nspawned=Nspawned+1 @@ -852,10 +874,13 @@ end -- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. function COMMANDER:_OptimizeAssetSelection(assets, Mission, includePayload) + -- Target position. + local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil + -- Calculate the mission score of all assets. for _,_asset in pairs(assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.score=asset.legion:CalculateAssetMissionScore(asset, Mission, includePayload) + asset.score=asset.legion:CalculateAssetMissionScore(asset, Mission, TargetVec2, includePayload) end --- Sort assets wrt to their mission score. Higher is better. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index abd32593c..f2a00a427 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2550,10 +2550,11 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Waypoints from current position to holding point. local wp={} - wp[#wp+1]=c0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Current Pos") - wp[#wp+1]=c1:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Climb") - wp[#wp+1]=c2:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Descent") - wp[#wp+1]=p0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {TaskArrived, TaskHold, TaskKlar}, "Holding Point") + -- NOTE: Currently, this first waypoint confuses the AI. It makes them go in circles. Looks like they cannot find the waypoint and are flying around it. + --wp[#wp+1]=c0:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Current Pos") + wp[#wp+1]=c1:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Climb") + wp[#wp+1]=c2:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Descent") + wp[#wp+1]=p0:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {TaskArrived, TaskHold, TaskKlar}, "Holding Point") -- Approach point: 10 NN in direction of runway. if airbase:IsAirdrome() then @@ -2563,7 +2564,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) --- local papp=airbase:GetCoordinate():Translate(x1, runway.heading-180):SetAltitude(h1) - wp[#wp+1]=papp:WaypointAirTurningPoint(nil, UTILS.KnotsToKmph(SpeedLand), {}, "Final Approach") + wp[#wp+1]=papp:WaypointAirTurningPoint("BARO", UTILS.KnotsToKmph(SpeedLand), {}, "Final Approach") -- Okay, it looks like it's best to specify the coordinates not at the airbase but a bit away. This causes a more direct landing approach. local pland=airbase:GetCoordinate():Translate(x2, runway.heading-180):SetAltitude(h2) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 116a1582f..30ee30224 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -196,12 +196,12 @@ function INTEL:New(DetectionSet, Coalition, Alias) if Alias then self.alias=tostring(Alias) else - self.alias="SPECTRE" + self.alias="INTEL SPECTRE" if self.coalition then if self.coalition==coalition.side.RED then - self.alias="KGB" + self.alias="INTEL KGB" elseif self.coalition==coalition.side.BLUE then - self.alias="CIA" + self.alias="INTEL CIA" end end end @@ -214,7 +214,7 @@ function INTEL:New(DetectionSet, Coalition, Alias) self.DetectDLINK = true -- Set some string id for output to DCS.log file. - self.lid=string.format("INTEL %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") -- Start State. self:SetStartState("Stopped") diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index be466cc7c..63ba51c93 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -1775,9 +1775,10 @@ end -- @param #LEGION self -- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset -- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. +-- @param DCS#Vec2 TargetVec2 Target 2D vector. -- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. -- @return #number Mission score. -function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) +function LEGION:CalculateAssetMissionScore(asset, Mission, TargetVec2, includePayload) -- Mission score. local score=0 @@ -1800,10 +1801,7 @@ function LEGION:CalculateAssetMissionScore(asset, Mission, includePayload) if includePayload and asset.payload then score=score+self:GetPayloadPeformance(asset.payload, Mission.type) end - - -- Target position. - local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil --Mission:GetTargetVec2() - + -- Origin: We take the flightgroups position or the one of the legion. local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2() or self:GetVec2() @@ -1843,10 +1841,13 @@ end -- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. function LEGION:_OptimizeAssetSelection(assets, Mission, includePayload) + -- Target position. + local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil + -- Calculate the mission score of all assets. for _,_asset in pairs(assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.score=self:CalculateAssetMissionScore(asset, Mission, includePayload) + asset.score=self:CalculateAssetMissionScore(asset, Mission, TargetVec2, includePayload) end --- Sort assets wrt to their mission score. Higher is better. diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index d4ca460b1..1127954f8 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -30,6 +30,7 @@ -- @field #number Tattacked Abs. mission time stamp when an attack was started. -- @field #number dTCapture Time interval in seconds until a zone is captured. -- @field #boolean neutralCanCapture Neutral units can capture. Default `false`. +-- @field #drawZone If `true`, draw the zone on the F10 map. -- @extends Core.Fsm#FSM --- Be surprised! diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 35aa98cda..01ec67d9f 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -17,6 +17,7 @@ -- @type TARGET -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity level. +-- @field #number uid Unique ID of the target. -- @field #string lid Class id string for output to DCS log file. -- @field #table targets Table of target objects. -- @field #number targetcounter Running number to generate target object IDs. @@ -152,6 +153,9 @@ function TARGET:New(TargetObject) -- Increase counter. _TARGETID=_TARGETID+1 + + -- Set UID. + self.uid=_TARGETID -- Add object. self:AddObject(TargetObject) @@ -163,6 +167,10 @@ function TARGET:New(TargetObject) self:E("ERROR: No valid TARGET!") return nil end + + -- Defaults. + self:SetPriority() + self:SetImportance() -- Target Name. self.name=self:GetTargetName(Target) @@ -862,10 +870,25 @@ function TARGET:GetLife() return N end +--- Get target 2D position vector. +-- @param #TARGET self +-- @param #TARGET.Object Target Target object. +-- @return DCS#Vec2 Vector with x,y components. +function TARGET:GetTargetVec2(Target) + + local vec3=self:GetTargetVec3(Target) + + if vec3 then + return {x=vec3.x, y=vec3.z} + end + + return nil +end + --- Get target 3D position vector. -- @param #TARGET self -- @param #TARGET.Object Target Target object. --- @return DCS#Vec3 Vector with x,y,z components +-- @return DCS#Vec3 Vector with x,y,z components. function TARGET:GetTargetVec3(Target) if Target.Type==TARGET.ObjectType.GROUP then @@ -1031,6 +1054,46 @@ function TARGET:GetName() return self.name end +--- Get 2D vector. +-- @param #TARGET self +-- @return DCS#Vec2 2D vector of the target. +function TARGET:GetVec2() + + for _,_target in pairs(self.targets) do + local Target=_target --#TARGET.Object + + local coordinate=self:GetTargetVec2(Target) + + if coordinate then + return coordinate + end + + end + + self:E(self.lid..string.format("ERROR: Cannot get Vec2 of target %s", self.name)) + return nil +end + +--- Get 3D vector. +-- @param #TARGET self +-- @return DCS#Vec3 3D vector of the target. +function TARGET:GetVec3() + + for _,_target in pairs(self.targets) do + local Target=_target --#TARGET.Object + + local coordinate=self:GetTargetVec3(Target) + + if coordinate then + return coordinate + end + + end + + self:E(self.lid..string.format("ERROR: Cannot get Vec3 of target %s", self.name)) + return nil +end + --- Get coordinate. -- @param #TARGET self -- @return Core.Point#COORDINATE Coordinate of the target. From 9161cec23848245ee570f70e9969c24db438a128 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 16 Sep 2021 21:30:36 +0200 Subject: [PATCH 104/141] OPS - Simplified asset selection by using just one routine for LEGION, COMMANDER and CHIEF --- Moose Development/Moose/Ops/Chief.lua | 49 +---- Moose Development/Moose/Ops/Cohort.lua | 6 +- Moose Development/Moose/Ops/Commander.lua | 83 +------- Moose Development/Moose/Ops/Legion.lua | 227 ++++++++-------------- 4 files changed, 96 insertions(+), 269 deletions(-) diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 8b2f918df..6993c25e2 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -663,10 +663,7 @@ function CHIEF:onafterStatus(From, Event, To) self:AddTarget(Target) end - - -- Is this a threat? - local threat=contact.threatlevel>=self.threatLevelMin and contact.threatlevel<=self.threatLevelMax - + --[[ local redalert=true if self.borderzoneset:Count()>0 then @@ -986,9 +983,12 @@ function CHIEF:CheckTargetQueue() -- Loop over targets. for _,_target in pairs(self.targetqueue) do local target=_target --Ops.Target#TARGET + + -- Is this a threat? + local isThreat=target.threatlevel0>=self.threatLevelMin and target.threatlevel0<=self.threatLevelMax -- Check that target is alive and not already a mission has been assigned. - if target:IsAlive() and (target.importance==nil or target.importance<=vip) and not target.mission then + if target:IsAlive() and (target.importance==nil or target.importance<=vip) and isThreat and not target.mission then -- Check if this target is "valid", i.e. fits with the current strategy. local valid=false @@ -1425,7 +1425,7 @@ function CHIEF:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMa end -- Now we have a long list with assets. - self:_OptimizeAssetSelection(Assets, MissionType, TargetVec2, false) + LEGION._OptimizeAssetSelection(self, Assets, MissionType, TargetVec2, false) for _,_asset in pairs(Assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem @@ -1454,7 +1454,7 @@ function CHIEF:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMa end -- Now find the best asset for the given payloads. - self:_OptimizeAssetSelection(Assets, MissionType, TargetVec2, true) + LEGION._OptimizeAssetSelection(self, Assets, MissionType, TargetVec2, true) -- Number of assets. At most NreqMax. local Nassets=math.min(#Assets, NassetsMax) @@ -1507,41 +1507,6 @@ function CHIEF:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMa return nil, {}, {} end ---- Optimize chosen assets for the mission at hand. --- @param #CHIEF self --- @param #table assets Table of (unoptimized) assets. --- @param #string MissionType MissionType for which the best assets are desired. --- @param DCS#Vec2 TargetVec2 Target 2D vector. --- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. -function CHIEF:_OptimizeAssetSelection(assets, MissionType, TargetVec2, includePayload) - - -- Calculate the mission score of all assets. - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.score=asset.legion:CalculateAssetMissionScore(asset, MissionType, TargetVec2, includePayload) - end - - --- Sort assets wrt to their mission score. Higher is better. - local function optimize(a, b) - local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem - local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem - -- Higher score wins. If equal score ==> closer wins. - return (assetA.score>assetB.score) - end - table.sort(assets, optimize) - - -- Remove distance parameter. - local text=string.format("Optimized %d assets for %s mission (payload=%s):", #assets, MissionType, tostring(includePayload)) - for i,Asset in pairs(assets) do - local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem - text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) - asset.score=nil - end - self:T2(self.lid..text) - -end - - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 62d543db7..1bf9db20a 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -294,7 +294,7 @@ function COHORT:AddMissionCapability(MissionTypes, Performance) capability.MissionType=missiontype capability.Performance=Performance or 50 table.insert(self.missiontypes, capability) - env.info("FF adding mission capability "..tostring(capability.MissionType)) + self:T(self.lid..string.format("Adding mission capability %s, performance=%d", tostring(capability.MissionType), capability.Performance)) end end @@ -829,13 +829,13 @@ function COHORT:RecruitAssets(MissionType, Npayloads) -- Check if the payload of this asset is compatible with the mission. -- Note: we do not check the payload as an asset that is on a GCICAP mission should be able to do an INTERCEPT as well! - self:I(self.lid.."Adding asset on GCICAP mission for an INTERCEPT mission") + self:I(self.lid..string.format("Adding asset on GCICAP mission for an INTERCEPT mission")) table.insert(assets, asset) elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.ALERT5) and self:CheckMissionCapability(MissionType, asset.payload.capabilities) then -- Check if the payload of this asset is compatible with the mission. - self:I(self.lid.."Adding asset on ALERT 5 mission for XXX mission") + self:I(self.lid..string.format("Adding asset on ALERT 5 mission for %s mission", MissionType)) table.insert(assets, asset) end diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 460709388..3181cd513 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -775,8 +775,11 @@ function COMMANDER:RecruitAssets(Mission) end + -- Target position. + local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil + -- Now we have a long list with assets. - self:_OptimizeAssetSelection(Assets, Mission, false) + LEGION._OptimizeAssetSelection(self, Assets, Mission.type, TargetVec2, false) for _,_asset in pairs(Assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem @@ -813,7 +816,7 @@ function COMMANDER:RecruitAssets(Mission) end -- Now find the best asset for the given payloads. - self:_OptimizeAssetSelection(Assets, Mission, true) + LEGION._OptimizeAssetSelection(self, Assets, Mission.type, TargetVec2, true) -- Get number of required assets. local Nassets=Mission:GetRequiredAssets(self) @@ -867,43 +870,6 @@ function COMMANDER:RecruitAssets(Mission) return nil, {} end ---- Optimize chosen assets for the mission at hand. --- @param #COMMANDER self --- @param #table assets Table of (unoptimized) assets. --- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. --- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. -function COMMANDER:_OptimizeAssetSelection(assets, Mission, includePayload) - - -- Target position. - local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil - - -- Calculate the mission score of all assets. - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.score=asset.legion:CalculateAssetMissionScore(asset, Mission, TargetVec2, includePayload) - end - - --- Sort assets wrt to their mission score. Higher is better. - local function optimize(a, b) - local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem - local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem - -- Higher score wins. If equal score ==> closer wins. - return (assetA.score>assetB.score) - end - table.sort(assets, optimize) - - -- Remove distance parameter. - local text=string.format("Optimized %d assets for %s mission (payload=%s):", #assets, Mission.type, tostring(includePayload)) - for i,Asset in pairs(assets) do - local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem - text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) - asset.dist=nil - asset.score=nil - end - self:T2(self.lid..text) - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Transport Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1061,8 +1027,12 @@ function COMMANDER:RecruitAssetsForTransport(Transport) end + -- Target position. + local TargetVec2=Transport:GetDeployZone():GetVec2() + -- Now we have a long list with assets. - self:_OptimizeAssetSelectionForTransport(Assets, Transport) + LEGION._OptimizeAssetSelection(self, Assets, AUFTRAG.Type.OPSTRANSPORT, TargetVec2, false) + for _,_asset in pairs(Assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem @@ -1146,39 +1116,6 @@ function COMMANDER:RecruitAssetsForTransport(Transport) return nil, {} end ---- Optimize chosen assets for the given transport. --- @param #COMMANDER self --- @param #table assets Table of (unoptimized) assets. --- @param Ops.OpsTransport#OPSTRANSPORT Transport Transport assignment. -function COMMANDER:_OptimizeAssetSelectionForTransport(assets, Transport) - - -- Calculate the mission score of all assets. - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.score=asset.legion:CalculateAssetTransportScore(asset, Transport) - end - - --- Sort assets wrt to their mission score. Higher is better. - local function optimize(a, b) - local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem - local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem - -- Higher score wins. If equal score ==> closer wins. - return (assetA.score>assetB.score) - end - table.sort(assets, optimize) - - -- Remove distance parameter. - local text=string.format("Optimized %d assets for %s mission (payload=%s):", #assets, Mission.type, tostring(includePayload)) - for i,Asset in pairs(assets) do - local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem - text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) - asset.dist=nil - asset.score=nil - end - self:T2(self.lid..text) - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Resources ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 63ba51c93..0a3b6e28b 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -1677,8 +1677,11 @@ function LEGION:RecruitAssets(Mission) end + -- Target position. + local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil + -- Now we have a long list with assets. - self:_OptimizeAssetSelection(Assets, Mission, false) + self:_OptimizeAssetSelection(Assets, Mission.type, TargetVec2, false) -- If airwing, get the best payload available. if self:IsAirwing() then @@ -1713,7 +1716,7 @@ function LEGION:RecruitAssets(Mission) end -- Now find the best asset for the given payloads. - self:_OptimizeAssetSelection(Assets, Mission, true) + self:_OptimizeAssetSelection(Assets, Mission.type, TargetVec2, true) end @@ -1771,106 +1774,6 @@ function LEGION:RecruitAssets(Mission) end ---- Calculate the mission score of an asset. --- @param #LEGION self --- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset --- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. --- @param DCS#Vec2 TargetVec2 Target 2D vector. --- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. --- @return #number Mission score. -function LEGION:CalculateAssetMissionScore(asset, Mission, TargetVec2, includePayload) - - -- Mission score. - local score=0 - - -- Prefer highly skilled assets. - if asset.skill==AI.Skill.AVERAGE then - score=score+0 - elseif asset.skill==AI.Skill.GOOD then - score=score+10 - elseif asset.skill==AI.Skill.HIGH then - score=score+20 - elseif asset.skill==AI.Skill.EXCELLENT then - score=score+30 - end - - -- Add mission performance to score. - score=score+asset.cohort:GetMissionPeformance(Mission.type) - - -- Add payload performance to score. - if includePayload and asset.payload then - score=score+self:GetPayloadPeformance(asset.payload, Mission.type) - end - - -- Origin: We take the flightgroups position or the one of the legion. - local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2() or self:GetVec2() - - -- Distance factor. - local distance=0 - if TargetVec2 and OrigVec2 then - -- Distance in NM. - distance=UTILS.MetersToNM(UTILS.VecDist2D(OrigVec2, TargetVec2)) - -- Round: 55 NM ==> 5.5 ==> 6, 63 NM ==> 6.3 ==> 6 - distance=UTILS.Round(distance/10, 0) - end - - -- Reduce score for legions that are futher away. - score=score-distance - - -- Intercepts need to be carried out quickly. We prefer spawned assets. - if Mission.type==AUFTRAG.Type.INTERCEPT then - if asset.spawned then - self:T(self.lid.."Adding 25 to asset because it is spawned") - score=score+25 - end - end - - -- TODO: This could be vastly improved. Need to gather ideas during testing. - -- Calculate ETA? Assets on orbit missions should arrive faster even if they are further away. - -- Max speed of assets. - -- Fuel amount? - -- Range of assets? - - return score -end - ---- Optimize chosen assets for the mission at hand. --- @param #LEGION self --- @param #table assets Table of (unoptimized) assets. --- @param Ops.Auftrag#AUFTRAG Mission Mission for which the best assets are desired. --- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. -function LEGION:_OptimizeAssetSelection(assets, Mission, includePayload) - - -- Target position. - local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil - - -- Calculate the mission score of all assets. - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.score=self:CalculateAssetMissionScore(asset, Mission, TargetVec2, includePayload) - end - - --- Sort assets wrt to their mission score. Higher is better. - local function optimize(a, b) - local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem - local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem - -- Higher score wins. If equal score ==> closer wins. - return (assetA.score>assetB.score) - end - table.sort(assets, optimize) - - -- Remove distance parameter. - local text=string.format("Optimized %d assets for %s mission (payload=%s):", #assets, Mission.type, tostring(includePayload)) - for i,Asset in pairs(assets) do - local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem - text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) - asset.dist=nil - asset.score=nil - end - self:T2(self.lid..text) - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Transport Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1939,8 +1842,11 @@ function LEGION:RecruitAssetsForTransport(Transport) end + -- Target is the deploy zone. + local TargetVec2=Transport:GetDeployZone():GetVec2() + -- Sort asset list. Best ones come first. - self:_OptimizeAssetSelectionForTransport(Assets, Transport) + self:_OptimizeAssetSelection(Assets, AUFTRAG.Type.OPSTRANSPORT, TargetVec2, false) -- If airwing, get the best payload available. if self:IsAirwing() then @@ -2025,46 +1931,18 @@ function LEGION:RecruitAssetsForTransport(Transport) end - ---- Optimize chosen assets for the mission at hand. --- @param #LEGION self --- @param #table assets Table of (unoptimized) assets. --- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. -function LEGION:_OptimizeAssetSelectionForTransport(assets, Transport) - - -- Calculate the mission score of all assets. - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.score=self:CalculateAssetTransportScore(asset, Transport) - end - - --- Sort assets wrt to their mission score. Higher is better. - local function optimize(a, b) - local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem - local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem - -- Higher score wins. If equal score ==> closer wins. - return (assetA.score>assetB.score) - end - table.sort(assets, optimize) - - -- Remove distance parameter. - local text=string.format("Optimized %d assets for transport:", #assets) - for i,Asset in pairs(assets) do - local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem - text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) - asset.dist=nil - asset.score=nil - end - self:T2(self.lid..text) - -end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Optimization Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Calculate the mission score of an asset. -- @param #LEGION self -- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset --- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. +-- @param #string MissionType Mission type for which the best assets are desired. +-- @param DCS#Vec2 TargetVec2 Target 2D vector. +-- @param #boolean IncludePayload If `true`, include the payload in the calulation if the asset has one attached. -- @return #number Mission score. -function LEGION:CalculateAssetTransportScore(asset, Transport) +function LEGION:CalculateAssetMissionScore(asset, MissionType, TargetVec2, IncludePayload) -- Mission score. local score=0 @@ -2081,13 +1959,15 @@ function LEGION:CalculateAssetTransportScore(asset, Transport) end -- Add mission performance to score. - score=score+asset.cohort:GetMissionPeformance(AUFTRAG.Type.OPSTRANSPORT) + score=score+asset.cohort:GetMissionPeformance(MissionType) - -- Target position. - local TargetVec2=Transport:GetDeployZone():GetVec2() - - -- Origin: We take the flightgroups position or the one of the legion. - local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2() or self:GetVec2() + -- Add payload performance to score. + if IncludePayload and asset.payload then + score=score+LEGION.GetPayloadPeformance(self, asset.payload, MissionType) + end + + -- Origin: We take the OPSGROUP position or the one of the legion. + local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2() or asset.legion:GetVec2() -- Distance factor. local distance=0 @@ -2101,18 +1981,63 @@ function LEGION:CalculateAssetTransportScore(asset, Transport) -- Reduce score for legions that are futher away. score=score-distance - -- Add 1 score point for each 10 kg of cargo bay. - score=score+UTILS.Round(asset.cargobaymax/10, 0) - - --TODO: Check ALERT 5 for Transports. - if asset.spawned then - self:T(self.lid.."Adding 25 to asset because it is spawned") - score=score+25 + -- Intercepts need to be carried out quickly. We prefer spawned assets. + if MissionType==AUFTRAG.Type.INTERCEPT then + if asset.spawned then + self:T(self.lid.."Adding 25 to asset because it is spawned") + score=score+25 + end end + + -- TRANSPORT specific. + if MissionType==AUFTRAG.Type.OPSTRANSPORT then + -- Add 1 score point for each 10 kg of cargo bay. + score=score+UTILS.Round(asset.cargobaymax/10, 0) + end + + -- TODO: This could be vastly improved. Need to gather ideas during testing. + -- Calculate ETA? Assets on orbit missions should arrive faster even if they are further away. + -- Max speed of assets. + -- Fuel amount? + -- Range of assets? return score end +--- Optimize chosen assets for the mission at hand. +-- @param #LEGION self +-- @param #table assets Table of (unoptimized) assets. +-- @param #string MissionType Mission type. +-- @param DCS#Vec2 TargetVec2 Target position as 2D vector. +-- @param #boolean IncludePayload If `true`, include the payload in the calulation if the asset has one attached. +function LEGION:_OptimizeAssetSelection(assets, MissionType, TargetVec2, IncludePayload) + + -- Calculate the mission score of all assets. + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.score=LEGION.CalculateAssetMissionScore(self, asset, MissionType, TargetVec2, IncludePayload) + end + + --- Sort assets wrt to their mission score. Higher is better. + local function optimize(a, b) + local assetA=a --Functional.Warehouse#WAREHOUSE.Assetitem + local assetB=b --Functional.Warehouse#WAREHOUSE.Assetitem + -- Higher score wins. If equal score ==> closer wins. + return (assetA.score>assetB.score) + end + table.sort(assets, optimize) + + -- Remove distance parameter. + local text=string.format("Optimized %d assets for %s mission/transport (payload=%s):", #assets, MissionType, tostring(IncludePayload)) + for i,Asset in pairs(assets) do + local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem + text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) + asset.score=nil + end + self:T2(self.lid..text) + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From e755bba6080a28529899edee128455dfee579678 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 17 Sep 2021 16:59:43 +0200 Subject: [PATCH 105/141] OPS - Clean up on "CheckMissionType" and "CheckMissionCapability" functions ==> moved to AUFTRAG as global functions. --- Moose Development/Moose/Ops/AirWing.lua | 8 +- Moose Development/Moose/Ops/Auftrag.lua | 80 ++++++++ Moose Development/Moose/Ops/Chief.lua | 20 +- Moose Development/Moose/Ops/Cohort.lua | 75 ++----- Moose Development/Moose/Ops/Commander.lua | 24 +-- Moose Development/Moose/Ops/Legion.lua | 231 ++++++---------------- 6 files changed, 166 insertions(+), 272 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index e1eb895ac..44e87e638 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -330,7 +330,7 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance) end -- Add ORBIT for all. - if not self:CheckMissionType(AUFTRAG.Type.ORBIT, MissionTypes) then + if not AUFTRAG.CheckMissionType(AUFTRAG.Type.ORBIT, MissionTypes) then local capability={} --Ops.Auftrag#AUFTRAG.Capability capability.MissionType=AUFTRAG.Type.ORBIT capability.Performance=50 @@ -452,7 +452,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) local payload=_payload --#AIRWING.Payload local specialpayload=_checkPayloads(payload) - local compatible=self:CheckMissionCapability(MissionType, payload.capabilities) + local compatible=AUFTRAG.CheckMissionCapability(MissionType, payload.capabilities) local goforit = specialpayload or (specialpayload==nil and compatible) @@ -466,7 +466,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) self:I(self.lid..string.format("Sorted payloads for mission type %s and aircraft type=%s:", MissionType, UnitType)) for _,_payload in ipairs(self.payloads) do local payload=_payload --#AIRWING.Payload - if payload.aircrafttype==UnitType and self:CheckMissionCapability(MissionType, payload.capabilities) then + if payload.aircrafttype==UnitType and AUFTRAG.CheckMissionCapability(MissionType, payload.capabilities) then local performace=self:GetPayloadPeformance(payload, MissionType) self:I(self.lid..string.format("- %s payload for %s: avail=%d performace=%d", MissionType, payload.aircrafttype, payload.navail, performace)) end @@ -1165,7 +1165,7 @@ function AIRWING:CountPayloadsInStock(MissionTypes, UnitTypes, Payloads) for _,MissionType in pairs(MissionTypes) do local specialpayload=_checkPayloads(payload) - local compatible=self:CheckMissionCapability(MissionType, payload.capabilities) + local compatible=AUFTRAG.CheckMissionCapability(MissionType, payload.capabilities) local goforit = specialpayload or (specialpayload==nil and compatible) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 936f61750..4b7030154 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -4317,6 +4317,86 @@ function AUFTRAG:GetMissionTaskforMissionType(MissionType) return mtask end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Global Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Checks if a mission type is contained in a table of possible types. +-- @param #string MissionType The requested mission type. +-- @param #table PossibleTypes A table with possible mission types. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function AUFTRAG.CheckMissionType(MissionType, PossibleTypes) + + if type(PossibleTypes)=="string" then + PossibleTypes={PossibleTypes} + end + + for _,canmission in pairs(PossibleTypes) do + if canmission==MissionType then + return true + end + end + + return false +end + +--- Check if a mission type is contained in a list of possible capabilities. +-- @param #table MissionTypes The requested mission type. Can also be passed as a single mission type `#string`. +-- @param #table Capabilities A table with possible capabilities `Ops.Auftrag#AUFTRAG.Capability`. +-- @param #boolean All If `true`, given mission type must be includedin ALL capabilities. If `false` or `nil`, it must only match one. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function AUFTRAG.CheckMissionCapability(MissionTypes, Capabilities, All) + + -- Ensure table. + if type(MissionTypes)~="table" then + MissionTypes={MissionTypes} + end + + for _,cap in pairs(Capabilities) do + local capability=cap --Ops.Auftrag#AUFTRAG.Capability + for _,MissionType in pairs(MissionTypes) do + if All==true then + if capability.MissionType~=MissionType then + return false + end + else + if capability.MissionType==MissionType then + return true + end + end + end + end + + if All==true then + return true + else + return false + end +end + + +--- Check if a mission type is contained in a list of possible capabilities. +-- @param #table MissionTypes The requested mission type. Can also be passed as a single mission type `#string`. +-- @param #table Capabilities A table with possible capabilities `Ops.Auftrag#AUFTRAG.Capability`. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function AUFTRAG.CheckMissionCapabilityAny(MissionTypes, Capabilities) + + local res=AUFTRAG.CheckMissionCapability(MissionTypes, Capabilities, false) + + return res +end + + +--- Check if a mission type is contained in a list of possible capabilities. +-- @param #table MissionTypes The requested mission type. Can also be passed as a single mission type `#string`. +-- @param #table Capabilities A table with possible capabilities `Ops.Auftrag#AUFTRAG.Capability`. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function AUFTRAG.CheckMissionCapabilityAll(MissionTypes, Capabilities) + + local res=AUFTRAG.CheckMissionCapability(MissionTypes, Capabilities, true) + + return res +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 6993c25e2..739d29dc3 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -1388,31 +1388,15 @@ function CHIEF:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMa -- Distance to target. local TargetDistance=Target:GetCoordinate():Get2DDistance(legion:GetCoordinate()) - - -- Number of payloads in stock per aircraft type. - local Npayloads={} - - -- First get payloads for aircraft types of squadrons. - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - if Npayloads[cohort.aircrafttype]==nil then - Npayloads[cohort.aircrafttype]=legion:IsAirwing() and legion:CountPayloadsInStock(MissionType, cohort.aircrafttype) or 999 - self:T2(self.lid..string.format("Got N=%d payloads for mission type %s [%s]", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) - end - end -- Loops over cohorts. for _,_cohort in pairs(legion.cohorts) do local cohort=_cohort --Ops.Cohort#COHORT - local npayloads=Npayloads[cohort.aircrafttype] - - if cohort:IsOnDuty() and cohort:CheckMissionCapability({MissionType}) and cohort.engageRange>=TargetDistance and npayloads>0 then + if cohort:IsOnDuty() and AUFTRAG.CheckMissionCapability({MissionType}, cohort.missiontypes) and cohort.engageRange>=TargetDistance then -- Recruit assets from squadron. - local assets, npayloads=cohort:RecruitAssets(MissionType, npayloads) - - Npayloads[cohort.aircrafttype]=npayloads + local assets, npayloads=cohort:RecruitAssets(MissionType, 999) for _,asset in pairs(assets) do table.insert(Assets, asset) diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 1bf9db20a..a5489e012 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -45,11 +45,9 @@ -- -- === -- --- ![Banner Image](..\Presentations\OPS\Cohort\_Main.png) --- -- # The COHORT Concept -- --- A COHORT is essential part of an BRIGADE and consists of **one** type of aircraft. +-- A COHORT is essential part of a LEGION and consists of **one** unit type. -- -- -- @@ -83,7 +81,7 @@ COHORT.version="0.0.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: A lot! --- TODO: Make general so that PLATOON and SQUADRON can inherit this class. +-- DONE: Make general so that PLATOON and SQUADRON can inherit this class. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -285,7 +283,7 @@ function COHORT:AddMissionCapability(MissionTypes, Performance) for _,missiontype in pairs(MissionTypes) do -- Check not to add the same twice. - if self:CheckMissionCapability(missiontype, self.missiontypes) then + if AUFTRAG.CheckMissionCapability(missiontype, self.missiontypes) then self:E(self.lid.."WARNING: Mission capability already present! No need to add it twice.") -- TODO: update performance. else @@ -736,7 +734,7 @@ function COHORT:CanMission(Mission) end -- Check mission type. WARNING: This assumes that all assets of the cohort can do the same mission types! - if not self:CheckMissionType(Mission.type, self:GetMissionTypes()) then + if not AUFTRAG.CheckMissionType(Mission.type, self:GetMissionTypes()) then self:T(self.lid..string.format("INFO: Cohort cannot do mission type %s (%s, %s)", Mission.type, Mission.name, Mission:GetTargetName())) return false end @@ -780,7 +778,7 @@ function COHORT:CountAssets(InStock, MissionTypes, Attributes) for _,_asset in pairs(self.assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - if MissionTypes==nil or self:CheckMissionCapability(MissionTypes, self.missiontypes) then + if MissionTypes==nil or AUFTRAG.CheckMissionCapability(MissionTypes, self.missiontypes) then if Attributes==nil or self:CheckAttribute(Attributes) then if asset.spawned then if InStock==false or InStock==nil then @@ -806,6 +804,9 @@ end -- @return #number Number of payloads still available after recruiting the assets. function COHORT:RecruitAssets(MissionType, Npayloads) + -- Debug info. + self:T3(self.lid..string.format("Recruiting asset for Mission type=%s", MissionType)) + -- Recruited assets. local assets={} @@ -832,7 +833,7 @@ function COHORT:RecruitAssets(MissionType, Npayloads) self:I(self.lid..string.format("Adding asset on GCICAP mission for an INTERCEPT mission")) table.insert(assets, asset) - elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.ALERT5) and self:CheckMissionCapability(MissionType, asset.payload.capabilities) then + elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.ALERT5) and AUFTRAG.CheckMissionCapability(MissionType, asset.payload.capabilities) then -- Check if the payload of this asset is compatible with the mission. self:I(self.lid..string.format("Adding asset on ALERT 5 mission for %s mission", MissionType)) @@ -856,7 +857,7 @@ function COHORT:RecruitAssets(MissionType, Npayloads) local flightgroup=asset.flightgroup - if flightgroup and flightgroup:IsAlive() then + if flightgroup and flightgroup:IsAlive() and not (flightgroup:IsDead() or flightgroup:IsStopped()) then -- Assume we are ready and check if any condition tells us we are not. local combatready=true @@ -885,7 +886,7 @@ function COHORT:RecruitAssets(MissionType, Npayloads) if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() then combatready=false end - if asset.payload and not self:CheckMissionCapability(MissionType, asset.payload.capabilities) then + if asset.payload and not AUFTRAG.CheckMissionCapability(MissionType, asset.payload.capabilities) then combatready=false end @@ -901,12 +902,6 @@ function COHORT:RecruitAssets(MissionType, Npayloads) end - -- Applies to all opsgroups. - if flightgroup:IsDead() or flightgroup:IsStopped() then - combatready=false - end - - --TODO: Check transport for combat readyness! -- This asset is "combatready". @@ -940,6 +935,8 @@ function COHORT:RecruitAssets(MissionType, Npayloads) end -- not requested check end -- loop over assets + self:T2(self.lid..string.format("Recruited %d assets for Mission type=%s", #assets, MissionType)) + return assets, Npayloads end @@ -988,52 +985,6 @@ function COHORT:IsRepaired(Asset) end - ---- Checks if a mission type is contained in a table of possible types. --- @param #COHORT self --- @param #string MissionType The requested mission type. --- @param #table PossibleTypes A table with possible mission types. --- @return #boolean If true, the requested mission type is part of the possible mission types. -function COHORT:CheckMissionType(MissionType, PossibleTypes) - - if type(PossibleTypes)=="string" then - PossibleTypes={PossibleTypes} - end - - for _,canmission in pairs(PossibleTypes) do - if canmission==MissionType then - return true - end - end - - return false -end - ---- Check if a mission type is contained in a list of possible capabilities. --- @param #COHORT self --- @param #table MissionTypes The requested mission type. Can also be passed as a single mission type `#string`. --- @param #table Capabilities (Optional) A table with possible capabilities `Ops.Auftrag#AUFTRAG.Capability`. Default is capabilities of the cohort. --- @return #boolean If true, the requested mission type is part of the possible mission types. -function COHORT:CheckMissionCapability(MissionTypes, Capabilities) - - if type(MissionTypes)~="table" then - MissionTypes={MissionTypes} - end - - Capabilities=Capabilities or self.missiontypes - - for _,cap in pairs(Capabilities) do - local capability=cap --Ops.Auftrag#AUFTRAG.Capability - for _,MissionType in pairs(MissionTypes) do - if capability.MissionType==MissionType then - return true - end - end - end - - return false -end - --- Check if the cohort attribute matches the given attribute(s). -- @param #COHORT self -- @param #table Attributes The requested attributes. See `WAREHOUSE.Attribute` enum. Can also be passed as a single attribute `#string`. diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 3181cd513..67b539dbe 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -735,35 +735,15 @@ function COMMANDER:RecruitAssets(Mission) for _,_legion in pairs(legions) do local legion=_legion --Ops.Legion#LEGION - - -- Number of payloads in stock per aircraft type. - local Npayloads={} - - -- First get payloads for aircraft types of squadrons. - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - if Npayloads[cohort.aircrafttype]==nil then - local MissionType=Mission.type - if MissionType==AUFTRAG.Type.ALERT5 then - MissionType=Mission.alert5MissionType - end - Npayloads[cohort.aircrafttype]=legion:IsAirwing() and legion:CountPayloadsInStock(MissionType, cohort.aircrafttype, Mission.payloads) or 999 - self:T2(self.lid..string.format("Got N=%d payloads for mission type %s [%s]", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) - end - end -- Loops over cohorts. for _,_cohort in pairs(legion.cohorts) do local cohort=_cohort --Ops.Cohort#COHORT - local npayloads=Npayloads[cohort.aircrafttype] - - if cohort:CanMission(Mission) and npayloads>0 then + if cohort:CanMission(Mission) then -- Recruit assets from squadron. - local assets, npayloads=cohort:RecruitAssets(Mission.type, npayloads) - - Npayloads[cohort.aircrafttype]=npayloads + local assets, npayloads=cohort:RecruitAssets(Mission.type, 999) for _,asset in pairs(assets) do table.insert(Assets, asset) diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 0a3b6e28b..e3eb13807 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -288,7 +288,6 @@ function LEGION:AddMission(Mission) --Mission.opstransport:SetPickupZone(self.spawnzone) --Mission.opstransport:SetEmbarkZone(self.spawnzone) - -- Loop over all defined transport legions. for _,_legion in pairs(Mission.transportLegions) do local legion=_legion --Ops.Legion#LEGION @@ -1240,7 +1239,7 @@ function LEGION:IsAssetOnMission(asset, MissionTypes) local status=mission:GetGroupStatus(asset.flightgroup) -- Only if mission is started or executing. - if (status==AUFTRAG.GroupStatus.STARTED or status==AUFTRAG.GroupStatus.EXECUTING) and self:CheckMissionType(mission.type, MissionTypes) then + if (status==AUFTRAG.GroupStatus.STARTED or status==AUFTRAG.GroupStatus.EXECUTING) and AUFTRAG.CheckMissionType(mission.type, MissionTypes) then return true end @@ -1340,7 +1339,7 @@ function LEGION:CountPayloadsInStock(MissionTypes, UnitTypes, Payloads) for _,MissionType in pairs(MissionTypes) do local specialpayload=_checkPayloads(payload) - local compatible=self:CheckMissionCapability(MissionType, payload.capabilities) + local compatible=AUFTRAG.CheckMissionCapability(MissionType, payload.capabilities) local goforit = specialpayload or (specialpayload==nil and compatible) @@ -1374,7 +1373,7 @@ function LEGION:CountMissionsInQueue(MissionTypes) local mission=_mission --Ops.Auftrag#AUFTRAG -- Check if this mission type is requested. - if mission:IsNotOver() and self:CheckMissionType(mission.type, MissionTypes) then + if mission:IsNotOver() and AUFTRAG.CheckMissionType(mission.type, MissionTypes) then N=N+1 end @@ -1462,7 +1461,7 @@ function LEGION:CountAssetsOnMission(MissionTypes, Cohort) local mission=_mission --Ops.Auftrag#AUFTRAG -- Check if this mission type is requested. - if self:CheckMissionType(mission.type, MissionTypes or AUFTRAG.Type) then + if AUFTRAG.CheckMissionType(mission.type, MissionTypes or AUFTRAG.Type) then for _,_asset in pairs(mission.assets or {}) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem @@ -1504,7 +1503,7 @@ function LEGION:GetAssetsOnMission(MissionTypes) local mission=_mission --Ops.Auftrag#AUFTRAG -- Check if this mission type is requested. - if self:CheckMissionType(mission.type, MissionTypes) then + if AUFTRAG.CheckMissionType(mission.type, MissionTypes) then for _,_asset in pairs(mission.assets or {}) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem @@ -1556,76 +1555,12 @@ function LEGION:GetAircraftTypes(onlyactive, cohorts) return unittypes end ---- Check if assets for a given mission type are available. --- --- OBSOLETE and renamed to _CanMission (to see if it is still used somewhere) --- +--- Recruit assets for a given mission. -- @param #LEGION self -- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @return #boolean If true, enough assets are available. --- @return #table Assets that can do the required mission. -function LEGION:_CanMission(Mission) +-- @return #boolean If `true` enough assets could be recruited. +function LEGION:RecruitAssets(Mission) - -- Assume we CAN and NO assets are available. - local Can=true - local Assets={} - - -- Squadrons for the job. If user assigned to mission or simply all. - local cohorts=Mission.squadrons or self.cohorts - - -- Number of required assets. - local Nassets=Mission:GetRequiredAssets(self) - - -- Get aircraft unit types for the job. - local unittypes=self:GetAircraftTypes(true, cohorts) - - -- Count all payloads in stock. - if self:IsAirwing() then - - -- Number of payloads in stock. - local Npayloads=self:CountPayloadsInStock(Mission.type, unittypes, Mission.payloads) - - if Npayloads#Assets then - self:T(self.lid..string.format("INFO: Not enough assets available! Got %d but need at least %d", #Assets, Mission.nassets)) - Can=false - end - - return Can, Assets end --- Recruit assets for a given mission. @@ -1634,41 +1569,36 @@ end -- @return #boolean If `true` enough assets could be recruited. function LEGION:RecruitAssets(Mission) - -- Number of payloads in stock per aircraft type. - local Npayloads={} + -- The recruited assets. + local Assets={} + + -- Get number of required assets. + local Nassets=Mission:GetRequiredAssets(self) -- Squadrons for the job. If user assigned to mission or simply all. local cohorts=Mission.squadrons or self.cohorts - -- First get payloads for aircraft types of squadrons. - for _,_cohort in pairs(cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - if Npayloads[cohort.aircrafttype]==nil then - local MissionType=Mission.type - if MissionType==AUFTRAG.Type.ALERT5 then - MissionType=Mission.alert5MissionType - end - Npayloads[cohort.aircrafttype]=self:IsAirwing() and self:CountPayloadsInStock(MissionType, cohort.aircrafttype, Mission.payloads) or 999 - self:T2(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) - end + -- Target position. + local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil + + -- Set mission type. + local MissionType=Mission.type + if MissionType==AUFTRAG.Type.ALERT5 and Mission.alert5MissionType then + -- If this is an Alert5 mission, we try to find the assets that are + MissionType=Mission.alert5MissionType end - - -- The recruited assets. - local Assets={} -- Loops over cohorts. for _,_cohort in pairs(cohorts) do local cohort=_cohort --Ops.Cohort#COHORT - local npayloads=Npayloads[cohort.aircrafttype] + -- Check OnDuty, mission type, range and refueling type (if TANKER). + if cohort:CanMission(Mission) then - if cohort:CanMission(Mission) and npayloads>0 then - - -- Recruit assets from squadron. - local assets, npayloads=cohort:RecruitAssets(Mission.type, npayloads) - - Npayloads[cohort.aircrafttype]=npayloads + -- Recruit assets from cohort. + local assets, npayloads=cohort:RecruitAssets(Mission.type, 999) + -- Add assets to the list. for _,asset in pairs(assets) do table.insert(Assets, asset) end @@ -1677,9 +1607,6 @@ function LEGION:RecruitAssets(Mission) end - -- Target position. - local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil - -- Now we have a long list with assets. self:_OptimizeAssetSelection(Assets, Mission.type, TargetVec2, false) @@ -1692,14 +1619,6 @@ function LEGION:RecruitAssets(Mission) -- Only assets that have no payload. Should be only spawned assets! if not asset.payload then - -- Set mission type. - local MissionType=Mission.type - - -- Get a loadout for the actual mission this group is waiting for. - if Mission.type==AUFTRAG.Type.ALERT5 and Mission.alert5MissionType then - MissionType=Mission.alert5MissionType - end - -- Fetch payload for asset. This can be nil! asset.payload=self:FetchPayloadFromStock(asset.unittype, MissionType, Mission.payloads) @@ -1720,9 +1639,6 @@ function LEGION:RecruitAssets(Mission) end - -- Get number of required assets. - local Nassets=Mission:GetRequiredAssets(self) - if #Assets>=Nassets then --- @@ -1774,6 +1690,35 @@ function LEGION:RecruitAssets(Mission) end +--- Recruit assets for a given mission. +-- @param #LEGION self +-- @param #string MissionType Mission type. +-- @param #table Cohorts Cohorts included. +-- @param #table Payloads (Optional) Special payloads. +-- @return #table Table of payloads for each unit type. +function LEGION:_CountPayloads(MissionType, Cohorts, Payloads) + + -- Number of payloads in stock per aircraft type. + local Npayloads={} + + -- First get payloads for aircraft types of squadrons. + for _,_cohort in pairs(Cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + -- We only need that element once. + if Npayloads[cohort.aircrafttype]==nil then + + -- Count number of payloads in stock for the cohort aircraft type. + Npayloads[cohort.aircrafttype]=cohort.legion:IsAirwing() and self:CountPayloadsInStock(MissionType, cohort.aircrafttype, Payloads) or 999 + + -- Debug info. + self:T2(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) + end + end + + return Npayloads +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Transport Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1806,18 +1751,15 @@ function LEGION:RecruitAssetsForTransport(Transport) end - -- Number of payloads in stock per aircraft type. - local Npayloads={} - - -- First get payloads for aircraft types of squadrons. - for _,_cohort in pairs(self.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - if Npayloads[cohort.aircrafttype]==nil then - Npayloads[cohort.aircrafttype]=self:IsAirwing() and self:CountPayloadsInStock(AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype) or 999 - self:T2(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype)) - end - end + -- Target is the deploy zone. + local TargetVec2=Transport:GetDeployZone():GetVec2() + -- Number of payloads in stock per aircraft type. + local Npayloads=self:_CountPayloads(AUFTRAG.Type.OPSTRANSPORT, self.cohorts) + + -- Number of required carriers. + local NreqMin,NreqMax=Transport:GetRequiredCarriers() + -- The recruited assets. local Assets={} @@ -1825,9 +1767,9 @@ function LEGION:RecruitAssetsForTransport(Transport) for _,_cohort in pairs(self.cohorts) do local cohort=_cohort --Ops.Cohort#COHORT - local npayloads=Npayloads[cohort.aircrafttype] + local npayloads=999 --Npayloads[cohort.aircrafttype] - if cohort:IsOnDuty() and npayloads>0 and cohort:CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}) and cohort.cargobayLimit>=weightGroup then + if cohort:IsOnDuty() and npayloads>0 and AUFTRAG.CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}, cohort.missiontypes) and cohort.cargobayLimit>=weightGroup then -- Recruit assets from squadron. local assets, npayloads=cohort:RecruitAssets(AUFTRAG.Type.OPSTRANSPORT, npayloads) @@ -1842,9 +1784,6 @@ function LEGION:RecruitAssetsForTransport(Transport) end - -- Target is the deploy zone. - local TargetVec2=Transport:GetDeployZone():GetVec2() - -- Sort asset list. Best ones come first. self:_OptimizeAssetSelection(Assets, AUFTRAG.Type.OPSTRANSPORT, TargetVec2, false) @@ -1874,9 +1813,6 @@ function LEGION:RecruitAssetsForTransport(Transport) end - -- Number of required carriers. - local NreqMin,NreqMax=Transport:GetRequiredCarriers() - -- Number of assets. At most NreqMax. local Nassets=math.min(#Assets, NreqMax) @@ -2042,43 +1978,6 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Check if a mission type is contained in a list of possible types. --- @param #LEGION self --- @param #string MissionType The requested mission type. --- @param #table PossibleTypes A table with possible mission types. --- @return #boolean If true, the requested mission type is part of the possible mission types. -function LEGION:CheckMissionType(MissionType, PossibleTypes) - - if type(PossibleTypes)=="string" then - PossibleTypes={PossibleTypes} - end - - for _,canmission in pairs(PossibleTypes) do - if canmission==MissionType then - return true - end - end - - return false -end - ---- Check if a mission type is contained in a list of possible capabilities. --- @param #LEGION self --- @param #string MissionType The requested mission type. --- @param #table Capabilities A table with possible capabilities. --- @return #boolean If true, the requested mission type is part of the possible mission types. -function LEGION:CheckMissionCapability(MissionType, Capabilities) - - for _,cap in pairs(Capabilities) do - local capability=cap --Ops.Auftrag#AUFTRAG.Capability - if capability.MissionType==MissionType then - return true - end - end - - return false -end - --- Get payload performance for a given type of misson type. -- @param #LEGION self -- @param Ops.Airwing#AIRWING.Payload Payload The payload table. From bbbca04066afe4641d4488fdf1a46ccf968a0f66 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 18 Sep 2021 00:11:04 +0200 Subject: [PATCH 106/141] OPS Legion - Added new global function to recruit assets. --- Moose Development/Moose/Ops/Chief.lua | 30 +++ Moose Development/Moose/Ops/Commander.lua | 37 ++++ Moose Development/Moose/Ops/Legion.lua | 224 +++++++++++++++++----- 3 files changed, 240 insertions(+), 51 deletions(-) diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 739d29dc3..f7a25c7f8 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -1373,6 +1373,36 @@ end -- @return #table Legions that have recruited assets. -- @return #table Assets that have been recruited from all legions. function CHIEF:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMax) + + -- Cohorts. + local Cohorts={} + for _,_legion in pairs(self.commander.legions) do + local legion=_legion --Ops.Legion#LEGION + -- Loops over cohorts. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end + + -- Target position. + local TargetVec2=Target:GetVec2() + + -- Recruite assets. + local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2) + + return recruited, assets, legions + +end + +--- Recruit assets for a given mission. OLD! +-- @param #CHIEF self +-- @param Ops.Target#TARGET Target The target. +-- @param #string MissionType Mission Type. +-- @return #boolean If `true` enough assets could be recruited. +-- @return #table Legions that have recruited assets. +-- @return #table Assets that have been recruited from all legions. +function CHIEF:_RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMax) -- The recruited assets. local Assets={} diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 67b539dbe..4f2e26467 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -714,6 +714,43 @@ function COMMANDER:CheckMissionQueue() end +--- Recruit assets for a given mission. +-- @param #COMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return #boolean If `true` enough assets could be recruited. +-- @return #table Recruited assets. +-- @return #table Legions that have recruited assets. +function COMMANDER:RecruitAssetsForMission(Mission) + + -- Cohorts. + local Cohorts=Mission.squadrons + if not Cohorts then + Cohorts={} + for _,_legion in pairs(Mission.mylegions or self.legions) do + local legion=_legion --Ops.Legion#LEGION + -- Loops over cohorts. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end + end + + -- Number of required assets. + local NreqMin=Mission:GetRequiredAssets() + local NreqMax=NreqMin + + -- Target position. + local TargetVec2=Mission:GetTargetVec2() + + -- Special payloads. + local Payloads=Mission.payloads + + -- Recruite assets. + local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads, Mission.engageRange, Mission.refuelSystem, nil) + + return recruited, assets, legions +end --- Recruit assets for a given mission. -- @param #COMMANDER self diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index e3eb13807..122a43347 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -472,10 +472,16 @@ function LEGION:_GetNextMission() if mission:IsQueued(self) and mission:IsReadyToGo() and (mission.importance==nil or mission.importance<=vip) then -- Recruit best assets for the job. - local recruited=self:RecruitAssets(mission) - + local recruited, assets, legions=self:RecruitAssetsForMission(mission) + -- Did we find enough assets? if recruited then + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.isReserved=true + mission:AddAsset(asset) + end + return mission end @@ -1559,8 +1565,19 @@ end -- @param #LEGION self -- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @return #boolean If `true` enough assets could be recruited. -function LEGION:RecruitAssets(Mission) +-- @return #table Recruited assets. +-- @return #table Legions of recruited assets. +function LEGION:RecruitAssetsForMission(Mission) + local NreqMin=Mission:GetRequiredAssets() + local NreqMax=NreqMin + + local TargetVec2=Mission:GetTargetVec2() + local Payloads=Mission.payloads + + local recruited, assets, legions=LEGION.RecruitCohortAssets(self.cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads, Mission.engageRange, Mission.refuelSystem, nil) + + return recruited, assets, legions end --- Recruit assets for a given mission. @@ -1868,17 +1885,153 @@ function LEGION:RecruitAssetsForTransport(Transport) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Optimization Functions +-- Recruiting and Optimization Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Recruit assets from Cohorts for the given parameters. +-- @param #table Cohorts Cohorts included. +-- @param #string MissionTypeRecruit Mission type for recruiting the cohort assets. +-- @param #string MissionTypeOpt Mission type for which the assets are optimized. Default is the same as `MissionTypeRecruit`. +-- @param #number NreqMin Minimum number of required assets. +-- @param #number NreqMax Maximum number of required assets. +-- @param DCS#Vec2 TargetVec2 Target position as 2D vector. +-- @param #table Payloads Special payloads. +-- @param #number RangeMax Max range in meters. +-- @param #number RefuelSystem Refuelsystem. +-- @param #number CargoWeight Cargo weight for recruiting transport carriers. +-- @return #boolean If `true` enough assets could be recruited. +-- @return #table Recruited assets. +-- @return #table Legions of recruited assets. +function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, NreqMin, NreqMax, TargetVec2, Payloads, RangeMax, RefuelSystem, CargoWeight) + + -- The recruited assets. + local Assets={} + + -- Legions of recruited assets. + local Legions={} + + if MissionTypeOpt==nil then + MissionTypeOpt=MissionTypeRecruit + end + + -- Loops over cohorts. + for _,_cohort in pairs(Cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + -- Distance to target. + local TargetDistance=TargetVec2 and UTILS.VecDist2D(TargetVec2, cohort.legion:GetVec2()) or 0 + + -- Is in range? + local InRange=(RangeMax and math.max(RangeMax, cohort.engageRange) or cohort.engageRange) >= TargetDistance + + -- Has the requested refuelsystem? + local Refuel=RefuelSystem and RefuelSystem==cohort.tankerSystem or true + + -- Is capable of the mission type? + local Capable=AUFTRAG.CheckMissionCapability({MissionTypeRecruit}, cohort.missiontypes) + + -- Can carry the cargo? + local CanCarry=CargoWeight and cohort.cargobayLimit>=CargoWeight or true + + -- Check OnDuty, capable, in range and refueling type (if TANKER). + if cohort:IsOnDuty() and Capable and InRange and Refuel and CanCarry then + + -- Recruit assets from cohort. + local assets, npayloads=cohort:RecruitAssets(MissionTypeRecruit, 999) + + -- Add assets to the list. + for _,asset in pairs(assets) do + table.insert(Assets, asset) + end + + end + + end + + -- Now we have a long list with assets. + LEGION._OptimizeAssetSelection(Assets, MissionTypeOpt, TargetVec2, false) + + + -- Get payloads for air assets. + for _,_asset in pairs(Assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Only assets that have no payload. Should be only spawned assets! + if asset.legion:IsAirwing() and not asset.payload then + + -- Fetch payload for asset. This can be nil! + asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, MissionTypeOpt, Payloads) + + end + end + + -- Remove assets that dont have a payload. + for i=#Assets,1,-1 do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.payload then + table.remove(Assets, i) + end + end + + -- Now find the best asset for the given payloads. + LEGION._OptimizeAssetSelection(Assets, MissionTypeOpt, TargetVec2, true) + + -- Number of assets. At most NreqMax. + local Nassets=math.min(#Assets, NreqMax) + + if #Assets>=NreqMin then + + --- + -- Found enough assets + --- + + -- Add assets to mission. + for i=1,Nassets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + Legions[asset.legion.alias]=asset.legion + end + + -- Return payloads of not needed assets. + for i=Nassets+1,#Assets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.spawned then + asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + asset.legion:ReturnPayloadFromAsset(asset) + end + end + + -- Found enough assets. + return true, Assets, Legions + else + + --- + -- NOT enough assets + --- + + -- Return payloads of assets. + for i=1,#Assets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion:IsAirwing() and not asset.spawned then + asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + asset.legion:ReturnPayloadFromAsset(asset) + end + end + + -- Not enough assets found. + return false, {}, {} + end + + return false, {}, {} +end + + --- Calculate the mission score of an asset. --- @param #LEGION self -- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset -- @param #string MissionType Mission type for which the best assets are desired. -- @param DCS#Vec2 TargetVec2 Target 2D vector. -- @param #boolean IncludePayload If `true`, include the payload in the calulation if the asset has one attached. -- @return #number Mission score. -function LEGION:CalculateAssetMissionScore(asset, MissionType, TargetVec2, IncludePayload) +function LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, IncludePayload) -- Mission score. local score=0 @@ -1898,8 +2051,18 @@ function LEGION:CalculateAssetMissionScore(asset, MissionType, TargetVec2, Inclu score=score+asset.cohort:GetMissionPeformance(MissionType) -- Add payload performance to score. + local function scorePayload(Payload, MissionType) + for _,Capability in pairs(Payload.capabilities) do + local capability=Capability --Ops.Auftrag#AUFTRAG.Capability + if capability.MissionType==MissionType then + return capability.Performance + end + end + return 0 + end + if IncludePayload and asset.payload then - score=score+LEGION.GetPayloadPeformance(self, asset.payload, MissionType) + score=score+scorePayload(asset.payload, MissionType) end -- Origin: We take the OPSGROUP position or the one of the legion. @@ -1920,7 +2083,6 @@ function LEGION:CalculateAssetMissionScore(asset, MissionType, TargetVec2, Inclu -- Intercepts need to be carried out quickly. We prefer spawned assets. if MissionType==AUFTRAG.Type.INTERCEPT then if asset.spawned then - self:T(self.lid.."Adding 25 to asset because it is spawned") score=score+25 end end @@ -1941,17 +2103,16 @@ function LEGION:CalculateAssetMissionScore(asset, MissionType, TargetVec2, Inclu end --- Optimize chosen assets for the mission at hand. --- @param #LEGION self -- @param #table assets Table of (unoptimized) assets. -- @param #string MissionType Mission type. -- @param DCS#Vec2 TargetVec2 Target position as 2D vector. -- @param #boolean IncludePayload If `true`, include the payload in the calulation if the asset has one attached. -function LEGION:_OptimizeAssetSelection(assets, MissionType, TargetVec2, IncludePayload) +function LEGION._OptimizeAssetSelection(assets, MissionType, TargetVec2, IncludePayload) -- Calculate the mission score of all assets. for _,_asset in pairs(assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.score=LEGION.CalculateAssetMissionScore(self, asset, MissionType, TargetVec2, IncludePayload) + asset.score=LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, IncludePayload) end --- Sort assets wrt to their mission score. Higher is better. @@ -1970,7 +2131,7 @@ function LEGION:_OptimizeAssetSelection(assets, MissionType, TargetVec2, Include text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) asset.score=nil end - self:T2(self.lid..text) + env.info(text) end @@ -1978,45 +2139,6 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Get payload performance for a given type of misson type. --- @param #LEGION self --- @param Ops.Airwing#AIRWING.Payload Payload The payload table. --- @param #string MissionType Type of mission. --- @return #number Performance or -1. -function LEGION:GetPayloadPeformance(Payload, MissionType) - - if Payload then - - for _,Capability in pairs(Payload.capabilities) do - local capability=Capability --Ops.Auftrag#AUFTRAG.Capability - if capability.MissionType==MissionType then - return capability.Performance - end - end - - else - self:E(self.lid.."ERROR: Payload is nil!") - end - - return -1 -end - ---- Get mission types a payload can perform. --- @param #LEGION self --- @param Ops.Airwing#AIRWING.Payload Payload The payload table. --- @return #table Mission types. -function LEGION:GetPayloadMissionTypes(Payload) - - local missiontypes={} - - for _,Capability in pairs(Payload.capabilities) do - local capability=Capability --Ops.Auftrag#AUFTRAG.Capability - table.insert(missiontypes, capability.MissionType) - end - - return missiontypes -end - --- Returns the mission for a given mission ID (Autragsnummer). -- @param #LEGION self -- @param #number mid Mission ID (Auftragsnummer). From 4dfdb99731a434baf218091b1e36be11ab023bb1 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 18 Sep 2021 18:09:41 +0200 Subject: [PATCH 107/141] OPS - Recruit assets with new function (LEGION, COMMANDER, CHIEF) --- Moose Development/Moose/Ops/Chief.lua | 4 +- Moose Development/Moose/Ops/Commander.lua | 73 ++++++++++++++++++++- Moose Development/Moose/Ops/Legion.lua | 78 +++++++++++++++++++++-- 3 files changed, 145 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index f7a25c7f8..1d2a40039 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -1061,7 +1061,7 @@ function CHIEF:CheckTargetQueue() self:I(self.lid..string.format("Recruiting assets for mission type %s [performance=%d] of target %s", mp.MissionType, mp.Performance, target:GetName())) -- Recruit assets. - local recruited, legions, assets=self:RecruitAssetsForTarget(target, mp.MissionType, NassetsMin, NassetsMax) + local recruited, assets, legions=self:RecruitAssetsForTarget(target, mp.MissionType, NassetsMin, NassetsMax) if recruited then @@ -1370,8 +1370,8 @@ end -- @param Ops.Target#TARGET Target The target. -- @param #string MissionType Mission Type. -- @return #boolean If `true` enough assets could be recruited. --- @return #table Legions that have recruited assets. -- @return #table Assets that have been recruited from all legions. +-- @return #table Legions that have recruited assets. function CHIEF:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMax) -- Cohorts. diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 4f2e26467..e88f34f53 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -683,9 +683,16 @@ function COMMANDER:CheckMissionQueue() --- -- Recruite assets from legions. - local recruited, legions=self:RecruitAssets(mission) + local recruited, assets, legions=self:RecruitAssetsForMission(mission) if recruited then + + -- Add asset to transport. + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.isReserved=true + mission:AddAsset(asset) + end for _,_legion in pairs(legions) do local legion=_legion --Ops.Legion#LEGION @@ -935,9 +942,16 @@ function COMMANDER:CheckTransportQueue() --- -- Recruite assets from legions. - local recruited, legions=self:RecruitAssetsForTransport(transport) + local recruited, assets, legions=self:RecruitAssetsForTransport(transport) if recruited then + + -- Add asset to transport. + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.isReserved=true + transport:AddAsset(asset) + end for _,_legion in pairs(legions) do local legion=_legion --Ops.Legion#LEGION @@ -966,12 +980,65 @@ function COMMANDER:CheckTransportQueue() end +--- Recruit assets for a given OPS transport. +-- @param #COMMANDER self +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The OPS transport. +-- @return #boolean If `true`, enough assets could be recruited. +-- @return #table Recruited assets. +-- @return #table Legions that have recruited assets. +function COMMANDER:RecruitAssetsForTransport(Transport) + + -- Get all undelivered cargo ops groups. + local cargoOpsGroups=Transport:GetCargoOpsGroups(false) + + local weightGroup=0 + + -- At least one group should be spawned. + if #cargoOpsGroups>0 then + + -- Calculate the max weight so we know which cohorts can provide carriers. + for _,_opsgroup in pairs(cargoOpsGroups) do + local opsgroup=_opsgroup --Ops.OpsGroup#OPSGROUP + local weight=opsgroup:GetWeightTotal() + if weight>weightGroup then + weightGroup=weight + end + end + else + -- No cargo groups! + return false + end + + -- Cohorts. + local Cohorts={} + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION + -- Loops over cohorts. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end + + + -- Target is the deploy zone. + local TargetVec2=Transport:GetDeployZone():GetVec2() + + -- Number of required carriers. + local NreqMin,NreqMax=Transport:GetRequiredCarriers() + + -- Recruit assets and legions. + local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.OPSTRANSPORT, nil, NreqMin, NreqMax, TargetVec2, nil, nil, nil, weightGroup) + + return recruited, assets, legions +end + --- Recruit assets for a given transport. -- @param #COMMANDER self -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. -- @return #boolean If `true`, enough assets could be recruited. -- @return #table Legions that have recruited assets. -function COMMANDER:RecruitAssetsForTransport(Transport) +function COMMANDER:_RecruitAssetsForTransport(Transport) -- Get all undelivered cargo ops groups. local cargoOpsGroups=Transport:GetCargoOpsGroups(false) diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 122a43347..4a8e4726c 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -504,20 +504,44 @@ function LEGION:_GetNextTransport() return nil end - --TODO: Sort transports wrt to prio and importance. See mission sorting! + -- Sort results table wrt prio and start time. + local function _sort(a, b) + local taskA=a --Ops.Auftrag#AUFTRAG + local taskB=b --Ops.Auftrag#AUFTRAG + return (taskA.prio0 then + + -- Calculate the max weight so we know which cohorts can provide carriers. + for _,_opsgroup in pairs(cargoOpsGroups) do + local opsgroup=_opsgroup --Ops.OpsGroup#OPSGROUP + local weight=opsgroup:GetWeightTotal() + if weight>weightGroup then + weightGroup=weight + end + end + else + -- No cargo groups! + return false + end + + + -- Target is the deploy zone. + local TargetVec2=Transport:GetDeployZone():GetVec2() + + -- Number of required carriers. + local NreqMin,NreqMax=Transport:GetRequiredCarriers() + + + -- Recruit assets and legions. + local recruited, assets, legions=LEGION.RecruitCohortAssets(self.cohorts, AUFTRAG.Type.OPSTRANSPORT, nil, NreqMin, NreqMax, TargetVec2, nil, nil, nil, weightGroup) + + return recruited, assets, legions +end + + --- Recruit assets for a given mission. -- @param #LEGION self -- @param Ops.Auftrag#AUFTRAG Mission The mission. @@ -1744,7 +1812,7 @@ end -- @param #LEGION self -- @param Ops.OpsTransport#OPSTRANSPORT Transport The OPS transport. -- @return #boolean If `true`, enough assets could be recruited. -function LEGION:RecruitAssetsForTransport(Transport) +function LEGION:_RecruitAssetsForTransport(Transport) -- Get all undelivered cargo ops groups. local cargoOpsGroups=Transport:GetCargoOpsGroups(false) From 09015449cd10e93b158b10ad05a063164807dc84 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 18 Sep 2021 18:23:22 +0200 Subject: [PATCH 108/141] OPS Recruting - Removed obsolete recruiting functions from code --- Moose Development/Moose/Ops/Chief.lua | 130 +------- Moose Development/Moose/Ops/Commander.lua | 306 +------------------ Moose Development/Moose/Ops/Legion.lua | 344 +++------------------- 3 files changed, 45 insertions(+), 735 deletions(-) diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 1d2a40039..d1019de85 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -1070,7 +1070,9 @@ function CHIEF:CheckTargetQueue() -- Add asset to mission. if mission then - for _,asset in pairs(assets) do + for _,_asset in pairs(assets) do + local asset=_asset + asset.isReserved=true mission:AddAsset(asset) end Legions=legions @@ -1395,132 +1397,6 @@ function CHIEF:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMa end ---- Recruit assets for a given mission. OLD! --- @param #CHIEF self --- @param Ops.Target#TARGET Target The target. --- @param #string MissionType Mission Type. --- @return #boolean If `true` enough assets could be recruited. --- @return #table Legions that have recruited assets. --- @return #table Assets that have been recruited from all legions. -function CHIEF:_RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMax) - - -- The recruited assets. - local Assets={} - - -- Legions which have the best assets for the Mission. - local Legions={} - - -- Target vector. - local TargetVec2=Target:GetVec2() - - for _,_legion in pairs(self.commander.legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Distance to target. - local TargetDistance=Target:GetCoordinate():Get2DDistance(legion:GetCoordinate()) - - -- Loops over cohorts. - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - - if cohort:IsOnDuty() and AUFTRAG.CheckMissionCapability({MissionType}, cohort.missiontypes) and cohort.engageRange>=TargetDistance then - - -- Recruit assets from squadron. - local assets, npayloads=cohort:RecruitAssets(MissionType, 999) - - for _,asset in pairs(assets) do - table.insert(Assets, asset) - end - - end - - end - - end - - -- Now we have a long list with assets. - LEGION._OptimizeAssetSelection(self, Assets, MissionType, TargetVec2, false) - - for _,_asset in pairs(Assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - if asset.legion:IsAirwing() then - - -- Only assets that have no payload. Should be only spawned assets! - if not asset.payload then - - -- Fetch payload for asset. This can be nil! - asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, MissionType) - - end - - end - - end - - -- Remove assets that dont have a payload. - for i=#Assets,1,-1 do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.legion:IsAirwing() and not asset.payload then - self:T3(self.lid..string.format("Remove asset %s with no payload", tostring(asset.spawngroupname))) - table.remove(Assets, i) - end - end - - -- Now find the best asset for the given payloads. - LEGION._OptimizeAssetSelection(self, Assets, MissionType, TargetVec2, true) - - -- Number of assets. At most NreqMax. - local Nassets=math.min(#Assets, NassetsMax) - - if #Assets>=Nassets then - - --- - -- Found enough assets - --- - - -- Get Legions of assets and put into table. - for i=1,Nassets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - Legions[asset.legion.alias]=asset.legion - end - - - -- Return payloads and remove not needed assets. - for i=#Assets,Nassets+1,-1 do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.legion:IsAirwing() and not asset.spawned then - self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) - asset.legion:ReturnPayloadFromAsset(asset) - end - table.remove(Assets, i) - end - - -- Found enough assets. - return true, Legions, Assets - else - - --- - -- NOT enough assets - --- - - -- Return payloads of assets. - - for i=1,#Assets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.legion:IsAirwing() and not asset.spawned then - self:T2(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) - asset.legion:ReturnPayloadFromAsset(asset) - end - end - - -- Not enough assets found. - return false, {}, {} - end - - return nil, {}, {} -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index e88f34f53..055888f08 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -687,13 +687,14 @@ function COMMANDER:CheckMissionQueue() if recruited then - -- Add asset to transport. + -- Add asset to mission. for _,_asset in pairs(assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem asset.isReserved=true mission:AddAsset(asset) end + -- Assign mission to legion(s). for _,_legion in pairs(legions) do local legion=_legion --Ops.Legion#LEGION @@ -759,141 +760,6 @@ function COMMANDER:RecruitAssetsForMission(Mission) return recruited, assets, legions end ---- Recruit assets for a given mission. --- @param #COMMANDER self --- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @return #boolean If `true` enough assets could be recruited. --- @return #table Legions that have recruited assets. -function COMMANDER:RecruitAssets(Mission) - - -- The recruited assets. - local Assets={} - - -- Legions we consider for selecting assets. - local legions=Mission.mylegions or self.legions - - --TODO: Setting of Mission.squadrons (cohorts) will not work here! - - -- Legions which have the best assets for the Mission. - local Legions={} - - for _,_legion in pairs(legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Loops over cohorts. - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - - if cohort:CanMission(Mission) then - - -- Recruit assets from squadron. - local assets, npayloads=cohort:RecruitAssets(Mission.type, 999) - - for _,asset in pairs(assets) do - table.insert(Assets, asset) - end - - end - - end - - end - - -- Target position. - local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil - - -- Now we have a long list with assets. - LEGION._OptimizeAssetSelection(self, Assets, Mission.type, TargetVec2, false) - - for _,_asset in pairs(Assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - if asset.legion:IsAirwing() then - - -- Only assets that have no payload. Should be only spawned assets! - if not asset.payload then - - -- Set mission type. - local MissionType=Mission.type - - -- Get a loadout for the actual mission this group is waiting for. - if Mission.type==AUFTRAG.Type.ALERT5 and Mission.alert5MissionType then - MissionType=Mission.alert5MissionType - end - - -- Fetch payload for asset. This can be nil! - asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, MissionType, Mission.payloads) - - end - - end - - end - - -- Remove assets that dont have a payload. - for i=#Assets,1,-1 do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.legion:IsAirwing() and not asset.payload then - self:T3(self.lid..string.format("Remove asset %s with no payload", tostring(asset.spawngroupname))) - table.remove(Assets, i) - end - end - - -- Now find the best asset for the given payloads. - LEGION._OptimizeAssetSelection(self, Assets, Mission.type, TargetVec2, true) - - -- Get number of required assets. - local Nassets=Mission:GetRequiredAssets(self) - - if #Assets>=Nassets then - - --- - -- Found enough assets - --- - - -- Add assets to mission. - for i=1,Nassets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - asset.isReserved=true - Mission:AddAsset(asset) - Legions[asset.legion.alias]=asset.legion - end - - - -- Return payloads of not needed assets. - for i=Nassets+1,#Assets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.legion:IsAirwing() and not asset.spawned then - self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) - asset.legion:ReturnPayloadFromAsset(asset) - end - end - - -- Found enough assets. - return true, Legions - else - - --- - -- NOT enough assets - --- - - -- Return payloads of assets. - - for i=1,#Assets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.legion:IsAirwing() and not asset.spawned then - self:T2(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) - asset.legion:ReturnPayloadFromAsset(asset) - end - end - - -- Not enough assets found. - return false, {} - end - - return nil, {} -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Transport Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -953,6 +819,7 @@ function COMMANDER:CheckTransportQueue() transport:AddAsset(asset) end + -- Assign transport to legion(s). for _,_legion in pairs(legions) do local legion=_legion --Ops.Legion#LEGION @@ -1033,173 +900,6 @@ function COMMANDER:RecruitAssetsForTransport(Transport) return recruited, assets, legions end ---- Recruit assets for a given transport. --- @param #COMMANDER self --- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. --- @return #boolean If `true`, enough assets could be recruited. --- @return #table Legions that have recruited assets. -function COMMANDER:_RecruitAssetsForTransport(Transport) - - -- Get all undelivered cargo ops groups. - local cargoOpsGroups=Transport:GetCargoOpsGroups(false) - - local weightGroup=0 - - -- At least one group should be spawned. - if #cargoOpsGroups>0 then - - -- Calculate the max weight so we know which cohorts can provide carriers. - for _,_opsgroup in pairs(cargoOpsGroups) do - local opsgroup=_opsgroup --Ops.OpsGroup#OPSGROUP - local weight=opsgroup:GetWeightTotal() - if weight>weightGroup then - weightGroup=weight - end - end - - else - -- No cargo groups! - return false, {} - end - - -- The recruited assets. - local Assets={} - - -- Legions we consider for selecting assets. - local legions=self.legions - - --TODO: Setting of Mission.squadrons (cohorts) will not work here! - - -- Legions which have the best assets for the Mission. - local Legions={} - - for _,_legion in pairs(legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Number of payloads in stock per aircraft type. - local Npayloads={} - - -- First get payloads for aircraft types of squadrons. - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - if Npayloads[cohort.aircrafttype]==nil then - Npayloads[cohort.aircrafttype]=legion:IsAirwing() and legion:CountPayloadsInStock(AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype) or 999 - self:T2(self.lid..string.format("Got N=%d payloads for mission type %s [%s]", Npayloads[cohort.aircrafttype], AUFTRAG.Type.OPSTRANSPORT, cohort.aircrafttype)) - end - end - - -- Loops over cohorts. - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - - local npayloads=Npayloads[cohort.aircrafttype] - - if cohort:IsOnDuty() and npayloads>0 and cohort:CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}) and cohort.cargobayLimit>=weightGroup then - - -- Recruit assets from squadron. - local assets, npayloads=cohort:RecruitAssets(AUFTRAG.Type.OPSTRANSPORT, npayloads) - - Npayloads[cohort.aircrafttype]=npayloads - - for _,asset in pairs(assets) do - table.insert(Assets, asset) - end - - end - - end - - end - - -- Target position. - local TargetVec2=Transport:GetDeployZone():GetVec2() - - -- Now we have a long list with assets. - LEGION._OptimizeAssetSelection(self, Assets, AUFTRAG.Type.OPSTRANSPORT, TargetVec2, false) - - - for _,_asset in pairs(Assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - if asset.legion:IsAirwing() then - - -- Only assets that have no payload. Should be only spawned assets! - if not asset.payload then - - -- Fetch payload for asset. This can be nil! - asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype, AUFTRAG.Type.OPSTRANSPORT) - - end - - end - - end - - -- Remove assets that dont have a payload. - for i=#Assets,1,-1 do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.legion:IsAirwing() and not asset.payload then - table.remove(Assets, i) - end - end - - - -- Number of required carriers. - local NreqMin,NreqMax=Transport:GetRequiredCarriers() - - -- Number of assets. At most NreqMax. - local Nassets=math.min(#Assets, NreqMax) - - if Nassets>=NreqMin then - - --- - -- Found enough assets - --- - - -- Add assets to transport. - for i=1,Nassets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - asset.isReserved=true - Transport:AddAsset(asset) - Legions[asset.legion.alias]=asset.legion - end - - - -- Return payloads of not needed assets. - for i=Nassets+1,#Assets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.legion:IsAirwing() and not asset.spawned then - self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) - asset.legion:ReturnPayloadFromAsset(asset) - end - end - - -- Found enough assets. - return true, Legions - else - - --- - -- NOT enough assets - --- - - -- Return payloads of assets. - if self:IsAirwing() then - for i=1,#Assets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.legion:IsAirwing() and not asset.spawned then - self:T2(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) - asset.legion:ReturnPayloadFromAsset(asset) - end - end - end - - -- Not enough assets found. - return false, {} - end - - return nil, {} -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Resources ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 4a8e4726c..843c34b3d 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -1585,6 +1585,39 @@ function LEGION:GetAircraftTypes(onlyactive, cohorts) return unittypes end +--- Count payloads of all cohorts for all unit types. +-- @param #LEGION self +-- @param #string MissionType Mission type. +-- @param #table Cohorts Cohorts included. +-- @param #table Payloads (Optional) Special payloads. +-- @return #table Table of payloads for each unit type. +function LEGION:_CountPayloads(MissionType, Cohorts, Payloads) + + -- Number of payloads in stock per aircraft type. + local Npayloads={} + + -- First get payloads for aircraft types of squadrons. + for _,_cohort in pairs(Cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + + -- We only need that element once. + if Npayloads[cohort.aircrafttype]==nil then + + -- Count number of payloads in stock for the cohort aircraft type. + Npayloads[cohort.aircrafttype]=cohort.legion:IsAirwing() and self:CountPayloadsInStock(MissionType, cohort.aircrafttype, Payloads) or 999 + + -- Debug info. + self:T2(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) + end + end + + return Npayloads +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Recruiting Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Recruit assets for a given mission. -- @param #LEGION self -- @param Ops.Auftrag#AUFTRAG Mission The mission. @@ -1593,14 +1626,20 @@ end -- @return #table Legions of recruited assets. function LEGION:RecruitAssetsForMission(Mission) + -- Get required assets. local NreqMin=Mission:GetRequiredAssets() local NreqMax=NreqMin + -- Target position vector. local TargetVec2=Mission:GetTargetVec2() + + -- Payloads. local Payloads=Mission.payloads + -- Cohorts. local Cohorts=Mission.squadrons or self.cohorts + -- Recuit assets. local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads, Mission.engageRange, Mission.refuelSystem, nil) return recruited, assets, legions @@ -1647,311 +1686,6 @@ function LEGION:RecruitAssetsForTransport(Transport) return recruited, assets, legions end - ---- Recruit assets for a given mission. --- @param #LEGION self --- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @return #boolean If `true` enough assets could be recruited. -function LEGION:RecruitAssets(Mission) - - -- The recruited assets. - local Assets={} - - -- Get number of required assets. - local Nassets=Mission:GetRequiredAssets(self) - - -- Squadrons for the job. If user assigned to mission or simply all. - local cohorts=Mission.squadrons or self.cohorts - - -- Target position. - local TargetVec2=Mission.type~=AUFTRAG.Type.ALERT5 and Mission:GetTargetVec2() or nil - - -- Set mission type. - local MissionType=Mission.type - if MissionType==AUFTRAG.Type.ALERT5 and Mission.alert5MissionType then - -- If this is an Alert5 mission, we try to find the assets that are - MissionType=Mission.alert5MissionType - end - - -- Loops over cohorts. - for _,_cohort in pairs(cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - - -- Check OnDuty, mission type, range and refueling type (if TANKER). - if cohort:CanMission(Mission) then - - -- Recruit assets from cohort. - local assets, npayloads=cohort:RecruitAssets(Mission.type, 999) - - -- Add assets to the list. - for _,asset in pairs(assets) do - table.insert(Assets, asset) - end - - end - - end - - -- Now we have a long list with assets. - self:_OptimizeAssetSelection(Assets, Mission.type, TargetVec2, false) - - -- If airwing, get the best payload available. - if self:IsAirwing() then - - for _,_asset in pairs(Assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Only assets that have no payload. Should be only spawned assets! - if not asset.payload then - - -- Fetch payload for asset. This can be nil! - asset.payload=self:FetchPayloadFromStock(asset.unittype, MissionType, Mission.payloads) - - end - - end - - -- Remove assets that dont have a payload. - for i=#Assets,1,-1 do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if not asset.payload then - table.remove(Assets, i) - end - end - - -- Now find the best asset for the given payloads. - self:_OptimizeAssetSelection(Assets, Mission.type, TargetVec2, true) - - end - - if #Assets>=Nassets then - - --- - -- Found enough assets - --- - - -- Add assets to mission. - for i=1,Nassets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - self:T(self.lid..string.format("Adding asset %s to mission %s [%s]", asset.spawngroupname, Mission.name, Mission.type)) - Mission:AddAsset(asset) - end - - if self:IsAirwing() then - - -- Return payloads of not needed assets. - for i=Nassets+1,#Assets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if not asset.spawned then - self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) - self:ReturnPayloadFromAsset(asset) - end - end - - end - - -- Found enough assets. - return true - else - - --- - -- NOT enough assets - --- - - -- Return payloads of assets. - if self:IsAirwing() then - for i=1,#Assets do - local asset=Assets[i] - if not asset.spawned then - self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) - self:ReturnPayloadFromAsset(asset) - end - end - end - - -- Not enough assets found. - return false - end - -end - ---- Recruit assets for a given mission. --- @param #LEGION self --- @param #string MissionType Mission type. --- @param #table Cohorts Cohorts included. --- @param #table Payloads (Optional) Special payloads. --- @return #table Table of payloads for each unit type. -function LEGION:_CountPayloads(MissionType, Cohorts, Payloads) - - -- Number of payloads in stock per aircraft type. - local Npayloads={} - - -- First get payloads for aircraft types of squadrons. - for _,_cohort in pairs(Cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - - -- We only need that element once. - if Npayloads[cohort.aircrafttype]==nil then - - -- Count number of payloads in stock for the cohort aircraft type. - Npayloads[cohort.aircrafttype]=cohort.legion:IsAirwing() and self:CountPayloadsInStock(MissionType, cohort.aircrafttype, Payloads) or 999 - - -- Debug info. - self:T2(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s", Npayloads[cohort.aircrafttype], MissionType, cohort.aircrafttype)) - end - end - - return Npayloads -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Transport Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Recruit assets for a given OPS transport. --- @param #LEGION self --- @param Ops.OpsTransport#OPSTRANSPORT Transport The OPS transport. --- @return #boolean If `true`, enough assets could be recruited. -function LEGION:_RecruitAssetsForTransport(Transport) - - -- Get all undelivered cargo ops groups. - local cargoOpsGroups=Transport:GetCargoOpsGroups(false) - - local weightGroup=0 - - -- At least one group should be spawned. - if #cargoOpsGroups>0 then - - -- Calculate the max weight so we know which cohorts can provide carriers. - for _,_opsgroup in pairs(cargoOpsGroups) do - local opsgroup=_opsgroup --Ops.OpsGroup#OPSGROUP - local weight=opsgroup:GetWeightTotal() - if weight>weightGroup then - weightGroup=weight - end - end - else - -- No cargo groups! - return false - end - - - -- Target is the deploy zone. - local TargetVec2=Transport:GetDeployZone():GetVec2() - - -- Number of payloads in stock per aircraft type. - local Npayloads=self:_CountPayloads(AUFTRAG.Type.OPSTRANSPORT, self.cohorts) - - -- Number of required carriers. - local NreqMin,NreqMax=Transport:GetRequiredCarriers() - - -- The recruited assets. - local Assets={} - - -- Loops over cohorts. - for _,_cohort in pairs(self.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - - local npayloads=999 --Npayloads[cohort.aircrafttype] - - if cohort:IsOnDuty() and npayloads>0 and AUFTRAG.CheckMissionCapability({AUFTRAG.Type.OPSTRANSPORT}, cohort.missiontypes) and cohort.cargobayLimit>=weightGroup then - - -- Recruit assets from squadron. - local assets, npayloads=cohort:RecruitAssets(AUFTRAG.Type.OPSTRANSPORT, npayloads) - - Npayloads[cohort.aircrafttype]=npayloads - - for _,asset in pairs(assets) do - table.insert(Assets, asset) - end - - end - - end - - -- Sort asset list. Best ones come first. - self:_OptimizeAssetSelection(Assets, AUFTRAG.Type.OPSTRANSPORT, TargetVec2, false) - - -- If airwing, get the best payload available. - if self:IsAirwing() then - - for _,_asset in pairs(Assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Only assets that have no payload. Should be only spawned assets! - if not asset.payload then - - -- Fetch payload for asset. This can be nil! - asset.payload=self:FetchPayloadFromStock(asset.unittype, AUFTRAG.Type.OPSTRANSPORT) - - end - - end - - -- Remove assets that dont have a payload. - for i=#Assets,1,-1 do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if not asset.payload then - table.remove(Assets, i) - end - end - - end - - -- Number of assets. At most NreqMax. - local Nassets=math.min(#Assets, NreqMax) - - if Nassets>=NreqMin then - - --- - -- Found enough assets - --- - - -- Add assets to mission. - for i=1,Nassets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - asset.isReserved=true - Transport:AddAsset(asset) - end - - if self:IsAirwing() then - - -- Return payloads of not needed assets. - for i=Nassets+1,#Assets do - local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem - if not asset.spawned then - self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) - self:ReturnPayloadFromAsset(asset) - end - end - - end - - -- Found enough assets. - return true - else - - --- - -- NOT enough assets - --- - - -- Return payloads of assets. - if self:IsAirwing() then - for i=1,#Assets do - local asset=Assets[i] - if not asset.spawned then - self:T(self.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) - self:ReturnPayloadFromAsset(asset) - end - end - end - - -- Not enough assets found. - return false - end - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Recruiting and Optimization Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 73940fffc6d727d5db59fbfcaacf2bd44931d6c8 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 18 Sep 2021 18:47:06 +0200 Subject: [PATCH 109/141] Update Legion.lua - Fix that assets did not get removed --- Moose Development/Moose/Ops/Legion.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 843c34b3d..66cf48d0e 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -1794,12 +1794,13 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, end -- Return payloads of not needed assets. - for i=Nassets+1,#Assets do + for i=#Assets,Nassets+1,-1 do local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem if asset.legion:IsAirwing() and not asset.spawned then asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) asset.legion:ReturnPayloadFromAsset(asset) end + table.remove(Assets, i) end -- Found enough assets. From 7148bd1d4280970a2ebfb488f1504c2724e8b91c Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 19 Sep 2021 22:10:52 +0200 Subject: [PATCH 110/141] OPS Escort - Refined functions to recruit escort assets. --- Moose Development/Moose/Ops/AirWing.lua | 2 +- Moose Development/Moose/Ops/Auftrag.lua | 252 ++++++++++++++++-- Moose Development/Moose/Ops/Commander.lua | 143 +++++++++- Moose Development/Moose/Ops/Legion.lua | 139 +++++++++- Moose Development/Moose/Ops/OpsGroup.lua | 19 +- Moose Development/Moose/Ops/Target.lua | 45 ++-- .../Moose/Wrapper/Controllable.lua | 2 +- 7 files changed, 532 insertions(+), 70 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 44e87e638..f4a7bf747 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -815,7 +815,7 @@ function AIRWING:onafterStatus(From, Event, To) local mission=_mission --Ops.Auftrag#AUFTRAG local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end - local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.nassets) + local assets=string.format("%d/%d", mission:CountOpsGroups(), mission:GetNumberOfRequiredAssets()) local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage()) local mystatus=mission:GetLegionStatus(self) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 4b7030154..7b8a2b7d1 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -32,6 +32,7 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #number auftragsnummer Auftragsnummer. -- @field #string type Mission type. +-- @field #table categories Mission categories. -- @field #string status Mission status. -- @field #table legions Assigned legions. -- @field #table statusLegion Mission status of all assigned LEGIONs. @@ -95,15 +96,23 @@ -- @field #number artyRadius Radius in meters. -- @field #number artyShots Number of shots fired. -- +-- @field #string alert5MissionType Alert 5 mission type. This is the mission type, the alerted assets will be able to carry out. +-- -- @field Ops.Chief#CHIEF chief The CHIEF managing this mission. -- @field Ops.Commander#COMMANDER commander The COMMANDER managing this mission. -- @field #table assets Warehouse assets assigned for this mission. --- @field #number nassets Number of required warehouse assets. --- @field #table Nassets Number of required warehouse assets for each assigned legion. +-- @field #number NassetsMin Min. number of required warehouse assets. +-- @field #number NassetsMax Max. number of required warehouse assets. +-- @field #number NescortMin Min. number of required escort assets for each group the mission is assigned to. +-- @field #number NescortMax Max. number of required escort assets for each group the mission is assigned to. +-- @field #number Nassets Number of requested warehouse assets. +-- @field #table NassetsLegMin Number of required warehouse assets for each assigned legion. +-- @field #table NassetsLegMax Number of required warehouse assets for each assigned legion. -- @field #table requestID The ID of the queued warehouse request. Necessary to cancel the request if the mission was cancelled before the request is processed. +-- @field #table specialLegions User specified legions assigned for this mission. Only these will be considered for the job! +-- @field #table specialCohorts User specified cohorts assigned for this mission. Only these will be considered for the job! -- @field #table squadrons User specified airwing squadrons assigned for this mission. Only these will be considered for the job! -- @field #table payloads User specified airwing payloads for this mission. Only these will be considered for the job! --- @field #table mylegions User specified legions for this mission. Only these will be considered for the job! -- @field Ops.AirWing#AIRWING.PatrolData patroldata Patrol data. -- -- @field #string missionTask Mission task. See `ENUMS.MissionTask`. @@ -244,9 +253,11 @@ -- -- An arty mission can be created with the @{#AUFTRAG.NewARTY}() function. -- +-- -- # Options and Parameters -- -- +-- -- # Assigning Missions -- -- An AUFTRAG can be assigned to groups, airwings or wingcommanders @@ -261,13 +272,19 @@ -- -- Assigning an AUFTRAG to a navy groups is done via the @{Ops.NavyGroup#NAVYGROUP.AddMission} function. See NAVYGROUP docs for details. -- --- ## Airwing Level +-- ## Legion Level -- -- Adding an AUFTRAG to an airwing is done via the @{Ops.AirWing#AIRWING.AddMission} function. See AIRWING docs for further details. +-- Similarly, an AUFTRAG can be added to a brigade via the @{Ops.Brigade#BRIGADE.AddMission} function -- --- ## Wing Commander Level +-- ## Commander Level -- --- Assigning an AUFTRAG to a wing commander is done via the @{Ops.WingCommander#WINGCOMMANDER.AddMission} function. See WINGCOMMADER docs for details. +-- Assigning an AUFTRAG to acommander is done via the @{Ops.Commander#COMMANDER.AddMission} function. See COMMADER docs for details. +-- +-- ## Chief Level +-- +-- Assigning an AUFTRAG to a wing commander is done via the @{Ops.Chief#CHIEF.AddMission} function. See CHIEF docs for details. +-- -- -- -- # Events @@ -289,7 +306,8 @@ AUFTRAG = { statusLegion = {}, requestID = {}, assets = {}, - Nassets = {}, + NassetsLegMin = {}, + NassetsLegMax = {}, missionFraction = 0.5, enrouteTasks = {}, marker = nil, @@ -444,6 +462,22 @@ AUFTRAG.TargetType={ SETUNIT="SetUnit", } +--- Mission category. +-- @type AUFTRAG.Category +-- @field #string AIRCRAFT Airplanes and helicopters. +-- @field #string AIRPLANE Airplanes. +-- @field #string HELICOPTER Helicopter. +-- @field #string GROUND Ground troops. +-- @field #string NAVAL Naval grous. +AUFTRAG.Category={ + ALL="All", + AIRCRAFT="Aircraft", + AIRPLANE="Airplane", + HELICOPTER="Helicopter", + GROUND="Ground", + NAVAL="Naval", +} + --- Target data. -- @type AUFTRAG.TargetData -- @field Wrapper.Positionable#POSITIONABLE Target Target Object. @@ -483,7 +517,7 @@ AUFTRAG.TargetType={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.8.0" +AUFTRAG.version="0.8.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -493,6 +527,7 @@ AUFTRAG.version="0.8.0" -- TODO: Mission success options damaged, destroyed. -- TODO: F10 marker to create new missions. -- TODO: Add recovery tanker mission for boat ops. +-- DONE: Added auftrag category. -- DONE: Missions can be assigned to multiple legions. -- DONE: Option to assign a specific payload for the mission (requires an AIRWING). -- NOPE: Clone mission. How? Deepcopy? ==> Create a new auftrag. @@ -621,6 +656,8 @@ function AUFTRAG:NewANTISHIP(Target, Altitude) mission.optionROE=ENUMS.ROE.OpenFire mission.optionROT=ENUMS.ROT.EvadeFire + mission.categories={AUFTRAG.Category.AIRCRAFT} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -662,6 +699,8 @@ function AUFTRAG:NewORBIT(Coordinate, Altitude, Speed, Heading, Leg) mission.missionFraction=0.9 mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense + + mission.categories={AUFTRAG.Category.AIRCRAFT} mission.DCStask=mission:GetDCSMissionTask() @@ -722,6 +761,8 @@ function AUFTRAG:NewGCICAP(Coordinate, Altitude, Speed, Heading, Leg) mission.missionTask=ENUMS.MissionTask.INTERCEPT mission.optionROT=ENUMS.ROT.PassiveDefense + mission.categories={AUFTRAG.Category.AIRCRAFT} + return mission end @@ -751,6 +792,8 @@ function AUFTRAG:NewTANKER(Coordinate, Altitude, Speed, Heading, Leg, RefuelSyst mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.PassiveDefense + mission.categories={AUFTRAG.Category.AIRCRAFT} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -779,6 +822,8 @@ function AUFTRAG:NewAWACS(Coordinate, Altitude, Speed, Heading, Leg) mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.PassiveDefense + mission.categories={AUFTRAG.Category.AIRCRAFT} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -802,6 +847,8 @@ function AUFTRAG:NewINTERCEPT(Target) mission.optionROE=ENUMS.ROE.OpenFire mission.optionROT=ENUMS.ROT.EvadeFire + mission.categories={AUFTRAG.Category.AIRCRAFT} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -842,6 +889,8 @@ function AUFTRAG:NewCAP(ZoneCAP, Altitude, Speed, Coordinate, Heading, Leg, Targ mission.optionROE=ENUMS.ROE.OpenFire mission.optionROT=ENUMS.ROT.EvadeFire + mission.categories={AUFTRAG.Category.AIRCRAFT} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -881,6 +930,8 @@ function AUFTRAG:NewCAS(ZoneCAS, Altitude, Speed, Coordinate, Heading, Leg, Targ mission.missionTask=ENUMS.MissionTask.CAS mission.optionROE=ENUMS.ROE.OpenFire mission.optionROT=ENUMS.ROT.EvadeFire + + mission.categories={AUFTRAG.Category.AIRCRAFT} mission.DCStask=mission:GetDCSMissionTask() @@ -915,6 +966,8 @@ function AUFTRAG:NewFACA(Target, Designation, DataLink, Frequency, Modulation) mission.missionFraction=0.5 mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense + + mission.categories={AUFTRAG.Category.AIRCRAFT} mission.DCStask=mission:GetDCSMissionTask() @@ -945,6 +998,8 @@ function AUFTRAG:NewBAI(Target, Altitude) mission.optionROE=ENUMS.ROE.OpenFire mission.optionROT=ENUMS.ROT.PassiveDefense + mission.categories={AUFTRAG.Category.AIRCRAFT} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -974,6 +1029,8 @@ function AUFTRAG:NewSEAD(Target, Altitude) mission.optionROT=ENUMS.ROT.EvadeFire --mission.optionROT=ENUMS.ROT.AllowAbortMission + mission.categories={AUFTRAG.Category.AIRCRAFT} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -1002,6 +1059,8 @@ function AUFTRAG:NewSTRIKE(Target, Altitude) mission.optionROE=ENUMS.ROE.OpenFire mission.optionROT=ENUMS.ROT.PassiveDefense + mission.categories={AUFTRAG.Category.AIRCRAFT} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -1033,6 +1092,8 @@ function AUFTRAG:NewBOMBING(Target, Altitude) -- Evaluate result after 5 min. We might need time until the bombs have dropped and targets have been detroyed. mission.dTevaluate=5*60 + mission.categories={AUFTRAG.Category.AIRCRAFT} + -- Get DCS task. mission.DCStask=mission:GetDCSMissionTask() @@ -1069,6 +1130,8 @@ function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude) -- Evaluate result after 5 min. mission.dTevaluate=5*60 + mission.categories={AUFTRAG.Category.AIRCRAFT} + -- Get DCS task. mission.DCStask=mission:GetDCSMissionTask() @@ -1105,6 +1168,8 @@ function AUFTRAG:NewBOMBCARPET(Target, Altitude, CarpetLength) -- Evaluate result after 5 min. mission.dTevaluate=5*60 + mission.categories={AUFTRAG.Category.AIRCRAFT} + -- Get DCS task. mission.DCStask=mission:GetDCSMissionTask() @@ -1123,7 +1188,13 @@ function AUFTRAG:NewESCORT(EscortGroup, OffsetVector, EngageMaxDistance, TargetT local mission=AUFTRAG:New(AUFTRAG.Type.ESCORT) - mission:_TargetFromObject(EscortGroup) + -- If only a string is passed we set a variable and check later if the group exists. + if type(EscortGroup)=="string" then + mission.escortGroupName=EscortGroup + mission:_TargetFromObject() + else + mission:_TargetFromObject(EscortGroup) + end -- DCS task parameters: mission.escortVec3=OffsetVector or {x=-100, y=0, z=200} @@ -1137,6 +1208,8 @@ function AUFTRAG:NewESCORT(EscortGroup, OffsetVector, EngageMaxDistance, TargetT mission.optionROE=ENUMS.ROE.OpenFire -- TODO: what's the best ROE here? Make dependent on ESCORT or FOLLOW! mission.optionROT=ENUMS.ROT.PassiveDefense + mission.categories={AUFTRAG.Category.AIRCRAFT} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -1158,13 +1231,15 @@ function AUFTRAG:NewRESCUEHELO(Carrier) mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.NoReaction + mission.categories={AUFTRAG.Category.HELICOPTER} + mission.DCStask=mission:GetDCSMissionTask() return mission end ---- **[AIR ROTARY]** Create a TROOP TRANSPORT mission. +--- **[AIR ROTARY, GROUND]** Create a TROOP TRANSPORT mission. -- @param #AUFTRAG self -- @param Core.Set#SET_GROUP TransportGroupSet The set group(s) to be transported. -- @param Core.Point#COORDINATE DropoffCoordinate Coordinate where the helo will land drop off the the troops. @@ -1197,6 +1272,8 @@ function AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet, DropoffCoordinate, PickupC mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense + mission.categories={AUFTRAG.Category.HELICOPTER, AUFTRAG.Category.GROUND} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -1231,6 +1308,8 @@ function AUFTRAG:NewOPSTRANSPORT(CargoGroupSet, PickupZone, DeployZone) mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense + mission.categories={AUFTRAG.Category.ALL} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -1262,6 +1341,8 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius) -- Evaluate after 8 min. mission.dTevaluate=8*60 + mission.categories={AUFTRAG.Category.GROUND} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -1292,6 +1373,8 @@ function AUFTRAG:NewPATROLZONE(Zone, Speed, Altitude) mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude) or nil + mission.categories={AUFTRAG.Category.ALL} + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -1318,6 +1401,8 @@ function AUFTRAG:NewRECON(ZoneSet, Speed, Altitude, Adinfinitum, Randomly) mission.missionFraction=0.5 mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude) or UTILS.FeetToMeters(2000) + + mission.categories={AUFTRAG.Category.ALL} mission.DCStask=mission:GetDCSMissionTask() mission.DCStask.params.adinfitum=Adinfinitum @@ -1340,6 +1425,8 @@ function AUFTRAG:NewAMMOSUPPLY(Zone) mission.optionAlarm=ENUMS.AlarmState.Auto mission.missionFraction=0.9 + + mission.categories={AUFTRAG.Category.GROUND} mission.DCStask=mission:GetDCSMissionTask() @@ -1360,6 +1447,8 @@ function AUFTRAG:NewFUELSUPPLY(Zone) mission.optionAlarm=ENUMS.AlarmState.Auto mission.missionFraction=0.9 + + mission.categories={AUFTRAG.Category.GROUND} mission.DCStask=mission:GetDCSMissionTask() @@ -1383,6 +1472,8 @@ function AUFTRAG:NewALERT5(MissionType) mission.alert5MissionType=MissionType mission.missionFraction=1.0 + + mission.categories={AUFTRAG.Category.AIRCRAFT} mission.DCStask=mission:GetDCSMissionTask() @@ -1686,28 +1777,58 @@ function AUFTRAG:SetRepeatOnSuccess(Nrepeat) return self end ---- Define how many assets are required to do the job. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, ...) or higher level. +--- Define how many assets are required to do the job. Only used if the mission is handled by a **LEGION** (AIRWING, BRIGADE, ...) or higher level. -- @param #AUFTRAG self --- @param #number Nassets Number of asset groups. Default 1. +-- @param #number NassetsMin Minimum number of asset groups. Default 1. +-- @param #number NassetsMax Maximum Number of asset groups. Default is same as `NassetsMin`. -- @return #AUFTRAG self -function AUFTRAG:SetRequiredAssets(Nassets) - self.nassets=Nassets or 1 +function AUFTRAG:SetRequiredAssets(NassetsMin, NassetsMax) + + self.NassetsMin=NassetsMin or 1 + + self.NassetsMax=NassetsMax or self.NassetsMin + + -- Ensure that max is at least equal to min. + if self.NassetsMax0) then self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, COMMANDER or LEGION! Stopping AUFTRAG") self:Stop() return false @@ -3498,7 +3702,7 @@ function AUFTRAG:_TargetFromObject(Object) if not self.engageTarget then - if Object:IsInstanceOf("TARGET") then + if Object and Object:IsInstanceOf("TARGET") then self.engageTarget=Object @@ -3513,7 +3717,7 @@ function AUFTRAG:_TargetFromObject(Object) -- Target was already specified elsewhere. end - + -- Debug info. --self:T2(self.lid..string.format("Mission Target %s Type=%s, Ntargets=%d, Lifepoints=%d", self.engageTarget.lid, self.engageTarget.lid, self.engageTarget.N0, self.engageTarget:GetLife())) @@ -3848,7 +4052,7 @@ function AUFTRAG:UpdateMarker() local text=string.format("%s %s: %s", self.name, self.type:upper(), self.status:upper()) text=text..string.format("\n%s", self:GetTargetName()) text=text..string.format("\nTargets %d/%d, Life Points=%d/%d", self:CountMissionTargets(), self:GetTargetInitialNumber(), self:GetTargetLife(), self:GetTargetInitialLife()) - text=text..string.format("\nFlights %d/%d", self:CountOpsGroups(), self.nassets) + text=text..string.format("\nOpsGroups %d/%d", self:CountOpsGroups(), self.nassets) if not self.marker then diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 055888f08..e0c23bfa0 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -692,19 +692,30 @@ function COMMANDER:CheckMissionQueue() local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem asset.isReserved=true mission:AddAsset(asset) - end - - -- Assign mission to legion(s). - for _,_legion in pairs(legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Debug message. - self:I(self.lid..string.format("Assigning mission %s [%s] to legion %s", mission:GetName(), mission:GetType(), legion.alias)) - - -- Add mission to legion. - self:MissionAssign(legion, mission) - end + + -- Recruit asset for escorting recruited mission assets. + local EscortAvail=self:RecruitAssetsForEscort(mission, assets) + + + if EscortAvail then + + -- Assign mission to legion(s). + for _,_legion in pairs(legions) do + local legion=_legion --Ops.Legion#LEGION + + -- Debug message. + self:I(self.lid..string.format("Assigning mission %s [%s] to legion %s", mission:GetName(), mission:GetType(), legion.alias)) + + -- Add mission to legion. + self:MissionAssign(legion, mission) + + end + + else + -- Recruited assets but no requested escort available. Unrecruit assets! + LEGION.UnRecruitAssets(assets, mission) + end -- Only ONE mission is assigned. return @@ -745,8 +756,7 @@ function COMMANDER:RecruitAssetsForMission(Mission) end -- Number of required assets. - local NreqMin=Mission:GetRequiredAssets() - local NreqMax=NreqMin + local NreqMin, NreqMax=Mission:GetRequiredAssets() -- Target position. local TargetVec2=Mission:GetTargetVec2() @@ -760,6 +770,111 @@ function COMMANDER:RecruitAssetsForMission(Mission) return recruited, assets, legions end +--- Recruit assets performing an escort mission for a given asset. +-- @param #COMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @param #table Assets Table of assets. +-- @return #boolean If `true`, enough assets could be recruited or no escort was required in the first place. +function COMMANDER:RecruitAssetsForEscort(Mission, Assets) + + -- Cohorts. + local Cohorts=Mission.squadrons + if not Cohorts then + Cohorts={} + for _,_legion in pairs(Mission.mylegions or self.legions) do + local legion=_legion --Ops.Legion#LEGION + -- Loops over cohorts. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end + end + + -- Is an escort requested in the first place? + if Mission.NescortMin and Mission.NescortMax and (Mission.NescortMin>0 or Mission.NescortMax>0) then + + -- Debug info. + self:I(self.lid..string.format("Reqested escort for mission %s [%s]. Required assets=%d-%d", Mission:GetName(), Mission:GetType(), Mission.NescortMin,Mission.NescortMax)) + + -- Escorts for each asset. + local Escorts={} + + local EscortAvail=true + for _,_asset in pairs(Assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Recruit escort asset for the mission asset. + local Erecruited, eassets, elegions=LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.ESCORT, nil, Mission.NescortMin, Mission.NescortMax) + + if Erecruited then + Escorts[asset.spawngroupname]={EscortLegions=elegions, EscortAssets=eassets} + else + -- Could not find escort for this asset ==> Escort not possible ==> Break the loop. + EscortAvail=false + break + end + end + + -- ALL escorts could be recruited. + if EscortAvail then + + local N=0 + for groupname,value in pairs(Escorts) do + + local Elegions=value.EscortLegions + local Eassets=value.EscortAssets + + for _,_legion in pairs(Elegions) do + local legion=_legion --Ops.Legion#LEGION + + -- Create and ESCORT mission for this asset. + local escort=AUFTRAG:NewESCORT(groupname) + + -- Reserve assts and add to mission. + for _,_asset in pairs(Eassets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.isReserved=true + escort:AddAsset(asset) + N=N+1 + end + + -- Add mission. + legion:AddMission(escort) + + -- Request mission. + legion:MissionRequest(escort) + + end + end + + -- Debug info. + self:I(self.lid..string.format("Recruited %d escort assets for mission %s [%s]", N, Mission:GetName(), Mission:GetType())) + + -- Yup! + return true + else + + -- Debug info. + self:I(self.lid..string.format("Could not get at least one escort for mission %s [%s]! Unrecruit all recruited assets", Mission:GetName(), Mission:GetType())) + + -- Could not get at least one escort. Unrecruit all recruited ones. + for groupname,value in pairs(Escorts) do + local Eassets=value.EscortAssets + LEGION.UnRecruitAssets(Eassets) + end + + -- No,no! + return false + end + + else + -- No escort required. + return true + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Transport Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 66cf48d0e..f42d19909 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -473,17 +473,30 @@ function LEGION:_GetNextMission() -- Recruit best assets for the job. local recruited, assets, legions=self:RecruitAssetsForMission(mission) - + -- Did we find enough assets? if recruited then + + -- Reserve assets and add to mission. for _,_asset in pairs(assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem asset.isReserved=true mission:AddAsset(asset) end - - return mission - end + + -- Recruit asset for escorting recruited mission assets. + local EscortAvail=self:RecruitAssetsForEscort(mission, assets) + + -- Is escort required and available? + if EscortAvail then + -- Got a missin. + return mission + else + -- Recruited assets but no requested escort available. Unrecruit assets! + LEGION.UnRecruitAssets(assets, mission) + end + + end -- recruited mission assets end -- mission due? end -- mission loop @@ -1627,8 +1640,7 @@ end function LEGION:RecruitAssetsForMission(Mission) -- Get required assets. - local NreqMin=Mission:GetRequiredAssets() - local NreqMax=NreqMin + local NreqMin, NreqMax=Mission:GetRequiredAssets() -- Target position vector. local TargetVec2=Mission:GetTargetVec2() @@ -1686,6 +1698,97 @@ function LEGION:RecruitAssetsForTransport(Transport) return recruited, assets, legions end +--- Recruit assets performing an escort mission for a given asset. +-- @param #LEGION self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @param #table Assets Table of assets. +-- @return #boolean If `true`, enough assets could be recruited or no escort was required in the first place. +function LEGION:RecruitAssetsForEscort(Mission, Assets) + + -- Is an escort requested in the first place? + if Mission.NescortMin and Mission.NescortMax and (Mission.NescortMin>0 or Mission.NescortMax>0) then + + -- Debug info. + self:I(self.lid..string.format("Reqested escort for mission %s [%s]. Required assets=%d-%d", Mission:GetName(), Mission:GetType(), Mission.NescortMin,Mission.NescortMax)) + + -- Escorts for each asset. + local Escorts={} + + local EscortAvail=true + for _,_asset in pairs(Assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Recruit escort asset for the mission asset. + local Erecruited, eassets, elegions=LEGION.RecruitCohortAssets(self.cohorts, AUFTRAG.Type.ESCORT, nil, Mission.NescortMin, Mission.NescortMax) + + if Erecruited then + Escorts[asset.spawngroupname]={EscortLegions=elegions, EscortAssets=eassets} + else + -- Could not find escort for this asset ==> Escort not possible ==> Break the loop. + EscortAvail=false + break + end + end + + -- ALL escorts could be recruited. + if EscortAvail then + + local N=0 + for groupname,value in pairs(Escorts) do + + local Elegions=value.EscortLegions + local Eassets=value.EscortAssets + + for _,_legion in pairs(Elegions) do + local legion=_legion --Ops.Legion#LEGION + + -- Create and ESCORT mission for this asset. + local escort=AUFTRAG:NewESCORT(groupname) + + -- Reserve assts and add to mission. + for _,_asset in pairs(Eassets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + asset.isReserved=true + escort:AddAsset(asset) + N=N+1 + end + + -- Add mission. + legion:AddMission(escort) + + -- Request mission. + legion:MissionRequest(escort) + + end + end + + -- Debug info. + self:I(self.lid..string.format("Recruited %d escort assets for mission %s [%s]", N, Mission:GetName(), Mission:GetType())) + + -- Yup! + return true + else + + -- Debug info. + self:I(self.lid..string.format("Could not get at least one escort for mission %s [%s]! Unrecruit all recruited assets", Mission:GetName(), Mission:GetType())) + + -- Could not get at least one escort. Unrecruit all recruited ones. + for groupname,value in pairs(Escorts) do + local Eassets=value.EscortAssets + LEGION.UnRecruitAssets(Eassets) + end + + -- No,no! + return false + end + + else + -- No escort required. + return true + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Recruiting and Optimization Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1827,6 +1930,30 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, return false, {}, {} end +--- Unrecruit assets. Set `isReserved` to false, return payload to airwing and (optionally) remove from assigned mission. +-- @param #table Assets List of assets. +-- @param Ops.Auftrag#AUFTRAG Mission (Optional) The mission from which the assets will be deleted. +function LEGION.UnRecruitAssets(Assets, Mission) + + -- Return payloads of assets. + for i=1,#Assets do + local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + -- Not reserved any more. + asset.isReserved=false + -- Return payload. + if asset.legion:IsAirwing() and not asset.spawned then + asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s", asset.spawngroupname)) + asset.legion:ReturnPayloadFromAsset(asset) + end + -- Remove from mission. + if Mission then + Mission:DelAsset(asset) + end + end + +end + + --- Calculate the mission score of an asset. -- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ee5501cf9..133bcc167 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3763,9 +3763,6 @@ function OPSGROUP:_GetNextMission() end table.sort(self.missionqueue, _sort) - -- Current time. - local time=timer.getAbsTime() - -- Look for first mission that is SCHEDULED. local vip=math.huge for _,_mission in pairs(self.missionqueue) do @@ -3794,9 +3791,23 @@ function OPSGROUP:_GetNextMission() -- TODO: One could think of opsgroup specific start conditions. A legion also checks if "ready" but it can be other criteria for the group to actually start the mission. -- Good example is the above transport. The legion should start the mission but the group should only start after the transport is finished. + + -- Escort mission. Check that escorted group is alive. + local isEscort=true + if mission.type==AUFTRAG.Type.ESCORT then + local target=mission:GetTargetData() + if not target:IsAlive() then + isEscort=false + end + end + + local isScheduled=mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.SCHEDULED + local isReadyToGo=(mission:IsReadyToGo() or self.legion) + local isImportant=(mission.importance==nil or mission.importance<=vip) + local isTransport=transport -- Check necessary conditions. - if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.SCHEDULED and (mission:IsReadyToGo() or self.legion) and (mission.importance==nil or mission.importance<=vip) and transport then + if isScheduled and isReadyToGo and isImportant and isTransport and isEscort then return mission end diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 01ec67d9f..32b9eafd1 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -130,13 +130,14 @@ _TARGETID=0 --- TARGET class version. -- @field #string version -TARGET.version="0.5.1" +TARGET.version="0.5.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Add pseudo functions. +-- DONE: Initial object can be nil. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -156,30 +157,20 @@ function TARGET:New(TargetObject) -- Set UID. self.uid=_TARGETID + + if TargetObject then - -- Add object. - self:AddObject(TargetObject) - - -- Get first target. - local Target=self.targets[1] --#TARGET.Object - - if not Target then - self:E("ERROR: No valid TARGET!") - return nil + -- Add object. + self:AddObject(TargetObject) + end -- Defaults. self:SetPriority() self:SetImportance() - - -- Target Name. - self.name=self:GetTargetName(Target) - - -- Target category. - self.category=self:GetTargetCategory(Target) -- Log ID. - self.lid=string.format("TARGET #%03d [%s] | ", _TARGETID, tostring(self.category)) + self.lid=string.format("TARGET #%03d | ", _TARGETID) -- Start state. self:SetStartState("Stopped") @@ -317,8 +308,15 @@ end -- @param #TARGET self -- @return #boolean If true, target is alive. function TARGET:IsAlive() - local is=self:Is("Alive") - return is + + for _,_target in pairs(self.targets) do + local target=_target --Ops.Target#TARGET.Object + if target.Status==TARGET.ObjectStatus.ALIVE then + return true + end + end + + return false end --- Check if TARGET is destroyed. @@ -756,6 +754,13 @@ function TARGET:_AddObject(Object) target.Object=Object table.insert(self.targets, target) + + if self.name==nil then + self.name=self:GetTargetName(target) + end + if self.category==nil then + self.category=self:GetTargetCategory(target) + end end @@ -1051,7 +1056,7 @@ end -- @param #TARGET self -- @return #string Name of the target usually the first object. function TARGET:GetName() - return self.name + return self.name or "Unknown" end --- Get 2D vector. diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 5b130ec61..397832e85 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1407,7 +1407,7 @@ function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, E DCSTask = { id = 'Escort', params = { - groupId = FollowControllable:GetID(), + groupId = FollowControllable and FollowControllable:GetID() or nil, pos = Vec3, lastWptIndexFlag = LastWaypointIndex and true or false, lastWptIndex = LastWaypointIndex, From 9b6cae6c49733b2ead98b099b3fbafcf90d1555d Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 19 Sep 2021 22:45:58 +0200 Subject: [PATCH 111/141] Update Commander.lua --- Moose Development/Moose/Ops/Commander.lua | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index e0c23bfa0..ed02fe670 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -839,12 +839,8 @@ function COMMANDER:RecruitAssetsForEscort(Mission, Assets) N=N+1 end - -- Add mission. - legion:AddMission(escort) - - -- Request mission. - legion:MissionRequest(escort) - + -- Assign mission to legion. + self:MissionAssign(legion, escort) end end From c5af279730a30c0a8dd2b9c300de033a16da8f89 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 20 Sep 2021 22:47:51 +0200 Subject: [PATCH 112/141] OPS - Fixed some stuff for Egress coordinate - Fixed some bugs in OPSTRANPORT if disembark carriers are specified --- Moose Development/Moose/Ops/Auftrag.lua | 71 ++++++++++---- Moose Development/Moose/Ops/FlightGroup.lua | 13 +-- Moose Development/Moose/Ops/OpsGroup.lua | 99 ++++++++++++++++---- Moose Development/Moose/Ops/OpsTransport.lua | 40 +++++++- 4 files changed, 179 insertions(+), 44 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 7b8a2b7d1..e5e7182b7 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -385,7 +385,7 @@ AUFTRAG.Type={ } --- Mission status of an assigned group. --- @type AUFTRAG.GroupStatus +-- @type AUFTRAG.SpecialTask -- @field #string PATROLZONE Patrol zone task. -- @field #string RECON Recon task -- @field #string AMMOSUPPLY Ammo Supply. @@ -508,8 +508,10 @@ AUFTRAG.Category={ -- @type AUFTRAG.GroupData -- @field Ops.OpsGroup#OPSGROUP opsgroup The OPS group. -- @field Core.Point#COORDINATE waypointcoordinate Ingress waypoint coordinate. --- @field #number waypointindex Waypoint index. +-- @field #number waypointindex Mission (ingress) Waypoint UID. +-- @field #number waypointEgressUID Egress Waypoint UID. -- @field Core.Point#COORDINATE wpegresscoordinate Egress waypoint coordinate. +-- -- @field Ops.OpsGroup#OPSGROUP.Task waypointtask Waypoint task. -- @field #string status Group mission status. -- @field Functional.Warehouse#WAREHOUSE.Assetitem asset The warehouse asset. @@ -2599,7 +2601,7 @@ end --- Check if mission is ready to be pushed. -- * Mission push time already passed. --- * All push conditions are true. +-- * **All** push conditions are true. -- @param #AUFTRAG self -- @return #boolean If true, mission groups can push. function AUFTRAG:IsReadyToPush() @@ -2607,7 +2609,7 @@ function AUFTRAG:IsReadyToPush() local Tnow=timer.getAbsTime() -- Push time passed? - if self.Tpush and Tnow0) then if Mission:IsReadyToPush() then - - -- Not waiting any more. - self.Twaiting=nil - self.dTwait=nil + + --- + -- READY to push yet + --- + + -- Group is currently waiting. + if self:IsWaiting() then + + -- Not waiting any more. + self.Twaiting=nil + self.dTwait=nil + + -- For a flight group, we must cancel the wait/orbit task. + if self:IsFlightgroup() then + + -- Set hold flag to 1. This is a condition in the wait/orbit task. + self.flaghold:Set(1) + + -- Reexecute task in 1 sec to allow to flag to take effect. + --self:__TaskExecute(-1, Task) + + -- Deny transition for now. + --return false + end + end else --- - -- Not ready to push yet + -- NOT READY to push yet --- if self:IsWaiting() then -- Group is already waiting else + -- Wait indefinately. self:Wait() end - -- Time to for the next try. + -- Time to for the next try. Best guess is when push time is reached or 20 sec when push conditions are not true yet. local dt=Mission.Tpush and Mission.Tpush-timer.getAbsTime() or 20 - -- Debug info. self:T(self.lid..string.format("Mission %s task execute suspended for %d seconds", Mission.name, dt)) @@ -3594,8 +3615,13 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) local status=Mission:GetGroupStatus(self) if status~=AUFTRAG.GroupStatus.PAUSED then - self:T(self.lid.."Task Done ==> Mission Done!") - self:MissionDone(Mission) + local EgressUID=Mission:GetGroupEgressWaypointUID(self) + if EgressUID then + self:T(self.lid..string.format("Task Done but Egress waypoint defined ==> Will call Mission Done once group passed waypoint UID=%d!", EgressUID)) + else + self:T(self.lid.."Task Done ==> Mission Done!") + self:MissionDone(Mission) + end else --Mission paused. Do nothing! Just set the current mission to nil so we can launch a new one. if self.currentmission and self.currentmission==Mission.auftragsnummer then @@ -3800,7 +3826,8 @@ function OPSGROUP:_GetNextMission() isEscort=false end end - + + -- Conditons to start. local isScheduled=mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.SCHEDULED local isReadyToGo=(mission:IsReadyToGo() or self.legion) local isImportant=(mission.importance==nil or mission.importance<=vip) @@ -4297,7 +4324,9 @@ function OPSGROUP:RouteToMission(mission, delay) -- Add egress waypoint. local egress=mission:GetMissionEgressCoord() if egress then + --egress:MarkToAll(string.format("Egress Mission %s alt=%d m", mission:GetName(), waypointcoord.y)) local waypointEgress=self:AddWaypoint(egress, SpeedToMission, waypoint.uid, formation, false) ; waypointEgress.missionUID=mission.auftragsnummer + mission:SetGroupEgressWaypointUID(self, waypointEgress.uid) end --- @@ -4633,13 +4662,26 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) end -- Passing mission waypoint? + local isEgress=false if Waypoint.missionUID then - self:T2(self.lid..string.format("Passing mission waypoint")) + + -- Debug info. + self:T2(self.lid..string.format("Passing mission waypoint UID=%s", tostring(Waypoint.uid))) + + -- Get the mission. + local mission=self:GetMissionByID(Waypoint.missionUID) + + -- Check if this was an Egress waypoint of the mission. If so, call Mission Done! This will call CheckGroupDone. + local EgressUID=mission and mission:GetGroupEgressWaypointUID(self) or nil + isEgress=EgressUID and Waypoint.uid==EgressUID + if isEgress and mission:GetGroupStatus(self)~=AUFTRAG.GroupStatus.DONE then + self:MissionDone(mission) + end end -- Check if all tasks/mission are done? -- Note, we delay it for a second to let the OnAfterPassingwaypoint function to be executed in case someone wants to add another waypoint there. - if ntasks==0 and self:HasPassedFinalWaypoint() then + if ntasks==0 and self:HasPassedFinalWaypoint() and not isEgress then self:_CheckGroupDone(0.01) end @@ -6788,10 +6830,24 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Loading time stamp. self.Tloading=timer.getAbsTime() - --TODO: sort cargos wrt weight. - -- Cargo group table. - local cargos=self.cargoTZC.Cargos + --local cargos=self.cargoTZC.Cargos + + local cargos={} + for _,_cargo in pairs(self.cargoTZC.Cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + if self:CanCargo(cargo.opsgroup) and (not (cargo.delivered or cargo.opsgroup:IsDead())) then + table.insert(cargos, cargo) + end + end + + -- Sort results table wrt descending weight. + local function _sort(a, b) + local cargoA=a --Ops.OpsGroup#OPSGROUP.CargoGroup + local cargoB=b --Ops.OpsGroup#OPSGROUP.CargoGroup + return cargoA.opsgroup:GetWeightTotal()>cargoB.opsgroup:GetWeightTotal() + end + table.sort(cargos, _sort) -- Loop over all cargos. for _,_cargo in pairs(cargos) do @@ -8019,8 +8075,16 @@ function OPSGROUP:_CheckStuck() -- Time we are holding. local holdtime=Tnow-self.stuckTimestamp + + if holdtime>=5*60 and holdtime<10*60 then - if holdtime>=10*60 then + -- Debug warning. + self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec", speed, ExpectedSpeed, holdtime)) + + -- Give cruise command again. + self:__Cruise(1) + + elseif holdtime>=10*60 then -- Debug warning. self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec", speed, ExpectedSpeed, holdtime)) @@ -8428,6 +8492,9 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) local destbase=self:GetDestinationFromWaypoints() self.destbase=self.destbase or destbase self.currbase=self:GetHomebaseFromWaypoints() + + --env.info("FF home base "..(self.homebase and self.homebase:GetName() or "unknown")) + --env.info("FF dest base "..(self.destbase and self.destbase:GetName() or "unknown")) -- Remove the landing waypoint. We use RTB for that. It makes adding new waypoints easier as we do not have to check if the last waypoint is the landing waypoint. if destbase and #self.waypoints>1 then diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 87352904b..195debb81 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -700,7 +700,7 @@ end --- Get transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. --- @return #table Table of carriers. +-- @return #table Table of carrier OPS groups. function OPSTRANSPORT:GetDisembarkCarriers(TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. @@ -909,7 +909,7 @@ function OPSTRANSPORT:GetCargoOpsGroups(Delivered, Carrier, TransportZoneCombo) end end end - + return opsgroups end @@ -1898,15 +1898,49 @@ function OPSTRANSPORT:_CountCargosInZone(Zone, Delivered, Carrier, TransportZone -- Get cargo ops groups. local cargos=self:GetCargoOpsGroups(Delivered, Carrier, TransportZoneCombo) + --- Function to check if carrier is supposed to be disembarked to. + local function iscarrier(_cargo) + local cargo=_cargo --Ops.OpsGroup#OPSGROUP + + local mycarrier=cargo:_GetMyCarrierGroup() + + if mycarrier and mycarrier:IsUnloading() then + + local carriers=mycarrier.cargoTransport:GetDisembarkCarriers(mycarrier.cargoTZC) + + for _,_carrier in pairs(carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + if Carrier:GetName()==carrier:GetName() then + return true + end + end + + end + + return false + end + local N=0 for _,_cargo in pairs(cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP + + local isNotCargo=cargo:IsNotCargo(true) + if not isNotCargo then + isNotCargo=iscarrier(cargo) + end + -- Debug info. + --self:T2(self.lid..string.format("Cargo=%s: notcargo=%s, iscarrier=%s inzone=%s, inutero=%s", cargo:GetName(), tostring(cargo:IsNotCargo(true)), tostring(iscarrier(cargo)), tostring(cargo:IsInZone(Zone)), tostring(cargo:IsInUtero()) )) + + -- We look for groups that are not cargo, in the zone or in utero. - if cargo:IsNotCargo(true) and (cargo:IsInZone(Zone) or cargo:IsInUtero()) then + if isNotCargo and (cargo:IsInZone(Zone) or cargo:IsInUtero()) then N=N+1 end end + + -- Debug info. + self:T(self.lid..string.format("Found %d units in zone %s", N, Zone:GetName())) return N end From d7dae1366db20bbbed4528138f77df4f7387726f Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 22 Sep 2021 19:27:33 +0200 Subject: [PATCH 113/141] OPS - Improved OPSTRANSPORT for LEGION assets. --- .../Moose/Functional/Warehouse.lua | 4 +- Moose Development/Moose/Ops/Auftrag.lua | 41 +-- Moose Development/Moose/Ops/Brigade.lua | 3 + Moose Development/Moose/Ops/Chief.lua | 261 ++++++++++++++++-- Moose Development/Moose/Ops/Cohort.lua | 4 + Moose Development/Moose/Ops/Commander.lua | 211 ++++++++++---- Moose Development/Moose/Ops/FlightGroup.lua | 5 +- Moose Development/Moose/Ops/Legion.lua | 60 +++- Moose Development/Moose/Ops/OpsGroup.lua | 29 +- Moose Development/Moose/Ops/OpsTransport.lua | 31 ++- Moose Development/Moose/Ops/OpsZone.lua | 140 ++++++++-- 11 files changed, 631 insertions(+), 158 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index e67257aa5..77378e7fb 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1867,6 +1867,7 @@ function WAREHOUSE:New(warehouse, alias) else self.isunit=true if warehouse:IsShip() then + env.info("FF warehouse is ship!") self.isShip=true end end @@ -1925,7 +1926,6 @@ function WAREHOUSE:New(warehouse, alias) self:SetMarker(true) self:SetReportOff() self:SetRunwayRepairtime() - --self:SetVerbosityLevel(0) -- Add warehouse to database. _WAREHOUSEDB.Warehouses[self.uid]=self @@ -5829,7 +5829,7 @@ function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, late -- Late activation. template.lateActivation=lateactivated - env.info("FF lateActivation="..tostring(template.lateActivation)) + --env.info("FF lateActivation="..tostring(template.lateActivation)) template.route.points[1].x = coord.x template.route.points[1].y = coord.z diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index e5e7182b7..853b0e9a2 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1968,9 +1968,10 @@ end -- @param #AUFTRAG self -- @param Core.Zone#ZONE DeployZone Zone where assets are deployed. -- @param Core.Zone#ZONE DisembarkZone Zone where assets are disembarked to. --- @param Core.Set#SET_OPSGROUP Carriers Set of carriers. Can also be a single group. Can also be added via the AddTransportCarriers functions. +-- @param #number NcarriersMin Number of carriers *at least* required. Default 1. +-- @param #number NcarriersMax Number of carriers *at most* used for transportation. Default is same as `NcarriersMin`. -- @return #AUFTRAG self -function AUFTRAG:SetTransportForAssets(DeployZone, DisembarkZone, Carriers) +function AUFTRAG:SetTransportForAssets(DeployZone, DisembarkZone, NcarriersMin, NcarriersMax) -- OPS transport from pickup to deploy zone. self.opstransport=OPSTRANSPORT:New(nil, nil, DeployZone) @@ -1978,8 +1979,20 @@ function AUFTRAG:SetTransportForAssets(DeployZone, DisembarkZone, Carriers) if DisembarkZone then self.opstransport:SetDisembarkZone(DisembarkZone) end - - if Carriers then + + -- Set required carriers. + self:SetRequiredCarriers(NcarriersMin, NcarriersMax) + + return self +end + +--- Add carriers for a transport of mission assets. +-- @param #AUFTRAG self +-- @param Core.Set#SET_OPSGROUP Carriers Set of carriers. Can also be a single group. +-- @return #AUFTRAG self +function AUFTRAG:AddTransportCarriers(Carriers) + + if self.opstransport then if Carriers:IsInstanceOf("SET_OPSGROUP") then for _,_carrier in pairs(Carriers.Set) do @@ -1992,12 +2005,7 @@ function AUFTRAG:SetTransportForAssets(DeployZone, DisembarkZone, Carriers) end end - - -- Set min/max number of carriers to be assigned. - self.opstransport.nCarriersMin=self.nCarriersMin - self.opstransport.nCarriersMax=self.nCarriersMax - - return self + end --- Set number of required carrier groups if an OPSTRANSPORT assignment is required. @@ -2347,9 +2355,11 @@ function AUFTRAG:AddOpsGroup(OpsGroup) if self.opstransport then for _,_tzc in pairs(self.opstransport.tzCombos) do local tzc=_tzc --Ops.OpsTransport#OPSTRANSPORT.TransportZoneCombo - if tzc.uid~=self.opstransport.tzcDefault.uid then + + if tzc.assetsCargo and tzc.assetsCargo[OpsGroup:GetName()] then self.opstransport:AddCargoGroups(OpsGroup, tzc) end + end end @@ -2544,15 +2554,6 @@ function AUFTRAG:IsReadyToGo() return false end - -- Ops transport at - if self.opstransport then - if #self.legions>0 then - end - if self.opstransport:IsPlanned() or self.opstransport:IsQueued() or self.opstransport:IsRequested() then - --return false - end - end - -- All start conditions true? local startme=self:EvalConditionsAll(self.conditionStart) diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index be89ff9eb..5210a85c2 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -52,6 +52,9 @@ BRIGADE.version="0.1.0" -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Spawn when hosting warehouse is a ship or oil rig or gas platform. +-- TODO: Rearming zones. +-- TODO: Retreat zones. -- DONE: Add weapon range. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index d1019de85..82ed4cdac 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -509,6 +509,23 @@ function CHIEF:AddStrateticZone(Zone) return self end +--- Add strategically important zone. +-- @param #CHIEF self +-- @param Ops.OpsZone#OPSZONE OpsZone OPS zone object. +-- @return #CHIEF self +function CHIEF:AddOpsZone(OpsZone) + + -- Start ops zone. + if OpsZone:IsStopped() then + OpsZone:Start() + end + + -- Add to table. + table.insert(self.zonequeue, OpsZone) + + return self +end + --- Set border zone set. -- @param #CHIEF self @@ -663,27 +680,7 @@ function CHIEF:onafterStatus(From, Event, To) self:AddTarget(Target) end - - --[[ - local redalert=true - if self.borderzoneset:Count()>0 then - redalert=inred - end - - if redalert and threat and not contact.target then - - -- Create a new TARGET of the contact group. - local Target=TARGET:New(contact.group) - - -- Set to contact. - contact.target=Target - - -- Add target to queue. - self:AddTarget(Target) - - end - ]] - + end --- @@ -706,6 +703,13 @@ function CHIEF:onafterStatus(From, Event, To) -- Check target queue and assign missions to new targets. self:CheckTargetQueue() + --- + -- Check Strategic Zone Queue + --- + + -- Check target queue and assign missions to new targets. + self:CheckOpsZoneQueue() + --- -- Info General --- @@ -771,7 +775,24 @@ function CHIEF:onafterStatus(From, Event, To) text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target) end self:I(self.lid..text) - end + end + + --- + -- Info Strategic Zones + --- + + -- 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 + + text=text..string.format("\n[%d] %s [%s]: owner=%d [%d]: Blue=%d, Red=%d, Neutral=%d", i, opszone.zone:GetName(), opszone:GetState(), opszone:GetOwner(), opszone:GetPreviousOwner(), opszone.Nblu, opszone.Nred, opszone.Nnut) + + end + self:I(self.lid..text) + end + --- -- Info Assets @@ -1111,6 +1132,63 @@ function CHIEF:CheckTargetQueue() end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Strategic Zone Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +--- Check strategic zone queue. +-- @param #CHIEF self +function CHIEF:CheckOpsZoneQueue() + + -- Number of zones. + local Nzones=#self.zonequeue + + -- Treat special cases. + if Nzones==0 then + return nil + end + + -- Sort results table wrt ?. + local function _sort(a, b) + local taskA=a --Ops.Target#TARGET + local taskB=b --Ops.Target#TARGET + return (taskA.prioweightGroup then + weightGroup=weight + end + end + + env.info(string.format("FF mission requires transport for cargo weight %d", weightGroup)) + + -- Recruit transport assets. + local TransportAvail, assetsTrans, legionsTrans=self:RecruitAssetsForTransport(mission.opstransport, weightGroup) + + if TransportAvail then + + env.info(string.format("FF Transport available with %d carrier assets", #assetsTrans)) + + -- Add cargo assets to transport. + for _,_legion in pairs(legions) do + local legion=_legion --Ops.Legion#LEGION + + local pickupzone=legion.spawnzone + if legion.airbase and legion:IsRunwayOperational() then + pickupzone=ZONE_AIRBASE:New(legion.airbasename, 4000) + end + + -- Add TZC from legion spawn zone to deploy zone. + local tpz=mission.opstransport:AddTransportZoneCombo(pickupzone, mission.opstransport:GetDeployZone()) + mission.opstransport:SetEmbarkZone(legion.spawnzone, tpz) + + -- Add cargo assets to transport. + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion.alias==legion.alias then + mission.opstransport:AddAssetCargo(asset, tpz) + end + end + end + + -- Add carrier assets. + for _,_asset in pairs(assetsTrans) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + mission.opstransport:AddAsset(asset) + end + + + -- Assign TRANSPORT to legions. This also sends the request for the assets. + for _,_legion in pairs(legionsTrans) do + local legion=_legion --Ops.Legion#LEGION + self:TransportAssign(legion, mission.opstransport) + end + + else + -- Uncrecruit transport assets. + LEGION.UnRecruitAssets(assetsTrans) + end + + end + + end + + -- Escort and transport must be available (or not required). + if EscortAvail and TransportAvail then + -- Assign mission to legion(s). for _,_legion in pairs(legions) do local legion=_legion --Ops.Legion#LEGION @@ -741,11 +820,14 @@ end -- @return #table Legions that have recruited assets. function COMMANDER:RecruitAssetsForMission(Mission) + -- Debug info. + env.info(string.format("FF recruiting assets for mission %s [%s]", Mission:GetName(), Mission:GetType())) + -- Cohorts. local Cohorts=Mission.squadrons if not Cohorts then Cohorts={} - for _,_legion in pairs(Mission.mylegions or self.legions) do + for _,_legion in pairs(Mission.specialLegions or self.legions) do local legion=_legion --Ops.Legion#LEGION -- Loops over cohorts. for _,_cohort in pairs(legion.cohorts) do @@ -781,7 +863,7 @@ function COMMANDER:RecruitAssetsForEscort(Mission, Assets) local Cohorts=Mission.squadrons if not Cohorts then Cohorts={} - for _,_legion in pairs(Mission.mylegions or self.legions) do + for _,_legion in pairs(Mission.specialLegions or self.legions) do local legion=_legion --Ops.Legion#LEGION -- Loops over cohorts. for _,_cohort in pairs(legion.cohorts) do @@ -917,33 +999,56 @@ function COMMANDER:CheckTransportQueue() -- 1. Select best assets from legions -- 2. Assign mission to legions that have the best assets. --- - - -- Recruite assets from legions. - local recruited, assets, legions=self:RecruitAssetsForTransport(transport) - - if recruited then - - -- Add asset to transport. - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.isReserved=true - transport:AddAsset(asset) - end - -- Assign transport to legion(s). - for _,_legion in pairs(legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Debug message. - self:I(self.lid..string.format("Assigning transport UID=%d to legion %s", transport.uid, legion.alias)) + -- Get all undelivered cargo ops groups. + local cargoOpsGroups=transport:GetCargoOpsGroups(false) - -- Add mission to legion. - self:TransportAssign(legion, transport) - - end + -- Weight of the heaviest cargo group. Necessary condition that this fits into on carrier unit! + local weightGroup=0 + + -- Calculate the max weight so we know which cohorts can provide carriers. + if #cargoOpsGroups>0 then + for _,_opsgroup in pairs(cargoOpsGroups) do + local opsgroup=_opsgroup --Ops.OpsGroup#OPSGROUP + local weight=opsgroup:GetWeightTotal() + if weight>weightGroup then + weightGroup=weight + end + end + end + + if weightGroup>0 then - -- Only ONE transport is assigned. - return + -- Recruite assets from legions. + local recruited, assets, legions=self:RecruitAssetsForTransport(transport, weightGroup) + + if recruited then + + -- Add asset to transport. + for _,_asset in pairs(assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + transport:AddAsset(asset) + end + + -- Assign transport to legion(s). + for _,_legion in pairs(legions) do + local legion=_legion --Ops.Legion#LEGION + + -- Debug message. + self:I(self.lid..string.format("Assigning transport UID=%d to legion %s", transport.uid, legion.alias)) + + -- Add mission to legion. + self:TransportAssign(legion, transport) + + end + + -- Only ONE transport is assigned. + return + else + -- Not recruited. + LEGION.UnRecruitAssets(assets) + end + end else @@ -964,37 +1069,29 @@ end -- @return #boolean If `true`, enough assets could be recruited. -- @return #table Recruited assets. -- @return #table Legions that have recruited assets. -function COMMANDER:RecruitAssetsForTransport(Transport) - - -- Get all undelivered cargo ops groups. - local cargoOpsGroups=Transport:GetCargoOpsGroups(false) +function COMMANDER:RecruitAssetsForTransport(Transport, CargoWeight) - local weightGroup=0 - - -- At least one group should be spawned. - if #cargoOpsGroups>0 then - - -- Calculate the max weight so we know which cohorts can provide carriers. - for _,_opsgroup in pairs(cargoOpsGroups) do - local opsgroup=_opsgroup --Ops.OpsGroup#OPSGROUP - local weight=opsgroup:GetWeightTotal() - if weight>weightGroup then - weightGroup=weight - end - end - else + if weightGroup==0 then -- No cargo groups! - return false + return false, {}, {} end -- Cohorts. local Cohorts={} for _,_legion in pairs(self.legions) do - local legion=_legion --Ops.Legion#LEGION - -- Loops over cohorts. - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - table.insert(Cohorts, cohort) + local legion=_legion --Ops.Legion#LEGION + + -- Check that runway is operational. + local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true + + if legion:IsRunning() and Runway then + + -- Loops over cohorts. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end end @@ -1006,7 +1103,7 @@ function COMMANDER:RecruitAssetsForTransport(Transport) local NreqMin,NreqMax=Transport:GetRequiredCarriers() -- Recruit assets and legions. - local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.OPSTRANSPORT, nil, NreqMin, NreqMax, TargetVec2, nil, nil, nil, weightGroup) + local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.OPSTRANSPORT, nil, NreqMin, NreqMax, TargetVec2, nil, nil, nil, CargoWeight) return recruited, assets, legions end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 02ce12453..f9dc40ba8 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1864,10 +1864,11 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.ARRIVED) end - local airwing=self:GetAirWing() + --TODO: Check that current base is airwing base. + local airwing=self:GetAirWing() --airwing:GetAirbaseName()==self.currbase:GetName() -- Check what to do. - if airwing then + if airwing and not (self:IsPickingup() or self:IsTransporting()) then -- Debug info. self:T(self.lid..string.format("Airwing asset group %s arrived ==> Adding asset back to stock of airwing %s", self.groupname, airwing.alias)) diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index f42d19909..c9be9aa88 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -275,7 +275,7 @@ function LEGION:AddMission(Mission) end -- Add ops transport to transport Legions. - if Mission.opstransport then + if Mission.opstransport and false then local PickupZone=self.spawnzone local DeployZone=Mission.opstransport.tzcDefault.DeployZone @@ -622,7 +622,7 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) -- Cancel the current ALERT 5 mission. if currM and currM.type==AUFTRAG.Type.ALERT5 then - asset.flightgroup:MissionCancel(currM) + asset.flightgroup:MissionCancel(currM) end @@ -673,8 +673,8 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) if request then if self.isShip then - self:T(self.lid.."FF request late activated") - request.lateActivation=true + --self:T(self.lid.."FF request late activated") + --request.lateActivation=true end end @@ -1793,7 +1793,7 @@ end -- Recruiting and Optimization Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Recruit assets from Cohorts for the given parameters. +--- Recruit assets from Cohorts for the given parameters. **NOTE** that we set the `asset.isReserved=true` flag so it cant be recruited by anyone else. -- @param #table Cohorts Cohorts included. -- @param #string MissionTypeRecruit Mission type for recruiting the cohort assets. -- @param #string MissionTypeOpt Mission type for which the assets are optimized. Default is the same as `MissionTypeRecruit`. @@ -1804,10 +1804,12 @@ end -- @param #number RangeMax Max range in meters. -- @param #number RefuelSystem Refuelsystem. -- @param #number CargoWeight Cargo weight for recruiting transport carriers. +-- @param #table Categories Group categories. +-- @param #table Attributes Group attributes. See `GROUP.Attribute.` -- @return #boolean If `true` enough assets could be recruited. --- @return #table Recruited assets. +-- @return #table Recruited assets. **NOTE** that we set the `asset.isReserved=true` flag so it cant be recruited by anyone else. -- @return #table Legions of recruited assets. -function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, NreqMin, NreqMax, TargetVec2, Payloads, RangeMax, RefuelSystem, CargoWeight) +function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, NreqMin, NreqMax, TargetVec2, Payloads, RangeMax, RefuelSystem, CargoWeight, Categories, Attributes) -- The recruited assets. local Assets={} @@ -1815,10 +1817,39 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, -- Legions of recruited assets. local Legions={} + -- Set MissionTypeOpt to Recruit if nil. if MissionTypeOpt==nil then MissionTypeOpt=MissionTypeRecruit end + --- Function to check category. + local function CheckCategory(_cohort) + local cohort=_cohort --Ops.Cohort#COHORT + if Categories and #Categories>0 then + for _,category in pairs(Categories) do + if category==cohort.category then + return true + end + end + else + return true + end + end + + --- Function to check attribute. + local function CheckAttribute(_cohort) + local cohort=_cohort --Ops.Cohort#COHORT + if Attributes and #Attributes>0 then + for _,attribute in pairs(Attributes) do + if attribute==cohort.attribute then + return true + end + end + else + return true + end + end + -- Loops over cohorts. for _,_cohort in pairs(Cohorts) do local cohort=_cohort --Ops.Cohort#COHORT @@ -1838,9 +1869,19 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, -- Can carry the cargo? local CanCarry=CargoWeight and cohort.cargobayLimit>=CargoWeight or true - -- Check OnDuty, capable, in range and refueling type (if TANKER). - if cohort:IsOnDuty() and Capable and InRange and Refuel and CanCarry then + -- Right category. + local RightCategory=CheckCategory(cohort) + -- Right attribute. + local RightAttribute=CheckAttribute(cohort) + + -- Debug info. + cohort:I(cohort.lid..string.format("State=%s: Capable=%s, InRange=%s, Refuel=%s, CanCarry=%s, RightCategory=%s, RightAttribute=%s", + cohort:GetState(), tostring(Capable), tostring(InRange), tostring(Refuel), tostring(CanCarry), tostring(RightCategory), tostring(RightAttribute))) + + -- Check OnDuty, capable, in range and refueling type (if TANKER). + if cohort:IsOnDuty() and Capable and InRange and Refuel and CanCarry and RightCategory and RightAttribute then + -- Recruit assets from cohort. local assets, npayloads=cohort:RecruitAssets(MissionTypeRecruit, 999) @@ -1893,6 +1934,7 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, -- Add assets to mission. for i=1,Nassets do local asset=Assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + asset.isReserved=true Legions[asset.legion.alias]=asset.legion end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index e5845c8cc..7c8f64225 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3801,20 +3801,7 @@ function OPSGROUP:_GetNextMission() -- Look for first mission that is SCHEDULED. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - - -- Local transport. - local transport=true - if mission.opstransport then - local cargos=mission.opstransport:GetCargoOpsGroups(false) or {} - for _,_opsgroup in pairs(cargos) do - local opscargo=_opsgroup --Ops.OpsGroup#OPSGROUP - if opscargo.groupname==self.groupname then - transport=false - break - end - end - end - + -- TODO: One could think of opsgroup specific start conditions. A legion also checks if "ready" but it can be other criteria for the group to actually start the mission. -- Good example is the above transport. The legion should start the mission but the group should only start after the transport is finished. @@ -3826,12 +3813,24 @@ function OPSGROUP:_GetNextMission() isEscort=false end end + + -- Local transport. + local isTransport=true + if mission.opstransport then + local cargos=mission.opstransport:GetCargoOpsGroups(false) or {} + for _,_opsgroup in pairs(cargos) do + local opscargo=_opsgroup --Ops.OpsGroup#OPSGROUP + if opscargo.groupname==self.groupname then + isTransport=false + break + end + end + end -- Conditons to start. local isScheduled=mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.SCHEDULED local isReadyToGo=(mission:IsReadyToGo() or self.legion) local isImportant=(mission.importance==nil or mission.importance<=vip) - local isTransport=transport -- Check necessary conditions. if isScheduled and isReadyToGo and isImportant and isTransport and isEscort then diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 195debb81..8d5ece50d 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -168,6 +168,7 @@ OPSTRANSPORT.Status={ -- @field #table DisembarkCarriers Carriers where the cargo is directly disembarked to. -- @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. --- Path used for pickup or transport. -- @type OPSTRANSPORT.Path @@ -1225,14 +1226,16 @@ end --- Add asset to transport. -- @param #OPSTRANSPORT self -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset to be added. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:AddAsset(Asset) +function OPSTRANSPORT:AddAsset(Asset, TransportZoneCombo) -- Debug info - self:T(self.lid..string.format("Adding asset \"%s\" to transport", tostring(Asset.spawngroupname))) + self:T(self.lid..string.format("Adding asset carrier \"%s\" to transport", tostring(Asset.spawngroupname))) -- Add asset to table. - self.assets=self.assets or {} + self.assets=self.assets or {} + table.insert(self.assets, Asset) return self @@ -1258,6 +1261,28 @@ function OPSTRANSPORT:DelAsset(Asset) return self end +--- Add cargo asset. +-- @param #OPSTRANSPORT self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset to be added. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:AddAssetCargo(Asset, TransportZoneCombo) + + -- Debug info + self:T(self.lid..string.format("Adding asset cargo \"%s\" to transport and TZC=%s", tostring(Asset.spawngroupname), TransportZoneCombo and TransportZoneCombo.uid or "N/A")) + + -- Add asset to table. + self.assetsCargo=self.assetsCargo or {} + + table.insert(self.assetsCargo, Asset) + + TransportZoneCombo.assetsCargo=TransportZoneCombo.assetsCargo or {} + + TransportZoneCombo.assetsCargo[Asset.spawngroupname]=Asset + + return self +end + --- Add LEGION to the transport. -- @param #OPSTRANSPORT self -- @param Ops.Legion#LEGION Legion The legion. diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index 1127954f8..140f9a688 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -2,7 +2,8 @@ -- -- **Main Features:** -- --- * Monitor if zone is captured. +-- * Monitor if a zone is captured. +-- * Monitor if an airbase is captured. -- -- === -- @@ -17,6 +18,8 @@ -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity of output. -- @field Core.Zone#ZONE zone The zone. +-- @field Wrapper.Airbase#AIRBASE airbase The airbase that is monitored. +-- @field #string airbaseName Name of the airbase that is monitored. -- @field #string zoneName Name of the zone. -- @field #number zoneRadius Radius of the zone in meters. -- @field #number ownerCurrent Coalition of the current owner of the zone. @@ -30,7 +33,7 @@ -- @field #number Tattacked Abs. mission time stamp when an attack was started. -- @field #number dTCapture Time interval in seconds until a zone is captured. -- @field #boolean neutralCanCapture Neutral units can capture. Default `false`. --- @field #drawZone If `true`, draw the zone on the F10 map. +-- @field #boolean drawZone If `true`, draw the zone on the F10 map. -- @extends Core.Fsm#FSM --- Be surprised! @@ -63,6 +66,7 @@ OPSZONE.version="0.1.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_! @@ -113,17 +117,19 @@ function OPSZONE:New(Zone, CoalitionOwner) self:SetObjectCategories() self:SetUnitCategories() + self.drawZone=true + -- Status timer. self.timerStatus=TIMER:New(OPSZONE.Status, self) -- FMS start state is EMPTY. - self:SetStartState("Empty") + self:SetStartState("Stopped") -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("*", "Start", "*") -- Start FSM. - self:AddTransition("*", "Stop", "*") -- Stop FSM. + self:AddTransition("Stopped", "Start", "Empty") -- Start FSM. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Captured", "Guarded") -- Zone was captured. self:AddTransition("*", "Empty", "Empty") -- No red or blue units inside the zone. @@ -372,9 +378,16 @@ function OPSZONE:IsContested() return self.isContested end +--- Check if FMS is stopped. +-- @param #OPSZONE self +-- @return #boolean If `true`, FSM is stopped +function OPSZONE:IsStopped() + local is=self:is("Stopped") + return is +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start/Stop and Status Functions +-- Start/Stop Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start OPSZONE FSM. @@ -395,6 +408,25 @@ function OPSZONE:onafterStart(From, Event, To) end +--- Stop OPSZONE FSM. +-- @param #OPSZONE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSZONE:onafterStop(From, Event, To) + + -- Info. + self:I(self.lid..string.format("Stopping OPSZONE")) + + -- Reinit the timer. + self.timerStatus:Stop() + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Status Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Update status. -- @param #OPSZONE self function OPSZONE:Status() @@ -434,7 +466,7 @@ function OPSZONE:onafterCaptured(From, Event, To, NewOwnerCoalition) -- Set owners. self.ownerPrevious=self.ownerCurrent self.ownerCurrent=NewOwnerCoalition - + end --- On after "Empty" event. @@ -445,7 +477,7 @@ end function OPSZONE:onafterEmpty(From, Event, To) -- Debug info. - self:T(self.lid..string.format("Zone is empty now")) + self:T(self.lid..string.format("Zone is empty EVENT")) end @@ -462,19 +494,6 @@ function OPSZONE:onafterAttacked(From, Event, To, AttackerCoalition) end - ---- On after "Empty" event. --- @param #OPSZONE self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function OPSZONE:onafterEmpty(From, Event, To) - - -- Debug info. - self:T(self.lid..string.format("Zone is empty now")) - -end - --- On after "Defeated" event. -- @param #OPSZONE self -- @param #string From From state. @@ -504,6 +523,14 @@ function OPSZONE:onenterGuarded(From, Event, To) -- Not attacked any more. self.Tattacked=nil + if self.drawZone then + self.zone:UndrawZone() + + local color=self:_GetZoneColor() + + self.zone:DrawZone(nil, color, 1.0, color, 0.7) + end + end --- On enter "Guarded" state. @@ -519,6 +546,34 @@ function OPSZONE:onenterAttacked(From, Event, To) -- Time stamp when the attack started. self.Tattacked=timer.getAbsTime() + if self.drawZone then + self.zone:UndrawZone() + + local color={1,1,1} + + self.zone:DrawZone(nil, color, 1.0, color, 0.9) + end + +end + +--- On enter "Empty" event. +-- @param #OPSZONE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSZONE:onenterEmpty(From, Event, To) + + -- Debug info. + self:T(self.lid..string.format("Zone is empty now")) + + if self.drawZone then + self.zone:UndrawZone() + + local color=self:_GetZoneColor() + + self.zone:DrawZone(nil, color, 1.0, color, 0.2) + end + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -837,6 +892,49 @@ function OPSZONE:OnEventHit(EventData) end +--- Monitor hit events. +-- @param #OPSZONE self +-- @param Core.Event#EVENTDATA EventData The event data. +function OPSZONE:OnEventBaseCaptured(EventData) + + if EventData and EventData.Place and self.airbase and self.airbaseName then + + -- Place is the airbase that was captured. + local airbase=EventData.Place --Wrapper.Airbase#AIRBASE + + -- Check that this airbase belongs or did belong to this warehouse. + if EventData.PlaceName==self.airbaseName then + + end + + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get RGB color of zone depending on current owner. +-- @param #OPSZONE self +-- @return #table RGB color. +function OPSZONE:_GetZoneColor() + + local color={0,0,0} + + if self.ownerCurrent==coalition.side.NEUTRAL then + color={0, 1, 0} + elseif self.ownerCurrent==coalition.side.BLUE then + color={1, 0, 0} + elseif self.ownerCurrent==coalition.side.RED then + color={0, 0, 1} + else + + end + + return color +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 972fa9f674b4f8ff95e2a9989a38fabcd78fb339 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 24 Sep 2021 14:47:36 +0200 Subject: [PATCH 114/141] OPS - Fixed bug in WAREHOUSE isShip - FLIGHTGROUP added damage and ammo checks that trigger FSM events - Improved engage detected targets - Improved OPSZONE --- .../Moose/Functional/Warehouse.lua | 82 ++++-- Moose Development/Moose/Ops/AirWing.lua | 5 +- Moose Development/Moose/Ops/ArmyGroup.lua | 80 ++++-- Moose Development/Moose/Ops/Auftrag.lua | 256 +++++++++++++++-- Moose Development/Moose/Ops/Chief.lua | 70 ++++- Moose Development/Moose/Ops/Commander.lua | 3 +- Moose Development/Moose/Ops/FlightGroup.lua | 167 ++--------- Moose Development/Moose/Ops/Legion.lua | 15 +- Moose Development/Moose/Ops/NavyGroup.lua | 25 +- Moose Development/Moose/Ops/OpsGroup.lua | 269 ++++++++++++++++-- Moose Development/Moose/Ops/OpsZone.lua | 77 ++++- 11 files changed, 759 insertions(+), 290 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 77378e7fb..38f980a88 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -80,7 +80,8 @@ -- @field #string autosavepath Path where the asset file is saved on auto save. -- @field #string autosavefile File name of the auto asset save file. Default is auto generated from warehouse id and name. -- @field #boolean safeparking If true, parking spots for aircraft are considered as occupied if e.g. a client aircraft is parked there. Default false. --- @field #boolean isunit If true, warehouse is represented by a unit instead of a static. +-- @field #boolean isUnit If `true`, warehouse is represented by a unit instead of a static. +-- @field #boolean isShip If `true`, warehouse is represented by a ship unit. -- @field #number lowfuelthresh Low fuel threshold. Triggers the event AssetLowFuel if for any unit fuel goes below this number. -- @field #boolean respawnafterdestroyed If true, warehouse is respawned after it was destroyed. Assets are kept. -- @field #number respawndelay Delay before respawn in seconds. @@ -1590,7 +1591,8 @@ WAREHOUSE = { autosavepath = nil, autosavefile = nil, saveparking = false, - isunit = false, + isUnit = false, + isShip = false, lowfuelthresh = 0.15, respawnafterdestroyed=false, respawndelay = nil, @@ -1655,6 +1657,7 @@ WAREHOUSE = { -- @field #table transportassets Table of transport carrier assets. Each element of the table is a @{#WAREHOUSE.Assetitem}. -- @field #number transportattribute Attribute of transport assets of type @{#WAREHOUSE.Attribute}. -- @field #number transportcategory Category of transport assets of type @{#WAREHOUSE.Category}. +-- @field #boolean lateActivation Assets are spawned in late activated state. --- Item of the warehouse pending queue table. -- @type WAREHOUSE.Pendingitem @@ -1857,39 +1860,45 @@ WAREHOUSE.version="1.0.2" -- @return #WAREHOUSE self function WAREHOUSE:New(warehouse, alias) + -- Inherit everthing from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #WAREHOUSE + -- Check if just a string was given and convert to static. if type(warehouse)=="string" then - local warehousename=warehouse + local warehousename=warehouse warehouse=UNIT:FindByName(warehousename) if warehouse==nil then warehouse=STATIC:FindByName(warehousename, true) - self.isunit=false - else - self.isunit=true - if warehouse:IsShip() then - env.info("FF warehouse is ship!") - self.isShip=true - end end end -- Nil check. if warehouse==nil then - BASE:E("ERROR: Warehouse does not exist!") + env.error("ERROR: Warehouse does not exist!") return nil end + + -- Check if we have a STATIC or UNIT object. + if warehouse:IsInstanceOf("STATIC") then + self.isUnit=false + elseif warehouse:IsInstanceOf("UNIT") then + self.isUnit=true + if warehouse:IsShip() then + self.isShip=true + end + else + env.error("ERROR: Warehouse is neither STATIC nor UNIT object!") + return nil + end -- Set alias. self.alias=alias or warehouse:GetName() - -- Print version. - env.info(string.format("Adding warehouse v%s for structure %s with alias %s", WAREHOUSE.version, warehouse:GetName(), self.alias)) - - -- Inherit everthing from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #WAREHOUSE - -- Set some string id for output to DCS.log file. self.lid=string.format("WAREHOUSE %s | ", self.alias) + + -- Print version. + self:I(self.lid..string.format("Adding warehouse v%s for structure %s [isUnit=%s, isShip=%s]", WAREHOUSE.version, warehouse:GetName(), tostring(self:IsUnit()), tostring(self:IsShip()))) -- Set some variables. self.warehouse=warehouse @@ -3324,7 +3333,7 @@ end --- Check if runway is operational. -- @param #WAREHOUSE self --- @return #boolean If true, runway is operational. +-- @return #boolean If `true`, runway is operational. function WAREHOUSE:IsRunwayOperational() if self.airbase then if self.runwaydestroyed then @@ -3360,6 +3369,27 @@ function WAREHOUSE:GetRunwayRepairtime() return 0 end +--- Check if warehouse physical representation is a unit (not a static) object. +-- @param #WAREHOUSE self +-- @return #boolean If `true`, warehouse object is a unit. +function WAREHOUSE:IsUnit() + return self.isUnit +end + +--- Check if warehouse physical representation is a static (not a unit) object. +-- @param #WAREHOUSE self +-- @return #boolean If `true`, warehouse object is a static. +function WAREHOUSE:IsStatic() + return not self.isUnit +end + +--- Check if warehouse physical representation is a ship. +-- @param #WAREHOUSE self +-- @return #boolean If `true`, warehouse object is a ship. +function WAREHOUSE:IsShip() + return self.isShip +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM states ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -4399,7 +4429,7 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) -- Delete request from queue because it will never be possible. -- Unless(!) at least one is a moving warehouse, which could, e.g., be an aircraft carrier. - if not (self.isunit or Request.warehouse.isunit) then + if not (self.isUnit or Request.warehouse.isUnit) then self:_DeleteQueueItem(Request, self.queue) end @@ -5828,8 +5858,6 @@ function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, late -- Late activation. template.lateActivation=lateactivated - - --env.info("FF lateActivation="..tostring(template.lateActivation)) template.route.points[1].x = coord.x template.route.points[1].y = coord.z @@ -6108,18 +6136,10 @@ function WAREHOUSE:_RouteGround(group, request) end for n,wp in ipairs(Waypoints) do - --env.info(n) local tf=self:_SimpleTaskFunctionWP("warehouse:_PassingWaypoint",group, n, #Waypoints) group:SetTaskWaypoint(wp, tf) end - -- Task function triggering the arrived event at the last waypoint. - --local TaskFunction = self:_SimpleTaskFunction("warehouse:_Arrived", group) - - -- Put task function on last waypoint. - --local Waypoint = Waypoints[#Waypoints] - --group:SetTaskWaypoint(Waypoint, TaskFunction) - -- Route group to destination. group:Route(Waypoints, 1) @@ -7676,7 +7696,7 @@ function WAREHOUSE:_SimpleTaskFunction(Function, group) local DCSScript = {} DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:FindByName(\"%s\") ', groupname) -- The group that executes the task function. Very handy with the "...". - if self.isunit then + if self.isUnit then DCSScript[#DCSScript+1] = string.format("local mywarehouse = UNIT:FindByName(\"%s\") ", warehouse) -- The unit that holds the warehouse self object. else DCSScript[#DCSScript+1] = string.format("local mywarehouse = STATIC:FindByName(\"%s\") ", warehouse) -- The static that holds the warehouse self object. @@ -7707,7 +7727,7 @@ function WAREHOUSE:_SimpleTaskFunctionWP(Function, group, n, N) local DCSScript = {} DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:FindByName(\"%s\") ', groupname) -- The group that executes the task function. Very handy with the "...". - if self.isunit then + if self.isUnit then DCSScript[#DCSScript+1] = string.format("local mywarehouse = UNIT:FindByName(\"%s\") ", warehouse) -- The unit that holds the warehouse self object. else DCSScript[#DCSScript+1] = string.format("local mywarehouse = STATIC:FindByName(\"%s\") ", warehouse) -- The static that holds the warehouse self object. diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index f4a7bf747..a2024e1c2 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -147,9 +147,10 @@ AIRWING.version="0.9.0" -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Spawn in air or hot ==> Needs WAREHOUSE update. +-- TODO: Spawn in air ==> Needs WAREHOUSE update. +-- DONE: Spawn in air. -- TODO: Make special request to transfer squadrons to anther airwing (or warehouse). --- TODO: Check that airbase has enough parking spots if a request is BIG. Alternatively, split requests. +-- TODO: Check that airbase has enough parking spots if a request is BIG. -- DONE: Add squadrons to warehouse. -- DONE: Build mission queue. -- DONE: Find way to start missions. diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index a08bd3a43..eb0d99cb8 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -55,10 +55,13 @@ ARMYGROUP = { engage = {}, } ---- Target +--- Engage Target. -- @type ARMYGROUP.Target -- @field Ops.Target#TARGET Target The target. -- @field Core.Point#COORDINATE Coordinate Last known coordinate of the target. +-- @field Ops.OpsGroup#OPSGROUP.Waypoint Waypoint the waypoint created to go to the target. +-- @field #number roe ROE backup. +-- @field #number alarmstate Alarm state backup. --- Army Group version. -- @field #string version @@ -332,7 +335,11 @@ end function ARMYGROUP:IsCombatReady() local combatready=true - if self:IsRearming() or self:IsRetreating() or self.outofAmmo or self:IsEngaging() or self:is("Retreated") or self:IsDead() or self:IsStopped() or self:IsInUtero() then + if self:IsRearming() or self:IsRetreating() or self:IsOutOfAmmo() or self:IsEngaging() or self:IsDead() or self:IsStopped() or self:IsInUtero() then + combatready=false + end + + if self:IsPickingup() or self:IsLoading() or self:IsTransporting() or self:IsLoaded() or self:IsCargo() or self:IsCarrier() then combatready=false end @@ -356,26 +363,20 @@ function ARMYGROUP:Status() if alive then - --- - -- Detection - --- + -- Update position etc. + self:_UpdatePosition() -- Check if group has detected any units. - if self.detectionOn then - self:_CheckDetectedUnits() - end + self:_CheckDetectedUnits() -- Check ammo status. self:_CheckAmmoStatus() - - -- Update position etc. - self:_UpdatePosition() - - -- Check if group got stuck. - self:_CheckStuck() - + -- Check damage of elements and group. self:_CheckDamage() + + -- Check if group got stuck. + self:_CheckStuck() -- Update engagement. if self:IsEngaging() then @@ -462,6 +463,21 @@ function ARMYGROUP:Status() end self:I(self.lid..text) end + + --- + -- Engage Detected Targets + --- + if self:IsCruising() and self.detectionOn and self.engagedetectedOn then + + local targetgroup, targetdist=self:_GetDetectedTarget() + + -- If we found a group, we engage it. + if targetgroup then + self:I(self.lid..string.format("Engaging target group %s at distance %d meters", targetgroup:GetName(), targetdist)) + self:EngageTarget(targetgroup) + end + + end --- @@ -580,7 +596,7 @@ function ARMYGROUP:onafterSpawned(From, Event, To) -- Update route. if #self.waypoints>1 then - self:Cruise(nil, self.option.Formation) + self:__Cruise(-0.1, nil, self.option.Formation) else self:FullStop() end @@ -1003,11 +1019,13 @@ function ARMYGROUP:onafterEngageTarget(From, Event, To, Target) -- Target coordinate. self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) - -- TODO: Backup current ROE and alarm state and reset after disengage. + -- Backup ROE and alarm state. + self.engage.roe=self:GetROE() + self.engage.alarmstate=self:GetAlarmstate() -- Switch ROE and alarm state. self:SwitchAlarmstate(ENUMS.AlarmState.Auto) - self:SwitchROE(ENUMS.ROE.WeaponFree) + self:SwitchROE(ENUMS.ROE.OpenFire) -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid @@ -1025,17 +1043,19 @@ end function ARMYGROUP:_UpdateEngageTarget() if self.engage.Target and self.engage.Target:IsAlive() then - - --env.info("FF Update Engage Target "..self.engage.Target:GetName()) - local vec3=self.engage.Target:GetCoordinate():GetVec3() + -- Get current position vector. + local vec3=self.engage.Target:GetVec3() - local dist=UTILS.VecDist2D(vec3, self.engage.Coordinate:GetVec3()) + -- Distance to last known position of target. + local dist=UTILS.VecDist3D(vec3, self.engage.Coordinate:GetVec3()) + -- Check if target moved more than 100 meters. if dist>100 then --env.info("FF Update Engage Target Moved "..self.engage.Target:GetName()) + -- Update new position. self.engage.Coordinate:UpdateFromVec3(vec3) -- ID of current waypoint. @@ -1053,7 +1073,10 @@ function ARMYGROUP:_UpdateEngageTarget() end else + + -- Target not alive any more == Disengage. self:Disengage() + end end @@ -1066,8 +1089,17 @@ end function ARMYGROUP:onafterDisengage(From, Event, To) self:T(self.lid.."Disengage Target") - -- TODO: Reset ROE and alarm state. - self:_CheckGroupDone(1) + -- Restore previous ROE and alarm state. + self:SwitchROE(self.engage.roe) + self:SwitchAlarmstate(self.engage.alarmstate) + + -- Remove current waypoint + if self.engage.Waypoint then + self:RemoveWaypointByID(self.engage.Waypoint.uid) + end + + -- Check group is done + self:_CheckGroupDone(1) end --- On after "Rearmed" event. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 853b0e9a2..7483f7435 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -152,16 +152,21 @@ -- -- === -- --- ![Banner Image](..\Presentations\OPS\Auftrag\_Main.png) --- -- # The AUFTRAG Concept -- -- The AUFTRAG class significantly simplifies the workflow of using DCS tasks. -- -- You can think of an AUFTRAG as document, which contains the mission briefing, i.e. information about the target location, mission altitude, speed and various other parameters. -- This document can be handed over directly to a pilot (or multiple pilots) via the @{Ops.FlightGroup#FLIGHTGROUP} class. The pilots will then execute the mission. --- The AUFTRAG document can also be given to an AIRWING. The airwing will then determine the best assets (pilots and payloads) available for the job. --- One more up the food chain, an AUFTRAG can be passed to a WINGCOMMANDER. The wing commander will find the best AIRWING and pass the job over to it. +-- +-- The AUFTRAG document can also be given to an AIRWING. The airwing will then determine the best assets (pilots and payloads) available for the job. +-- +-- Similarly, an AUFTRAG can be given to ground or navel groups via the @{Ops.ArmyGroup#ARMYGROUP} or @{Ops.NavyGroup#NAVYGROUP} classes, respectively. These classes have also +-- AIRWING analouges, which are called BRIGADE and FLEET. Brigades and fleets will likewise select the best assets they have available and pass on the AUFTRAG to them. +-- +-- +-- One more up the food chain, an AUFTRAG can be passed to a COMMANDER. The commander will recruit the best assets of AIRWINGs, BRIGADEs and/or FLEETs and pass the job over to it. +-- -- -- # Airborne Missions -- @@ -169,7 +174,7 @@ -- -- ## Anti-Ship -- --- An anti-ship mission can be created with the @{#AUFTRAG.NewANTISHIP}(*Target, Altitude*) function. +-- An anti-ship mission can be created with the @{#AUFTRAG.NewANTISHIP}() function. -- -- ## AWACS -- @@ -225,7 +230,7 @@ -- -- ## RECON -- --- Not implemented yet. +-- An reconnaissance mission can be created with the @{#AUFTRAG.NewRECON}() function. -- -- ## RESCUE HELO -- @@ -260,40 +265,43 @@ -- -- # Assigning Missions -- --- An AUFTRAG can be assigned to groups, airwings or wingcommanders +-- An AUFTRAG can be assigned to groups (FLIGHTGROUP, ARMYGROUP, NAVYGROUP), legions (AIRWING, BRIGADE, FLEET) or to a COMMANDER. -- -- ## Group Level -- -- ### Flight Group -- --- Assigning an AUFTRAG to a flight groups is done via the @{Ops.FlightGroup#FLIGHTGROUP.AddMission} function. See FLIGHTGROUP docs for details. +-- Assigning an AUFTRAG to a flight group is done via the @{Ops.FlightGroup#FLIGHTGROUP.AddMission} function. See FLIGHTGROUP docs for details. +-- +-- ### Army Group +-- +-- Assigning an AUFTRAG to an army group is done via the @{Ops.ArmyGroup#ARMYGROUP.AddMission} function. See ARMYGROUP docs for details. -- -- ### Navy Group -- --- Assigning an AUFTRAG to a navy groups is done via the @{Ops.NavyGroup#NAVYGROUP.AddMission} function. See NAVYGROUP docs for details. +-- Assigning an AUFTRAG to a navy group is done via the @{Ops.NavyGroup#NAVYGROUP.AddMission} function. See NAVYGROUP docs for details. -- -- ## Legion Level -- -- Adding an AUFTRAG to an airwing is done via the @{Ops.AirWing#AIRWING.AddMission} function. See AIRWING docs for further details. --- Similarly, an AUFTRAG can be added to a brigade via the @{Ops.Brigade#BRIGADE.AddMission} function +-- Similarly, an AUFTRAG can be added to a brigade via the @{Ops.Brigade#BRIGADE.AddMission} function. -- -- ## Commander Level -- --- Assigning an AUFTRAG to acommander is done via the @{Ops.Commander#COMMANDER.AddMission} function. See COMMADER docs for details. +-- Assigning an AUFTRAG to acommander is done via the @{Ops.Commander#COMMANDER.AddMission} function. See COMMANDER docs for details. -- --- ## Chief Level --- --- Assigning an AUFTRAG to a wing commander is done via the @{Ops.Chief#CHIEF.AddMission} function. See CHIEF docs for details. -- --- --- -- # Events -- --- The AUFTRAG class creates many useful (FSM) events, which can be used in the mission designers script. +-- The AUFTRAG class creates many useful (FSM) events, which can be used in the mission designers script. +-- +-- TODO -- -- -- # Examples -- +-- TODO +-- -- -- @field #AUFTRAG AUFTRAG = { @@ -599,8 +607,8 @@ function AUFTRAG:New(Type) self:SetStartState(self.status) -- PLANNED --> (QUEUED) --> (REQUESTED) --> SCHEDULED --> STARTED --> EXECUTING --> DONE - self:AddTransition("*", "Planned", AUFTRAG.Status.PLANNED) -- Mission is in planning stage. - self:AddTransition(AUFTRAG.Status.PLANNED, "Queued", AUFTRAG.Status.QUEUED) -- Mission is in queue of an AIRWING. + self:AddTransition("*", "Planned", AUFTRAG.Status.PLANNED) -- Mission is in planning stage. Could be in the queue of a COMMANDER or CHIEF. + self:AddTransition(AUFTRAG.Status.PLANNED, "Queued", AUFTRAG.Status.QUEUED) -- Mission is in queue of a LEGION. self:AddTransition(AUFTRAG.Status.QUEUED, "Requested", AUFTRAG.Status.REQUESTED) -- Mission assets have been requested from the warehouse. self:AddTransition(AUFTRAG.Status.REQUESTED, "Scheduled", AUFTRAG.Status.SCHEDULED) -- Mission added to the first ops group queue. @@ -624,6 +632,171 @@ function AUFTRAG:New(Type) self:AddTransition("*", "ElementDestroyed", "*") self:AddTransition("*", "GroupDead", "*") self:AddTransition("*", "AssetDead", "*") + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Status". + -- @function [parent=#AUFTRAG] Status + -- @param #AUFTRAG self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#AUFTRAG] __Status + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". + -- @function [parent=#AUFTRAG] Stop + -- @param #AUFTRAG self + + --- Triggers the FSM event "Stop" after a delay. + -- @function [parent=#AUFTRAG] __Stop + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Planned". + -- @function [parent=#AUFTRAG] Planned + -- @param #AUFTRAG self + + --- Triggers the FSM event "Planned" after a delay. + -- @function [parent=#AUFTRAG] __Planned + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Queued". + -- @function [parent=#AUFTRAG] Queued + -- @param #AUFTRAG self + + --- Triggers the FSM event "Queued" after a delay. + -- @function [parent=#AUFTRAG] __Queued + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Requested". + -- @function [parent=#AUFTRAG] Requested + -- @param #AUFTRAG self + + --- Triggers the FSM event "Requested" after a delay. + -- @function [parent=#AUFTRAG] __Requested + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Scheduled". + -- @function [parent=#AUFTRAG] Scheduled + -- @param #AUFTRAG self + + --- Triggers the FSM event "Scheduled" after a delay. + -- @function [parent=#AUFTRAG] __Scheduled + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Started". + -- @function [parent=#AUFTRAG] Started + -- @param #AUFTRAG self + + --- Triggers the FSM event "Started" after a delay. + -- @function [parent=#AUFTRAG] __Started + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Executing". + -- @function [parent=#AUFTRAG] Executing + -- @param #AUFTRAG self + + --- Triggers the FSM event "Executing" after a delay. + -- @function [parent=#AUFTRAG] __Executing + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Cancel". + -- @function [parent=#AUFTRAG] Cancel + -- @param #AUFTRAG self + + --- Triggers the FSM event "Cancel" after a delay. + -- @function [parent=#AUFTRAG] __Cancel + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + --- On after "Cancel" event. + -- @function [parent=#AUFTRAG] OnAfterCancel + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Done". + -- @function [parent=#AUFTRAG] Done + -- @param #AUFTRAG self + + --- Triggers the FSM event "Done" after a delay. + -- @function [parent=#AUFTRAG] __Done + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + --- On after "Done" event. + -- @function [parent=#AUFTRAG] OnAfterDone + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Success". + -- @function [parent=#AUFTRAG] Success + -- @param #AUFTRAG self + + --- Triggers the FSM event "Success" after a delay. + -- @function [parent=#AUFTRAG] __Success + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + --- On after "Success" event. + -- @function [parent=#AUFTRAG] OnAfterSuccess + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- Triggers the FSM event "Failure". + -- @function [parent=#AUFTRAG] Failure + -- @param #AUFTRAG self + + --- Triggers the FSM event "Failure" after a delay. + -- @function [parent=#AUFTRAG] __Failure + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + --- On after "Failure" event. + -- @function [parent=#AUFTRAG] OnAfterFailure + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- Triggers the FSM event "Repeat". + -- @function [parent=#AUFTRAG] Repeat + -- @param #AUFTRAG self + + --- Triggers the FSM event "Repeat" after a delay. + -- @function [parent=#AUFTRAG] __Repeat + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + --- On after "Repeat" event. + -- @function [parent=#AUFTRAG] OnAfterRepeat + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. -- Init status update. self:__Status(-1) @@ -1921,6 +2094,44 @@ function AUFTRAG:SetEngageAltitude(Altitude) return self end +--- Enable to automatically engage detected targets. +-- @param #AUFTRAG self +-- @param #number RangeMax Max range in NM. Only detected targets within this radius from the group will be engaged. Default is 25 NM. +-- @param #table TargetTypes Types of target attributes that will be engaged. See [DCS enum attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes). Default "All". +-- @param Core.Set#SET_ZONE EngageZoneSet Set of zones in which targets are engaged. Default is anywhere. +-- @param Core.Set#SET_ZONE NoEngageZoneSet Set of zones in which targets are *not* engaged. Default is nowhere. +-- @return #AUFTRAG self +function AUFTRAG:SetEngageDetected(RangeMax, TargetTypes, EngageZoneSet, NoEngageZoneSet) + + -- Ensure table. + if TargetTypes then + if type(TargetTypes)~="table" then + TargetTypes={TargetTypes} + end + else + TargetTypes={"All"} + end + + -- Ensure SET_ZONE if ZONE is provided. + if EngageZoneSet and EngageZoneSet:IsInstanceOf("ZONE_BASE") then + local zoneset=SET_ZONE:New():AddZone(EngageZoneSet) + EngageZoneSet=zoneset + end + if NoEngageZoneSet and NoEngageZoneSet:IsInstanceOf("ZONE_BASE") then + local zoneset=SET_ZONE:New():AddZone(NoEngageZoneSet) + NoEngageZoneSet=zoneset + end + + -- Set parameters. + self.engagedetectedOn=true + self.engagedetectedRmax=UTILS.NMToMeters(RangeMax or 25) + self.engagedetectedTypes=TargetTypes + self.engagedetectedEngageZones=EngageZoneSet + self.engagedetectedNoEngageZones=NoEngageZoneSet + + return self +end + --- Set mission altitude. This is the altitude of the waypoint create where the DCS task is executed. -- @param #AUFTRAG self -- @param #string Altitude Altitude in feet. @@ -4039,8 +4250,9 @@ end -- @param #AUFTRAG self -- @param Wrapper.Group#GROUP group Group. -- @param #number randomradius Random radius in meters. +-- @param #table surfacetypes Surface types of random zone. -- @return Core.Point#COORDINATE Coordinate where the mission is executed. -function AUFTRAG:GetMissionWaypointCoord(group, randomradius) +function AUFTRAG:GetMissionWaypointCoord(group, randomradius, surfacetypes) -- Check if a coord has been explicitly set. if self.missionWaypointCoord then @@ -4052,12 +4264,12 @@ function AUFTRAG:GetMissionWaypointCoord(group, randomradius) end -- Create waypoint coordinate half way between us and the target. - local waypointcoord=group:GetCoordinate():GetIntermediateCoordinate(self:GetTargetCoordinate(), self.missionFraction) + local waypointcoord=group:GetCoordinate():GetIntermediateCoordinate(self:GetTargetCoordinate(), self.missionFraction) local alt=waypointcoord.y -- Add some randomization. if randomradius then - waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), randomradius):GetRandomCoordinate():SetAltitude(alt, false) + waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), randomradius):GetRandomCoordinate(nil, nil, surfacetypes):SetAltitude(alt, false) end -- Set altitude of mission waypoint. diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 82ed4cdac..e19263e13 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -413,6 +413,47 @@ function CHIEF:GetCommander() return self.commander end + +--- Add an AIRWING to the chief's commander. +-- @param #CHIEF self +-- @param Ops.AirWing#AIRWING Airwing The airwing to add. +-- @return #CHIEF self +function CHIEF:AddAirwing(Airwing) + + -- Add airwing to the commander. + self:AddLegion(Airwing) + + return self +end + +--- Add a BRIGADE to the chief's commander. +-- @param #CHIEF self +-- @param Ops.Brigade#BRIGADE Brigade The brigade to add. +-- @return #CHIEF self +function CHIEF:AddBrigade(Brigade) + + -- Add brigade to the commander + self:AddLegion(Brigade) + + return self +end + +--- Add a LEGION to the chief's commander. +-- @param #CHIEF self +-- @param Ops.Legion#LEGION Legion The legion to add. +-- @return #CHIEF self +function CHIEF:AddLegion(Legion) + + -- Set chief of the legion. + Legion.chief=self + + -- Add legion to the commander. + self.commander:AddLegion(Legion) + + return self +end + + --- Add mission to mission queue of the COMMANDER. -- @param #CHIEF self -- @param Ops.Auftrag#AUFTRAG Mission Mission to be added. @@ -1181,10 +1222,8 @@ function CHIEF:CheckOpsZoneQueue() env.info(string.format("Zone %s is owned by coalition %d", opszone.zone:GetName(), ownercoalition)) -- Recruit ground assets that - local recruited, assets, legions=self:RecruitAssetsForZone(opszone, AUFTRAG.Type.PATROLZONE, 1, 3, {Group.Category.GROUND}, {GROUP.Attribute.GROUND_INFANTRY}) - - - + local recruited=self:RecruitAssetsForZone(opszone, AUFTRAG.Type.PATROLZONE, 1, 3, {Group.Category.GROUND}, {GROUP.Attribute.GROUND_INFANTRY}) + end end @@ -1494,8 +1533,6 @@ end -- @param #table Categories Group categories of the assets. -- @param #table Attributes Generalized group attributes. -- @return #boolean If `true` enough assets could be recruited. --- @return #table Assets that have been recruited from all legions. --- @return #table Legions that have recruited assets. function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax, Categories, Attributes) -- Cohorts. @@ -1520,7 +1557,7 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax -- Target position. local TargetVec2=OpsZone.zone:GetVec2() - -- Recruite assets. + -- Recruite infantry assets. local recruitedInf, assetsInf, legionsInf=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2, nil, nil, nil, nil, Categories, Attributes) if recruitedInf then @@ -1536,9 +1573,9 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax end end - -- Recruite assets. + -- Recruite carrier assets. This need to be ground or helicopters to deploy at a zone. local recruitedTrans, assetsTrans, legionsTrans= - LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.OPSTRANSPORT, nil, NassetsMin, NassetsMax, TargetVec2, nil, nil, nil, weightMax, {Group.Category.HELICOPTER, Group.Category.GROUND}) + LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.OPSTRANSPORT, nil, 1, 1, TargetVec2, nil, nil, nil, weightMax, {Group.Category.HELICOPTER, Group.Category.GROUND}) local transport=nil --Ops.OpsTransport#OPSTRANSPORT if recruitedTrans then @@ -1550,7 +1587,15 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax -- Add cargo assets to transport. for _,_legion in pairs(legionsInf) do local legion=_legion --Ops.Legion#LEGION - local tpz=transport:AddTransportZoneCombo(legion.spawnzone, OpsZone.zone) + + -- Pickup at spawnzone or at airbase if the legion warehouse has one. + local pickupzone=legion.spawnzone + if legion.airbase and legion:IsRunwayOperational() then + pickupzone=ZONE_AIRBASE:New(legion.airbasename, 4000) + end + + local tpz=transport:AddTransportZoneCombo(pickupzone, OpsZone.zone) + for _,_asset in pairs(assetsInf) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if asset.legion.alias==legion.alias then @@ -1579,6 +1624,7 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax -- Create Patrol zone mission. local mission=AUFTRAG:NewPATROLZONE(OpsZone.zone) + mission:SetEngageDetected() for _,asset in pairs(assetsInf) do mission:AddAsset(asset) @@ -1593,11 +1639,13 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax OpsZone.missionPatrol=mission + return true else LEGION.UnRecruitAssets(assetsInf) + return false end - return recruited, assets, legions + return nil end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 4856e1a57..e73e9381d 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -20,7 +20,7 @@ -- @field #table legions Table of legions which are commanded. -- @field #table missionqueue Mission queue. -- @field #table transportqueue Transport queue. --- @field Ops.ChiefOfStaff#CHIEF chief Chief of staff. +-- @field Ops.Chief#CHIEF chief Chief of staff. -- @extends Core.Fsm#FSM --- Be surprised! @@ -736,6 +736,7 @@ function COMMANDER:CheckMissionQueue() for _,_legion in pairs(legions) do local legion=_legion --Ops.Legion#LEGION + -- Set pickup zone to spawn zone or airbase if the legion has one that is operational. local pickupzone=legion.spawnzone if legion.airbase and legion:IsRunwayOperational() then pickupzone=ZONE_AIRBASE:New(legion.airbasename, 4000) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index f9dc40ba8..3ab6c3257 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -501,55 +501,6 @@ function FLIGHTGROUP:SetFuelCriticalRTB(switch) return self end ---- Enable to automatically engage detected targets. --- @param #FLIGHTGROUP self --- @param #number RangeMax Max range in NM. Only detected targets within this radius from the group will be engaged. Default is 25 NM. --- @param #table TargetTypes Types of target attributes that will be engaged. See [DCS enum attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes). Default "All". --- @param Core.Set#SET_ZONE EngageZoneSet Set of zones in which targets are engaged. Default is anywhere. --- @param Core.Set#SET_ZONE NoEngageZoneSet Set of zones in which targets are *not* engaged. Default is nowhere. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetEngageDetectedOn(RangeMax, TargetTypes, EngageZoneSet, NoEngageZoneSet) - - -- Ensure table. - if TargetTypes then - if type(TargetTypes)~="table" then - TargetTypes={TargetTypes} - end - else - TargetTypes={"All"} - end - - -- Ensure SET_ZONE if ZONE is provided. - if EngageZoneSet and EngageZoneSet:IsInstanceOf("ZONE_BASE") then - local zoneset=SET_ZONE:New():AddZone(EngageZoneSet) - EngageZoneSet=zoneset - end - if NoEngageZoneSet and NoEngageZoneSet:IsInstanceOf("ZONE_BASE") then - local zoneset=SET_ZONE:New():AddZone(NoEngageZoneSet) - NoEngageZoneSet=zoneset - end - - -- Set parameters. - self.engagedetectedOn=true - self.engagedetectedRmax=UTILS.NMToMeters(RangeMax or 25) - self.engagedetectedTypes=TargetTypes - self.engagedetectedEngageZones=EngageZoneSet - self.engagedetectedNoEngageZones=NoEngageZoneSet - - -- Ensure detection is ON or it does not make any sense. - self:SetDetection(true) - - return self -end - ---- Disable to automatically engage detected targets. --- @param #FLIGHTGROUP self --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetEngageDetectedOff() - self.engagedetectedOn=false - return self -end - --- Enable that the group is despawned after landing. This can be useful to avoid DCS taxi issues with other AI or players or jamming taxiways. -- @param #FLIGHTGROUP self @@ -841,23 +792,28 @@ function FLIGHTGROUP:Status() -- FSM state. local fsmstate=self:GetState() + + -- Is group alive? + local alive=self:IsAlive() -- Update position. self:_UpdatePosition() - --- - -- Detection - --- - -- Check if group has detected any units. - if self.detectionOn then - self:_CheckDetectedUnits() - end - + self:_CheckDetectedUnits() + + -- Check ammo status. + self:_CheckAmmoStatus() + + -- 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 @@ -930,11 +886,6 @@ function FLIGHTGROUP:Status() local lp0=unit:GetLife0() local parking=element.parking and tostring(element.parking.TerminalID) or "X" - -- Check if element is not dead and we missed an event. - --if life<=0 and element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then - -- self:ElementDead(element) - --end - -- Get ammo. local ammo=self:GetAmmoElement(element) @@ -952,7 +903,9 @@ function FLIGHTGROUP:Status() -- Distance travelled --- - if self.verbose>=4 and self:IsAlive() then + if self.verbose>=4 and alive then + + -- TODO: _Check distance travelled. -- Travelled distance since last check. local ds=self.travelds @@ -998,18 +951,14 @@ function FLIGHTGROUP:Status() end - --- - -- Tasks & Missions - --- - - self:_PrintTaskAndMissionStatus() - --- -- Fuel State --- + -- TODO: _CheckFuelState() function. + -- Only if group is in air. - if self:IsAlive() and self.group:IsAirborne(true) then + if alive and self.group:IsAirborne(true) then local fuelmin=self:GetFuelMin() @@ -1051,77 +1000,7 @@ function FLIGHTGROUP:Status() --- if self:IsAirborne() and self:IsFuelGood() and self.detectionOn and self.engagedetectedOn then - -- Target. - local targetgroup=nil --Wrapper.Group#GROUP - local targetdist=math.huge - - -- Loop over detected groups. - for _,_group in pairs(self.detectedgroups:GetSet()) do - local group=_group --Wrapper.Group#GROUP - - if group and group:IsAlive() then - - -- Get 3D vector of target. - local targetVec3=group:GetVec3() - - -- Distance to target. - local distance=UTILS.VecDist3D(self.position, targetVec3) - - if distance<=self.engagedetectedRmax and distance=1 then @@ -702,7 +699,7 @@ function NAVYGROUP:onafterSpawned(From, Event, To) -- Update route. if #self.waypoints>1 then - self:Cruise() + self:__Cruise(-0.1) else self:FullStop() end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 7c8f64225..229a1a1b6 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -85,6 +85,12 @@ -- -- @field Core.Astar#ASTAR Astar path finding. -- @field #boolean ispathfinding If true, group is on pathfinding route. +-- +-- @field #boolean engagedetectedOn If `true`, auto engage detected targets. +-- @field #number engagedetectedRmax Max range in NM. Only detected targets within this radius from the group will be engaged. Default is 25 NM. +-- @field #table engagedetectedTypes Types of target attributes that will be engaged. See [DCS enum attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes). Default "All". +-- @field Core.Set#SET_ZONE engagedetectedEngageZones Set of zones in which targets are engaged. Default is anywhere. +-- @field Core.Set#SET_ZONE engagedetectedNoEngageZones Set of zones in which targets are *not* engaged. Default is nowhere. -- -- @field #OPSGROUP.Radio radio Current radio settings. -- @field #OPSGROUP.Radio radioDefault Default radio settings. @@ -607,8 +613,6 @@ function OPSGROUP:New(group) self:AddTransition("*", "InUtero", "InUtero") -- Deactivated group goes back to mummy. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - --self:AddTransition("*", "Status", "*") -- Status update. - self:AddTransition("*", "Destroyed", "*") -- The whole group is dead. self:AddTransition("*", "Damaged", "*") -- Someone in the group took damage. @@ -929,6 +933,7 @@ end -- @param #boolean Switch If `true`, detection is on. If `false` or `nil`, detection is off. Default is off. -- @return #OPSGROUP self function OPSGROUP:SetDetection(Switch) + self:T(self.lid..string.format("Detection is %s", tostring(Switch))) self.detectionOn=Switch return self end @@ -1112,6 +1117,59 @@ function OPSGROUP:GetHighestThreat() return threat, levelmax end +--- Enable to automatically engage detected targets. +-- @param #OPSGROUP self +-- @param #number RangeMax Max range in NM. Only detected targets within this radius from the group will be engaged. Default is 25 NM. +-- @param #table TargetTypes Types of target attributes that will be engaged. See [DCS enum attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes). Default "All". +-- @param Core.Set#SET_ZONE EngageZoneSet Set of zones in which targets are engaged. Default is anywhere. +-- @param Core.Set#SET_ZONE NoEngageZoneSet Set of zones in which targets are *not* engaged. Default is nowhere. +-- @return #OPSGROUP self +function OPSGROUP:SetEngageDetectedOn(RangeMax, TargetTypes, EngageZoneSet, NoEngageZoneSet) + + -- Ensure table. + if TargetTypes then + if type(TargetTypes)~="table" then + TargetTypes={TargetTypes} + end + else + TargetTypes={"All"} + end + + -- Ensure SET_ZONE if ZONE is provided. + if EngageZoneSet and EngageZoneSet:IsInstanceOf("ZONE_BASE") then + local zoneset=SET_ZONE:New():AddZone(EngageZoneSet) + EngageZoneSet=zoneset + end + if NoEngageZoneSet and NoEngageZoneSet:IsInstanceOf("ZONE_BASE") then + local zoneset=SET_ZONE:New():AddZone(NoEngageZoneSet) + NoEngageZoneSet=zoneset + end + + -- Set parameters. + self.engagedetectedOn=true + self.engagedetectedRmax=UTILS.NMToMeters(RangeMax or 25) + self.engagedetectedTypes=TargetTypes + self.engagedetectedEngageZones=EngageZoneSet + self.engagedetectedNoEngageZones=NoEngageZoneSet + + -- Debug info. + self:T(self.lid..string.format("Engage detected ON: Rmax=%d NM", UTILS.MetersToNM(self.engagedetectedRmax))) + + -- Ensure detection is ON or it does not make any sense. + self:SetDetection(true) + + return self +end + +--- Disable to automatically engage detected targets. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:SetEngageDetectedOff() + self:T(self.lid..string.format("Engage detected OFF")) + self.engagedetectedOn=false + return self +end + --- Check if an element of the group has line of sight to a coordinate. -- @param #OPSGROUP self -- @param Core.Point#COORDINATE Coordinate The position to which we check the LoS. @@ -2042,7 +2100,7 @@ function OPSGROUP:HasPassedFinalWaypoint() return self.passedfinalwp end ---- Check if the group is currently rearming. +--- Check if the group is currently rearming or on its way to the rearming place. -- @param #OPSGROUP self -- @return #boolean If true, group is rearming. function OPSGROUP:IsRearming() @@ -2050,6 +2108,42 @@ function OPSGROUP:IsRearming() return rearming end +--- Check if the group is completely out of ammo. +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is out-of-ammo. +function OPSGROUP:IsOutOfAmmo() + return self.outofAmmo +end + +--- Check if the group is out of bombs. +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is out of bombs. +function OPSGROUP:IsOutOfBombs() + return self.outofBombs +end + +--- Check if the group is out of guns. +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is out of guns. +function OPSGROUP:IsOutOfGuns() + return self.outofGuns +end + +--- Check if the group is out of missiles. +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is out of missiles. +function OPSGROUP:IsOutOfMissiles() + return self.outofMissiles +end + +--- Check if the group is out of torpedos. +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is out of torpedos. +function OPSGROUP:IsOutOfTorpedos() + return self.outofTorpedos +end + + --- Check if the group has currently switched a LASER on. -- @param #OPSGROUP self -- @return #boolean If true, LASER of the group is on. @@ -2057,14 +2151,23 @@ function OPSGROUP:IsLasing() return self.spot.On end ---- Check if the group is currently retreating. +--- Check if the group is currently retreating or retreated. -- @param #OPSGROUP self --- @return #boolean If true, group is retreating. +-- @return #boolean If true, group is retreating or retreated. function OPSGROUP:IsRetreating() - local is=self:is("Retreating") + local is=self:is("Retreating") or self:is("Retreated") return is end +--- Check if the group is retreated (has reached its retreat zone). +-- @param #OPSGROUP self +-- @return #boolean If true, group is retreated. +function OPSGROUP:IsRetreated() + local is=self:is("Retreated") + return is +end + + --- Check if the group is currently returning to a zone. -- @param #OPSGROUP self -- @return #boolean If true, group is returning. @@ -2812,15 +2915,6 @@ function OPSGROUP:SetTask(DCSTask) if self:IsAlive() then - if self.taskcurrent>0 then - - -- TODO: Why the hell did I do this? It breaks scheduled tasks. I comment it out for now to see where it fails. - --local task=self:GetTaskCurrent() - --self:RemoveTask(task) - --self.taskcurrent=0 - - end - -- Inject enroute tasks. if self.taskenroute and #self.taskenroute>0 then if tostring(DCSTask.id)=="ComboTask" then @@ -2836,7 +2930,6 @@ function OPSGROUP:SetTask(DCSTask) end -- Set task. - --self.group:SetTask(DCSTask) self.controller:setTask(DCSTask) -- Debug info. @@ -2861,7 +2954,6 @@ function OPSGROUP:PushTask(DCSTask) if self:IsAlive() then -- Push task. - --self.group:PushTask(DCSTask) self.controller:pushTask(DCSTask) -- Debug info. @@ -3315,7 +3407,6 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Insert into task queue. Not sure any more, why I added this. But probably if a task is just executed without having been put into the queue. if self:GetTaskCurrent()==nil then - --env.info("FF adding current task to queue") table.insert(self.taskqueue, Task) end @@ -3355,8 +3446,15 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Parameters. local zone=Task.dcstask.params.zone --Core.Zone#ZONE + local surfacetypes=nil + if self:IsArmygroup() then + surfacetypes={land.SurfaceType.LAND, land.SurfaceType.ROAD} + elseif self:IsNavygroup() then + surfacetypes={land.SurfaceType.WATER, land.SurfaceType.SHALLOW_WATER} + end + -- Random coordinate in zone. - local Coordinate=zone:GetRandomCoordinate() + local Coordinate=zone:GetRandomCoordinate(nil, nil, surfacetypes) --Coordinate:MarkToAll("Random Patrol Zone Coordinate") @@ -3968,6 +4066,11 @@ function OPSGROUP:onafterMissionExecute(From, Event, To, Mission) -- Set mission status to EXECUTING. Mission:Executing() + + -- Set auto engage detected targets. + if Mission.engagedetectedOn then + self:SetEngageDetectedOn(UTILS.MetersToNM(Mission.engagedetectedRmax), Mission.engagedetectedTypes, Mission.engagedetectedEngageZones, Mission.engagedetectedNoEngageZones) + end end @@ -4124,6 +4227,11 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) Mission.patroldata.noccupied=Mission.patroldata.noccupied-1 AIRWING.UpdatePatrolPointMarker(Mission.patroldata) end + + -- Switch auto engage detected off. This IGNORES that engage detected had been activated for the group! + if Mission.engagedetectedOn then + self:SetEngageDetectedOff() + end -- ROE to default. if Mission.optionROE then @@ -4201,17 +4309,20 @@ function OPSGROUP:RouteToMission(mission, delay) -- Catch dead or stopped groups. if self:IsDead() or self:IsStopped() then + self:T(self.lid..string.format("Route To Mission: I am DEAD or STOPPED! Ooops...")) return end -- OPSTRANSPORT: Just add the ops transport to the queue. if mission.type==AUFTRAG.Type.OPSTRANSPORT then + self:T(self.lid..string.format("Route To Mission: I am OPSTRANSPORT! Add transport and return...")) self:AddOpsTransport(mission.opstransport) return end - -- ALERT 5: Just set the mission to executing. + -- ALERT5: Just set the mission to executing. if mission.type==AUFTRAG.Type.ALERT5 then + self:T(self.lid..string.format("Route To Mission: I am ALERT5! Go right to MissionExecute()...")) self:MissionExecute(mission) return end @@ -4219,15 +4330,28 @@ function OPSGROUP:RouteToMission(mission, delay) -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid - -- Random radius. + -- Ingress waypoint coordinate where the mission is executed. + local waypointcoord=nil --Core.Point#COORDINATE + + -- Random radius of 1000 meters. local randomradius=1000 + + -- Surface types. + local surfacetypes=nil + if self:IsArmygroup() then + surfacetypes={land.SurfaceType.LAND, land.SurfaceType.ROAD} + elseif self:IsNavygroup() then + surfacetypes={land.SurfaceType.WATER, land.SurfaceType.SHALLOW_WATER} + end + + -- Get ingress waypoint. if mission.type==AUFTRAG.Type.PATROLZONE then - randomradius=nil + local zone=mission.engageTarget:GetObject() --Core.Zone#ZONE + waypointcoord=zone:GetRandomCoordinate(nil , nil, surfacetypes) + else + waypointcoord=mission:GetMissionWaypointCoord(self.group, randomradius, surfacetypes) end - -- Get coordinate where the mission is executed. - local waypointcoord=mission:GetMissionWaypointCoord(self.group, randomradius) - -- Add enroute tasks. for _,task in pairs(mission.enrouteTasks) do self:AddTaskEnroute(task) @@ -4532,9 +4656,17 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Zone object. local zone=task.dcstask.params.zone --Core.Zone#ZONE - -- Random coordinate in zone. - local Coordinate=zone:GetRandomCoordinate() + -- Surface types. + local surfacetypes=nil + if self:IsArmygroup() then + surfacetypes={land.SurfaceType.LAND, land.SurfaceType.ROAD} + elseif self:IsNavygroup() then + surfacetypes={land.SurfaceType.WATER, land.SurfaceType.SHALLOW_WATER} + end + -- Random coordinate in zone. + local Coordinate=zone:GetRandomCoordinate(nil, nil, surfacetypes) + -- Speed and altitude. local Speed=UTILS.KmphToKnots(task.dcstask.params.speed or self.speedCruise) local Altitude=task.dcstask.params.altitude and UTILS.MetersToFeet(task.dcstask.params.altitude) or nil @@ -7820,7 +7952,7 @@ end -- @param #OPSGROUP self function OPSGROUP:_CheckDetectedUnits() - if self.group and not self:IsDead() then + if self.detectionOn and self.group and not self:IsDead() then -- Get detected DCS units. local detectedtargets=self.group:GetDetectedTargets() @@ -10636,6 +10768,87 @@ function OPSGROUP:ClearWaypoints(IndexMin, IndexMax) --self.waypoints={} end +--- Get target group. +-- @param #OPSGROUP self +-- @return Wrapper.Group#GROUP Detected target group. +-- @return #number Distance to target. +function OPSGROUP:_GetDetectedTarget() + + -- Target. + local targetgroup=nil --Wrapper.Group#GROUP + local targetdist=math.huge + + -- Loop over detected groups. + for _,_group in pairs(self.detectedgroups:GetSet()) do + local group=_group --Wrapper.Group#GROUP + + if group and group:IsAlive() then + + -- Get 3D vector of target. + local targetVec3=group:GetVec3() + + -- Distance to target. + local distance=UTILS.VecDist3D(self.position, targetVec3) + + if distance<=self.engagedetectedRmax and distance0 and self.Nred>0 then + + elseif self.Nblu>0 then + + elseif self.Nred>0 then + + end + -- Status update. self.timerStatus:Start(1, 60) @@ -528,12 +563,12 @@ function OPSZONE:onenterGuarded(From, Event, To) local color=self:_GetZoneColor() - self.zone:DrawZone(nil, color, 1.0, color, 0.7) + self.zone:DrawZone(nil, color, 1.0, color, 0.5) end end ---- On enter "Guarded" state. +--- On enter "Attacked" state. -- @param #OPSZONE self -- @param #string From From state. -- @param #string Event Event. @@ -549,9 +584,10 @@ function OPSZONE:onenterAttacked(From, Event, To) if self.drawZone then self.zone:UndrawZone() - local color={1,1,1} - self.zone:DrawZone(nil, color, 1.0, color, 0.9) + local color={1,204/255,204/255} + + self.zone:DrawZone(nil, color, 1.0, color, 0.5) end end @@ -580,7 +616,7 @@ end -- Scan Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Add a platoon to the brigade. +--- Scan zone. -- @param #OPSZONE self -- @return #OPSZONE self function OPSZONE:Scan() @@ -720,7 +756,20 @@ function OPSZONE:Scan() self.Nred=Nred self.Nblu=Nblu self.Nnut=Nnut - + + return self +end + +--- Evaluate zone. +-- @param #OPSZONE self +-- @return #OPSZONE self +function OPSZONE:EvaluateZone() + + -- Set values. + local Nred=self.Nred + local Nblu=self.Nblu + local Nnut=self.Nnut + if self:IsRed() then --- @@ -746,7 +795,7 @@ function OPSZONE:Scan() else - -- Still red units in red zone. + -- Red units in red zone. if Nblu>0 then @@ -758,6 +807,9 @@ function OPSZONE:Scan() if self:IsAttacked() and self:IsContested() then self:Defeated(coalition.side.BLUE) + elseif self:IsEmpty() then + -- Red units left zone and returned (or from initial Empty state). + self:Guarded() end end @@ -810,6 +862,9 @@ function OPSZONE:Scan() if self:IsAttacked() and self:IsContested() then -- Blue defeated read attack. self:Defeated(coalition.side.RED) + elseif self:IsEmpty() then + -- Blue units left zone and returned (or from initial Empty state). + self:Guarded() end end @@ -858,7 +913,7 @@ function OPSZONE:Scan() self:E(self.lid.."ERROR!") end - return self + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -923,11 +978,11 @@ function OPSZONE:_GetZoneColor() local color={0,0,0} if self.ownerCurrent==coalition.side.NEUTRAL then - color={0, 1, 0} + color={1, 1, 1} elseif self.ownerCurrent==coalition.side.BLUE then - color={1, 0, 0} - elseif self.ownerCurrent==coalition.side.RED then color={0, 0, 1} + elseif self.ownerCurrent==coalition.side.RED then + color={1, 0, 0} else end From 994689f05a673574b4c72158a371a093466856b2 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 25 Sep 2021 20:18:16 +0200 Subject: [PATCH 115/141] OPS - Improved transport assignments --- Moose Development/Moose/Ops/Auftrag.lua | 30 +- Moose Development/Moose/Ops/Brigade.lua | 2 +- Moose Development/Moose/Ops/Chief.lua | 67 +-- Moose Development/Moose/Ops/Commander.lua | 204 +++------ Moose Development/Moose/Ops/Legion.lua | 444 ++++++++++++++----- Moose Development/Moose/Ops/OpsGroup.lua | 42 +- Moose Development/Moose/Ops/OpsTransport.lua | 19 +- 7 files changed, 466 insertions(+), 342 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 7483f7435..c6c301994 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -91,7 +91,13 @@ -- @field Core.Set#SET_GROUP transportGroupSet Groups to be transported. -- @field Core.Point#COORDINATE transportPickup Coordinate where to pickup the cargo. -- @field Core.Point#COORDINATE transportDropoff Coordinate where to drop off the cargo. +-- -- @field Ops.OpsTransport#OPSTRANSPORT opstransport OPS transport assignment. +-- @field #number NcarriersMin Min number of required carrier assets. +-- @field #number NcarriersMax Max number of required carrier assets. +-- @field Core.Zone#ZONE transportDeployZone Deploy zone of an OPSTRANSPORT. +-- @field Core.Zone#ZONE transportDisembarkZone Disembark zone of an OPSTRANSPORT. +-- @field #table transportLegions Legions explicitly requested for providing carrier assets. -- -- @field #number artyRadius Radius in meters. -- @field #number artyShots Number of shots fired. @@ -588,7 +594,7 @@ function AUFTRAG:New(Type) self:SetPriority() self:SetTime() self:SetRequiredAssets() - self:SetRequiredCarriers() + --self:SetRequiredCarriers() self.engageAsGroup=true self.dTevaluate=5 @@ -2182,14 +2188,11 @@ end -- @param #number NcarriersMin Number of carriers *at least* required. Default 1. -- @param #number NcarriersMax Number of carriers *at most* used for transportation. Default is same as `NcarriersMin`. -- @return #AUFTRAG self -function AUFTRAG:SetTransportForAssets(DeployZone, DisembarkZone, NcarriersMin, NcarriersMax) +function AUFTRAG:SetRequiredTransport(DeployZone, DisembarkZone, NcarriersMin, NcarriersMax) -- OPS transport from pickup to deploy zone. - self.opstransport=OPSTRANSPORT:New(nil, nil, DeployZone) - - if DisembarkZone then - self.opstransport:SetDisembarkZone(DisembarkZone) - end + self.transportDeployZone=DeployZone + self.transportDisembarkZone=DisembarkZone -- Set required carriers. self:SetRequiredCarriers(NcarriersMin, NcarriersMax) @@ -2226,18 +2229,13 @@ end -- @return #AUFTRAG self function AUFTRAG:SetRequiredCarriers(NcarriersMin, NcarriersMax) - self.nCarriersMin=NcarriersMin or 1 + self.NcarriersMin=NcarriersMin or 1 - self.nCarriersMax=NcarriersMax or self.nCarriersMin + self.NcarriersMax=NcarriersMax or self.NcarriersMin -- Ensure that max is at least equal to min. - if self.nCarriersMax %s", self.strategy, Strategy)) end @@ -1563,80 +1563,31 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax if recruitedInf then env.info(string.format("Recruited %d assets from for PATROL mission", #assetsInf)) - - -- Get max weight. - local weightMax=nil - for _,_asset in pairs(assetsInf) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - if weightMax==nil or weightMax<=asset.weight then - weightMax=asset.weight - end - end - - -- Recruite carrier assets. This need to be ground or helicopters to deploy at a zone. - local recruitedTrans, assetsTrans, legionsTrans= - LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.OPSTRANSPORT, nil, 1, 1, TargetVec2, nil, nil, nil, weightMax, {Group.Category.HELICOPTER, Group.Category.GROUND}) - - local transport=nil --Ops.OpsTransport#OPSTRANSPORT - if recruitedTrans then - env.info(string.format("Recruited %d assets for OPSTRANSPORT mission", #assetsTrans)) - - -- Create an OPSTRANSPORT assignment. - transport=OPSTRANSPORT:New(nil, nil, OpsZone.zone) - - -- Add cargo assets to transport. - for _,_legion in pairs(legionsInf) do - local legion=_legion --Ops.Legion#LEGION - -- Pickup at spawnzone or at airbase if the legion warehouse has one. - local pickupzone=legion.spawnzone - if legion.airbase and legion:IsRunwayOperational() then - pickupzone=ZONE_AIRBASE:New(legion.airbasename, 4000) - end - - local tpz=transport:AddTransportZoneCombo(pickupzone, OpsZone.zone) - - for _,_asset in pairs(assetsInf) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.legion.alias==legion.alias then - transport:AddAssetCargo(asset, tpz) - end - end - end - - -- Add carrier assets. - for _,_asset in pairs(assetsTrans) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - transport:AddAsset(asset) - end - - - -- Assign TRANSPORT to legions. This also sends the request for the assets. - for _,_legion in pairs(legionsTrans) do - local legion=_legion --Ops.Legion#LEGION - self.commander:TransportAssign(legion, transport) - end - - else - -- Uncrecruite - LEGION.UnRecruitAssets(assetsTrans) - end + -- Recruit transport assets for infantry. + local recruitedTrans, transport=LEGION.AssignAssetsForTransport(self.commander, self.commander.legions, assetsInf, 1, 1, OpsZone.zone, nil, {Group.Category.HELICOPTER, Group.Category.GROUND}) + -- Create Patrol zone mission. local mission=AUFTRAG:NewPATROLZONE(OpsZone.zone) mission:SetEngageDetected() + -- Add assets to mission. for _,asset in pairs(assetsInf) do mission:AddAsset(asset) end + -- Attach OPS transport to mission. mission.opstransport=transport + -- Assign mission to legions. for _,_legion in pairs(legionsInf) do local legion=_legion --Ops.Legion#LEGION self.commander:MissionAssign(legion, mission) end + -- Attach mission to ops zone. + -- TODO: Need a better way! OpsZone.missionPatrol=mission return true diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index e73e9381d..d57bab942 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -645,9 +645,6 @@ end -- @param #COMMANDER self function COMMANDER:CheckMissionQueue() - -- TODO: Sort mission queue. wrt what? Threat level? - -- Currently, we sort wrt to priority. So that should reflect the threat level of the mission target. - -- Number of missions. local Nmissions=#self.missionqueue @@ -708,69 +705,18 @@ function COMMANDER:CheckMissionQueue() -- Escort requested and available. if EscortAvail then - -- Check if mission assets need a transport. - if mission.opstransport then + -- Check if mission assets need a transport. + if mission.NcarriersMin then - -- Weight of the heaviest cargo group. Necessary condition that this fits into on carrier unit! - local weightGroup=0 - - -- Calculate the max weight of the cargo assets. - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - local weight=asset.weight - if weight>weightGroup then - weightGroup=weight - end - end + -- Recruit carrier assets for transport. + local Transport=nil + local Legions=mission.transportLegions or self.legions - env.info(string.format("FF mission requires transport for cargo weight %d", weightGroup)) + TransportAvail, Transport=LEGION.AssignAssetsForTransport(self, Legions, assets, mission.NcarriersMin, mission.NcarriersMax, mission.transportDeployZone, mission.transportDisembarkZone) - -- Recruit transport assets. - local TransportAvail, assetsTrans, legionsTrans=self:RecruitAssetsForTransport(mission.opstransport, weightGroup) - - if TransportAvail then - - env.info(string.format("FF Transport available with %d carrier assets", #assetsTrans)) - - -- Add cargo assets to transport. - for _,_legion in pairs(legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Set pickup zone to spawn zone or airbase if the legion has one that is operational. - local pickupzone=legion.spawnzone - if legion.airbase and legion:IsRunwayOperational() then - pickupzone=ZONE_AIRBASE:New(legion.airbasename, 4000) - end - - -- Add TZC from legion spawn zone to deploy zone. - local tpz=mission.opstransport:AddTransportZoneCombo(pickupzone, mission.opstransport:GetDeployZone()) - mission.opstransport:SetEmbarkZone(legion.spawnzone, tpz) - - -- Add cargo assets to transport. - for _,_asset in pairs(assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - if asset.legion.alias==legion.alias then - mission.opstransport:AddAssetCargo(asset, tpz) - end - end - end - - -- Add carrier assets. - for _,_asset in pairs(assetsTrans) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - mission.opstransport:AddAsset(asset) - end - - - -- Assign TRANSPORT to legions. This also sends the request for the assets. - for _,_legion in pairs(legionsTrans) do - local legion=_legion --Ops.Legion#LEGION - self:TransportAssign(legion, mission.opstransport) - end - - else - -- Uncrecruit transport assets. - LEGION.UnRecruitAssets(assetsTrans) + -- Add opstransport to mission. + if TransportAvail and Transport then + mission.opstransport=Transport end end @@ -856,102 +802,34 @@ end --- Recruit assets performing an escort mission for a given asset. -- @param #COMMANDER self -- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @param #table Assets Table of assets. +-- @param #table Assets Table of assets to be escorted. -- @return #boolean If `true`, enough assets could be recruited or no escort was required in the first place. function COMMANDER:RecruitAssetsForEscort(Mission, Assets) - -- Cohorts. - local Cohorts=Mission.squadrons - if not Cohorts then - Cohorts={} - for _,_legion in pairs(Mission.specialLegions or self.legions) do - local legion=_legion --Ops.Legion#LEGION - -- Loops over cohorts. - for _,_cohort in pairs(legion.cohorts) do - local cohort=_cohort --Ops.Cohort#COHORT - table.insert(Cohorts, cohort) - end - end - end - -- Is an escort requested in the first place? if Mission.NescortMin and Mission.NescortMax and (Mission.NescortMin>0 or Mission.NescortMax>0) then - - -- Debug info. - self:I(self.lid..string.format("Reqested escort for mission %s [%s]. Required assets=%d-%d", Mission:GetName(), Mission:GetType(), Mission.NescortMin,Mission.NescortMax)) - - -- Escorts for each asset. - local Escorts={} - - local EscortAvail=true - for _,_asset in pairs(Assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Recruit escort asset for the mission asset. - local Erecruited, eassets, elegions=LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.ESCORT, nil, Mission.NescortMin, Mission.NescortMax) - - if Erecruited then - Escorts[asset.spawngroupname]={EscortLegions=elegions, EscortAssets=eassets} - else - -- Could not find escort for this asset ==> Escort not possible ==> Break the loop. - EscortAvail=false - break - end - end - - -- ALL escorts could be recruited. - if EscortAvail then - - local N=0 - for groupname,value in pairs(Escorts) do - - local Elegions=value.EscortLegions - local Eassets=value.EscortAssets - - for _,_legion in pairs(Elegions) do - local legion=_legion --Ops.Legion#LEGION - - -- Create and ESCORT mission for this asset. - local escort=AUFTRAG:NewESCORT(groupname) - - -- Reserve assts and add to mission. - for _,_asset in pairs(Eassets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.isReserved=true - escort:AddAsset(asset) - N=N+1 - end - - -- Assign mission to legion. - self:MissionAssign(legion, escort) + + -- Cohorts. + local Cohorts=Mission.squadrons + if not Cohorts then + Cohorts={} + for _,_legion in pairs(Mission.specialLegions or self.legions) do + local legion=_legion --Ops.Legion#LEGION + -- Loops over cohorts. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) end - end - - -- Debug info. - self:I(self.lid..string.format("Recruited %d escort assets for mission %s [%s]", N, Mission:GetName(), Mission:GetType())) - - -- Yup! - return true - else - - -- Debug info. - self:I(self.lid..string.format("Could not get at least one escort for mission %s [%s]! Unrecruit all recruited assets", Mission:GetName(), Mission:GetType())) - - -- Could not get at least one escort. Unrecruit all recruited ones. - for groupname,value in pairs(Escorts) do - local Eassets=value.EscortAssets - LEGION.UnRecruitAssets(Eassets) - end - - -- No,no! - return false + end end - else - -- No escort required. - return true - end + -- Call LEGION function but provide COMMANDER as self. + local assigned=LEGION.AssignAssetsForEscort(self, Cohorts, Assets, Mission.NescortMin, Mission.NescortMin) + + return assigned + end + return true end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1072,7 +950,7 @@ end -- @return #table Legions that have recruited assets. function COMMANDER:RecruitAssetsForTransport(Transport, CargoWeight) - if weightGroup==0 then + if CargoWeight==0 then -- No cargo groups! return false, {}, {} end @@ -1221,6 +1099,30 @@ function COMMANDER:GetLegionsForMission(Mission) return legions end +--- Get assets on given mission or missions. +-- @param #COMMANDER self +-- @param #table MissionTypes Types on mission to be checked. Default all. +-- @return #table Assets on pending requests. +function COMMANDER:GetAssetsOnMission(MissionTypes) + + local assets={} + + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + -- Check if this mission type is requested. + if AUFTRAG.CheckMissionType(mission.type, MissionTypes) then + + for _,_asset in pairs(mission.assets or {}) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + table.insert(assets, asset) + end + end + end + + return assets +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index d908e11a5..ee8d7e74d 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -83,9 +83,11 @@ function LEGION:New(WarehouseName, LegionName) -- From State --> Event --> To State self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + self:AddTransition("*", "MissionAssign", "*") -- Recruit assets, add to queue and request immediately. self:AddTransition("*", "TransportRequest", "*") -- Add a (mission) request to the warehouse. self:AddTransition("*", "TransportCancel", "*") -- Cancel transport. + self:AddTransition("*", "TransportAssign", "*") -- Recruit assets, add to queue and request immediately. self:AddTransition("*", "OpsOnMission", "*") -- An OPSGROUP was send on a Mission (AUFTRAG). @@ -118,18 +120,27 @@ function LEGION:New(WarehouseName, LegionName) -- @param #LEGION self -- @param Ops.Auftrag#AUFTRAG Mission The mission. - --- Triggers the FSM event "MissionCancel" after a delay. - -- @function [parent=#LEGION] __MissionCancel + + --- Triggers the FSM event "MissionAssign". + -- @function [parent=#LEGION] MissionAssign -- @param #LEGION self - -- @param #number delay Delay in seconds. + -- @param Ops.Legion#LEGION Legion The legion from which the mission assets are requested. -- @param Ops.Auftrag#AUFTRAG Mission The mission. - --- On after "MissionCancel" event. - -- @function [parent=#LEGION] OnAfterMissionCancel + --- Triggers the FSM event "MissionAssign" after a delay. + -- @function [parent=#LEGION] __MissionAssign + -- @param #LEGION self + -- @param #number delay Delay in seconds. + -- @param Ops.Legion#LEGION Legion The legion from which the mission assets are requested. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "MissionAssign" event. + -- @function [parent=#LEGION] OnAfterMissionAssign -- @param #LEGION self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. + -- @param Ops.Legion#LEGION Legion The legion from which the mission assets are requested. -- @param Ops.Auftrag#AUFTRAG Mission The mission. @@ -153,6 +164,44 @@ function LEGION:New(WarehouseName, LegionName) -- @param Ops.Auftrag#AUFTRAG Mission The mission. + --- Triggers the FSM event "MissionCancel" after a delay. + -- @function [parent=#LEGION] __MissionCancel + -- @param #LEGION self + -- @param #number delay Delay in seconds. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + --- On after "MissionCancel" event. + -- @function [parent=#LEGION] OnAfterMissionCancel + -- @param #LEGION self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Auftrag#AUFTRAG Mission The mission. + + + --- Triggers the FSM event "TransportAssign". + -- @function [parent=#LEGION] TransportAssign + -- @param #LEGION self + -- @param Ops.Legion#LEGION Legion The legion from which the transport assets are requested. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- Triggers the FSM event "TransportAssign" after a delay. + -- @function [parent=#LEGION] __TransportAssign + -- @param #LEGION self + -- @param #number delay Delay in seconds. + -- @param Ops.Legion#LEGION Legion The legion from which the transport assets are requested. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- On after "TransportAssign" event. + -- @function [parent=#LEGION] OnAfterTransportAssign + -- @param #LEGION self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Legion#LEGION Legion The legion from which the transport assets are requested. + -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + + --- Triggers the FSM event "TransportRequest". -- @function [parent=#LEGION] TransportRequest -- @param #LEGION self @@ -274,30 +323,6 @@ function LEGION:AddMission(Mission) if Mission.type==AUFTRAG.Type.ALERT5 then Mission:_TargetFromObject(self:GetCoordinate()) end - - -- Add ops transport to transport Legions. - if Mission.opstransport and false then - - local PickupZone=self.spawnzone - local DeployZone=Mission.opstransport.tzcDefault.DeployZone - - -- Add a new TZC: from pickup here to the deploy zone. - local tzc=Mission.opstransport:AddTransportZoneCombo(PickupZone, DeployZone) - - --TODO: Depending on "from where to where" the assets need to transported, we need to set ZONE_AIRBASE etc. - - --Mission.opstransport:SetPickupZone(self.spawnzone) - --Mission.opstransport:SetEmbarkZone(self.spawnzone) - - -- Loop over all defined transport legions. - for _,_legion in pairs(Mission.transportLegions) do - local legion=_legion --Ops.Legion#LEGION - - -- Add ops transport to legion. - legion:AddOpsTransport(Mission.opstransport) - end - - end -- Add mission to queue. table.insert(self.missionqueue, Mission) @@ -481,20 +506,38 @@ function LEGION:_GetNextMission() -- Reserve assets and add to mission. for _,_asset in pairs(assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.isReserved=true mission:AddAsset(asset) end - + -- Recruit asset for escorting recruited mission assets. local EscortAvail=self:RecruitAssetsForEscort(mission, assets) + + -- Transport available (or not required). + local TransportAvail=true -- Is escort required and available? if EscortAvail then + + -- Recruit carrier assets for transport. + local Transport=nil + if mission.NcarriersMin then + local Legions=mission.transportLegions or {self} + TransportAvail, Transport=self:AssignAssetsForTransport(Legions, assets, mission.NcarriersMin, mission.NcarriersMax, mission.transportDeployZone, mission.transportDisembarkZone) + end + + -- Add opstransport to mission. + if TransportAvail and Transport then + mission.opstransport=Transport + end + + end + + if EscortAvail and TransportAvail then -- Got a missin. - return mission + return mission else -- Recruited assets but no requested escort available. Unrecruit assets! - LEGION.UnRecruitAssets(assets, mission) + LEGION.UnRecruitAssets(assets, mission) end end -- recruited mission assets @@ -552,7 +595,6 @@ function LEGION:_GetNextTransport() -- Add asset to transport. for _,_asset in pairs(assets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.isReserved=true transport:AddAsset(asset) end @@ -572,6 +614,27 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- On after "MissionAssign" event. Mission is added to a LEGION mission queue and already requested. Needs assets to be added to the mission already. +-- @param #LEGION 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. +function LEGION: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 legion. + Legion:AddMission(Mission) + + -- Directly request the mission as the assets have already been selected. + Legion:MissionRequest(Mission) + +end + + --- On after "MissionRequest" event. Performs a self request to the warehouse for the mission assets. Sets mission status to REQUESTED. -- @param #LEGION self -- @param #string From From state. @@ -683,7 +746,27 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) end ---- On after "MissionRequest" event. Performs a self request to the warehouse for the mission assets. Sets mission status to REQUESTED. +--- On after "TransportAssign" event. Transport is added to a LEGION transport queue and assets are requested from the LEGION warehouse. +-- @param #LEGION self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Legion#LEGION Legion The LEGION. +-- @param Ops.OpsTransport#OPSTRANSPORT The transport. +function LEGION:onafterTransportAssign(From, Event, To, Legion, Transport) + + -- Debug info. + self:I(self.lid..string.format("Assigning transport %d to legion %s", Transport.uid, Legion.alias)) + + -- Add mission to legion. + Legion:AddOpsTransport(Transport) + + -- Directly request the mission as the assets have already been selected. + Legion:TransportRequest(Transport) + +end + +--- On after "TransportRequest" event. Performs a self request to the warehouse for the transport assets. Sets transport status to REQUESTED. -- @param #LEGION self -- @param #string From From state. -- @param #string Event Event. @@ -987,6 +1070,7 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) -- Check if we have a cohort or if this was some other request. if cohort then + -- Debug info. self:I(self.lid..string.format("Cohort asset spawned %s", asset.spawngroupname)) -- Create a flight group. @@ -1666,6 +1750,8 @@ end -- @param #LEGION self -- @param Ops.OpsTransport#OPSTRANSPORT Transport The OPS transport. -- @return #boolean If `true`, enough assets could be recruited. +-- @return #table assets Recruited assets. +-- @return #table legions Legions of recruited assets. function LEGION:RecruitAssetsForTransport(Transport) -- Get all undelivered cargo ops groups. @@ -1716,82 +1802,15 @@ function LEGION:RecruitAssetsForEscort(Mission, Assets) -- Debug info. self:I(self.lid..string.format("Reqested escort for mission %s [%s]. Required assets=%d-%d", Mission:GetName(), Mission:GetType(), Mission.NescortMin,Mission.NescortMax)) - -- Escorts for each asset. - local Escorts={} + --TODO: Maybe add escortCohorts as mission option? - local EscortAvail=true - for _,_asset in pairs(Assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - -- Recruit escort asset for the mission asset. - local Erecruited, eassets, elegions=LEGION.RecruitCohortAssets(self.cohorts, AUFTRAG.Type.ESCORT, nil, Mission.NescortMin, Mission.NescortMax) - - if Erecruited then - Escorts[asset.spawngroupname]={EscortLegions=elegions, EscortAssets=eassets} - else - -- Could not find escort for this asset ==> Escort not possible ==> Break the loop. - EscortAvail=false - break - end - end + -- Call LEGION function but provide COMMANDER as self. + local assigned=LEGION.AssignAssetsForEscort(self, self.cohorts, Assets, Mission.NescortMin, Mission.NescortMin) - -- ALL escorts could be recruited. - if EscortAvail then - - local N=0 - for groupname,value in pairs(Escorts) do - - local Elegions=value.EscortLegions - local Eassets=value.EscortAssets - - for _,_legion in pairs(Elegions) do - local legion=_legion --Ops.Legion#LEGION - - -- Create and ESCORT mission for this asset. - local escort=AUFTRAG:NewESCORT(groupname) - - -- Reserve assts and add to mission. - for _,_asset in pairs(Eassets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - asset.isReserved=true - escort:AddAsset(asset) - N=N+1 - end - - -- Add mission. - legion:AddMission(escort) - - -- Request mission. - legion:MissionRequest(escort) - - end - end - - -- Debug info. - self:I(self.lid..string.format("Recruited %d escort assets for mission %s [%s]", N, Mission:GetName(), Mission:GetType())) - - -- Yup! - return true - else - - -- Debug info. - self:I(self.lid..string.format("Could not get at least one escort for mission %s [%s]! Unrecruit all recruited assets", Mission:GetName(), Mission:GetType())) - - -- Could not get at least one escort. Unrecruit all recruited ones. - for groupname,value in pairs(Escorts) do - local Eassets=value.EscortAssets - LEGION.UnRecruitAssets(Eassets) - end - - -- No,no! - return false - end - - else - -- No escort required. - return true - end + return assigned + end + return true end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2001,6 +2020,215 @@ function LEGION.UnRecruitAssets(Assets, Mission) end +--- Recruit and assign assets performing an escort mission for a given asset list. Note that each asset gets an escort. +-- @param #LEGION self +-- @param #table Cohorts Cohorts for escorting assets. +-- @param #table Assets Table of assets to be escorted. +-- @param #number NescortMin Min number of escort groups required per escorted asset. +-- @param #number NescortMax Max number of escort groups required per escorted asset. +-- @return #boolean If `true`, enough assets could be recruited or no escort was required in the first place. +function LEGION:AssignAssetsForEscort(Cohorts, Assets, NescortMin, NescortMax) + + -- Is an escort requested in the first place? + if NescortMin and NescortMax and (NescortMin>0 or NescortMax>0) then + + -- Debug info. + self:I(self.lid..string.format("Reqested escort for %d assets from %d cohorts. Required escort assets=%d-%d", #Assets, #Cohorts, NescortMin, NescortMax)) + + -- Escorts for each asset. + local Escorts={} + + local EscortAvail=true + for _,_asset in pairs(Assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + -- Target vector is the legion of the asset. + local TargetVec2=asset.legion:GetVec2() + + -- We want airplanes for airplanes and helos for everything else. + local Categories={Group.Category.HELICOPTER} + if asset.category==Group.Category.AIRPLANE then + Categories={Group.Category.AIRPLANE} + end + + -- Recruit escort asset for the mission asset. + local Erecruited, eassets, elegions=LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.ESCORT, nil, NescortMin, NescortMax, TargetVec2, nil, nil, nil, nil, Categories) + + if Erecruited then + Escorts[asset.spawngroupname]={EscortLegions=elegions, EscortAssets=eassets} + else + -- Could not find escort for this asset ==> Escort not possible ==> Break the loop. + EscortAvail=false + break + end + end + + -- ALL escorts could be recruited. + if EscortAvail then + + local N=0 + for groupname,value in pairs(Escorts) do + + local Elegions=value.EscortLegions + local Eassets=value.EscortAssets + + for _,_legion in pairs(Elegions) do + local legion=_legion --Ops.Legion#LEGION + + -- Create and ESCORT mission for this asset. + local escort=AUFTRAG:NewESCORT(groupname) + + -- Reserve assts and add to mission. + for _,_asset in pairs(Eassets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + escort:AddAsset(asset) + N=N+1 + end + + -- Assign mission to legion. + self:MissionAssign(legion, escort) + end + end + + -- Debug info. + self:I(self.lid..string.format("Recruited %d escort assets", N)) + + -- Yup! + return true + else + + -- Debug info. + self:I(self.lid..string.format("Could not get at least one escort!")) + + -- Could not get at least one escort. Unrecruit all recruited ones. + for groupname,value in pairs(Escorts) do + local Eassets=value.EscortAssets + LEGION.UnRecruitAssets(Eassets) + end + + -- No,no! + return false + end + + else + -- No escort required. + return true + end + +end + +--- Recruit and assign assets performing an OPSTRANSPORT for a given asset list. +-- @param #LEGION self +-- @param #table Legions Transport legions. +-- @param #table CargoAssets Weight of the heaviest cargo group to be transported. +-- @param #number NcarriersMin Min number of carrier assets. +-- @param #number NcarriersMax Max number of carrier assets. +-- @param Core.Zone#ZONE DeployZone Deploy zone. +-- @param Core.Zone#ZONE DisembarkZone (Optional) Disembark zone. +-- @return #boolean If `true`, enough assets could be recruited and an OPSTRANSPORT object was created. +-- @return Ops.OpsTransport#OPSTRANSPORT Transport The transport. +function LEGION:AssignAssetsForTransport(Legions, CargoAssets, NcarriersMin, NcarriersMax, DeployZone, DisembarkZone, Categories, Attributes) + + -- Is an escort requested in the first place? + if NcarriersMin and NcarriersMax and (NcarriersMin>0 or NcarriersMax>0) then + + -- Cohorts. + local Cohorts={} + for _,_legion in pairs(Legions) do + local legion=_legion --Ops.Legion#LEGION + + -- Check that runway is operational. + local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true + + if legion:IsRunning() and Runway then + + -- Loops over cohorts. + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + + end + end + + -- Get all legions and heaviest cargo group weight + local CargoLegions={} ; local CargoWeight=nil + for _,_asset in pairs(CargoAssets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + CargoLegions[asset.legion.alias]=asset.legion + if CargoWeight==nil or asset.weight>CargoWeight then + CargoWeight=asset.weight + end + end + + -- Target is the deploy zone. + local TargetVec2=DeployZone:GetVec2() + + -- Recruit assets and legions. + local TransportAvail, CarrierAssets, CarrierLegions= + LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.OPSTRANSPORT, nil, NcarriersMin, NcarriersMax, TargetVec2, nil, nil, nil, CargoWeight, Categories, Attributes) + + if TransportAvail then + + -- Create and OPSTRANSPORT assignment. + local Transport=OPSTRANSPORT:New(nil, nil, DeployZone) + if DisembarkZone then + Transport:SetDisembarkZone(DisembarkZone) + end + + -- Debug info. + env.info(string.format("FF Transport available with %d carrier assets", #CarrierAssets)) + + -- Add cargo assets to transport. + for _,_legion in pairs(CargoLegions) do + local legion=_legion --Ops.Legion#LEGION + + -- Set pickup zone to spawn zone or airbase if the legion has one that is operational. + local pickupzone=legion.spawnzone + if legion.airbase and legion:IsRunwayOperational() then + pickupzone=ZONE_AIRBASE:New(legion.airbasename, 4000) + end + + -- Add TZC from legion spawn zone to deploy zone. + local tpz=Transport:AddTransportZoneCombo(pickupzone, Transport:GetDeployZone()) + Transport:SetEmbarkZone(legion.spawnzone, tpz) + + -- Add cargo assets to transport. + for _,_asset in pairs(CargoAssets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + if asset.legion.alias==legion.alias then + Transport:AddAssetCargo(asset, tpz) + end + end + end + + -- Add carrier assets. + for _,_asset in pairs(CarrierAssets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + Transport:AddAsset(asset) + end + + -- Assign TRANSPORT to legions. This also sends the request for the assets. + for _,_legion in pairs(CarrierLegions) do + local legion=_legion --Ops.Legion#LEGION + self:TransportAssign(legion, Transport) + end + + -- Got transport. + return true, Transport + else + -- Uncrecruit transport assets. + LEGION.UnRecruitAssets(CarrierAssets) + return false, nil + end + + return nil, nil + end + + -- No transport requested in the first place. + return true, nil +end + --- Calculate the mission score of an asset. -- @param Functional.Warehouse#WAREHOUSE.Assetitem asset Asset diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 229a1a1b6..3769b30ae 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2311,6 +2311,43 @@ function OPSGROUP:IsLoaded(CarrierGroupName) return self.cargoStatus==OPSGROUP.CargoStatus.LOADED end +--- Check if the group is currently busy doing something. +-- +-- * Boarding +-- * Rearming +-- * Returning +-- * Pickingup, Loading, Transporting, Unloading +-- * Engageing +-- +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is busy. +function OPSGROUP:IsBusy() + + if self:IsBoarding() then + return true + end + + if self:IsRearming() then + return true + end + + if self:IsReturning() then + return true + end + + -- Busy as carrier? + if self:IsPickingup() or self:IsLoading() or self:IsTransporting() or self:IsUnloading() then + return true + end + + if self:IsEngaging() then + return true + end + + + return false +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -6995,7 +7032,8 @@ function OPSGROUP:onafterLoading(From, Event, To) if cargo.opsgroup:IsNotCargo(true) and not isCarrier then -- Check if cargo is in embark/pickup zone. - local inzone=cargo.opsgroup:IsInZone(self.cargoTZC.EmbarkZone) + -- Added InUtero here, if embark zone is moving (ship) and cargo has been spawned late activated and its position is not updated. Not sure if that breaks something else! + local inzone=cargo.opsgroup:IsInZone(self.cargoTZC.EmbarkZone) --or cargo.opsgroup:IsInUtero() -- Cargo MUST be inside zone or it will not be loaded! if inzone then @@ -7018,7 +7056,7 @@ function OPSGROUP:onafterLoading(From, Event, To) else -- Debug info. - self:T(self.lid..string.format("Cargo %s NOT in embark zone %s", cargo.opsgroup:GetName(), self.cargoTZC.EmbarkZone:GetName())) + self:T(self.lid..string.format("Cargo %s NOT in embark zone %s (and not InUTERO)", cargo.opsgroup:GetName(), self.cargoTZC.EmbarkZone:GetName())) end end diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 8d5ece50d..e3e262d7a 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -864,7 +864,8 @@ function OPSTRANSPORT:_DelCarrier(CarrierGroup) end end - if #self.carriers==0 then + if #self.carriers==0 then + -- TODO: This call can be WRONG! self:DeadCarrierAll() end @@ -1948,18 +1949,24 @@ function OPSTRANSPORT:_CountCargosInZone(Zone, Delivered, Carrier, TransportZone local N=0 for _,_cargo in pairs(cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP - + + -- Is not cargo? local isNotCargo=cargo:IsNotCargo(true) if not isNotCargo then isNotCargo=iscarrier(cargo) - end + end + + -- Is in zone? + local isInZone=cargo:IsInZone(Zone) + -- Is in utero? + local isInUtero=cargo:IsInUtero() + -- Debug info. - --self:T2(self.lid..string.format("Cargo=%s: notcargo=%s, iscarrier=%s inzone=%s, inutero=%s", cargo:GetName(), tostring(cargo:IsNotCargo(true)), tostring(iscarrier(cargo)), tostring(cargo:IsInZone(Zone)), tostring(cargo:IsInUtero()) )) - + self:I(self.lid..string.format("Cargo=%s: notcargo=%s, iscarrier=%s inzone=%s, inutero=%s", cargo:GetName(), tostring(cargo:IsNotCargo(true)), tostring(iscarrier(cargo)), tostring(isInZone), tostring(isInUtero))) -- We look for groups that are not cargo, in the zone or in utero. - if isNotCargo and (cargo:IsInZone(Zone) or cargo:IsInUtero()) then + if isNotCargo and (isInZone or isInUtero) then N=N+1 end end From be0558849c41f998ed750edcfe26116335621f36 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 27 Sep 2021 16:11:03 +0200 Subject: [PATCH 116/141] OPS - Added rearming zones - Improved rearming --- .../Moose/Functional/Warehouse.lua | 8 +- Moose Development/Moose/Ops/AirWing.lua | 44 ++-- Moose Development/Moose/Ops/ArmyGroup.lua | 142 ++++++++++-- Moose Development/Moose/Ops/Auftrag.lua | 184 +++++++++++---- Moose Development/Moose/Ops/Brigade.lua | 83 ++++--- Moose Development/Moose/Ops/Chief.lua | 12 + Moose Development/Moose/Ops/Commander.lua | 90 ++++++-- Moose Development/Moose/Ops/FlightGroup.lua | 4 +- Moose Development/Moose/Ops/Legion.lua | 108 ++++++--- Moose Development/Moose/Ops/NavyGroup.lua | 212 ++++++++++++++++-- Moose Development/Moose/Ops/OpsGroup.lua | 73 +++++- Moose Development/Moose/Ops/OpsTransport.lua | 6 + Moose Development/Moose/Wrapper/Unit.lua | 57 +++++ 13 files changed, 823 insertions(+), 200 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 38f980a88..5512dde73 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -8100,10 +8100,10 @@ function WAREHOUSE:_GetIDsFromGroup(group) end -- Debug info - self:T(self.lid..string.format("Group Name = %s", tostring(name))) - self:T(self.lid..string.format("Warehouse ID = %s", tostring(wid))) - self:T(self.lid..string.format("Asset ID = %s", tostring(aid))) - self:T(self.lid..string.format("Request ID = %s", tostring(rid))) + self:T3(self.lid..string.format("Group Name = %s", tostring(name))) + self:T3(self.lid..string.format("Warehouse ID = %s", tostring(wid))) + self:T3(self.lid..string.format("Asset ID = %s", tostring(aid))) + self:T3(self.lid..string.format("Request ID = %s", tostring(rid))) return wid,aid,rid else diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index a2024e1c2..4c3e62a66 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -787,6 +787,20 @@ function AIRWING:onafterStatus(From, Event, To) -- Check Rescue Helo missions. self:CheckRescuhelo() + ---------------- + -- Transport --- + ---------------- + + -- Check transport queue. + self:CheckTransportQueue() + + -------------- + -- Mission --- + -------------- + + -- Check mission queue. + self:CheckMissionQueue() + -- General info: if self.verbose>=1 then @@ -843,36 +857,6 @@ function AIRWING:onafterStatus(From, Event, To) self:I(self.lid..text) end - ---------------- - -- Transport --- - ---------------- - - -- Check if any transports should be cancelled. - --self:_CheckTransports() - - -- Get next mission. - local transport=self:_GetNextTransport() - - -- Request mission execution. - if transport then - self:TransportRequest(transport) - end - - -------------- - -- Mission --- - -------------- - - -- Check if any missions should be cancelled. - self:_CheckMissions() - - -- Get next mission. - local mission=self:_GetNextMission() - - -- Request mission execution. - if mission then - self:MissionRequest(mission) - end - end --- Get patrol data. diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index eb0d99cb8..499548c3f 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -812,6 +812,44 @@ function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Formation, end +--- On before "Rearm" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Point#COORDINATE Coordinate Coordinate where to rearm. +-- @param #number Formation Formation of the group. +function ARMYGROUP:onbeforeRearm(From, Event, To, Coordinate, Formation) + + local dt=nil + local allowed=true + + -- Pause current mission. + if self.currentmission and self.currentmission>0 then + self:T(self.lid.."Rearm command but have current mission ==> Pausing mission!") + self:PauseMission() + dt=-0.1 + allowed=false + end + + -- Disengage. + if self:IsEngaging() then + self:T(self.lid.."Rearm command but currently engaging ==> Disengage!") + self:Disengage() + dt=-0.1 + allowed=false + end + + -- Try again... + if dt then + self:T(self.lid..string.format("Trying Rearm again in %.2f sec", dt)) + self:__Rearm(dt, Coordinate, Formation) + allowed=false + end + + return allowed +end + --- On after "Rearm" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -821,6 +859,9 @@ end -- @param #number Formation Formation of the group. function ARMYGROUP:onafterRearm(From, Event, To, Coordinate, Formation) + -- Debug info. + self:I(self.lid..string.format("Group send to rearm")) + -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid @@ -832,6 +873,18 @@ function ARMYGROUP:onafterRearm(From, Event, To, Coordinate, Formation) end +--- On after "Rearmed" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARMYGROUP:onafterRearmed(From, Event, To) + self:I(self.lid.."Group rearmed") + + -- Check group done. + self:_CheckGroupDone(1) +end + --- On after "RTZ" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -991,14 +1044,32 @@ end -- @param Wrapper.Group#GROUP Group the group to be engaged. function ARMYGROUP:onbeforeEngageTarget(From, Event, To, Target) + local dt=nil + local allowed=true + local ammo=self:GetAmmoTot() if ammo.Total==0 then self:E(self.lid.."WARNING: Cannot engage TARGET because no ammo left!") return false end + + -- Pause current mission. + if self.currentmission and self.currentmission>0 then + self:T(self.lid.."Engage command but have current mission ==> Pausing mission!") + self:PauseMission() + dt=-0.1 + allowed=false + end - return true + -- Try again... + if dt then + self:T(self.lid..string.format("Trying Engage again in %.2f sec", dt)) + self:__EngageTarget(dt, Target) + allowed=false + end + + return allowed end --- On after "EngageTarget" event. @@ -1015,9 +1086,14 @@ function ARMYGROUP:onafterEngageTarget(From, Event, To, Target) else self.engage.Target=TARGET:New(Target) end - + -- Target coordinate. - self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) + self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) + + + local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9) + + -- Backup ROE and alarm state. self.engage.roe=self:GetROE() @@ -1031,7 +1107,7 @@ function ARMYGROUP:onafterEngageTarget(From, Event, To, Target) local uid=self:GetWaypointCurrent().uid -- Add waypoint after current. - self.engage.Waypoint=self:AddWaypoint(self.engage.Coordinate, nil, uid, Formation, true) + self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true) -- Set if we want to resume route after reaching the detour waypoint. self.engage.Waypoint.detour=1 @@ -1063,9 +1139,11 @@ function ARMYGROUP:_UpdateEngageTarget() -- Remove current waypoint self:RemoveWaypointByID(self.engage.Waypoint.uid) + + local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9) -- Add waypoint after current. - self.engage.Waypoint=self:AddWaypoint(self.engage.Coordinate, nil, uid, Formation, true) + self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true) -- Set if we want to resume route after reaching the detour waypoint. self.engage.Waypoint.detour=0 @@ -1102,19 +1180,6 @@ function ARMYGROUP:onafterDisengage(From, Event, To) self:_CheckGroupDone(1) end ---- On after "Rearmed" event. --- @param #ARMYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function ARMYGROUP:onafterRearmed(From, Event, To) - self:I(self.lid.."Group rearmed") - - -- Check group done. - self:_CheckGroupDone(1) - -end - --- On after "DetourReached" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -1334,7 +1399,48 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Find the neares ammo supply group within a given radius. +-- @param #ARMYGROUP self +-- @param #number Radius Search radius in NM. Default 30 NM. +-- @return Wrapper.Group#GROUP Closest ammo supplying group or `nil` if no group is in the given radius. +function ARMYGROUP:FindNearestAmmoSupply(Radius) + -- Radius in meters. + Radius=UTILS.NMToMeters(Radius or 30) + + -- Current positon. + local coord=self:GetCoordinate() + + -- Scanned units. + local units=coord:ScanUnits(Radius) + + -- Find closest + local dmin=math.huge + local truck=nil --Wrapper.Unit#UNIT + for _,_unit in pairs(units.Set) do + local unit=_unit --Wrapper.Unit#UNIT + + -- Check coaliton and if unit can supply ammo. + if unit:GetCoalition()==self:GetCoalition() and unit:IsAmmoSupply() then + + -- Distance. + local d=unit:GetCoordinate():Get2DDistance(coord) + + -- Check if distance is smaller. + if d=1 then @@ -361,34 +393,21 @@ function BRIGADE:onafterStatus(From, Event, To) self:I(self.lid..text) end - ---------------- - -- Transport --- - ---------------- + ------------------- + -- Rearming Info -- + ------------------- + if self.verbose>=0 then + local text="Rearming Zones:" + for i,_rearmingzone in pairs(self.rearmingZones) do + local rearmingzone=_rearmingzone --#BRIGADE.RearmingZone + + local name=rearmingzone.zone:GetName() - -- Check if any transports should be cancelled. - --self:_CheckTransports() - - -- Get next mission. - local transport=self:_GetNextTransport() - - -- Request mission execution. - if transport then - self:TransportRequest(transport) - end - - -------------- - -- Mission --- - -------------- - - -- Check if any missions should be cancelled. - self:_CheckMissions() - - -- Get next mission. - local mission=self:_GetNextMission() - - -- Request mission execution. - if mission then - self:MissionRequest(mission) + -- Platoon text. + text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", name, rearmingzone.mission:GetState(), rearmingzone.mission:CountOpsGroups()) + + end + self:I(self.lid..text) end end diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index dab2b93cb..892987ceb 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -567,6 +567,18 @@ function CHIEF:AddOpsZone(OpsZone) return self end +--- Add a rearming zone. +-- @param #CHIEF self +-- @param Core.Zone#ZONE RearmingZone Rearming zone. +-- @return Ops.Brigade#BRIGADE.RearmingZone The rearming zone data. +function CHIEF:AddRearmingZone(RearmingZone) + + -- Hand over to commander. + local rearmingzone=self.commander:AddRearmingZone(RearmingZone) + + return rearmingzone +end + --- Set border zone set. -- @param #CHIEF self diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index d57bab942..cab981797 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -20,6 +20,7 @@ -- @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.RearmingZone`. -- @field Ops.Chief#CHIEF chief Chief of staff. -- @extends Core.Fsm#FSM @@ -39,6 +40,7 @@ COMMANDER = { legions = {}, missionqueue = {}, transportqueue = {}, + rearmingZones = {}, } --- COMMANDER class version. @@ -330,6 +332,24 @@ function COMMANDER:RemoveTransport(Transport) return self end +--- Add a rearming zone. +-- @param #COMMANDER self +-- @param Core.Zone#ZONE RearmingZone Rearming zone. +-- @return Ops.Brigade#BRIGADE.RearmingZone The rearming zone data. +function COMMANDER:AddRearmingZone(RearmingZone) + + local rearmingzone={} --Ops.Brigade#BRIGADE.RearmingZone + + rearmingzone.zone=RearmingZone + rearmingzone.occupied=false + rearmingzone.mission=nil + rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(), "Rearming Zone"):ToCoalition(self:GetCoalition()) + + table.insert(self.rearmingZones, rearmingzone) + + return rearmingzone +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -370,14 +390,25 @@ function COMMANDER:onafterStatus(From, Event, To) -- Status. if self.verbose>=1 then - local text=string.format("Status %s: Legions=%d, Missions=%d", fsmstate, #self.legions, #self.missionqueue) + local text=string.format("Status %s: Legions=%d, Missions=%d, Transports", fsmstate, #self.legions, #self.missionqueue, #self.transportqueue) self:I(self.lid..text) end -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() - -- Check mission queue and assign one PLANNED mission + -- Check transport queue and assign one PLANNED transport. + self:CheckTransportQueue() + + -- Check rearming zones. + for _,_rearmingzone in pairs(self.rearmingZones) do + local rearmingzone=_rearmingzone --#BRIGADE.RearmingZone + -- Check if mission is nil or over. + if (not rearmingzone.mission) or rearmingzone.mission:IsOver() then + rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone) + self:AddMission(rearmingzone.mission) + end + end --- -- LEGIONS @@ -769,20 +800,31 @@ function COMMANDER:RecruitAssetsForMission(Mission) -- Debug info. env.info(string.format("FF recruiting assets for mission %s [%s]", Mission:GetName(), Mission:GetType())) - + -- Cohorts. - local Cohorts=Mission.squadrons - if not Cohorts then - Cohorts={} - for _,_legion in pairs(Mission.specialLegions or self.legions) do - local legion=_legion --Ops.Legion#LEGION - -- Loops over cohorts. + local Cohorts={} + for _,_legion in pairs(Mission.specialLegions or {}) do + local legion=_legion --Ops.Legion#LEGION + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end + for _,_cohort in pairs(Mission.specialCohorts or {}) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + + -- No special mission legions/cohorts found ==> take own legions. + if #Cohorts==0 then + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION for _,_cohort in pairs(legion.cohorts) do local cohort=_cohort --Ops.Cohort#COHORT table.insert(Cohorts, cohort) end - end - end + end + end -- Number of required assets. local NreqMin, NreqMax=Mission:GetRequiredAssets() @@ -810,18 +852,30 @@ function COMMANDER:RecruitAssetsForEscort(Mission, Assets) if Mission.NescortMin and Mission.NescortMax and (Mission.NescortMin>0 or Mission.NescortMax>0) then -- Cohorts. - local Cohorts=Mission.squadrons - if not Cohorts then - Cohorts={} - for _,_legion in pairs(Mission.specialLegions or self.legions) do - local legion=_legion --Ops.Legion#LEGION - -- Loops over cohorts. + local Cohorts={} + for _,_legion in pairs(Mission.escortLegions or {}) do + local legion=_legion --Ops.Legion#LEGION + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end + for _,_cohort in pairs(Mission.escortCohorts or {}) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + + -- No special escort legions/cohorts found ==> take own legions. + if #Cohorts==0 then + for _,_legion in pairs(self.legions) do + local legion=_legion --Ops.Legion#LEGION for _,_cohort in pairs(legion.cohorts) do local cohort=_cohort --Ops.Cohort#COHORT table.insert(Cohorts, cohort) end - end + end end + -- Call LEGION function but provide COMMANDER as self. local assigned=LEGION.AssignAssetsForEscort(self, Cohorts, Assets, Mission.NescortMin, Mission.NescortMin) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 3ab6c3257..c59578513 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -3148,13 +3148,13 @@ function FLIGHTGROUP:FindNearestTanker(Radius) local istanker, refuelsystem=unit:IsTanker() - if istanker and self.refueltype==refuelsystem then + if istanker and self.refueltype==refuelsystem and unit:GetCoalition()==self:GetCoalition() then -- Distance. local d=unit:GetCoordinate():Get2DDistance(coord) if d Request and return. + self:TransportRequest(transport) + return true end end @@ -1736,9 +1733,25 @@ function LEGION:RecruitAssetsForMission(Mission) -- Payloads. local Payloads=Mission.payloads - - -- Cohorts. - local Cohorts=Mission.squadrons or self.cohorts + + -- Get special escort legions and/or cohorts. + local Cohorts={} + for _,_legion in pairs(Mission.specialLegions or {}) do + local legion=_legion --Ops.Legion#LEGION + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end + for _,_cohort in pairs(Mission.specialCohorts or {}) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + + -- No escort cohorts/legions given ==> take own cohorts. + if #Cohorts==0 then + Cohorts=self.cohorts + end -- Recuit assets. local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads, Mission.engageRange, Mission.refuelSystem, nil) @@ -1776,6 +1789,8 @@ function LEGION:RecruitAssetsForTransport(Transport) end + -- TODO: Special transport cohorts/legions. + -- Target is the deploy zone. local TargetVec2=Transport:GetDeployZone():GetVec2() @@ -1802,10 +1817,27 @@ function LEGION:RecruitAssetsForEscort(Mission, Assets) -- Debug info. self:I(self.lid..string.format("Reqested escort for mission %s [%s]. Required assets=%d-%d", Mission:GetName(), Mission:GetType(), Mission.NescortMin,Mission.NescortMax)) - --TODO: Maybe add escortCohorts as mission option? + -- Get special escort legions and/or cohorts. + local Cohorts={} + for _,_legion in pairs(Mission.escortLegions or {}) do + local legion=_legion --Ops.Legion#LEGION + for _,_cohort in pairs(legion.cohorts) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + end + for _,_cohort in pairs(Mission.escortCohorts or {}) do + local cohort=_cohort --Ops.Cohort#COHORT + table.insert(Cohorts, cohort) + end + + -- No escort cohorts/legions given ==> take own cohorts. + if #Cohorts==0 then + Cohorts=self.cohorts + end -- Call LEGION function but provide COMMANDER as self. - local assigned=LEGION.AssignAssetsForEscort(self, self.cohorts, Assets, Mission.NescortMin, Mission.NescortMin) + local assigned=LEGION.AssignAssetsForEscort(self, Cohorts, Assets, Mission.NescortMin, Mission.NescortMin) return assigned end @@ -2047,15 +2079,17 @@ function LEGION:AssignAssetsForEscort(Cohorts, Assets, NescortMin, NescortMax) -- We want airplanes for airplanes and helos for everything else. local Categories={Group.Category.HELICOPTER} + local TargetTypes={"Ground Units"} if asset.category==Group.Category.AIRPLANE then Categories={Group.Category.AIRPLANE} + TargetTypes={"Air"} end -- Recruit escort asset for the mission asset. local Erecruited, eassets, elegions=LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.ESCORT, nil, NescortMin, NescortMax, TargetVec2, nil, nil, nil, nil, Categories) if Erecruited then - Escorts[asset.spawngroupname]={EscortLegions=elegions, EscortAssets=eassets} + Escorts[asset.spawngroupname]={EscortLegions=elegions, EscortAssets=eassets, ecategory=asset.category, TargetTypes=TargetTypes} else -- Could not find escort for this asset ==> Escort not possible ==> Break the loop. EscortAvail=false @@ -2071,12 +2105,22 @@ function LEGION:AssignAssetsForEscort(Cohorts, Assets, NescortMin, NescortMax) local Elegions=value.EscortLegions local Eassets=value.EscortAssets + local ecategory=value.ecategory for _,_legion in pairs(Elegions) do local legion=_legion --Ops.Legion#LEGION + local OffsetVector=nil --DCS#Vec3 + if ecategory==Group.Category.GROUND then + -- Overhead + OffsetVector={} + OffsetVector.x=0 + OffsetVector.y=UTILS.FeetToMeters(1000) + OffsetVector.z=0 + end + -- Create and ESCORT mission for this asset. - local escort=AUFTRAG:NewESCORT(groupname) + local escort=AUFTRAG:NewESCORT(groupname, OffsetVector, nil, value.TargetTypes) -- Reserve assts and add to mission. for _,_asset in pairs(Eassets) do diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 00b34d84f..4b7d1139a 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -5,7 +5,7 @@ -- * Let the group steam into the wind -- * Command a full stop -- * Patrol waypoints *ad infinitum* --- * Collision warning, if group is heading towards a land mass +-- * Collision warning, if group is heading towards a land mass or another obstacle -- * Automatic pathfinding, e.g. around islands -- * Let a submarine dive and surface -- * Manage TACAN and ICLS beacons @@ -48,9 +48,7 @@ --- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson -- -- === --- --- ![Banner Image](..\Presentations\OPS\NavyGroup\_Main.png) --- +-- -- # The NAVYGROUP Concept -- -- This class enhances naval groups. @@ -88,6 +86,9 @@ NAVYGROUP.version="0.7.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Add RTZ. +-- TODO: Add Retreat. +-- TODO: Add EngageTarget. -- TODO: Submaries. -- TODO: Extend, shorten turn into wind windows. -- TODO: Skipper menu. @@ -132,6 +133,20 @@ function NAVYGROUP:New(group) -- From State --> Event --> To State self:AddTransition("*", "FullStop", "Holding") -- Hold position. self:AddTransition("*", "Cruise", "Cruising") -- Hold position. + + self:AddTransition("*", "RTZ", "Returning") -- Group is returning to (home) zone. + self:AddTransition("Returning", "Returned", "Returned") -- Group is returned to (home) zone. + + self:AddTransition("*", "Detour", "Cruising") -- Make a detour to a coordinate and resume route afterwards. + self:AddTransition("*", "DetourReached", "*") -- Group reached the detour coordinate. + + self:AddTransition("*", "Retreat", "Retreating") -- Order a retreat. + self:AddTransition("Retreating", "Retreated", "Retreated") -- Group retreated. + + self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target from Cruising state + self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target from Holding state + self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target from OnDetour state + self:AddTransition("Engaging", "Disengage", "Cruising") -- Disengage and back to cruising. self:AddTransition("*", "TurnIntoWind", "Cruising") -- Command the group to turn into the wind. self:AddTransition("*", "TurnedIntoWind", "*") -- Group turned into wind. @@ -141,9 +156,6 @@ function NAVYGROUP:New(group) self:AddTransition("*", "TurningStarted", "*") -- Group started turning. self:AddTransition("*", "TurningStopped", "*") -- Group stopped turning. - self:AddTransition("*", "Detour", "Cruising") -- Make a detour to a coordinate and resume route afterwards. - self:AddTransition("*", "DetourReached", "*") -- Group reached the detour coordinate. - self:AddTransition("*", "CollisionWarning", "*") -- Collision warning. self:AddTransition("*", "ClearAhead", "*") -- Clear ahead. @@ -154,15 +166,190 @@ function NAVYGROUP:New(group) --- Pseudo Functions --- ------------------------ - --- Triggers the FSM event "Stop". Stops the NAVYGROUP and all its event handlers. + --- Triggers the FSM event "TurnIntoWind". + -- @function [parent=#NAVYGROUP] TurnIntoWind -- @param #NAVYGROUP self + -- @param #NAVYGROUP.IntoWind Into wind parameters. - --- Triggers the FSM event "Stop" after a delay. Stops the NAVYGROUP and all its event handlers. - -- @function [parent=#NAVYGROUP] __Stop + --- Triggers the FSM event "TurnIntoWind" after a delay. + -- @function [parent=#NAVYGROUP] __TurnIntoWind -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. - - -- TODO: Add pseudo functions. + -- @param #NAVYGROUP.IntoWind Into wind parameters. + + --- On after "TurnIntoWind" event. + -- @function [parent=#NAVYGROUP] OnAfterTurnIntoWind + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #NAVYGROUP.IntoWind Into wind parameters. + + + --- Triggers the FSM event "TurnedIntoWind". + -- @function [parent=#NAVYGROUP] TurnedIntoWind + -- @param #NAVYGROUP self + + --- Triggers the FSM event "TurnedIntoWind" after a delay. + -- @function [parent=#NAVYGROUP] __TurnedIntoWind + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "TurnedIntoWind" event. + -- @function [parent=#NAVYGROUP] OnAfterTurnedIntoWind + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "TurnIntoWindStop". + -- @function [parent=#NAVYGROUP] TurnIntoWindStop + -- @param #NAVYGROUP self + + --- Triggers the FSM event "TurnIntoWindStop" after a delay. + -- @function [parent=#NAVYGROUP] __TurnIntoWindStop + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "TurnIntoWindStop" event. + -- @function [parent=#NAVYGROUP] OnAfterTurnIntoWindStop + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "TurnIntoWindOver". + -- @function [parent=#NAVYGROUP] TurnIntoWindOver + -- @param #NAVYGROUP self + -- @param #NAVYGROUP.IntoWind IntoWindData Data table. + + --- Triggers the FSM event "TurnIntoWindOver" after a delay. + -- @function [parent=#NAVYGROUP] __TurnIntoWindOver + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + -- @param #NAVYGROUP.IntoWind IntoWindData Data table. + + --- On after "TurnIntoWindOver" event. + -- @function [parent=#NAVYGROUP] OnAfterTurnIntoWindOver + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #NAVYGROUP.IntoWind IntoWindData Data table. + + + + --- Triggers the FSM event "TurningStarted". + -- @function [parent=#NAVYGROUP] TurningStarted + -- @param #NAVYGROUP self + + --- Triggers the FSM event "TurningStarted" after a delay. + -- @function [parent=#NAVYGROUP] __TurningStarted + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "TurningStarted" event. + -- @function [parent=#NAVYGROUP] OnAfterTurningStarted + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "TurningStopped". + -- @function [parent=#NAVYGROUP] TurningStopped + -- @param #NAVYGROUP self + + --- Triggers the FSM event "TurningStopped" after a delay. + -- @function [parent=#NAVYGROUP] __TurningStopped + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "TurningStopped" event. + -- @function [parent=#NAVYGROUP] OnAfterTurningStopped + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "CollisionWarning". + -- @function [parent=#NAVYGROUP] CollisionWarning + -- @param #NAVYGROUP self + + --- Triggers the FSM event "CollisionWarning" after a delay. + -- @function [parent=#NAVYGROUP] __CollisionWarning + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "CollisionWarning" event. + -- @function [parent=#NAVYGROUP] OnAfterCollisionWarning + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "ClearAhead". + -- @function [parent=#NAVYGROUP] ClearAhead + -- @param #NAVYGROUP self + + --- Triggers the FSM event "ClearAhead" after a delay. + -- @function [parent=#NAVYGROUP] __ClearAhead + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "ClearAhead" event. + -- @function [parent=#NAVYGROUP] OnAfterClearAhead + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Dive". + -- @function [parent=#NAVYGROUP] Dive + -- @param #NAVYGROUP self + -- @param #number Depth Dive depth in meters. Default 50 meters. + -- @param #number Speed Speed in knots until next waypoint is reached. + + --- Triggers the FSM event "Dive" after a delay. + -- @function [parent=#NAVYGROUP] __Dive + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + -- @param #number Depth Dive depth in meters. Default 50 meters. + -- @param #number Speed Speed in knots until next waypoint is reached. + + --- On after "Dive" event. + -- @function [parent=#NAVYGROUP] OnAfterDive + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number Depth Dive depth in meters. Default 50 meters. + -- @param #number Speed Speed in knots until next waypoint is reached. + + + --- Triggers the FSM event "Surface". + -- @function [parent=#NAVYGROUP] Surface + -- @param #NAVYGROUP self + -- @param #number Speed Speed in knots until next waypoint is reached. + + --- Triggers the FSM event "Surface" after a delay. + -- @function [parent=#NAVYGROUP] __Surface + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + -- @param #number Speed Speed in knots until next waypoint is reached. + + --- On after "Surface" event. + -- @function [parent=#NAVYGROUP] OnAfterSurface + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number Speed Speed in knots until next waypoint is reached. -- Init waypoints. @@ -385,7 +572,6 @@ function NAVYGROUP:RemoveTurnIntoWind(IntoWindData) -- Check if this is a window currently open. if self.intowind and self.intowind.Id==IntoWindData.Id then - --env.info("FF stop in remove") self:TurnIntoWindStop() return end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 3769b30ae..dbe18ae14 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -393,7 +393,7 @@ OPSGROUP.TaskType={ -- @field #string name Waypoint description. Shown in the F10 map. -- @field #number x Waypoint x-coordinate. -- @field #number y Waypoint y-coordinate. --- @field #boolean detour If true, this waypoint is not part of the normal route. +-- @field #number detour Signifies that this waypoint is not part of the normal route: 0=Hold, 1=Resume Route. -- @field #boolean intowind If true, this waypoint is a turn into wind route point. -- @field #boolean astar If true, this waypint was found by A* pathfinding algorithm. -- @field #boolean temp If true, this is a temporary waypoint and will be deleted when passed. Also the passing waypoint FSM event is not triggered. @@ -2693,7 +2693,7 @@ end -- @return #number Expected speed in m/s. function OPSGROUP:GetExpectedSpeed() - if self:IsHolding() then + if self:IsHolding() or self:Is("Rearming") or self:IsWaiting() or self:IsRetreated() then return 0 else return self.speedWp or 0 @@ -2723,6 +2723,12 @@ end function OPSGROUP:RemoveWaypoint(wpindex) if self.waypoints then + + -- The waypoitn to be removed. + local wp=self:GetWaypoint(wpindex) + + -- Is this a temporary waypoint. + local istemp=wp.temp or wp.detour or wp.astar or wp.missionUID -- Number of waypoints before delete. local N=#self.waypoints @@ -2740,7 +2746,6 @@ function OPSGROUP:RemoveWaypoint(wpindex) end -- Remove waypoint marker. - local wp=self:GetWaypoint(wpindex) if wp and wp.marker then wp.marker:Remove() end @@ -2752,7 +2757,7 @@ function OPSGROUP:RemoveWaypoint(wpindex) local n=#self.waypoints -- Debug info. - self:T(self.lid..string.format("Removing waypoint index %d, current wp index %d. N %d-->%d", wpindex, self.currentwp, N, n)) + self:T(self.lid..string.format("Removing waypoint UID=%d [temp=%s]: index=%d [currentwp=%d]. N %d-->%d", wp.uid, tostring(istemp), wpindex, self.currentwp, N, n)) -- Waypoint was not reached yet. if wpindex > self.currentwp then @@ -2764,7 +2769,7 @@ function OPSGROUP:RemoveWaypoint(wpindex) -- TODO: patrol adinfinitum. Not sure this is handled correctly. If patrol adinfinitum and we have now only one WP left, we should at least go back. -- Could be that the waypoint we are currently moving to was the LAST waypoint. Then we now passed the final waypoint. - if self.currentwp>=n and not self.adinfinitum then + if self.currentwp>=n and not (self.adinfinitum or istemp) then self:_PassedFinalWaypoint(true, "Removed FUTURE waypoint we are currently moving to and that was the LAST waypoint") end @@ -2990,6 +2995,20 @@ function OPSGROUP:PushTask(DCSTask) if self:IsAlive() then + -- Inject enroute tasks. + if self.taskenroute and #self.taskenroute>0 then + if tostring(DCSTask.id)=="ComboTask" then + for _,task in pairs(self.taskenroute) do + table.insert(DCSTask.params.tasks, 1, task) + end + else + local tasks=UTILS.DeepCopy(self.taskenroute) + table.insert(tasks, DCSTask) + + DCSTask=self.group.TaskCombo(self, tasks) + end + end + -- Push task. self.controller:pushTask(DCSTask) @@ -3762,6 +3781,7 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) if self.currentmission and self.currentmission==Mission.auftragsnummer then self.currentmission=nil end + env.info("Remove mission waypoints") self:_RemoveMissionWaypoints(Mission, false) end @@ -5964,6 +5984,15 @@ function OPSGROUP:onafterStop(From, Event, To) self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") end +--- On after "OutOfAmmo" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterOutOfAmmo(From, Event, To) + self:T(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Cargo Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -8091,32 +8120,54 @@ end -- @param #number delay Delay in seconds. function OPSGROUP:_CheckGroupDone(delay) + -- FSM state. + local fsmstate=self:GetState() + if self:IsAlive() and self.isAI then if delay and delay>0 then + -- Debug info. + self:T(self.lid..string.format("Check OPSGROUP [state=%s] done in %.3f seconds...", fsmstate, delay)) + -- Delayed call. self:ScheduleOnce(delay, self._CheckGroupDone, self) else -- Debug info. - self:T(self.lid.."Check OPSGROUP done?") + self:T(self.lid..string.format("Check OSGROUP [state=%s] done?", fsmstate)) -- Group is engaging something. if self:IsEngaging() then + self:T(self.lid.."Engaging! Group NOT done ==> UpdateRoute()") self:UpdateRoute() return end -- Group is returning if self:IsReturning() then + self:T(self.lid.."Returning! Group NOT done...") return end + + -- Group is returning + if self:IsRearming() then + self:T(self.lid.."Rearming! Group NOT done...") + return + end -- Group is waiting. We deny all updates. if self:IsWaiting() then -- If group is waiting, we assume that is the way it is meant to be. + self:T(self.lid.."Waiting! Group NOT done...") return end + + -- First check if there is a paused mission that + if self.missionpaused then + self:T(self.lid..string.format("Found paused mission %s [%s]. Unpausing mission...", self.missionpaused.name, self.missionpaused.type)) + self:UnpauseMission() + return + end -- Get current waypoint. local waypoint=self:GetWaypoint(self.currentwp) @@ -8226,7 +8277,7 @@ function OPSGROUP:_CheckStuck() local speed=self:GetVelocity() -- Check speed. - if speed<0.5 then + if speed<0.1 then if ExpectedSpeed>0 and not self.stuckTimestamp then self:T2(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected", speed, ExpectedSpeed)) @@ -8824,9 +8875,9 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) end -- Check if final waypoint was reached. - if opsgroup.currentwp==#opsgroup.waypoints and not opsgroup.adinfinitum then + if opsgroup.currentwp==#opsgroup.waypoints and not (opsgroup.adinfinitum or wpistemp) then -- Set passed final waypoint. - opsgroup:_PassedFinalWaypoint(true, "_PassingWaypoint currentwp==#waypoints and NOT adinfinitum") + opsgroup:_PassedFinalWaypoint(true, "_PassingWaypoint currentwp==#waypoints and NOT adinfinitum and NOT a temporary waypoint") end -- Trigger PassingWaypoint event. @@ -8934,7 +8985,8 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) elseif opsgroup:IsEngaging() then -- Nothing to do really. - + opsgroup:T(opsgroup.lid.."Passing engaging waypoint") + else -- Trigger DetourReached event. @@ -9574,6 +9626,7 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) Modulation=Modulation or self.radioDefault.Modu if self:IsFlightgroup() and not self.radio.On then + env.info("FF radio OFF") self.group:SetOption(AI.Option.Air.id.SILENCE, false) end diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index e3e262d7a..8e40b8a4f 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -48,6 +48,8 @@ -- @field #number Ncargo Total number of cargo groups. -- @field #number Ncarrier Total number of assigned carriers. -- @field #number Ndelivered Total number of cargo groups delivered. +-- @field #number NcarrierDead Total number of dead carrier groups +-- @field #number NcargoDead Totalnumber of dead cargo groups. -- -- @field Ops.Auftrag#AUFTRAG mission The mission attached to this transport. -- @field #table assets Warehouse assets assigned for this transport. @@ -192,6 +194,7 @@ OPSTRANSPORT.version="0.5.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Special transport cohorts/legions. Similar to mission. -- TODO: Stop/abort transport. -- DONE: Allow multiple pickup/depoly zones. -- DONE: Add start conditions. @@ -232,6 +235,8 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) self.Ncargo=0 self.Ncarrier=0 self.Ndelivered=0 + self.NcargoDead=0 + self.NcarrierDead=0 -- Set default TZC. self.tzcDefault=self:AddTransportZoneCombo(PickupZone, DeployZone, CargoGroups) @@ -1648,6 +1653,7 @@ end function OPSTRANSPORT:onafterDeadCarrierGroup(From, Event, To, OpsGroup) self:I(self.lid..string.format("Carrier OPSGROUP %s dead!", OpsGroup:GetName())) -- Remove group from carrier list/table. + self.NdeadCarrier=self.NdeadCarrier+1 self:_DelCarrier(OpsGroup) end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index eb6d472d8..7c32bbb51 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -524,6 +524,63 @@ function UNIT:IsTanker() return tanker, system end +--- Check if the unit can supply ammo. Currently, we have +-- +-- * M 818 +-- * Ural-375 +-- * ZIL-135 +-- +-- This list needs to be extended, if DCS adds other units capable of supplying ammo. +-- +-- @param #UNIT self +-- @return #boolean If `true`, unit can supply ammo. +function UNIT:IsAmmoSupply() + + -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. + local typename=self:GetTypeName() + + if typename=="M 818" then + -- Blue ammo truck. + return true + elseif typename=="Ural-375" then + -- Red ammo truck. + return true + elseif typename=="ZIL-135" then + -- Red ammo truck. Checked that it can also provide ammo. + return true + end + + return false +end + +--- Check if the unit can supply fuel. Currently, we have +-- +-- * M978 HEMTT Tanker +-- * ATMZ-5 +-- * ATMZ-10 +-- * ATZ-5 +-- +-- This list needs to be extended, if DCS adds other units capable of supplying fuel. +-- +-- @param #UNIT self +-- @return #boolean If `true`, unit can supply fuel. +function UNIT:IsFuelSupply() + + -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. + local typename=self:GetTypeName() + + if typename=="M978 HEMTT Tanker" then + return true + elseif typename=="ATMZ-5" then + return true + elseif typename=="ATMZ-10" then + return true + elseif typename=="ATZ-5" then + return true + end + + return false +end --- Returns the unit's group if it exist and nil otherwise. -- @param Wrapper.Unit#UNIT self From b48958995a1505fa4811283fe84fdd3a2626c09e Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 28 Sep 2021 23:26:33 +0200 Subject: [PATCH 117/141] OPS - Fixed oscillation between UpdateRoute and CheckGroupDone in FLIGHTGROUP - Improvements regarding capturing zones. - Reduced log output --- Moose Development/Moose/Core/Zone.lua | 2 +- Moose Development/Moose/Ops/ArmyGroup.lua | 45 ++++- Moose Development/Moose/Ops/Auftrag.lua | 61 +++++- Moose Development/Moose/Ops/Brigade.lua | 84 ++++++-- Moose Development/Moose/Ops/Chief.lua | 196 +++++++++++++------ Moose Development/Moose/Ops/Cohort.lua | 10 +- Moose Development/Moose/Ops/Commander.lua | 52 +++-- Moose Development/Moose/Ops/FlightGroup.lua | 22 ++- Moose Development/Moose/Ops/Legion.lua | 13 +- Moose Development/Moose/Ops/OpsGroup.lua | 90 ++++++++- Moose Development/Moose/Ops/OpsTransport.lua | 20 +- Moose Development/Moose/Ops/OpsZone.lua | 126 +++++++++--- 12 files changed, 561 insertions(+), 160 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 813d74e1f..279474280 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1342,7 +1342,7 @@ function ZONE:New( ZoneName ) -- Error! if not Zone then - error( "Zone " .. ZoneName .. " does not exist." ) + env.error( "ERROR: Zone " .. ZoneName .. " does not exist!" ) return nil end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 499548c3f..14fa66182 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -812,6 +812,39 @@ function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Formation, end +--- On after "OutOfAmmo" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARMYGROUP:onafterOutOfAmmo(From, Event, To) + self:T(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) + + -- Fist, check if we want to rearm once out-of-ammo. + if self.rearmOnOutOfAmmo then + local truck=self:FindNearestAmmoSupply(30) + if truck then + self:T(self.lid..string.format("Found Ammo Truck %s [%s]")) + local Coordinate=truck:GetCoordinate() + self:Rearm(Coordinate, Formation) + return + end + end + + -- Second, check if we want to retreat once out of ammo. + if self.retreatOnOutOfAmmo then + self:Retreat() + return + end + + -- Third, check if we want to RTZ once out of ammo. + if self.rtzOnOutOfAmmo then + self:RTZ() + end + +end + + --- On before "Rearm" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -1403,6 +1436,7 @@ end -- @param #ARMYGROUP self -- @param #number Radius Search radius in NM. Default 30 NM. -- @return Wrapper.Group#GROUP Closest ammo supplying group or `nil` if no group is in the given radius. +-- @return #number Distance to closest group in meters. function ARMYGROUP:FindNearestAmmoSupply(Radius) -- Radius in meters. @@ -1410,6 +1444,9 @@ function ARMYGROUP:FindNearestAmmoSupply(Radius) -- Current positon. local coord=self:GetCoordinate() + + -- Get my coalition. + local myCoalition=self:GetCoalition() -- Scanned units. local units=coord:ScanUnits(Radius) @@ -1421,10 +1458,10 @@ function ARMYGROUP:FindNearestAmmoSupply(Radius) local unit=_unit --Wrapper.Unit#UNIT -- Check coaliton and if unit can supply ammo. - if unit:GetCoalition()==self:GetCoalition() and unit:IsAmmoSupply() then + if unit:GetCoalition()==myCoalition and unit:IsAmmoSupply() then -- Distance. - local d=unit:GetCoordinate():Get2DDistance(coord) + local d=coord:Get2DDistance(unit:GetCoord()) -- Check if distance is smaller. if d%s", Legion.alias, tostring(status), tostring(Status))) + self:T(self.lid..string.format("Setting LEGION %s to status %s-->%s", Legion.alias, tostring(status), tostring(Status))) -- New status. self.statusLegion[Legion.alias]=Status @@ -4750,6 +4773,24 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) DCStask.params=param table.insert(DCStasks, DCStask) + + elseif self.type==AUFTRAG.Type.ONGUARD then + + ---------------------- + -- ON GUARD Mission -- + ---------------------- + + local DCStask={} + + DCStask.id=AUFTRAG.SpecialTask.ONGUARD + + -- We create a "fake" DCS task and pass the parameters to the OPSGROUP. + local param={} + param.coordinate=self:GetObjective() + + DCStask.params=param + + table.insert(DCStasks, DCStask) else self:E(self.lid..string.format("ERROR: Unknown mission task!")) diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index 86b743233..9d77f24ed 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -18,7 +18,8 @@ -- @type BRIGADE -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity of output. --- @field #table rearmingZones Rearming zones. Each element is of type `#BRIGADE.RearmingZone`. +-- @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 Core.Set#SET_ZONE retreatZones Retreat zone set. -- @extends Ops.Legion#LEGION @@ -33,16 +34,17 @@ -- -- @field #BRIGADE BRIGADE = { - ClassName = "BRIGADE", - verbose = 0, - rearmingZones = {}, + ClassName = "BRIGADE", + verbose = 0, + rearmingZones = {}, + refuellingZones = {}, } ---- Rearming Zone. --- @type BRIGADE.RearmingZone +--- Supply Zone. +-- @type BRIGADE.SupplyZone -- @field Core.Zone#ZONE zone The zone. --- @field #boolean occupied If `true`, a rearming truck is present in the zone. --- @field Ops.Auftrag#AUFTRAG mission Mission assigned to supply ammo. +-- @field Ops.Auftrag#AUFTRAG mission Mission assigned to supply ammo or fuel. +-- @field #boolean markerOn If `true`, marker is on. -- @field Wrapper.Marker#MARKER marker F10 marker. --- BRIGADE class version. @@ -225,13 +227,12 @@ end --- Add a rearming zone. -- @param #BRIGADE self -- @param Core.Zone#ZONE RearmingZone Rearming zone. --- @return #BRIGADE.RearmingZone The rearming zone data. +-- @return #BRIGADE.SupplyZone The rearming zone data. function BRIGADE:AddRearmingZone(RearmingZone) - local rearmingzone={} --#BRIGADE.RearmingZone + local rearmingzone={} --#BRIGADE.SupplyZone rearmingzone.zone=RearmingZone - rearmingzone.occupied=false rearmingzone.mission=nil rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(), "Rearming Zone"):ToCoalition(self:GetCoalition()) @@ -241,6 +242,24 @@ function BRIGADE:AddRearmingZone(RearmingZone) end +--- Add a refuelling zone. +-- @param #BRIGADE self +-- @param Core.Zone#ZONE RefuellingZone Refuelling zone. +-- @return #BRIGADE.SupplyZone The refuelling zone data. +function BRIGADE:AddRefuellingZone(RefuellingZone) + + local supplyzone={} --#BRIGADE.SupplyZone + + supplyzone.zone=RefuellingZone + supplyzone.mission=nil + supplyzone.marker=MARKER:New(supplyzone.zone:GetCoordinate(), "Refuelling Zone"):ToCoalition(self:GetCoalition()) + + table.insert(self.rearmingZones, supplyzone) + + return supplyzone +end + + --- Get platoon by name. -- @param #BRIGADE self -- @param #string PlatoonName Name of the platoon. @@ -312,14 +331,28 @@ function BRIGADE:onafterStatus(From, Event, To) -- Rearming Zones --- --------------------- - for i,_rearmingzone in pairs(self.rearmingZones) do - local rearmingzone=_rearmingzone --#BRIGADE.RearmingZone + for _,_rearmingzone in pairs(self.rearmingZones) do + local rearmingzone=_rearmingzone --#BRIGADE.SupplyZone if (not rearmingzone.mission) or rearmingzone.mission:IsOver() then rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone) self:AddMission(rearmingzone.mission) end end + ----------------------- + -- Refuelling Zones --- + ----------------------- + + -- Check refuelling zones. + for _,_supplyzone in pairs(self.refuellingZones) do + local supplyzone=_supplyzone --#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 + ----------- -- Info --- @@ -396,19 +429,28 @@ function BRIGADE:onafterStatus(From, Event, To) ------------------- -- Rearming Info -- ------------------- - if self.verbose>=0 then + if self.verbose>=4 then local text="Rearming Zones:" for i,_rearmingzone in pairs(self.rearmingZones) do - local rearmingzone=_rearmingzone --#BRIGADE.RearmingZone - - local name=rearmingzone.zone:GetName() - - -- Platoon text. - text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", name, rearmingzone.mission:GetState(), rearmingzone.mission:CountOpsGroups()) - + local rearmingzone=_rearmingzone --#BRIGADE.SupplyZone + -- Info text. + text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", rearmingzone.zone:GetName(), rearmingzone.mission:GetState(), rearmingzone.mission:CountOpsGroups()) end self:I(self.lid..text) end + + ------------------- + -- Refuelling Info -- + ------------------- + if self.verbose>=4 then + local text="Refuelling Zones:" + for i,_refuellingzone in pairs(self.refuellingZones) do + local refuellingzone=_refuellingzone --#BRIGADE.SupplyZone + -- Info text. + text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", refuellingzone.zone:GetName(), refuellingzone.mission:GetState(), refuellingzone.mission:CountOpsGroups()) + end + self:I(self.lid..text) + end end diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 892987ceb..48eaf2b62 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -60,11 +60,13 @@ CHIEF.DEFCON = { --- Strategy. -- @type CHIEF.Strategy +-- @field #string PASSIVE No targets at all are engaged. -- @field #string DEFENSIVE Only target in our own terretory are engaged. -- @field #string OFFENSIVE Targets in own terretory and yellow zones are engaged. --- @field #string AGGRESSIVE Targets in own terretroy, yellow zones and engage zones are engaged. +-- @field #string AGGRESSIVE Targets in own terretory, conflict zones and attack zones are engaged. -- @field #string TOTALWAR Anything is engaged anywhere. CHIEF.Strategy = { + PASSIVE="Passive", DEFENSIVE="Defensive", OFFENSIVE="Offensive", AGGRESSIVE="Aggressive", @@ -570,13 +572,25 @@ end --- Add a rearming zone. -- @param #CHIEF self -- @param Core.Zone#ZONE RearmingZone Rearming zone. --- @return Ops.Brigade#BRIGADE.RearmingZone The rearming zone data. +-- @return Ops.Brigade#BRIGADE.SupplyZone The rearming zone data. function CHIEF:AddRearmingZone(RearmingZone) -- Hand over to commander. - local rearmingzone=self.commander:AddRearmingZone(RearmingZone) + local supplyzone=self.commander:AddRearmingZone(RearmingZone) - return rearmingzone + return supplyzone +end + +--- Add a refuelling zone. +-- @param #CHIEF self +-- @param Core.Zone#ZONE RefuellingZone Refuelling zone. +-- @return Ops.Brigade#BRIGADE.SupplyZone The refuelling zone data. +function CHIEF:AddRefuellingZone(RefuellingZone) + + -- Hand over to commander. + local supplyzone=self.commander:AddRefuellingZone(RefuellingZone) + + return supplyzone end @@ -840,7 +854,8 @@ function CHIEF:onafterStatus(From, Event, To) for i,_opszone in pairs(self.zonequeue) do local opszone=_opszone --Ops.OpsZone#OPSZONE - text=text..string.format("\n[%d] %s [%s]: owner=%d [%d]: Blue=%d, Red=%d, Neutral=%d", i, opszone.zone:GetName(), opszone:GetState(), opszone:GetOwner(), opszone:GetPreviousOwner(), opszone.Nblu, opszone.Nred, opszone.Nnut) + 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) end self:I(self.lid..text) @@ -1059,14 +1074,22 @@ function CHIEF:CheckTargetQueue() local target=_target --Ops.Target#TARGET -- Is this a threat? - local isThreat=target.threatlevel0>=self.threatLevelMin and target.threatlevel0<=self.threatLevelMax + local isThreat=target.threatlevel0>=self.threatLevelMin and target.threatlevel0<=self.threatLevelMax -- Check that target is alive and not already a mission has been assigned. if target:IsAlive() and (target.importance==nil or target.importance<=vip) and isThreat and not target.mission then -- Check if this target is "valid", i.e. fits with the current strategy. local valid=false - if self.strategy==CHIEF.Strategy.DEFENSIVE then + if self.strategy==CHIEF.Strategy.PASSIVE then + + --- + -- PASSIVE: No targets at all are attacked. + --- + + valid=false + + elseif self.strategy==CHIEF.Strategy.DEFENSIVE then --- -- DEFENSIVE: Attack inside borders only. @@ -1146,7 +1169,6 @@ function CHIEF:CheckTargetQueue() if mission then for _,_asset in pairs(assets) do local asset=_asset - asset.isReserved=true mission:AddAsset(asset) end Legions=legions @@ -1204,19 +1226,20 @@ function CHIEF:CheckOpsZoneQueue() -- Sort results table wrt ?. local function _sort(a, b) - local taskA=a --Ops.Target#TARGET - local taskB=b --Ops.Target#TARGET + local taskA=a --Ops.OpsZone#OPSZONE + local taskB=b --Ops.OpsZone#OPSZONE return (taskA.prio 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}) + + end + + else + + if not hasMissionCAS then + + -- Debug message. + self:T(self.lid..string.format("Zone is NOT empty ==> recruit CAS assets")) + + -- Recruite CAS assets. + local recruited=self:RecruitAssetsForZone(opszone, AUFTRAG.Type.CAS, 1, 3) + + end + + end end @@ -1568,45 +1619,78 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax -- Target position. local TargetVec2=OpsZone.zone:GetVec2() - + -- Recruite infantry assets. - local recruitedInf, assetsInf, legionsInf=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2, nil, nil, nil, nil, Categories, Attributes) + local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2, nil, nil, nil, nil, Categories, Attributes) - if recruitedInf then + if recruited then - env.info(string.format("Recruited %d assets from for PATROL mission", #assetsInf)) - - -- Recruit transport assets for infantry. - local recruitedTrans, transport=LEGION.AssignAssetsForTransport(self.commander, self.commander.legions, assetsInf, 1, 1, OpsZone.zone, nil, {Group.Category.HELICOPTER, Group.Category.GROUND}) - - - -- Create Patrol zone mission. - local mission=AUFTRAG:NewPATROLZONE(OpsZone.zone) - mission:SetEngageDetected() - - -- Add assets to mission. - for _,asset in pairs(assetsInf) do - mission:AddAsset(asset) - end - - -- Attach OPS transport to mission. - mission.opstransport=transport + if MissionType==AUFTRAG.Type.PATROLZONE then + + -- Debug messgage. + self:T2(self.lid..string.format("Recruited %d assets from for PATROL mission", #assets)) + + local recruitedTrans=true + local transport=nil + if Attributes and Attributes[1]==GROUP.Attribute.GROUND_INFANTRY then + + -- 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}) - -- Assign mission to legions. - for _,_legion in pairs(legionsInf) do - local legion=_legion --Ops.Legion#LEGION - self.commander:MissionAssign(legion, mission) - end - - -- Attach mission to ops zone. - -- TODO: Need a better way! - OpsZone.missionPatrol=mission + end + + if recruitedTrans then - return true - else - LEGION.UnRecruitAssets(assetsInf) - return false - end + -- Create Patrol zone mission. + local mission=AUFTRAG:NewPATROLZONE(OpsZone.zone) + mission:SetEngageDetected() + + + -- Add assets to mission. + for _,asset in pairs(assets) do + mission:AddAsset(asset) + end + + -- Attach OPS transport to mission. + mission.opstransport=transport + + -- Assign mission to legions. + for _,_legion in pairs(legions) do + local legion=_legion --Ops.Legion#LEGION + self.commander:MissionAssign(legion, mission) + end + + -- Attach mission to ops zone. + -- TODO: Need a better way! + OpsZone.missionPatrol=mission + + else + LEGION.UnRecruitAssets(assets) + end + + elseif MissionType==AUFTRAG.Type.CAS then + + -- Create Patrol zone mission. + local mission=AUFTRAG:NewCAS(OpsZone.zone) + + -- Add assets to mission. + for _,asset in pairs(assets) do + mission:AddAsset(asset) + end + + -- Assign mission to legions. + for _,_legion in pairs(legions) do + local legion=_legion --Ops.Legion#LEGION + self.commander:MissionAssign(legion, mission) + end + + -- Attach mission to ops zone. + -- TODO: Need a better way! + OpsZone.missionCAS=mission + + end + + end return nil end diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 47a167e8a..11ed4dd31 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -906,8 +906,14 @@ function COHORT:RecruitAssets(MissionType, Npayloads) end - --TODO: Check transport for combat readyness! - + -- Check transport/cargo for combat readyness! + if flightgroup:IsLoading() or flightgroup:IsTransporting() or flightgroup:IsUnloading() or flightgroup:IsPickingup() or flightgroup:IsCarrier() then + combatready=false + end + if flightgroup:IsCargo() or flightgroup:IsBoarding() or flightgroup:IsAwaitingLift() then + combatready=false + end + -- This asset is "combatready". if combatready then self:I(self.lid.."Adding SPAWNED asset to ANOTHER mission as it is COMBATREADY") diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index cab981797..3eacd7030 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -20,7 +20,8 @@ -- @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.RearmingZone`. +-- @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 Ops.Chief#CHIEF chief Chief of staff. -- @extends Core.Fsm#FSM @@ -35,12 +36,13 @@ -- -- @field #COMMANDER COMMANDER = { - ClassName = "COMMANDER", - verbose = 0, - legions = {}, - missionqueue = {}, - transportqueue = {}, - rearmingZones = {}, + ClassName = "COMMANDER", + verbose = 0, + legions = {}, + missionqueue = {}, + transportqueue = {}, + rearmingZones = {}, + refuellingZones = {}, } --- COMMANDER class version. @@ -335,13 +337,12 @@ end --- Add a rearming zone. -- @param #COMMANDER self -- @param Core.Zone#ZONE RearmingZone Rearming zone. --- @return Ops.Brigade#BRIGADE.RearmingZone The rearming zone data. +-- @return Ops.Brigade#BRIGADE.SupplyZone The rearming zone data. function COMMANDER:AddRearmingZone(RearmingZone) - local rearmingzone={} --Ops.Brigade#BRIGADE.RearmingZone + local rearmingzone={} --Ops.Brigade#BRIGADE.SupplyZone rearmingzone.zone=RearmingZone - rearmingzone.occupied=false rearmingzone.mission=nil rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(), "Rearming Zone"):ToCoalition(self:GetCoalition()) @@ -350,6 +351,23 @@ function COMMANDER:AddRearmingZone(RearmingZone) return rearmingzone end +--- Add a refuelling zone. +-- @param #COMMANDER self +-- @param Core.Zone#ZONE RefuellingZone Refuelling zone. +-- @return Ops.Brigade#BRIGADE.SupplyZone The refuelling zone data. +function COMMANDER:AddRefuellingZone(RefuellingZone) + + local rearmingzone={} --Ops.Brigade#BRIGADE.SupplyZone + + rearmingzone.zone=RefuellingZone + rearmingzone.mission=nil + rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(), "Refuelling Zone"):ToCoalition(self:GetCoalition()) + + table.insert(self.refuellingZones, rearmingzone) + + return rearmingzone +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -402,13 +420,23 @@ function COMMANDER:onafterStatus(From, Event, To) -- Check rearming zones. for _,_rearmingzone in pairs(self.rearmingZones) do - local rearmingzone=_rearmingzone --#BRIGADE.RearmingZone + local rearmingzone=_rearmingzone --#BRIGADE.SupplyZone -- Check if mission is nil or over. if (not rearmingzone.mission) or rearmingzone.mission:IsOver() then rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone) self:AddMission(rearmingzone.mission) end - end + end + + -- Check refuelling zones. + for _,_supplyzone in pairs(self.refuellingZones) do + local supplyzone=_supplyzone --#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 --- -- LEGIONS diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index c59578513..16cb5e3ae 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1140,7 +1140,7 @@ function FLIGHTGROUP:OnEventEngineShutdown(EventData) if element.unit and element.unit:IsAlive() then local airbase=self:GetClosestAirbase() - local parking=self:GetParkingSpot(element, 10, airbase) + local parking=self:GetParkingSpot(element, 100, airbase) if airbase and parking then self:ElementArrived(element, airbase, parking) @@ -1548,7 +1548,7 @@ end function FLIGHTGROUP:onafterParking(From, Event, To) -- Get closest airbase - local airbase=self:GetClosestAirbase() --self.group:GetCoordinate():GetClosestAirbase() + local airbase=self:GetClosestAirbase() local airbasename=airbase:GetName() or "unknown" -- Debug info @@ -2006,7 +2006,7 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n, N) end -- Set current waypoint or we get problem that the _PassingWaypoint function is triggered too early, i.e. right now and not when passing the next WP. - local current=self.group:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO, waypointType, waypointAction, speed, true, nil, {}, "Current") + local current=self:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO, waypointType, waypointAction, speed, true, nil, {}, "Current") table.insert(wp, current) -- Add remaining waypoints to route. @@ -2087,14 +2087,14 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) if delay and delay>0 then -- Debug info. - self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done in %.3f seconds...", fsmstate, delay)) + self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done in %.3f seconds... (t=%.4f)", fsmstate, delay, timer.getTime())) -- Delayed call. self:ScheduleOnce(delay, FLIGHTGROUP._CheckGroupDone, self) else -- Debug info. - self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done?", fsmstate)) + self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done? (t=%.4f)", fsmstate, timer.getTime())) -- First check if there is a paused mission that if self.missionpaused then @@ -2134,7 +2134,9 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) self:T(self.lid..string.format("Remaining (final=%s): missions=%d, tasks=%d, transports=%d", tostring(self.passedfinalwp), nMissions, nTasks, nTransports)) -- Final waypoint passed? - if self:HasPassedFinalWaypoint() then + -- Or next waypoint index is the first waypoint. Could be that the group was on a mission and the mission waypoints were deleted. then the final waypoint is FALSE but no real waypoint left. + -- Since we do not do ad infinitum, this leads to a rapid oscillation between UpdateRoute and CheckGroupDone! + if self:HasPassedFinalWaypoint() or self:GetWaypointIndexNext()==1 then --- -- Final Waypoint PASSED @@ -2376,7 +2378,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) local althold=self.isHelo and 1000+math.random(10)*100 or math.random(4,10)*1000 -- Holding points. - local c0=self.group:GetCoordinate() + local c0=self:GetCoordinate() local p0=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(althold)) local p1=nil local wpap=nil @@ -2527,7 +2529,7 @@ end function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Altitude, Speed) -- Group will orbit at its current position. - local Coord=self.group:GetCoordinate() + local Coord=self:GetCoordinate() -- Set altitude: 1000 ft for helos and 10,000 ft for panes. if Altitude then @@ -2597,7 +2599,7 @@ function FLIGHTGROUP:onafterRefuel(From, Event, To, Coordinate) local Speed=self.speedCruise - local coordinate=self.group:GetCoordinate() + local coordinate=self:GetCoordinate() Coordinate=Coordinate or coordinate:Translate(UTILS.NMToMeters(5), self.group:GetHeading(), true) @@ -3972,7 +3974,7 @@ function FLIGHTGROUP:_UpdateMenu(delay) self:I(self.lid.."FF updating menu NOW") -- Get current position of group. - local position=self.group:GetCoordinate() + local position=self:GetCoordinate() -- Get all FLIGHTCONTROLS local fc={} diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 144f89a6d..5a78d0ac0 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -841,6 +841,13 @@ function LEGION:onafterTransportCancel(From, Event, To, Transport) opsgroup:TransportCancel(Transport) end + -- Delete awaited transport. + local cargos=Transport:GetCargoOpsGroups(false) + for _,_cargo in pairs(cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP + cargo:_DelMyLift(Transport) + end + -- Remove asset from mission. Transport:DelAsset(asset) @@ -1068,7 +1075,7 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) if cohort then -- Debug info. - self:I(self.lid..string.format("Cohort asset spawned %s", asset.spawngroupname)) + self:T(self.lid..string.format("Cohort asset spawned %s", asset.spawngroupname)) -- Create a flight group. local flightgroup=self:_CreateFlightGroup(asset) @@ -1150,7 +1157,7 @@ function LEGION:onafterAssetSpawned(From, Event, To, group, asset, request) -- Add group to the detection set of the CHIEF (INTEL). local chief=self.chief or (self.commander and self.commander.chief or nil) --Ops.Chief#CHIEF if chief then - self:I(self.lid..string.format("Adding group %s to agents of CHIEF", group:GetName())) + self:T(self.lid..string.format("Adding group %s to agents of CHIEF", group:GetName())) chief.detectionset:AddGroup(asset.flightgroup.group) end @@ -1932,7 +1939,7 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, local RightAttribute=CheckAttribute(cohort) -- Debug info. - cohort:I(cohort.lid..string.format("State=%s: Capable=%s, InRange=%s, Refuel=%s, CanCarry=%s, RightCategory=%s, RightAttribute=%s", + cohort:T(cohort.lid..string.format("State=%s: Capable=%s, InRange=%s, Refuel=%s, CanCarry=%s, RightCategory=%s, RightAttribute=%s", cohort:GetState(), tostring(Capable), tostring(InRange), tostring(Refuel), tostring(CanCarry), tostring(RightCategory), tostring(RightAttribute))) -- Check OnDuty, capable, in range and refueling type (if TANKER). diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index dbe18ae14..2a09a1f56 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -69,6 +69,8 @@ -- @field #number Ndestroyed Number of destroyed units. -- @field #number Nkills Number kills of this groups. -- +-- @field #boolean rearmOnOutOfAmmo If `true`, group will go to rearm once it runs out of ammo. +-- -- @field Ops.Legion#LEGION legion Legion the group belongs to. -- @field Ops.Cohort#COHORT cohort Cohort the group belongs to. -- @@ -424,11 +426,13 @@ OPSGROUP.CarrierStatus={ --- Cargo status. -- @type OPSGROUP.CargoStatus +-- @field #string AWAITING Group is awaiting carrier. -- @field #string NOTCARGO This group is no cargo yet. -- @field #string ASSIGNED Cargo is assigned to a carrier. -- @field #string BOARDING Cargo is boarding a carrier. -- @field #string LOADED Cargo is loaded into a carrier. OPSGROUP.CargoStatus={ + AWAITING="Awaiting carrier", NOTCARGO="not cargo", ASSIGNED="assigned to carrier", BOARDING="boarding", @@ -1170,6 +1174,14 @@ function OPSGROUP:SetEngageDetectedOff() return self end +--- Set that group is going to rearm once it runs out of ammo. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:SetRearmOnOutOfAmmo() + self.rearmOnOutOfAmmo=true + return self +end + --- Check if an element of the group has line of sight to a coordinate. -- @param #OPSGROUP self -- @param Core.Point#COORDINATE Coordinate The position to which we check the LoS. @@ -2277,6 +2289,49 @@ function OPSGROUP:IsNotCargo(CheckTransport) return notcargo end +--- Check if awaiting a transport. +-- @param #OPSGROUP self +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. +-- @return #OPSGROUP self +function OPSGROUP:_AddMyLift(Transport) + self.mylifts=self.mylifts or {} + self.mylifts[Transport.uid]=true + return self +end + +--- Remove my lift. +-- @param #OPSGROUP self +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. +-- @return #OPSGROUP self +function OPSGROUP:_DelMyLift(Transport) + if self.mylifts then + self.mylifts[Transport.uid]=nil + end + return self +end + + +--- Check if awaiting a transport lift. +-- @param #OPSGROUP self +-- @param Ops.OpsTransport#OPSTRANSPORT Transport (Optional) The transport. +-- @return #boolean If true, group is awaiting transport lift.. +function OPSGROUP:IsAwaitingLift(Transport) + + if self.mylifts then + + for uid,iswaiting in pairs(self.mylifts) do + if Transport==nil or Transport.uid==uid then + if iswaiting==true then + return true + end + end + end + + end + + return false +end + --- Check if the group is currently boarding a carrier. -- @param #OPSGROUP self -- @param #string CarrierGroupName (Optional) Additionally check if group is boarding this particular carrier group. @@ -3589,10 +3644,20 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) elseif Task.dcstask.id==AUFTRAG.SpecialTask.ALERT5 then --- - -- Task Alert 5 mission. + -- Task "Alert 5" mission. --- -- Just stay put on the airfield and wait until something happens. + + elseif Task.dcstask.id==AUFTRAG.SpecialTask.ONGUARD then + + --- + -- Task "On Guard" Mission. + --- + + -- Just stay put. + --TODO: Change ALARM STATE + else @@ -3690,7 +3755,9 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) elseif Task.dcstask.id==AUFTRAG.SpecialTask.FUELSUPPLY then done=true elseif Task.dcstask.id==AUFTRAG.SpecialTask.ALERT5 then - done=true + done=true + elseif Task.dcstask.id==AUFTRAG.SpecialTask.ONGUARD then + done=true elseif stopflag==1 or (not self:IsAlive()) or self:IsDead() or self:IsStopped() then -- Manual call TaskDone if setting flag to one was not successful. done=true @@ -4200,7 +4267,7 @@ function OPSGROUP:onafterMissionCancel(From, Event, To, Mission) --- -- Alert 5 missoins dont have a task set, which could be cancelled. - if Mission.type==AUFTRAG.Type.ALERT5 then + if Mission.type==AUFTRAG.Type.ALERT5 or Mission.type==AUFTRAG.Type.ONGUARD then self:MissionDone(Mission) return end @@ -4440,6 +4507,10 @@ function OPSGROUP:RouteToMission(mission, delay) elseif mission.type==AUFTRAG.Type.ARTY then + --- + -- ARTY + --- + -- Get weapon range. local weapondata=self:GetWeaponData(mission.engageWeaponType) @@ -5990,7 +6061,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterOutOfAmmo(From, Event, To) - self:T(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) + self:T(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -7174,7 +7245,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) -- New cargo status. CargoGroup:_NewCargoStatus(OPSGROUP.CargoStatus.LOADED) - + -- Clear all waypoints. CargoGroup:ClearWaypoints() @@ -7191,6 +7262,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) -- Trigger "Loaded" event for current cargo transport. if self.cargoTransport then + CargoGroup:_DelMyLift(self.cargoTransport) self.cargoTransport:Loaded(CargoGroup, self, carrier) else self:T(self.lid..string.format("WARNING: Loaded cargo but no current OPSTRANSPORT assignment!")) @@ -7341,7 +7413,7 @@ function OPSGROUP:onafterTransport(From, Event, To) end -- ARMYGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=1 + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, ENUMS.Formation.Vehicle.OnRoad) ; waypoint.detour=1 -- Give cruise command. self:Cruise() @@ -8444,7 +8516,7 @@ function OPSGROUP:_CheckAmmoStatus() if self.outofMissilesAA and ammo.MissilesAA>0 then self.outofMissilesAA=false end - if ammo.MissilesAA and self.ammo.MissilesAA>0 and not self.outofMissilesAA then + if ammo.MissilesAA==0 and self.ammo.MissilesAA>0 and not self.outofMissilesAA then self.outofMissilesAA=true self:OutOfMissilesAA() end @@ -8453,7 +8525,7 @@ function OPSGROUP:_CheckAmmoStatus() if self.outofMissilesAG and ammo.MissilesAG>0 then self.outofMissilesAG=false end - if ammo.MissilesAG and self.ammo.MissilesAG>0 and not self.outofMissilesAG then + if ammo.MissilesAG==0 and self.ammo.MissilesAG>0 and not self.outofMissilesAG then self.outofMissilesAG=true self:OutOfMissilesAG() end @@ -8462,7 +8534,7 @@ function OPSGROUP:_CheckAmmoStatus() if self.outofMissilesAS and ammo.MissilesAS>0 then self.outofMissilesAS=false end - if ammo.MissilesAS and self.ammo.MissilesAS>0 and not self.outofMissilesAS then + if ammo.MissilesAS==0 and self.ammo.MissilesAS>0 and not self.outofMissilesAS then self.outofMissilesAS=true self:OutOfMissilesAS() end diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 8e40b8a4f..d55de1522 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -485,6 +485,8 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) table.insert(TransportZoneCombo.Cargos, cargo) TransportZoneCombo.Ncargo=TransportZoneCombo.Ncargo+1 + cargo.opsgroup:_AddMyLift(self) + end else @@ -1296,7 +1298,7 @@ end function OPSTRANSPORT:AddLegion(Legion) -- Debug info. - self:I(self.lid..string.format("Adding legion %s", Legion.alias)) + self:T(self.lid..string.format("Adding legion %s", Legion.alias)) -- Add legion to table. table.insert(self.legions, Legion) @@ -1315,7 +1317,7 @@ function OPSTRANSPORT:RemoveLegion(Legion) local legion=self.legions[i] --Ops.Legion#LEGION if legion.alias==Legion.alias then -- Debug info. - self:I(self.lid..string.format("Removing legion %s", Legion.alias)) + self:T(self.lid..string.format("Removing legion %s", Legion.alias)) table.remove(self.legions, i) return self end @@ -1409,7 +1411,7 @@ function OPSTRANSPORT:SetLegionStatus(Legion, Status) local status=self:GetLegionStatus(Legion) -- Debug info. - self:I(self.lid..string.format("Setting LEGION %s to status %s-->%s", Legion.alias, tostring(status), tostring(Status))) + self:T(self.lid..string.format("Setting LEGION %s to status %s-->%s", Legion.alias, tostring(status), tostring(Status))) -- New status. self.statusLegion[Legion.alias]=Status @@ -1681,7 +1683,7 @@ function OPSTRANSPORT:onafterCancel(From, Event, To) local Ngroups = #self.carriers -- Debug info. - self:I(self.lid..string.format("CANCELLING mission in status %s. Will wait for %d groups to report mission DONE before evaluation", self.status, Ngroups)) + self:I(self.lid..string.format("CANCELLING transport in status %s. Will wait for %d carrier groups to report DONE before evaluation", self.status, Ngroups)) -- Time stamp. self.Tover=timer.getAbsTime() @@ -1728,6 +1730,14 @@ function OPSTRANSPORT:onafterCancel(From, Event, To) carrier:TransportCancel(self) end + -- Delete awaited transport. + local cargos=self:GetCargoOpsGroups(false) + for _,_cargo in pairs(cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP + cargo:_DelMyLift(self) + end + + end -- Special mission states. @@ -1969,7 +1979,7 @@ function OPSTRANSPORT:_CountCargosInZone(Zone, Delivered, Carrier, TransportZone local isInUtero=cargo:IsInUtero() -- Debug info. - self:I(self.lid..string.format("Cargo=%s: notcargo=%s, iscarrier=%s inzone=%s, inutero=%s", cargo:GetName(), tostring(cargo:IsNotCargo(true)), tostring(iscarrier(cargo)), tostring(isInZone), tostring(isInUtero))) + self:T(self.lid..string.format("Cargo=%s: notcargo=%s, iscarrier=%s inzone=%s, inutero=%s", cargo:GetName(), tostring(cargo:IsNotCargo(true)), tostring(iscarrier(cargo)), tostring(isInZone), tostring(isInUtero))) -- We look for groups that are not cargo, in the zone or in utero. if isNotCargo and (isInZone or isInUtero) then diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index 5ea26d633..284c0a5f4 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -36,6 +36,8 @@ -- @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). -- @extends Core.Fsm#FSM --- Be surprised! @@ -90,15 +92,34 @@ function OPSZONE:New(Zone, CoalitionOwner) local self=BASE:Inherit(self, FSM:New()) -- #OPSZONE -- Check if zone name instead of ZONE object was passed. - if type(Zone)=="string" then - Zone=ZONE:New(Zone) + if Zone then + if type(Zone)=="string" then + -- Convert string into a ZONE or ZONE_AIRBASE + local Name=Zone + Zone=ZONE:New(Name) + if not Zone then + local airbase=AIRBASE:FindByName(Name) + if airbase then + Zone=ZONE_AIRBASE:New(Name, 2000) + end + end + if not Zone then + self:E(string.format("ERROR: No ZONE or ZONE_AIRBASE found for name: %s", Name)) + return nil + end + end + else + self:E("ERROR: First parameter Zone is nil in OPSZONE:New(Zone) call!") + return nil end -- Basic checks. - if not Zone then - self:E("ERROR: OPSZONE not found!") - return nil - elseif not Zone:IsInstanceOf("ZONE_RADIUS") then + if Zone:IsInstanceOf("ZONE_AIRBASE") then + self.airbase=Zone._.ZoneAirbase + self.airbaseName=self.airbase:GetName() + elseif Zone:IsInstanceOf("ZONE_RADIUS") then + -- Nothing to do. + else self:E("ERROR: OPSZONE must be a SPHERICAL zone due to DCS restrictions!") return nil end @@ -115,10 +136,21 @@ function OPSZONE:New(Zone, CoalitionOwner) self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL self.ownerPrevious=CoalitionOwner or coalition.side.NEUTRAL + -- 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 -- Status timer. @@ -138,8 +170,7 @@ function OPSZONE:New(Zone, CoalitionOwner) self:AddTransition("Empty", "Guarded", "Guarded") -- Owning coalition left the zone and returned. self:AddTransition("*", "Empty", "Empty") -- No red or blue units inside the zone. - - + self:AddTransition("*", "Attacked", "Attacked") -- A guarded zone is under attack. self:AddTransition("*", "Defeated", "Guarded") -- The owning coalition defeated an attack. @@ -318,6 +349,25 @@ function OPSZONE:SetNeutralCanCapture(CanCapture) return self end +--- **[CHIEF]** Set mission priority. +-- @param #OPSZONE self +-- @param #number Prio Priority 1=high, 100=low. Default 50. +-- @return #OPSZONE self +function OPSZONE:SetPriority(Prio) + self.prio=Prio or 50 + return self +end + +--- **[CHIEF]** Set importance. +-- @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`. +-- @return #OPSZONE self +function OPSZONE:SetImportance(Importance) + self.importance=Importance + return self +end + + --- Get current owner of the zone. -- @param #OPSZONE self -- @return #number Owner coalition. @@ -426,21 +476,14 @@ function OPSZONE:onafterStart(From, Event, To) -- Reinit the timer. self.timerStatus=self.timerStatus or TIMER:New(OPSZONE.Status, self) - -- Perform initial scan. - self:Scan() - - if self.Nblu==0 and self.Nred==0 then - elseif self.Nblu>0 and self.Nred>0 then - - elseif self.Nblu>0 then - - elseif self.Nred>0 then - - end - -- Status update. self.timerStatus:Start(1, 60) + -- Handle base captured event. + if self.airbase then + self:HandleEvent(EVENTS.BaseCaptured) + end + end --- Stop OPSZONE FSM. @@ -456,6 +499,9 @@ function OPSZONE:onafterStop(From, Event, To) -- Reinit the timer. self.timerStatus:Stop() + -- Unhandle events. + self:UnHandleEvent(EVENTS.BaseCaptured) + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -481,6 +527,9 @@ function OPSZONE:Status() -- Scanning zone. self:Scan() + -- Evaluate the scan result. + self:EvaluateZone() + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -782,10 +831,14 @@ function OPSZONE:EvaluateZone() if Nblu>0 then -- Blue captured red zone. - self:Captured(coalition.side.BLUE) + if not self.airbase then + self:Captured(coalition.side.BLUE) + end elseif Nnut>0 and self.neutralCanCapture then -- Neutral captured red zone. - self:Captured(coalition.side.NEUTRAL) + if not self.airbase then + self:Captured(coalition.side.NEUTRAL) + end else -- Red zone is now empty (but will remain red). if not self:IsEmpty() then @@ -835,10 +888,14 @@ function OPSZONE:EvaluateZone() if Nred>0 then -- Red captured blue zone. - self:Captured(coalition.side.RED) + if not self.airbase then + self:Captured(coalition.side.RED) + end elseif Nnut>0 and self.neutralCanCapture then -- Neutral captured blue zone. - self:Captured(coalition.side.NEUTRAL) + if not self.airbase then + self:Captured(coalition.side.NEUTRAL) + end else -- Blue zone is empty now. if not self:IsEmpty() then @@ -896,10 +953,14 @@ function OPSZONE:EvaluateZone() self.isContested=true elseif Nred>0 then -- Red captured neutral zone. - self:Captured(coalition.side.RED) + if not self.airbase then + self:Captured(coalition.side.RED) + end elseif Nblu>0 then -- Blue captured neutral zone. - self:Captured(coalition.side.BLUE) + if not self.airbase then + self:Captured(coalition.side.BLUE) + end else -- Neutral zone is empty now. if not self:IsEmpty() then @@ -947,7 +1008,7 @@ function OPSZONE:OnEventHit(EventData) end ---- Monitor hit events. +--- Monitor base captured events. -- @param #OPSZONE self -- @param Core.Event#EVENTDATA EventData The event data. function OPSZONE:OnEventBaseCaptured(EventData) @@ -960,6 +1021,17 @@ function OPSZONE:OnEventBaseCaptured(EventData) -- Check that this airbase belongs or did belong to this warehouse. if EventData.PlaceName==self.airbaseName then + -- New coalition of the airbase + local CoalitionNew=airbase:GetCoalition() + + -- Debug info. + self:I(self.lid..string.format("EVENT BASE CAPTURED: New coalition of airbase %s: %d [previous=%d]", self.airbaseName, CoalitionNew, self.ownerCurrent)) + + -- Check that coalition actually changed. + if CoalitionNew~=self.ownerCurrent then + self:Captured(CoalitionNew) + end + end end From c7a2b34f597c63e379962448cb4155ba21c094a2 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 29 Sep 2021 08:15:22 +0200 Subject: [PATCH 118/141] Update Warehouse.lua - Disabled fuel check. --- Moose Development/Moose/Functional/Warehouse.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 5512dde73..600d1ebdf 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -3600,7 +3600,7 @@ function WAREHOUSE:onafterStatus(From, Event, To) self:_PrintQueue(self.pending, "Queue pending") -- Check fuel for all assets. - self:_CheckFuel() + --self:_CheckFuel() -- Update warhouse marker on F10 map. self:_UpdateWarehouseMarkText() From 2ae2ee64be804bc3a079e613b1ec491987c35bb9 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 1 Oct 2021 12:04:15 +0200 Subject: [PATCH 119/141] 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 + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From fa6fbca67b08662def42c3a0220a7b6cc1a18805 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 3 Oct 2021 21:33:33 +0200 Subject: [PATCH 120/141] OPS - Added tanker and CAP zones --- Moose Development/Moose/Core/Base.lua | 3 +- Moose Development/Moose/Ops/AirWing.lua | 19 ++-- Moose Development/Moose/Ops/Auftrag.lua | 19 ++-- Moose Development/Moose/Ops/Chief.lua | 64 +++++++++--- Moose Development/Moose/Ops/Cohort.lua | 2 +- Moose Development/Moose/Ops/Commander.lua | 99 +++++++++++++++++-- Moose Development/Moose/Ops/FlightControl.lua | 1 + Moose Development/Moose/Ops/FlightGroup.lua | 5 +- Moose Development/Moose/Ops/Legion.lua | 6 +- Moose Development/Moose/Ops/OpsGroup.lua | 4 + Moose Development/Moose/Ops/Target.lua | 9 +- Moose Development/Moose/Wrapper/Unit.lua | 2 +- 12 files changed, 189 insertions(+), 44 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 2c492a949..3bd050937 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -234,7 +234,8 @@ FORMATION = { -- @param #BASE self -- @return #BASE function BASE:New() - local self = routines.utils.deepCopy( self ) -- Create a new self instance + --local self = routines.utils.deepCopy( self ) -- Create a new self instance + local self = UTILS.DeepCopy(self) _ClassID = _ClassID + 1 self.ClassID = _ClassID diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 91311559a..d23079f07 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -139,6 +139,16 @@ AIRWING = { -- @field #number noccupied Number of flights on this patrol point. -- @field Wrapper.Marker#MARKER marker F10 marker. +--- Patrol zone. +-- @type AIRWING.PatrolZone +-- @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 Ops.Auftrag#AUFTRAG mission Mission assigned. +-- @field Wrapper.Marker#MARKER marker F10 marker. + --- AWACS zone. -- @type AIRWING.AwacsZone -- @field Core.Zone#ZONE zone Zone. @@ -146,20 +156,13 @@ AIRWING = { -- @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. +-- @extends #AIRWING.PatrolZone --- AIRWING class version. -- @field #string version diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index d6ed68002..f8775e972 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -4098,12 +4098,13 @@ end -- @return #number Number of alive target units. function AUFTRAG:CountMissionTargets() + local N=0 + if self.engageTarget then - return self.engageTarget:CountTargets() - else - return 0 + N=self.engageTarget:CountTargets() end + return N end --- Get initial number of targets. @@ -4167,14 +4168,16 @@ end -- @param #AUFTRAG self -- @return Wrapper.Positionable#POSITIONABLE The target object. Could be many things. function AUFTRAG:GetObjective() - return self:GetTargetData():GetObject() + local objective=self:GetTargetData():GetObject() + return objective end --- Get type of target. -- @param #AUFTRAG self -- @return #string The target type. function AUFTRAG:GetTargetType() - return self:GetTargetData().Type + local ttype=self:GetTargetData().Type + return ttype end --- Get 2D vector of target. @@ -4183,7 +4186,8 @@ end function AUFTRAG:GetTargetVec2() local coord=self:GetTargetCoordinate() if coord then - return coord:GetVec2() + local vec2=coord:GetVec2() + return vec2 end return nil end @@ -4216,7 +4220,8 @@ end function AUFTRAG:GetTargetName() if self.engageTarget then - return self.engageTarget:GetName() + local name=self.engageTarget:GetName() + return name end return "N/A" diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index be51a22ce..e1be3bdb0 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -645,7 +645,7 @@ function CHIEF:IsTarget(Target) for _,_target in pairs(self.targetqueue) do local target=_target --Ops.Target#TARGET - if target.uid==Target.uid then + if target.uid==Target.uid or target:GetName()==Target:GetName() then return true end end @@ -725,20 +725,54 @@ function CHIEF:AddRefuellingZone(RefuellingZone) return supplyzone end ---- Add an AWACS zone. +--- Add a CAP zone. -- @param #CHIEF self --- @param Core.Zone#ZONE AwacsZone Zone. +-- @param Core.Zone#ZONE Zone 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) +-- @return Ops.AirWing#AIRWING.PatrolZone The CAP zone data. +function CHIEF:AddCapZone(Zone, Altitude, Speed, Heading, Leg) -- Hand over to commander. - local awacszone=self.commander:AddAwacsZone(AwacsZone, Altitude, Speed, Heading, Leg) + local zone=self.commander:AddCapZone(Zone, Altitude, Speed, Heading, Leg) - return awacszone + return zone +end + + +--- Add an AWACS zone. +-- @param #CHIEF self +-- @param Core.Zone#ZONE Zone 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.PatrolZone The AWACS zone data. +function CHIEF:AddAwacsZone(Zone, Altitude, Speed, Heading, Leg) + + -- Hand over to commander. + local zone=self.commander:AddAwacsZone(Zone, Altitude, Speed, Heading, Leg) + + return zone +end + +--- Add a refuelling tanker zone. +-- @param #CHIEF self +-- @param Core.Zone#ZONE Zone 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. +-- @param #number RefuelSystem Refuelling system. +-- @return Ops.AirWing#AIRWING.TankerZone The tanker zone data. +function CHIEF:AddTankerZone(Zone, Altitude, Speed, Heading, Leg, RefuelSystem) + + -- Hand over to commander. + local zone=self.commander:AddTankerZone(Zone, Altitude, Speed, Heading, Leg, RefuelSystem) + + return zone end @@ -931,12 +965,14 @@ function CHIEF:onafterStatus(From, Event, To) -- Cancel this mission. contact.mission:Cancel() - - -- Remove a target from the queue. - self:RemoveTarget(contact.target) end + -- Remove a target from the queue. + if contact.target then + self:RemoveTarget(contact.target) + end + end --- @@ -1330,11 +1366,17 @@ function CHIEF:CheckTargetQueue() for _,_target in pairs(self.targetqueue) do local target=_target --Ops.Target#TARGET + local isAlive=target:IsAlive() + local isImportant=(target.importance==nil or target.importance<=vip) + -- Is this a threat? local isThreat=target.threatlevel0>=self.threatLevelMin and target.threatlevel0<=self.threatLevelMax + + -- Debug message. + self:T(self.lid..string.format("Target %s: Alive=%s, Threat=%s, Important=%s", target:GetName(), tostring(isAlive), tostring(isThreat), tostring(isImportant))) -- Check that target is alive and not already a mission has been assigned. - if target:IsAlive() and (target.importance==nil or target.importance<=vip) and isThreat and not target.mission then + if isAlive and isThreat and isImportant and not target.mission then -- Check if this target is "valid", i.e. fits with the current strategy. local valid=false diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 3e6a45508..611f56abd 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -81,7 +81,7 @@ COHORT.version="0.0.2" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: A lot! +-- TODO: Create FLOTILLA class. -- DONE: Make general so that PLATOON and SQUADRON can inherit this class. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 06f2df9ef..d3f478966 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -24,7 +24,9 @@ -- @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 #table capZones CAP zones. Each element is of type `#AIRWING.PatrolZone`. +-- @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. -- @extends Core.Fsm#FSM @@ -47,7 +49,9 @@ COMMANDER = { transportqueue = {}, rearmingZones = {}, refuellingZones = {}, + capZones = {}, awacsZones = {}, + tankerZones = {}, } --- COMMANDER class version. @@ -58,6 +62,8 @@ COMMANDER.version="0.1.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Add CAP zones. +-- TODO: Add tanker zones. -- 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. @@ -81,7 +87,7 @@ function COMMANDER:New(Coalition, Alias) -- Set coaliton. self.coalition=Coalition -- Alias name. - self.alias=Alias or string.format("Jon Doe") + self.alias=Alias or string.format("John Doe") -- Log ID. self.lid=string.format("COMMANDER %s [%s] | ", self.alias, UTILS.GetCoalitionName(self.coalition)) @@ -416,19 +422,44 @@ function COMMANDER:AddRefuellingZone(RefuellingZone) return rearmingzone end ---- Add an AWACS zone. +--- Add a CAP zone. -- @param #COMMANDER self --- @param Core.Zone#ZONE AwacsZone Zone. +-- @param Core.Zone#ZONE CapZone 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) +-- @return Ops.AirWing#AIRWING.PatrolZone The CAP zone data. +function COMMANDER:AddCapZone(Zone, Altitude, Speed, Heading, Leg) - local awacszone={} --Ops.AirWing#AIRWING.AwacsZone + local patrolzone={} --Ops.AirWing#AIRWING.PatrolZone - awacszone.zone=AwacsZone + patrolzone.zone=Zone + patrolzone.altitude=Altitude or 12000 + patrolzone.heading=Heading or 270 + patrolzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, patrolzone.altitude) + patrolzone.leg=Leg or 30 + patrolzone.mission=nil + patrolzone.marker=MARKER:New(patrolzone.zone:GetCoordinate(), "AWACS Zone"):ToCoalition(self:GetCoalition()) + + table.insert(self.capZones, patrolzone) + + return awacszone +end + +--- Add an AWACS zone. +-- @param #COMMANDER self +-- @param Core.Zone#ZONE Zone 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.PatrolZone The AWACS zone data. +function COMMANDER:AddAwacsZone(Zone, Altitude, Speed, Heading, Leg) + + local awacszone={} --Ops.AirWing#AIRWING.PatrolZone + + awacszone.zone=Zone awacszone.altitude=Altitude or 12000 awacszone.heading=Heading or 270 awacszone.speed=UTILS.KnotsToAltKIAS(Speed or 350, awacszone.altitude) @@ -441,6 +472,33 @@ function COMMANDER:AddAwacsZone(AwacsZone, Altitude, Speed, Heading, Leg) return awacszone end +--- Add a refuelling tanker zone. +-- @param #COMMANDER self +-- @param Core.Zone#ZONE Zone 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. +-- @param #number RefuelSystem Refuelling system. +-- @return Ops.AirWing#AIRWING.TankerZone The tanker zone data. +function COMMANDER:AddTankerZone(Zone, Altitude, Speed, Heading, Leg, RefuelSystem) + + local tankerzone={} --Ops.AirWing#AIRWING.TankerZone + + tankerzone.zone=Zone + tankerzone.altitude=Altitude or 12000 + tankerzone.heading=Heading or 270 + tankerzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, tankerzone.altitude) + tankerzone.leg=Leg or 30 + tankerzone.refuelsystem=RefuelSystem + tankerzone.mission=nil + tankerzone.marker=MARKER:New(tankerzone.zone:GetCoordinate(), "Tanker Zone"):ToCoalition(self:GetCoalition()) + + table.insert(self.tankerZones, tankerzone) + + return awacszone +end + --- Check if this mission is already in the queue. -- @param #COMMANDER self -- @param Ops.Auftrag#AUFTRAG Mission The mission. @@ -526,16 +584,39 @@ function COMMANDER:onafterStatus(From, Event, To) self:AddMission(supplyzone.mission) end end + + + -- Check CAP zones. + for _,_patrolzone in pairs(self.capZones) do + local patrolzone=_patrolzone --Ops.AirWing#AIRWING.PatrolZone + -- Check if mission is nil or over. + if (not patrolzone.mission) or patrolzone.mission:IsOver() then + local Coordinate=patrolzone.zone:GetCoordinate() + patrolzone.mission=AUFTRAG:NewCAP(patrolzone.zone, patrolzone.altitude, patrolzone.speed, Coordinate, patrolzone.heading, patrolzone.leg) + self:AddMission(patrolzone.mission) + end + end -- Check AWACS zones. for _,_awacszone in pairs(self.awacsZones) do - local awacszone=_awacszone --Ops.AirWing#AIRWING.AwacsZone + local awacszone=_awacszone --Ops.AirWing#AIRWING.Patrol -- 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 + + -- Check Tanker zones. + for _,_tankerzone in pairs(self.tankerZones) do + local tankerzone=_tankerzone --Ops.AirWing#AIRWING.TankerZone + -- Check if mission is nil or over. + if (not tankerzone.mission) or tankerzone.mission:IsOver() then + local Coordinate=tankerzone.zone:GetCoordinate() + tankerzone.mission=AUFTRAG:NewTANKER(Coordinate, tankerzone.altitude, tankerzone.speed, tankerzone.heading, tankerzone.leg, tankerzone.refuelsystem) + self:AddMission(tankerzone.mission) + end end --- diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index 97917bb7e..3465a12c5 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -2161,6 +2161,7 @@ function FLIGHTCONTROL:RemoveParkingGuard(spot, delay) else if spot.ParkingGuard then + self:I(self.lid..string.format("Removing parking guard at spot %d", spot.TerminalID)) spot.ParkingGuard:Destroy() spot.ParkingGuard=nil end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 46d170ffa..1c5e657cd 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2487,8 +2487,9 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Warning, looks like this can make DCS CRASH! Had this after calling RTB once passed the final waypoint. --self:ClearTasks() - -- Just route the group. Respawn might happen when going from holding to final. - self:Route(wp) + -- Just route the group. Respawn might happen when going from holding to final. + -- NOTE: I have delayed that here because of RTB calling _LandAtAirbase which resets current task immediately. So the stop flag change to 1 will not trigger TaskDone() and a current mission is not done either + self:Route(wp, 0.1) end diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 746f5344e..0d639291c 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -49,8 +49,8 @@ LEGION.version="0.1.0" -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Create FLOTILLA class. --- TODO: OPS transport. +-- TODO: Create FLEED class. +-- DONE: OPS transport. -- DONE: Make general so it can be inherited by AIRWING and BRIGADE classes. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -440,7 +440,7 @@ function LEGION:onafterStart(From, Event, To) self:GetParent(self, LEGION).onafterStart(self, From, Event, To) -- Info. - self:I(self.lid..string.format("Starting LEGION v%s", LEGION.version)) + self:T3(self.lid..string.format("Starting LEGION v%s", LEGION.version)) end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 4473b92f9..459bf826d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6067,6 +6067,10 @@ function OPSGROUP:onafterStop(From, Event, To) if self.Scheduler then self.Scheduler:Clear() end + + if self.flightcontrol then + + end if self:IsAlive() and not (self:IsDead() or self:IsStopped()) then local life, life0=self:GetLifePoints() diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 32b9eafd1..33cce7664 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -136,6 +136,7 @@ TARGET.version="0.5.2" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Had cases where target life was 0 but target was not dead. Need to figure out why! -- TODO: Add pseudo functions. -- DONE: Initial object can be nil. @@ -383,6 +384,11 @@ function TARGET:onafterStatus(From, Event, To) damaged=true end + if life==0 then + self:I(self.lid..string.format("FF life is zero but no object dead event fired ==> object dead now for traget object %s!", tostring(target.Name))) + self:ObjectDead(target) + end + end -- Target was damaged. @@ -1056,7 +1062,8 @@ end -- @param #TARGET self -- @return #string Name of the target usually the first object. function TARGET:GetName() - return self.name or "Unknown" + local name=self.name or "Unknown" + return name end --- Get 2D vector. diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 19664867a..5e9ca5105 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1053,7 +1053,7 @@ function UNIT:GetThreatLevel() elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and not Attributes["ATGM"] then ThreatLevel = 3 elseif Attributes["Old Tanks"] or Attributes["APC"] or Attributes["Artillery"] then ThreatLevel = 2 - elseif Attributes["Infantry"] then ThreatLevel = 1 + elseif Attributes["Infantry"] or Attributes["EWR"] then ThreatLevel = 1 end ThreatText = ThreatLevels[ThreatLevel+1] From 1790d19809359fbc6e25c448ec3ddb6a1dd55a3d Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 3 Oct 2021 23:09:45 +0200 Subject: [PATCH 121/141] FC --- .../Moose/Functional/Warehouse.lua | 7 ++++-- Moose Development/Moose/Ops/FlightControl.lua | 23 ++++++++++--------- Moose Development/Moose/Ops/OpsGroup.lua | 15 ++++++++---- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 600d1ebdf..f38c71d8e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -87,6 +87,7 @@ -- @field #number respawndelay Delay before respawn in seconds. -- @field #number runwaydestroyed Time stamp timer.getAbsTime() when the runway was destroyed. -- @field #number runwayrepairtime Time in seconds until runway will be repaired after it was destroyed. Default is 3600 sec (one hour). +-- @field Ops.FlightControl#FLIGHTCONTROL flightcontrol Flight control of this warehouse. -- @extends Core.Fsm#FSM --- Have your assets at the right place at the right time - or not! @@ -6217,9 +6218,11 @@ function WAREHOUSE:_RouteAir(aircraft) self:T2(self.lid..string.format("RouteAir aircraft group %s alive=%s", aircraft:GetName(), tostring(aircraft:IsAlive()))) -- Give start command to activate uncontrolled aircraft within the next 60 seconds. - local starttime=math.random(60) + if not self.flightcontrol then + local starttime=math.random(60) - aircraft:StartUncontrolled(starttime) + aircraft:StartUncontrolled(starttime) + end -- Debug info. self:T2(self.lid..string.format("RouteAir aircraft group %s alive=%s (after start command)", aircraft:GetName(), tostring(aircraft:IsAlive()))) diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index 3465a12c5..af0c700f6 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -1,4 +1,4 @@ ---- **OPS** - Manage recovery of aircraft at airdromes. +--- **OPS** - Manage launching and recovery of aircraft at airdromes. -- -- -- @@ -43,8 +43,6 @@ -- -- === -- --- ![Banner Image](..\Presentations\FLIGHTCONTROL\FlightControl_Main.jpg) --- -- # The FLIGHTCONTROL Concept -- -- @@ -1536,7 +1534,7 @@ end function FLIGHTCONTROL:_PlayerRequestInfo(groupname) -- Get flight group. - local flight=_DATABASE:GetFlightGroup(groupname) + local flight=_DATABASE:GetOpsGroup(groupname) if flight then @@ -1596,7 +1594,7 @@ end function FLIGHTCONTROL:_PlayerRequestInfoQueues(groupname) -- Get flight group. - local flight=_DATABASE:GetFlightGroup(groupname) + local flight=_DATABASE:GetOpsGroup(groupname) if flight then @@ -1628,7 +1626,7 @@ end function FLIGHTCONTROL:_PlayerInbound(groupname) -- Get flight group. - local flight=_DATABASE:GetFlightGroup(groupname) + local flight=_DATABASE:GetOpsGroup(groupname) if flight then @@ -1681,7 +1679,7 @@ end function FLIGHTCONTROL:_PlayerHolding(groupname) -- Get flight group. - local flight=_DATABASE:GetFlightGroup(groupname) + local flight=_DATABASE:GetOpsGroup(groupname) if flight then @@ -1719,11 +1717,11 @@ end function FLIGHTCONTROL:_PlayerMyStatus(groupname) -- Get flight group. - local flight=_DATABASE:GetFlightGroup(groupname) + local flight=_DATABASE:GetOpsGroup(groupname) if flight then - local fc=flight.flightcontrol + local fc=flight.flightcontrol --Ops.FlightControl#FLIGHTCONTROL local text=string.format("My Status:") text=text..string.format("\nFlight status: %s", tostring(flight:GetState())) @@ -1757,12 +1755,15 @@ function FLIGHTCONTROL:_PlayerRequestTaxi(groupname) for _,_element in pairs(flight.elements) do local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element + env.info("FF 100") if element.parking then local spot=self:GetParkingSpotByID(element.parking.TerminalID) if element.ai then + env.info("FF 200") self:RemoveParkingGuard(spot, 30) else self:RemoveParkingGuard(spot, 3) + env.info("FF 300") end end --flight:ElementTaxiing(element) @@ -1785,7 +1786,7 @@ function FLIGHTCONTROL:_PlayerRequestTakeoff(groupname) MESSAGE:New("Request takeoff", 5):ToAll() - local flight=_DATABASE:GetFlightGroup(groupname) + local flight=_DATABASE:GetOpsGroup(groupname) if flight then @@ -1819,7 +1820,7 @@ function FLIGHTCONTROL:_PlayerAbortTakeoff(groupname) MESSAGE:New("Abort takeoff", 5):ToAll() - local flight=_DATABASE:GetFlightGroup(groupname) + local flight=_DATABASE:GetOpsGroup(groupname) if flight then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 459bf826d..e74bd3147 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6068,8 +6068,15 @@ function OPSGROUP:onafterStop(From, Event, To) self.Scheduler:Clear() end + -- Flightcontrol. if self.flightcontrol then - + self.flightcontrol:_RemoveFlight(self) + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if element.parking then + self.flightcontrol:SetParkingFree(element.parking) + end + end end if self:IsAlive() and not (self:IsDead() or self:IsStopped()) then @@ -10804,8 +10811,8 @@ function OPSGROUP:_AddElementByName(unitname) if unit then -- Get unit template. - --local unittemplate=unit:GetTemplate() - local unittemplate=_DATABASE:GetUnitTemplateFromUnitName(unitname) + local unittemplate=unit:GetTemplate() + --local unittemplate=_DATABASE:GetUnitTemplateFromUnitName(unitname) -- Element table. local element={} --#OPSGROUP.Element @@ -10897,7 +10904,7 @@ function OPSGROUP:_AddElementByName(unitname) local text=string.format("Adding element %s: status=%s, skill=%s, life=%.1f/%.1f category=%s (%d), type=%s, size=%.1f (L=%.1f H=%.1f W=%.1f), weight=%.1f/%.1f (cargo=%.1f/%.1f)", element.name, element.status, element.skill, element.life, element.life0, element.categoryname, element.category, element.typename, element.size, element.length, element.height, element.width, element.weight, element.weightMaxTotal, element.weightCargo, element.weightMaxCargo) - self:T(self.lid..text) + self:I(self.lid..text) -- Add element to table. if not self:_IsElement(unitname) then From 5dbf743052448c6fac36dd6211e1d1cba2a9ab37 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 5 Oct 2021 23:59:24 +0200 Subject: [PATCH 122/141] OPS - Fixed a couple of minor bugs. - FC added _PlayerAbortInbound function --- Moose Development/Moose/Ops/Auftrag.lua | 3 ++ Moose Development/Moose/Ops/Chief.lua | 12 ++++- Moose Development/Moose/Ops/Commander.lua | 6 +-- Moose Development/Moose/Ops/FlightControl.lua | 51 +++++++++++++++++-- Moose Development/Moose/Ops/FlightGroup.lua | 25 ++++++--- Moose Development/Moose/Ops/Intelligence.lua | 4 ++ Moose Development/Moose/Ops/Legion.lua | 13 +++-- Moose Development/Moose/Ops/OpsGroup.lua | 9 ++-- 8 files changed, 98 insertions(+), 25 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index f8775e972..faed0e4f9 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2076,6 +2076,9 @@ function AUFTRAG:SetRequiredEscorts(NescortMin, NescortMax) if self.NescortMax=8 then + NassetsMax=3 + elseif target.threatlevel0>=5 then + NassetsMax=2 + else + NassetsMax=1 + end for _,_mp in pairs(MissionPerformances) do local mp=_mp --#CHIEF.MissionPerformance diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index d3f478966..d3834fda3 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -668,7 +668,7 @@ function COMMANDER:onafterStatus(From, Event, To) state=asset.flightgroup:GetState() local mission=legion:GetAssetCurrentMission(asset) if mission then - state=state..string.format("Mission %s [%s]", mission:GetName(), mission:GetType()) + state=state..string.format(", Mission \"%s\" [%s]", mission:GetName(), mission:GetType()) end else if asset.spawned then @@ -1023,7 +1023,7 @@ end function COMMANDER:RecruitAssetsForMission(Mission) -- Debug info. - env.info(string.format("FF recruiting assets for mission %s [%s]", Mission:GetName(), Mission:GetType())) + self:T2(self.lid..string.format("Recruiting assets for mission \"%s\" [%s]", Mission:GetName(), Mission:GetType())) -- Cohorts. local Cohorts={} @@ -1102,7 +1102,7 @@ function COMMANDER:RecruitAssetsForEscort(Mission, Assets) -- Call LEGION function but provide COMMANDER as self. - local assigned=LEGION.AssignAssetsForEscort(self, Cohorts, Assets, Mission.NescortMin, Mission.NescortMin) + local assigned=LEGION.AssignAssetsForEscort(self, Cohorts, Assets, Mission.NescortMin, Mission.NescortMax) return assigned end diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index af0c700f6..54397657d 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -90,6 +90,7 @@ FLIGHTCONTROL = { --- Parking spot data. -- @type FLIGHTCONTROL.FlightStatus +-- @field #string UNKNOWN Flight state is unknown. -- @field #string INBOUND Flight is inbound. -- @field #string HOLDING Flight is holding. -- @field #string LANDING Flight is landing. @@ -99,6 +100,7 @@ FLIGHTCONTROL = { -- @field #string READYTO Flight is ready for takeoff. -- @field #string TAKEOFF Flight is taking off. FLIGHTCONTROL.FlightStatus={ + UNKNOWN="Unknown", INBOUND="Inbound", HOLDING="Holding", LANDING="Landing", @@ -923,8 +925,11 @@ function FLIGHTCONTROL:_RemoveFlightFromQueue(queue, flight, queuename) self:I(self.lid..string.format("Removing flight group %s from %s queue.", flight.groupname, queuename)) table.remove(queue, i) - if not flight.isAI then - flight:_UpdateMenu() + if not flight.isAI then + if flight.flightcontrol and flight.flightcontrol.airbasename==self.airbasename then + flight.flightcontrol=nil + end + flight:_UpdateMenu(0.1) end return true, i @@ -1413,7 +1418,7 @@ function FLIGHTCONTROL:_CreatePlayerMenu(flight, atcmenu) local airbaseName=airbasename local airbaseName2=airbaseName if gotcontrol then - airbaseName2=airbaseName2.." *" + --airbaseName2=airbaseName2.." *" end local Tag=airbasename local Tnow=timer.getTime() @@ -1466,6 +1471,10 @@ function FLIGHTCONTROL:_CreatePlayerMenu(flight, atcmenu) MENU_GROUP_COMMAND_DELAYED:New(group, "Holding", rootmenu, self._PlayerHolding, self, groupname):SetTime(Tnow):SetTag(Tag) end + + if flight:IsInbound() or flight:IsHolding() or flight:IsLanding() or flight:IsLanded() then + MENU_GROUP_COMMAND_DELAYED:New(group, "Abort Inbound", rootmenu, self._PlayerAbortInbound, self, groupname):SetTime(Tnow):SetTag(Tag) + end if flight:IsInbound() or flight:IsHolding() or flight:IsLanding() or flight:IsLanded() then MENU_GROUP_COMMAND_DELAYED:New(group, "Request Parking", rootmenu, self._PlayerRequestParking, self, groupname):SetTime(Tnow):SetTag(Tag) @@ -1692,7 +1701,7 @@ function FLIGHTCONTROL:_PlayerHolding(groupname) local text=string.format("Roger, you are added to the holding queue!") MESSAGE:New(text, 5):ToGroup(flight.group) - -- Call holding event. + -- Call holding event. Updates the menu. flight:Holding() else @@ -1841,6 +1850,40 @@ function FLIGHTCONTROL:_PlayerAbortTakeoff(groupname) end +--- Player wants to abort inbound. +-- @param #FLIGHTCONTROL self +-- @param #string groupname Name of the flight group. +function FLIGHTCONTROL:_PlayerAbortInbound(groupname) + + MESSAGE:New("Abort inbound", 5):ToAll() + + local flight=_DATABASE:GetOpsGroup(groupname) + + if flight then + + local flightstatus=self:GetFlightStatus(flight) + if flightstatus==FLIGHTCONTROL.FlightStatus.INBOUND or flightstatus==FLIGHTCONTROL.FlightStatus.HOLDING or flightstatus==FLIGHTCONTROL.FlightStatus.LANDING then + + MESSAGE:New("Afirm, You are removed from all queues queue", 5):ToAll() + + --TODO: what now? taxi inbound? or just another later attempt to takeoff. + self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.UNKNOWN) + + -- Remove flight. + self:_RemoveFlight(flight) + + -- Trigger cruise event. + flight:Cruise() + + + else + MESSAGE:New("Negative, You are NOT in the state INBOUND, HOLDING or LANDING!", 5):ToAll() + end + + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Flight and Element Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 1c5e657cd..54e6e10d4 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1544,6 +1544,8 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) self:__UpdateRoute(-0.5) else + + env.info("FF Spawned update menu") -- F10 other menu. self:_UpdateMenu() @@ -1695,7 +1697,7 @@ function FLIGHTGROUP:onafterCruise(From, Event, To) -- CLIENT --- - self:_UpdateMenu() + self:_UpdateMenu(0.1) end @@ -3218,7 +3220,7 @@ end -- @return #boolean Hot start? function FLIGHTGROUP:IsTakeoffHot() - local wp=self:GetWaypoint(1) + local wp=self.waypoints0 and self.waypoints0[1] or nil --self:GetWaypoint(1) if wp then @@ -3238,7 +3240,7 @@ end -- @return #boolean Cold start, i.e. engines off when spawned? function FLIGHTGROUP:IsTakeoffCold() - local wp=self:GetWaypoint(1) + local wp=self.waypoints0 and self.waypoints0[1] or nil --self:GetWaypoint(1) if wp then @@ -3258,7 +3260,7 @@ end -- @return #boolean Runway start? function FLIGHTGROUP:IsTakeoffRunway() - local wp=self:GetWaypoint(1) + local wp=self.waypoints0 and self.waypoints0[1] or nil --self:GetWaypoint(1) if wp then @@ -3278,7 +3280,7 @@ end -- @return #boolean Air start? function FLIGHTGROUP:IsTakeoffAir() - local wp=self:GetWaypoint(1) + local wp=self.waypoints0 and self.waypoints0[1] or nil --self:GetWaypoint(1) if wp then @@ -4009,19 +4011,28 @@ function FLIGHTGROUP:_UpdateMenu(delay) return a.dist0 or Mission.NescortMax>0) then -- Debug info. - self:I(self.lid..string.format("Reqested escort for mission %s [%s]. Required assets=%d-%d", Mission:GetName(), Mission:GetType(), Mission.NescortMin,Mission.NescortMax)) + self:I(self.lid..string.format("Requested escort for mission %s [%s]. Required assets=%d-%d", Mission:GetName(), Mission:GetType(), Mission.NescortMin,Mission.NescortMax)) -- Get special escort legions and/or cohorts. local Cohorts={} @@ -1855,7 +1855,7 @@ function LEGION:RecruitAssetsForEscort(Mission, Assets) end -- Call LEGION function but provide COMMANDER as self. - local assigned=LEGION.AssignAssetsForEscort(self, Cohorts, Assets, Mission.NescortMin, Mission.NescortMin) + local assigned=LEGION.AssignAssetsForEscort(self, Cohorts, Assets, Mission.NescortMin, Mission.NescortMax) return assigned end @@ -2083,7 +2083,7 @@ function LEGION:AssignAssetsForEscort(Cohorts, Assets, NescortMin, NescortMax) if NescortMin and NescortMax and (NescortMin>0 or NescortMax>0) then -- Debug info. - self:I(self.lid..string.format("Reqested escort for %d assets from %d cohorts. Required escort assets=%d-%d", #Assets, #Cohorts, NescortMin, NescortMax)) + self:T(self.lid..string.format("Requested escort for %d assets from %d cohorts. Required escort assets=%d-%d", #Assets, #Cohorts, NescortMin, NescortMax)) -- Escorts for each asset. local Escorts={} @@ -2153,14 +2153,14 @@ function LEGION:AssignAssetsForEscort(Cohorts, Assets, NescortMin, NescortMax) end -- Debug info. - self:I(self.lid..string.format("Recruited %d escort assets", N)) + self:T(self.lid..string.format("Recruited %d escort assets", N)) -- Yup! return true else -- Debug info. - self:I(self.lid..string.format("Could not get at least one escort!")) + self:T(self.lid..string.format("Could not get at least one escort!")) -- Could not get at least one escort. Unrecruit all recruited ones. for groupname,value in pairs(Escorts) do @@ -2174,6 +2174,7 @@ function LEGION:AssignAssetsForEscort(Cohorts, Assets, NescortMin, NescortMax) else -- No escort required. + self:T(self.lid..string.format("No escort required! NescortMin=%s, NescortMax=%s", tostring(NescortMin), tostring(NescortMax))) return true end @@ -2392,6 +2393,7 @@ function LEGION._OptimizeAssetSelection(assets, MissionType, TargetVec2, Include table.sort(assets, optimize) -- Remove distance parameter. + --[[ local text=string.format("Optimized %d assets for %s mission/transport (payload=%s):", #assets, MissionType, tostring(IncludePayload)) for i,Asset in pairs(assets) do local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem @@ -2399,6 +2401,7 @@ function LEGION._OptimizeAssetSelection(assets, MissionType, TargetVec2, Include asset.score=nil end env.info(text) + ]] end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index e74bd3147..7ab640d1b 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3469,7 +3469,8 @@ function OPSGROUP:onbeforeTaskExecute(From, Event, To, Task) -- Group is already waiting else -- Wait indefinately. - self:Wait() + local alt=Mission.missionAltitude and UTILS.MetersToFeet(Mission.missionAltitude) or nil + self:Wait(nil, alt) end -- Time to for the next try. Best guess is when push time is reached or 20 sec when push conditions are not true yet. @@ -8762,7 +8763,7 @@ end function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) -- Template waypoints. - self.waypoints0=self.group:GetTemplateRoutePoints() + self.waypoints0=UTILS.DeepCopy(_DATABASE:GetGroupTemplate(self.groupname).route.points) --self.group:GetTemplateRoutePoints() -- Waypoints empty! self.waypoints={} @@ -10904,7 +10905,7 @@ function OPSGROUP:_AddElementByName(unitname) local text=string.format("Adding element %s: status=%s, skill=%s, life=%.1f/%.1f category=%s (%d), type=%s, size=%.1f (L=%.1f H=%.1f W=%.1f), weight=%.1f/%.1f (cargo=%.1f/%.1f)", element.name, element.status, element.skill, element.life, element.life0, element.categoryname, element.category, element.typename, element.size, element.length, element.height, element.width, element.weight, element.weightMaxTotal, element.weightCargo, element.weightMaxCargo) - self:I(self.lid..text) + self:T(self.lid..text) -- Add element to table. if not self:_IsElement(unitname) then @@ -10930,7 +10931,7 @@ end function OPSGROUP:_SetTemplate(Template) -- Set the template. - self.template=Template or self.group:GetTemplate() + self.template=Template or UTILS.DeepCopy(_DATABASE:GetGroupTemplate(self.groupname)) --self.group:GetTemplate() -- Debug info. self:T3(self.lid.."Setting group template") From 653dfe82fa1976990e07ea192bb64bd0e1750657 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 6 Oct 2021 13:25:57 +0200 Subject: [PATCH 123/141] OPS CHIEF - Improved target queue for assigning new missions --- Moose Development/Moose/Ops/Auftrag.lua | 8 +-- Moose Development/Moose/Ops/Chief.lua | 78 +++++++++++++++++++++---- Moose Development/Moose/Ops/Target.lua | 2 + 3 files changed, 74 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index faed0e4f9..eb91f0b7a 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1204,7 +1204,7 @@ end --- **[AIR]** Create a BAI mission. -- @param #AUFTRAG self -- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be a GROUP, UNIT or STATIC object. --- @param #number Altitude Engage altitude in feet. Default 2000 ft. +-- @param #number Altitude Engage altitude in feet. Default 5000 ft. -- @return #AUFTRAG self function AUFTRAG:NewBAI(Target, Altitude) @@ -1215,7 +1215,7 @@ function AUFTRAG:NewBAI(Target, Altitude) -- DCS Task options: mission.engageWeaponType=ENUMS.WeaponFlag.AnyAG mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL - mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) + mission.engageAltitude=UTILS.FeetToMeters(Altitude or 5000) -- Mission options: mission.missionTask=ENUMS.MissionTask.GROUNDATTACK @@ -1234,7 +1234,7 @@ end --- **[AIR]** Create a SEAD mission. -- @param #AUFTRAG self -- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be a GROUP or UNIT object. --- @param #number Altitude Engage altitude in feet. Default 2000 ft. +-- @param #number Altitude Engage altitude in feet. Default 8000 ft. -- @return #AUFTRAG self function AUFTRAG:NewSEAD(Target, Altitude) @@ -1245,7 +1245,7 @@ function AUFTRAG:NewSEAD(Target, Altitude) -- DCS Task options: mission.engageWeaponType=ENUMS.WeaponFlag.AnyAG --ENUMS.WeaponFlag.Cannons mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL - mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) + mission.engageAltitude=UTILS.FeetToMeters(Altitude or 8000) -- Mission options: mission.missionTask=ENUMS.MissionTask.SEAD diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index a51de9d9a..4531bbdd8 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -21,6 +21,8 @@ -- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory. -- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected. -- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged. +-- @field #number threatLevelMin Lowest threat level of targets to attack. +-- @field #number threatLevelMax Highest threat level of targets to attack. -- @field #string Defcon Defence condition. -- @field #string strategy Strategy of the CHIEF. -- @field Ops.Commander#COMMANDER commander Commander of assigned legions. @@ -115,11 +117,11 @@ CHIEF.version="0.0.1" --- Create a new CHIEF object and start the FSM. -- @param #CHIEF self --- @param Core.Set#SET_GROUP AgentSet Set of agents (groups) providing intel. Default is an empty set. -- @param #number Coalition Coalition side, e.g. `coaliton.side.BLUE`. Can also be passed as a string "red", "blue" or "neutral". +-- @param Core.Set#SET_GROUP AgentSet Set of agents (groups) providing intel. Default is an empty set. -- @param #string Alias An *optional* alias how this object is called in the logs etc. -- @return #CHIEF self -function CHIEF:New(AgentSet, Coalition, Alias) +function CHIEF:New(Coalition, AgentSet, Alias) -- Set alias. Alias=Alias or "CHIEF" @@ -1076,9 +1078,10 @@ function CHIEF:onafterStatus(From, Event, To) local text="Contacts:" for i,_contact in pairs(self.Contacts) do local contact=_contact --Ops.Intelligence#INTEL.Contact + local mtext="N/A" if contact.mission then - mtext=string.format("Mission %s (%s) %s", contact.mission.name, contact.mission.type, contact.mission.status:upper()) + mtext=string.format("\"%s\" [%s] %s", contact.mission:GetName(), contact.mission:GetType(), contact.mission.status:upper()) end text=text..string.format("\n[%d] %s Type=%s (%s): Threat=%d Mission=%s", i, contact.groupname, contact.categoryname, contact.typename, contact.threatlevel, mtext) end @@ -1093,10 +1096,13 @@ function CHIEF:onafterStatus(From, Event, To) local text="Targets:" for i,_target in pairs(self.targetqueue) do local target=_target --Ops.Target#TARGET - - text=text..string.format("\n[%d] %s: Category=%s, prio=%d, importance=%d, alive=%s [%.1f/%.1f]", - i, target:GetName(), target.category, target.prio, target.importance or -1, tostring(target:IsAlive()), target:GetLife(), target:GetLife0()) - + + local mtext="N/A" + if target.mission then + mtext=string.format("\"%s\" [%s] %s", target.mission:GetName(), target.mission:GetType(), target.mission.status:upper()) + end + text=text..string.format("\n[%d] %s: Category=%s, prio=%d, importance=%d, alive=%s [%.1f/%.1f], Mission=%s", + i, target:GetName(), target.category, target.prio, target.importance or -1, tostring(target:IsAlive()), target:GetLife(), target:GetLife0(), mtext) end self:I(self.lid..text) end @@ -1357,7 +1363,7 @@ function CHIEF:CheckTargetQueue() local vip=math.huge for _,_target in pairs(self.targetqueue) do local target=_target --Ops.Target#TARGET - if target.importance and target.importance=self.threatLevelMin and target.threatlevel0<=self.threatLevelMax + -- Airbases, Zones and Coordinates have threat level 0. We consider them threads independent of min/max threat level set. + if target.category==TARGET.Category.AIRBASE or target.category==TARGET.Category.ZONE or target.Category==TARGET.Category.COORDINATE then + isThreat=true + end + -- Debug message. - self:T(self.lid..string.format("Target %s: Alive=%s, Threat=%s, Important=%s", target:GetName(), tostring(isAlive), tostring(isThreat), tostring(isImportant))) + local text=string.format("Target %s: Alive=%s, Threat=%s, Important=%s", target:GetName(), tostring(isAlive), tostring(isThreat), tostring(isImportant)) + + -- Check if mission is done. + if target.mission then + text=text..string.format(", Mission \"%s\" (%s) [%s]", target.mission:GetName(), target.mission:GetState(), target.mission:GetType()) + if target.mission:IsOver() then + text=text..string.format(" - DONE ==> removing mission") + target.mission=nil + end + else + text=text..string.format(", NO mission yet") + end + self:I(self.lid..text) -- Check that target is alive and not already a mission has been assigned. if isAlive and isThreat and isImportant and not target.mission then @@ -1469,6 +1492,8 @@ function CHIEF:CheckTargetQueue() if recruited then + self:I(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) @@ -1483,7 +1508,8 @@ function CHIEF:CheckTargetQueue() -- We got what we wanted ==> leave loop. break end - + else + self:I(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 @@ -1793,18 +1819,31 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target) table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.SEAD, 100)) + elseif attribute==GROUP.Attribute.GROUND_EWR then + + -- EWR + + --table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.SEAD, 100)) + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100)) + elseif attribute==GROUP.Attribute.GROUND_AAA then + -- AAA + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100)) elseif attribute==GROUP.Attribute.GROUND_ARTILLERY then + -- ARTY + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100)) table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING, 70)) table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.ARTY, 30)) elseif attribute==GROUP.Attribute.GROUND_INFANTRY then + -- Infantry + table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BAI, 100)) else @@ -1827,16 +1866,35 @@ function CHIEF:_GetMissionPerformanceFromTarget(Target) end elseif airbase then + + --- + -- AIRBASE + --- + + -- Bomb runway. table.insert(missionperf, self:_CreateMissionPerformance(AUFTRAG.Type.BOMBRUNWAY, 100)) + elseif scenery then + + --- + -- SCENERY + --- + 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 + + --- + -- COORDINATE + --- + 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 diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 33cce7664..600f2022e 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -33,6 +33,8 @@ -- @field #table casualties Table of dead element names. -- @field #number prio Priority. -- @field #number importance Importance. +-- @field Ops.Auftrag#AUFTRAG mission Mission attached to this target. +-- @field Ops.Intelligence#INTEL.Contact contact Contact attached to this target. -- @field #boolean isDestroyed If true, target objects were destroyed. -- @extends Core.Fsm#FSM From f6dce02203b53ab38a765ac6bdba837b108aa40b Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 6 Oct 2021 22:03:02 +0200 Subject: [PATCH 124/141] INTEL - Introduced pcall to getName() of detected objects. Found a problem with an object of ID 5,000,031 --- Moose Development/Moose/Ops/Chief.lua | 8 +-- Moose Development/Moose/Ops/Cohort.lua | 2 +- Moose Development/Moose/Ops/FlightGroup.lua | 56 ++++++-------------- Moose Development/Moose/Ops/Intelligence.lua | 26 ++++++--- Moose Development/Moose/Ops/OpsGroup.lua | 5 +- 5 files changed, 40 insertions(+), 57 deletions(-) diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 4531bbdd8..20c82e8ad 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -1396,7 +1396,7 @@ function CHIEF:CheckTargetQueue() else text=text..string.format(", NO mission yet") end - self:I(self.lid..text) + self:T2(self.lid..text) -- Check that target is alive and not already a mission has been assigned. if isAlive and isThreat and isImportant and not target.mission then @@ -1485,14 +1485,14 @@ function CHIEF:CheckTargetQueue() local mp=_mp --#CHIEF.MissionPerformance -- Debug info. - self:I(self.lid..string.format("Recruiting assets for mission type %s [performance=%d] of target %s", mp.MissionType, mp.Performance, target:GetName())) + 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:I(self.lid..string.format("Recruited %d assets for mission type %s [performance=%d] of target %s", #assets, mp.MissionType, mp.Performance, target:GetName())) + 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) @@ -1509,7 +1509,7 @@ function CHIEF:CheckTargetQueue() break end else - self:I(self.lid..string.format("Could NOT recruit assets for mission type %s [performance=%d] of target %s", mp.MissionType, mp.Performance, target:GetName())) + 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 diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 611f56abd..f23704450 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -132,7 +132,7 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) -- Mission range depends on if self.category==Group.Category.AIRPLANE then - self:SetMissionRange(150) + self:SetMissionRange(200) elseif self.category==Group.Category.HELICOPTER then self:SetMissionRange(150) elseif self.category==Group.Category.GROUND then diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 54e6e10d4..bfd5b0657 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2172,7 +2172,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) elseif destbase then if self.currbase and self.currbase.AirbaseName==destbase.AirbaseName and self:IsParking() then self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports AND parking at destination airbase ==> Arrived!") - self:__Arrived(0.1) + self:Arrived() else self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") self:__RTB(-0.1, destbase) @@ -2818,6 +2818,7 @@ end -- @param #string To To state. function FLIGHTGROUP:onafterFuelLow(From, Event, To) + -- Current min fuel. local fuel=self:GetFuelMin() or 0 -- Debug message. @@ -2829,58 +2830,31 @@ function FLIGHTGROUP:onafterFuelLow(From, Event, To) -- Back to destination or home. local airbase=self.destbase or self.homebase - - local airwing=self:GetAirWing() - if airwing then + if self.fuellowrefuel and self.refueltype then - -- Get closest tanker from airwing that can refuel this flight. - local tanker=airwing:GetTankerForFlight(self) + -- Find nearest tanker within 50 NM. + local tanker=self:FindNearestTanker(50) - if tanker and self.fuellowrefuel then + if tanker then -- Debug message. - self:I(self.lid..string.format("Send to refuel at tanker %s", tanker.flightgroup:GetName())) + self:I(self.lid..string.format("Send to refuel at tanker %s", tanker:GetName())) -- Get a coordinate towards the tanker. - local coordinate=self:GetCoordinate():GetIntermediateCoordinate(tanker.flightgroup:GetCoordinate(), 0.75) + local coordinate=self:GetCoordinate():GetIntermediateCoordinate(tanker:GetCoordinate(), 0.75) - -- Send flight to tanker with refueling task. + -- Trigger refuel even. self:Refuel(coordinate) - else - - if airbase and self.fuellowrtb then - self:RTB(airbase) - --TODO: RTZ - end - - end - - else - - if self.fuellowrefuel and self.refueltype then - - local tanker=self:FindNearestTanker(50) - - if tanker then - - self:I(self.lid..string.format("Send to refuel at tanker %s", tanker:GetName())) - - -- Get a coordinate towards the tanker. - local coordinate=self:GetCoordinate():GetIntermediateCoordinate(tanker:GetCoordinate(), 0.75) - - self:Refuel(coordinate) - - return - end - end - - if airbase and self.fuellowrtb then - self:RTB(airbase) - --TODO: RTZ + return end + end + -- Send back to airbase. + if airbase and self.fuellowrtb then + self:RTB(airbase) + --TODO: RTZ end end diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index de5159841..0dbf99004 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -804,17 +804,29 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua for DetectionObjectID, Detection in pairs(detectedtargets or {}) do local DetectedObject=Detection.object -- DCS#Object + -- NOTE: Got an object that exists but when trying UNIT:Find() the DCS getName() function failed. ID of the object was 5,000,031 if DetectedObject and DetectedObject:isExist() and DetectedObject.id_<50000000 then - local unit=UNIT:Find(DetectedObject) - - if unit and unit:IsAlive() then + -- Protected call to get the name of the object. + local status,name = pcall( + function() + local name=DetectedObject:getName() + return name + end) - local unitname=unit:GetName() + if status then + + local unit=UNIT:FindByName(name) + + if unit and unit:IsAlive() then + DetectedUnits[name]=unit + RecceDetecting[name]=reccename + self:T(string.format("Unit %s detect by %s", name, reccename)) + end - DetectedUnits[unitname]=unit - RecceDetecting[unitname]=reccename - self:T(string.format("Unit %s detect by %s", unitname, reccename)) + else + -- Warning! + self:T(self.lid..string.format("WARNING: Could not get name of detected object ID=%s! Detected by %s", DetectedObject.id_, reccename)) end end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 7ab640d1b..284c4d3bd 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6008,10 +6008,7 @@ function OPSGROUP:onafterDead(From, Event, To) self.cohort:DelGroup(self.groupname) end else - if self.airwing then - -- Not all assets were destroyed (despawn) ==> Add asset back to airwing. - --self.airwing:AddAsset(self.group, 1) - end + -- Not all assets were destroyed (despawn) ==> Add asset back to legion? end -- Stop in a sec. From 0f4d46695307929851e656f8f5debc36ecd36c3a Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 11 Oct 2021 21:28:35 +0200 Subject: [PATCH 125/141] OPS - Changed mission, legion in MissionAssign functions (LEGION, COMMANDER, CHIEF) - OPSTRANSPORT: improved TZC selection - ARMYGROUP needs to --- Moose Development/Moose/Ops/ArmyGroup.lua | 240 ++++++++++++++++++- Moose Development/Moose/Ops/Auftrag.lua | 4 +- Moose Development/Moose/Ops/Chief.lua | 31 +-- Moose Development/Moose/Ops/Commander.lua | 198 ++++++++++----- Moose Development/Moose/Ops/Legion.lua | 87 +++---- Moose Development/Moose/Ops/NavyGroup.lua | 30 ++- Moose Development/Moose/Ops/OpsGroup.lua | 35 ++- Moose Development/Moose/Ops/OpsTransport.lua | 160 ++++++++++--- 8 files changed, 602 insertions(+), 183 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 4c27e75ee..30bb2f75e 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -134,17 +134,234 @@ function ARMYGROUP:New(group) ------------------------ --- Pseudo Functions --- ------------------------ - - --- Triggers the FSM event "Stop". Stops the ARMYGROUP and all its event handlers. + --- Triggers the FSM event "Cruise". + -- @function [parent=#ARMYGROUP] Cruise -- @param #ARMYGROUP self + -- @param #number Speed Speed in knots until next waypoint is reached. + -- @param #number Formation Formation. - --- Triggers the FSM event "Stop" after a delay. Stops the ARMYGROUP and all its event handlers. - -- @function [parent=#ARMYGROUP] __Stop + --- Triggers the FSM event "Cruise" after a delay. + -- @function [parent=#ARMYGROUP] __Cruise -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. - - -- TODO: Add pseudo functions. + -- @param #number Speed Speed in knots until next waypoint is reached. + -- @param #number Formation Formation. + --- On after "Cruise" event. + -- @function [parent=#ARMYGROUP] OnAfterCruise + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number Speed Speed in knots until next waypoint is reached. + -- @param #number Formation Formation. + + + --- Triggers the FSM event "FullStop". + -- @function [parent=#ARMYGROUP] FullStop + -- @param #ARMYGROUP self + + --- Triggers the FSM event "FullStop" after a delay. + -- @function [parent=#ARMYGROUP] __FullStop + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "FullStop" event. + -- @function [parent=#ARMYGROUP] OnAfterFullStop + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "RTZ". + -- @function [parent=#ARMYGROUP] RTZ + -- @param #ARMYGROUP self + + --- Triggers the FSM event "RTZ" after a delay. + -- @function [parent=#ARMYGROUP] __RTZ + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "RTZ" event. + -- @function [parent=#ARMYGROUP] OnAfterRTZ + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Returned". + -- @function [parent=#ARMYGROUP] Returned + -- @param #ARMYGROUP self + + --- Triggers the FSM event "Returned" after a delay. + -- @function [parent=#ARMYGROUP] __Returned + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "Returned" event. + -- @function [parent=#ARMYGROUP] OnAfterReturned + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Detour". + -- @function [parent=#ARMYGROUP] Detour + -- @param #ARMYGROUP self + + --- Triggers the FSM event "Detour" after a delay. + -- @function [parent=#ARMYGROUP] __Detour + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "Detour" event. + -- @function [parent=#ARMYGROUP] OnAfterDetour + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "DetourReached". + -- @function [parent=#ARMYGROUP] DetourReached + -- @param #ARMYGROUP self + + --- Triggers the FSM event "DetourReached" after a delay. + -- @function [parent=#ARMYGROUP] __DetourReached + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "DetourReached" event. + -- @function [parent=#ARMYGROUP] OnAfterDetourReached + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Retreat". + -- @function [parent=#ARMYGROUP] Retreat + -- @param #ARMYGROUP self + + --- Triggers the FSM event "Retreat" after a delay. + -- @function [parent=#ARMYGROUP] __Retreat + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "Retreat" event. + -- @function [parent=#ARMYGROUP] OnAfterRetreat + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Retreated". + -- @function [parent=#ARMYGROUP] Retreated + -- @param #ARMYGROUP self + + --- Triggers the FSM event "Retreated" after a delay. + -- @function [parent=#ARMYGROUP] __Retreated + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "Retreated" event. + -- @function [parent=#ARMYGROUP] OnAfterRetreated + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "EngageTarget". + -- @function [parent=#ARMYGROUP] EngageTarget + -- @param #ARMYGROUP self + + --- Triggers the FSM event "EngageTarget" after a delay. + -- @function [parent=#ARMYGROUP] __EngageTarget + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "EngageTarget" event. + -- @function [parent=#ARMYGROUP] OnAfterEngageTarget + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Disengage". + -- @function [parent=#ARMYGROUP] Disengage + -- @param #ARMYGROUP self + + --- Triggers the FSM event "Disengage" after a delay. + -- @function [parent=#ARMYGROUP] __Disengage + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "Disengage" event. + -- @function [parent=#ARMYGROUP] OnAfterDisengage + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Rearm". + -- @function [parent=#ARMYGROUP] Rearm + -- @param #ARMYGROUP self + + --- Triggers the FSM event "Rearm" after a delay. + -- @function [parent=#ARMYGROUP] __Rearm + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "Rearm" event. + -- @function [parent=#ARMYGROUP] OnAfterRearm + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Rearming". + -- @function [parent=#ARMYGROUP] Rearming + -- @param #ARMYGROUP self + + --- Triggers the FSM event "Rearming" after a delay. + -- @function [parent=#ARMYGROUP] __Rearming + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "Rearming" event. + -- @function [parent=#ARMYGROUP] OnAfterRearming + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Rearmed". + -- @function [parent=#ARMYGROUP] Rearmed + -- @param #ARMYGROUP self + + --- Triggers the FSM event "Rearmed" after a delay. + -- @function [parent=#ARMYGROUP] __Rearmed + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + --- On after "Rearmed" event. + -- @function [parent=#ARMYGROUP] OnAfterRearmed + -- @param #ARMYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + -- TODO: Add pseudo functions. -- Init waypoints. self:_InitWaypoints() @@ -598,8 +815,10 @@ function ARMYGROUP:onafterSpawned(From, Event, To) -- Update route. if #self.waypoints>1 then + self:T(self.lid.."Got waypoints on spawn ==> Cruise in -0.1 sec!") self:__Cruise(-0.1, nil, self.option.Formation) else + self:T(self.lid.."No waypoints on spawn ==> Full Stop!") self:FullStop() end @@ -629,6 +848,8 @@ function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, N, Speed, Formation) elseif self:IsStopped() then self:E(self.lid.."Update route denied. Group is STOPPED!") return false + elseif self:IsHolding() then + self:E(self.lid.."Update route denied. Group is holding position! Use Cruise()") end return true end @@ -724,10 +945,10 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Debug output. - if false then + if self.verbose>=5 then for i,_wp in pairs(waypoints) do local wp=_wp - local text=string.format("WP #%d UID=%d type=%s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.uid and wp.uid or 0, wp.type, wp.speed, wp.alt, wp.action) + local text=string.format("WP #%d UID=%d type=%s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.uid and wp.uid or -1, wp.type, wp.speed, wp.alt, wp.action) self:T(text) end end @@ -1376,7 +1597,8 @@ function ARMYGROUP:_InitGroup(Template) -- Add elemets. for _,unit in pairs(units) do - self:_AddElementByName(unit:GetName()) + local unitname=unit:GetName() + self:_AddElementByName(unitname) end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index eb91f0b7a..10b1b7e2c 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2255,11 +2255,11 @@ end --- Attach OPS transport to the mission. Mission assets will be transported before the mission is started at the OPSGROUP level. -- @param #AUFTRAG self -- @param Core.Zone#ZONE DeployZone Zone where assets are deployed. --- @param Core.Zone#ZONE DisembarkZone Zone where assets are disembarked to. -- @param #number NcarriersMin Number of carriers *at least* required. Default 1. -- @param #number NcarriersMax Number of carriers *at most* used for transportation. Default is same as `NcarriersMin`. +-- @param Core.Zone#ZONE DisembarkZone Zone where assets are disembarked to. -- @return #AUFTRAG self -function AUFTRAG:SetRequiredTransport(DeployZone, DisembarkZone, NcarriersMin, NcarriersMax) +function AUFTRAG:SetRequiredTransport(DeployZone, NcarriersMin, NcarriersMax, DisembarkZone) -- OPS transport from pickup to deploy zone. self.transportDeployZone=DeployZone diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 20c82e8ad..1c723f44c 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -235,15 +235,15 @@ function CHIEF:New(Coalition, AgentSet, Alias) --- 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. + -- @param #table Legions The Legion(s) to which the mission is assigned. --- 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. + -- @param #table Legions The Legion(s) to which the mission is assigned. --- On after "MissionAssign" event. -- @function [parent=#CHIEF] OnAfterMissionAssign @@ -251,9 +251,8 @@ function CHIEF:New(Coalition, AgentSet, Alias) -- @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. - + -- @param #table Legions The Legion(s) to which the mission is assigned. --- Triggers the FSM event "MissionCancel". -- @function [parent=#CHIEF] MissionCancel @@ -1183,15 +1182,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:onafterMissionAssign(From, Event, To, Legion, Mission) + -- @param #table Legions The Legion(s) to which the mission is assigned. +function CHIEF:onafterMissionAssign(From, Event, To, Mission, Legions) if self.commander then self:I(self.lid..string.format("Assigning mission %s (%s) to COMMANDER", Mission.name, Mission.type)) Mission.chief=self Mission.statusChief=AUFTRAG.Status.QUEUED - self.commander:MissionAssign(Legion, Mission) + self.commander:MissionAssign(Mission, Legions) else self:E(self.lid..string.format("Mission cannot be assigned as no COMMANDER is defined!")) end @@ -1523,11 +1522,9 @@ function CHIEF:CheckTargetQueue() -- Mission parameters. mission.prio=target.prio mission.importance=target.importance - - -- Assign mission to legions. - for _,Legion in pairs(Legions) do - self:MissionAssign(Legion, mission) - end + + -- Assign mission to legions. + self:MissionAssign(mission, Legions) -- Only ONE target is assigned per check. return @@ -2054,10 +2051,7 @@ function CHIEF:RecruitAssetsForZone(StratZone, MissionType, NassetsMin, NassetsM mission.opstransport=transport -- Assign mission to legions. - for _,_legion in pairs(legions) do - local legion=_legion --Ops.Legion#LEGION - self:MissionAssign(legion, mission) - end + self:MissionAssign(mission, legions) -- Attach mission to ops zone. -- TODO: Need a better way! @@ -2078,10 +2072,7 @@ function CHIEF:RecruitAssetsForZone(StratZone, MissionType, NassetsMin, NassetsM end -- Assign mission to legions. - for _,_legion in pairs(legions) do - local legion=_legion --Ops.Legion#LEGION - self:MissionAssign(legion, mission) - end + self:MissionAssign(mission, legions) -- Attach mission to ops zone. -- TODO: Need a better way! diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index d3834fda3..1bae4aecf 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -1,4 +1,4 @@ ---- **Ops** - Commander of Airwings, Brigades and Flotillas. +--- **Ops** - Commander of Airwings, Brigades and Fleets. -- -- **Main Features:** -- @@ -36,8 +36,81 @@ -- -- # The COMMANDER Concept -- --- A commander is the head of legions. He/she will find the best LEGIONs to perform an assigned AUFTRAG (mission). +-- A commander is the head of legions. He/she will find the best LEGIONs to perform an assigned AUFTRAG (mission) or OPSTRANSPORT. +-- A legion can be an AIRWING, BRIGADE or FLEET. -- +-- # Constructor +-- +-- A new CHIEF object is created with the @{#CHIEF.New}(*Coalition, Alias*) function, where the parameter *Coalition* is the coalition side. +-- It can be `coalition.side.RED`, `coalition.side.BLUE` or `coalition.side.NEUTRAL`. This parameter is mandatory. +-- The second parameter *Alias* is optional and can be used to give the COMMANDER a "name", which is used for output in the dcs.log file. +-- +-- local myCommander=COMANDER:New(coalition.side.BLUE, "General Patton") +-- +-- # Adding Legions +-- +-- Legions, i.e. AIRWINGS, BRIGADES and FLEETS can be added via the @{#COMMANDER.AddLegion}(*Legion*) command: +-- +-- myCommander:AddLegion(myLegion) +-- +-- ## Adding Airwings +-- +-- It is also possible to use @{#COMMANDER.AddAirwing}(*myAirwing*) function. This does the same as the `AddLegion` function but might be a bit more intuitive. +-- +-- ## Adding Brigades +-- +-- It is also possible to use @{#COMMANDER.AddBrigade}(*myBrigade*) function. This does the same as the `AddLegion` function but might be a bit more intuitive. +-- +-- ## Adding Fleets +-- +-- It is also possible to use @{#COMMANDER.AddFleet}(*myFleet*) function. This does the same as the `AddLegion` function but might be a bit more intuitive. +-- +-- # Adding Missions +-- +-- Mission can be added via the @{#COMMANDER.AddMission}(*myMission*) function. +-- +-- # Adding OPS Transports +-- +-- Transportation assignments can be added via the @{#COMMANDER.AddOpsTransport}(*myTransport*) function. +-- +-- # Adding CAP Zones +-- +-- A CAP zone can be added via the @{#COMMANDER.AddCapZone}() function. +-- +-- # Adding Rearming Zones +-- +-- A rearming zone can be added via the @{#COMMANDER.AddRearmingZone}() function. +-- +-- # Adding Refuelling Zones +-- +-- A refuelling zone can be added via the @{#COMMANDER.AddRefuellingZone}() function. +-- +-- +-- # FSM Events +-- +-- The COMMANDER will +-- +-- # OPSGROUP on Mission +-- +-- Whenever an OPSGROUP (FLIGHTGROUP, ARMYGROUP or NAVYGROUP) is send on a mission, the `OnAfterOpsOnMission()` event is triggered. +-- Mission designers can hook into the event with the @{#COMMANDER.OnAfterOpsOnMission}() function +-- +-- function myCommander:OnAfterOpsOnMission(From, Event, To, OpsGroup, Mission) +-- -- Your code +-- end +-- +-- # Canceling a Mission +-- +-- A mission can be cancelled with the @{#COMMMANDER.MissionCancel}() function +-- +-- myCommander:MissionCancel(myMission) +-- +-- or +-- myCommander:__MissionCancel(5*60, myMission) +-- +-- The last commander cancels the mission after 5 minutes (300 seconds). +-- +-- The cancel command will be forwarded to all assigned legions and OPS groups, which will abort their mission or remove it from their queue. -- -- @field #COMMANDER COMMANDER = { @@ -62,8 +135,8 @@ COMMANDER.version="0.1.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Add CAP zones. --- TODO: Add tanker zones. +-- DONE: Add CAP zones. +-- DONE: Add tanker zones. -- 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. @@ -83,11 +156,28 @@ function COMMANDER:New(Coalition, Alias) -- Inherit everything from INTEL class. local self=BASE:Inherit(self, FSM:New()) --#COMMANDER + + if Coalition==nil then + env.error("ERROR: Coalition parameter is nil in COMMANDER:New() call!") + return nil + end -- Set coaliton. self.coalition=Coalition + -- Alias name. - self.alias=Alias or string.format("John Doe") + self.alias=Alias + + -- Choose a name for red or blue. + if self.alias==nil then + if Coalition==coalition.side.BLUE then + self.alias="George S. Patton" + elseif Coalition==coalition.side.RED then + self.alias="Georgy Zhukov" + elseif Coalition==coalition.side.NEUTRAL then + self.alias="Mahatma Gandhi" + end + end -- Log ID. self.lid=string.format("COMMANDER %s [%s] | ", self.alias, UTILS.GetCoalitionName(self.coalition)) @@ -145,15 +235,15 @@ function COMMANDER:New(Coalition, Alias) --- Triggers the FSM event "MissionAssign". Mission is added to a LEGION mission queue and already requested. Needs assets to be added to the mission! -- @function [parent=#COMMANDER] MissionAssign -- @param #COMMANDER self - -- @param Ops.Legion#LEGION Legion The Legion. -- @param Ops.Auftrag#AUFTRAG Mission The mission. + -- @param #table Legions The Legion(s) to which the mission is assigned. --- Triggers the FSM event "MissionAssign" after a delay. Mission is added to a LEGION mission queue and already requested. Needs assets to be added to the mission! -- @function [parent=#COMMANDER] __MissionAssign -- @param #COMMANDER self -- @param #number delay Delay in seconds. - -- @param Ops.Legion#LEGION Legion The Legion. -- @param Ops.Auftrag#AUFTRAG Mission The mission. + -- @param #table Legions The Legion(s) to which the mission is assigned. --- On after "MissionAssign" event. -- @function [parent=#COMMANDER] OnAfterMissionAssign @@ -161,8 +251,8 @@ function COMMANDER:New(Coalition, Alias) -- @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. + -- @param #table Legions The Legion(s) to which the mission is assigned. --- Triggers the FSM event "MissionCancel". @@ -188,15 +278,15 @@ function COMMANDER:New(Coalition, Alias) --- Triggers the FSM event "TransportAssign". -- @function [parent=#COMMANDER] TransportAssign -- @param #COMMANDER self - -- @param Ops.Legion#LEGION Legion The Legion. -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + -- @param #table Legions The legion(s) to which this transport is assigned. --- Triggers the FSM event "TransportAssign" after a delay. -- @function [parent=#COMMANDER] __TransportAssign -- @param #COMMANDER self -- @param #number delay Delay in seconds. - -- @param Ops.Legion#LEGION Legion The Legion. -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + -- @param #table Legions The legion(s) to which this transport is assigned. --- On after "TransportAssign" event. -- @function [parent=#COMMANDER] OnAfterTransportAssign @@ -204,8 +294,8 @@ function COMMANDER:New(Coalition, Alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param Ops.Legion#LEGION Legion The Legion. -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + -- @param #table Legions The legion(s) to which this transport is assigned. --- Triggers the FSM event "TransportCancel". @@ -377,7 +467,7 @@ function COMMANDER:RemoveTransport(Transport) local transport=_transport --Ops.OpsTransport#OPSTRANSPORT if transport.uid==Transport.uid then - self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", transport.uid, transport:GetState())) + self:I(self.lid..string.format("Removing transport UID=%d status=%s from queue", transport.uid, transport:GetState())) transport.commander=nil table.remove(self.transportqueue, i) break @@ -444,7 +534,7 @@ function COMMANDER:AddCapZone(Zone, Altitude, Speed, Heading, Leg) table.insert(self.capZones, patrolzone) - return awacszone + return patrolzone end --- Add an AWACS zone. @@ -496,7 +586,7 @@ function COMMANDER:AddTankerZone(Zone, Altitude, Speed, Heading, Leg, RefuelSyst table.insert(self.tankerZones, tankerzone) - return awacszone + return tankerzone end --- Check if this mission is already in the queue. @@ -762,24 +852,29 @@ 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 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)) +-- @param #table Legions The Legion(s) to which the mission is assigned. +function COMMANDER:onafterMissionAssign(From, Event, To, Mission, Legions) -- 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 - - -- Add mission to legion. - Legion:AddMission(Mission) - - -- Directly request the mission as the assets have already been selected. - Legion:MissionRequest(Mission) + + for _,_Legion in pairs(Legions) do + local Legion=_Legion --Ops.Legion#LEGION + + -- Debug info. + self:I(self.lid..string.format("Assigning mission \"%s\" [%s] to legion \"%s\"", Mission.name, Mission.type, Legion.alias)) + + -- Add mission to legion. + Legion:AddMission(Mission) + + -- Directly request the mission as the assets have already been selected. + Legion:MissionRequest(Mission) + + end end @@ -792,7 +887,7 @@ end function COMMANDER: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)) + self:I(self.lid..string.format("Cancelling mission \"%s\" [%s] in status %s", Mission.name, Mission.type, Mission.status)) -- Set commander status. Mission.statusCommander=AUFTRAG.Status.CANCELLED @@ -825,21 +920,26 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.Legion#LEGION Legion The LEGION. --- @param Ops.OpsTransport#OPSTRANSPORT -function COMMANDER:onafterTransportAssign(From, Event, To, Legion, Transport) - - -- Debug info. - self:I(self.lid..string.format("Assigning transport %d to legion %s", Transport.uid, Legion.alias)) +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + -- @param #table Legions The legion(s) to which this transport is assigned. +function COMMANDER:onafterTransportAssign(From, Event, To, Transport, Legions) -- Set mission commander status to QUEUED as it is now queued at a legion. Transport.statusCommander=OPSTRANSPORT.Status.QUEUED - -- Add mission to legion. - Legion:AddOpsTransport(Transport) + for _,_Legion in pairs(Legions) do + local Legion=_Legion --Ops.Legion#LEGION + + -- Debug info. + self:I(self.lid..string.format("Assigning transport UID=%d to legion \"%s\"", Transport.uid, Legion.alias)) - -- Directly request the mission as the assets have already been selected. - Legion:TransportRequest(Transport) + -- Add mission to legion. + Legion:AddOpsTransport(Transport) + + -- Directly request the mission as the assets have already been selected. + Legion:TransportRequest(Transport) + + end end @@ -889,7 +989,7 @@ end -- @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())) + self:T2(self.lid..string.format("Group \"%s\" on mission \"%s\" [%s]", OpsGroup:GetName(), Mission:GetName(), Mission:GetType())) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -981,17 +1081,8 @@ function COMMANDER:CheckMissionQueue() -- Escort and transport must be available (or not required). if EscortAvail and TransportAvail then - -- Assign mission to legion(s). - for _,_legion in pairs(legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Debug message. - self:I(self.lid..string.format("Assigning mission %s [%s] to legion %s", mission:GetName(), mission:GetType(), legion.alias)) - - -- Add mission to legion. - self:MissionAssign(legion, mission) - - end + -- Add mission to legion. + self:MissionAssign(mission, legions) else -- Recruited assets but no requested escort available. Unrecruit assets! @@ -1188,16 +1279,7 @@ function COMMANDER:CheckTransportQueue() end -- Assign transport to legion(s). - for _,_legion in pairs(legions) do - local legion=_legion --Ops.Legion#LEGION - - -- Debug message. - self:I(self.lid..string.format("Assigning transport UID=%d to legion %s", transport.uid, legion.alias)) - - -- Add mission to legion. - self:TransportAssign(legion, transport) - - end + self:TransportAssign(transport, legions) -- Only ONE transport is assigned. return diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 372aad628..97c8e8a2f 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -125,15 +125,15 @@ function LEGION:New(WarehouseName, LegionName) --- Triggers the FSM event "MissionAssign". -- @function [parent=#LEGION] MissionAssign -- @param #LEGION self - -- @param Ops.Legion#LEGION Legion The legion from which the mission assets are requested. -- @param Ops.Auftrag#AUFTRAG Mission The mission. + -- @param #table Legions The legion(s) from which the mission assets are requested. --- Triggers the FSM event "MissionAssign" after a delay. -- @function [parent=#LEGION] __MissionAssign -- @param #LEGION self -- @param #number delay Delay in seconds. - -- @param Ops.Legion#LEGION Legion The legion from which the mission assets are requested. -- @param Ops.Auftrag#AUFTRAG Mission The mission. + -- @param #table Legions The legion(s) from which the mission assets are requested. --- On after "MissionAssign" event. -- @function [parent=#LEGION] OnAfterMissionAssign @@ -141,8 +141,8 @@ function LEGION:New(WarehouseName, LegionName) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param Ops.Legion#LEGION Legion The legion from which the mission assets are requested. -- @param Ops.Auftrag#AUFTRAG Mission The mission. + -- @param #table Legions The legion(s) from which the mission assets are requested. --- Triggers the FSM event "MissionRequest". @@ -183,15 +183,15 @@ function LEGION:New(WarehouseName, LegionName) --- Triggers the FSM event "TransportAssign". -- @function [parent=#LEGION] TransportAssign -- @param #LEGION self - -- @param Ops.Legion#LEGION Legion The legion from which the transport assets are requested. -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + -- @param #table Legions The legion(s) to which this transport is assigned. --- Triggers the FSM event "TransportAssign" after a delay. -- @function [parent=#LEGION] __TransportAssign -- @param #LEGION self -- @param #number delay Delay in seconds. - -- @param Ops.Legion#LEGION Legion The legion from which the transport assets are requested. -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + -- @param #table Legions The legion(s) to which this transport is assigned. --- On after "TransportAssign" event. -- @function [parent=#LEGION] OnAfterTransportAssign @@ -199,8 +199,8 @@ function LEGION:New(WarehouseName, LegionName) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param Ops.Legion#LEGION Legion The legion from which the transport assets are requested. -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. + -- @param #table Legions The legion(s) to which this transport is assigned. --- Triggers the FSM event "TransportRequest". @@ -617,18 +617,23 @@ 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 LEGION:onafterMissionAssign(From, Event, To, Legion, Mission) +-- @param #table Legions The LEGIONs. +function LEGION:onafterMissionAssign(From, Event, To, Mission, Legions) + + for _,_Legion in pairs(Legions) do + local Legion=_Legion --Ops.Legion#LEGION - -- Debug info. - self:I(self.lid..string.format("Assigning mission %s (%s) to legion %s", Mission.name, Mission.type, Legion.alias)) - - -- Add mission to legion. - Legion:AddMission(Mission) - - -- Directly request the mission as the assets have already been selected. - Legion:MissionRequest(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 legion. + Legion:AddMission(Mission) + + -- Directly request the mission as the assets have already been selected. + Legion:MissionRequest(Mission) + + end end @@ -749,18 +754,23 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.Legion#LEGION Legion The LEGION. --- @param Ops.OpsTransport#OPSTRANSPORT The transport. -function LEGION:onafterTransportAssign(From, Event, To, Legion, Transport) +-- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. +-- @param #table Legions The legion(s) to which the transport is assigned. +function LEGION:onafterTransportAssign(From, Event, To, Transport, Legions) - -- Debug info. - self:I(self.lid..string.format("Assigning transport %d to legion %s", Transport.uid, Legion.alias)) + for _,_Legion in pairs(Legions) do + local Legion=_Legion --Ops.Legion#LEGION + + -- Debug info. + self:I(self.lid..string.format("Assigning transport %d to legion %s", Transport.uid, Legion.alias)) + + -- Add mission to legion. + Legion:AddOpsTransport(Transport) - -- Add mission to legion. - Legion:AddOpsTransport(Transport) - - -- Directly request the mission as the assets have already been selected. - Legion:TransportRequest(Transport) + -- Directly request the mission as the assets have already been selected. + Legion:TransportRequest(Transport) + + end end @@ -2148,7 +2158,7 @@ function LEGION:AssignAssetsForEscort(Cohorts, Assets, NescortMin, NescortMax) end -- Assign mission to legion. - self:MissionAssign(legion, escort) + self:MissionAssign(escort, {legion}) end end @@ -2240,7 +2250,7 @@ function LEGION:AssignAssetsForTransport(Legions, CargoAssets, NcarriersMin, Nca end -- Debug info. - env.info(string.format("FF Transport available with %d carrier assets", #CarrierAssets)) + self:T(self.lid..string.format("Transport available with %d carrier assets", #CarrierAssets)) -- Add cargo assets to transport. for _,_legion in pairs(CargoLegions) do @@ -2272,10 +2282,7 @@ function LEGION:AssignAssetsForTransport(Legions, CargoAssets, NcarriersMin, Nca end -- Assign TRANSPORT to legions. This also sends the request for the assets. - for _,_legion in pairs(CarrierLegions) do - local legion=_legion --Ops.Legion#LEGION - self:TransportAssign(legion, Transport) - end + self:TransportAssign(Transport, CarrierLegions) -- Got transport. return true, Transport @@ -2393,15 +2400,15 @@ function LEGION._OptimizeAssetSelection(assets, MissionType, TargetVec2, Include table.sort(assets, optimize) -- Remove distance parameter. - --[[ - local text=string.format("Optimized %d assets for %s mission/transport (payload=%s):", #assets, MissionType, tostring(IncludePayload)) - for i,Asset in pairs(assets) do - local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem - text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) - asset.score=nil + if LEGION.verbose>0 then + local text=string.format("Optimized %d assets for %s mission/transport (payload=%s):", #assets, MissionType, tostring(IncludePayload)) + for i,Asset in pairs(assets) do + local asset=Asset --Functional.Warehouse#WAREHOUSE.Assetitem + text=text..string.format("\n%s %s: score=%d", asset.squadname, asset.spawngroupname, asset.score) + asset.score=nil + end + env.info(text) end - env.info(text) - ]] end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 4b7d1139a..77adc930b 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -159,13 +159,38 @@ function NAVYGROUP:New(group) self:AddTransition("*", "CollisionWarning", "*") -- Collision warning. self:AddTransition("*", "ClearAhead", "*") -- Clear ahead. - self:AddTransition("*", "Dive", "*") -- Command a submarine to dive. - self:AddTransition("Diving", "Surface", "*") -- Command a submarine to go to the surface. + self:AddTransition("Cruising", "Dive", "Cruising") -- Command a submarine to dive. + self:AddTransition("Cruising", "Surface", "Cruising") -- Command a submarine to go to the surface. ------------------------ --- Pseudo Functions --- ------------------------ + --- Triggers the FSM event "Cruise". + -- @function [parent=#NAVYGROUP] Cruise + -- @param #NAVYGROUP self + -- @param #number Speed Speed in knots until next waypoint is reached. + + --- Triggers the FSM event "Cruise" after a delay. + -- @function [parent=#NAVYGROUP] __Cruise + -- @param #NAVYGROUP self + -- @param #number delay Delay in seconds. + -- @param #number Speed Speed in knots until next waypoint is reached. + + --- On after "Cruise" event. + -- @function [parent=#NAVYGROUP] OnAfterCruise + -- @param #NAVYGROUP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number Speed Speed in knots until next waypoint is reached. + + + + + + + --- Triggers the FSM event "TurnIntoWind". -- @function [parent=#NAVYGROUP] TurnIntoWind -- @param #NAVYGROUP self @@ -240,7 +265,6 @@ function NAVYGROUP:New(group) -- @param #NAVYGROUP.IntoWind IntoWindData Data table. - --- Triggers the FSM event "TurningStarted". -- @function [parent=#NAVYGROUP] TurningStarted -- @param #NAVYGROUP self diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 284c4d3bd..14f09e47e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2899,6 +2899,9 @@ function OPSGROUP:OnEventBirth(EventData) local element=self:GetElementByName(unitname) if element and element.status~=OPSGROUP.ElementStatus.SPAWNED then + + -- Debug info. + self:T(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) -- Set element to spawned state. self:ElementSpawned(element) @@ -6113,7 +6116,6 @@ function OPSGROUP:_CheckCargoTransport() local Time=timer.getAbsTime() -- Cargo bay debug info. - -- Check cargo bay and declare cargo groups dead. if self.verbose>=1 then local text="" for _,_element in pairs(self.elements) do @@ -8046,10 +8048,15 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) -- Debug info. self:T(self.lid..string.format("Board with direct load to carrier %s", CarrierGroup:GetName())) - local mycarriergroup=self:_GetMyCarrierGroup() + -- Get current carrier group. + local mycarriergroup=self:_GetMyCarrierGroup() + if mycarriergroup then + self:T(self.lid..string.format("Current carrier group %s", mycarriergroup:GetName())) + end -- Unload cargo first. - if mycarriergroup then + if mycarriergroup and mycarriergroup:GetName()~=CarrierGroup:GetName() and self:IsLoaded() then + -- TODO: Unload triggers other stuff like Disembarked. This can be a problem! mycarriergroup:Unload(self) end @@ -8880,8 +8887,8 @@ function OPSGROUP:Route(waypoints, delay) airborne = self:IsFlightgroup(), route={points=waypoints}, }, - } - + } + -- Set mission task. self:SetTask(DCSTask) @@ -10813,11 +10820,20 @@ function OPSGROUP:_AddElementByName(unitname) --local unittemplate=_DATABASE:GetUnitTemplateFromUnitName(unitname) -- Element table. - local element={} --#OPSGROUP.Element + local element=self:GetElementByName(unitname) + + -- Add element to table. + if element then + -- We already know this element. + else + -- Add a new element. + element={} + element.status=OPSGROUP.ElementStatus.INUTERO + table.insert(self.elements, element) + end -- Name and status. element.name=unitname - element.status=OPSGROUP.ElementStatus.INUTERO -- Unit and group. element.unit=unit @@ -10904,11 +10920,6 @@ function OPSGROUP:_AddElementByName(unitname) element.size, element.length, element.height, element.width, element.weight, element.weightMaxTotal, element.weightCargo, element.weightMaxCargo) self:T(self.lid..text) - -- Add element to table. - if not self:_IsElement(unitname) then - table.insert(self.elements, element) - end - -- Trigger spawned event if alive. if unit:IsAlive() and element.status~=OPSGROUP.ElementStatus.SPAWNED then -- This needs to be slightly delayed (or moved elsewhere) or the first element will always trigger the group spawned event as it is not known that more elements are in the group. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index e860d50a5..ec7f9ac44 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -38,8 +38,8 @@ -- @field #number duration Duration (`Tstop-Tstart`) of the transport in seconds. -- @field #table conditionStart Start conditions. -- --- @field #table cargos Cargos. Each element is a @{Ops.OpsGroup#OPSGROUP.CargoGroup}. -- @field #table carriers Carriers assigned for this transport. +-- @field #table carrierTransportStatus Status of each carrier. -- -- @field #table tzCombos Table of transport zone combos. Each element of the table is of type `#OPSTRANSPORT.TransportZoneCombo`. -- @field #number tzcCounter Running number of added transport zone combos. @@ -117,7 +117,6 @@ OPSTRANSPORT = { ClassName = "OPSTRANSPORT", verbose = 0, - cargos = {}, carriers = {}, carrierTransportStatus = {}, tzCombos = {}, @@ -152,7 +151,7 @@ OPSTRANSPORT.Status={ FAILED="failed", } ---- Pickup and deploy set. +--- Transport zone combination. -- @type OPSTRANSPORT.TransportZoneCombo -- @field #number uid Unique ID of the TZ combo. -- @field #number Ncarriers Number of carrier groups using this transport zone. @@ -233,7 +232,6 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) self:SetRequiredCarriers() -- Init arrays and counters. - self.cargos={} self.carriers={} self.Ncargo=0 self.Ncarrier=0 @@ -256,7 +254,7 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. self:AddTransition("*", "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. - self:AddTransition("*", "Status", "*") + self:AddTransition("*", "StatusUpdate", "*") self:AddTransition("*", "Stop", "*") self:AddTransition("*", "Cancel", OPSTRANSPORT.Status.CANCELLED) -- Command to cancel the transport. @@ -272,12 +270,12 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) --- Pseudo Functions --- ------------------------ - --- Triggers the FSM event "Status". - -- @function [parent=#OPSTRANSPORT] Status + --- Triggers the FSM event "StatusUpdate". + -- @function [parent=#OPSTRANSPORT] StatusUpdate -- @param #OPSTRANSPORT self --- Triggers the FSM event "Status" after a delay. - -- @function [parent=#OPSTRANSPORT] __Status + -- @function [parent=#OPSTRANSPORT] __StatusUpdate -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. @@ -291,6 +289,13 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. + --- On after "Planned" event. + -- @function [parent=#OPSTRANSPORT] OnAfterPlanned + -- @param #OPSTRANSPORT self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + --- Triggers the FSM event "Queued". -- @function [parent=#OPSTRANSPORT] Queued @@ -301,6 +306,13 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. + --- On after "Queued" event. + -- @function [parent=#OPSTRANSPORT] OnAfterQueued + -- @param #OPSTRANSPORT self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + --- Triggers the FSM event "Requested". -- @function [parent=#OPSTRANSPORT] Requested @@ -311,6 +323,13 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. + --- On after "Requested" event. + -- @function [parent=#OPSTRANSPORT] OnAfterRequested + -- @param #OPSTRANSPORT self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + --- Triggers the FSM event "Scheduled". -- @function [parent=#OPSTRANSPORT] Scheduled @@ -321,6 +340,13 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. + --- On after "Scheduled" event. + -- @function [parent=#OPSTRANSPORT] OnAfterScheduled + -- @param #OPSTRANSPORT self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + --- Triggers the FSM event "Executing". -- @function [parent=#OPSTRANSPORT] Executing @@ -331,6 +357,13 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. + --- On after "Executing" event. + -- @function [parent=#OPSTRANSPORT] OnAfterExecuting + -- @param #OPSTRANSPORT self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + --- Triggers the FSM event "Delivered". -- @function [parent=#OPSTRANSPORT] Delivered @@ -341,6 +374,13 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. + --- On after "Delivered" event. + -- @function [parent=#OPSTRANSPORT] OnAfterDelivered + -- @param #OPSTRANSPORT self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + --- Triggers the FSM event "Cancel". -- @function [parent=#OPSTRANSPORT] Cancel @@ -414,7 +454,7 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) --TODO: Psydofunctions -- Call status update. - self:__Status(-1) + self:__StatusUpdate(-1) return self end @@ -481,7 +521,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) if cargo then -- Add to main table. - table.insert(self.cargos, cargo) + --table.insert(self.cargos, cargo) self.Ncargo=self.Ncargo+1 -- Add to TZC table. @@ -514,7 +554,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) Weight=Weight+weight text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) end - text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #self.cargos, Weight) + text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", self.Ncargo, Weight) self:I(self.lid..text) end @@ -927,13 +967,8 @@ function OPSTRANSPORT:_DelCarrier(CarrierGroup) self:T(self.lid..string.format("Removing carrier %s", CarrierGroup.groupname)) table.remove(self.carriers, i) end - end - - if #self.carriers==0 then - -- TODO: This call can be WRONG! - self:DeadCarrierAll() end - + end return self @@ -1557,12 +1592,12 @@ end -- Status Update ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after "Status" event. +--- On after "StatusUpdate" event. -- @param #OPSTRANSPORT self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function OPSTRANSPORT:onafterStatus(From, Event, To) +function OPSTRANSPORT:onafterStatusUpdate(From, Event, To) -- Current FSM state. local fsmstate=self:GetState() @@ -1577,7 +1612,9 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) for i,_tz in pairs(self.tzCombos) do local tz=_tz --#OPSTRANSPORT.TransportZoneCombo - text=text..string.format("\n[%d] %s --> %s", i, tz.PickupZone and tz.PickupZone:GetName() or "Unknown", tz.DeployZone and tz.DeployZone and tz.DeployZone:GetName() or "Unknown", tz.Ncarriers) + local pickupzone=tz.PickupZone and tz.PickupZone:GetName() or "Unknown" + local deployzone=tz.DeployZone and tz.DeployZone:GetName() or "Unknown" + text=text..string.format("\n[%d] %s --> %s: Ncarriers=%d, Ncargo=%d (%d)", i, pickupzone, deployzone, tz.Ncarriers, #tz.Cargos, tz.Ncargo) end end @@ -1613,7 +1650,7 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) -- Update status again. if not self:IsDelivered() then - self:__Status(-30) + self:__StatusUpdate(-30) end end @@ -1671,9 +1708,9 @@ end function OPSTRANSPORT:onafterDelivered(From, Event, To) self:T(self.lid..string.format("New status: %s-->%s", From, To)) - -- Inform all assigned carriers that cargo was delivered. They can have this in the queue or are currently processing this transport. - for _,_carrier in pairs(self.carriers) do - local carrier=_carrier --Ops.OpsGroup#OPSGROUP + -- Inform all assigned carriers that cargo was delivered. They can have this in the queue or are currently processing this transport. + for i=#self.carriers, 1, -1 do + local carrier=self.carriers[i] --Ops.OpsGroup#OPSGROUP if self:GetCarrierTransportStatus(carrier)~=OPSTRANSPORT.Status.DELIVERED then carrier:Delivered(self) end @@ -1712,8 +1749,15 @@ end -- @param Ops.OpsGroup#OPSGROUP OpsGroup Carrier OPSGROUP that is dead. function OPSTRANSPORT:onafterDeadCarrierGroup(From, Event, To, OpsGroup) self:I(self.lid..string.format("Carrier OPSGROUP %s dead!", OpsGroup:GetName())) - -- Remove group from carrier list/table. - self.NdeadCarrier=self.NdeadCarrier+1 + + -- Increase dead counter. + self.NcarrierDead=self.NcarrierDead+1 + + if #self.carriers==0 then + self:DeadCarrierAll() + end + + -- Remove group from carrier list/table. self:_DelCarrier(OpsGroup) end @@ -1724,7 +1768,11 @@ end -- @param #string To To state. function OPSTRANSPORT:onafterDeadCarrierAll(From, Event, To) self:I(self.lid..string.format("ALL Carrier OPSGROUPs are dead! Setting stage to PLANNED if not all cargo was delivered.")) + + -- Check if cargo was delivered. self:_CheckDelivered() + + -- Set state back to PLANNED if not delivered. if not self:IsDelivered() then self:Planned() end @@ -2064,9 +2112,16 @@ function OPSTRANSPORT:_GetTransportZoneCombo(Carrier) -- Get carrier position. local vec2=Carrier:GetVec2() + + --- Penalty function. + local function penalty(candidate) + local p=candidate.ncarriers*10-candidate.ncargo+candidate.distance/10 + return p + end + + -- TZC candidates. + local candidates={} - local pickup=nil --#OPSTRANSPORT.TransportZoneCombo - local distmin=nil for i,_transportzone in pairs(self.tzCombos) do local tz=_transportzone --#OPSTRANSPORT.TransportZoneCombo @@ -2078,31 +2133,58 @@ function OPSTRANSPORT:_GetTransportZoneCombo(Carrier) -- Count undelivered cargos in embark(!) zone that fit into the carrier. local ncargo=self:_CountCargosInZone(tz.EmbarkZone, false, Carrier, tz) - --env.info(string.format("FF GetPickupZone i=%d, ncargo=%d", i, ncargo)) - -- At least one group in the zone. if ncargo>=1 then -- Distance to the carrier in meters. local dist=tz.PickupZone:Get2DDistance(vec2) - - if distmin==nil or dist0 then + + -- Minimize penalty. + local function optTZC(candA, candB) + return candA.penalty=3 then + local text="TZC optimized" + for i,candidate in pairs(candidates) do + text=text..string.format("\n[%d] TPZ=%d, Ncarriers=%d, Ncargo=%d, Distance=%.1f km, PENALTY=%d", i, candidate.tzc.uid, candidate.ncarriers, candidate.ncargo, candidate.distance, candidate.penalty) + end + self:I(self.lid..text) + end + + -- Return best candidate. + return candidates[1].tzc else + -- No candidates. self:T(self.lid..string.format("Could NOT find a pickup zone (with cargo) for carrier group %s", Carrier:GetName())) - end + end - return pickup + return nil end --- Get an OPSGROUP from a given OPSGROUP or GROUP object. If the object is a GROUUP, an OPSGROUP is created automatically. From de5d9195f3efcf4cf0012cb744b9e2a05a87bd15 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 16 Oct 2021 09:32:47 +0200 Subject: [PATCH 126/141] OPSGROUP - Cargo bay of element remains after respawn - Added some docs for CHIEF --- Moose Development/Moose/Ops/Chief.lua | 114 ++++++++++++++++++-- Moose Development/Moose/Ops/Commander.lua | 4 +- Moose Development/Moose/Ops/FlightGroup.lua | 2 - Moose Development/Moose/Ops/Legion.lua | 8 +- Moose Development/Moose/Ops/OpsGroup.lua | 22 ++-- Moose Development/Moose/Ops/Target.lua | 2 +- 6 files changed, 128 insertions(+), 24 deletions(-) diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 1c723f44c..1e522f3fd 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -2,7 +2,13 @@ -- -- **Main Features:** -- --- * Stuff +-- * Automatic target engagement based on detection network +-- * Define multiple border, conflict and attack zones +-- * Define strategic "capture" zones +-- * Set stragegy of chief from passive to agressive +-- * Manual target engagement via AUFTRAG and TARGET classes +-- * Add AIRWINGS, BRIGADES and FLEETS as resources +-- * Seamless air-to-air, air-to-ground, ground-to-ground dispatching -- -- === -- @@ -28,14 +34,95 @@ -- @field Ops.Commander#COMMANDER commander Commander of assigned legions. -- @extends Ops.Intelligence#INTEL ---- Be surprised! +--- *In preparing for battle I have always found that plans are useless, but planning is indispensable* -- Dwight D Eisenhower -- -- === -- -- # The CHIEF Concept -- -- The Chief of staff gathers INTEL and assigns missions (AUFTRAG) the airforce, army and/or navy. +-- +-- # Territory +-- +-- The chief class allows you to define boarder zones, conflict zones and attack zones. +-- +-- ## Border Zones +-- +-- Border zones define your own territory. +-- They can be set via the @{#CHIEF.SetBorderZones}() function as a set or added zone by zone via the @{#CHIEF.AddBorderZone}() function. +-- +-- ## Conflict Zones +-- +-- Conflict zones define areas, which usually are under dispute of different coalitions. +-- They can be set via the @{#CHIEF.SetConflictZones}() function as a set or added zone by zone via the @{#CHIEF.AddConflictZone}() function. +-- +-- ## Attack Zones +-- +-- Attack zones are zones that usually lie within the enemy territory. They are only enganged with an agressive strategy. +-- They can be set via the @{#CHIEF.SetAttackZones}() function as a set or added zone by zone via the @{#CHIEF.AddAttackZone}() function. -- +-- # Defense Condition +-- +-- The defence condition (DEFCON) depends on enemy activity detected in the different zone types and is set automatically. +-- +-- * `CHIEF.Defcon.GREEN`: No enemy activities detected. +-- * `CHIEF.Defcon.YELLOW`: Enemy activity detected in conflict zones. +-- * `CHIEF.Defcon.RED`: Enemy activity detected in border zones. +-- +-- The current DEFCON can be retrieved with the @(#CHIEF.GetDefcon)() function. +-- +-- When the DEFCON changed, an FSM event @{#CHIEF.DefconChange} is triggered. Mission designers can hook into this event via the @{#CHIEF.OnAfterDefconChange}() function: +-- +-- --- Function called when the DEFCON changes. +-- function myChief:OnAfterDefconChange(From, Event, To, Defcon) +-- local text=string.format("Changed DEFCON to %s", Defcon) +-- MESSAGE:New(text, 120):ToAll() +-- end +-- +-- # Strategy +-- +-- The strategy of the chief determines, in which areas targets are engaged automatically. +-- +-- * `CHIEF.Strategy.PASSIVE`: Chief is completely passive. No targets at all are engaged automatically. +-- * `CHIEF.Strategy.DEFENSIVE`: Chief acts defensively. Only targets in his own territory are engaged. +-- * `CHIEF.Strategy.OFFENSIVE`: Chief behaves offensively. Targets in his own territory and in conflict zones are enganged. +-- * `CHIEF.Strategy.AGGRESSIVE`: Chief is aggressive. Targets in his own territory, in conflict zones and in attack zones are enganged. +-- * `CHIEF.Strategy.TOTALWAR`: Anything anywhere is enganged. +-- +-- The strategy can be set by the @(#CHIEF.SetStrategy)() and retrieved with the @(#CHIEF.GetStrategy)() function. +-- +-- When the strategy is changed, the FSM event @{#CHIEF.StrategyChange} is triggered and customized code can be added to the @{#CHIEF.OnAfterStrategyChange}() function: +-- +-- --- Function called when the STRATEGY changes. +-- function myChief:OnAfterStrategyChange(From, Event, To, Strategy) +-- local text=string.format("Strategy changd to %s", Strategy) +-- MESSAGE:New(text, 120):ToAll() +-- end +-- +-- # Strategic (Capture) Zones +-- +-- Strategically important zones, which should be captured can be added via the @{#CHIEF.AddStrateticZone}() function. +-- +-- If the zone is currently owned by another coalition and enemy ground troops are present in the zone, a CAS mission is lauchned. +-- +-- Once the zone is cleaned of enemy forces, ground (infantry) troops are send there. These require a transportation via helicopters. +-- So in order to deploy our own troops, infantry assets with `AUFTRAG.Type.ONGUARD` and helicopters with `AUFTRAG.Type.OPSTRANSPORT` need to be available. +-- +-- Whenever a strategic zone is captured by us the FSM event @{#CHIEF.ZoneCaptured} is triggered and customized further actions can be executed +-- with the @{#CHIEF.OnAfterZoneCaptured}() function. +-- +-- Whenever a strategic zone is lost (captured by the enemy), the FSM event @{#CHIEF.ZoneLost} is triggered and customized further actions can be executed +-- with the @{#CHIEF.OnAfterZoneLost}() function. +-- +-- Further events are +-- +-- * @{#CHIEF.ZoneEmpty}, once the zone is completely empty of ground troops. Code can be added to the @{#CHIEF.OnAfterZoneEmpty}() function. +-- * @{#CHIEF.ZoneAttacked}, once the zone is under attack. Code can be added to the @{#CHIEF.OnAfterZoneAttacked}() function. +-- +-- Note that the ownership of a zone is determined via zone scans, i.e. not via the detection network. In other words, there is an all knowing eye. +-- Think of it as the local population providing the intel. It's not totally realistic but the best compromise within the limits of DCS. +-- +-- -- -- @field #CHIEF CHIEF = { @@ -377,13 +464,13 @@ function CHIEF:New(Coalition, AgentSet, Alias) --- Triggers the FSM event "ZoneAttacked". -- @function [parent=#CHIEF] ZoneAttacked -- @param #CHIEF self - -- @param Ops.OpsZone#OPSZONE OpsZone Zone that is beeing attacked. + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that is being 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. + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that is being attacked. --- On after "ZoneAttacked" event. -- @function [parent=#CHIEF] OnAfterZoneAttacked @@ -391,7 +478,7 @@ function CHIEF:New(Coalition, AgentSet, Alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param Ops.OpsZone#OPSZONE OpsZone Zone that is beeing attacked. + -- @param Ops.OpsZone#OPSZONE OpsZone Zone that is being attacked. return self end @@ -1326,7 +1413,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsZone#OPSZONE OpsZone The zone that beeing attacked. +-- @param Ops.OpsZone#OPSZONE OpsZone The zone that being attacked. function CHIEF:onafterZoneAttacked(From, Event, To, OpsZone) -- Debug info. self:T(self.lid..string.format("Zone %s attacked!", OpsZone:GetName())) @@ -2005,9 +2092,17 @@ function CHIEF:RecruitAssetsForZone(StratZone, MissionType, NassetsMin, NassetsM -- Target position. local TargetVec2=StratZone.opszone.zone:GetVec2() + + -- Max range in meters. + local RangeMax=nil + + -- Set max range to 250 NM because we use helos as transport for the infantry. + if MissionType==AUFTRAG.Type.PATROLZONE or MissionType==AUFTRAG.Type.ONGUARD then + RangeMax=UTILS.NMToMeters(250) + end -- Recruite infantry assets. - local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2, nil, nil, nil, nil, Categories, Attributes) + local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2, nil, RangeMax, nil, nil, Categories, Attributes) if recruited then @@ -2057,8 +2152,10 @@ function CHIEF:RecruitAssetsForZone(StratZone, MissionType, NassetsMin, NassetsM -- TODO: Need a better way! StratZone.missionPatrol=mission + return true else LEGION.UnRecruitAssets(assets) + return false end elseif MissionType==AUFTRAG.Type.CAS then @@ -2078,11 +2175,12 @@ function CHIEF:RecruitAssetsForZone(StratZone, MissionType, NassetsMin, NassetsM -- TODO: Need a better way! StratZone.missionCAS=mission + return true end end - return nil + return false end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 1bae4aecf..51747f4b1 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -3,7 +3,7 @@ -- **Main Features:** -- -- * Manages AIRWINGS, BRIGADEs and FLOTILLAs --- * Handles missions (AUFTRAG) and finds the best man for the job +-- * Handles missions (AUFTRAG) and finds the best assets for the job -- -- === -- @@ -30,7 +30,7 @@ -- @field Ops.Chief#CHIEF chief Chief of staff. -- @extends Core.Fsm#FSM ---- Be surprised! +--- *He who has never leared to obey cannot be a good commander* -- Aristotle -- -- === -- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index bfd5b0657..91714054d 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -60,8 +60,6 @@ -- -- === -- --- ![Banner Image](..\Presentations\OPS\FlightGroup\_Main.png) --- -- # The FLIGHTGROUP Concept -- -- # Events diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 97c8e8a2f..8447babaa 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -1,6 +1,6 @@ --- **Ops** - Legion Warehouse. -- --- Parent class of Airwings and Brigades. +-- Parent class of Airwings, Brigades and Fleets. -- -- === -- @@ -21,15 +21,17 @@ -- @field Ops.Chief#CHIEF chief Chief of this legion. -- @extends Functional.Warehouse#WAREHOUSE ---- Be surprised! +--- *Per aspera ad astra* -- -- === -- -- # The LEGION Concept -- --- The LEGION class contains all functions that are common for the AIRWING, BRIGADE and XXX classes, which inherit the LEGION class. +-- The LEGION class contains all functions that are common for the AIRWING, BRIGADE and FLEET classes, which inherit the LEGION class. -- -- An LEGION consists of multiple COHORTs. These cohorts "live" in a WAREHOUSE, i.e. a physical structure that can be destroyed or captured. +-- +-- ** The LEGION class is not meant to be used directly. Use AIRWING, BRIGADE or FLEET instead! ** -- -- @field #LEGION LEGION = { diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 14f09e47e..a5f0a5ee8 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6731,7 +6731,7 @@ function OPSGROUP:GetWeightCargo(UnitName, IncludeReserved) if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then - weight=weight+element.weightCargo + weight=weight+element.weightCargo or 0 end @@ -10871,12 +10871,6 @@ function OPSGROUP:_AddElementByName(unitname) -- Weight and cargo. element.weightEmpty=element.descriptors.massEmpty or 666 - element.weightCargo=0 - element.weight=element.weightEmpty+element.weightCargo - - -- Looks like only aircraft have a massMax value in the descriptors. - element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+8*95 --If max mass is not given, we assume 8 soldiers. - if self.isArmygroup then @@ -10886,6 +10880,11 @@ function OPSGROUP:_AddElementByName(unitname) element.weightMaxTotal=element.weightEmpty+10*1000 + else + + -- Looks like only aircraft have a massMax value in the descriptors. + element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+8*95 --If max mass is not given, we assume 8 soldiers. + end -- Max cargo weight: @@ -10893,7 +10892,14 @@ function OPSGROUP:_AddElementByName(unitname) element.weightMaxCargo=unit.__.CargoBayWeightLimit -- Cargo bay (empty). - element.cargoBay={} + if element.cargoBay then + -- After a respawn, the cargo bay might not be empty! + element.weightCargo=self:GetWeightCargo(element.name, false) + else + element.cargoBay={} + element.weightCargo=0 + end + element.weight=element.weightEmpty+element.weightCargo -- FLIGHTGROUP specific. if self.isFlightgroup then diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 600f2022e..a92606276 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -38,7 +38,7 @@ -- @field #boolean isDestroyed If true, target objects were destroyed. -- @extends Core.Fsm#FSM ---- **It is far more important to be able to hit the target than it is to haggle over who makes a weapon or who pulls a trigger** -- Dwight D. Eisenhower +--- **It is far more important to be able to hit the target than it is to haggle over who makes a weapon or who pulls a trigger** -- Dwight D Eisenhower -- -- === -- From c6995d6e588b83258aa8c61c47a4706c98efc49c Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 19 Oct 2021 10:19:26 +0200 Subject: [PATCH 127/141] OPS - Fixed AUFTRAG :Repeat() - Fixed bug in BRIGADE - Improved cargo bay calculation in POSITIONABLE (includes rel. fuel) - Changed carrier event names in OPSGROUP - New class F10MENU (not sure about it) --- Moose Development/Moose/Core/F10Menu.lua | 212 ++++++++++++++++++ Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/ArmyGroup.lua | 3 +- Moose Development/Moose/Ops/Auftrag.lua | 22 +- Moose Development/Moose/Ops/Brigade.lua | 2 +- Moose Development/Moose/Ops/NavyGroup.lua | 3 + Moose Development/Moose/Ops/OpsGroup.lua | 24 +- .../Moose/Wrapper/Positionable.lua | 14 +- Moose Setup/Moose.files | 1 + 9 files changed, 266 insertions(+), 16 deletions(-) create mode 100644 Moose Development/Moose/Core/F10Menu.lua diff --git a/Moose Development/Moose/Core/F10Menu.lua b/Moose Development/Moose/Core/F10Menu.lua new file mode 100644 index 000000000..de8a04314 --- /dev/null +++ b/Moose Development/Moose/Core/F10Menu.lua @@ -0,0 +1,212 @@ +---- **Core** - F10 Other Menu. +-- +-- **Main Features:** +-- +-- * Add Menus and Commands to the "F10 Other" Menu +-- * Create menus and commands at specific locations within the parent menu +-- * Events when command functions are executed +-- +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.F10Menu +-- @image OPS_F10Menu.png + +--- F10Menu class. +-- @type F10MENU +-- @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 #string text Text of the menu item. +-- @field #table path Path of the menu. +-- @field #F10MENU parent Parent menu or `nil`. +-- @field #table commands Commands within this menu. +-- @field #table submenues Sub menues withing this menu. +-- @extends Core.Fsm#FSM + +--- *In preparing for battle I have always found that plans are useless, but planning is indispensable* -- Dwight D Eisenhower +-- +-- === +-- +-- # The CHIEF Concept +-- +-- +-- @field #F10MENU +F10MENU = { + ClassName = "F10MENU", + verbose = 0, + lid = nil, + commands = {}, + submenues = {}, +} + +--- Command executing a function. +-- @type F10MENU.Command +-- @field #number uid Unique ID. +-- @field #string text Description. +-- @field #function func Function. +-- @field #table args Arguments. +-- @field #table path Path. + +--- F10 menu class version. +-- @field #string version +F10MENU.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructors +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new F10 menu entry. +-- @param #F10MENU self +-- @return #F10MENU self +function F10MENU:_New() + + -- Inherit everything from INTEL class. + local self=BASE:Inherit(self, FSM:New()) --#F10MENU + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "MissionAssign", "*") -- Assign mission to a COMMANDER. + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new F10 menu for all players. +-- @param #F10MENU self +-- @param #string Text Description of menu. +-- @param #F10MENU ParentMenu Parent menu to which this menu is added. If not specified, the menu is added to the root menu. +-- @return #F10MENU self +function F10MENU:NewForAll(Text, ParentMenu) + + -- Inherit everything from INTEL class. + local self=self:_New() + + self.text=Text + + self.parent=ParentMenu + + if self.parent then + self.parent:_AddSubmenu(self) + end + + local path=self.parent and self.parent:GetPath() or nil + + self.path=missionCommands.addSubMenu(self.text, path) + + return self +end + +--- Removes the F10 menu and its entire contents. +-- @param #F10MENU self +-- @return #F10MENU self +function F10MENU:Remove() + + for path,_submenu in pairs(self.submenues) do + local submenu=_submenu --#F10MENU + submenu:Remove() + end + +end + +--- Get path. +-- @param #F10MENU self +-- @return #table Path. +function F10MENU:GetPath() + return self.path +end + +--- Get commands +-- @param #F10MENU self +-- @return #table Path. +function F10MENU:GetCommands() + return self.commands +end + +--- Get submenues. +-- @param #F10MENU self +-- @return #table Path. +function F10MENU:GetSubmenues() + return self.submenues +end + + +--- Add a command for all players. +-- @param #F10MENU self +-- @param #string Text Description. +-- @param #function CommandFunction Function to call. +-- @param ... Function arguments. +-- @return #F10MENU.Command Command. +function F10MENU:AddCommandForAll(Text, CommandFunction, ...) + + local command={} --#F10MENU.Command + command.uid=1 + command.text=Text + command.func=CommandFunction + command.args=... + command.path=missionCommands.addCommand(command.text, self.path, command.func, command.args) + + table.insert(self.commands, command) + + return command +end + +--- Add a command for players of a specific coalition. +-- @param #F10MENU self +-- @return #F10MENU self +function F10MENU:AddCommandForCoalition() + +end + + +--- Add a command for players of a specific group. +-- @param #F10MENU self +-- @return #F10MENU self +function F10MENU:AddCommandForGroup() + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Private functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Add a command for players of a specific group. +-- @param #F10MENU self +-- @param #F10MENU Submenu The submenu to add. +-- @return #F10MENU self +function F10MENU:_AddSubmenu(Submenu) + + self.submenues[Submenu.path]=Submenu + +end + +--- Add a command for players of a specific group. +-- @param #F10MENU self +-- @return #F10MENU self +function F10MENU:_Refresh() + + + + for _,_submenu in pairs(self.submenues) do + local submenu=_submenu --#F10MENU + + + end + +end + diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 7d47cbba1..0f2b6d565 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -29,6 +29,7 @@ __Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spot.lua' ) __Moose.Include( 'Scripts/Moose/Core/Astar.lua' ) __Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' ) +__Moose.Include( 'Scripts/Moose/Core/F10Menu.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' ) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 30bb2f75e..613e53b5a 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -849,7 +849,8 @@ function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, N, Speed, Formation) self:E(self.lid.."Update route denied. Group is STOPPED!") return false elseif self:IsHolding() then - self:E(self.lid.."Update route denied. Group is holding position! Use Cruise()") + self:T(self.lid.."Update route denied. Group is holding position!") + return false end return true end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 10b1b7e2c..a4a85841e 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -46,6 +46,8 @@ -- @field #number Tstart Mission start time in abs. seconds. -- @field #number Tstop Mission stop time in abs. seconds. -- @field #number duration Mission duration in seconds. +-- @field #number durationExe Mission execution time in seconds. +-- @field #number Texecuting Mission time stamp (abs) when it started to execute. Is #nil on start. -- @field #number Tpush Mission push/execute time in abs. seconds. -- @field Wrapper.Marker#MARKER marker F10 map marker. -- @field #boolean markerOn If true, display marker on F10 map with the AUFTRAG status. @@ -2703,7 +2705,7 @@ function AUFTRAG:AddOpsGroup(OpsGroup) return self end ---- Remove an Ops group from the mission. +--- Remove an Ops group from the mission. -- @param #AUFTRAG self -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPSGROUP object. -- @return #AUFTRAG self @@ -3061,9 +3063,18 @@ function AUFTRAG:onafterStatus(From, Event, To) self:Cancel() elseif self.durationExe and self.Texecuting and Tnow-self.Texecuting>self.durationExe then + + -- Backup repeat values + local Nrepeat=self.Nrepeat + local NrepeatS=self.NrepeatSuccess + local NrepeatF=self.NrepeatFailure -- Cancel mission if stop time passed. self:Cancel() + + self.Nrepeat=Nrepeat + self.NrepeatSuccess=NrepeatS + self.NrepeatFailure=NrepeatF elseif (Ntargets0>0 and Ntargets==0) then @@ -3811,6 +3822,9 @@ function AUFTRAG:onafterDone(From, Event, To) -- Set time stamp. self.Tover=timer.getAbsTime() + -- Not executing any more. + self.Texecuting=nil + -- Set status for CHIEF, COMMANDER and LEGIONs self.statusChief=AUFTRAG.Status.DONE self.statusCommander=AUFTRAG.Status.DONE @@ -3983,7 +3997,7 @@ function AUFTRAG:onafterRepeat(From, Event, To) end else - self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, WINGCOMMANDER or AIRWING! Stopping AUFTRAG") + self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, COMMANDER or LEGION! Stopping AUFTRAG") self:Stop() return end @@ -3992,7 +4006,9 @@ function AUFTRAG:onafterRepeat(From, Event, To) -- No mission assets. self.assets={} - for _,_groupdata in pairs(self.groupdata) do + + -- Remove OPS groups. This also removes the mission from the OPSGROUP mission queue. + for groupname,_groupdata in pairs(self.groupdata) do local groupdata=_groupdata --#AUFTRAG.GroupData local opsgroup=groupdata.opsgroup if opsgroup then diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index 9d77f24ed..be530f2e5 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -384,7 +384,7 @@ function BRIGADE:onafterStatus(From, Event, To) local mission=_mission --Ops.Auftrag#AUFTRAG local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end - local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.nassets) + local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.Nassets or 0) local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage()) text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 77adc930b..79a3ef082 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -940,6 +940,9 @@ function NAVYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Depth) elseif self:IsStopped() then self:E(self.lid.."Update route denied. Group is STOPPED!") return false + elseif self:IsHolding() then + self:T(self.lid.."Update route denied. Group is holding position!") + return false end return true end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index a5f0a5ee8..414ddf409 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -684,12 +684,13 @@ function OPSGROUP:New(group) self:AddTransition("*", "Pickup", "*") -- Carrier and is on route to pick up cargo. self:AddTransition("*", "Loading", "*") -- Carrier is loading cargo. self:AddTransition("*", "Load", "*") -- Carrier loads cargo into carrier. - self:AddTransition("*", "Loaded", "*") -- Carrier loaded all assigned/possible cargo into carrier. + self:AddTransition("*", "Loaded", "*") -- Carrier loaded cargo into carrier. + self:AddTransition("*", "LoadingDone", "*") -- Carrier loaded all assigned/possible cargo into carrier. self:AddTransition("*", "Transport", "*") -- Carrier is transporting cargo. self:AddTransition("*", "Unloading", "*") -- Carrier is unloading the cargo. - self:AddTransition("*", "Unload", "*") -- Carrier unload a cargo group. - self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its current cargo. - self:AddTransition("*", "UnloadingDone", "*") -- Carrier is unloading the cargo. + self:AddTransition("*", "Unload", "*") -- Carrier unloads a cargo group. + self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded a cargo group. + self:AddTransition("*", "UnloadingDone", "*") -- Carrier unloaded all its current cargo. self:AddTransition("*", "Delivered", "*") -- Carrier delivered ALL cargo of the transport assignment. self:AddTransition("*", "TransportCancel", "*") -- Cancel (current) transport. @@ -904,7 +905,7 @@ end --- Set default cruise altitude. -- @param #OPSGROUP self --- @param #number Altitude Altitude in feet. Default is 10,000 ft for airplanes and 1,000 feet for helicopters. +-- @param #number Altitude Altitude in feet. Default is 10,000 ft for airplanes and 1,500 feet for helicopters. -- @return #OPSGROUP self function OPSGROUP:SetDefaultAltitude(Altitude) if Altitude then @@ -912,7 +913,7 @@ function OPSGROUP:SetDefaultAltitude(Altitude) else if self:IsFlightgroup() then if self.isHelo then - self.altitudeCruise=UTILS.FeetToMeters(1000) + self.altitudeCruise=UTILS.FeetToMeters(1500) else self.altitudeCruise=UTILS.FeetToMeters(10000) end @@ -6235,7 +6236,7 @@ function OPSGROUP:_CheckCargoTransport() -- Boarding finished ==> Transport cargo. if gotcargo and self.cargoTransport:_CheckRequiredCargos(self.cargoTZC) and not boarding then self:T(self.lid.."Boarding finished ==> Loaded") - self:Loaded() + self:LoadingDone() else -- No cargo and no one is boarding ==> check again if we can make anyone board. self:Loading() @@ -7301,6 +7302,9 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) -- Trigger embarked event for cargo group. CargoGroup:Embarked(self, carrier) + -- Trigger Loaded event. + self:Loaded(CargoGroup) + -- Trigger "Loaded" event for current cargo transport. if self.cargoTransport then CargoGroup:_DelMyLift(self.cargoTransport) @@ -7315,15 +7319,15 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) end ---- On after "Loaded" event. Carrier has loaded all (possible) cargo at the pickup zone. +--- On after "LoadingDone" event. Carrier has loaded all (possible) cargo at the pickup zone. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function OPSGROUP:onafterLoaded(From, Event, To) +function OPSGROUP:onafterLoadingDone(From, Event, To) -- Debug info. - self:T(self.lid.."Carrier Loaded ==> Transport") + self:T(self.lid.."Carrier Loading Done ==> Transport") -- Order group to transport. self:__Transport(1) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index bc0a82dd4..49172b5a9 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1452,8 +1452,20 @@ do -- Cargo ["C-17A"] = 35000, --77519 cannot be used, because it loads way too much apcs and infantry. ["C-130"] = 22000 --The real value cannot be used, because it loads way too much apcs and infantry. } + + local Weight=Weights[Desc.typeName] + + if not Weight then + local fuelrel=self:GetFuel() or 1.0 + local Mmax=Desc.massMax or 0 + local Mempty=Desc.massEmpty or 0 + local Mfuel=Desc.fuelMassMax and Desc.fuelMassMax*fuelrel or 0 + Weight=Mmax-(Mempty+Mfuel) + self:I(string.format("Setting Cargo bay weight limit [%s]=%d kg (Mass max=%d, empty=%d, fuel=%d kg, fuelrel=%.3f)", Desc.typeName or "unknown type", Weight, Mmax, Mempty, Mfuel, fuelrel)) + end - self.__.CargoBayWeightLimit = Weights[Desc.typeName] or ( Desc.massMax - ( Desc.massEmpty + Desc.fuelMassMax ) ) + self.__.CargoBayWeightLimit = Weight + elseif self:IsShip() then local Desc = self:GetDesc() self:F({Desc=Desc}) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 9608179cf..7a1553aff 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -28,6 +28,7 @@ Core/SpawnStatic.lua Core/Timer.lua Core/Goal.lua Core/Spot.lua +Core/F10Menu.lua Wrapper/Object.lua Wrapper/Identifiable.lua From ed6d5f727a8c704a87d9c73179a10bb7e3a2828d Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 20 Oct 2021 19:49:26 +0200 Subject: [PATCH 128/141] OPS --- Moose Development/Moose/Ops/ArmyGroup.lua | 2 +- Moose Development/Moose/Ops/Auftrag.lua | 7 +++++-- Moose Development/Moose/Ops/OpsGroup.lua | 25 +++++++++++++++++------ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 613e53b5a..dfc0e7abf 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1169,7 +1169,7 @@ function ARMYGROUP:onafterRTZ(From, Event, To, Zone, Formation) -- Set if we want to resume route after reaching the detour waypoint. wp.detour=0 - + else self:E(self.lid.."ERROR: No RTZ zone given!") end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index a4a85841e..c23511459 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -102,6 +102,7 @@ -- -- @field #number artyRadius Radius in meters. -- @field #number artyShots Number of shots fired. +-- @field #number artyAltitude Altitude in meters. Can be used for a Barrage. -- -- @field #string alert5MissionType Alert 5 mission type. This is the mission type, the alerted assets will be able to carry out. -- @@ -1549,8 +1550,9 @@ end -- @param Core.Point#COORDINATE Target Center of the firing solution. -- @param #number Nshots Number of shots to be fired. Default 3. -- @param #number Radius Radius of the shells in meters. Default 100 meters. +-- @param #number Altitude Altitude in meters. Can be used to setup a Barrage. Default `#nil`. -- @return #AUFTRAG self -function AUFTRAG:NewARTY(Target, Nshots, Radius) +function AUFTRAG:NewARTY(Target, Nshots, Radius, Altitude) local mission=AUFTRAG:New(AUFTRAG.Type.ARTY) @@ -1558,6 +1560,7 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius) mission.artyShots=Nshots or 3 mission.artyRadius=Radius or 100 + mission.artyAltitude=Altitude mission.engageWeaponType=ENUMS.WeaponFlag.Auto @@ -4721,7 +4724,7 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) -- ARTY Mission -- ------------------ - local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, self:GetTargetVec2(), self.artyRadius, self.artyShots, self.engageWeaponType) + local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, self:GetTargetVec2(), self.artyRadius, self.artyShots, self.engageWeaponType, self.artyAltitude) table.insert(DCStasks, DCStask) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 414ddf409..48a360302 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4414,7 +4414,7 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) -- We add a 10 sec delay for ARTY. Found that they need some time to readjust the barrel of their gun. Not sure if necessary for all. Needs some more testing! local delay=1 if Mission.type==AUFTRAG.Type.ARTY then - delay=10 + delay=60 end -- Check if group is done. @@ -8427,16 +8427,17 @@ function OPSGROUP:_CheckStuck() self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec", speed, ExpectedSpeed, holdtime)) -- Give cruise command again. - self:__Cruise(1) + if self:IsReturning() then + self:__RTZ(1) + else + self:__Cruise(1) + end - elseif holdtime>=10*60 then + elseif holdtime>=10*60 and holdtime<30*60 then -- Debug warning. self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec", speed, ExpectedSpeed, holdtime)) - -- Give cruise command again. - self:__Cruise(1) - --TODO: Stuck event! -- Look for a current mission and cancel it as we do not seem to be able to perform it. @@ -8444,7 +8445,19 @@ function OPSGROUP:_CheckStuck() if mission then self:E(self.lid..string.format("WARNING: Cancelling mission %s [%s] due to being stuck", mission:GetName(), mission:GetType())) self:MissionCancel(mission) + else + -- Give cruise command again. + if self:IsReturning() then + self:__RTZ(1) + else + self:__Cruise(1) + end end + + elseif holdtime>=30*60 then + + -- Debug warning. + self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec", speed, ExpectedSpeed, holdtime)) end From 60094329332733543541b67f607e14d82f166c3a Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 22 Oct 2021 11:23:29 +0200 Subject: [PATCH 129/141] OPS - NAVYGROUP added engage target - Barrage improvments --- Moose Development/Moose/Ops/ArmyGroup.lua | 36 +++-- Moose Development/Moose/Ops/Auftrag.lua | 75 ++++++++- Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/NavyGroup.lua | 152 +++++++++++++++++- Moose Development/Moose/Ops/OpsGroup.lua | 36 +++-- .../Moose/Wrapper/Controllable.lua | 6 +- 6 files changed, 272 insertions(+), 35 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index dfc0e7abf..fdb9fda9f 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1046,9 +1046,9 @@ function ARMYGROUP:onafterOutOfAmmo(From, Event, To) -- Fist, check if we want to rearm once out-of-ammo. if self.rearmOnOutOfAmmo then - local truck=self:FindNearestAmmoSupply(30) + local truck, dist=self:FindNearestAmmoSupply(30) if truck then - self:T(self.lid..string.format("Found Ammo Truck %s [%s]")) + self:T(self.lid..string.format("Found Ammo Truck %s [%s]", truck:GetName(), truck:GetTypeName())) local Coordinate=truck:GetCoordinate() self:Rearm(Coordinate, Formation) return @@ -1159,16 +1159,22 @@ function ARMYGROUP:onafterRTZ(From, Event, To, Zone, Formation) if zone then - -- Debug info. - self:I(self.lid..string.format("RTZ to Zone %s", zone:GetName())) - - local Coordinate=zone:GetRandomCoordinate() - - -- Add waypoint after current. - local wp=self:AddWaypoint(Coordinate, nil, uid, Formation, true) - - -- Set if we want to resume route after reaching the detour waypoint. - wp.detour=0 + if self:IsInZone(zone) then + self:Returned() + else + + -- Debug info. + self:I(self.lid..string.format("RTZ to Zone %s", zone:GetName())) + + local Coordinate=zone:GetRandomCoordinate() + + -- Add waypoint after current. + local wp=self:AddWaypoint(Coordinate, nil, uid, Formation, true) + + -- Set if we want to resume route after reaching the detour waypoint. + wp.detour=0 + + end else self:E(self.lid.."ERROR: No RTZ zone given!") @@ -1684,7 +1690,7 @@ function ARMYGROUP:FindNearestAmmoSupply(Radius) local unit=_unit --Wrapper.Unit#UNIT -- Check coaliton and if unit can supply ammo. - if unit:GetCoalition()==myCoalition and unit:IsAmmoSupply() then + if unit:IsAlive() and unit:GetCoalition()==myCoalition and unit:IsAmmoSupply() and unit:GetVelocityKMH()<1 then -- Distance. local d=coord:Get2DDistance(unit:GetCoord()) @@ -1693,8 +1699,10 @@ function ARMYGROUP:FindNearestAmmoSupply(Radius) if d%d[%d] /%d [%s] Dist=%.1f NM ETA=%s - Speed=%.1f (%.1f) kts, Depth=%.1f m, Hdg=%03d, Turn=%s Collision=%d IntoWind=%s", - fsmstate, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, wpN, wpF, wpDist, wpETA, speed, speedExpected, alt, self.heading, turning, freepath, intowind) + fsmstate, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, wpN, wpF, wpDist, wpETA, speed, speedExpected, alt, self.heading or 0, turning, freepath, intowind) self:I(self.lid..text) end @@ -809,6 +820,21 @@ function NAVYGROUP:Status(From, Event, To) end + --- + -- Engage Detected Targets + --- + if self:IsCruising() and self.detectionOn and self.engagedetectedOn then + + local targetgroup, targetdist=self:_GetDetectedTarget() + + -- If we found a group, we engage it. + if targetgroup then + self:I(self.lid..string.format("Engaging target group %s at distance %d meters", targetgroup:GetName(), targetdist)) + self:EngageTarget(targetgroup) + end + + end + --- -- Cargo --- @@ -973,6 +999,8 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Depth) -- Waypoint. local wp=UTILS.DeepCopy(self.waypoints[i]) --Ops.OpsGroup#OPSGROUP.Waypoint + + --env.info(string.format("FF i=%d UID=%d n=%d, N=%d", i, wp.uid, n, N)) -- Speed. if Speed then @@ -1012,9 +1040,17 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Depth) if self:IsEngaging() or not self.passedfinalwp then + + --[[ + env.info("FF:") + for i=2,#waypoints do + local wp=waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint + self:I(self.lid..string.format("[%d] UID=%d", i-1, wp.uid)) + end + ]] -- Debug info. - self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Depth=%d m", self.currentwp, n, #waypoints, #self.waypoints, UTILS.MpsToKnots(self.speedWp), self.altWp)) + self:I(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Depth=%d m", self.currentwp, n, #waypoints, #self.waypoints, UTILS.MpsToKnots(self.speedWp), self.altWp)) -- Route group to all defined waypoints remaining. self:Route(waypoints) @@ -1311,6 +1347,114 @@ function NAVYGROUP:onafterCollisionWarning(From, Event, To, Distance) self.collisionwarning=true end +--- On after "EngageTarget" event. +-- @param #NAVYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Group#GROUP Group the group to be engaged. +function NAVYGROUP:onafterEngageTarget(From, Event, To, Target) + self:T(self.lid.."Engaging Target") + + if Target:IsInstanceOf("TARGET") then + self.engage.Target=Target + else + self.engage.Target=TARGET:New(Target) + end + + -- Target coordinate. + self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) + + + local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9) + + + + -- Backup ROE and alarm state. + self.engage.roe=self:GetROE() + self.engage.alarmstate=self:GetAlarmstate() + + -- Switch ROE and alarm state. + self:SwitchAlarmstate(ENUMS.AlarmState.Auto) + self:SwitchROE(ENUMS.ROE.OpenFire) + + -- ID of current waypoint. + local uid=self:GetWaypointCurrent().uid + + -- Add waypoint after current. + self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true) + + -- Set if we want to resume route after reaching the detour waypoint. + self.engage.Waypoint.detour=1 + +end + +--- Update engage target. +-- @param #NAVYGROUP self +function NAVYGROUP:_UpdateEngageTarget() + + if self.engage.Target and self.engage.Target:IsAlive() then + + -- Get current position vector. + local vec3=self.engage.Target:GetVec3() + + -- Distance to last known position of target. + local dist=UTILS.VecDist3D(vec3, self.engage.Coordinate:GetVec3()) + + -- Check if target moved more than 100 meters. + if dist>100 then + + --env.info("FF Update Engage Target Moved "..self.engage.Target:GetName()) + + -- Update new position. + self.engage.Coordinate:UpdateFromVec3(vec3) + + -- ID of current waypoint. + local uid=self:GetWaypointCurrent().uid + + -- Remove current waypoint + self:RemoveWaypointByID(self.engage.Waypoint.uid) + + local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9) + + -- Add waypoint after current. + self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true) + + -- Set if we want to resume route after reaching the detour waypoint. + self.engage.Waypoint.detour=0 + + end + + else + + -- Target not alive any more == Disengage. + self:Disengage() + + end + +end + +--- On after "Disengage" event. +-- @param #NAVYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function NAVYGROUP:onafterDisengage(From, Event, To) + self:T(self.lid.."Disengage Target") + + -- Restore previous ROE and alarm state. + self:SwitchROE(self.engage.roe) + self:SwitchAlarmstate(self.engage.alarmstate) + + -- Remove current waypoint + if self.engage.Waypoint then + self:RemoveWaypointByID(self.engage.Waypoint.uid) + end + + -- Check group is done + self:_CheckGroupDone(1) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 48a360302..02c70076b 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3663,20 +3663,34 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Just stay put. --TODO: Change ALARM STATE - else -- If task is scheduled (not waypoint) set task. if Task.type==OPSGROUP.TaskType.SCHEDULED or Task.ismission then + local DCSTask=nil --UTILS.DeepCopy(Task.dcstask) + + -- BARRAGE is special! + if Task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then + --env.info("FF Barrage") + local vec2=self:GetVec2() + local param=Task.dcstask.params + local heading=param.heading or math.random(1, 360) + local distance=param.distance or 100 + local tvec2=UTILS.Vec2Translate(vec2, distance, heading) + DCSTask=CONTROLLABLE.TaskFireAtPoint(nil, tvec2, param.radius, param.shots, param.weaponType, param.altitude) + else + DCSTask=Task.dcstask + end + local DCStasks={} - if Task.dcstask.id=='ComboTask' then + if DCSTask.id=='ComboTask' then -- Loop over all combo tasks. - for TaskID, Task in ipairs(Task.dcstask.params.tasks) do + for TaskID, Task in ipairs(DCSTask.params.tasks) do table.insert(DCStasks, Task) end else - table.insert(DCStasks, Task.dcstask) + table.insert(DCStasks, DCSTask) end -- Combo task. @@ -3853,7 +3867,7 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) if self.currentmission and self.currentmission==Mission.auftragsnummer then self.currentmission=nil end - env.info("Remove mission waypoints") + self:T(self.lid.."Remove mission waypoints") self:_RemoveMissionWaypoints(Mission, false) end @@ -4474,7 +4488,7 @@ function OPSGROUP:RouteToMission(mission, delay) end -- Get ingress waypoint. - if mission.type==AUFTRAG.Type.PATROLZONE then + if mission.type==AUFTRAG.Type.PATROLZONE or mission.type==AUFTRAG.Type.BARRAGE then local zone=mission.engageTarget:GetObject() --Core.Zone#ZONE waypointcoord=zone:GetRandomCoordinate(nil , nil, surfacetypes) else @@ -5041,6 +5055,8 @@ end -- @param #number UID The goto waypoint unique ID. -- @param #number Speed (Optional) Speed to waypoint in knots. function OPSGROUP:onafterGotoWaypoint(From, Event, To, UID, Speed) + + --env.info("FF goto waypoint uid="..tostring(UID)) local n=self:GetWaypointIndex(UID) @@ -5050,10 +5066,10 @@ function OPSGROUP:onafterGotoWaypoint(From, Event, To, UID, Speed) Speed=Speed or self:GetSpeedToWaypoint(n) -- Debug message - self:T(self.lid..string.format("Goto Waypoint UID=%d index=%d from %d at speed %.1f knots", UID, n, self.currentwp, Speed)) + self:I(self.lid..string.format("Goto Waypoint UID=%d index=%d from %d at speed %.1f knots", UID, n, self.currentwp, Speed)) -- Update the route. - self:__UpdateRoute(-0.01, n, nil, Speed) + self:__UpdateRoute(0.1, n, nil, Speed) end @@ -8388,7 +8404,7 @@ end function OPSGROUP:_CheckStuck() -- Cases we are not stuck. - if self:IsHolding() or self:Is("Rearming") or self:IsWaiting() then + if self:IsHolding() or self:Is("Rearming") or self:IsWaiting() or self:HasPassedFinalWaypoint() then return end @@ -9764,7 +9780,7 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) Modulation=Modulation or self.radioDefault.Modu if self:IsFlightgroup() and not self.radio.On then - env.info("FF radio OFF") + --env.info("FF radio OFF") self.group:SetOption(AI.Option.Air.id.SILENCE, false) end diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 428b931a2..ab5f35250 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1437,8 +1437,8 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti id = 'FireAtPoint', params = { point = Vec2, - x=Vec2.x, - y=Vec2.y, + x = Vec2.x, + y = Vec2.y, zoneRadius = Radius, radius = Radius, expendQty = 100, -- dummy value @@ -1460,7 +1460,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti DCSTask.params.weaponType=WeaponType end - --self:I(DCSTask) + --BASE:I(DCSTask) return DCSTask end From 02b0ba2278b71f12d0ee91de64413b1be95d90f8 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 25 Oct 2021 22:17:56 +0200 Subject: [PATCH 130/141] OPS - Improved behaviour of OPSTRANSPORT when cargo or carrier is on mission. --- Moose Development/Moose/Ops/Auftrag.lua | 10 +++ Moose Development/Moose/Ops/OpsGroup.lua | 80 ++++++++++-------------- 2 files changed, 42 insertions(+), 48 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index ba8683097..61fb8aa4b 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -137,6 +137,7 @@ -- @field #number missionRange Mission range in meters. Used by LEGION classes (AIRWING, BRIGADE, ...). -- @field Core.Point#COORDINATE missionWaypointCoord Mission waypoint coordinate. -- @field Core.Point#COORDINATE missionEgressCoord Mission egress waypoint coordinate. +-- @field #number missionWaypointRadius Random radius in meters. -- -- @field #table enrouteTasks Mission enroute tasks. -- @@ -4415,6 +4416,15 @@ function AUFTRAG:SetMissionWaypointCoord(Coordinate) return self end +--- Set randomization of the mission waypoint coordinate. Each assigned group will get a random ingress coordinate, where the mission is executed. +-- @param #AUFTRAG self +-- @param #number Radius Distance in meters. Default `#nil`. +-- @return #AUFTRAG self +function AUFTRAG:SetMissionWaypointRandomization(Radius) + self.missionWaypointRadius=Radius + return self +end + --- Set the mission egress coordinate. This is the coordinate where the assigned group will go once the mission is finished. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Egrees coordinate. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 02c70076b..7596646bc 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4134,6 +4134,13 @@ function OPSGROUP:GetMissionCurrent() return self:GetMissionByID(self.currentmission) end +--- Check if group is currently on a mission. +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is currently on a mission +function OPSGROUP:IsOnMission() + return self.currentmission~=nil +end + --- On before "MissionStart" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -4477,8 +4484,8 @@ function OPSGROUP:RouteToMission(mission, delay) local waypointcoord=nil --Core.Point#COORDINATE -- Random radius of 1000 meters. - local randomradius=1000 - + local randomradius=mission.missionWaypointRadius or 1000 + -- Surface types. local surfacetypes=nil if self:IsArmygroup() then @@ -4491,6 +4498,8 @@ function OPSGROUP:RouteToMission(mission, delay) if mission.type==AUFTRAG.Type.PATROLZONE or mission.type==AUFTRAG.Type.BARRAGE then local zone=mission.engageTarget:GetObject() --Core.Zone#ZONE waypointcoord=zone:GetRandomCoordinate(nil , nil, surfacetypes) + elseif mission.type==AUFTRAG.Type.ONGUARD then + waypointcoord=mission:GetMissionWaypointCoord(self.group, nil, surfacetypes) else waypointcoord=mission:GetMissionWaypointCoord(self.group, randomradius, surfacetypes) end @@ -6175,7 +6184,7 @@ function OPSGROUP:_CheckCargoTransport() end -- Check if there is anything in the queue. - if not self.cargoTransport then + if not self.cargoTransport and not self:IsOnMission() then self.cargoTransport=self:_GetNextCargoTransport() if self.cargoTransport and not self:IsActive() then self:Activate() @@ -7156,13 +7165,21 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Loading time stamp. self.Tloading=timer.getAbsTime() - -- Cargo group table. - --local cargos=self.cargoTZC.Cargos - + -- Get valid cargos of the TZC. local cargos={} for _,_cargo in pairs(self.cargoTZC.Cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - if self:CanCargo(cargo.opsgroup) and (not (cargo.delivered or cargo.opsgroup:IsDead())) then + + local isCarrier=cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading() + + local isOnMission=cargo.opsgroup:IsOnMission() + + -- Check if cargo is in embark/pickup zone. + -- Added InUtero here, if embark zone is moving (ship) and cargo has been spawned late activated and its position is not updated. Not sure if that breaks something else! + local inzone=cargo.opsgroup:IsInZone(self.cargoTZC.EmbarkZone) --or cargo.opsgroup:IsInUtero() + + -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() + if self:CanCargo(cargo.opsgroup) and inzone and cargo.opsgroup:IsNotCargo(true) and (not (cargo.delivered or cargo.opsgroup:IsDead() or isCarrier or isOnMission)) then table.insert(cargos, cargo) end end @@ -7179,53 +7196,20 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - -- Check that cargo weight is - if self:CanCargo(cargo.opsgroup) and (not (cargo.delivered or cargo.opsgroup:IsDead())) then - - -- Check if cargo is currently acting as carrier. - local isCarrier=cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading() + -- Find a carrier for this cargo. + local carrier=self:FindCarrierForCargo(cargo.opsgroup) - -- Check that group is NOT cargo and NOT acting as carrier already - -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() - if cargo.opsgroup:IsNotCargo(true) and not isCarrier then + if carrier then - -- Check if cargo is in embark/pickup zone. - -- Added InUtero here, if embark zone is moving (ship) and cargo has been spawned late activated and its position is not updated. Not sure if that breaks something else! - local inzone=cargo.opsgroup:IsInZone(self.cargoTZC.EmbarkZone) --or cargo.opsgroup:IsInUtero() + -- Set cargo status. + cargo.opsgroup:_NewCargoStatus(OPSGROUP.CargoStatus.ASSIGNED) - -- Cargo MUST be inside zone or it will not be loaded! - if inzone then - - -- Find a carrier for this cargo. - local carrier=self:FindCarrierForCargo(cargo.opsgroup) - - if carrier then - - -- Set cargo status. - cargo.opsgroup:_NewCargoStatus(OPSGROUP.CargoStatus.ASSIGNED) - - -- Order cargo group to board the carrier. - cargo.opsgroup:Board(self, carrier) - - else - -- Debug info. - self:T(self.lid..string.format("Cannot board carrier! Group %s is NOT (yet) in zone %s", cargo.opsgroup:GetName(), self.cargoTZC.EmbarkZone:GetName())) - end - - else - -- Debug info. - self:T(self.lid..string.format("Cargo %s NOT in embark zone %s (and not InUTERO)", cargo.opsgroup:GetName(), self.cargoTZC.EmbarkZone:GetName())) - end - - end - - else - -- Debug info. - self:T3(self.lid.."Cargo already delivered, is dead or carrier cannot") + -- Order cargo group to board the carrier. + cargo.opsgroup:Board(self, carrier) + end end - end --- Set (new) cargo status. From 902c001aa4c28bc4087d18c85f8ec5cc9acb8a73 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 28 Oct 2021 10:04:01 +0200 Subject: [PATCH 131/141] OPS Improved OPSTRANSPORT --- Moose Development/Moose/DCS.lua | 5 + Moose Development/Moose/Ops/FlightGroup.lua | 63 ++++++++++- Moose Development/Moose/Ops/NavyGroup.lua | 5 +- Moose Development/Moose/Ops/OpsGroup.lua | 36 ++++-- Moose Development/Moose/Ops/OpsTransport.lua | 7 +- .../Moose/Wrapper/Identifiable.lua | 26 ++--- .../Moose/Wrapper/Positionable.lua | 105 ++++++++++++++---- 7 files changed, 184 insertions(+), 63 deletions(-) diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 7c9328cc9..24d7862d0 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -1150,6 +1150,11 @@ do -- Unit -- @function [parent=#Unit] getAmmo -- @param #Unit self -- @return #Unit.Ammo + + --- Returns the number of infantry that can be embark onto the aircraft. Only returns a value if run on airplanes or helicopters. Returns nil if run on ground or ship units. + -- @function [parent=#Unit] getDescentCapacity + -- @param #Unit self + -- @return #number Number of soldiers that embark. --- Returns the unit sensors. -- @function [parent=#Unit] getSensors diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 877457b99..65aa7c3f5 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2248,7 +2248,7 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) if not self.group:IsAirborne(true) then -- this should really not happen, either the AUFTRAG is cancelled before the group was airborne or it is stuck at the ground for some reason - self:I(self.lid..string.format("WARNING: Group is not AIRBORNE ==> RTB event is suspended for 20 sec")) + self:I(self.lid..string.format("WARNING: Group [%s] is not AIRBORNE ==> RTB event is suspended for 20 sec", self:GetState())) allowed=false Tsuspend=-20 local groupspeed = self.group:GetVelocityMPS() @@ -2256,7 +2256,7 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) self.RTBRecallCount = self.RTBRecallCount+1 end if self.RTBRecallCount>6 then - self:I(self.lid..string.format("WARNING: Group is not moving and was called RTB %d times. Assuming a problem and despawning!", self.RTBRecallCount)) + self:I(self.lid..string.format("WARNING: Group [%s] is not moving and was called RTB %d times. Assuming a problem and despawning!", self:GetState(), self.RTBRecallCount)) self.RTBRecallCount=0 self:Despawn(5) return @@ -2342,6 +2342,65 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp end + +--- On before "LandAtAirbase" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Airbase#AIRBASE airbase The airbase to hold at. +function FLIGHTGROUP:onbeforeLandAtAirbase(From, Event, To, airbase) + + if self:IsAlive() then + + local allowed=true + local Tsuspend=nil + + if airbase==nil then + self:E(self.lid.."ERROR: Airbase is nil in LandAtAirase() call!") + allowed=false + end + + -- Check that coaliton is okay. We allow same (blue=blue, red=red) or landing on neutral bases. + if airbase and airbase:GetCoalition()~=self.group:GetCoalition() and airbase:GetCoalition()>0 then + self:E(self.lid..string.format("ERROR: Wrong airbase coalition %d in LandAtAirbase() call! We allow only same as group %d or neutral airbases 0", airbase:GetCoalition(), self.group:GetCoalition())) + return false + end + + if self.currbase and self.currbase:GetName()==airbase:GetName() then + self:E(self.lid.."WARNING: Currbase is already same as LandAtAirbase airbase. LandAtAirbase canceled!") + return false + end + + -- Check if the group has landed at an airbase. If so, we lost control and RTBing is not possible (only after a respawn). + if self:IsLanded() then + self:E(self.lid.."WARNING: Flight has already landed. LandAtAirbase canceled!") + return false + end + + if self:IsParking() then + allowed=false + Tsuspend=-30 + self:E(self.lid.."WARNING: Flight is parking. LandAtAirbase call delayed by 30 sec") + elseif self:IsTaxiing() then + allowed=false + Tsuspend=-1 + self:E(self.lid.."WARNING: Flight is parking. LandAtAirbase call delayed by 1 sec") + end + + if Tsuspend and not allowed then + self:__LandAtAirbase(Tsuspend, airbase) + end + + return allowed + else + self:E(self.lid.."WARNING: Group is not alive! LandAtAirbase call not allowed") + return false + end + +end + + --- On after "LandAtAirbase" event. -- @param #FLIGHTGROUP self -- @param #string From From state. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index fee9c4a9f..d76cd10b4 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1462,7 +1462,7 @@ end --- Add an a waypoint to the route. -- @param #NAVYGROUP self --- @param Core.Point#COORDINATE Coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. +-- @param Core.Point#COORDINATE Coordinate The coordinate of the waypoint. Use `COORDINATE:SetAltitude()` to define the altitude. -- @param #number Speed Speed in knots. Default is default cruise speed or 70% of max speed. -- @param #number AfterWaypointWithID Insert waypoint after waypoint given ID. Default is to insert as last waypoint. -- @param #number Depth Depth at waypoint in meters. Only for submarines. @@ -1514,9 +1514,6 @@ function NAVYGROUP:_InitGroup(Template) -- Get template of group. local template=Template or self:_GetTemplate() - --TODO: Submarine check - --self.isSubmarine=self.group:IsSubmarine() - -- Ships are always AI. self.isAI=true diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 7596646bc..f93519b40 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6846,7 +6846,7 @@ function OPSGROUP:AddWeightCargo(UnitName, Weight) -- For airborne units, we set the weight in game. if self.isFlightgroup then - --trigger.action.setUnitInternalCargo(element.name, element.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + trigger.action.setUnitInternalCargo(element.name, element.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo end end @@ -7515,10 +7515,18 @@ function OPSGROUP:onafterUnloading(From, Event, To) if cargo.opsgroup:IsLoaded(self.groupname) and not cargo.opsgroup:IsDead() then -- Disembark to carrier. - local needscarrier=false - local carrier=nil - local carrierGroup=nil + local needscarrier=false --#boolean + local carrier=nil --Ops.OpsGroup#OPSGROUP.Element + local carrierGroup=nil --Ops.OpsGroup#OPSGROUP + -- Try to get the OPSGROUP if deploy zone is a ship. + if zone and zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then + local shipname=zone:GetAirbase():GetName() + local ship=UNIT:FindByName(shipname) + local group=ship:GetGroup() + carrierGroup=_DATABASE:GetOpsGroup(group:GetName()) + carrier=carrierGroup:GetElementByName(shipname) + end if self.cargoTZC.DisembarkCarriers and #self.cargoTZC.DisembarkCarriers>0 then @@ -7554,8 +7562,10 @@ function OPSGROUP:onafterUnloading(From, Event, To) -- Issue warning. self:E(self.lid.."ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") - --TODO: Dumb into sea. - + + -- Unload but keep "in utero" (no coordinate provided). + self:Unload(cargo.opsgroup) + else --- @@ -7800,9 +7810,6 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Check if this was the current transport. if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then - -- This is not a carrier anymore. - self:_NewCarrierStatus(OPSGROUP.CarrierStatus.NOTCARRIER) - -- Checks if self:IsPickingup() then -- Delete pickup waypoint? @@ -7810,6 +7817,8 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) if wpindex then self:RemoveWaypoint(wpindex) end + -- Remove landing airbase. + self.isLandingAtAirbase=nil elseif self:IsLoading() then -- Nothing to do? elseif self:IsTransporting() then @@ -7818,6 +7827,9 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Nothing to do? end + -- This is not a carrier anymore. + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.NOTCARRIER) + -- Startup uncontrolled aircraft to allow it to go back. if self:IsFlightgroup() then @@ -7939,7 +7951,7 @@ function OPSGROUP:onafterTransportCancel(From, Event, To, Transport) -- Transport delivered. if calldelivered then - self:Delivered(Transport) + self:__Delivered(-2, Transport) end else @@ -9067,7 +9079,7 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) -- Land at current pos and wait for 60 min max. local coordinate=nil if opsgroup.cargoTZC then - coordinate=opsgroup.cargoTZC.PickupZone:GetCoordinate() + coordinate=opsgroup.cargoTZC.PickupZone:GetRandomCoordinate(nil, nil, {land.SurfaceType.LAND}) else coordinate=opsgroup:GetCoordinate() end @@ -9088,7 +9100,7 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) -- Land at current pos and wait for 60 min max. local coordinate=nil if opsgroup.cargoTZC then - coordinate=opsgroup.cargoTZC.DeployZone:GetCoordinate() + coordinate=opsgroup.cargoTZC.DeployZone:GetRandomCoordinate(nil, nil, {land.SurfaceType.LAND}) else coordinate=opsgroup:GetCoordinate() end diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index ec7f9ac44..d515ae44b 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -385,13 +385,11 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) --- Triggers the FSM event "Cancel". -- @function [parent=#OPSTRANSPORT] Cancel -- @param #OPSTRANSPORT self - -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. --- Triggers the FSM event "Cancel" after a delay. -- @function [parent=#OPSTRANSPORT] __Cancel -- @param #OPSTRANSPORT self -- @param #number delay Delay in seconds. - -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. --- On after "Cancel" event. -- @function [parent=#OPSTRANSPORT] OnAfterCancel @@ -399,7 +397,6 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param Ops.OpsTransport#OPSTRANSPORT Transport The transport. --- Triggers the FSM event "Loaded". @@ -1789,7 +1786,7 @@ function OPSTRANSPORT:onafterCancel(From, Event, To) local Ngroups = #self.carriers -- Debug info. - self:I(self.lid..string.format("CANCELLING transport in status %s. Will wait for %d carrier groups to report DONE before evaluation", self.status, Ngroups)) + self:I(self.lid..string.format("CANCELLING transport in status %s. Will wait for %d carrier groups to report DONE before evaluation", self:GetState(), Ngroups)) -- Time stamp. self.Tover=timer.getAbsTime() @@ -1848,7 +1845,7 @@ function OPSTRANSPORT:onafterCancel(From, Event, To) -- Special mission states. if self:IsPlanned() or self:IsQueued() or self:IsRequested() or Ngroups==0 then - self:T(self.lid..string.format("Cancelled transport was in %s stage with %d carrier groups assigned and alive. Call it DELIVERED!", self.status, Ngroups)) + self:T(self.lid..string.format("Cancelled transport was in %s stage with %d carrier groups assigned and alive. Call it DELIVERED!", self:GetState(), Ngroups)) self:Delivered() end diff --git a/Moose Development/Moose/Wrapper/Identifiable.lua b/Moose Development/Moose/Wrapper/Identifiable.lua index 5b340aec8..4f3fabeae 100644 --- a/Moose Development/Moose/Wrapper/Identifiable.lua +++ b/Moose Development/Moose/Wrapper/Identifiable.lua @@ -56,8 +56,7 @@ end -- If the Identifiable is not alive, nil is returned. -- If the Identifiable is alive, true is returned. -- @param #IDENTIFIABLE self --- @return #boolean true if Identifiable is alive. --- @return #nil if the Identifiable is not existing or is not alive. +-- @return #boolean true if Identifiable is alive or `#nil` if the Identifiable is not existing or is not alive. function IDENTIFIABLE:IsAlive() self:F3( self.IdentifiableName ) @@ -77,11 +76,8 @@ end --- Returns DCS Identifiable object name. -- The function provides access to non-activated objects too. -- @param #IDENTIFIABLE self --- @return #string The name of the DCS Identifiable. --- @return #nil The DCS Identifiable is not existing or alive. +-- @return #string The name of the DCS Identifiable or `#nil`. function IDENTIFIABLE:GetName() - self:F2( self.IdentifiableName ) - local IdentifiableName = self.IdentifiableName return IdentifiableName end @@ -148,8 +144,7 @@ end --- Returns coalition of the Identifiable. -- @param #IDENTIFIABLE self --- @return DCS#coalition.side The side of the coalition. --- @return #nil The DCS Identifiable is not existing or alive. +-- @return DCS#coalition.side The side of the coalition or `#nil` The DCS Identifiable is not existing or alive. function IDENTIFIABLE:GetCoalition() self:F2( self.IdentifiableName ) @@ -190,8 +185,7 @@ end --- Returns country of the Identifiable. -- @param #IDENTIFIABLE self --- @return DCS#country.id The country identifier. --- @return #nil The DCS Identifiable is not existing or alive. +-- @return DCS#country.id The country identifier or `#nil` The DCS Identifiable is not existing or alive. function IDENTIFIABLE:GetCountry() self:F2( self.IdentifiableName ) @@ -222,8 +216,7 @@ end --- Returns Identifiable descriptor. Descriptor type depends on Identifiable category. -- @param #IDENTIFIABLE self --- @return DCS#Object.Desc The Identifiable descriptor. --- @return #nil The DCS Identifiable is not existing or alive. +-- @return DCS#Object.Desc The Identifiable descriptor or `#nil` The DCS Identifiable is not existing or alive. function IDENTIFIABLE:GetDesc() self:F2( self.IdentifiableName ) @@ -242,8 +235,7 @@ end --- Check if the Object has the attribute. -- @param #IDENTIFIABLE self -- @param #string AttributeName The attribute name. --- @return #boolean true if the attribute exists. --- @return #nil The DCS Identifiable is not existing or alive. +-- @return #boolean true if the attribute exists or `#nil` The DCS Identifiable is not existing or alive. function IDENTIFIABLE:HasAttribute( AttributeName ) self:F2( self.IdentifiableName ) @@ -266,8 +258,10 @@ function IDENTIFIABLE:GetCallsign() return '' end - +--- Gets the threat level. +-- @param #IDENTIFIABLE self +-- @return #number Threat level. +-- @return #string Type. function IDENTIFIABLE:GetThreatLevel() - return 0, "Scenery" end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 49172b5a9..6dc5ddf7b 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1368,8 +1368,6 @@ do -- Cargo return self.__.Cargo end - - --- Remove cargo. -- @param #POSITIONABLE self -- @param Core.Cargo#CARGO Cargo @@ -1414,6 +1412,16 @@ do -- Cargo return ItemCount end + --- Get the number of infantry soldiers that can be embarked into an aircraft (airplane or helicopter). + -- Returns `nil` for ground or ship units. + -- @param #POSITIONABLE self + -- @return #number Descent number of soldiers that fit into the unit. Returns `#nil` for ground and ship units. + function POSITIONABLE:GetTroopCapacity() + local DCSunit=self:GetDCSObject() --DCS#Unit + local capacity=DCSunit:getDescentCapacity() + return capacity + end + --- Get Cargo Bay Free Weight in kg. -- @param #POSITIONABLE self -- @return #number CargoBayFreeWeight @@ -1433,43 +1441,78 @@ do -- Cargo --- Set Cargo Bay Weight Limit in kg. -- @param #POSITIONABLE self - -- @param #number WeightLimit + -- @param #number WeightLimit (Optional) Weight limit in kg. If not given, the value is taken from the descriptors or hard coded. function POSITIONABLE:SetCargoBayWeightLimit( WeightLimit ) - if WeightLimit then + if WeightLimit then + --- + -- User defined value + --- self.__.CargoBayWeightLimit = WeightLimit elseif self.__.CargoBayWeightLimit~=nil then -- Value already set ==> Do nothing! else - -- If weightlimit is not provided, we will calculate it depending on the type of unit. + --- + -- Weightlimit is not provided, we will calculate it depending on the type of unit. + --- + + -- Descriptors that contain the type name and for aircraft also weights. + local Desc = self:GetDesc() + self:F({Desc=Desc}) + + -- Unit type name. + local TypeName=Desc.typeName or "Unknown Type" -- When an airplane or helicopter, we calculate the weightlimit based on the descriptor. if self:IsAir() then - local Desc = self:GetDesc() - self:F({Desc=Desc}) + -- Max takeoff weight if DCS descriptors have unrealstic values. local Weights = { - ["C-17A"] = 35000, --77519 cannot be used, because it loads way too much apcs and infantry. - ["C-130"] = 22000 --The real value cannot be used, because it loads way too much apcs and infantry. + -- C-17A + -- Wiki says: max=265,352, empty=128,140, payload=77,516 (134 troops, 1 M1 Abrams tank, 2 M2 Bradley or 3 Stryker) + -- DCS says: max=265,350, empty=125,645, fuel=132,405 ==> Cargo Bay=7300 kg with a full fuel load (lot of fuel!) and 73300 with half a fuel load. + --["C-17A"] = 35000, --77519 cannot be used, because it loads way too much apcs and infantry. + -- C-130: + -- DCS says: max=79,380, empty=36,400, fuel=10,415 kg ==> Cargo Bay=32,565 kg with fuel load. + -- Wiki says: max=70,307, empty=34,382, payload=19,000 kg (92 passengers, 2-3 Humvees or 2 M113s), max takeoff weight 70,037 kg. + -- Here we say two M113s should be transported. Each one weights 11,253 kg according to DCS. So the cargo weight should be 23,000 kg with a full load of fuel. + -- This results in a max takeoff weight of 69,815 kg (23,000+10,415+36,400), which is very close to the Wiki value of 70,037 kg. + ["C-130"] = 70000, } - local Weight=Weights[Desc.typeName] + -- Max (takeoff) weight (empty+fuel+cargo weight). + local massMax= Desc.massMax or 0 - if not Weight then - local fuelrel=self:GetFuel() or 1.0 - local Mmax=Desc.massMax or 0 - local Mempty=Desc.massEmpty or 0 - local Mfuel=Desc.fuelMassMax and Desc.fuelMassMax*fuelrel or 0 - Weight=Mmax-(Mempty+Mfuel) - self:I(string.format("Setting Cargo bay weight limit [%s]=%d kg (Mass max=%d, empty=%d, fuel=%d kg, fuelrel=%.3f)", Desc.typeName or "unknown type", Weight, Mmax, Mempty, Mfuel, fuelrel)) + -- Adjust value if set above. + local maxTakeoff=Weights[TypeName] + if maxTakeoff then + massMax=maxTakeoff end - - self.__.CargoBayWeightLimit = Weight + + -- Empty weight. + local massEmpty=Desc.massEmpty or 0 + + -- Fuel. The descriptor provides the max fuel mass in kg. This needs to be multiplied by the relative fuel amount to calculate the actual fuel mass on board. + local massFuelMax=Desc.fuelMassMax or 0 + local relFuel=self:GetFuel() or 1.0 + local massFuel=massFuelMax*relFuel + + -- Number of soldiers according to DCS function + local troopcapacity=self:GetTroopCapacity() or 0 + + -- Calculate max cargo weight, which is the max (takeoff) weight minus the empty weight minus the actual fuel weight. + local CargoWeight=massMax-(massEmpty+massFuel) + + -- Debug info. + self:I(string.format("Setting Cargo bay weight limit [%s]=%d kg (Mass max=%d, empty=%d, fuelMax=%d kg (rel=%.3f), fuel=%d kg", TypeName, CargoWeight, massMax, massEmpty, massFuelMax, relFuel, massFuel)) + self:I(string.format("Descent Troop Capacity=%d ==> %d kg (for 95 kg soldier)", troopcapacity, troopcapacity*95)) + + -- Set value. + self.__.CargoBayWeightLimit = CargoWeight elseif self:IsShip() then - local Desc = self:GetDesc() - self:F({Desc=Desc}) + -- Hard coded cargo weights in kg. local Weights = { ["Type_071"] = 245000, ["LHA_Tarawa"] = 500000, @@ -1482,11 +1525,11 @@ do -- Cargo ["speedboat"] = 500, -- 500 kg ~ 5 persons ["Seawise_Giant"] =261000000, -- Gross tonnage is 261,000 tonns. } - self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 ) + self.__.CargoBayWeightLimit = ( Weights[TypeName] or 50000 ) else - local Desc = self:GetDesc() + -- Hard coded number of soldiers. local Weights = { ["AAV7"] = 25, ["Bedford_MWD"] = 8, -- new by kappa @@ -1532,7 +1575,8 @@ do -- Cargo ["VAB_Mephisto"] = 8, -- new by Apple } - local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 95 + -- Assuming that each passenger weighs 95 kg on average. + local CargoBayWeightLimit = ( Weights[TypeName] or 0 ) * 95 self.__.CargoBayWeightLimit = CargoBayWeightLimit end @@ -1540,6 +1584,19 @@ do -- Cargo self:F({CargoBayWeightLimit = self.__.CargoBayWeightLimit}) end + + --- Get Cargo Bay Weight Limit in kg. + -- @param #POSITIONABLE self + -- @return #number Max cargo weight in kg. + function POSITIONABLE:GetCargoBayWeightLimit() + + if self.__.CargoBayWeightLimit==nil then + self:SetCargoBayWeightLimit() + end + + return self.__.CargoBayWeightLimit + end + end --- Cargo --- Signal a flare at the position of the POSITIONABLE. From c823a686166183dcc5942159eb01cb3f7b6be4b8 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 2 Nov 2021 21:14:15 +0100 Subject: [PATCH 132/141] OPS - Fixed several bugs --- Moose Development/Moose/Ops/ArmyGroup.lua | 12 +- Moose Development/Moose/Ops/Auftrag.lua | 2 +- Moose Development/Moose/Ops/Chief.lua | 4 +- Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/NavyGroup.lua | 4 +- Moose Development/Moose/Ops/OpsGroup.lua | 184 ++++++++++++++----- Moose Development/Moose/Ops/OpsTransport.lua | 110 ++++++++--- 7 files changed, 238 insertions(+), 80 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index fdb9fda9f..131c168b1 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -816,7 +816,7 @@ function ARMYGROUP:onafterSpawned(From, Event, To) -- Update route. if #self.waypoints>1 then self:T(self.lid.."Got waypoints on spawn ==> Cruise in -0.1 sec!") - self:__Cruise(-0.1, nil, self.option.Formation) + self:__Cruise(-1, nil, self.option.Formation) else self:T(self.lid.."No waypoints on spawn ==> Full Stop!") self:FullStop() @@ -910,7 +910,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) wp.action=ENUMS.Formation.Vehicle.OffRoad -- Add "On Road" waypoint in between. - local wproad=wp.roadcoord:WaypointGround(wp.speed, ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint + local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint -- Insert road waypoint. table.insert(waypoints, wproad) @@ -946,10 +946,12 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Debug output. - if self.verbose>=5 then + if self.verbose>=0 then for i,_wp in pairs(waypoints) do - local wp=_wp + local wp=_wp --Ops.OpsGroup#OPSGROUP.Waypoint local text=string.format("WP #%d UID=%d type=%s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.uid and wp.uid or -1, wp.type, wp.speed, wp.alt, wp.action) + local coord=COORDINATE:NewFromWaypoint(wp):MarkToAll(text) + --wp.coordinate:MarkToAll(text) self:T(text) end end @@ -1487,7 +1489,7 @@ function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation) self.Twaiting=nil self.dTwait=nil - self:__UpdateRoute(-1, nil, nil, Speed, Formation) + self:__UpdateRoute(-0.1, nil, nil, Speed, Formation) end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 61fb8aa4b..e35841cb4 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1760,7 +1760,7 @@ end --- **[GROUND, NAVAL]** Create an ON GUARD mission. -- @param #AUFTRAG self --- @param Core.Point#COORDINATE Coordinate Coordinate, were to be on guard. +-- @param Core.Point#COORDINATE Coordinate Coordinate, where to stand guard. -- @return #AUFTRAG self function AUFTRAG:NewONGUARD(Coordinate) diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 1e522f3fd..7a6ffe020 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -2161,7 +2161,9 @@ function CHIEF:RecruitAssetsForZone(StratZone, MissionType, NassetsMin, NassetsM elseif MissionType==AUFTRAG.Type.CAS then -- Create Patrol zone mission. - local mission=AUFTRAG:NewCAS(StratZone.opszone.zone, 7000) + --local mission=AUFTRAG:NewCAS(StratZone.opszone.zone, 7000) + local mission=AUFTRAG:NewPATROLZONE(StratZone.opszone.zone, 250, 7000) + mission:SetEngageDetected(25, TargetTypes,EngageZoneSet,NoEngageZoneSet) -- Add assets to mission. for _,asset in pairs(assets) do diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 65aa7c3f5..3850d41f0 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2155,7 +2155,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) --- -- Got current mission or task? - if self.currentmission==nil and self.taskcurrent==0 and self.cargoTransport==nil then + if self.currentmission==nil and self.taskcurrent==0 and (self.cargoTransport==nil or self.cargoTransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.DELIVERED) then -- Number of remaining tasks/missions? if nTasks==0 and nMissions==0 and nTransports==0 then diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index d76cd10b4..c0c581eb9 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1008,7 +1008,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Depth) wp.speed=UTILS.KnotsToMps(Speed) else -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. - if self.adinfinitum and wp.speed<0.1 then + if wp.speed<0.1 then --self.adinfinitum and wp.speed=UTILS.KmphToMps(self.speedCruise) end end @@ -1275,7 +1275,7 @@ function NAVYGROUP:onafterCruise(From, Event, To, Speed) -- No set depth. self.depth=nil - self:__UpdateRoute(-1, nil, nil, Speed) + self:__UpdateRoute(-0.1, nil, nil, Speed) end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index f93519b40..ea6a20683 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -460,8 +460,7 @@ OPSGROUP.CargoStatus={ -- @type OPSGROUP.CargoGroup -- @field #OPSGROUP opsgroup The cargo opsgroup. -- @field #boolean delivered If `true`, group was delivered. --- @field #OPSGROUP disembarkCarrierGroup Carrier group where the cargo group is directly loaded to. --- @field #OPSGROUP disembarkCarrierElement Carrier element to which the cargo group is directly loaded to. +-- @field #boolean disembarkActivation If `true`, group is activated. If `false`, group is late activated. -- @field #string status Status of the cargo group. Not used yet. --- OpsGroup version. @@ -533,7 +532,6 @@ function OPSGROUP:New(group) self.isTrain=true elseif self.category==Group.Category.SHIP then self.isNavygroup=true - -- TODO submarine elseif self.category==Group.Category.AIRPLANE then self.isFlightgroup=true elseif self.category==Group.Category.HELICOPTER then @@ -3663,6 +3661,13 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Just stay put. --TODO: Change ALARM STATE + if self:IsArmygroup() or self:IsNavygroup() then + -- Especially NAVYGROUP needs a full stop as patrol ad infinitum + self:FullStop() + else + -- FLIGHTGROUP not implemented (intended!) for this AUFTRAG type. + end + else -- If task is scheduled (not waypoint) set task. @@ -3874,10 +3879,17 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) else if Task.description=="Engage_Target" then + self:T(self.lid.."Taske DONE Engage_Target ==> Cruise") self:Disengage() end + + if Task.description==AUFTRAG.SpecialTask.ONGUARD then + self:T(self.lid.."Taske DONE OnGuard ==> Cruise") + self:Cruise() + end if Task.description=="Task_Land_At" then + self:T(self.lid.."Taske DONE Task_Land_At ==> Wait") self:Wait(20, 100) else self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 0 sec") @@ -3997,7 +4009,7 @@ function OPSGROUP:CountRemainingTransports() end -- In case we directly set the cargo transport (not in queue). - if N==0 and self.cargoTransport then + if N==0 and self.cargoTransport and self.cargoTransport:GetState()~=OPSTRANSPORT.Status.DELIVERED then N=1 end @@ -4916,32 +4928,46 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Final waypoint reached? if wpindex==nil or wpindex==#self.waypoints then - -- Ad infinitum? - if self.adinfinitum then - + -- Ad infinitum and not mission waypoint? + if self.adinfinitum then --- - -- Ad Infinitum and last waypoint reached. + -- Ad Infinitum --- - - if #self.waypoints<=1 then - -- Only one waypoint. Ad infinitum does not really make sense. However, another waypoint could be added later... - self:_PassedFinalWaypoint(true, "PassingWaypoint: adinfinitum but only ONE WAYPOINT left") + + if Waypoint.missionUID then + --- + -- Last waypoint was a mission waypoint ==> Do nothing (when mission is over, it should take care of this) + --- else - - -- Looks like the passing waypoint function is triggered over and over again if the group is near the final waypoint. - -- So the only good solution is to guide the group away from that waypoint and then update the route. - - -- Get first waypoint. - local wp1=self:GetWaypointByIndex(1) - - -- Get a waypoint - local Coordinate=Waypoint.coordinate:GetIntermediateCoordinate(wp1.coordinate, 0.1) - - -- Detour to the temp waypoint. When reached, the normal route is resumed. - self:Detour(Coordinate, self.speedCruise, nil, true) - + + --- + -- Last waypoint reached. + --- + + if #self.waypoints<=1 then + -- Only one waypoint. Ad infinitum does not really make sense. However, another waypoint could be added later... + self:_PassedFinalWaypoint(true, "PassingWaypoint: adinfinitum but only ONE WAYPOINT left") + else + + -- Looks like the passing waypoint function is triggered over and over again if the group is near the final waypoint. + -- So the only good solution is to guide the group away from that waypoint and then update the route. + + -- Get first waypoint. + local wp1=self:GetWaypointByIndex(1) + + -- Get a waypoint + local Coordinate=Waypoint.coordinate:GetIntermediateCoordinate(wp1.coordinate, 0.1) + + -- Detour to the temp waypoint. When reached, the normal route is resumed. + self:Detour(Coordinate, self.speedCruise, nil, true) + + end end else + --- + -- NOT Ad Infinitum + --- + -- Final waypoint reached. self:_PassedFinalWaypoint(true, "PassingWaypoint: wpindex=#self.waypoints (or wpindex=nil)") end @@ -6182,6 +6208,14 @@ function OPSGROUP:_CheckCargoTransport() self:I(self.lid.."Cargo queue:"..text) end end + + if self.cargoTransport and self.cargoTransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.DELIVERED then + -- Remove transport from queue. + self:DelOpsTransport(self.cargoTransport) + -- No current transport any more. + self.cargoTransport=nil + self.cargoTZC=nil + end -- Check if there is anything in the queue. if not self.cargoTransport and not self:IsOnMission() then @@ -6311,7 +6345,7 @@ function OPSGROUP:_CheckCargoTransport() -- Unloading finished ==> pickup next batch or call it a day. if delivered then self:T(self.lid.."Unloading finished ==> UnloadingDone") - self:__UnloadingDone(10) + self:UnloadingDone() else self:Unloading() end @@ -6614,11 +6648,14 @@ function OPSGROUP:DelOpsTransport(CargoTransport) local transport=self.cargoqueue[i] --Ops.OpsTransport#OPSTRANSPORT if transport.uid==CargoTransport.uid then + -- Debug info. + self:T(self.lid..string.format("Removing transport UID=%d", transport.uid)) + -- Remove from queue. table.remove(self.cargoqueue, i) -- Remove carrier from ops transport. - CargoTransport:_DelCarrier(self) + CargoTransport:_DelCarrier(self, 1) return self end @@ -6627,6 +6664,22 @@ function OPSGROUP:DelOpsTransport(CargoTransport) return self end +--- Get cargo transport assignment from the cargo queue by its unique ID. +-- @param #OPSGROUP self +-- @param #number uid Unique ID of the transport +-- @return Ops.OpsTransport#OPSTRANSPORT Transport. +function OPSGROUP:GetOpsTransportByUID(uid) + + for i=#self.cargoqueue,1,-1 do + local transport=self.cargoqueue[i] --Ops.OpsTransport#OPSTRANSPORT + if transport.uid==uid then + return transport + end + end + + return nil +end + --- Get total weight of the group including cargo. Optionally, the total weight of a specific unit can be requested. -- @param #OPSGROUP self @@ -7110,7 +7163,7 @@ function OPSGROUP:onafterPickup(From, Event, To) end -- NAVYGROUP - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=1 + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid, self.altitudeCruise) ; waypoint.detour=1 -- Give cruise command. self:__Cruise(-2) @@ -7397,9 +7450,16 @@ function OPSGROUP:onafterTransport(From, Event, To) self:__Unloading(-5) else + + local surfacetypes=nil + if self:IsArmygroup() then + surfacetypes={land.SurfaceType.LAND} + elseif self:IsNavygroup() then + surfacetypes={land.SurfaceType.WATER} + end -- Coord where the carrier goes to unload. - local Coordinate=Zone:GetRandomCoordinate() --Core.Point#COORDINATE + local Coordinate=Zone:GetRandomCoordinate(nil, nil, surfacetypes) --Core.Point#COORDINATE -- Add waypoint. if self:IsFlightgroup() then @@ -7452,6 +7512,14 @@ function OPSGROUP:onafterTransport(From, Event, To) -- Formation used for transporting. local Formation=self.cargoTransport:_GetFormationTransport(self.cargoTZC) + --[[ + local coordinate=self:GetCoordinate() + local pathonroad=coordinate:GetPathOnRoad(Coordinate, false, false, true) + if pathonroad then + env.info("FF got path on road") + end + ]] + if path then -- Loop over coordinates. for i,coordinate in pairs(path) do @@ -7483,7 +7551,7 @@ function OPSGROUP:onafterTransport(From, Event, To) end -- NAVYGROUP - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=1 + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid, self.altitudeCruise) ; waypoint.detour=1 -- Give cruise command. self:Cruise() @@ -7585,26 +7653,40 @@ function OPSGROUP:onafterUnloading(From, Event, To) local Coordinate=nil - if DisembarkZone then + if DisembarkZone then -- Random coordinate in disembark zone. Coordinate=DisembarkZone:GetRandomCoordinate() else + + local element=cargo.opsgroup:_GetMyCarrierElement() + + if element then - -- Get random point in disembark zone. - local zoneCarrier=self:GetElementZoneUnload(cargo.opsgroup:_GetMyCarrierElement().name) - - -- Random coordinate/heading in the zone. - Coordinate=zoneCarrier:GetRandomCoordinate() + -- Get random point in disembark zone. + local zoneCarrier=self:GetElementZoneUnload(element.name) + + -- Random coordinate/heading in the zone. + Coordinate=zoneCarrier:GetRandomCoordinate() + + else + env.info(string.format("FF ERROR carrier element nil!")) + end end -- Random heading of the group. local Heading=math.random(0,359) + + -- Activation on/off. + local activation=self.cargoTransport:GetDisembarkActivation(self.cargoTZC) + if cargo.disembarkActivation~=nil then + activation=cargo.disembarkActivation + end -- Unload to Coordinate. - self:Unload(cargo.opsgroup, Coordinate, self.cargoTransport:GetDisembarkActivation(self.cargoTZC), Heading) + self:Unload(cargo.opsgroup, Coordinate, activation, Heading) end @@ -7771,6 +7853,7 @@ function OPSGROUP:onafterUnloadingDone(From, Event, To) if not delivered then + -- Get new TZC. self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self) if self.cargoTZC then @@ -7856,17 +7939,18 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) self:__Cruise(0.1) end - -- Check group done. - self:T(self.lid.."All cargo delivered ==> check group done in 0.2 sec") - self:_CheckGroupDone(0.2) + -- Set carrier transport status. + self.cargoTransport:SetCarrierTransportStatus(self, OPSTRANSPORT.Status.DELIVERED) + + -- Check group done. + self:T(self.lid..string.format("All cargo of transport UID=%d delivered ==> check group done in 0.2 sec", self.cargoTransport.uid)) + self:_CheckGroupDone(0.2) + - -- No current transport any more. - self.cargoTransport=nil - self.cargoTZC=nil end -- Remove cargo transport from cargo queue. - self:DelOpsTransport(CargoTransport) + --self:DelOpsTransport(CargoTransport) end @@ -8023,7 +8107,8 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) local CargoIsArmyOrNavy=self:IsArmygroup() or self:IsNavygroup() -- Check that carrier is standing still. - if (CarrierIsArmyOrNavy and (CarrierGroup:IsHolding() and CarrierGroup:GetVelocity(Carrier.name)<=1)) or (CarrierGroup:IsFlightgroup() and (CarrierGroup:IsParking() or CarrierGroup:IsLandedAt())) then + --if (CarrierIsArmyOrNavy and (CarrierGroup:IsHolding() and CarrierGroup:GetVelocity(Carrier.name)<=1)) or (CarrierGroup:IsFlightgroup() and (CarrierGroup:IsParking() or CarrierGroup:IsLandedAt())) then + if (CarrierIsArmyOrNavy and (CarrierGroup:GetVelocity(Carrier.name)<=1)) or (CarrierGroup:IsFlightgroup() and (CarrierGroup:IsParking() or CarrierGroup:IsLandedAt())) then -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. local board=self.speedMax>0 and CargoIsArmyOrNavy and self:IsAlive() and CarrierGroup:IsAlive() @@ -8036,7 +8121,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) if board then -- Debug info. - self:T(self.lid..string.format("Boarding group=%s [%s], carrier=%s", CarrierGroup:GetName(), CarrierGroup:GetState(), Carrier.name)) + self:T(self.lid..string.format("Boarding group=%s [%s], carrier=%s", CarrierGroup:GetName(), CarrierGroup:GetState(), tostring(Carrier.name))) -- TODO: Implement embarkzone. local Coordinate=Carrier.unit:GetCoordinate() @@ -8062,7 +8147,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) --- -- Debug info. - self:T(self.lid..string.format("Board with direct load to carrier %s", CarrierGroup:GetName())) + self:T(self.lid..string.format("Board [loaded=%s] with direct load to carrier group=%s, element=%s", tostring(self:IsLoaded()), CarrierGroup:GetName(), tostring(Carrier.name))) -- Get current carrier group. local mycarriergroup=self:_GetMyCarrierGroup() @@ -8071,8 +8156,9 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) end -- Unload cargo first. - if mycarriergroup and mycarriergroup:GetName()~=CarrierGroup:GetName() and self:IsLoaded() then + if mycarriergroup and mycarriergroup:GetName()~=CarrierGroup:GetName() then -- TODO: Unload triggers other stuff like Disembarked. This can be a problem! + self:T(self.lid.."Unloading from mycarrier") mycarriergroup:Unload(self) end @@ -8340,7 +8426,7 @@ function OPSGROUP:_CheckGroupDone(delay) -- Debug info. self:T(self.lid..string.format("Adinfinitum=TRUE ==> Goto WP index=%d at speed=%d knots", i, speed)) - + else self:E(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop")) self:__FullStop(-1) diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index d515ae44b..1838be0de 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -503,8 +503,9 @@ end -- @param #OPSTRANSPORT self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be passed as a single GROUP or OPSGROUP object. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @param #boolean DisembarkActivation If `true`, cargo group is activated when disembarked. If `false`, cargo groups are late activated when disembarked. Default `nil` (usually activated). -- @return #OPSTRANSPORT self -function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) +function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo, DisembarkActivation) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault @@ -513,7 +514,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then -- We got a single GROUP or OPSGROUP object. - local cargo=self:_CreateCargoGroupData(GroupSet, TransportZoneCombo) + local cargo=self:_CreateCargoGroupData(GroupSet, TransportZoneCombo, DisembarkActivation) if cargo then @@ -536,7 +537,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo) for _,group in pairs(GroupSet.Set) do -- Call iteravely for each group. - self:AddCargoGroups(group, TransportZoneCombo) + self:AddCargoGroups(group, TransportZoneCombo, DisembarkActivation) end end @@ -651,6 +652,58 @@ function OPSTRANSPORT:GetEmbarkZone(TransportZoneCombo) return TransportZoneCombo.EmbarkZone end +--[[ + +--- Set transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. +-- @param #OPSTRANSPORT self +-- @param Core.Set#SET_GROUP Carriers Carrier set. Can also be passed as a #GROUP, #OPSGROUP or #SET_OPSGROUP object. +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetEmbarkCarriers(Carriers, TransportZoneCombo) + + -- Debug info. + self:T(self.lid.."Setting embark carriers!") + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + if Carriers:IsInstanceOf("GROUP") or Carriers:IsInstanceOf("OPSGROUP") then + + local carrier=self:_GetOpsGroupFromObject(Carriers) + if carrier then + table.insert(TransportZoneCombo.EmbarkCarriers, carrier) + end + + elseif Carriers:IsInstanceOf("SET_GROUP") or Carriers:IsInstanceOf("SET_OPSGROUP") then + + for _,object in pairs(Carriers:GetSet()) do + local carrier=self:_GetOpsGroupFromObject(object) + if carrier then + table.insert(TransportZoneCombo.EmbarkCarriers, carrier) + end + end + + else + self:E(self.lid.."ERROR: Carriers must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!") + end + + return self +end + +--- Get embark transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. +-- @param #OPSTRANSPORT self +-- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @return #table Table of carrier OPS groups. +function OPSTRANSPORT:GetEmbarkCarriers(TransportZoneCombo) + + -- Use default TZC if no transport zone combo is provided. + TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + return TransportZoneCombo.EmbarkCarriers +end + +]] + --- Set disembark zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE DisembarkZone Zone where the troops are disembarked. @@ -953,19 +1006,24 @@ end --- Remove group from the current carrier list/table. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. +-- @param #number Delay Delay in seconds before the carrier is removed. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:_DelCarrier(CarrierGroup) +function OPSTRANSPORT:_DelCarrier(CarrierGroup, Delay) - if self:IsCarrier(CarrierGroup) then - - for i=#self.carriers,1,-1 do - local carrier=self.carriers[i] --Ops.OpsGroup#OPSGROUP - if carrier.groupname==CarrierGroup.groupname then - self:T(self.lid..string.format("Removing carrier %s", CarrierGroup.groupname)) - table.remove(self.carriers, i) + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSTRANSPORT._DelCarrier, CarrierGroup) + else + if self:IsCarrier(CarrierGroup) then + + for i=#self.carriers,1,-1 do + local carrier=self.carriers[i] --Ops.OpsGroup#OPSGROUP + if carrier.groupname==CarrierGroup.groupname then + self:T(self.lid..string.format("Removing carrier %s", CarrierGroup.groupname)) + table.remove(self.carriers, i) + end end + end - end return self @@ -1280,6 +1338,13 @@ end -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetCarrierTransportStatus(CarrierGroup, Status) + -- Old status + local oldstatus=self:GetCarrierTransportStatus(CarrierGroup) + + -- Debug info. + self:I(self.lid..string.format("New carrier transport status for %s: %s --> %s", CarrierGroup:GetName(), oldstatus, Status)) + + -- Set new status. self.carrierTransportStatus[CarrierGroup.groupname]=Status return self @@ -1290,7 +1355,8 @@ end -- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. -- @return #string Carrier status. function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup) - return self.carrierTransportStatus[CarrierGroup.groupname] + local status=self.carrierTransportStatus[CarrierGroup.groupname] or "unknown" + return status end --- Get unique ID of the transport assignment. @@ -1423,10 +1489,12 @@ end -- @return #boolean If true, group is an assigned carrier. function OPSTRANSPORT:IsCarrier(CarrierGroup) - for _,_carrier in pairs(self.carriers) do - local carrier=_carrier --Ops.OpsGroup#OPSGROUP - if carrier.groupname==CarrierGroup.groupname then - return true + if CarrierGroup then + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + if carrier.groupname==CarrierGroup.groupname then + return true + end end end @@ -2001,9 +2069,10 @@ end --- Create a cargo group data structure. -- @param #OPSTRANSPORT self -- @param Wrapper.Group#GROUP group The GROUP or OPSGROUP object. --- @return Ops.OpsGroup#OPSGROUP.CargoGroup Cargo group data. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. -function OPSTRANSPORT:_CreateCargoGroupData(group, TransportZoneCombo) +-- @param #boolean DisembarkActivation If `true`, cargo group is activated when disembarked. +-- @return Ops.OpsGroup#OPSGROUP.CargoGroup Cargo group data. +function OPSTRANSPORT:_CreateCargoGroupData(group, TransportZoneCombo, DisembarkActivation) -- Get ops group. local opsgroup=self:_GetOpsGroupFromObject(group) @@ -2024,8 +2093,7 @@ function OPSTRANSPORT:_CreateCargoGroupData(group, TransportZoneCombo) cargo.opsgroup=opsgroup cargo.delivered=false cargo.status="Unknown" - cargo.disembarkCarrierElement=nil - cargo.disembarkCarrierGroup=nil + cargo.disembarkActivation=DisembarkActivation cargo.tzcUID=TransportZoneCombo return cargo From 7a2508bf17f9aa39b47484325ff850b0407ca431 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 6 Nov 2021 18:10:49 +0100 Subject: [PATCH 133/141] OPSTRANSPORT - Improved pre-defined paths for pickup and transport --- Moose Development/Moose/Core/Zone.lua | 41 ++--- Moose Development/Moose/Ops/OpsGroup.lua | 163 ++++++++++++++----- Moose Development/Moose/Ops/OpsTransport.lua | 151 ++++++++--------- 3 files changed, 205 insertions(+), 150 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index eec1dace9..8ca1a07bd 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1201,8 +1201,9 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) end local function _checkSurface(point) + local stype=land.getSurfaceType(point) for _,sf in pairs(surfacetypes) do - if sf==land.getSurfaceType(point) then + if sf==stype then return true end end @@ -1212,10 +1213,15 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) local point=_getpoint() if surfacetypes then - local N=1 ; local Nmax=1000 - while _checkSurface(point)==false and N<=Nmax do - point=_getpoint() - N=N+1 + local N=1 ; local Nmax=1000 ; local gotit=false + while gotit==false and N<=Nmax do + gotit=_checkSurface(point) + if gotit then + env.info(string.format("Got random coordinate with surface type %d after N=%d/%d iterations", land.getSurfaceType(point), N, Nmax)) + else + point=_getpoint() + N=N+1 + end end end @@ -2195,6 +2201,9 @@ end do -- ZONE_AIRBASE --- @type ZONE_AIRBASE + -- @field #boolean isShip If `true`, airbase is a ship. + -- @field #boolean isHelipad If `true`, airbase is a helipad. + -- @field #boolean isAirdrome If `true`, airbase is an airdrome. -- @extends #ZONE_RADIUS @@ -2251,9 +2260,9 @@ do -- ZONE_AIRBASE return self._.ZoneAirbase end - --- Returns the current location of the @{Wrapper.Group}. + --- Returns the current location of the AIRBASE. -- @param #ZONE_AIRBASE self - -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. + -- @return DCS#Vec2 The location of the zone based on the AIRBASE location. function ZONE_AIRBASE:GetVec2() self:F( self.ZoneName ) @@ -2271,24 +2280,6 @@ do -- ZONE_AIRBASE return ZoneVec2 end - --- Returns a random location within the zone of the @{Wrapper.Group}. - -- @param #ZONE_AIRBASE self - -- @return DCS#Vec2 The random location of the zone based on the @{Wrapper.Group} location. - function ZONE_AIRBASE:GetRandomVec2() - self:F( self.ZoneName ) - - local Point = {} - local Vec2 = self._.ZoneAirbase:GetVec2() - - local angle = math.random() * math.pi*2; - Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - - self:T( { Point } ) - - return Point - end - --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. -- @param #ZONE_AIRBASE self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ea6a20683..a5ae33c04 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3998,18 +3998,23 @@ function OPSGROUP:CountRemainingTransports() -- Loop over mission queue. for _,_transport in pairs(self.cargoqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + + local mystatus=transport:GetCarrierTransportStatus(self) + local status=transport:GetState() -- Debug info. - self:T(self.lid..string.format("Transport status=%s [%s]", transport:GetCarrierTransportStatus(self), transport:GetState())) + self:T(self.lid..string.format("Transport my status=%s [%s]", mystatus, status)) -- Count not delivered (executing or scheduled) assignments. - if transport and transport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.SCHEDULED and transport:GetState()~=OPSTRANSPORT.Status.DELIVERED then + if transport and mystatus==OPSTRANSPORT.Status.SCHEDULED and status~=OPSTRANSPORT.Status.DELIVERED and status~=OPSTRANSPORT.Status.CANCELLED then N=N+1 end end -- In case we directly set the cargo transport (not in queue). - if N==0 and self.cargoTransport and self.cargoTransport:GetState()~=OPSTRANSPORT.Status.DELIVERED then + if N==0 and self.cargoTransport and + self.cargoTransport:GetState()~=OPSTRANSPORT.Status.DELIVERED and self.cargoTransport:GetCarrierTransportStatus(self)~=OPSTRANSPORT.Status.DELIVERED and + self.cargoTransport:GetState()~=OPSTRANSPORT.Status.CANCELLED and self.cargoTransport:GetCarrierTransportStatus(self)~=OPSTRANSPORT.Status.CANCELLED then N=1 end @@ -6440,6 +6445,28 @@ function OPSGROUP:_AddCargobay(CargoGroup, CarrierElement, Reserved) return self end +--- Get all groups currently loaded as cargo. +-- @param #OPSGROUP self +-- @param #string CarrierName (Optional) Only return cargo groups loaded into a particular carrier unit. +-- @return #table Cargo ops groups. +function OPSGROUP:GetCargoGroups(CarrierName) + local cargos={} + + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if CarrierName==nil or element.name==CarrierName then + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + if not cargo.reserved then + table.insert(cargos, cargo.group) + end + end + end + end + + return cargos +end + --- Get cargo bay item. -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group. @@ -6655,7 +6682,7 @@ function OPSGROUP:DelOpsTransport(CargoTransport) table.remove(self.cargoqueue, i) -- Remove carrier from ops transport. - CargoTransport:_DelCarrier(self, 1) + CargoTransport:_DelCarrier(self) return self end @@ -7049,6 +7076,9 @@ end -- @param #string To To state. function OPSGROUP:onafterPickup(From, Event, To) + -- Old status. + local oldstatus=self.carrierStatus + -- Set carrier status. self:_NewCarrierStatus(OPSGROUP.CarrierStatus.PICKUP) @@ -7094,8 +7124,18 @@ function OPSGROUP:onafterPickup(From, Event, To) else + -- Set surface type of random coordinate. + local surfacetypes=nil + if self:IsArmygroup() or self:IsFlightgroup() then + surfacetypes={land.SurfaceType.LAND} + elseif self:IsNavygroup() then + surfacetypes={land.SurfaceType.WATER} + end + -- Get a random coordinate in the pickup zone and let the carrier go there. - local Coordinate=Zone:GetRandomCoordinate() + local Coordinate=Zone:GetRandomCoordinate(nil, nil, surfacetypes) + + --Coordinate:MarkToAll(string.format("Pickup coordinate for group %s [Surface type=%d]", self:GetName(), Coordinate:GetSurfaceType())) -- Add waypoint. if self:IsFlightgroup() then @@ -7151,19 +7191,29 @@ function OPSGROUP:onafterPickup(From, Event, To) local uid=cwp and cwp.uid or nil -- Get a (random) pre-defined transport path. - local path=self.cargoTransport:_GetPathPickup(self.cargoTZC) + local path=self.cargoTransport:_GetPathPickup(self.category, self.cargoTZC) - if path then - -- Loop over coordinates. - for i,coordinate in pairs(path) do - local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true - uid=waypoint.uid - --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) + -- Get transport path. + if path and oldstatus~=OPSGROUP.CarrierStatus.NOTCARRIER then + if path.reverse then + for i=#path.waypoints,1,-1 do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid, nil, false) ; waypoint.temp=true + uid=waypoint.uid + end + else + for i=1,#path.waypoints do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid, nil, false) ; waypoint.temp=true + uid=waypoint.uid + end end end -- NAVYGROUP - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid, self.altitudeCruise) ; waypoint.detour=1 + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid, self.altitudeCruise, false) ; waypoint.detour=1 -- Give cruise command. self:__Cruise(-2) @@ -7179,21 +7229,32 @@ function OPSGROUP:onafterPickup(From, Event, To) local uid=cwp and cwp.uid or nil -- Get a (random) pre-defined transport path. - local path=self.cargoTransport:_GetPathPickup(self.cargoTZC) + local path=self.cargoTransport:_GetPathPickup(self.category, self.cargoTZC) -- Formation used to go to the pickup zone.. local Formation=self.cargoTransport:_GetFormationPickup(self.cargoTZC) + -- Get transport path. if path then - -- Loop over coordinates. - for i,coordinate in pairs(path) do - local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid, Formation) ; waypoint.temp=true - uid=waypoint.uid + if path.reverse then + for i=#path.waypoints,1,-1 do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid, wp.action, false) ; waypoint.temp=true + uid=waypoint.uid + end + else + for i=1,#path.waypoints do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid, wp.action, false) ; waypoint.temp=true + uid=waypoint.uid + end end end -- ARMYGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, uid, Formation) ; waypoint.detour=1 + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, uid, Formation, false) ; waypoint.detour=1 -- Give cruise command. self:__Cruise(-2) @@ -7452,7 +7513,7 @@ function OPSGROUP:onafterTransport(From, Event, To) else local surfacetypes=nil - if self:IsArmygroup() then + if self:IsArmygroup() or self:IsFlightgroup() then surfacetypes={land.SurfaceType.LAND} elseif self:IsNavygroup() then surfacetypes={land.SurfaceType.WATER} @@ -7460,6 +7521,8 @@ function OPSGROUP:onafterTransport(From, Event, To) -- Coord where the carrier goes to unload. local Coordinate=Zone:GetRandomCoordinate(nil, nil, surfacetypes) --Core.Point#COORDINATE + + --Coordinate:MarkToAll(string.format("Deploy coordinate for group %s [Surface type=%d]", self:GetName(), Coordinate:GetSurfaceType())) -- Add waypoint. if self:IsFlightgroup() then @@ -7507,29 +7570,34 @@ function OPSGROUP:onafterTransport(From, Event, To) local uid=cwp and cwp.uid or nil -- Get transport path. - local path=self.cargoTransport:_GetPathTransport(self.cargoTZC) + local path=self.cargoTransport:_GetPathTransport(self.category, self.cargoTZC) -- Formation used for transporting. local Formation=self.cargoTransport:_GetFormationTransport(self.cargoTZC) - - --[[ - local coordinate=self:GetCoordinate() - local pathonroad=coordinate:GetPathOnRoad(Coordinate, false, false, true) - if pathonroad then - env.info("FF got path on road") - end - ]] + -- Get transport path. if path then - -- Loop over coordinates. - for i,coordinate in pairs(path) do - local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid, Formation) ; waypoint.temp=true - uid=waypoint.uid + env.info("FF 100") + if path.reverse then + for i=#path.waypoints,1,-1 do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid, wp.action, false) ; waypoint.temp=true + uid=waypoint.uid + end + else + for i=1,#path.waypoints do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + coordinate:MarkToAll("Path") + local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid, wp.action, false) ; waypoint.temp=true + uid=waypoint.uid + end end end -- ARMYGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, Formation) ; waypoint.detour=1 + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, uid, Formation, false) ; waypoint.detour=1 -- Give cruise command. self:Cruise() @@ -7540,13 +7608,24 @@ function OPSGROUP:onafterTransport(From, Event, To) local uid=cwp and cwp.uid or nil -- Get a (random) pre-defined transport path. - local path=self.cargoTransport:_GetPathTransport(self.cargoTZC) + local path=self.cargoTransport:_GetPathTransport(self.category, self.cargoTZC) + -- Get transport path. if path then - -- Loop over coordinates. - for i,coordinate in pairs(path) do - local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true - uid=waypoint.uid + if path.reverse then + for i=#path.waypoints,1,-1 do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true + uid=waypoint.uid + end + else + for i=1,#path.waypoints do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true + uid=waypoint.uid + end end end @@ -8045,13 +8124,13 @@ function OPSGROUP:onafterTransportCancel(From, Event, To, Transport) --- -- Set mission group status. - --Transport:SetGroupStatus(self, AUFTRAG.GroupStatus.CANCELLED) + Transport:SetCarrierTransportStatus(self, AUFTRAG.GroupStatus.CANCELLED) - -- Remove transport from queue. + -- Remove transport from queue. This also removes the carrier from the transport. self:DelOpsTransport(Transport) -- Remove carrier. - Transport:_DelCarrier(self) + --Transport:_DelCarrier(self) -- Send group RTB or WAIT if nothing left to do. self:_CheckGroupDone(1) diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 1838be0de..9f9412e13 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -175,9 +175,10 @@ OPSTRANSPORT.Status={ --- Path used for pickup or transport. -- @type OPSTRANSPORT.Path --- @field #table coords Table of coordinates. --- @field #number radius Radomization radius in meters. Default 0 m. --- @field #number altitude Altitude in feet AGL. Only for aircraft. +-- @field #table waypoints Table of waypoints. +-- @field #number category Category for which carriers this path is used. +-- @field #number radius Radomization radius for waypoints in meters. Default 0 m. +-- @field #boolean reverse If `true`, path is used in reversed order. --- Generic transport condition. -- @type OPSTRANSPORT.Condition @@ -189,15 +190,17 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.5.0" +OPSTRANSPORT.version="0.6.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Trains. --- TODO: Special transport cohorts/legions. Similar to mission. --- TODO: Stop/abort transport. +-- TODO: Stop transport. +-- TODO: Improve pickup and transport paths. +-- DONE: Special transport cohorts/legions. Similar to mission. +-- DONE: Cancel transport. -- DONE: Allow multiple pickup/depoly zones. -- DONE: Add start conditions. -- DONE: Check carrier(s) dead. @@ -1011,7 +1014,7 @@ end function OPSTRANSPORT:_DelCarrier(CarrierGroup, Delay) if Delay and Delay>0 then - self:ScheduleOnce(Delay, OPSTRANSPORT._DelCarrier, CarrierGroup) + self:ScheduleOnce(Delay, OPSTRANSPORT._DelCarrier, self, CarrierGroup) else if self:IsCarrier(CarrierGroup) then @@ -1180,41 +1183,34 @@ function OPSTRANSPORT:AddConditionStart(ConditionFunction, ...) return self end ---- Add path used for transportation from the pickup to the deploy zone. If multiple paths are defined, a random one is chosen. +--- Add path used for transportation from the pickup to the deploy zone for **ground** and **naval** carriers. +-- If multiple paths are defined, a random one is chosen. The path is retrieved from the waypoints of a given group. +-- **NOTE** that the category group defines for which carriers this path is valid. +-- For example, if you specify a GROUND group to provide the waypoints, only assigned GROUND carriers will use the +-- path. -- @param #OPSTRANSPORT self -- @param Wrapper.Group#GROUP PathGroup A (late activated) GROUP defining a transport path by their waypoints. -- @param #boolean Reversed If `true`, add waypoints of group in reversed order. -- @param #number Radius Randomization radius in meters. Default 0 m. --- @param #number Altitude Altitude in feet AGL. Only for aircraft. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:AddPathTransport(PathGroup, Reversed, Radius, Altitude, TransportZoneCombo) +function OPSTRANSPORT:AddPathTransport(PathGroup, Reversed, Radius, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault - local path={} --#OPSTRANSPORT.Path - path.coords={} - path.radius=Radius or 0 - path.altitude=Altitude - - -- Get route points. - local waypoints=PathGroup:GetTaskRoute() - - if Reversed then - for i=#waypoints,1,-1 do - local wp=waypoints[i] - local coord=COORDINATE:New(wp.x, wp.alt, wp.y) - table.insert(path.coords, coord) - end - else - for i=1,#waypoints do - local wp=waypoints[i] - local coord=COORDINATE:New(wp.x, wp.alt, wp.y) - table.insert(path.coords, coord) - end + if type(PathGroup)=="string" then + PathGroup=GROUP:FindByName(PathGroup) end + local path={} --#OPSTRANSPORT.Path + path.coords={} + path.category=PathGroup:GetCategory() + path.radius=Radius or 0 + path.reverse=Reversed + path.waypoints=PathGroup:GetTaskRoute() + + -- TODO: Check that only flyover waypoints are given for aircraft. -- Add path. table.insert(TransportZoneCombo.TransportPaths, path) @@ -1224,9 +1220,10 @@ end --- Get a path for transportation. -- @param #OPSTRANSPORT self +-- @param #number Category Group category. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. --- @return #table The path of COORDINATEs. -function OPSTRANSPORT:_GetPathTransport(TransportZoneCombo) +-- @return #OPSTRANSPORT.Path The path object. +function OPSTRANSPORT:_GetPathTransport(Category, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault @@ -1235,21 +1232,21 @@ function OPSTRANSPORT:_GetPathTransport(TransportZoneCombo) if pathsTransport and #pathsTransport>0 then - -- Get a random path for transport. - local path=pathsTransport[math.random(#pathsTransport)] --#OPSTRANSPORT.Path - + local paths={} - local coordinates={} - for _,_coord in ipairs(path.coords) do - local coord=_coord --Core.Point#COORDINATE - - -- Get random coordinate. - local c=coord:GetRandomCoordinateInRadius(path.radius) - - table.insert(coordinates, c) + for _,_path in pairs(pathsTransport) do + local path=_path --#OPSTRANSPORT.Path + if path.category==Category then + table.insert(paths, path) + end + end + + if #paths>0 then + + local path=paths[math.random(#paths)] --#OPSTRANSPORT.Path + + return path end - - return coordinates end return nil @@ -1261,35 +1258,22 @@ end -- @param Wrapper.Group#GROUP PathGroup A (late activated) GROUP defining a transport path by their waypoints. -- @param #boolean Reversed If `true`, add waypoints of group in reversed order. -- @param #number Radius Randomization radius in meters. Default 0 m. --- @param #number Altitude Altitude in feet AGL. Only for aircraft. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:AddPathPickup(PathGroup, Reversed, Radius, Altitude, TransportZoneCombo) +function OPSTRANSPORT:AddPathPickup(PathGroup, Reversed, Radius, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault - local path={} --#OPSTRANSPORT.Path - path.coords={} - path.radius=Radius or 0 - path.altitude=Altitude - - -- Get route points. - local waypoints=PathGroup:GetTaskRoute() - - if Reversed then - for i=#waypoints,1,-1 do - local wp=waypoints[i] - local coord=COORDINATE:New(wp.x, wp.alt, wp.y) - table.insert(path.coords, coord) - end - else - for i=1,#waypoints do - local wp=waypoints[i] - local coord=COORDINATE:New(wp.x, wp.alt, wp.y) - table.insert(path.coords, coord) - end + if type(PathGroup)=="string" then + PathGroup=GROUP:FindByName(PathGroup) end + + local path={} --#OPSTRANSPORT.Path + path.category=PathGroup:GetCategory() + path.radius=Radius or 0 + path.reverse=Reversed + path.waypoints=PathGroup:GetTaskRoute() -- Add path. table.insert(TransportZoneCombo.PickupPaths, path) @@ -1299,32 +1283,33 @@ end --- Get a path for pickup. -- @param #OPSTRANSPORT self +-- @param #number Category Group category. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. -- @return #table The path of COORDINATEs. -function OPSTRANSPORT:_GetPathPickup(TransportZoneCombo) +function OPSTRANSPORT:_GetPathPickup(Category, TransportZoneCombo) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault - local paths=TransportZoneCombo.PickupPaths + local Paths=TransportZoneCombo.PickupPaths - if paths and #paths>0 then + if Paths and #Paths>0 then - -- Get a random path for transport. - local path=paths[math.random(#paths)] --#OPSTRANSPORT.Path - + local paths={} - local coordinates={} - for _,_coord in ipairs(path.coords) do - local coord=_coord --Core.Point#COORDINATE - - -- Get random coordinate. - local c=coord:GetRandomCoordinateInRadius(path.radius) - - table.insert(coordinates, c) + for _,_path in pairs(Paths) do + local path=_path --#OPSTRANSPORT.Path + if path.category==Category then + table.insert(paths, path) + end + end + + if #paths>0 then + + local path=paths[math.random(#paths)] --#OPSTRANSPORT.Path + + return path end - - return coordinates end return nil From fb4caf1a423d5f60f65ba92d2a0e6e1bffbe7fdf Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 7 Nov 2021 16:03:44 +0100 Subject: [PATCH 134/141] OPS - Improved transport path --- Moose Development/Moose/Functional/Range.lua | 3 +- Moose Development/Moose/Ops/ArmyGroup.lua | 5 +- Moose Development/Moose/Ops/FlightGroup.lua | 4 + Moose Development/Moose/Ops/OpsGroup.lua | 215 +++++++++++------- .../Moose/Utilities/Routines.lua | 2 +- 5 files changed, 136 insertions(+), 93 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 9e5db273e..58bb14d9f 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -349,7 +349,6 @@ RANGE.Defaults={ boxlength=3000, boxwidth=300, goodpass=20, - goodhitrange=25, foulline=610, } @@ -2570,7 +2569,7 @@ function RANGE:_DisplayBombTargets(_unitname) end end - self:_DisplayMessageToGroup(_unit,_text, 60, true, true) + self:_DisplayMessageToGroup(_unit,_text, 120, true, true) end end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 131c168b1..72825b81e 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -946,13 +946,12 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Debug output. - if self.verbose>=0 then + if self.verbose>=10 then for i,_wp in pairs(waypoints) do local wp=_wp --Ops.OpsGroup#OPSGROUP.Waypoint local text=string.format("WP #%d UID=%d type=%s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.uid and wp.uid or -1, wp.type, wp.speed, wp.alt, wp.action) local coord=COORDINATE:NewFromWaypoint(wp):MarkToAll(text) - --wp.coordinate:MarkToAll(text) - self:T(text) + self:I(text) end end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 3850d41f0..7a43fc82d 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1677,6 +1677,7 @@ function FLIGHTGROUP:onafterCruise(From, Event, To) -- AI --- + --[[ if self:IsTransporting() then if self.cargoTransport and self.cargoTZC and self.cargoTZC.DeployAirbase then self:LandAtAirbase(self.cargoTZC.DeployAirbase) @@ -1688,6 +1689,9 @@ function FLIGHTGROUP:onafterCruise(From, Event, To) else self:_CheckGroupDone(nil, 120) end + ]] + + self:_CheckGroupDone(nil, 120) else diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index a5ae33c04..6eb60e01d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -7133,10 +7133,15 @@ function OPSGROUP:onafterPickup(From, Event, To) end -- Get a random coordinate in the pickup zone and let the carrier go there. - local Coordinate=Zone:GetRandomCoordinate(nil, nil, surfacetypes) - + local Coordinate=Zone:GetRandomCoordinate(nil, nil, surfacetypes) --Coordinate:MarkToAll(string.format("Pickup coordinate for group %s [Surface type=%d]", self:GetName(), Coordinate:GetSurfaceType())) + -- Current Waypoint. + local cwp=self:GetWaypointCurrent() + + -- Current waypoint ID. + local uid=cwp and cwp.uid or nil + -- Add waypoint. if self:IsFlightgroup() then @@ -7155,8 +7160,33 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Pickup at airbase --- - -- Order group to land at an airbase. - self:__LandAtAirbase(-0.1, airbasePickup) + -- Get a (random) pre-defined transport path. + local path=self.cargoTransport:_GetPathTransport(self.category, self.cargoTZC) + + -- Get transport path. + if path and oldstatus~=OPSGROUP.CarrierStatus.NOTCARRIER then + + for i=#path.waypoints,1,-1 do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=FLIGHTGROUP.AddWaypoint(self, coordinate, nil, uid, nil, false) ; waypoint.temp=true + uid=waypoint.uid + if i==1 then + waypoint.temp=false + waypoint.detour=1 --Needs to trigger the landatairbase function. + end + end + + else + + local coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate, 0.5) + + --coordinate:MarkToAll("Pickup Inter Coord") + + -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. + local waypoint=FLIGHTGROUP.AddWaypoint(self, coordinate, nil, uid, UTILS.MetersToFeet(self.altitudeCruise), true) ; waypoint.detour=1 + + end elseif self.isHelo then @@ -7165,7 +7195,7 @@ function OPSGROUP:onafterPickup(From, Event, To) --- -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, UTILS.MetersToFeet(self.altitudeCruise), false) ; waypoint.detour=1 + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, uid, UTILS.MetersToFeet(self.altitudeCruise), false) ; waypoint.detour=1 else self:E(self.lid.."ERROR: Transportcarrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") @@ -7179,7 +7209,11 @@ function OPSGROUP:onafterPickup(From, Event, To) else self:E(self.lid.."ERROR: No current task but landed at?!") end - end + end + + if self:IsWaiting() then + self:__Cruise(-2) + end elseif self:IsNavygroup() then @@ -7187,29 +7221,17 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Navy Group --- - local cwp=self:GetWaypointCurrent() - local uid=cwp and cwp.uid or nil - -- Get a (random) pre-defined transport path. - local path=self.cargoTransport:_GetPathPickup(self.category, self.cargoTZC) + local path=self.cargoTransport:_GetPathTransport(self.category, self.cargoTZC) -- Get transport path. - if path and oldstatus~=OPSGROUP.CarrierStatus.NOTCARRIER then - if path.reverse then - for i=#path.waypoints,1,-1 do - local wp=path.waypoints[i] - local coordinate=COORDINATE:NewFromWaypoint(wp) - local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid, nil, false) ; waypoint.temp=true - uid=waypoint.uid - end - else - for i=1,#path.waypoints do - local wp=path.waypoints[i] - local coordinate=COORDINATE:NewFromWaypoint(wp) - local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid, nil, false) ; waypoint.temp=true - uid=waypoint.uid - end - end + if path then --and oldstatus~=OPSGROUP.CarrierStatus.NOTCARRIER then + for i=#path.waypoints,1,-1 do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid, nil, false) ; waypoint.temp=true + uid=waypoint.uid + end end -- NAVYGROUP @@ -7223,34 +7245,22 @@ function OPSGROUP:onafterPickup(From, Event, To) --- -- Army Group - --- - - local cwp=self:GetWaypointCurrent() - local uid=cwp and cwp.uid or nil + --- -- Get a (random) pre-defined transport path. - local path=self.cargoTransport:_GetPathPickup(self.category, self.cargoTZC) + local path=self.cargoTransport:_GetPathTransport(self.category, self.cargoTZC) -- Formation used to go to the pickup zone.. - local Formation=self.cargoTransport:_GetFormationPickup(self.cargoTZC) + local Formation=self.cargoTransport:_GetFormationTransport(self.cargoTZC) -- Get transport path. - if path then - if path.reverse then - for i=#path.waypoints,1,-1 do - local wp=path.waypoints[i] - local coordinate=COORDINATE:NewFromWaypoint(wp) - local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid, wp.action, false) ; waypoint.temp=true - uid=waypoint.uid - end - else - for i=1,#path.waypoints do - local wp=path.waypoints[i] - local coordinate=COORDINATE:NewFromWaypoint(wp) - local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid, wp.action, false) ; waypoint.temp=true - uid=waypoint.uid - end - end + if path and oldstatus~=OPSGROUP.CarrierStatus.NOTCARRIER then + for i=#path.waypoints,1,-1 do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid, wp.action, false) ; waypoint.temp=true + uid=waypoint.uid + end end -- ARMYGROUP @@ -7537,9 +7547,40 @@ function OPSGROUP:onafterTransport(From, Event, To) --- -- Deploy at airbase --- + + local cwp=self:GetWaypointCurrent() + local uid=cwp and cwp.uid or nil + + -- Get a (random) pre-defined transport path. + local path=self.cargoTransport:_GetPathTransport(self.category, self.cargoTZC) + + -- Get transport path. + if path then + + for i=1, #path.waypoints do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=FLIGHTGROUP.AddWaypoint(self, coordinate, nil, uid, nil, false) ; waypoint.temp=true + uid=waypoint.uid + if i==#path.waypoints then + waypoint.temp=false + waypoint.detour=1 --Needs to trigger the landatairbase function. + end + end + + else + + local coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate, 0.5) + + --coordinate:MarkToAll("Transport Inter Waypoint") + + -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. + local waypoint=FLIGHTGROUP.AddWaypoint(self, coordinate, nil, uid, UTILS.MetersToFeet(self.altitudeCruise), true) ; waypoint.detour=1 + + end -- Order group to land at an airbase. - self:__LandAtAirbase(-0.1, airbaseDeploy) + --self:__LandAtAirbase(-0.1, airbaseDeploy) elseif self.isHelo then @@ -7577,22 +7618,11 @@ function OPSGROUP:onafterTransport(From, Event, To) -- Get transport path. if path then - env.info("FF 100") - if path.reverse then - for i=#path.waypoints,1,-1 do - local wp=path.waypoints[i] - local coordinate=COORDINATE:NewFromWaypoint(wp) - local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid, wp.action, false) ; waypoint.temp=true - uid=waypoint.uid - end - else - for i=1,#path.waypoints do - local wp=path.waypoints[i] - local coordinate=COORDINATE:NewFromWaypoint(wp) - coordinate:MarkToAll("Path") - local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid, wp.action, false) ; waypoint.temp=true - uid=waypoint.uid - end + for i=1,#path.waypoints do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid, wp.action, false) ; waypoint.temp=true + uid=waypoint.uid end end @@ -7612,25 +7642,16 @@ function OPSGROUP:onafterTransport(From, Event, To) -- Get transport path. if path then - if path.reverse then - for i=#path.waypoints,1,-1 do - local wp=path.waypoints[i] - local coordinate=COORDINATE:NewFromWaypoint(wp) - local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true - uid=waypoint.uid - end - else - for i=1,#path.waypoints do - local wp=path.waypoints[i] - local coordinate=COORDINATE:NewFromWaypoint(wp) - local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true - uid=waypoint.uid - end + for i=1,#path.waypoints do + local wp=path.waypoints[i] + local coordinate=COORDINATE:NewFromWaypoint(wp) + local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid, nil, false) ; waypoint.temp=true + uid=waypoint.uid end end -- NAVYGROUP - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid, self.altitudeCruise) ; waypoint.detour=1 + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid, self.altitudeCruise, false) ; waypoint.detour=1 -- Give cruise command. self:Cruise() @@ -9242,13 +9263,23 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) if opsgroup:IsFlightgroup() then -- Land at current pos and wait for 60 min max. - local coordinate=nil if opsgroup.cargoTZC then - coordinate=opsgroup.cargoTZC.PickupZone:GetRandomCoordinate(nil, nil, {land.SurfaceType.LAND}) + + if opsgroup.cargoTZC.PickupAirbase then + -- Pickup airbase specified. Land there. + env.info(opsgroup.lid.."FF Land at Pickup Airbase") + opsgroup:LandAtAirbase(opsgroup.cargoTZC.PickupAirbase) + else + -- Land somewhere in the pickup zone. Only helos can do that. + local coordinate=opsgroup.cargoTZC.PickupZone:GetRandomCoordinate(nil, nil, {land.SurfaceType.LAND}) + opsgroup:LandAt(coordinate, 60*60) + end + else - coordinate=opsgroup:GetCoordinate() + local coordinate=opsgroup:GetCoordinate() + opsgroup:LandAt(coordinate, 60*60) end - opsgroup:LandAt(coordinate, 60*60) + else @@ -9263,13 +9294,23 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) if opsgroup:IsFlightgroup() then -- Land at current pos and wait for 60 min max. - local coordinate=nil if opsgroup.cargoTZC then - coordinate=opsgroup.cargoTZC.DeployZone:GetRandomCoordinate(nil, nil, {land.SurfaceType.LAND}) + + if opsgroup.cargoTZC.DeployAirbase then + -- Pickup airbase specified. Land there. + env.info(opsgroup.lid.."FF Land at Deploy Airbase") + opsgroup:LandAtAirbase(opsgroup.cargoTZC.DeployAirbase) + else + -- Land somewhere in the pickup zone. Only helos can do that. + local coordinate=opsgroup.cargoTZC.DeployZone:GetRandomCoordinate(nil, nil, {land.SurfaceType.LAND}) + opsgroup:LandAt(coordinate, 60*60) + end + else coordinate=opsgroup:GetCoordinate() + opsgroup:LandAt(coordinate, 60*60) end - opsgroup:LandAt(coordinate, 60*60) + else -- Stop and unload. diff --git a/Moose Development/Moose/Utilities/Routines.lua b/Moose Development/Moose/Utilities/Routines.lua index 15e4cc1c8..eddd2c8dd 100644 --- a/Moose Development/Moose/Utilities/Routines.lua +++ b/Moose Development/Moose/Utilities/Routines.lua @@ -750,7 +750,7 @@ routines.groupRandomDistSelf = function(gpData, dist, form, heading, speed) local pos = routines.getLeadPos(gpData) local fakeZone = {} fakeZone.radius = dist or math.random(300, 1000) - fakeZone.point = {x = pos.x, y, pos.y, z = pos.z} + fakeZone.point = {x = pos.x, y = pos.y, z = pos.z} routines.groupToRandomZone(gpData, fakeZone, form, heading, speed) return From 9d0bd0aabbe338d91305f903bef10a71bcd2bcf2 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 7 Nov 2021 19:39:53 +0100 Subject: [PATCH 135/141] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 5bd2d30b6..3bbdccc0d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -9437,7 +9437,7 @@ function OPSGROUP:SwitchROE(roe) if self:IsInUtero() then self:T2(self.lid..string.format("Setting current ROE=%d when GROUP is SPAWNED", self.option.ROE)) else - + self.group:OptionROE(self.option.ROE) self:T(self.lid..string.format("Setting current ROE=%d (%s)", self.option.ROE, self:_GetROEName(self.option.ROE))) From 2ea951db6102dcf5825c64260b7f96bfe407feac Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 7 Nov 2021 19:43:02 +0100 Subject: [PATCH 136/141] Removed WIP classes - F10MENUE and FLIGHTCONTROL --- Moose Development/Moose/Core/F10Menu.lua | 212 -- Moose Development/Moose/Modules.lua | 2 - Moose Development/Moose/Ops/FlightControl.lua | 2276 ----------------- Moose Setup/Moose.files | 2 - 4 files changed, 2492 deletions(-) delete mode 100644 Moose Development/Moose/Core/F10Menu.lua delete mode 100644 Moose Development/Moose/Ops/FlightControl.lua diff --git a/Moose Development/Moose/Core/F10Menu.lua b/Moose Development/Moose/Core/F10Menu.lua deleted file mode 100644 index de8a04314..000000000 --- a/Moose Development/Moose/Core/F10Menu.lua +++ /dev/null @@ -1,212 +0,0 @@ ----- **Core** - F10 Other Menu. --- --- **Main Features:** --- --- * Add Menus and Commands to the "F10 Other" Menu --- * Create menus and commands at specific locations within the parent menu --- * Events when command functions are executed --- --- --- === --- --- ### Author: **funkyfranky** --- @module Ops.F10Menu --- @image OPS_F10Menu.png - ---- F10Menu class. --- @type F10MENU --- @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 #string text Text of the menu item. --- @field #table path Path of the menu. --- @field #F10MENU parent Parent menu or `nil`. --- @field #table commands Commands within this menu. --- @field #table submenues Sub menues withing this menu. --- @extends Core.Fsm#FSM - ---- *In preparing for battle I have always found that plans are useless, but planning is indispensable* -- Dwight D Eisenhower --- --- === --- --- # The CHIEF Concept --- --- --- @field #F10MENU -F10MENU = { - ClassName = "F10MENU", - verbose = 0, - lid = nil, - commands = {}, - submenues = {}, -} - ---- Command executing a function. --- @type F10MENU.Command --- @field #number uid Unique ID. --- @field #string text Description. --- @field #function func Function. --- @field #table args Arguments. --- @field #table path Path. - ---- F10 menu class version. --- @field #string version -F10MENU.version="0.0.1" - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO list -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- TODO: A lot. - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Constructors -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Create a new F10 menu entry. --- @param #F10MENU self --- @return #F10MENU self -function F10MENU:_New() - - -- Inherit everything from INTEL class. - local self=BASE:Inherit(self, FSM:New()) --#F10MENU - - -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("*", "MissionAssign", "*") -- Assign mission to a COMMANDER. - - ------------------------ - --- Pseudo Functions --- - ------------------------ - - - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- User functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Create a new F10 menu for all players. --- @param #F10MENU self --- @param #string Text Description of menu. --- @param #F10MENU ParentMenu Parent menu to which this menu is added. If not specified, the menu is added to the root menu. --- @return #F10MENU self -function F10MENU:NewForAll(Text, ParentMenu) - - -- Inherit everything from INTEL class. - local self=self:_New() - - self.text=Text - - self.parent=ParentMenu - - if self.parent then - self.parent:_AddSubmenu(self) - end - - local path=self.parent and self.parent:GetPath() or nil - - self.path=missionCommands.addSubMenu(self.text, path) - - return self -end - ---- Removes the F10 menu and its entire contents. --- @param #F10MENU self --- @return #F10MENU self -function F10MENU:Remove() - - for path,_submenu in pairs(self.submenues) do - local submenu=_submenu --#F10MENU - submenu:Remove() - end - -end - ---- Get path. --- @param #F10MENU self --- @return #table Path. -function F10MENU:GetPath() - return self.path -end - ---- Get commands --- @param #F10MENU self --- @return #table Path. -function F10MENU:GetCommands() - return self.commands -end - ---- Get submenues. --- @param #F10MENU self --- @return #table Path. -function F10MENU:GetSubmenues() - return self.submenues -end - - ---- Add a command for all players. --- @param #F10MENU self --- @param #string Text Description. --- @param #function CommandFunction Function to call. --- @param ... Function arguments. --- @return #F10MENU.Command Command. -function F10MENU:AddCommandForAll(Text, CommandFunction, ...) - - local command={} --#F10MENU.Command - command.uid=1 - command.text=Text - command.func=CommandFunction - command.args=... - command.path=missionCommands.addCommand(command.text, self.path, command.func, command.args) - - table.insert(self.commands, command) - - return command -end - ---- Add a command for players of a specific coalition. --- @param #F10MENU self --- @return #F10MENU self -function F10MENU:AddCommandForCoalition() - -end - - ---- Add a command for players of a specific group. --- @param #F10MENU self --- @return #F10MENU self -function F10MENU:AddCommandForGroup() - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Private functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Add a command for players of a specific group. --- @param #F10MENU self --- @param #F10MENU Submenu The submenu to add. --- @return #F10MENU self -function F10MENU:_AddSubmenu(Submenu) - - self.submenues[Submenu.path]=Submenu - -end - ---- Add a command for players of a specific group. --- @param #F10MENU self --- @return #F10MENU self -function F10MENU:_Refresh() - - - - for _,_submenu in pairs(self.submenues) do - local submenu=_submenu --#F10MENU - - - end - -end - diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 0f2b6d565..30f02b45d 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -29,7 +29,6 @@ __Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spot.lua' ) __Moose.Include( 'Scripts/Moose/Core/Astar.lua' ) __Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' ) -__Moose.Include( 'Scripts/Moose/Core/F10Menu.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' ) @@ -96,7 +95,6 @@ __Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' ) __Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' ) __Moose.Include( 'Scripts/Moose/Ops/OpsZone.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Chief.lua' ) -__Moose.Include( 'Scripts/Moose/Ops/FlightControl.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua deleted file mode 100644 index 54397657d..000000000 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ /dev/null @@ -1,2276 +0,0 @@ ---- **OPS** - Manage launching and recovery of aircraft at airdromes. --- --- --- --- **Main Features:** --- --- * Manage aircraft recovery. --- --- === --- --- ### Author: **funkyfranky** --- @module OPS.FlightControl --- @image OPS_FlightControl.png - - ---- FLIGHTCONTROL class. --- @type FLIGHTCONTROL --- @field #string ClassName Name of the class. --- @field #number verbose Verbosity level. --- @field #string theatre The DCS map used in the mission. --- @field #string lid Class id string for output to DCS log file. --- @field #string airbasename Name of airbase. --- @field #number airbasetype Type of airbase. --- @field Wrapper.Airbase#AIRBASE airbase Airbase object. --- @field Core.Zone#ZONE zoneAirbase Zone around the airbase. --- @field #table parking Parking spots table. --- @field #table runways Runway table. --- @field #table flights All flights table. --- @field #table clients Table with all clients spawning at this airbase. --- @field Ops.ATIS#ATIS atis ATIS object. --- @field #number activerwyno Number of active runway. --- @field #number atcfreq ATC radio frequency. --- @field Core.RadioQueue#RADIOQUEUE atcradio ATC radio queue. --- @field #number Nlanding Max number of aircraft groups in the landing pattern. --- @field #number dTlanding Time interval in seconds between landing clearance. --- @field #number Nparkingspots Total number of parking spots. --- @field Core.Spawn#SPAWN parkingGuard Parking guard spawner. --- @extends Core.Fsm#FSM - ---- **Ground Control**: Airliner X, Good news, you are clear to taxi to the active. --- **Pilot**: Roger, What's the bad news? --- **Ground Control**: No bad news at the moment, but you probably want to get gone before I find any. --- --- === --- --- # The FLIGHTCONTROL Concept --- --- --- --- @field #FLIGHTCONTROL -FLIGHTCONTROL = { - ClassName = "FLIGHTCONTROL", - verbose = 3, - lid = nil, - theatre = nil, - airbasename = nil, - airbase = nil, - airbasetype = nil, - zoneAirbase = nil, - parking = {}, - runways = {}, - flights = {}, - clients = {}, - atis = nil, - activerwyno = 1, - atcfreq = nil, - atcradio = nil, - atcradiounitname = nil, - Nlanding = nil, - dTlanding = nil, - Nparkingspots = nil, -} - ---- Holding point --- @type FLIGHTCONTROL.HoldingPoint --- @field Core.Point#COORDINATE pos0 First poosition of racetrack holding point. --- @field Core.Point#COORDINATE pos1 Second position of racetrack holding point. --- @field #number angelsmin Smallest holding altitude in angels. --- @field #number angelsmax Largest holding alitude in angels. - ---- Player menu data. --- @type FLIGHTCONTROL.PlayerMenu --- @field Core.Menu#MENU_GROUP root Root menu. --- @field Core.Menu#MENU_GROUP_COMMAND RequestTaxi Request taxi. - ---- Parking spot data. --- @type FLIGHTCONTROL.ParkingSpot --- @field Wrapper.Group#GROUP ParkingGuard Parking guard for this spot. --- @extends Wrapper.Airbase#AIRBASE.ParkingSpot - ---- Parking spot data. --- @type FLIGHTCONTROL.FlightStatus --- @field #string UNKNOWN Flight state is unknown. --- @field #string INBOUND Flight is inbound. --- @field #string HOLDING Flight is holding. --- @field #string LANDING Flight is landing. --- @field #string TAXIINB Flight is taxiing to parking area. --- @field #string ARRIVED Flight arrived at parking spot. --- @field #string TAXIOUT Flight is taxiing to runway for takeoff. --- @field #string READYTO Flight is ready for takeoff. --- @field #string TAKEOFF Flight is taking off. -FLIGHTCONTROL.FlightStatus={ - UNKNOWN="Unknown", - INBOUND="Inbound", - HOLDING="Holding", - LANDING="Landing", - TAXIINB="Taxi Inbound", - ARRIVED="Arrived", - PARKING="Parking", - TAXIOUT="Taxi to runway", - READYTO="Ready For Takeoff", - TAKEOFF="Takeoff", -} - - ---- Runway data. --- @type FLIGHTCONTROL.Runway --- @field #number direction Direction of the runway. --- @field #number length Length of runway in meters. --- @field #number width Width of runway in meters. --- @field Core.Point#COORDINATE position Position of runway start. - - ---- Sound file data. --- @type FLIGHTCONTROL.Soundfile --- @field #string filename Name of the file --- @field #number duration Duration in seconds. - ---- Sound files. --- @type FLIGHTCONTROL.Sound --- @field #FLIGHTCONTROL.Soundfile ActiveRunway -FLIGHTCONTROL.Sound = { - ActiveRunway={filename="ActiveRunway.ogg", duration=0.99}, -} - ---- FlightControl class version. --- @field #string version -FLIGHTCONTROL.version="0.5.0" - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO list -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- TODO: Runway destroyed. --- TODO: Define holding zone --- DONE: Add parking guard. --- TODO: Accept and forbit parking spots. --- NOGO: Add FARPS? --- TODO: Add helos. --- TODO: Talk me down option. --- TODO: ATIS option. --- TODO: ATC voice overs. --- TODO: Check runways and clean up. --- DONE: Interface with FLIGHTGROUP. - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Constructor -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Create a new FLIGHTCONTROL class object for an associated airbase. --- @param #FLIGHTCONTROL self --- @param #string airbasename Name of the airbase. --- @return #FLIGHTCONTROL self -function FLIGHTCONTROL:New(airbasename) - - -- Inherit everything from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #FLIGHTCONTROL - - -- Try to get the airbase. - self.airbase=AIRBASE:FindByName(airbasename) - - -- Name of the airbase. - self.airbasename=airbasename - - -- Set some string id for output to DCS.log file. - self.lid=string.format("FLIGHTCONTROL %s | ", airbasename) - - -- Check if the airbase exists. - if not self.airbase then - self:E(string.format("ERROR: Could not find airbase %s!", tostring(airbasename))) - return nil - end - -- Check if airbase is an airdrome. - if self.airbase:GetAirbaseCategory()~=Airbase.Category.AIRDROME then - self:E(string.format("ERROR: Airbase %s is not an AIRDROME! Script does not handle FARPS or ships.", tostring(airbasename))) - return nil - end - - - -- Airbase category airdrome, FARP, SHIP. - self.airbasetype=self.airbase:GetAirbaseCategory() - - -- Current map. - self.theatre=env.mission.theatre - - -- 5 NM zone around the airbase. - self.zoneAirbase=ZONE_RADIUS:New("FC", self:GetCoordinate():GetVec2(), UTILS.NMToMeters(5)) - - -- Defaults: - self:SetLandingMax() - self:SetLandingInterval() - - - -- Init runways. - self:_InitRunwayData() - - -- Start State. - self:SetStartState("Stopped") - - -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("Stopped", "Start", "Running") -- Start FSM. - self:AddTransition("*", "Status", "*") -- Update status. - - -- Add to data base. - _DATABASE:AddFlightControl(self) - - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- User API Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Set the number of aircraft groups, that are allowed to land simultaniously. --- @param #FLIGHTCONTROL self --- @param #number n Max number of aircraft landing simultaniously. Default 2. --- @return #FLIGHTCONTROL self -function FLIGHTCONTROL:SetLandingMax(n) - - self.Nlanding=n or 2 - - return self -end - ---- Set time interval between landing clearance of groups. --- @param #FLIGHTCONTROL self --- @param #number dt Time interval in seconds. Default 180 sec (3 min). --- @return #FLIGHTCONTROL self -function FLIGHTCONTROL:SetLandingInterval(dt) - - self.dTlanding=dt or 180 - - return self -end - - ---- Set runway. This clears all auto generated runways. --- @param #FLIGHTCONTROL self --- @param #FLIGHTCONTROL.Runway Runway. --- @return #FLIGHTCONTROL self -function FLIGHTCONTROL:SetRunway(runway) - - -- Reset table. - self.runways={} - - -- Set runway. - table.insert(self.runways, runway) - - return self -end - ---- Add runway. --- @param #FLIGHTCONTROL self --- @param #FLIGHTCONTROL.Runway Runway. --- @return #FLIGHTCONTROL self -function FLIGHTCONTROL:AddRunway(runway) - - -- Set runway. - table.insert(self.runways, runway) - - return self -end - ---- Set active runway number. Counting refers to the position in the table entry. --- @param #FLIGHTCONTROL self --- @param #number no Number in the runways table. --- @return #FLIGHTCONTROL self -function FLIGHTCONTROL:SetActiveRunwayNumber(no) - self.activerwyno=no - return self -end - - ---- Set the parking guard group. --- @param #FLIGHTCONTROL self --- @param #string TemplateGroupName Name of the template group. --- @return #FLIGHTCONTROL self -function FLIGHTCONTROL:SetParkingGuard(TemplateGroupName) - - local alias=string.format("Parking Guard %s", self.airbasename) - - -- Need spawn with alias for multiple FCs. - self.parkingGuard=SPAWN:NewWithAlias(TemplateGroupName, alias) - - --self.parkingGuard=SPAWNSTATIC:NewFromStatic("Parking Guard"):InitNamePrefix(alias) - - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Status -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Start FLIGHTCONTROL FSM. Handle events. --- @param #FLIGHTCONTROL self -function FLIGHTCONTROL:onafterStart() - - -- Events are handled my MOOSE. - self:I(self.lid..string.format("Starting FLIGHTCONTROL v%s for airbase %s of type %d on map %s", FLIGHTCONTROL.version, self.airbasename, self.airbasetype, self.theatre)) - - -- Init parking spots. - self:_InitParkingSpots() - - -- Handle events. - self:HandleEvent(EVENTS.Birth) - self:HandleEvent(EVENTS.EngineStartup) - self:HandleEvent(EVENTS.Takeoff) - self:HandleEvent(EVENTS.Land) - self:HandleEvent(EVENTS.EngineShutdown) - self:HandleEvent(EVENTS.Crash) - - self.atcradio=RADIOQUEUE:New(self.atcfreq or 305, nil, string.format("FC %s", self.airbasename)) - self.atcradio:Start(1, 0.1) - - -- Init status updates. - self:__Status(-1) -end - ---- Update status. --- @param #FLIGHTCONTROL self -function FLIGHTCONTROL:onafterStatus() - - -- Check status of all registered flights. - self:_CheckFlights() - - -- Check parking spots. - --self:_CheckParking() - - -- Check waiting and landing queue. - self:_CheckQueues() - - -- Get runway. - local runway=self:GetActiveRunway() - - local Nflights= self:CountFlights() - local NQparking=self:CountFlights(FLIGHTCONTROL.FlightStatus.PARKING) - local NQtaxiout=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIOUT) - local NQreadyto=self:CountFlights(FLIGHTCONTROL.FlightStatus.READYTO) - local NQtakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF) - local NQinbound=self:CountFlights(FLIGHTCONTROL.FlightStatus.INBOUND) - local NQholding=self:CountFlights(FLIGHTCONTROL.FlightStatus.HOLDING) - local NQlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) - local NQtaxiinb=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIINB) - local NQarrived=self:CountFlights(FLIGHTCONTROL.FlightStatus.ARRIVED) - -- ========================================================================================================= - local Nqueues = (NQparking+NQtaxiout+NQreadyto+NQtakeoff) + (NQinbound+NQholding+NQlanding+NQtaxiinb+NQarrived) - - -- Count free parking spots. - --TODO: get and substract number of reserved parking spots. - local nfree=self.Nparkingspots-NQarrived-NQparking - - local Nfree=self:CountParking(AIRBASE.SpotStatus.FREE) - local Noccu=self:CountParking(AIRBASE.SpotStatus.OCCUPIED) - local Nresv=self:CountParking(AIRBASE.SpotStatus.RESERVED) - - if Nfree+Noccu+Nresv~=self.Nparkingspots then - self:E(self.lid..string.format("WARNING: Number of parking spots does not match! Nfree=%d, Noccu=%d, Nreserved=%d != %d total", Nfree, Noccu, Nresv, self.Nparkingspots)) - end - - -- Info text. - if self.verbose>0 then - local text=string.format("State %s - Runway %s - Parking F=%d/O=%d/R=%d of %d - Flights=%s", self:GetState(), runway.idx, Nfree, Noccu, Nresv, self.Nparkingspots, Nflights) - self:I(self.lid..text) - end - - if Nflights==Nqueues then - --Check! - else - self:E(string.format("WARNING: Number of total flights %d!=%d number of flights in all queues!", Nflights, Nqueues)) - end - - if self.verbose>1 then - local text="Queue:" - text=text..string.format("\n- Flights = %d", Nflights) - text=text..string.format("\n---------------------------------------------") - text=text..string.format("\n- Parking = %d", NQparking) - text=text..string.format("\n- Taxi Out = %d", NQtaxiout) - text=text..string.format("\n- Ready TO = %d", NQreadyto) - text=text..string.format("\n- Take off = %d", NQtakeoff) - text=text..string.format("\n---------------------------------------------") - text=text..string.format("\n- Inbound = %d", NQinbound) - text=text..string.format("\n- Holding = %d", NQholding) - text=text..string.format("\n- Landing = %d", NQlanding) - text=text..string.format("\n- Taxi Inb = %d", NQtaxiinb) - text=text..string.format("\n- Arrived = %d", NQarrived) - text=text..string.format("\n---------------------------------------------") - self:I(self.lid..text) - end - - -- Next status update in ~30 seconds. - self:__Status(-20) -end - ---- Start FLIGHTCONTROL FSM. Handle events. --- @param #FLIGHTCONTROL self -function FLIGHTCONTROL:onafterStop() - - -- Handle events. - self:HandleEvent(EVENTS.Birth) - self:HandleEvent(EVENTS.EngineStartup) - self:HandleEvent(EVENTS.Takeoff) - self:HandleEvent(EVENTS.Land) - self:HandleEvent(EVENTS.EngineShutdown) - self:HandleEvent(EVENTS.Crash) - - self.atcradio:Stop() -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Event Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Event handler for event birth. --- @param #FLIGHTCONTROL self --- @param Core.Event#EVENTDATA EventData -function FLIGHTCONTROL:OnEventBirth(EventData) - self:F3({EvendData=EventData}) - - if EventData and EventData.IniGroupName and EventData.IniUnit then - - -- Debug - self:T2(self.lid..string.format("BIRTH: unit = %s", tostring(EventData.IniUnitName))) - self:T2(self.lid..string.format("BIRTH: group = %s", tostring(EventData.IniGroupName))) - - -- Unit that was born. - local unit=EventData.IniUnit - - -- Check if birth took place at this airfield. - local bornhere=EventData.Place and EventData.Place:GetName()==self.airbasename or false - - -- We delay this, to have all elements of the group in the game. - if unit:IsAir() and bornhere then - - -- We got a player? - local playerunit, playername=self:_GetPlayerUnitAndName(EventData.IniUnitName) - - -- Create flight group. - if playername then - --self:ScheduleOnce(0.5, self._CreateFlightGroup, self, EventData.IniGroup) - end - self:ScheduleOnce(0.5, self._CreateFlightGroup, self, EventData.IniGroup) - - -- Spawn parking guard. - self:SpawnParkingGuard(unit) - - end - - end - -end - ---- Event handler for event land. --- @param #FLIGHTCONTROL self --- @param Core.Event#EVENTDATA EventData -function FLIGHTCONTROL:OnEventLand(EventData) - self:F3({EvendData=EventData}) - - self:T2(self.lid..string.format("LAND: unit = %s", tostring(EventData.IniUnitName))) - self:T2(self.lid..string.format("LAND: group = %s", tostring(EventData.IniGroupName))) - -end - ---- Event handler for event takeoff. --- @param #FLIGHTCONTROL self --- @param Core.Event#EVENTDATA EventData -function FLIGHTCONTROL:OnEventTakeoff(EventData) - self:F3({EvendData=EventData}) - - self:T2(self.lid..string.format("TAKEOFF: unit = %s", tostring(EventData.IniUnitName))) - self:T2(self.lid..string.format("TAKEOFF: group = %s", tostring(EventData.IniGroupName))) - - -- This would be the closest airbase. - local airbase=EventData.Place - - -- Unit that took off. - local unit=EventData.IniUnit - - -- Nil check for airbase. Crashed as player gave me no airbase. - if not (airbase or unit) then - self:E(self.lid.."WARNING: Airbase or IniUnit is nil in takeoff event!") - return - end - -end - ---- Event handler for event engine startup. --- @param #FLIGHTCONTROL self --- @param Core.Event#EVENTDATA EventData -function FLIGHTCONTROL:OnEventEngineStartup(EventData) - self:F3({EvendData=EventData}) - - self:I(self.lid..string.format("ENGINESTARTUP: unit = %s", tostring(EventData.IniUnitName))) - self:T2(self.lid..string.format("ENGINESTARTUP: group = %s", tostring(EventData.IniGroupName))) - - -- Unit that took off. - local unit=EventData.IniUnit - - -- Nil check for unit. - if not unit then - return - end - -end - ---- Event handler for event engine shutdown. --- @param #FLIGHTCONTROL self --- @param Core.Event#EVENTDATA EventData -function FLIGHTCONTROL:OnEventEngineShutdown(EventData) - self:F3({EvendData=EventData}) - - self:I(self.lid..string.format("ENGINESHUTDOWN: unit = %s", tostring(EventData.IniUnitName))) - self:T2(self.lid..string.format("ENGINESHUTDOWN: group = %s", tostring(EventData.IniGroupName))) - - -- Unit that took off. - local unit=EventData.IniUnit - - -- Nil check for unit. - if not unit then - return - end - -end - ---- Event handler for event crash. --- @param #FLIGHTCONTROL self --- @param Core.Event#EVENTDATA EventData -function FLIGHTCONTROL:OnEventCrash(EventData) - self:F3({EvendData=EventData}) - - self:T2(self.lid..string.format("CRASH: unit = %s", tostring(EventData.IniUnitName))) - self:T2(self.lid..string.format("CRASH: group = %s", tostring(EventData.IniGroupName))) - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Queue Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Scan airbase zone. --- @param #FLIGHTCONTROL self -function FLIGHTCONTROL:_CheckQueues() - - -- Print queue. - if true then - self:_PrintQueue(self.flights, "All flights") - end - - -- Number of holding groups. - local nholding=self:CountFlights(FLIGHTCONTROL.FlightStatus.HOLDING) - - -- Number of groups landing. - local nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) - - -- Number of parking groups. - local nparking=self:CountFlights(FLIGHTCONTROL.FlightStatus.PARKING) - - -- Number of groups taking off. - local ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF) - - - -- Get next flight in line: either holding or parking. - local flight, isholding, parking=self:_GetNextFlight() - - - -- Check if somebody wants something. - if flight then - - if isholding then - - -------------------- - -- Holding flight -- - -------------------- - - -- No other flight is taking off and number of landing flights is below threshold. - if ntakeoff==0 and nlanding=self.dTlanding then - - -- Message. - local text=string.format("Flight %s, you are cleared to land.", flight.groupname) - MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() - - -- Give AI the landing signal. - -- TODO: Humans have to confirm via F10 menu. - if flight.isAI then - self:_LandAI(flight, parking) - end - - -- Set time last flight got landing clearance. - self.Tlanding=timer.getAbsTime() - - end - else - self:I(self.lid..string.format("FYI: Landing clearance for flight %s denied as other flights are taking off (N=%d) or max. landing reached (N=%d/%d).", flight.groupname, ntakeoff, nlanding, self.Nlanding)) - end - - else - - -------------------- - -- Takeoff flight -- - -------------------- - - -- No other flight is taking off or landing. - if ntakeoff==0 and nlanding==0 then - - -- Check if flight is AI. Humans have to request taxi via F10 menu. - if flight.isAI then - - --- - -- AI - --- - - -- Message. - local text=string.format("Flight %s, you are cleared to taxi to runway.", flight.groupname) - self:I(self.lid..text) - MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() - - -- Start uncontrolled aircraft. - if flight:IsUncontrolled() then - flight:StartUncontrolled() - end - - -- Add flight to takeoff queue. - self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.TAKEOFF) - - -- Remove parking guards. - for _,_element in pairs(flight.elements) do - local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element - if element and element.parking then - local spot=self:GetParkingSpotByID(element.parking.TerminalID) - self:RemoveParkingGuard(spot) - end - end - - else - - --- - -- PLAYER - --- - - local text=string.format("HUMAN Flight %s, you are cleared for takeoff.", flight.groupname) - self:I(self.lid..text) - MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() - - end - - else - self:I(self.lid..string.format("FYI: Take of for flight %s denied as other flights are taking off (N=%d) or landing (N=%d).", flight.groupname, ntakeoff, nlanding)) - end - end - else - self:I(self.lid..string.format("FYI: No flight in queue for takeoff or landing.")) - end - -end - ---- Get next flight in line, either waiting for landing or waiting for takeoff. --- @param #FLIGHTCONTROL self --- @return Ops.FlightGroup#FLIGHTGROUP Marshal flight next in line and ready to enter the pattern. Or nil if no flight is ready. --- @return #boolean If true, flight is holding and waiting for landing, if false, flight is parking and waiting for takeoff. --- @return #table Parking data for holding flights or nil. -function FLIGHTCONTROL:_GetNextFlight() - - local flightholding=self:_GetNextFightHolding() - local flightparking=self:_GetNextFightParking() - - -- If no flight is waiting for landing just return the takeoff flight or nil. - if not flightholding then - return flightparking, false, nil - end - - -- Get number of alive elements of the holding flight. - local nH=flightholding:GetNelements() - - -- Free parking spots. - local parking=flightholding:GetParking(self.airbase) - - - -- If no flight is waiting for takeoff return the holding flight or nil. - if not flightparking then - if parking then - return flightholding, true, parking - else - self:E(self.lid..string.format("WARNING: No flight parking but no parking spots! nP=%d nH=%d", #parking, nH)) - return nil, nil, nil - end - end - - - - -- We got flights waiting for landing and for takeoff. - if flightholding and flightparking then - - -- Return holding flight if fuel is low. - if flightholding.fuellow then - if parking then - -- Enough parking ==> land - return flightholding, true, parking - else - -- Not enough parking ==> take off - return flightparking, false, nil - end - end - - -- Return the flight which is waiting longer. NOTE that Tholding and Tparking are abs. mission time. So a smaller value means waiting longer. - if flightholding.Tholding0 then - - -- TODO: Could be sorted by distance to active runway! Take the runway spawn point for distance measure. - - -- First come, first serve. - return QreadyTO[1] - - end - - -- Get flights parking. - local Qparking=self:GetFlights(FLIGHTCONTROL.FlightStatus.PARKING) - - -- Check special cases where only up to one flight is waiting for takeoff. - if #Qparking==0 then - return nil - end - - -- Sort flights parking time. - local function _sortByTparking(a, b) - local flightA=a --Ops.FlightGroup#FLIGHTGROUP - local flightB=b --Ops.FlightGroup#FLIGHTGROUP - return flightA.Tparking=0 then - holding=UTILS.SecondsToClock(holding, true) - else - holding="X" - end - local parking=flight:GetParkingTime() - if parking>=0 then - parking=UTILS.SecondsToClock(parking, true) - else - parking="X" - end - - - local nunits=flight.nunits or 1 - - -- Main info. - text=text..string.format("\n[%d] %s (%s*%d): status=%s, ai=%s, fuel=%d, holding=%s, parking=%s", - i, flight.groupname, actype, nunits, flight:GetState(), ai, fuel, holding, parking) - - -- Elements info. - for j,_element in pairs(flight.elements) do - local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element - local life=element.unit:GetLife() - local life0=element.unit:GetLife0() - local park=element.parking and tostring(element.parking.TerminalID) or "N/A" - text=text..string.format("\n (%d) %s (%s): status=%s, ai=%s, airborne=%s life=%d/%d spot=%s", - j, tostring(element.modex), element.name, tostring(element.status), tostring(element.ai), tostring(element.unit:InAir()), life, life0, park) - end - end - end - - -- Display text. - self:I(self.lid..text) - - return text -end - ---- Remove a flight group from a queue. --- @param #FLIGHTCONTROL self --- @param #table queue The queue from which the group will be removed. --- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group that will be removed from queue. --- @param #string queuename Name of the queue. --- @return #boolean True, flight was in Queue and removed. False otherwise. --- @return #number Table index of removed queue element or nil. -function FLIGHTCONTROL:_RemoveFlightFromQueue(queue, flight, queuename) - - queuename=queuename or "unknown" - - -- Loop over all flights in group. - for i,_flight in pairs(queue) do - local qflight=_flight --Ops.FlightGroup#FLIGHTGROUP - - -- Check for name. - if qflight.groupname==flight.groupname then - self:I(self.lid..string.format("Removing flight group %s from %s queue.", flight.groupname, queuename)) - table.remove(queue, i) - - if not flight.isAI then - if flight.flightcontrol and flight.flightcontrol.airbasename==self.airbasename then - flight.flightcontrol=nil - end - flight:_UpdateMenu(0.1) - end - - return true, i - end - end - - self:I(self.lid..string.format("Could NOT remove flight group %s from %s queue.", flight.groupname, queuename)) - return false, nil -end - - ---- Set flight status. --- @param #FLIGHTCONTROL self --- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. --- @param #string status New status. --- @param #FLIGHTCONTROL self -function FLIGHTCONTROL:SetFlightStatus(flight, status) - - -- Debug info. - self:I(self.lid..string.format("New Flight Status for %s [%s]: %s-->%s", flight:GetName(), flight:GetState(), tostring(flight.controlstatus), status)) - - -- Set control status - flight.controlstatus=status - - return self -end - ---- Get flight status. --- @param #FLIGHTCONTROL self --- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. --- @return #string Flight status -function FLIGHTCONTROL:GetFlightStatus(flight) - - if flight then - return flight.controlstatus or "unkonwn" - end - - return "unknown" -end - ---- Check if FC has control over this flight. --- @param #FLIGHTCONTROL self --- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. --- @return #boolean If true, this FC is controlling this flight group. -function FLIGHTCONTROL:IsControlling(flight) - return flight.flightcontrol and flight.flightcontrol.airbasename==self.airbasename or false -end - - ---- Check if a group is in a queue. --- @param #FLIGHTCONTROL self --- @param #table queue The queue to check. --- @param Wrapper.Group#GROUP group The group to be checked. --- @return #boolean If true, group is in the queue. False otherwise. -function FLIGHTCONTROL:_InQueue(queue, group) - local name=group:GetName() - - for _,_flight in pairs(queue) do - local flight=_flight --Ops.FlightGroup#FLIGHTGROUP - if name==flight.groupname then - return true - end - end - - return false -end - ---- Get flights. --- @param #FLIGHTCONTROL self --- @param #string Status Return only flights in this status. --- @return #table Table of flights. -function FLIGHTCONTROL:GetFlights(Status) - - if Status then - - local flights={} - - for _,_flight in pairs(self.flights) do - local flight=_flight --Ops.FlightGroup#FLIGHTGROUP - - local status=self:GetFlightStatus(flight, Status) - - if status==Status then - table.insert(flights, flight) - end - - end - - return flights - else - return self.flights - end - -end - ---- Count flights in a given status. --- @param #FLIGHTCONTROL self --- @param #string Status Return only flights in this status. --- @return #number -function FLIGHTCONTROL:CountFlights(Status) - - if Status then - - local flights=self:GetFlights(Status) - - return #flights - - else - return #self.flights - end - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Runway Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Initialize data of runways. --- @param #FLIGHTCONTROL self -function FLIGHTCONTROL:_InitRunwayData() - self.runways=self.airbase:GetRunwayData() -end - ---- Get the active runway based on current wind direction. --- @param #FLIGHTCONTROL self --- @return Wrapper.Airbase#AIRBASE.Runway Active runway. -function FLIGHTCONTROL:GetActiveRunway() - return self.airbase:GetActiveRunway() -end - ---- Get the active runway based on current wind direction. --- @param #FLIGHTCONTROL self --- @return #string Runway text, e.g. "31L" or "09". -function FLIGHTCONTROL:GetActiveRunwayText() - local rwy="" - local rwyL - if self.atis then - rwy, rwyL=self.atis:GetActiveRunway() - if rwyL==true then - rwy=rwy.."L" - elseif rwyL==false then - rwy=rwy.."R" - end - else - rwy=self.airbase:GetActiveRunway().idx - end - return rwy -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Parking Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Init parking spots. --- @param #FLIGHTCONTROL self -function FLIGHTCONTROL:_InitParkingSpots() - - -- Parking spots of airbase. - local parkingdata=self.airbase:GetParkingSpotsTable() - - -- Init parking spots table. - self.parking={} - - self.Nparkingspots=0 - for _,_spot in pairs(parkingdata) do - local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot - - - -- Mark position. - local text=string.format("Parking ID=%d, Terminal=%d: Free=%s, Client=%s, Dist=%.1f", spot.TerminalID, spot.TerminalType, tostring(spot.Free), tostring(spot.ClientSpot), spot.DistToRwy) - self:I(self.lid..text) - - -- Add to table. - self.parking[spot.TerminalID]=spot - - spot.Marker=MARKER:New(spot.Coordinate, "Spot"):ReadOnly() - spot.Marker.tocoaliton=true - spot.Marker.coalition=self:GetCoalition() - - -- Check if spot is initially free or occupied. - if spot.Free then - - -- Parking spot is free. - self:SetParkingFree(spot) - - else - - -- Scan for the unit sitting here. - local unit=spot.Coordinate:FindClosestUnit(20) - - - if unit then - - local unitname=unit and unit:GetName() or "unknown" - - local isalive=unit:IsAlive() - - env.info(string.format("FF parking spot %d is occupied by unit %s alive=%s", spot.TerminalID, unitname, tostring(isalive))) - - if isalive then - - - self:SetParkingOccupied(spot, unitname) - - self:SpawnParkingGuard(unit) - - else - - -- TODO - env.info(string.format("FF parking spot %d is occupied by NOT ALIVE unit %s", spot.TerminalID, unitname)) - - -- Parking spot is free. - self:SetParkingFree(spot) - - end - - else - self:I(self.lid..string.format("ERROR: Parking spot is NOT FREE but no unit could be found there!")) - end - end - - -- Increase counter - self.Nparkingspots=self.Nparkingspots+1 - end - -end - ---- Get parking spot by its Terminal ID. --- @param #FLIGHTCONTROL self --- @param #number TerminalID --- @return #FLIGHTCONTROL.ParkingSpot Parking spot data table. -function FLIGHTCONTROL:GetParkingSpotByID(TerminalID) - return self.parking[TerminalID] -end - ---- Set parking spot to FREE and update F10 marker. --- @param #FLIGHTCONTROL self --- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot The parking spot data table. -function FLIGHTCONTROL:SetParkingFree(spot) - - local spot=self:GetParkingSpotByID(spot.TerminalID) - - -- Debug info. - self:I(self.lid..string.format("Parking spot %d: %s-->%s", spot.TerminalID, tostring(spot.Status), AIRBASE.SpotStatus.FREE)) - - spot.Status=AIRBASE.SpotStatus.FREE - spot.OccupiedBy=nil - spot.ReservedBy=nil - - self:UpdateParkingMarker(spot) - -end - ---- Set parking spot to RESERVED and update F10 marker. --- @param #FLIGHTCONTROL self --- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot The parking spot data table. --- @param #string unitname Name of the unit occupying the spot. Default "unknown". -function FLIGHTCONTROL:SetParkingReserved(spot, unitname) - - local spot=self:GetParkingSpotByID(spot.TerminalID) - - -- Debug info. - self:I(self.lid..string.format("Parking spot %d: %s-->%s", spot.TerminalID, tostring(spot.Status), AIRBASE.SpotStatus.RESERVED)) - - spot.Status=AIRBASE.SpotStatus.RESERVED - spot.ReservedBy=unitname or "unknown" - - self:UpdateParkingMarker(spot) - -end - ---- Set parking spot to OCCUPIED and update F10 marker. --- @param #FLIGHTCONTROL self --- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot The parking spot data table. --- @param #string unitname Name of the unit occupying the spot. Default "unknown". -function FLIGHTCONTROL:SetParkingOccupied(spot, unitname) - - local spot=self:GetParkingSpotByID(spot.TerminalID) - - -- Debug info. - self:I(self.lid..string.format("Parking spot %d: %s-->%s", spot.TerminalID, tostring(spot.Status), AIRBASE.SpotStatus.OCCUPIED)) - - spot.Status=AIRBASE.SpotStatus.OCCUPIED - spot.OccupiedBy=unitname or "unknown" - - self:UpdateParkingMarker(spot) - -end - ---- Get free parking spots. --- @param #FLIGHTCONTROL self --- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot The parking spot data table. -function FLIGHTCONTROL:UpdateParkingMarker(spot) - - local spot=self:GetParkingSpotByID(spot.TerminalID) - - --env.info(string.format("FF updateing spot %d status=%s", spot.TerminalID, spot.Status)) - - -- Only mark OCCUPIED and RESERVED spots. - if spot.Status==AIRBASE.SpotStatus.FREE then - - if spot.Marker then - spot.Marker:Remove() - end - - else - - local text=string.format("Spot %d (type %d): %s", spot.TerminalID, spot.TerminalType, spot.Status:upper()) - if spot.OccupiedBy then - text=text..string.format("\nOccupied by %s", spot.OccupiedBy) - end - if spot.ReservedBy then - text=text..string.format("\nReserved for %s", spot.ReservedBy) - end - if spot.ClientSpot then - text=text..string.format("\nClient %s", tostring(spot.ClientSpot)) - end - - if spot.Marker then - - if text~=spot.Marker.text then - spot.Marker:UpdateText(text) - end - - else - - spot.Marker=MARKER:New(spot.Coordinate, text):ToAll() - - end - - end -end - ---- Check if parking spot is free. --- @param #FLIGHTCONTROL self --- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot data. --- @return #boolean If true, parking spot is free. -function FLIGHTCONTROL:IsParkingFree(spot) - return spot.Status==AIRBASE.SpotStatus.FREE -end - ---- Check if a parking spot is reserved by a flight group. --- @param #FLIGHTCONTROL self --- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot to check. --- @return #string Name of element or nil. -function FLIGHTCONTROL:IsParkingOccupied(spot) - - if spot.Status==AIRBASE.SpotStatus.OCCUPIED then - return tostring(spot.OccupiedBy) - else - return false - end -end - ---- Check if a parking spot is reserved by a flight group. --- @param #FLIGHTCONTROL self --- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot to check. --- @return #string Name of element or *nil*. -function FLIGHTCONTROL:IsParkingReserved(spot) - - if spot.Status==AIRBASE.SpotStatus.RESERVED then - return tostring(spot.ReservedBy) - else - return false - end - - -- Init all elements as NOT parking anywhere. - for _,_flight in pairs(self.flights) do - local flight=_flight --Ops.FlightGroup#FLIGHTGROUP - -- Loop over all elements. - for _,_element in pairs(flight.elements) do - local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element - local parking=element.parking - if parking and parking.TerminalID==spot.TerminalID then - return element.name - end - end - end - - return nil -end - ---- Get free parking spots. --- @param #FLIGHTCONTROL self --- @param #number terminal Terminal type or nil. --- @return #number Number of free spots. Total if terminal=nil or of the requested terminal type. --- @return #table Table of free parking spots of data type #FLIGHCONTROL.ParkingSpot. -function FLIGHTCONTROL:_GetFreeParkingSpots(terminal) - - local freespots={} - - local n=0 - for _,_parking in pairs(self.parking) do - local parking=_parking --Wrapper.Airbase#AIRBASE.ParkingSpot - - if self:IsParkingFree(parking) then - if terminal==nil or terminal==parking.terminal then - n=n+1 - table.insert(freespots, parking) - end - end - end - - return n,freespots -end - ---- Get closest parking spot. --- @param #FLIGHTCONTROL self --- @param Core.Point#COORDINATE coordinate Reference coordinate. --- @param #number terminaltype (Optional) Check only this terminal type. --- @param #boolean free (Optional) If true, check only free spots. --- @return #FLIGHTCONTROL.ParkingSpot Closest parking spot. -function FLIGHTCONTROL:GetClosestParkingSpot(coordinate, terminaltype, free) - - local distmin=math.huge - local spotmin=nil - - for TerminalID, Spot in pairs(self.parking) do - local spot=Spot --Wrapper.Airbase#AIRBASE.ParkingSpot - - if (not free) or (free==true and not (self:IsParkingReserved(spot) or self:IsParkingOccupied(spot))) then - if terminaltype==nil or terminaltype==spot.TerminalType then - - -- Get distance from coordinate to spot. - local dist=coordinate:Get2DDistance(spot.Coordinate) - - -- Check if distance is smaller. - if dist0 then - MESSAGE:New("Negative ghostrider, other flights are currently landing. Talk to you soon.", 5):ToAll() - self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.READYTO) - elseif Ntakeoff>0 then - MESSAGE:New("Negative ghostrider, other flights are ahead of you. Talk to you soon.", 5):ToAll() - self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.READYTO) - end - - else - MESSAGE:New(string.format("Negative, you must request TAXI before you can request TAKEOFF!"), 5):ToAll() - end - end - -end - ---- Player wants to abort takeoff. --- @param #FLIGHTCONTROL self --- @param #string groupname Name of the flight group. -function FLIGHTCONTROL:_PlayerAbortTakeoff(groupname) - - MESSAGE:New("Abort takeoff", 5):ToAll() - - local flight=_DATABASE:GetOpsGroup(groupname) - - if flight then - - if self:GetFlightStatus(flight)==FLIGHTCONTROL.FlightStatus.TAKEOFF then - - - MESSAGE:New("Afirm, You are removed from takeoff queue", 5):ToAll() - - --TODO: what now? taxi inbound? or just another later attempt to takeoff. - self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.READYTO) - - - else - MESSAGE:New("Negative, You are NOT in the takeoff queue", 5):ToAll() - end - - end - -end - ---- Player wants to abort inbound. --- @param #FLIGHTCONTROL self --- @param #string groupname Name of the flight group. -function FLIGHTCONTROL:_PlayerAbortInbound(groupname) - - MESSAGE:New("Abort inbound", 5):ToAll() - - local flight=_DATABASE:GetOpsGroup(groupname) - - if flight then - - local flightstatus=self:GetFlightStatus(flight) - if flightstatus==FLIGHTCONTROL.FlightStatus.INBOUND or flightstatus==FLIGHTCONTROL.FlightStatus.HOLDING or flightstatus==FLIGHTCONTROL.FlightStatus.LANDING then - - MESSAGE:New("Afirm, You are removed from all queues queue", 5):ToAll() - - --TODO: what now? taxi inbound? or just another later attempt to takeoff. - self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.UNKNOWN) - - -- Remove flight. - self:_RemoveFlight(flight) - - -- Trigger cruise event. - flight:Cruise() - - - else - MESSAGE:New("Negative, You are NOT in the state INBOUND, HOLDING or LANDING!", 5):ToAll() - end - - end - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Flight and Element Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Create a new flight group. --- @param #FLIGHTCONTROL self --- @param Wrapper.Group#GROUP group Aircraft group. --- @return Ops.FlightGroup#FLIGHTGROUP Flight group. -function FLIGHTCONTROL:_CreateFlightGroup(group) - - -- Check if not already in flights - if self:_InQueue(self.flights, group) then - self:E(self.lid..string.format("WARNING: Flight group %s does already exist!", group:GetName())) - return - end - - -- Debug info. - self:I(self.lid..string.format("Creating new flight for group %s of aircraft type %s.", group:GetName(), group:GetTypeName())) - - -- Get flightgroup from data base. - local flight=_DATABASE:GetOpsGroup(group:GetName()) - - -- If it does not exist yet, create one. - if not flight then - flight=FLIGHTGROUP:New(group:GetName()) - end - - --if flight.destination and flight.destination:GetName()==self.airbasename then - if flight.homebase and flight.homebase:GetName()==self.airbasename then - flight:SetFlightControl(self) - end - - flight:SetVerbosity(2) - - return flight -end - ---- Remove flight from all queues. --- @param #FLIGHTCONTROL self --- @param Ops.FlightGroup#FLIGHTGROUP flight The flight to be removed. -function FLIGHTCONTROL:_RemoveFlight(flight) - - self:_RemoveFlightFromQueue(self.flights, flight, "flights") - -end - ---- Get flight from group. --- @param #FLIGHTCONTROL self --- @param Wrapper.Group#GROUP group Group that will be removed from queue. --- @param #table queue The queue from which the group will be removed. --- @return Ops.FlightGroup#FLIGHTGROUP Flight group or nil. --- @return #number Queue index or nil. -function FLIGHTCONTROL:_GetFlightFromGroup(group) - - if group then - - -- Group name - local name=group:GetName() - - -- Loop over all flight groups in queue - for i,_flight in pairs(self.flights) do - local flight=_flight --Ops.FlightGroup#FLIGHTGROUP - - if flight.groupname==name then - return flight, i - end - end - - self:T2(self.lid..string.format("WARNING: Flight group %s could not be found in queue.", name)) - end - - self:T2(self.lid..string.format("WARNING: Flight group could not be found in queue. Group is nil!")) - return nil, nil -end - ---- Get element of flight from its unit name. --- @param #FLIGHTCONTROL self --- @param #string unitname Name of the unit. --- @return #FLIGHTCONTROL.FlightElement Element of the flight or nil. --- @return #number Element index or nil. --- @return Ops.FlightGroup#FLIGHTGROUP The Flight group or nil. -function FLIGHTCONTROL:_GetFlightElement(unitname) - - -- Get the unit. - local unit=UNIT:FindByName(unitname) - - -- Check if unit exists. - if unit then - - -- Get flight element from all flights. - local flight=self:_GetFlightFromGroup(unit:GetGroup()) - - -- Check if fight exists. - if flight then - - -- Loop over all elements in flight group. - for i,_element in pairs(flight.elements) do - local element=_element --#FLIGHTCONTROL.FlightElement - - if element.unit:GetName()==unitname then - return element, i, flight - end - end - - self:T2(self.lid..string.format("WARNING: Flight element %s could not be found in flight group.", unitname, flight.groupname)) - end - end - - return nil, nil, nil -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Check Sanity Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Check status of all registered flights and do some sanity checks. --- @param #FLIGHTCONTROL self -function FLIGHTCONTROL:_CheckFlights() - - -- First remove all dead flights. - for i=#self.flights,1,-1 do - local flight=self.flights[i] --Ops.FlightGroup#FLIGHTGROUP - if flight:IsDead() then - self:I(self.lid..string.format("Removing DEAD flight %s", tostring(flight.groupname))) - self:_RemoveFlight(flight) - end - end - - --TODO: check parking? - -end - ---- Check status of all registered flights and do some sanity checks. --- @param #FLIGHTCONTROL self -function FLIGHTCONTROL:_CheckParking() - - for TerminalID,_spot in pairs(self.parking) do - local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot - - if spot.Reserved then - if spot.MarkerID then - spot.Coordinate:RemoveMark(spot.MarkerID) - end - spot.MarkerID=spot.Coordinate:MarkToCoalition(string.format("Parking reserved for %s", tostring(spot.Reserved)), self:GetCoalition()) - end - - -- First remove all dead flights. - for i=1,#self.flights do - local flight=self.flights[i] --Ops.FlightGroup#FLIGHTGROUP - for _,_element in pairs(flight.elements) do - local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element - if element.parking and element.parking.TerminalID==TerminalID then - if spot.MarkerID then - spot.Coordinate:RemoveMark(spot.MarkerID) - end - spot.MarkerID=spot.Coordinate:MarkToCoalition(string.format("Parking spot occupied by %s", tostring(element.name)), self:GetCoalition()) - end - end - end - - end - - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Routing Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Tell AI to land at the airbase. Flight is added to the landing queue. --- @param #FLIGHTCONTROL self --- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. --- @param #table parking Free parking spots table. -function FLIGHTCONTROL:_LandAI(flight, parking) - - -- Debug info. - self:I(self.lid..string.format("Landing AI flight %s.", flight.groupname)) - - -- Set flight status to LANDING. - self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.LANDING) - - -- Flight is not holding any more. - flight.Tholding=nil - - - local respawn=false - - if respawn then - - -- Get group template. - local Template=flight.group:GetTemplate() - - -- TODO: get landing waypoints from flightgroup. - - -- Set route points. - Template.route.points=wp - - for i,unit in pairs(Template.units) do - local spot=parking[i] --Wrapper.Airbase#AIRBASE.ParkingSpot - - local element=flight:GetElementByName(unit.name) - if element then - - -- Set the parking spot at the destination airbase. - unit.parking_landing=spot.TerminalID - - local text=string.format("FF Reserving parking spot %d for unit %s", spot.TerminalID, tostring(unit.name)) - self:I(self.lid..text) - - -- Set parking to RESERVED. - self:SetParkingReserved(spot, element.name) - - else - env.info("FF error could not get element to assign parking!") - end - end - - -- Debug message. - MESSAGE:New(string.format("Respawning group %s", flight.groupname)):ToAll() - - --Respawn the group. - flight:Respawn(Template) - - else - - -- Give signal to land. - flight:ClearToLand() - - end - -end - ---- Get holding point. --- @param #FLIGHTCONTROL self --- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. --- @return #FLIGHTCONTROL.HoldingPoint Holding point. -function FLIGHTCONTROL:_GetHoldingpoint(flight) - - local holdingpoint={} --#FLIGHTCONTROL.HoldingPoint - - local runway=self:GetActiveRunway() - - local hdg=runway.heading+90 - local dx=UTILS.NMToMeters(5) - local dz=UTILS.NMToMeters(1) - - local angels=UTILS.FeetToMeters(math.random(6,10)*1000) - - holdingpoint.pos0=runway.position:Translate(dx, hdg):SetAltitude(angels) - holdingpoint.pos1=holdingpoint.pos0:Translate(dz, runway.heading):SetAltitude(angels) - - return holdingpoint -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Radio Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Transmission via RADIOQUEUE. --- @param #FLIGHTCONTROL self --- @param #FLIGHTCONTROL.Soundfile sound FLIGHTCONTROL sound object. --- @param #number interval Interval in seconds after the last transmission finished. --- @param #string subtitle Subtitle of the transmission. --- @param #string path Path to sound file. Default self.soundpath. -function FLIGHTCONTROL:Transmission(sound, interval, subtitle, path) - self.radioqueue:NewTransmission(sound.filename, sound.duration, path or self.soundpath, nil, interval, subtitle, self.subduration) -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Misc Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Add parking guard in front of a parking aircraft. --- @param #FLIGHTCONTROL self --- @param Wrapper.Unit#UNIT unit The aircraft. -function FLIGHTCONTROL:SpawnParkingGuard(unit) - - if unit and self.parkingGuard then - - -- Position of the unit. - local coordinate=unit:GetCoordinate() - - -- Parking spot. - local spot=self:GetClosestParkingSpot(coordinate) - - -- Current heading of the unit. - local heading=unit:GetHeading() - - -- Length of the unit + 3 meters. - local size, x, y, z=unit:GetObjectSize() - - self:I(self.lid..string.format("Parking guard for %s: heading=%d, distance x=%.1f m", unit:GetName(), heading, x)) - - -- Coordinate for the guard. - local Coordinate=coordinate:Translate(0.75*x+3, heading) - - -- Let him face the aircraft. - local lookat=heading-180 - - -- Set heading and AI off to save resources. - self.parkingGuard:InitHeading(lookat):InitAIOff() - - -- Group that is spawned. - spot.ParkingGuard=self.parkingGuard:SpawnFromCoordinate(Coordinate) - --spot.ParkingGuard=self.parkingGuard:SpawnFromCoordinate(Coordinate, lookat) - - end - -end - ---- Remove parking guard. --- @param #FLIGHTCONTROL self --- @param #FLIGHTCONTROL.ParkingSpot spot --- @param #number delay Delay in seconds. -function FLIGHTCONTROL:RemoveParkingGuard(spot, delay) - - if delay and delay>0 then - self:ScheduleOnce(delay, FLIGHTCONTROL.RemoveParkingGuard, self, spot) - else - - if spot.ParkingGuard then - self:I(self.lid..string.format("Removing parking guard at spot %d", spot.TerminalID)) - spot.ParkingGuard:Destroy() - spot.ParkingGuard=nil - end - - end - -end - - ---- Get coordinate of the airbase. --- @param #FLIGHTCONTROL self --- @return Core.Point#COORDINATE Coordinate of the airbase. -function FLIGHTCONTROL:GetCoordinate() - return self.airbase:GetCoordinate() -end - ---- Get coalition of the airbase. --- @param #FLIGHTCONTROL self --- @return #number Coalition ID. -function FLIGHTCONTROL:GetCoalition() - return self.airbase:GetCoalition() -end - ---- Get country of the airbase. --- @param #FLIGHTCONTROL self --- @return #number Country ID. -function FLIGHTCONTROL:GetCountry() - return self.airbase:GetCountry() -end - ---- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. --- @param #FLIGHTCONTROL self --- @param #string unitName Name of the player unit. --- @return Wrapper.Unit#UNIT Unit of player or nil. --- @return #string Name of the player or nil. -function FLIGHTCONTROL:_GetPlayerUnitAndName(unitName) - - if unitName then - - -- Get DCS unit from its name. - local DCSunit=Unit.getByName(unitName) - - if DCSunit then - - -- Get player name if any. - local playername=DCSunit:getPlayerName() - - -- Unit object. - local unit=UNIT:Find(DCSunit) - - -- Check if enverything is there. - if DCSunit and unit and playername then - self:T(self.lid..string.format("Found DCS unit %s with player %s", tostring(unitName), tostring(playername))) - return unit, playername - end - - end - - end - - -- Return nil if we could not find a player. - return nil,nil -end - - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 7a1553aff..7085a12e9 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -28,7 +28,6 @@ Core/SpawnStatic.lua Core/Timer.lua Core/Goal.lua Core/Spot.lua -Core/F10Menu.lua Wrapper/Object.lua Wrapper/Identifiable.lua @@ -89,7 +88,6 @@ Ops/Brigade.lua Ops/Intelligence.lua Ops/Commander.lua Ops/Chief.lua -Ops/FlightControl.lua Ops/CSAR.lua Ops/CTLD.lua From 6cd00c60a7671c8fe163d33ee2acbcd089c207c4 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 7 Nov 2021 21:39:38 +0100 Subject: [PATCH 137/141] Update OpsGroup.lua - Fixed waypoint alt for missions --- Moose Development/Moose/Ops/OpsGroup.lua | 35 ++++++++++++++++-------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 3bbdccc0d..1812aad4e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4596,17 +4596,19 @@ function OPSGROUP:RouteToMission(mission, delay) end end - -- Formation. - local formation=nil - if self.isArmygroup and mission.optionFormation then - formation=mission.optionFormation - end - -- UID of this waypoint. local uid=self:GetWaypointCurrent().uid -- Add waypoint. - local waypoint=self:AddWaypoint(waypointcoord, SpeedToMission, uid, formation, false) ; waypoint.missionUID=mission.auftragsnummer + local waypoint=nil --#OPSGROUP.Waypoint + if self:IsFlightgroup() then + waypoint=FLIGHTGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) + elseif self:IsArmygroup() then + waypoint=ARMYGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, mission.optionFormation, false) + elseif self:IsNavygroup() then + waypoint=NAVYGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, mission.missionAltitude or self.altitudeCruise, false) + end + waypoint.missionUID=mission.auftragsnummer -- Add waypoint task. UpdateRoute is called inside. local waypointtask=self:AddTaskWaypoint(mission.DCStask, waypoint, mission.name, mission.prio, mission.duration) @@ -4619,11 +4621,20 @@ function OPSGROUP:RouteToMission(mission, delay) mission:SetGroupWaypointIndex(self, waypoint.uid) -- Add egress waypoint. - local egress=mission:GetMissionEgressCoord() - if egress then - --egress:MarkToAll(string.format("Egress Mission %s alt=%d m", mission:GetName(), waypointcoord.y)) - local waypointEgress=self:AddWaypoint(egress, SpeedToMission, waypoint.uid, formation, false) ; waypointEgress.missionUID=mission.auftragsnummer - mission:SetGroupEgressWaypointUID(self, waypointEgress.uid) + local egresscoord=mission:GetMissionEgressCoord() + if egresscoord then + --egresscoord:MarkToAll(string.format("Egress Mission %s alt=%d m", mission:GetName(), waypointcoord.y)) + -- Add waypoint. + local waypoint=nil --#OPSGROUP.Waypoint + if self:IsFlightgroup() then + waypoint=FLIGHTGROUP.AddWaypoint(self, egresscoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) + elseif self:IsArmygroup() then + waypoint=ARMYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, uid, mission.optionFormation, false) + elseif self:IsNavygroup() then + waypoint=NAVYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, uid, mission.missionAltitude or self.altitudeCruise, false) + end + waypoint.missionUID=mission.auftragsnummer + mission:SetGroupEgressWaypointUID(self, waypoint.uid) end --- From f157f3b5d6166a67071683618e1fc97cfc582c4f Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 7 Nov 2021 22:32:17 +0100 Subject: [PATCH 138/141] OPS - Fixed mission alt for patrol zone auftrag --- Moose Development/Moose/Ops/Chief.lua | 6 +++--- Moose Development/Moose/Ops/NavyGroup.lua | 7 ++++++- Moose Development/Moose/Ops/OpsGroup.lua | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 7a6ffe020..b449dc95c 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -2132,6 +2132,7 @@ function CHIEF:RecruitAssetsForZone(StratZone, MissionType, NassetsMin, NassetsM if MissionType==AUFTRAG.Type.PATROLZONE then mission=AUFTRAG:NewPATROLZONE(StratZone.opszone.zone) + mission:SetEngageDetected(25, {"Ground Units", "Light armed ships", "Helicopters"}) elseif MissionType==AUFTRAG.Type.ONGUARD then mission=AUFTRAG:NewONGUARD(StratZone.opszone.zone:GetRandomCoordinate(), nil, nil, {land.SurfaceType.LAND}) end @@ -2161,9 +2162,8 @@ function CHIEF:RecruitAssetsForZone(StratZone, MissionType, NassetsMin, NassetsM elseif MissionType==AUFTRAG.Type.CAS then -- Create Patrol zone mission. - --local mission=AUFTRAG:NewCAS(StratZone.opszone.zone, 7000) - local mission=AUFTRAG:NewPATROLZONE(StratZone.opszone.zone, 250, 7000) - mission:SetEngageDetected(25, TargetTypes,EngageZoneSet,NoEngageZoneSet) + local mission=AUFTRAG:NewPATROLZONE(StratZone.opszone.zone) + mission:SetEngageDetected(25, {"Ground Units", "Light armed ships", "Helicopters"}) -- Add assets to mission. for _,asset in pairs(assets) do diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index c0c581eb9..2ca47fe90 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1465,7 +1465,7 @@ end -- @param Core.Point#COORDINATE Coordinate The coordinate of the waypoint. Use `COORDINATE:SetAltitude()` to define the altitude. -- @param #number Speed Speed in knots. Default is default cruise speed or 70% of max speed. -- @param #number AfterWaypointWithID Insert waypoint after waypoint given ID. Default is to insert as last waypoint. --- @param #number Depth Depth at waypoint in meters. Only for submarines. +-- @param #number Depth Depth at waypoint in feet. Only for submarines. -- @param #boolean Updateroute If true or nil, call UpdateRoute. If false, no call. -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Updateroute) @@ -1485,6 +1485,11 @@ function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Up -- Create waypoint data table. local waypoint=self:_CreateWaypoint(wp) + -- Set altitude. + if Depth then + waypoint.alt=UTILS.FeetToMeters(Depth) + end + -- Add waypoint to table. self:_AddWaypoint(waypoint, wpnumber) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 1812aad4e..210cb6f55 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4631,7 +4631,7 @@ function OPSGROUP:RouteToMission(mission, delay) elseif self:IsArmygroup() then waypoint=ARMYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, uid, mission.optionFormation, false) elseif self:IsNavygroup() then - waypoint=NAVYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, uid, mission.missionAltitude or self.altitudeCruise, false) + waypoint=NAVYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) end waypoint.missionUID=mission.auftragsnummer mission:SetGroupEgressWaypointUID(self, waypoint.uid) @@ -4854,7 +4854,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Speed and altitude. local Speed=UTILS.KmphToKnots(task.dcstask.params.speed or self.speedCruise) - local Altitude=task.dcstask.params.altitude and UTILS.MetersToFeet(task.dcstask.params.altitude) or nil + local Altitude=UTILS.MetersToFeet(task.dcstask.params.altitude or self.altitudeCruise) local currUID=self:GetWaypointCurrent().uid From a8132552deb89f1729961e3030fc100537d8adba Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 11 Nov 2021 21:16:48 +0100 Subject: [PATCH 139/141] OPS - Fixed bug in egress coord - Improved barrage mission - improved pickup at legion - fixed bug in SetPathfindingOff function --- Moose Development/Moose/Ops/ArmyGroup.lua | 59 +++++++++++- Moose Development/Moose/Ops/Auftrag.lua | 31 +++++-- Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/Legion.lua | 6 +- Moose Development/Moose/Ops/NavyGroup.lua | 19 ++-- Moose Development/Moose/Ops/OpsGroup.lua | 90 ++++++++++++------- Moose Development/Moose/Ops/OpsTransport.lua | 75 +--------------- .../Moose/Wrapper/Controllable.lua | 3 +- 8 files changed, 156 insertions(+), 129 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 72825b81e..9bdfb59a0 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -313,11 +313,15 @@ function ARMYGROUP:New(group) --- Triggers the FSM event "Rearm". -- @function [parent=#ARMYGROUP] Rearm -- @param #ARMYGROUP self + -- @param Core.Point#COORDINATE Coordinate Coordinate where to rearm. + -- @param #number Formation Formation of the group. --- Triggers the FSM event "Rearm" after a delay. -- @function [parent=#ARMYGROUP] __Rearm -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. + -- @param Core.Point#COORDINATE Coordinate Coordinate where to rearm. + -- @param #number Formation Formation of the group. --- On after "Rearm" event. -- @function [parent=#ARMYGROUP] OnAfterRearm @@ -325,6 +329,8 @@ function ARMYGROUP:New(group) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. + -- @param Core.Point#COORDINATE Coordinate Coordinate where to rearm. + -- @param #number Formation Formation of the group. --- Triggers the FSM event "Rearming". @@ -448,6 +454,40 @@ function ARMYGROUP:AddTaskFireAtPoint(Coordinate, Clock, Radius, Nshots, WeaponT return task end +--- Add a *scheduled* task to fire at a given coordinate. +-- @param #ARMYGROUP self +-- @param #string Clock Time when to start the attack. +-- @param #number Heading Heading min in Degrees. +-- @param #number Alpha Shooting angle in Degrees. +-- @param #number Altitude Altitude in meters. +-- @param #number Radius Radius in meters. Default 100 m. +-- @param #number Nshots Number of shots to fire. Default nil. +-- @param #number WeaponType Type of weapon. Default auto. +-- @param #number Prio Priority of the task. +-- @return Ops.OpsGroup#OPSGROUP.Task The task table. +function ARMYGROUP:AddTaskBarrage(Clock, Heading, Alpha, Altitude, Radius, Nshots, WeaponType, Prio) + + Heading=Heading or 0 + + Alpha=Alpha or 60 + + Altitude=Altitude or 100 + + local distance=Altitude/math.tan(math.rad(Alpha)) + + local a=self:GetVec2() + + local vec2=UTILS.Vec2Translate(a, distance, Heading) + + --local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("Fire At Point",ReadOnly,Text) + + local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, vec2, Radius, Nshots, WeaponType, Altitude) + + local task=self:AddTask(DCStask, Clock, nil, Prio) + + return task +end + --- Add a *waypoint* task to fire at a given coordinate. -- @param #ARMYGROUP self -- @param Core.Point#COORDINATE Coordinate Coordinate of the target. @@ -1043,28 +1083,39 @@ end -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterOutOfAmmo(From, Event, To) - self:T(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) + self:I(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) + + -- Get current task. + local task=self:GetTaskCurrent() + + if task then + if task.dcstask.id=="FireAtPoint" or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then + self:I(self.lid..string.format("Cancelling current %s task because out of ammo!", task.dcstask.id)) + self:TaskCancel(task) + end + end -- Fist, check if we want to rearm once out-of-ammo. + --TODO: IsMobile() check if self.rearmOnOutOfAmmo then local truck, dist=self:FindNearestAmmoSupply(30) if truck then self:T(self.lid..string.format("Found Ammo Truck %s [%s]", truck:GetName(), truck:GetTypeName())) local Coordinate=truck:GetCoordinate() - self:Rearm(Coordinate, Formation) + self:__Rearm(-1, Coordinate) return end end -- Second, check if we want to retreat once out of ammo. if self.retreatOnOutOfAmmo then - self:Retreat() + self:__Retreat(-1) return end -- Third, check if we want to RTZ once out of ammo. if self.rtzOnOutOfAmmo then - self:RTZ() + self:__RTZ(-1) end end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index e35841cb4..5d38b0551 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -105,7 +105,7 @@ -- @field #number artyShots Number of shots fired. -- @field #number artyAltitude Altitude in meters. Can be used for a Barrage. -- @field #number artyHeading Heading in degrees (for Barrage). --- @field #number artyDistance Distance in meters (for barrage). +-- @field #number artyAngle Shooting angle in degrees (for Barrage). -- -- @field #string alert5MissionType Alert 5 mission type. This is the mission type, the alerted assets will be able to carry out. -- @@ -1566,7 +1566,7 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius, Altitude) mission:_TargetFromObject(Target) - mission.artyShots=Nshots or 3 + mission.artyShots=Nshots or nil mission.artyRadius=Radius or 100 mission.artyAltitude=Altitude @@ -1590,13 +1590,13 @@ end --- **[GROUND, NAVAL]** Create an BARRAGE mission. Assigned groups will move to a random coordinate within a given zone and start firing into the air. -- @param #AUFTRAG self -- @param Core.Zone#ZONE Zone The zone where the unit will go. +-- @param #number Heading Heading in degrees. Default random heading [0, 360). +-- @param #number Angle Shooting angle in degrees. Default random [45, 85]. -- @param #number Radius Radius of the shells in meters. Default 100 meters. -- @param #number Altitude Altitude in meters. Default 500 m. --- @param #number Heading Heading in degrees. Default random heading [0, 360). --- @param #number Distance Distance in meters. Default 500 m. -- @param #number Nshots Number of shots to be fired. Default is until ammo is empty (`#nil`). -- @return #AUFTRAG self -function AUFTRAG:NewBARRAGE(Zone, Radius, Altitude, Heading, Distance, Nshots) +function AUFTRAG:NewBARRAGE(Zone, Heading, Angle, Radius, Altitude, Nshots) local mission=AUFTRAG:New(AUFTRAG.Type.BARRAGE) @@ -1606,7 +1606,7 @@ function AUFTRAG:NewBARRAGE(Zone, Radius, Altitude, Heading, Distance, Nshots) mission.artyRadius=Radius or 100 mission.artyAltitude=Altitude mission.artyHeading=Heading - mission.artyDistance=Distance + mission.artyAngle=Angle mission.engageWeaponType=ENUMS.WeaponFlag.Auto @@ -1701,7 +1701,9 @@ function AUFTRAG:NewAMMOSUPPLY(Zone) mission.optionROE=ENUMS.ROE.WeaponHold mission.optionAlarm=ENUMS.AlarmState.Auto - mission.missionFraction=0.9 + mission.missionFraction=1.0 + + mission.missionWaypointRadius=0 mission.categories={AUFTRAG.Category.GROUND} @@ -1723,7 +1725,7 @@ function AUFTRAG:NewFUELSUPPLY(Zone) mission.optionROE=ENUMS.ROE.WeaponHold mission.optionAlarm=ENUMS.AlarmState.Auto - mission.missionFraction=0.9 + mission.missionFraction=1.0 mission.categories={AUFTRAG.Category.GROUND} @@ -2572,6 +2574,17 @@ function AUFTRAG:SetICLS(Channel, Morse, UnitName) return self end +--- Set time interval between mission done and success/failure evaluation. +-- @param #AUFTRAG self +-- @param #number Teval Time in seconds before the mission result is evaluated. Default depends on mission type. +-- @return #AUFTRAG self +function AUFTRAG:SetEvaluationTime(Teval) + + self.dTevaluate=Teval or 60 + + return self +end + --- Get mission type. -- @param #AUFTRAG self -- @return #string Mission type, e.g. "BAI". @@ -4799,7 +4812,7 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) param.altitude=self.artyAltitude param.radius=self.artyRadius param.heading=self.artyHeading - param.distance=self.artyDistance + param.angle=self.artyAngle param.shots=self.artyShots param.weaponTypoe=self.engageWeaponType diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 7a43fc82d..24e435237 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2252,7 +2252,7 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) if not self.group:IsAirborne(true) then -- this should really not happen, either the AUFTRAG is cancelled before the group was airborne or it is stuck at the ground for some reason - self:I(self.lid..string.format("WARNING: Group [%s] is not AIRBORNE ==> RTB event is suspended for 20 sec", self:GetState())) + self:T(self.lid..string.format("WARNING: Group [%s] is not AIRBORNE ==> RTB event is suspended for 20 sec", self:GetState())) allowed=false Tsuspend=-20 local groupspeed = self.group:GetVelocityMPS() diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 8447babaa..6a6704717 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -2261,13 +2261,15 @@ function LEGION:AssignAssetsForTransport(Legions, CargoAssets, NcarriersMin, Nca -- Set pickup zone to spawn zone or airbase if the legion has one that is operational. local pickupzone=legion.spawnzone if legion.airbase and legion:IsRunwayOperational() then - pickupzone=ZONE_AIRBASE:New(legion.airbasename, 4000) + --pickupzone=ZONE_AIRBASE:New(legion.airbasename, 4000) end -- Add TZC from legion spawn zone to deploy zone. - local tpz=Transport:AddTransportZoneCombo(pickupzone, Transport:GetDeployZone()) + local tpz=Transport:AddTransportZoneCombo(nil, pickupzone, Transport:GetDeployZone()) + tpz.PickupAirbase=legion:IsRunwayOperational() and legion.airbase or nil Transport:SetEmbarkZone(legion.spawnzone, tpz) + -- Add cargo assets to transport. for _,_asset in pairs(CargoAssets) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 2ca47fe90..e34549feb 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -454,7 +454,7 @@ end -- @param #NAVYGROUP self -- @return #NAVYGROUP self function NAVYGROUP:SetPathfindingOff() - self:SetPathfinding(true, self.pathCorridor) + self:SetPathfinding(false, self.pathCorridor) return self end @@ -583,7 +583,7 @@ end -- @param #NAVYGROUP self -- @param #string starttime Start time, e.g. "8:00" for eight o'clock. Default now. -- @param #string stoptime Stop time, e.g. "9:00" for nine o'clock. Default 90 minutes after start time. --- @param #number speed Speed in knots during turn into wind leg. +-- @param #number speed Wind speed on deck in knots during turn into wind leg. Default 20 knots. -- @param #boolean uturn If `true` (or `nil`), carrier wil perform a U-turn and go back to where it came from before resuming its route to the next waypoint. If false, it will go directly to the next waypoint. -- @param #number offset Offset angle in degrees, e.g. to account for an angled runway. Default 0 deg. -- @return #NAVYGROUP.IntoWind Turn into window data table. @@ -1041,16 +1041,17 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Depth) if self:IsEngaging() or not self.passedfinalwp then - --[[ - env.info("FF:") - for i=2,#waypoints do - local wp=waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint - self:I(self.lid..string.format("[%d] UID=%d", i-1, wp.uid)) + if self.verbose>=10 then + for i=1,#waypoints do + local wp=waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint + local text=string.format("%s Waypoint [%d] UID=%d speed=%d", self.groupname, i-1, wp.uid or -1, wp.speed) + self:I(self.lid..text) + COORDINATE:NewFromWaypoint(wp):MarkToAll(text) + end end - ]] -- Debug info. - self:I(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Depth=%d m", self.currentwp, n, #waypoints, #self.waypoints, UTILS.MpsToKnots(self.speedWp), self.altWp)) + self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Depth=%d m", self.currentwp, n, #waypoints, #self.waypoints, UTILS.MpsToKnots(self.speedWp), self.altWp)) -- Route group to all defined waypoints remaining. self:Route(waypoints) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 210cb6f55..2211799d1 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3635,15 +3635,8 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Task "Ammo Supply" or "Fuel Supply" mission. --- - -- Parameters. - local zone=Task.dcstask.params.zone --Core.Zone#ZONE - - -- Random coordinate in zone. - local Coordinate=zone:GetRandomCoordinate() - - -- Speed and altitude. - local Speed=UTILS.KmphToKnots(Task.dcstask.params.speed or self.speedCruise) - + -- Just stay put and wait until something happens. + elseif Task.dcstask.id==AUFTRAG.SpecialTask.ALERT5 then --- @@ -3677,13 +3670,16 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- BARRAGE is special! if Task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then - --env.info("FF Barrage") + env.info("FF Barrage") local vec2=self:GetVec2() local param=Task.dcstask.params local heading=param.heading or math.random(1, 360) - local distance=param.distance or 100 + local Altitude=param.altitude or 500 + local Alpha=param.angle or math.random(45, 85) + local distance=Altitude/math.tan(math.rad(Alpha)) local tvec2=UTILS.Vec2Translate(vec2, distance, heading) - DCSTask=CONTROLLABLE.TaskFireAtPoint(nil, tvec2, param.radius, param.shots, param.weaponType, param.altitude) + self:T(self.lid..string.format("Barrage: Shots=%s, Altitude=%d m, Angle=%d°, heading=%03d°, distance=%d m", tostring(param.shots), Altitude, Alpha, heading, distance)) + DCSTask=CONTROLLABLE.TaskFireAtPoint(nil, tvec2, param.radius, param.shots, param.weaponType, Altitude) else DCSTask=Task.dcstask end @@ -3892,8 +3888,8 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) self:T(self.lid.."Taske DONE Task_Land_At ==> Wait") self:Wait(20, 100) else - self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 0 sec") - self:_CheckGroupDone() + self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec") + self:_CheckGroupDone(1) end end @@ -3950,6 +3946,11 @@ function OPSGROUP:RemoveMission(Mission) if Task then self:RemoveTask(Task) end + + -- Take care of a paused mission. + if self.missionpaused and self.missionpaused.auftragsnummer==Mission.auftragsnummer then + self.missionpaused=nil + end -- Remove mission from queue. table.remove(self.missionqueue, i) @@ -4213,7 +4214,28 @@ function OPSGROUP:onafterMissionStart(From, Event, To, Mission) Mission:__Started(3) -- Route group to mission zone. - self:RouteToMission(Mission, 3) + if self.speedMax>3.6 then + + self:RouteToMission(Mission, 3) + + else + --- + -- IMMOBILE Group + --- + + env.info("FF Immobile GROUP") + + -- Add waypoint task. UpdateRoute is called inside. + local Clock=Mission.Tpush and UTILS.SecondsToClock(Mission.Tpush) or 5 + local Task=self:AddTask(Mission.DCStask, Clock, Mission.name, Mission.prio, Mission.duration) + Task.ismission=true + + -- Set waypoint task. + Mission:SetGroupWaypointTask(self, Task) + + -- Execute task. This calls mission execute. + self:__TaskExecute(3, Task) + end end @@ -4285,7 +4307,9 @@ function OPSGROUP:onafterUnpauseMission(From, Event, To) local mission=self:GetMissionByID(self.missionpaused.auftragsnummer) - self:MissionStart(mission) + if mission then + self:MissionStart(mission) + end self.missionpaused=nil else @@ -4512,7 +4536,7 @@ function OPSGROUP:RouteToMission(mission, delay) end -- Get ingress waypoint. - if mission.type==AUFTRAG.Type.PATROLZONE or mission.type==AUFTRAG.Type.BARRAGE then + if mission.type==AUFTRAG.Type.PATROLZONE or mission.type==AUFTRAG.Type.BARRAGE or mission.type==AUFTRAG.Type.AMMOSUPPLY or mission.type.FUELSUPPLY then local zone=mission.engageTarget:GetObject() --Core.Zone#ZONE waypointcoord=zone:GetRandomCoordinate(nil , nil, surfacetypes) elseif mission.type==AUFTRAG.Type.ONGUARD then @@ -4597,7 +4621,7 @@ function OPSGROUP:RouteToMission(mission, delay) end -- UID of this waypoint. - local uid=self:GetWaypointCurrent().uid + --local uid=self:GetWaypointCurrent().uid -- Add waypoint. local waypoint=nil --#OPSGROUP.Waypoint @@ -4625,16 +4649,16 @@ function OPSGROUP:RouteToMission(mission, delay) if egresscoord then --egresscoord:MarkToAll(string.format("Egress Mission %s alt=%d m", mission:GetName(), waypointcoord.y)) -- Add waypoint. - local waypoint=nil --#OPSGROUP.Waypoint + local Ewaypoint=nil --#OPSGROUP.Waypoint if self:IsFlightgroup() then - waypoint=FLIGHTGROUP.AddWaypoint(self, egresscoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) + Ewaypoint=FLIGHTGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) elseif self:IsArmygroup() then - waypoint=ARMYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, uid, mission.optionFormation, false) + Ewaypoint=ARMYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, mission.optionFormation, false) elseif self:IsNavygroup() then - waypoint=NAVYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) + Ewaypoint=NAVYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) end - waypoint.missionUID=mission.auftragsnummer - mission:SetGroupEgressWaypointUID(self, waypoint.uid) + Ewaypoint.missionUID=mission.auftragsnummer + mission:SetGroupEgressWaypointUID(self, Ewaypoint.uid) end --- @@ -7115,7 +7139,7 @@ function OPSGROUP:onafterPickup(From, Event, To) else -- Aircraft is already parking at the pickup airbase. - ready4loading=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() + ready4loading=self.currbase and airbasePickup and self.currbase:GetName()==airbasePickup:GetName() and self:IsParking() -- If a helo is landed in the zone, we also are ready for loading. if ready4loading==false and self.isHelo and self:IsLandedAt() and inzone then @@ -7503,7 +7527,7 @@ function OPSGROUP:onafterTransport(From, Event, To) local Zone=self.cargoTZC.DeployZone -- Check if already in deploy zone. - local inzone=self:IsInZone(Zone) --Zone:IsCoordinateInZone(self:GetCoordinate()) + local inzone=self:IsInZone(Zone) -- Deploy airbase (if any). local airbaseDeploy=self.cargoTZC.DeployAirbase --Wrapper.Airbase#AIRBASE @@ -7514,7 +7538,7 @@ function OPSGROUP:onafterTransport(From, Event, To) ready2deploy=inzone else -- Aircraft is already parking at the pickup airbase. - ready2deploy=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() + ready2deploy=self.currbase and airbaseDeploy and self.currbase:GetName()==airbaseDeploy:GetName() and self:IsParking() -- If a helo is landed in the zone, we also are ready for loading. if ready2deploy==false and (self.isHelo or self.isVTOL) and self:IsLandedAt() and inzone then @@ -7538,7 +7562,7 @@ function OPSGROUP:onafterTransport(From, Event, To) if self:IsArmygroup() or self:IsFlightgroup() then surfacetypes={land.SurfaceType.LAND} elseif self:IsNavygroup() then - surfacetypes={land.SurfaceType.WATER} + surfacetypes={land.SurfaceType.WATER, land.SurfaceType.SHALLOW_WATER} end -- Coord where the carrier goes to unload. @@ -9235,8 +9259,10 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) -- Temporary Waypoint --- - if opsgroup:IsNavygroup() or opsgroup:IsArmygroup() then + if (opsgroup:IsNavygroup() or opsgroup:IsArmygroup()) and opsgroup.currentwp==#opsgroup.waypoints then --TODO: not sure if this works with FLIGHTGROUPS + + -- Removing this for now. opsgroup:Cruise() end @@ -9279,7 +9305,7 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) if opsgroup.cargoTZC.PickupAirbase then -- Pickup airbase specified. Land there. - env.info(opsgroup.lid.."FF Land at Pickup Airbase") + --env.info(opsgroup.lid.."FF Land at Pickup Airbase") opsgroup:LandAtAirbase(opsgroup.cargoTZC.PickupAirbase) else -- Land somewhere in the pickup zone. Only helos can do that. @@ -9310,7 +9336,7 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) if opsgroup.cargoTZC.DeployAirbase then -- Pickup airbase specified. Land there. - env.info(opsgroup.lid.."FF Land at Deploy Airbase") + --env.info(opsgroup.lid.."FF Land at Deploy Airbase") opsgroup:LandAtAirbase(opsgroup.cargoTZC.DeployAirbase) else -- Land somewhere in the pickup zone. Only helos can do that. @@ -9319,7 +9345,7 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) end else - coordinate=opsgroup:GetCoordinate() + local coordinate=opsgroup:GetCoordinate() opsgroup:LandAt(coordinate, 60*60) end diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 9f9412e13..67ceb16d0 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -243,7 +243,7 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) self.NcarrierDead=0 -- Set default TZC. - self.tzcDefault=self:AddTransportZoneCombo(PickupZone, DeployZone, CargoGroups) + self.tzcDefault=self:AddTransportZoneCombo(CargoGroups, PickupZone, DeployZone) -- FMS start state is PLANNED. self:SetStartState(OPSTRANSPORT.Status.PLANNED) @@ -465,11 +465,11 @@ end --- Add pickup and deploy zone combination. -- @param #OPSTRANSPORT self +-- @param Core.Set#SET_GROUP CargoGroups Groups to be transported as cargo. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. -- @param Core.Zone#ZONE PickupZone Zone where the troops are picked up. -- @param Core.Zone#ZONE DeployZone Zone where the troops are picked up. --- @param Core.Set#SET_GROUP CargoGroups Groups to be transported as cargo. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. -- @return #OPSTRANSPORT.TransportZoneCombo Transport zone table. -function OPSTRANSPORT:AddTransportZoneCombo(PickupZone, DeployZone, CargoGroups) +function OPSTRANSPORT:AddTransportZoneCombo(CargoGroups, PickupZone, DeployZone) -- Increase counter. self.tzcCounter=self.tzcCounter+1 @@ -1183,14 +1183,13 @@ function OPSTRANSPORT:AddConditionStart(ConditionFunction, ...) return self end ---- Add path used for transportation from the pickup to the deploy zone for **ground** and **naval** carriers. +--- Add path used for transportation from the pickup to the deploy zone. -- If multiple paths are defined, a random one is chosen. The path is retrieved from the waypoints of a given group. -- **NOTE** that the category group defines for which carriers this path is valid. -- For example, if you specify a GROUND group to provide the waypoints, only assigned GROUND carriers will use the -- path. -- @param #OPSTRANSPORT self -- @param Wrapper.Group#GROUP PathGroup A (late activated) GROUP defining a transport path by their waypoints. --- @param #boolean Reversed If `true`, add waypoints of group in reversed order. -- @param #number Radius Randomization radius in meters. Default 0 m. -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. -- @return #OPSTRANSPORT self @@ -1204,10 +1203,8 @@ function OPSTRANSPORT:AddPathTransport(PathGroup, Reversed, Radius, TransportZon end local path={} --#OPSTRANSPORT.Path - path.coords={} path.category=PathGroup:GetCategory() path.radius=Radius or 0 - path.reverse=Reversed path.waypoints=PathGroup:GetTaskRoute() -- TODO: Check that only flyover waypoints are given for aircraft. @@ -1252,70 +1249,6 @@ function OPSTRANSPORT:_GetPathTransport(Category, TransportZoneCombo) return nil end - ---- Add path used to go to the pickup zone. If multiple paths are defined, a random one is chosen. --- @param #OPSTRANSPORT self --- @param Wrapper.Group#GROUP PathGroup A (late activated) GROUP defining a transport path by their waypoints. --- @param #boolean Reversed If `true`, add waypoints of group in reversed order. --- @param #number Radius Randomization radius in meters. Default 0 m. --- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. --- @return #OPSTRANSPORT self -function OPSTRANSPORT:AddPathPickup(PathGroup, Reversed, Radius, TransportZoneCombo) - - -- Use default TZC if no transport zone combo is provided. - TransportZoneCombo=TransportZoneCombo or self.tzcDefault - - if type(PathGroup)=="string" then - PathGroup=GROUP:FindByName(PathGroup) - end - - local path={} --#OPSTRANSPORT.Path - path.category=PathGroup:GetCategory() - path.radius=Radius or 0 - path.reverse=Reversed - path.waypoints=PathGroup:GetTaskRoute() - - -- Add path. - table.insert(TransportZoneCombo.PickupPaths, path) - - return self -end - ---- Get a path for pickup. --- @param #OPSTRANSPORT self --- @param #number Category Group category. --- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport Zone combo. --- @return #table The path of COORDINATEs. -function OPSTRANSPORT:_GetPathPickup(Category, TransportZoneCombo) - - -- Use default TZC if no transport zone combo is provided. - TransportZoneCombo=TransportZoneCombo or self.tzcDefault - - local Paths=TransportZoneCombo.PickupPaths - - if Paths and #Paths>0 then - - local paths={} - - for _,_path in pairs(Paths) do - local path=_path --#OPSTRANSPORT.Path - if path.category==Category then - table.insert(paths, path) - end - end - - if #paths>0 then - - local path=paths[math.random(#paths)] --#OPSTRANSPORT.Path - - return path - end - end - - return nil -end - - --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index ab5f35250..35ede51b9 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1441,7 +1441,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti y = Vec2.y, zoneRadius = Radius, radius = Radius, - expendQty = 100, -- dummy value + expendQty = 1, -- dummy value expendQtyEnabled = false, alt_type = ASL and 0 or 1 } @@ -1460,6 +1460,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti DCSTask.params.weaponType=WeaponType end + --env.info("FF fireatpoint") --BASE:I(DCSTask) return DCSTask From b9b5938a911397973fafc2ef16f2e6b5304f467c Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 13 Nov 2021 20:08:22 +0100 Subject: [PATCH 140/141] OPS - Fixed refuel system always true. --- Moose Development/Moose/Ops/Cohort.lua | 2 +- Moose Development/Moose/Ops/Legion.lua | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index f23704450..3dde5b505 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -75,7 +75,7 @@ COHORT = { --- COHORT class version. -- @field #string version -COHORT.version="0.0.2" +COHORT.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 6a6704717..d85b696da 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -1947,7 +1947,19 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, local InRange=(RangeMax and math.max(RangeMax, cohort.engageRange) or cohort.engageRange) >= TargetDistance -- Has the requested refuelsystem? - local Refuel=RefuelSystem and RefuelSystem==cohort.tankerSystem or true + local Refuel=RefuelSystem~=nil and (RefuelSystem==cohort.tankerSystem) or true + + -- STRANGE: Why did the above line did not give the same result?! Above Refuel is always true! + local Refuel=true + if RefuelSystem then + if cohort.tankerSystem then + Refuel=RefuelSystem==cohort.tankerSystem + else + Refuel=false + end + end + + --env.info(string.format("Cohort=%s: RefuelSystem=%s, TankerSystem=%s ==> Refuel=%s", cohort.name, tostring(RefuelSystem), tostring(cohort.tankerSystem), tostring(Refuel))) -- Is capable of the mission type? local Capable=AUFTRAG.CheckMissionCapability({MissionTypeRecruit}, cohort.missiontypes) From d62bb59df7ef161600935623a22786a7d594cc22 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 14 Nov 2021 22:12:41 +0100 Subject: [PATCH 141/141] OPS - Clean up tracing --- Moose Development/Moose/Ops/Auftrag.lua | 2 ++ Moose Development/Moose/Ops/Commander.lua | 5 +++-- Moose Development/Moose/Ops/OpsGroup.lua | 16 ++++++++-------- Moose Development/Moose/Ops/OpsTransport.lua | 2 +- Moose Development/Moose/Wrapper/Positionable.lua | 8 ++++---- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 5d38b0551..96da3ce35 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1516,6 +1516,7 @@ function AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet, DropoffCoordinate, PickupC return mission end +--[[ --- **[AIR, GROUND, NAVAL]** Create a OPS TRANSPORT mission. -- @param #AUFTRAG self @@ -1552,6 +1553,7 @@ function AUFTRAG:NewOPSTRANSPORT(CargoGroupSet, PickupZone, DeployZone) return mission end +]] --- **[GROUND, NAVAL]** Create an ARTY mission. -- @param #AUFTRAG self diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 51747f4b1..894718a78 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -41,8 +41,9 @@ -- -- # Constructor -- --- A new CHIEF object is created with the @{#CHIEF.New}(*Coalition, Alias*) function, where the parameter *Coalition* is the coalition side. --- It can be `coalition.side.RED`, `coalition.side.BLUE` or `coalition.side.NEUTRAL`. This parameter is mandatory. +-- A new COMMANDER object is created with the @{#COMMANDER.New}(*Coalition, Alias*) function, where the parameter *Coalition* is the coalition side. +-- It can be `coalition.side.RED`, `coalition.side.BLUE` or `coalition.side.NEUTRAL`. This parameter is mandatory! +-- -- The second parameter *Alias* is optional and can be used to give the COMMANDER a "name", which is used for output in the dcs.log file. -- -- local myCommander=COMANDER:New(coalition.side.BLUE, "General Patton") diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 2211799d1..58766d671 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1610,13 +1610,13 @@ function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) if self.legion and not NoEventRemoveUnit then -- Add asset back in 10 seconds. - self:I(self.lid..string.format("Despawning Group by adding asset to LEGION!")) + self:T(self.lid..string.format("Despawning Group by adding asset to LEGION!")) self.legion:AddAsset(self.group, 1) return end -- Debug info. - self:I(self.lid..string.format("Despawning Group!")) + self:T(self.lid..string.format("Despawning Group!")) -- DCS group obejct. local DCSGroup=self:GetDCSGroup() @@ -1822,7 +1822,7 @@ function OPSGROUP:RadioTransmission(Text, Delay) self.msrs:SetModulations(modu) -- Debug info. - self:I(self.lid..string.format("Radio transmission on %.3f MHz %s: %s", freq, UTILS.GetModulationName(modu), Text)) + self:T(self.lid..string.format("Radio transmission on %.3f MHz %s: %s", freq, UTILS.GetModulationName(modu), Text)) self.msrs:PlayText(Text) end @@ -4796,14 +4796,14 @@ function OPSGROUP:onbeforeWait(From, Event, To, Duration) -- Check for a current task. if self.taskcurrent>0 then - self:I(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!")) + self:T(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!")) Tsuspend=-30 allowed=false end -- Check for a current transport assignment. if self.cargoTransport then - self:I(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) + self:T(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) Tsuspend=-30 allowed=false end @@ -5142,7 +5142,7 @@ function OPSGROUP:onafterGotoWaypoint(From, Event, To, UID, Speed) Speed=Speed or self:GetSpeedToWaypoint(n) -- Debug message - self:I(self.lid..string.format("Goto Waypoint UID=%d index=%d from %d at speed %.1f knots", UID, n, self.currentwp, Speed)) + self:T(self.lid..string.format("Goto Waypoint UID=%d index=%d from %d at speed %.1f knots", UID, n, self.currentwp, Speed)) -- Update the route. self:__UpdateRoute(0.1, n, nil, Speed) @@ -6054,7 +6054,7 @@ end function OPSGROUP:onafterDead(From, Event, To) -- Debug info. - self:I(self.lid..string.format("Group dead at t=%.3f", timer.getTime())) + self:T(self.lid..string.format("Group dead at t=%.3f", timer.getTime())) -- Is dead now. self.isDead=true @@ -11292,7 +11292,7 @@ function OPSGROUP:_GetDetectedTarget() local righttype=false for _,attribute in pairs(self.engagedetectedTypes) do local gotit=group:HasAttribute(attribute, false) - self:I(self.lid..string.format("Group %s has attribute %s = %s", group:GetName(), attribute, tostring(gotit))) + self:T(self.lid..string.format("Group %s has attribute %s = %s", group:GetName(), attribute, tostring(gotit))) if gotit then righttype=true break diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 67ceb16d0..c15154747 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -1260,7 +1260,7 @@ function OPSTRANSPORT:SetCarrierTransportStatus(CarrierGroup, Status) local oldstatus=self:GetCarrierTransportStatus(CarrierGroup) -- Debug info. - self:I(self.lid..string.format("New carrier transport status for %s: %s --> %s", CarrierGroup:GetName(), oldstatus, Status)) + self:T(self.lid..string.format("New carrier transport status for %s: %s --> %s", CarrierGroup:GetName(), oldstatus, Status)) -- Set new status. self.carrierTransportStatus[CarrierGroup.groupname]=Status diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 6dc5ddf7b..f1a704cc3 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1494,18 +1494,18 @@ do -- Cargo -- Fuel. The descriptor provides the max fuel mass in kg. This needs to be multiplied by the relative fuel amount to calculate the actual fuel mass on board. local massFuelMax=Desc.fuelMassMax or 0 - local relFuel=self:GetFuel() or 1.0 + local relFuel=math.max(self:GetFuel() or 1.0, 1.0) -- We take 1.0 as max in case of external fuel tanks. local massFuel=massFuelMax*relFuel -- Number of soldiers according to DCS function - local troopcapacity=self:GetTroopCapacity() or 0 + --local troopcapacity=self:GetTroopCapacity() or 0 -- Calculate max cargo weight, which is the max (takeoff) weight minus the empty weight minus the actual fuel weight. local CargoWeight=massMax-(massEmpty+massFuel) -- Debug info. - self:I(string.format("Setting Cargo bay weight limit [%s]=%d kg (Mass max=%d, empty=%d, fuelMax=%d kg (rel=%.3f), fuel=%d kg", TypeName, CargoWeight, massMax, massEmpty, massFuelMax, relFuel, massFuel)) - self:I(string.format("Descent Troop Capacity=%d ==> %d kg (for 95 kg soldier)", troopcapacity, troopcapacity*95)) + self:T(string.format("Setting Cargo bay weight limit [%s]=%d kg (Mass max=%d, empty=%d, fuelMax=%d kg (rel=%.3f), fuel=%d kg", TypeName, CargoWeight, massMax, massEmpty, massFuelMax, relFuel, massFuel)) + --self:T(string.format("Descent Troop Capacity=%d ==> %d kg (for 95 kg soldier)", troopcapacity, troopcapacity*95)) -- Set value. self.__.CargoBayWeightLimit = CargoWeight