diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index a2e7fe2bc..60c1f0bfa 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1277,6 +1277,7 @@ do -- COORDINATE -- @return Core.Point#COORDINATE Coordinate of the nearest parking spot. -- @return #number Terminal ID. -- @return #number Distance to closest parking spot in meters. + -- @return Wrapper.Airbase#AIRBASE#ParkingSpot Parking spot table. function COORDINATE:GetClosestParkingSpot(airbase, terminaltype, free) -- Get airbase table. @@ -1291,6 +1292,7 @@ do -- COORDINATE local _closest=nil --Core.Point#COORDINATE local _termID=nil local _distmin=nil + local spot=nil --Wrapper.Airbase#AIRBASE.ParkingSpot -- Loop over all airbases. for _,_airbase in pairs(airbases) do @@ -1310,11 +1312,13 @@ do -- COORDINATE _closest=_coord _distmin=_dist _termID=_spot.TerminalID + spot=_spot else if _dist<_distmin then _distmin=_dist _closest=_coord _termID=_spot.TerminalID + spot=_spot end end @@ -1322,7 +1326,7 @@ do -- COORDINATE end end - return _closest, _termID, _distmin + return _closest, _termID, _distmin, spot end --- Gets the nearest free parking spot. diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index ea457bddf..36079ea6b 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -71,6 +71,8 @@ __Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' ) __Moose.Include( 'Scripts/Moose/Ops/FlightControl.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Skipper.lua' ) __Moose.Include( 'Scripts/Moose/Ops/CarrierAirWing.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/FlightGroup.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index e29b0940a..b5e82a8c6 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -16,8 +16,13 @@ -- @field #string ClassName Name of the class. -- @field #boolean Debug Debug mode. Messages to all about status. -- @field #string sid Class id string for output to DCS log file. +-- @field #string groupname Name of flight group. +-- @field Wrapper.Group#GROUP flightgroup Flight group object. +-- @field #string type Aircraft type of flight group. -- @field #table element Table of elements, i.e. units of the group. --- @extends AI.AI_Air#AI_AIR +-- @field #table taskqueue Queue of tasks. +-- @field Core.Set#SET_UNIT detectedunits Set of detected units. +-- @extends Core.Fsm#FSM --- Be surprised! -- @@ -34,14 +39,32 @@ FLIGHTGROUP = { ClassName = "FLIGHTGROUP", sid = nil, groupname = nil, + flightgroup = nil, + type = nil, element = {}, taskqueue = {}, + detectedunits = nil, } --- Status of flight group element. -- @type FLIGHTGROUP.ElementStatus --- @field #string +-- @field #string SPAWNED Element was spawned into the world. +-- @field #string PARKING Element is parking after spawned on ramp. +-- @field #string TAXIING Element is taxiing after engine startup. +-- @field #string AIRBORNE Element is airborne after take off. +-- @field #string LANDED Element landed and is taxiing to its parking spot. +-- @field #string ARRIVED Element arrived at its parking spot and shut down its engines. +-- @field #string DEAD Element is dead after it crashed, pilot ejected or pilot dead events. +FLIGHTGROUP.ElementStatus={ + SPAWNED="spawned", + PARKING="parking", + TAXIING="taxiing", + AIRBORNE="airborne", + LANDED="landed", + DEAD="dead", +} + --- Flight group element. -- @type FLIGHTGROUP.Element @@ -83,32 +106,55 @@ FLIGHTGROUP.version="0.0.1" -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new FLIGHTGROUP class object. +--- Create a new FLIGHTGROUP object and start the FSM. -- @param #FLIGHTGROUP self --- @param Wrapper.Group#GROUP AIGroup The AI flight group. +-- @param #string groupname Name of the group. -- @return #FLIGHTGROUP self -function FLIGHTGROUP:New(AIGroup) +function FLIGHTGROUP:New(groupname) -- Inherit everything from WAREHOUSE class. - local self=BASE:Inherit(self, AI_AIR:New( AIGroup )) -- #FLIGHTGROUP - - if not self then - BASE:E(string.format("ERROR: Could not find flight group!")) - return nil - end + local self=BASE:Inherit(self, FSM:New()) -- #FLIGHTGROUP - self.groupname=AIGroup:GetName() + --self.flightgroup=AIGroup + self.groupname=tostring(groupname) -- Set some string id for output to DCS.log file. self.sid=string.format("FLIGHTGROUP %s |", self.groupname) + -- Start State. + self:SetStartState("Stopped") + + -- Check if the group is already alive and if so, add its elements. + local group=GROUP:FindByName(groupname) + if group and group:IsAlive() then + self.flightgroup=group + local units=group:GetUnits() + for _,_unit in pairs(units) do + local unit=_unit --Wrapper.Unit#UNIT + local element=self:AddElementByName(unit:GetName()) + if unit:InAir() then + element.status=FLIGHTGROUP.ElementStatus.AIRBORNE + else + element.status=FLIGHTGROUP.ElementStatus.SPAWNED + end + end + end + + -- Init set of detected units. + self.detectedunits=SET_UNIT:New() + + -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("*", "AirwingStatus", "*") -- FLIGHTGROUP status update. - self:AddTransition("*", "RequestCAP", "*") -- Request CAP flight. - self:AddTransition("*", "RequestIntercept", "*") -- Request Intercept. - self:AddTransition("*", "RequestCAS", "*") -- Request CAS. - self:AddTransition("*", "RequestSEAD", "*") -- Request SEAD. + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "FlightStatus", "*") -- FLIGHTGROUP status update. + self:AddTransition("*", "ElementDead", "*") -- An element crashed, ejected, or pilot dead. + self:AddTransition("*", "FlightAirborne", "Airborne") -- The whole flight group is airborne. + self:AddTransition("*", "FlightLanded", "Landed") -- The whole flight group has laned. + self:AddTransition("*", "FlightArrived", "Arrived") -- The whole flight group has arrived. + self:AddTransition("*", "FlightDead", "*") -- The whole flight group is dead. + self:AddTransition("*", "AddDetectedUnit", "*") -- Add a newly detected unit to the detected units set. + ------------------------ --- Pseudo Functions --- @@ -131,27 +177,16 @@ function FLIGHTGROUP:New(AIGroup) -- @param #FLIGHTGROUP self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "SkipperStatus". - -- @function [parent=#FLIGHTGROUP] AirwingStatus + --- Triggers the FSM event "FlightStatus". + -- @function [parent=#FLIGHTGROUP] FlightStatus -- @param #FLIGHTGROUP self --- Triggers the FSM event "SkipperStatus" after a delay. - -- @function [parent=#FLIGHTGROUP] __AirwingStatus + -- @function [parent=#FLIGHTGROUP] __FlightStatus -- @param #FLIGHTGROUP self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "RequestCAP". - -- @function [parent=#FLIGHTGROUP] RequestCAP - -- @param #FLIGHTGROUP self - -- @param Core.Point#COORDINATE coordinate - -- @param #number altitude Altitude - -- @param #number leg Race track length. - -- @param #number heading Heading in degrees. - -- @param #number speed Speed in knots. - -- @param #FLIGHTGROUP.Squadron squadron Explicitly request a specific squadron. - - -- Debug trace. if true then self.Debug=true @@ -159,9 +194,11 @@ function FLIGHTGROUP:New(AIGroup) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) end + + + self:Start() return self - end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -169,19 +206,18 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after Start event. Starts the warehouse. Addes event handlers and schedules status updates of reqests and queue. +--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. -- @param #FLIGHTGROUP self -- @param Wrapper.Group#GROUP Group Flight group. -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function FLIGHTGROUP:onafterStart(Group, From, Event, To) +function FLIGHTGROUP:onafterStart(From, Event, To) -- Short info. - local text=string.format("Starting flight group %s\n", self.groupname) + local text=string.format("Starting flight group %s.", self.groupname) self:I(self.sid..text) - -- Handle events: self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.EngineStartup, self.OnEventEngineStartup) @@ -193,7 +229,61 @@ function FLIGHTGROUP:onafterStart(Group, From, Event, To) self:HandleEvent(EVENTS.Crash, self.OnEventCrash) -- Start the status monitoring. - self:__Status(-1) + self:__FlightStatus(-1) +end + +--- On after "FlightStatus" event. +-- @param #FLIGHTGROUP self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function FLIGHTGROUP:onafterFlightStatus(From, Event, To) + + -- FSM state. + local fsmstate=self:GetState() + + -- Short info. + local text=string.format("Flight group FSM status %s.", fsmstate) + self:I(self.sid..text) + + text="Elements:" + for i,_element in pairs(self.element) do + local element=_element --#FLIGHTGROUP.Element + local name=element.name + local status=element.status + local unit=element.unit + local fuel=unit:GetFuel() or 0 + local life=unit:GetLifeRelative() or 0 + + text=text..string.format("\n[%d] %s: status=%s, fuel=%.1f, life=%.1f", i, name, status, fuel*100, life*100) + end + if #self.element==0 then + text=text.." none!" + end + self:I(text) + + -- Get detected DCS units. + local detectedtargets=self.flightgroup:GetDetectedTargets() + + for DetectionObjectID, Detection in pairs(detectedtargets) do + local DetectedObject=Detection.object -- DCS#Object + + if DetectedObject and DetectedObject:isExist() and DetectedObject.id_<50000000 then + local unit=UNIT:Find(DetectedObject) + if unit and unit:IsAlive() then + local unitname=unit:GetName() + if self.detectedunits:FindUnit(unitname) then + -- Unit is already in the detected unit set. + else + self:AddDetectedUnit(unit) + end + end + end + end + + -- Next check in ~30 seconds. + self:__FlightStatus(-30) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -205,19 +295,135 @@ end -- @param Core.Event#EVENTDATA EventData Event data. function FLIGHTGROUP:OnEventBirth(EventData) - if EventData and EventData.IniGroup and EventData.IniUnit then + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + self.flightgroup=self.flightgroup or EventData.IniGroup + + if not self:_IsElement(unitname) then + local element=self:AddElementByName(unitname) + element.status=FLIGHTGROUP.ElementStatus.SPAWNED + end + + end + +end + +--- Flightgroup event function handling the crash of a unit. +-- @param #FLIGHTGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function FLIGHTGROUP:OnEventEngineStartup(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + -- Get element. + local element=self:GetElementByName(unitname) + + if element then + element.status=FLIGHTGROUP.ElementStatus.TAXIING + end + + end + +end + +--- Flightgroup event function handling the crash of a unit. +-- @param #FLIGHTGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function FLIGHTGROUP:OnEventTakeOff(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + -- Get element. + local element=self:GetElementByName(unitname) + + if element then + element.status=FLIGHTGROUP.ElementStatus.AIRBORNE + end + + end + +end + +--- Flightgroup event function handling the crash of a unit. +-- @param #FLIGHTGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function FLIGHTGROUP:OnEventLanding(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + -- Get element. + local element=self:GetElementByName(unitname) + + if element then + element.status=FLIGHTGROUP.ElementStatus.LANDED + end + + end + +end + +--- Flightgroup event function handling the crash of a unit. +-- @param #FLIGHTGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function FLIGHTGROUP:OnEventEngineShutdown(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + -- Get element. + local element=self:GetElementByName(unitname) + + if element then + local coord=unit:GetCoordinate() + local airbase=coord:GetClosestAirbase() + local _,_,dist,parking=coord:GetClosestParkingSpot(airbase) + if dist and dist<10 and unit:InAir()==false then + element.status=FLIGHTGROUP.ElementStatus.ARRIVED + else + element.status=FLIGHTGROUP.ElementStatus.DEAD + end + end + + end + +end + + +--- Flightgroup event function handling the crash of a unit. +-- @param #FLIGHTGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function FLIGHTGROUP:OnEventCrash(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then local unit=EventData.IniUnit local group=EventData.IniGroup local unitname=EventData.IniUnitName + -- Get element. local element=self:GetElementByName(unitname) if element then - element.status="born" - - if unit:InAir() then - element.status="airborn" - end + element.status=FLIGHTGROUP.ElementStatus.DEAD end end @@ -228,11 +434,45 @@ end -- FSM functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- 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 +-- @parma #string assignment The (optional) assignment for the asset. +function FLIGHTGROUP:onafterAddDetectedUnit(From, Event, To, Unit) + self:I(self.sid..string.format("Detected unit %s.", Unit:GetName())) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Add an element to the flight group. +-- @param #FLIGHTGROUP self +-- @param #string unitname Name of unit. +-- @return #FLIGHTGROUP.Element The element or nil. +function FLIGHTGROUP:AddElementByName(unitname) + + local unit=UNIT:FindByName(unitname) + + if unit then + + local element={} --#FLIGHTGROUP.Element + + element.name=unitname + element.unit=unit + element.status="unknown" + + table.insert(self.element, element) + + return element + end + + return nil +end --- Check if a unit is and element of the flightgroup. -- @param #FLIGHTGROUP self @@ -256,6 +496,7 @@ end --- Check if a unit is and element of the flightgroup. -- @param #FLIGHTGROUP self -- @param #string unitname Name of unit. +-- @return #boolean If true, unit is element of the flight group or false if otherwise. function FLIGHTGROUP:_IsElement(unitname) for _,_element in pairs(self.element) do @@ -270,6 +511,35 @@ function FLIGHTGROUP:_IsElement(unitname) return false end +--- Check if all elements of the flight group have the same status or are dead. +-- @param #FLIGHTGROUP self +-- @param #string unitname Name of unit. +function FLIGHTGROUP:_AllStatus(status) + + for _,_element in pairs(self.element) do + local element=_element --#FLIGHTGROUP.Element + + if element.status==FLIGHTGROUP.ElementStatus.DEAD then + -- Do nothing. Element is already dead and does not count. + elseif element.status~=status then + -- At least this element has a different status. + return false + end + + end + + if status==FLIGHTGROUP.ElementStatus.DEAD then + -- All elements are dead. + self:FlightDead() + elseif status==FLIGHTGROUP.ElementStatus.ARRIVED then + self:FlightArrived() + elseif status==FLIGHTGROUP.ElementStatus.AIRBORNE then + self:FlightAirborne() + end + + return true +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------