From cc567cf342a432d4bcf610e9b2605884edbd899e Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 27 Feb 2020 16:56:27 +0100 Subject: [PATCH] Ops --- Moose Development/Moose/Ops/AirWing.lua | 116 ++- Moose Development/Moose/Ops/Auftrag.lua | 19 +- Moose Development/Moose/Ops/FlightGroup.lua | 916 +++++++++++--------- 3 files changed, 593 insertions(+), 458 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index bcd757fcb..537d2a50a 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -595,7 +595,7 @@ function AIRWING:onafterMissionRequest(From, Event, To, Mission) -- Set mission status to ASSIGNED. Mission.status=AUFTRAG.Status.ASSIGNED - --TODO: Some assets might already be spawned and even on a different mission (orbit). + -- Some assets might already be spawned and even on a different mission (orbit). -- Need to dived to set into spawned and instock assets and handle the other -- Assets to be requested @@ -603,18 +603,26 @@ function AIRWING:onafterMissionRequest(From, Event, To, Mission) for _,_asset in pairs(Mission.assets) do local asset=_asset --#AIRWING.SquadronAsset + if asset.spawned then - - + if asset.flightgroup then + --TODO: cancel current mission if there is any! + asset.flightgroup:AddMission(asset.mission, asset.mission.waypointcoord, asset.mission.waypointindex) + else + --TODO: create flightgroup. should already be there actually! + end else + -- These assets need to be requested and spawned. table.insert(areq, asset) end end - --TODO: Check that mission prio is same as warehouse prio (small=high or the other way around). - self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, areq, #areq, nil, nil, Mission.prio, tostring(Mission.auftragsnummer)) + -- Add request to airwing warehouse. + if #areq>0 then + self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, areq, #areq, nil, nil, Mission.prio, tostring(Mission.auftragsnummer)) + end end @@ -659,11 +667,22 @@ 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) + + -- Create a flight group. + self:_CreateFlightGroup(asset) - local mid=tonumber(request.assignment) + -- Get Mission (if any). + local mission=self:GetMissionByID(request.assignment) + + -- Add mission to flightgroup queue. + if mission then - -- Set mission. - asset.mission=self:GetMissionByID(mid) + -- Set mission. + asset.mission=mission + + -- Add mission to flightgroup queue. + asset.flightgroup:AddMission(asset.mission, asset.mission.waypointcoord, asset.mission.waypointindex) + end end @@ -702,17 +721,14 @@ end --- Create a new flight group after an asset was spawned. -- @param #AIRWING self --- @param Wrapper.Group#GROUP group The group. -- @param #AIRWING.SquadronAsset asset The asset. -function AIRWING:_CreateFlightGroup(group, asset) +function AIRWING:_CreateFlightGroup(asset) -- Create flightgroup. - local flightgroup=FLIGHTGROUP:New(group:GetName()) - - asset.flightgroup=flightgroup - - flightgroup:SetAirwing(self) - + local flightgroup=FLIGHTGROUP:New(asset.spawngroupname) + + -- Set airwing. + flightgroup:SetAirwing(self) --- Check if out of missiles. For A2A missions ==> RTB. function flightgroup:OnAfterOutOfMissiles() @@ -743,15 +759,8 @@ function AIRWING:_CreateFlightGroup(group, asset) -- TODO -- Mission failed ==> launch new mission? - end - - -- Add mission to flightgroup queue. - if asset.mission then - local Cstart=UTILS.SecondsToClock(asset.mission.Tstart) - local Cstop=asset.mission.Tstop and UTILS.SecondsToClock(asset.mission.Tstop) or nil - asset.flightgroup:AddMission(asset.mission, asset.mission.waypointcoord, asset.mission.waypointindex, Cstart, Cstop, asset.mission.name) end - + end @@ -779,8 +788,21 @@ function AIRWING:SquadronCanMission(Squadron, MissionType, Nassets) for _,_asset in pairs(Squadron.assets) do local asset=_asset --#AIRWING.SquadronAsset - -- Check if has already a mission assigned. - if asset.mission==nil then + -- Check if has already any missions in the queue. + if self:IsAssetOnMission() then + + --TODO: This only checks if it has an ORBIT mission. It could have others as well! + if self:IsAssetOnMission(AUFTRAG.Type.ORBIT) then + + -- Check if the payload of this asset is compatible with the mission. + if self:CheckMissionType(MissionType, asset.payload.missiontypes) then + -- TODO: check if asset actually has weapons left! + table.insert(assets, asset) + end + + end + + else --- -- This asset as no current mission @@ -801,13 +823,6 @@ function AIRWING:SquadronCanMission(Squadron, MissionType, Nassets) end end - elseif asset.mission==AUFTRAG.Type.ORBIT then - - -- Check if the payload of this asset is compatible with the mission. - if self:CheckMissionType(MissionType, asset.payload.missiontypes) then - -- TODO: check if asset actually has weapons left! - table.insert(assets, asset) - end end end @@ -822,11 +837,38 @@ function AIRWING:SquadronCanMission(Squadron, MissionType, Nassets) return cando, assets end ---- Check if assets for a given mission type are available. +--- Check if an asset is currently on a mission or has one in the queue. -- @param #AIRWING self --- @return #boolean If true, enough assets are available. -function AIRWING:IsAssetOnMission() +-- @param #AIRWING.SquadronAsset 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 then + + -- Loop over mission queue. + for _,_mission in pairs(asset.flightgroup.missionqueue or {}) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + -- Only if mission is not already over. + if mission.status~=AUFTRAG.Status.DONE and self:CheckMissionType(mission.type, MissionTypes) then + return true + end + + end + + end + + return false end --- Check if assets for a given mission type are available. @@ -867,7 +909,7 @@ function AIRWING:CanMission(MissionType, Nassets) -- Now clear all reserved payloads. for _,_asset in pairs(Assets) do local asset=_asset --#AIRWING.SquadronAsset - self:ReturnPayloadFromAsset(asset,payload) + self:ReturnPayloadFromAsset(asset) end return Can, Assets diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 4a2cd6d0d..0b2536d3e 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -29,7 +29,7 @@ -- @field #table DCStask DCS task structure. -- @field Core.Point#COORDINATE waypointcoord Coordinate of the waypoint task. -- @field #number waypointindex Waypoint number at which the task is executed. --- @field Ops.FlightGroup#AUFTRAG.Task waypointtask Waypoint task. +-- @field Ops.FlightGroup#FLIGHTGROUP.Task waypointtask Waypoint task. -- @field #number marker F10 map marker ID. -- @field Core.Point#COORDINATE coordOrbit Coordinate where to orbit. -- @field #number speedOrbit Orbit speed in m/s. @@ -42,7 +42,7 @@ -- @field #string squadname Name of the assigned squadron. -- @field #table assets Assets assigned for this mission. -- @field #number nassets Number of required assets. --- @extends Ops.FlightGroup#AUFTRAG +-- @extends Core.Fsm#FSM --- *To invent an airplane is nothing. To build one is something. To fly is everything.* -- Otto Lilienthal -- @@ -115,16 +115,19 @@ AUFTRAG.Type={ --- Mission status. -- @type AUFTRAG.Status --- @field #string SCHEDULED Mission is scheduled. --- @field #string ASSIGNED Mission was assigned. +-- @field #string PLANNED Mission is at the early planning stage. +-- @field #string SCHEDULED Mission is scheduled in a queue waiting to be assigned. +-- @field #string ASSIGNED Mission was assigned to somebody. +-- @field #string STARTED Mission started but is not executed yet. -- @field #string EXECUTING Mission is being executed. --- @field #string ACCOMPLISHED Mission is accomplished. +-- @field #string DONE Mission is over. AUFTRAG.Status={ + PLANNED="planned", SCHEDULED="scheduled", - ASSIGNED="assigned", + ASSIGNED="assigned", + STARTED="started", EXECUTING="executing", - ACCOMPLISHED="accomplished", - FAILED="failed", + DONE="Done", } diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 8a7a110d4..ac93b7b55 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -276,11 +276,13 @@ FLIGHTGROUP.Attribute = { -- @type FLIGHTGROUP.TaskStatus -- @field #string SCHEDULED Task is scheduled. -- @field #string EXECUTING Task is being executed. --- @field #string ACCOMPLISHED Task is accomplished. +-- @field #string PAUSED Task is paused. +-- @field #string DONE Task is done. FLIGHTGROUP.TaskStatus={ SCHEDULED="scheduled", EXECUTING="executing", - ACCOMPLISHED="accomplished", + PAUSED="paused", + DONE="done", } --- Flight group task status. @@ -334,6 +336,7 @@ FLIGHTGROUP.version="0.3.2" -- TODO: Mark assigned parking spot on F10 map. -- TODO: Let user request a parking spot via F10 marker :) -- TODO: Get proper monitoring of parking spots! Try to avoid scan and getParking as much as possible for performance reasons. +-- TODO: Monitor traveled distance in air. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -381,8 +384,8 @@ function FLIGHTGROUP:New(groupname, autostart) self:AddTransition("Stopped", "Start", "InUtero") -- Start FSM. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - self:AddTransition("*", "FlightStatus", "*") -- FLIGHTGROUP status update. - self:AddTransition("*", "QueueUpdate", "*") -- Update task queue. + self:AddTransition("*", "FlightStatus", "*") -- Status update. + self:AddTransition("*", "QueueUpdate", "*") -- Update task and mission queues. self:AddTransition("*", "DetectedUnit", "*") -- Add a newly detected unit to the detected units set. self:AddTransition("*", "DetectedUnitNew", "*") -- Add a newly detected unit to the detected units set. @@ -393,16 +396,18 @@ function FLIGHTGROUP:New(groupname, autostart) self:AddTransition("*", "Respawn", "*") -- Respawn group. self:AddTransition("*", "RTB", "Inbound") -- Group is returning to destination base. - self:AddTransition("*", "RTZ", "Inbound") -- Group is returning to destination zone. + 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", "Going2Refuel") -- Group is send to refuel at a tanker. - self:AddTransition("Going2Refuel", "Refueling", "Refueling") -- Group is send to refuel at a tanker. + + self:AddTransition("*", "Refuel", "Going2Refuel")-- Group is send to refuel at a tanker. Not implemented yet! + self:AddTransition("Going2Refuel", "Refueling", "Refueling") -- Group is send to refuel at a tanker. Not implemented yet! + self:AddTransition("*", "LandAt", "LandingAt") -- Helo group is ordered to land at a specific point. - self:AddTransition("LandingAt", "LandedAt", "LandedAt") -- Helo group is landed at a specific point. + self:AddTransition("LandingAt", "LandedAt", "LandedAt") -- Helo group landed landed at a specific point. self:AddTransition("*", "PassingWaypoint", "*") -- Group passed a waypoint. - self:AddTransition("*", "GotoWaypoint", "*") -- Group resumes route at a give nwaypoint. - self:AddTransition("*", "Orbit", "Orbiting") -- Group is is orbiting. + self:AddTransition("*", "GotoWaypoint", "*") -- Group switches to a specific waypoint. + self:AddTransition("*", "Orbit", "Orbiting") -- 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%. @@ -412,21 +417,21 @@ function FLIGHTGROUP:New(groupname, autostart) 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("*", "OutOfMissilesA2A", "*") -- Group is out of A2A missiles. - self:AddTransition("*", "OutOfMissilesA2G", "*") -- Group is out of A2G missiles. + self:AddTransition("*", "OutOfMissilesA2A", "*") -- Group is out of A2A missiles. Not implemented yet! + self:AddTransition("*", "OutOfMissilesA2G", "*") -- Group is out of A2G missiles. Not implemented yet! self:AddTransition("*", "TaskExecute", "*") -- Group will execute a task. - self:AddTransition("*", "TaskDone", "*") -- Group finished a task. - self:AddTransition("*", "TaskCancel", "*") -- Cancel current task. self:AddTransition("*", "TaskPause", "*") -- Pause current task. + self:AddTransition("*", "TaskCancel", "*") -- Cancel current task. + self:AddTransition("*", "TaskDone", "*") -- Task is over. - self:AddTransition("*", "MissionStart", "*") -- Mission has started. - self:AddTransition("*", "MissionUpdate", "*") -- Mission is updated with latest data. - self:AddTransition("*", "MissionDone", "*") -- Mission is over. - self:AddTransition("*", "MissionAbort", "*") -- Mission is aborted. + self:AddTransition("*", "MissionStart", "*") -- Mission is started. + self:AddTransition("*", "MissionUpdate", "*") -- Mission is updated with latest data. + self:AddTransition("*", "MissionCancel", "*") -- Cancel current mission. + self:AddTransition("*", "MissionDone", "*") -- Mission is over. - self:AddTransition("Airborne", "EngageTarget", "Engaging") -- Pause current task. - self:AddTransition("Engaging", "Disengage", "Airborne") -- Pause current task. + self:AddTransition("Airborne", "EngageTarget", "Engaging") -- Pause current task. + self:AddTransition("Engaging", "Disengage", "Airborne") -- Pause current task. self:AddTransition("*", "ElementSpawned", "*") -- An element was spawned. self:AddTransition("*", "ElementParking", "*") -- An element is parking. @@ -552,16 +557,13 @@ function FLIGHTGROUP:AddTask(task, clock, description, prio, duration) newtask.type=FLIGHTGROUP.TaskType.SCHEDULED newtask.stopflag=USERFLAG:New(string.format("StopTaskFlag %d", newtask.id)) newtask.stopflag:Set(0) - - - -- Info. - self:I(self.lid..string.format("Adding task %s scheduled at %s", newtask.description, UTILS.SecondsToClock(time, true))) - - -- Debug info. - self:T2({newtask=newtask}) -- Add to table. - table.insert(self.taskqueue, newtask) + table.insert(self.taskqueue, newtask) + + -- Info. + self:I(self.lid..string.format("Adding SCHEDULED task %s starting at %s", newtask.description, UTILS.SecondsToClock(time, true))) + self:T2({newtask=newtask}) return newtask end @@ -593,13 +595,12 @@ function FLIGHTGROUP:AddTaskWaypoint(task, waypointindex, description, prio, dur newtask.stopflag=USERFLAG:New(string.format("StopTaskFlag %d", newtask.id)) newtask.stopflag:Set(0) - self:T2({newtask=newtask}) - -- Add to table. table.insert(self.taskqueue, newtask) -- Info. - self:I(self.lid..string.format("Adding WAYPOINT task %s at WP %d", newtask.description, newtask.waypoint)) + self:I(self.lid..string.format("Adding WAYPOINT task %s at WP %d", newtask.description, newtask.waypoint)) + self:T2({newtask=newtask}) -- Update route. self:__UpdateRoute(-1) @@ -631,6 +632,7 @@ end --- Remove task. -- @param #FLIGHTGROUP self -- @param #number taskid ID of the task to remove. +-- @return #boolean True if task could be removed. function FLIGHTGROUP:RemoveTask(taskid) for i=#self.taskqueue,1,-1 do @@ -646,11 +648,11 @@ function FLIGHTGROUP:RemoveTask(taskid) self:__UpdateRoute(-1) end - break - end - + return true + end end - + + return false end --- Add mission to queue. @@ -675,15 +677,6 @@ function FLIGHTGROUP:AddMission(Mission, WaypointCoordinate, WaypointIndex) return self end ---- Set squadron the flight group belongs to. --- @param #FLIGHTGROUP self --- @param #boolean If true or nil, group get enroute task tanker. If false, not. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetTanker(swtich) - self.istanker=true - return self -end - --- Set AIRWING the flight group belongs to. -- @param #FLIGHTGROUP self -- @param Ops.AirWing#AIRWING airwing The AIRWING object. @@ -1203,6 +1196,10 @@ function FLIGHTGROUP:onafterFlightStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() + + --- + -- Detection + --- -- Check if group has detected any units. self:_CheckDetectedUnits() @@ -1220,10 +1217,10 @@ function FLIGHTGROUP:onafterFlightStatus(From, Event, To) -- Get distance to assigned parking spot. local dist=element.unit:GetCoordinate():Get2DDistance(element.parking.Coordinate) - -- If distance >10 meters or velocity of unit is >0, we consider the unit as taxiing. + -- 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 --or element.unit:GetVelocityMPS()>0 then + if dist>10 then if element.status==FLIGHTGROUP.ElementStatus.ENGINEON then self:ElementTaxiing(element) end @@ -1242,9 +1239,9 @@ function FLIGHTGROUP:onafterFlightStatus(From, Event, To) local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() -- Short info. - local text=string.format("Flight status %s [%d/%d]. Tasks=%d (%d,%d) Current=%d. Waypoint=%d/%d. Detected=%d. FC=%s. Destination=%s", + local text=string.format("Flight status %s [%d/%d]. Tasks=%d (%d,%d) Current=%d. Waypoint=%d/%d. Detected=%d. Destination=%s, FC=%s", fsmstate, #self.elements, #self.elements, nTaskTot, nTaskSched, nTaskWP, self.taskcurrent, self.currentwp or 0, self.waypoints and #self.waypoints or 0, - self.detectedunits:Count(), self.flightcontrol and self.flightcontrol.airbasename or "none", self.destbase and self.destbase:GetName() or "unknown") + self.detectedunits:Count(), self.destbase and self.destbase:GetName() or "unknown", self.flightcontrol and self.flightcontrol.airbasename or "none") self:I(self.lid..text) -- Element status. @@ -1258,11 +1255,9 @@ function FLIGHTGROUP:onafterFlightStatus(From, Event, To) local unit=element.unit local fuel=unit:GetFuel() or 0 local life=unit:GetLifeRelative() or 0 - local parking="X" - if element.parking then - parking=tostring(element.parking.TerminalID) - end + local parking=element.parking and tostring(element.parking.TerminalID) or "X" + -- Used later for low fuel thresh. if fuel*100=task.time then + return task + end + end + + return nil +end + +--- Get next mission. +-- @param #FLIGHTGROUP self +-- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. +function FLIGHTGROUP:_GetNextMission() + + -- Number of missions. + local Nmissions=#self.missionqueue + + -- Treat special cases. + if Nmissions==0 then + return nil + end + + -- Sort results table wrt times they have already been engaged. + local function _sort(a, b) + local taskA=a --Ops.Auftrag#AUFTRAG + local taskB=b --Ops.Auftrag#AUFTRAG + return (taskA.Tstart=mission.Tstart then + return mission + end + end + + return nil end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1441,6 +1529,7 @@ function FLIGHTGROUP:OnEventBirth(EventData) self.group=self.group or EventData.IniGroup if not self.groupinitialized then + --TODO: actually that is not very good here as if the first unit is born and in initgroup we initialize all elements! self:_InitGroup() end @@ -1607,6 +1696,7 @@ function FLIGHTGROUP:OnEventEngineShutdown(EventData) end else + -- element is nil end end @@ -1858,75 +1948,6 @@ function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.DEAD) end ---- Initialize group parameters. --- @param #FLIGHTGROUP self --- @return #FLIGHTGROUP self -function FLIGHTGROUP:_InitGroup() - - if self.groupinitialized then - self:E(self.lid.."WARNING: Group was already initialized!") - return - end - - -- Get template of group. - self.template=self.group:GetTemplate() - - -- Max speed in km/h. - self.speedmax=self.group:GetSpeedMax() - - local unit=self.group:GetUnit(1) - - if unit then - - --local nunits=self.group:GetUnits() - - self.rangemax=unit:GetRange() - - self.descriptors=unit:GetDesc() - - self.actype=unit:GetTypeName() - - self.ceiling=self.descriptors.Hmax - - self.ai=not self:_IsHuman(self.group) - - for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element - element.ai=not self:_IsHumanUnit(element.unit) - end - - -- Init waypoints. - if not self.waypoints then - self:InitWaypoints() - end - - -- Debug info. - local text=string.format("Initialized Flight Group %s:\n", self.groupname) - text=text..string.format("AC type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) - text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) - text=text..string.format("AI = %s\n", tostring(self.ai)) - 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("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) - - -- Init done. - self.groupinitialized=true - - end - - return self -end - --- On after "FlightSpawned" event. Sets the template, initializes the waypoints. -- @param #FLIGHTGROUP self @@ -1979,7 +2000,6 @@ function FLIGHTGROUP:onafterFlightParking(From, Event, To) end end - --- On after "FlightTaxiing" event. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -2304,8 +2324,10 @@ function FLIGHTGROUP:onafterPassingWaypoint(From, Event, To, n, N) self:I(self.lid..text) MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) + -- Get all waypoint tasks. local tasks=self:GetTasksWaypoint(n) + -- Debug info. local text=string.format("WP %d/%d tasks:", n, N) if tasks then for i,_task in pairs(tasks) do @@ -2334,10 +2356,10 @@ function FLIGHTGROUP:onafterPassingWaypoint(From, Event, To, n, N) -- Controlled task. table.insert(taskswp, self.group:TaskControlled(Task.dcstask, TaskCondition)) - - --table.insert(taskswp, Task.dcstask) - + -- Task done. + -- TODO: Maybe the _TaskDone needs to be outside of the TaskCombo?! If task is cancelled, this is not called! + -- TODO: Similar with more than one waypoint task! Structure is a bit more complicated I'm afraid. table.insert(taskswp, self.group:TaskFunction("FLIGHTGROUP._TaskDone", self, Task)) end @@ -2369,59 +2391,23 @@ end -- @param #number n The goto waypoint number. function FLIGHTGROUP:onafterGotoWaypoint(From, Event, To, n) - self.currentwp=n + -- The last waypoint passed was n-1 + self.currentwp=n-1 + -- TODO: switch to re-enable waypoint tasks. + if false then + local tasks=self:GetTasksWaypoint(n) + + for _,_task in pairs(tasks) do + local task=_task --#FLIGHTGROUP.Task + task.status=FLIGHTGROUP.TaskStatus.SCHEDULED + end + + end + + -- Update the route. self:UpdateRoute() - --TODO: option to re-enable waypoint tasks. - -end - - ---- On after "FuelLow" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function FLIGHTGROUP:onafterFuelLow(From, Event, To) - - -- Debug message. - local text=string.format("Low fuel for flight group %s", self.groupname) - MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) - self:I(self.lid..text) - - -- Set switch to true. - self.fuellow=true - - -- Route helo back home. It is respawned! But this is the only way to ensure that it actually lands at the airbase. - local airbase=self.destbase or self.homebase - - if airbase and self.fuellowrtb then - self:RTB(airbase) - end -end - ---- On after "FuelCritical" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function FLIGHTGROUP:onafterFuelCritical(From, Event, To) - - -- Debug message. - local text=string.format("Critical fuel for flight group %s", self.groupname) - MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) - self:I(self.lid..text) - - -- Set switch to true. - self.fuelcritical=true - - -- Route helo back home. It is respawned! But this is the only way to ensure that it actually lands at the airbase. - local airbase=self.destbase or self.homebase - - if airbase and self.fuelcriticalrtb then - self:RTB(airbase) - end end --- On after "Orbit" event. @@ -2446,7 +2432,7 @@ function FLIGHTGROUP:onafterOrbit(From, Event, To, Coord, Altitude, Speed) self:SetTask(TaskOrbit) end ---- On before "Hold" event. +--- On before "RTB" event. -- @param #FLIGHTGROUP self -- @param #string From From state. -- @param #string Event Event. @@ -2454,7 +2440,7 @@ end -- @param Wrapper.Airbase#AIRBASE airbase The airbase to hold at. -- @param #number SpeedTo Speed used for travelling from current position to holding point in knots. -- @param #number SpeedHold Holding speed in knots. -function FLIGHTGROUP:onbeforeHold(From, Event, To, airbase, SpeedTo, SpeedHold) +function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) local allowed=true local Tsuspend=nil @@ -2492,7 +2478,7 @@ function FLIGHTGROUP:onbeforeHold(From, Event, To, airbase, SpeedTo, SpeedHold) end if Tsuspend and not allowed then - self:__Hold(Tsuspend, airbase, SpeedTo, SpeedHold) + self:__RTB(Tsuspend, airbase, SpeedTo, SpeedHold) end return allowed @@ -2504,7 +2490,7 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Airbase#AIRBASE airbase The airbase to hold at. --- @param #number SpeedTo Speed used for travelling from current position to holding point in knots. Default 350 kts. +-- @param #number SpeedTo Speed used for traveling from current position to holding point in knots. Default 350 kts. -- @param #number SpeedHold Holding speed in knots. Default 250 kts. -- @param #number SpeedLand Landing speed in knots. Default 170 kts. function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, SpeedLand) @@ -2512,7 +2498,7 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp -- Defaults: SpeedTo=SpeedTo or 350 SpeedHold=SpeedHold or 250 - local SpeedLand=170 + SpeedLand=SpeedLand or 170 -- Debug message. local text=string.format("Flight group set to hold at airbase %s", airbase:GetName()) @@ -2553,13 +2539,11 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp local x2=UTILS.NMToMeters(5) local h1=x1*math.tan(alpha) local h2=x2*math.tan(alpha) - local SpeedLand=140 - local SpeedTo=180 local runway=airbase:GetActiveRunway() -- Clear all tasks. - self.group:ClearTasks() + self:ClearTasks() -- Set holding flag to 333. self.flaghold:Set(333) @@ -2567,15 +2551,15 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp -- Task fuction when reached holding point. local TaskArrived=self.group:TaskFunction("FLIGHTGROUP._ReachedHolding", self) - -- Orbit until flaghold=1 (=true) + -- Orbit until flaghold=1 (true) but max 10 min. local TaskOrbit=self.group:TaskOrbit(p0, nil, UTILS.KnotsToMps(SpeedHold), p1) - local TaskStop=self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1, nil, 10*60) - local TaskControlled=self.group:TaskControlled(TaskOrbit, TaskStop) + local TaskLand=self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1, nil, 10*60) + local TaskHold=self.group:TaskControlled(TaskOrbit, TaskLand) - -- Waypoints. + -- Waypoints from current position to holding point. local wp={} wp[#wp+1]=c0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Current Pos") - wp[#wp+1]=p0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {TaskArrived, TaskControlled}, "Holding Point") + wp[#wp+1]=p0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {TaskArrived, TaskHold}, "Holding Point") -- Approach point: 10 NN in direction of runway. local papp=airbase:GetCoordinate():Translate(x1, runway.heading-180):SetAltitude(h1) @@ -2632,15 +2616,45 @@ function FLIGHTGROUP:onafterHolding(From, Event, To) end ---- On after "Holding" event. Flight arrived at the holding point. +--- On after "Engage" event. Order to engage a set of units. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Set#SET_UNIT TargetUnitSet +function FLIGHTGROUP:onafterEngageTargets(From, Event, To, TargetUnitSet) + + local DCSTasks={} + + for _,_unit in paris(TargetUnitSet:GetSet()) do + local unit=_unit --Wrapper.Unit#UNIT + local task=self.group:TaskAttackUnit(unit, true) + table.insert(DCSTasks) + end + + -- Task combo. + local DCSTask=self.group:TaskCombo(DCSTasks) + + --TODO needs a task function that calls EngageDone or so event and updates the route again. + + -- Lets try if pushtask actually leaves the remaining tasks untouched. + -- TODO: the problem is if UpdateRoute is called because it would destroy this task! + self:PushTask(DCSTask) + +end + + +--- On after "LandAt" event. Order helicopter to land at a specific point. -- @param #FLIGHTGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Core.Point#COORDINATE Coordinate The coordinate where to land. --- @param #number Duration The duration in seconds to remain on ground. Default 600 sec. +-- @param #number Duration The duration in seconds to remain on ground. Default 600 sec (10 min). function FLIGHTGROUP:onafterLandAt(From, Event, To, Coordinate, Duration) - Duration=Duration or 500 + + -- Duration. + Duration=Duration or 600 local task=self.group:TaskLandAtVec2(Coordinate:GetVec2(), Duration) @@ -2649,6 +2663,168 @@ function FLIGHTGROUP:onafterLandAt(From, Event, To, Coordinate, Duration) end +--- On after "DetectedUnit" event. Add newly detected unit to detected units set. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Unit#UNIT Unit The detected unit. +function FLIGHTGROUP:onafterDetectedUnit(From, Event, To, Unit) + self:T(self.lid..string.format("Detected unit %s", Unit:GetName())) + self.detectedunits:AddUnit(Unit) +end + +--- On after "DetectedUnitNew" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Unit#UNIT Unit The detected unit. +function FLIGHTGROUP:onafterDetectedUnitNew(From, Event, To, Unit) + self:T(self.lid..string.format("Detected New unit %s", Unit:GetName())) +end + + +--- On after "FuelLow" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function FLIGHTGROUP:onafterFuelLow(From, Event, To) + + -- Debug message. + local text=string.format("Low fuel for flight group %s", self.groupname) + MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) + self:I(self.lid..text) + + -- Set switch to true. + self.fuellow=true + + -- Route helo back home. It is respawned! But this is the only way to ensure that it actually lands at the airbase. + local airbase=self.destbase or self.homebase + + if airbase and self.fuellowrtb then + self:RTB(airbase) + --TODO: RTZ + end +end + +--- On after "FuelCritical" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function FLIGHTGROUP:onafterFuelCritical(From, Event, To) + + -- Debug message. + local text=string.format("Critical fuel for flight group %s", self.groupname) + MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) + self:I(self.lid..text) + + -- Set switch to true. + self.fuelcritical=true + + -- Route helo back home. It is respawned! But this is the only way to ensure that it actually lands at the airbase. + local airbase=self.destbase or self.homebase + + if airbase and self.fuelcriticalrtb then + self:RTB(airbase) + --TODO: RTZ + end +end + + +--- On after "EnterZone" event. Sets self.inzones[zonename]=true. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE Zone The zone that the group entered. +function FLIGHTGROUP:onafterEnterZone(From, Event, To, Zone) + local zonename=Zone and Zone:GetName() or "unknown" + self:I(self.lid..string.format("Entered Zone %s", zonename)) + self.inzones:Add(Zone:GetName(), Zone) +end + +--- On after "LeaveZone" event. Sets self.inzones[zonename]=false. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE Zone The zone that the group entered. +function FLIGHTGROUP:onafterLeaveZone(From, Event, To, Zone) + local zonename=Zone and Zone:GetName() or "unknown" + self:I(self.lid..string.format("Left Zone %s", zonename)) + self.inzones:Remove(zonename, true) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Task functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get the unfinished waypoint tasks +-- @param #FLIGHTGROUP self +-- @param #number n Waypoint index. Counting starts at one. +-- @return #table Table of tasks. Table could also be empty {}. +function FLIGHTGROUP:GetTasksWaypoint(n) + + -- Tasks table. + local tasks={} + + -- Sort results table wrt times they have already been engaged. + local function _sort(a, b) + local taskA=a --#FLIGHTGROUP.Task + local taskB=b --#FLIGHTGROUP.Task + return (taskA.time Mission Done! if self.currentmission and self.currentmission.waypointtask and self.currentmission.waypointtask.id==Task.id then @@ -2804,157 +2994,6 @@ function FLIGHTGROUP:onafterTaskDone(From, Event, To, Task) end - ---- On after "DetectedUnit" event. Add newly detected unit to detected units set. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Wrapper.Unit#UNIT Unit The detected unit -function FLIGHTGROUP:onafterDetectedUnit(From, Event, To, Unit) - self:T(self.lid..string.format("Detected unit %s", Unit:GetName())) - self.detectedunits:AddUnit(Unit) -end - ---- On after "DetectedUnitNew" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Wrapper.Unit#UNIT Unit The detected unit -function FLIGHTGROUP:onafterDetectedUnitNew(From, Event, To, Unit) - self:T(self.lid..string.format("Detected New unit %s", Unit:GetName())) -end - - ---- On after "EnterZone" event. Sets self.inzones[zonename]=true. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE Zone The zone that the group entered. -function FLIGHTGROUP:onafterEnterZone(From, Event, To, Zone) - local zonename=Zone and Zone:GetName() or "unknown" - self:I(self.lid..string.format("Entered Zone %s", zonename)) - self.inzones:Add(Zone:GetName(), Zone) -end - ---- On after "LeaveZone" event. Sets self.inzones[zonename]=false. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE Zone The zone that the group entered. -function FLIGHTGROUP:onafterLeaveZone(From, Event, To, Zone) - local zonename=Zone and Zone:GetName() or "unknown" - self:I(self.lid..string.format("Left Zone %s", zonename)) - self.inzones:Remove(zonename, true) -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Task functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Get next task in queue. Task needs to be in state SCHEDULED and time must have passed. --- @param #FLIGHTGROUP self --- @return #FLIGHTGROUP.Task The next task in line or `nil`. -function FLIGHTGROUP:GetTask() - - if self.taskpaused then - --return self.taskpaused - end - - if #self.taskqueue==0 then - return nil - else - - -- Sort results table wrt times they have already been engaged. - local function _sort(a, b) - local taskA=a --#FLIGHTGROUP.Task - local taskB=b --#FLIGHTGROUP.Task - return (taskA.time=task.time then - return task - end - end - - end - - return nil -end - - ---- Get the unfinished waypoint tasks --- @param #FLIGHTGROUP self --- @param #number n Waypoint index. Counting starts at one. --- @return #table Table of tasks. Table could also be empty {}. -function FLIGHTGROUP:GetTasksWaypoint(n) - - -- Tasks table. - local tasks={} - - -- Sort results table wrt times they have already been engaged. - local function _sort(a, b) - local taskA=a --#FLIGHTGROUP.Task - local taskB=b --#FLIGHTGROUP.Task - return (taskA.time=mission.Tstart then - return mission - end - end - - return nil -end - --- Route group to mission. -- @param #FLIGHTGROUP self -- @param Ops.Auftrag#AUFTRAG mission The mission table. @@ -3143,7 +3163,7 @@ function FLIGHTGROUP:RouteToMission(mission, delay) -- Add waypoint. self:AddWaypointAir(Coordinate, mission.waypointindex, self.speedmax*0.8, false) - -- Add waypoint task. + -- Add waypoint task. UpdateRoute is called inside. mission.waypointtask=self:AddTaskWaypoint(mission.DCStask, mission.waypointindex, mission.name, mission.prio, mission.duration) end end @@ -3258,6 +3278,76 @@ end -- Misc functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:_InitGroup() + + if self.groupinitialized then + self:E(self.lid.."WARNING: Group was already initialized!") + return + end + + -- Get template of group. + self.template=self.group:GetTemplate() + + -- Max speed in km/h. + self.speedmax=self.group:GetSpeedMax() + + local unit=self.group:GetUnit(1) + + if unit then + + --local nunits=self.group:GetUnits() + + self.rangemax=unit:GetRange() + + self.descriptors=unit:GetDesc() + + self.actype=unit:GetTypeName() + + self.ceiling=self.descriptors.Hmax + + self.ai=not self:_IsHuman(self.group) + + for _,_element in pairs(self.elements) do + local element=_element --#FLIGHTGROUP.Element + element.ai=not self:_IsHumanUnit(element.unit) + end + + -- Init waypoints. + if not self.waypoints then + self:InitWaypoints() + end + + -- Debug info. + local text=string.format("Initialized Flight Group %s:\n", self.groupname) + text=text..string.format("AC 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("AI = %s\n", tostring(self.ai)) + 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("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) + + -- Init done. + self.groupinitialized=true + + end + + return self +end + --- Add an element to the flight group. -- @param #FLIGHTGROUP self -- @param #string unitname Name of unit.