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