From 75216793f9f845e31e90c4bd9c03584eb3563543 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 26 Feb 2020 17:03:23 +0100 Subject: [PATCH] Ops --- .../Moose/Functional/Warehouse.lua | 2 +- Moose Development/Moose/Ops/AirWing.lua | 281 +++++++++++------- Moose Development/Moose/Ops/Auftrag.lua | 12 +- Moose Development/Moose/Ops/FlightGroup.lua | 74 +++-- 4 files changed, 228 insertions(+), 141 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 997252e6b..167532f4c 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -283,7 +283,7 @@ -- -- Assets of the warehouse can be requested by other MOOSE warehouses. A request will first be scrutinized to check if can be fulfilled at all. If the request is valid, it is -- put into the warehouse queue and processed as soon as possible. - +-- -- Requested assets spawn in various "Rule of Engagement Rules" (ROE) and Alerts modes. If your assets will cross into dangerous areas, be sure to change these states. You can do this in @{#WAREHOUSE:OnAfterAssetSpawned}(*From, *Event, *To, *group, *asset, *request)) function. -- -- Initial Spawn states is as follows: diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 20c5a0f7b..bcd757fcb 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -16,11 +16,11 @@ -- @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 #string warehousename The name of the warehouse unit/static. -- @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. -- @extends Functional.Warehouse#WAREHOUSE --- Be surprised! @@ -36,12 +36,13 @@ -- @field #AIRWING AIRWING = { ClassName = "AIRWING", + Debug = false, lid = nil, - warehousename = nil, menu = nil, squadrons = nil, missionqueue = {}, payloads = {}, + payloadcounter = nil, } --- Squadron data. @@ -62,16 +63,17 @@ AIRWING = { --- Payload data. -- @type AIRWING.Payload +-- @field #string unitname Name of the unit this pylon was extracted from. -- @field #string aircrafttype Type of aircraft, which can use this payload. -- @field #table missiontypes Mission types for which this payload can be used. -- @field #table pylons Pylon data extracted for the unit template. -- @field #number navail Number of available payloads of this type. --- @field #number nreserved Number of payloads reserved. +-- @field #boolean unlimited If true, this payload is unlimited and does not get consumed. --- AIRWING class version. -- @field #string version -AIRWING.version="0.1.1" +AIRWING.version="0.1.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -79,11 +81,11 @@ AIRWING.version="0.1.1" -- DONE: Add squadrons to warehouse. -- TODO: Make special request to transfer squadrons to anther airwing (or warehouse). --- TODO: Build mission queue. --- TODO: Find way to start missions. --- TODO: Check if missions are accomplished. +-- DONE: Build mission queue. +-- DONE: Find way to start missions. +-- TODO: Check if missions are accomplished. -- TODO: Paylods as assets. --- TODO: Cargo as assets. +-- TODO: Spawn in air or hot. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -107,6 +109,7 @@ function AIRWING:New(warehousename, airwingname) -- Squadrons. self.squadrons={} + self.payloadcounter=0 -- Set some string id for output to DCS.log file. self.lid=string.format("AIRWING %s | ", self.alias) @@ -176,65 +179,82 @@ function AIRWING:AddSquadron(SquadronName, MissionTypes, Livery, Skill) return squadron end ---- Add a payload to air wing resources. +--- Add a **new** payload to air wing resources. -- @param #AIRWING self --- @param #string AircraftType Type of aircraft which can use this payload. --- @param #table Pylons The pylons table from UNIT:FindByName("Unitname"):GetTemplatePylons(). +-- @param #string UnitName Name of the unit, the payload is extracted from. -- @param #table MissionTypes Mission types this payload can be used for. -- @param #number Npayloads Number of payloads to add to the airwing resources. Default 99 (which should be enough for most scenarios). -function AIRWING:AddPayload(AircraftType, Pylons, MissionTypes, Npayloads) +function AIRWING:NewPayload(UnitName, MissionTypes, Npayloads) - local payload=nil --#AIRWING.Payload - if self.payloads[AircraftType] then - payload=self.payloads[AircraftType] - else - payload={} + local unit=UNIT:FindByName(UnitName) + + if unit then + + local unitpayload=unit:GetTemplatePayload() + + local payload={} --#AIRWING.Payload + + payload.unitname=UnitName + payload.aircrafttype=unit:GetTypeName() + payload.missiontypes=MissionTypes or {} + payload.pylons=unitpayload payload.navail=Npayloads or 99 - payload.navail=Npayloads or 99 - payload.missiontypes=MissionTypes - payload.aircrafttype=AircraftType - payload.pylons=Pylons - payload.nreserved=0 - self.payloads[AircraftType]=payload + payload.unlimited=false + + table.insert(self.payloads, payload) end - - - --[[ - payload={} --#AIRWING.Payload - - payload.navail=Npayloads or 99 - payload.missiontypes=MissionTypes - payload.aircrafttype=unit:GetTypeName() - payload.pylons=unit:GetTemplatePylons() - payload.nreserved=0 - - --TODO: maybe add fuel, chaff and gun? - - self.payloads[UnitName]=payload - - end - - end - - ]] end ---- Add a payload to air wing resources. +--- Fetch a payload from the airwing resources for a given unit and mission type. -- @param #AIRWING self --- @param #string UnitName Name of the unit from which the payload was extracted. --- @return #AIRWING.Payload -function AIRWING:GetPayloadByName(UnitName) - return self.payloads[UnitName] +-- @param #string UnitType The type of the unit. +-- @param #string MissionType The mission type. +-- @return #AIRWING.Payload Payload table or *nil*. +function AIRWING:FetchPayloadFromStock(UnitType, MissionType) + + for _,_payload in pairs(self.payloads) do + local payload=_payload --#AIRWING.Payload + + + -- Check right type, mission and available. + if payload.aircrafttype==UnitType and payload.navail>0 and self:CheckMissionType(MissionType, payload.missiontypes) then + + -- Consume if not unlimited. + if not payload.unlimited then + payload.navail=payload.navail-1 + end + + -- Return a copy of the table. + return payload + end + + end + + return nil end ---- Add a payload to air wing resources. +--- Return payload from asset back to stock. -- @param #AIRWING self -- @param #AIRWING.SquadronAsset asset The squadron asset. --- @param #string MissionType The mission type. --- @return #AIRWING.Payload -function AIRWING:GetPayloadForAsset(asset, MissionType) - return self.payloads[UnitName] +function AIRWING:ReturnPayloadFromAsset(asset) + + local payload=asset.payload + + if payload then + + -- Increase count if not unlimited. + if not payload.unlimited then + payload.navail=payload.navail+1 + end + + -- Remove asset payload. + asset.payload=nil + + else + self:E(self.lid.."ERROR: asset had no payload attached!") + end + end @@ -346,8 +366,8 @@ function AIRWING:onafterStatus(From, Event, To) ------------------ local text=string.format("Missions Total=%d:", #self.missionqueue) for i,_mission in pairs(self.missionqueue) do - local mission=_mission --#AIRWING.Missiondata - text=text..string.format("\n[%d] %s: Status=%s, Nassets=%d, Prio=%d, ID=%d (%s)", i, mission.type, mission.status, mission.nassets, mission.prio, mission.MID, mission.name) + local mission=_mission --Ops.Auftrag#AUFTRAG + text=text..string.format("\n[%d] %s: Status=%s, Nassets=%d, Prio=%d, ID=%d (%s)", i, mission.type, mission.status, mission.nassets, mission.prio, mission.auftragsnummer, mission.name) end self:I(self.lid..text) @@ -402,7 +422,7 @@ end --- Get next mission. -- @param #AIRWING self --- @return #AIRWING.Missiondata Next mission or *nil*. +-- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. function AIRWING:_GetNextMission() -- Number of missions. @@ -428,72 +448,94 @@ function AIRWING:_GetNextMission() -- Look for first task that is not accomplished. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG + + -- Firstly, check if mission is due? + if mission.status==AUFTRAG.Status.SCHEDULED and time>=mission.Tstart then - -- Check if airwing can do the mission and gather required assets. - local can, assets=self:CanMission(mission.type, mission.nassets) - - -- Debug output. - self:T3({self.lid.."Mission check:", MissionStatusScheduled=mission.status==AUFTRAG.Status.SCHEDULED, TstartPassed=time>=mission.Tstart, CanMission=can, Nassets=#assets}) - - -- Check that mission is still scheduled, time has passed and enough assets are available. - if mission.status==AUFTRAG.Status.SCHEDULED and time>=mission.Tstart and can then - - -- TODO: check that mission.assets table is clean. - mission.assets=mission.assets or {} - - -- Assign assets to mission. - for i=1,mission.nassets do + -- Check if airwing can do the mission and gather required assets. + local can, assets=self:CanMission(mission.type, mission.nassets) - local asset=assets[i] --#AIRWING.SquadronAsset - - asset.payload=self:GetPayloadForAsset(asset, mission.type) - if not asset.payload then - self:E("No payload for asset! This should not happen!") + -- Debug output. + self:T3({self.lid.."Mission check:", MissionStatusScheduled=mission.status==AUFTRAG.Status.SCHEDULED, TstartPassed=time>=mission.Tstart, CanMission=can, Nassets=#assets}) + + -- Check that mission is still scheduled, time has passed and enough assets are available. + if can then + + -- Optimize the asset selection. Most useful assets will come first. + self:_OptimizeAssetSelection(assets, mission) + + -- TODO: check that mission.assets table is clean. + mission.assets=mission.assets or {} + + -- Assign assets to mission. + for i=1,mission.nassets do + local asset=assets[i] --#AIRWING.SquadronAsset + + asset.payload=self:FetchPayloadFromStock(asset.unittype, mission.type) + + if not asset.payload then + self:E("No payload for asset! This should not happen!") + end + + table.insert(mission.assets, asset) + + asset.mission=mission end - table.insert(mission.assets, asset) - asset.mission=mission + return mission end - - return mission - end - end + + end -- mission due? + end -- mission loop return nil end ---- Get next mission. +--- Optimize chosen assets for the mission at hand. -- @param #AIRWING self -- @param #table assets Table of (unoptimized) assets. --- @return #AIRWING.Missiondata Next mission or *nil*. +-- @param Ops.Auftrag#AUFTRAG Next mission or *nil*. function AIRWING:_OptimizeAssetSelection(assets, Mission) local TargetCoordinate=Mission:GetTargetCoordinate() local dStock=self:GetCoordinate():Get2DDistance(TargetCoordinate) + for _,_asset in pairs(assets) do + local asset=_asset --#AIRWING.SquadronAsset + + if asset.spawned then + local group=GROUP:FindByName(asset.spawngroupname) + asset.dist=group:GetCoordinate():Get2DDistance(TargetCoordinate) + else + asset.dist=dStock + end + + end + -- Sort results table wrt distacance. - local function _sort(a, b) + local function optimize(a, b) local assetA=a --#AIRWING.SquadronAsset local assetB=b --#AIRWING.SquadronAsset - local dA=dStock - if assetA.spawned then - local group=GROUP:FindByName(assetA.spawngroupname) - dA=group:GetCoordinate():Get2DDistance(TargetCoordinate) - end - - local dB=dStock - if assetB.spawned then - local group=GROUP:FindByName(assetA.spawngroupname) - dB=group:GetCoordinate():Get2DDistance(TargetCoordinate) - 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 (dA0 then + local Task=self:GetTaskCurrent() + + if Task then -- Clear all tasks. self.group:ClearTasks() -- Task status executing. - Task.status=1 --FLIGHTGROUP.TaskStatus.PAUSED + Task.status=FLIGHTGROUP.TaskStatus.PAUSED end end +--- On after "TaskUnPause" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function FLIGHTGROUP:onafterTaskUnpause(From, Event, To) + + --self.task +end + --- On after "TaskCancel" event. Cancels the current task. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -2745,14 +2752,17 @@ function FLIGHTGROUP:onafterTaskCancel(From, Event, To) local task=self:GetTaskCurrent() if task then + + -- Debug info. local text=string.format("Task %s ID=%d cancelled.", task.description, task.id) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) - -- Clear tasks. - --self.group:ClearTasks() - --self.group:PopCurrentTask() + + -- Set stop flag. task.stopflag:Set(1) - self:TaskDone(task) + + -- Call task done. + self:TaskDone(task) else local text=string.format("WARNING: No current task to cancel!") MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) @@ -2943,7 +2953,6 @@ end --- Function called when a task is executed. --@param Wrapper.Group#GROUP group Group that reached the holding zone. ---@param #FLIGHTGROUP.Mission --@param #FLIGHTGROUP flightgroup Flight group. --@param #FLIGHTGROUP.Task task Task. function FLIGHTGROUP._TaskExecute(group, flightgroup, task) @@ -2960,7 +2969,6 @@ end --- Function called when a task is done. --@param Wrapper.Group#GROUP group Group that reached the holding zone. ---@param #FLIGHTGROUP.Mission --@param #FLIGHTGROUP flightgroup Flight group. --@param #FLIGHTGROUP.Task task Task. function FLIGHTGROUP._TaskDone(group, flightgroup, task) @@ -2984,7 +2992,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Mission Mission The mission table. +-- @param Ops.Auftrag#AUFTRAG Mission The mission table. function FLIGHTGROUP:onbeforeMissionStart(From, Event, To, Mission) self:I(self.lid..string.format("FF Starting mission %s, FSM=%s, LateActivated=%s, UnControlled=%s", tostring(Mission.name), self:GetState(), tostring(self:IsLateActivated()), tostring(self:IsUncontrolled()))) @@ -3016,7 +3024,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Mission Mission The mission table. +-- @param Ops.Auftrag#AUFTRAG Mission The mission table. function FLIGHTGROUP:onafterMissionStart(From, Event, To, Mission) local text=string.format("Starting Mission %s", tostring(Mission.name)) @@ -3041,7 +3049,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Mission Mission +-- @param Ops.Auftrag#AUFTRAG Mission function FLIGHTGROUP:onafterMissionDone(From, Event, To, Mission) -- TODO: evaluate mission success or failure! complicated! @@ -3751,8 +3759,8 @@ function FLIGHTGROUP:AddWaypointAir(coordinate, wpnumber, speed, updateroute) -- Shift all mission waypoints after the inserted waypoint. for _,_mission in pairs(self.missionqueue) do - local mission=_mission --#FLIGHTGROUP.Mission - if mission.status==AUFTRAG.Status.SCHEDULED and mission.waypoint>=wpnumber then + local mission=_mission --Ops.Auftrag#AUFTRAG + if mission.status==AUFTRAG.Status.SCHEDULED and mission.waypointindex and mission.waypointindex>=wpnumber then mission.waypointindex=mission.waypointindex+1 end end