From 4537784ad96b38bcc8216f6c412ac6378e6ae5f7 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 15 May 2020 22:44:15 +0200 Subject: [PATCH] Refactoring --- Moose Development/Moose/Ops/FlightGroup.lua | 1272 +------------------ Moose Development/Moose/Ops/NavyGroup.lua | 356 ++++-- Moose Development/Moose/Ops/OpsGroup.lua | 1174 ++++++++++++++++- 3 files changed, 1428 insertions(+), 1374 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 419958218..c7b32e0eb 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -43,34 +43,6 @@ -- @field #string controlstatus Flight control status. -- @field #boolean ishelo If true, the is a helicopter group. -- --- @field #number tacanChannelDefault The default TACAN channel. --- @field #string tacanMorseDefault The default TACAN morse code. --- @field #number tacanChannel The currenly used TACAN channel. --- @field #string tacanMorse The currently used TACAN morse code. --- @field #boolean tacanOn If true, TACAN is currently active. --- @field Wrapper.Unit#UNIT tacanBeacon The unit acting as TACAN beacon. --- --- @field #number radioFreqDefault Default radio frequency in MHz. --- @field #number radioFreq Currently used radio frequency in MHz. --- @field #number radioModuDefault Default Radio modulation `radio.modulation.AM` or `radio.modulation.FM`. --- @field #number radioModu Currently used radio modulation `radio.modulation.AM` or `radio.modulation.FM`. --- @field #boolean radioOn If true, radio is currently turned on. --- @field Core.RadioQueue#RADIOQUEUE radioQueue Radio queue. --- --- @field #number CallsignName Call sign name. --- @field #number CallsignNumber Call sign number. --- --- @field #boolean eplrsDefault Default EPLRS data link setting. --- @field #boolean eplrs If true, EPLRS data link is on. --- --- @field #string roeDefault Default ROE setting. --- @field #string rotDefault Default ROT setting. --- @field #string roe Current ROE setting. --- @field #string rot Current ROT setting. --- --- @field #number formationDefault Default formation setting. --- @field #number formation Current formation setting. --- -- @extends Ops.OpsGroup#OPSGROUP --- *To invent an airplane is nothing. To build one is something. To fly is everything.* -- Otto Lilienthal @@ -183,33 +155,6 @@ FLIGHTGROUP = { } ---- Status of flight group element. --- @type FLIGHTGROUP.ElementStatus --- @field #string INUTERO Element was not spawned yet or its status is unknown so far. --- @field #string SPAWNED Element was spawned into the world. --- @field #string PARKING Element is parking after spawned on ramp. --- @field #string ENGINEON Element started its engines. --- @field #string TAXIING Element is taxiing after engine startup. --- @field #string TAKEOFF Element took of after takeoff event. --- @field #string AIRBORNE Element is airborne. Either after takeoff or after air start. --- @field #string LANDING Element is landing. --- @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={ - INUTERO="inutero", - SPAWNED="spawned", - PARKING="parking", - ENGINEON="engineon", - TAXIING="taxiing", - TAKEOFF="takeoff", - AIRBORNE="airborne", - LANDING="landing", - LANDED="landed", - ARRIVED="arrived", - DEAD="dead", -} - --- Generalized attribute. See [DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) on hoggit. -- @type FLIGHTGROUP.Attribute -- @field #string TRANSPORTPLANE Airplane with transport capability. This can be used to transport other assets. @@ -247,7 +192,7 @@ FLIGHTGROUP.Attribute = { -- @field #number category Aircraft category. -- @field #string categoryname Aircraft category name. -- @field #string callsign Call sign, e.g. "Uzi 1-1". --- @field #string status Status, i.e. born, parking, taxiing. See @{#FLIGHTGROUP.ElementStatus}. +-- @field #string status Status, i.e. born, parking, taxiing. See @{#OPSGROUP.ElementStatus}. -- @field #number damage Damage of element in percent. -- @field Wrapper.Airbase#AIRBASE.ParkingSpot parking The parking spot table the element is parking on. @@ -265,7 +210,7 @@ FLIGHTGROUP.Attribute = { --- FLIGHTGROUP class version. -- @field #string version -FLIGHTGROUP.version="0.4.1" +FLIGHTGROUP.version="0.5.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -348,7 +293,7 @@ function FLIGHTGROUP:New(groupname, autostart) self:AddTransition("Airborne", "EngageTargets", "Engaging") -- Engage targets. self:AddTransition("Engaging", "Disengage", "Airborne") -- Engagement over. - self:AddTransition("*", "ElementSpawned", "*") -- An element was spawned. + self:AddTransition("*", "ElementParking", "*") -- An element is parking. self:AddTransition("*", "ElementEngineOn", "*") -- An element spooled up the engines. self:AddTransition("*", "ElementTaxiing", "*") -- An element is taxiing to the runway. @@ -356,11 +301,11 @@ function FLIGHTGROUP:New(groupname, autostart) self:AddTransition("*", "ElementAirborne", "*") -- An element is airborne. self:AddTransition("*", "ElementLanded", "*") -- An element landed. self:AddTransition("*", "ElementArrived", "*") -- An element arrived. - self:AddTransition("*", "ElementDead", "*") -- An element crashed, ejected, or pilot dead. + self:AddTransition("*", "ElementOutOfAmmo", "*") -- An element is completely out of ammo. - self:AddTransition("*", "FlightSpawned", "Spawned") -- The whole flight group was spawned. + self:AddTransition("*", "FlightParking", "Parking") -- The whole flight group is parking. self:AddTransition("*", "FlightTaxiing", "Taxiing") -- The whole flight group is taxiing. self:AddTransition("*", "FlightTakeoff", "Airborne") -- The whole flight group is airborne. @@ -368,7 +313,7 @@ function FLIGHTGROUP:New(groupname, autostart) self:AddTransition("*", "FlightLanding", "Landing") -- The whole flight group is landing. self:AddTransition("*", "FlightLanded", "Landed") -- The whole flight group has landed. self:AddTransition("*", "FlightArrived", "Arrived") -- The whole flight group has arrived. - self:AddTransition("*", "FlightDead", "Dead") -- The whole flight group is dead. + ------------------------ --- Pseudo Functions --- @@ -410,11 +355,22 @@ function FLIGHTGROUP:New(groupname, autostart) -- Add to data base. _DATABASE:AddFlightGroup(self) + + -- Handle events: + self:HandleEvent(EVENTS.Birth, self.OnEventBirth) + self:HandleEvent(EVENTS.EngineStartup, self.OnEventEngineStartup) + self:HandleEvent(EVENTS.Takeoff, self.OnEventTakeOff) + self:HandleEvent(EVENTS.Land, self.OnEventLanding) + self:HandleEvent(EVENTS.EngineShutdown, self.OnEventEngineShutdown) + self:HandleEvent(EVENTS.PilotDead, self.OnEventPilotDead) + self:HandleEvent(EVENTS.Ejection, self.OnEventEjection) + self:HandleEvent(EVENTS.Crash, self.OnEventCrash) + self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) - -- Autostart. - if autostart==true or autostart==nil then - self:Start() - end + -- Start the status monitoring. + self:__CheckZone(-1) + self:__Status(-2) + self:__QueueUpdate(-3) return self end @@ -546,21 +502,6 @@ function FLIGHTGROUP:SetFuelCriticalThreshold(threshold, rtb) end - ---- Check if flight is in state in utero. --- @param #FLIGHTGROUP self --- @return #boolean If true, flight is not spawned yet. -function FLIGHTGROUP:IsInUtero() - return self:Is("InUtero") -end - ---- Check if flight is in state spawned. --- @param #FLIGHTGROUP self --- @return #boolean If true, flight is spawned. -function FLIGHTGROUP:IsSpawned() - return self:Is("Spawned") -end - --- Check if flight is parking. -- @param #FLIGHTGROUP self -- @return #boolean If true, flight is parking after spawned. @@ -645,20 +586,6 @@ function FLIGHTGROUP:IsLandedAt() return self:Is("LandedAt") end ---- Check if flight is dead. --- @param #FLIGHTGROUP self --- @return #boolean If true, all units/elements of the flight are dead. -function FLIGHTGROUP:IsDead() - return self:Is("Dead") -end - ---- Check if flight FSM is stopped. --- @param #FLIGHTGROUP self --- @return #boolean If true, FSM state is stopped. -function FLIGHTGROUP:IsStopped() - return self:Is("Stopped") -end - --- Check if flight is low on fuel. -- @param #FLIGHTGROUP self -- @return #boolean If true, flight is low on fuel. @@ -826,21 +753,7 @@ function FLIGHTGROUP:onafterStart(From, Event, To) end - -- Handle events: - self:HandleEvent(EVENTS.Birth, self.OnEventBirth) - self:HandleEvent(EVENTS.EngineStartup, self.OnEventEngineStartup) - self:HandleEvent(EVENTS.Takeoff, self.OnEventTakeOff) - self:HandleEvent(EVENTS.Land, self.OnEventLanding) - self:HandleEvent(EVENTS.EngineShutdown, self.OnEventEngineShutdown) - self:HandleEvent(EVENTS.PilotDead, self.OnEventPilotDead) - self:HandleEvent(EVENTS.Ejection, self.OnEventEjection) - self:HandleEvent(EVENTS.Crash, self.OnEventCrash) - self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) - -- Start the status monitoring. - self:__CheckZone(-1) - self:__Status(-2) - self:__QueueUpdate(-3) end --- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. @@ -920,7 +833,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- TODO: Check distance threshold! If element is taxiing, the parking spot is free again. -- When the next plane is spawned on this spot, collisions should be avoided! if dist>10 then - if element.status==FLIGHTGROUP.ElementStatus.ENGINEON then + if element.status==OPSGROUP.ElementStatus.ENGINEON then self:ElementTaxiing(element) end end @@ -960,7 +873,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) local parking=element.parking and tostring(element.parking.TerminalID) or "X" -- Check if element is not dead and we missed an event. - if life<0 and element.status~=FLIGHTGROUP.ElementStatus.DEAD and element.status~=FLIGHTGROUP.ElementStatus.INUTERO then + if life<0 and element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then self:ElementDead(element) end @@ -1066,9 +979,9 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) end end -- Output text for element. - if task.type==FLIGHTGROUP.TaskType.SCHEDULED then + if task.type==OPSGROUP.TaskType.SCHEDULED then text=text..string.format("\n[%d] %s (%s): status=%s, scheduled=%s (%d sec), started=%s, duration=%d", i, taskid, name, status, clock, eta, started, duration) - elseif task.type==FLIGHTGROUP.TaskType.WAYPOINT then + elseif task.type==OPSGROUP.TaskType.WAYPOINT then text=text..string.format("\n[%d] %s (%s): status=%s, waypoint=%d, started=%s, duration=%d, stopflag=%d", i, taskid, name, status, task.waypoint, started, duration, task.stopflag:Get()) end end @@ -1457,7 +1370,7 @@ function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element) self:T(self.lid..string.format("Element spawned %s", Element.name)) -- Set element status. - self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.SPAWNED) + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.SPAWNED) if Element.unit:InAir() then -- Trigger ElementAirborne event. Add a little delay because spawn is also delayed! @@ -1491,7 +1404,7 @@ function FLIGHTGROUP:onafterElementParking(From, Event, To, Element, Spot) self:T(self.lid..string.format("Element parking %s at spot %s", Element.name, Element.parking and tostring(Element.parking.TerminalID) or "N/A")) -- Set element status. - self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.PARKING) + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.PARKING) if Spot then self:_SetElementParkingAt(Element, Spot) @@ -1518,7 +1431,7 @@ function FLIGHTGROUP:onafterElementEngineOn(From, Event, To, Element) self:T(self.lid..string.format("Element %s started engines", Element.name)) -- Set element status. - self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.ENGINEON) + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.ENGINEON) end --- On after "ElementTaxiing" event. @@ -1539,7 +1452,7 @@ function FLIGHTGROUP:onafterElementTaxiing(From, Event, To, Element) self:_SetElementParkingFree(Element) -- Set element status. - self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.TAXIING) + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.TAXIING) end --- On after "ElementTakeoff" event. @@ -1558,7 +1471,7 @@ function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase) end -- Set element status. - self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.TAKEOFF, airbase) + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.TAKEOFF, airbase) -- Trigger element airborne event. self:__ElementAirborne(2, Element) @@ -1574,7 +1487,7 @@ function FLIGHTGROUP:onafterElementAirborne(From, Event, To, Element) self:T2(self.lid..string.format("Element airborne %s", Element.name)) -- Set element status. - self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.AIRBORNE) + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.AIRBORNE) end --- On after "ElementLanded" event. @@ -1597,7 +1510,7 @@ function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) end -- Set element status. - self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.LANDED, airbase) + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.LANDED, airbase) end --- On after "ElementArrived" event. @@ -1614,7 +1527,7 @@ function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element, airbase, Pa self:_SetElementParkingAt(Element, Parking) -- Set element status. - self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.ARRIVED) + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.ARRIVED) end --- On after "ElementDead" event. @@ -1634,17 +1547,17 @@ function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) Element.parking=nil -- Set element status. - self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.DEAD) + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) end ---- On after "FlightSpawned" event. Sets the template, initializes the waypoints. +--- On after "Spawned" event. Sets the template, initializes the waypoints. -- @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:onafterFlightSpawned(From, Event, To) +function FLIGHTGROUP:onafterSpawned(From, Event, To) self:I(self.lid..string.format("Flight spawned!")) if self.ai then @@ -1668,6 +1581,9 @@ function FLIGHTGROUP:onafterFlightSpawned(From, Event, To) if self.radioFreqDefault then self:SwitchRadioOn(self.radioFreqDefault, self.radioModuDefault) end + + -- Update route. + self:__UpdateRoute(-0.5) else @@ -1784,7 +1700,7 @@ end function FLIGHTGROUP:onafterFlightLanding(From, Event, To) self:T(self.lid..string.format("Flight is landing")) - self:_SetElementStatusAll(FLIGHTGROUP.ElementStatus.LANDING) + self:_SetElementStatusAll(OPSGROUP.ElementStatus.LANDING) end @@ -2716,586 +2632,17 @@ function FLIGHTGROUP:onafterFuelCritical(From, Event, To) end - - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Task functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - ---- On after TaskExecute event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #FLIGHTGROUP.Task Task The task. -function FLIGHTGROUP:onafterTaskExecute(From, Event, To, Task) - - -- Debug message. - local text=string.format("Task %s ID=%d execute.", tostring(Task.description), Task.id) - MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) - self:I(self.lid..text) - - -- Cancel current task if there is any. - if self.taskcurrent>0 then - self:TaskCancel() - end - - -- Set current task. - self.taskcurrent=Task.id - - -- Set time stamp. - Task.timestamp=timer.getAbsTime() - - -- Task status executing. - Task.status=FLIGHTGROUP.TaskStatus.EXECUTING - - if Task.dcstask.id=="Formation" then - - -- Set of group(s) to follow Mother. - local followSet=SET_GROUP:New():AddGroup(self.group) - - local param=Task.dcstask.params - - local followUnit=UNIT:FindByName(param.unitname) - - -- Define AI Formation object. - Task.formation=AI_FORMATION:New(followUnit, followSet, "Formation", "Follow X at given parameters.") - - -- Formation parameters. - Task.formation:FormationCenterWing(-param.offsetX, 50, math.abs(param.altitude), 50, param.offsetZ, 50) - - -- Set follow time interval. - Task.formation:SetFollowTimeInterval(param.dtFollow) - - -- Formation mode. - Task.formation:SetFlightModeFormation(self.group) - - -- Start formation FSM. - Task.formation:Start() - - else - - -- If task is scheduled (not waypoint) set task. - if Task.type==FLIGHTGROUP.TaskType.SCHEDULED then - - local DCStasks={} - if Task.dcstask.id=='ComboTask' then - -- Loop over all combo tasks. - for TaskID, Task in ipairs(Task.dcstask.params.tasks) do - table.insert(DCStasks, Task) - end - else - table.insert(DCStasks, Task.dcstask) - end - - -- Combo task. - local TaskCombo=self.group:TaskCombo(DCStasks) - - -- Stop condition! - local TaskCondition=self.group:TaskCondition(nil, Task.stopflag:GetName(), 1, nil, Task.duration) - - -- Controlled task. - local TaskControlled=self.group:TaskControlled(TaskCombo, TaskCondition) - - -- Task done. - local TaskDone=self.group:TaskFunction("FLIGHTGROUP._TaskDone", self, Task) - - -- Final task. - local TaskFinal=self.group:TaskCombo({TaskControlled, TaskDone}) - - -- Set task for group. - self:SetTask(TaskFinal, 1) - - end - - end - - -- Get mission of this task (if any). - local Mission=self:GetMissionByTaskID(self.taskcurrent) - if Mission then - -- Set AUFTRAG status. - self:MissionExecute(Mission) - end - -end - ---- On after "TaskPause" event. Pauses the current task. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function FLIGHTGROUP:onafterTaskPause(From, Event, To) - - local Task=self:GetTaskCurrent() - - if Task then - - -- Clear all tasks. - self:ClearTasks() - - -- Task status executing. - 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 or simply sets the status to DONE if the task is not the current one. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #FLIGHTGROUP.Task Task The task to cancel. Default is the current task (if any). -function FLIGHTGROUP:onafterTaskCancel(From, Event, To, Task) - - -- Get current task. - local currenttask=self:GetTaskCurrent() - - -- If no task, we take the current task. But this could also be *nil*! - Task=Task or currenttask - - if Task then - - -- Check if the task is the current task? - if currenttask and Task.id==currenttask.id then - - -- Current stop flag value. I noticed cases, where setting the flag to 1 would not cancel the task, e.g. when firing HARMS on a dead ship. - local stopflag=Task.stopflag:Get() - - -- Debug info. - local text=string.format("Current task %s ID=%d cancelled (flag %s=%d)", Task.description, Task.id, Task.stopflag:GetName(), stopflag) - MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) - self:I(self.lid..text) - - -- Set stop flag. When the flag is true, the _TaskDone function is executed and calls :TaskDone() - Task.stopflag:Set(1) - - if Task.dcstask.id=="Formation" then - Task.formation:Stop() - self:TaskDone(Task) - elseif stopflag==1 then - -- Manual call TaskDone if setting flag to one was not successful. - self:TaskDone(Task) - end - - else - - -- Debug info. - self:I(self.lid..string.format("TaskCancel: Setting task %s ID=%d to DONE", Task.description, Task.id)) - - -- Call task done function. - self:TaskDone(Task) - - - --[[ - local mission=self:GetMissionByTaskID(Task.id) - - -- Is this a waypoint task? - if Task.type==FLIGHTGROUP.TaskType.WAYPOINT and Task.waypoint then - - -- Check that this is a mission waypoint and no other tasks are defined here. - if mission and #self:GetTasksWaypoint(Task.waypoint)==0 then - self:RemoveWaypoint(Task.waypoint) - end - end - ]] - end - - else - - local text=string.format("WARNING: No (current) task to cancel!") - MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) - self:E(self.lid..text) - - end - -end - ---- On before "TaskDone" event. Deny transition if task status is PAUSED. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #FLIGHTGROUP.Task Task -function FLIGHTGROUP:onbeforeTaskDone(From, Event, To, Task) - - local allowed=true - - if Task.status==FLIGHTGROUP.TaskStatus.PAUSED then - allowed=false - end - - return allowed -end - ---- On after "TaskDone" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #FLIGHTGROUP.Task Task -function FLIGHTGROUP:onafterTaskDone(From, Event, To, Task) - - -- Debug message. - local text=string.format("Task done: %s ID=%d", Task.description, Task.id) - MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) - self:I(self.lid..text) - - -- No current task. - if Task.id==self.taskcurrent then - self.taskcurrent=0 - end - - -- Task status done. - Task.status=FLIGHTGROUP.TaskStatus.DONE - - -- Check if this task was the task of the current mission ==> Mission Done! - local Mission=self:GetMissionByTaskID(Task.id) - - if Mission and Mission:IsNotOver() then - - local status=Mission:GetFlightStatus(self) - - if status~=AUFTRAG.FlightStatus.PAUSED then - self:I(self.lid.."FF Task Done ==> Mission Done!") - self:MissionDone(Mission) - else - --Mission paused. Do nothing! - end - else - self:I(self.lid.."FF Task Done but NO mission found ==> _CheckFlightDone in 1 sec") - self:_CheckFlightDone(1) - end - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Mission functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On before "MissionStart" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.Auftrag#AUFTRAG Mission The mission table. -function FLIGHTGROUP:onbeforeMissionStart(From, Event, To, Mission) - -- Debug info. - self:I(self.lid..string.format("Starting mission %s, FSM=%s, LateActivated=%s, UnControlled=%s", tostring(Mission.name), self:GetState(), tostring(self:IsLateActivated()), tostring(self:IsUncontrolled()))) - - -- Delay for route to mission. Group needs to be activated and controlled. - local delay=1 - - -- Check if group is spawned. - if self:IsInUtero() then - - -- Activate group if it is late activated. - if self:IsLateActivated() then - self:Activate(delay) - delay=delay+1 - end - - end - - -- Startup group if it is uncontrolled. - if self:IsParking() and self:IsUncontrolled() then - self:StartUncontrolled(delay) - else - --env.info("FF hallo!") - --self:StartUncontrolled(5) - end - - return true -end - ---- On after "MissionStart" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.Auftrag#AUFTRAG Mission The mission table. -function FLIGHTGROUP:onafterMissionStart(From, Event, To, Mission) - - -- Debug output. - local text=string.format("Starting %s Mission %s, target %s", Mission.type, tostring(Mission.name), Mission:GetTargetName()) - self:T(self.lid..text) - MESSAGE:New(text, 30, self.groupname):ToAllIf(true) - - -- Set current mission. - self.currentmission=Mission.auftragsnummer - - -- Set flight mission status to STARTED. - Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.STARTED) - - -- Set mission status to STARTED. - Mission:Started() - - -- Route flight to mission zone. - self:RouteToMission(Mission, 3) - -end - ---- On after "MissionExecute" event. Mission execution began. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.Auftrag#AUFTRAG Mission The mission table. -function FLIGHTGROUP:onafterMissionExecute(From, Event, To, Mission) - - local text=string.format("Executing %s Mission %s, target %s", Mission.type, tostring(Mission.name), Mission:GetTargetName()) - self:T(self.lid..text) - MESSAGE:New(text, 30, self.groupname):ToAllIf(true) - - -- Set flight mission status to EXECUTING. - Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.EXECUTING) - - -- Set mission status to EXECUTING. - Mission:Executing() - - -- Formation - if Mission.optionFormation then - self:SwitchFormation(Mission.optionFormation) - end - -end - ---- On after "PauseMission" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function FLIGHTGROUP:onafterPauseMission(From, Event, To) - - local Mission=self:GetMissionCurrent() - - if Mission then - - -- Set flight mission status to PAUSED. - Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.PAUSED) - - -- Get mission waypoint task. - local Task=Mission:GetFlightWaypointTask(self) - - -- Debug message. - self:I(self.lid..string.format("FF pausing current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF"))) - - -- Cancelling the mission is actually cancelling the current task. - self:TaskCancel(Task) - - -- Set mission to pause so we can unpause it later. - self.missionpaused=Mission - - end - -end - ---- On after "UnpauseMission" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function FLIGHTGROUP:onafterUnpauseMission(From, Event, To) - - self:I(self.lid..string.format("Unpausing mission")) - - if self.missionpaused then - - local mission=self:GetMissionByID(self.missionpaused.auftragsnummer) - - self:MissionStart(mission) - - self.missionpaused=nil - else - self:E(self.lid.."ERROR: No mission to unpause!") - end - -end - - ---- On after "MissionCancel" event. Cancels the mission. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.Auftrag#AUFTRAG Mission The mission to be cancelled. -function FLIGHTGROUP:onafterMissionCancel(From, Event, To, Mission) - - if self.currentmission and Mission.auftragsnummer==self.currentmission then - - -- Get mission waypoint task. - local Task=Mission:GetFlightWaypointTask(self) - - -- Debug info. - self:I(self.lid..string.format("FF Cancel current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF"))) - - -- Cancelling the mission is actually cancelling the current task. - -- Note that two things can happen. - -- 1.) Flight is still on the way to the waypoint (status should be STARTED). In this case there would not be a current task! - -- 2.) Flight already passed the mission waypoint (status should be EXECUTING). - - self:TaskCancel(Task) - - else - - -- Not the current mission. - -- TODO: remove mission from queue? - - -- Set mission flight status. - Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.CANCELLED) - - -- Send flight RTB or WAIT if nothing left to do. - self:_CheckFlightDone(1) - - end - -end - ---- On after "MissionDone" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.Auftrag#AUFTRAG Mission -function FLIGHTGROUP:onafterMissionDone(From, Event, To, Mission) - - -- Debug info. - local text=string.format("Mission %s DONE!", Mission.name) - self:I(self.lid..text) - MESSAGE:New(text, 30, self.groupname):ToAllIf(true) - - -- Set Flight status. - Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.DONE) - - -- Set current mission to nil. - if self.currentmission and Mission.auftragsnummer==self.currentmission then - self.currentmission=nil - end - - -- Remove mission waypoint. - local wpidx=Mission:GetFlightWaypointIndex(self) - if wpidx then - self:RemoveWaypoint(wpidx) - end - - -- Decrease patrol data. - if Mission.patroldata then - Mission.patroldata.noccupied=Mission.patroldata.noccupied-1 - AIRWING.UpdatePatrolPointMarker(Mission.patroldata) - end - - -- TODO: reset mission specific parameters like radio, ROE etc. - - -- Check if flight is done. - self:_CheckFlightDone(1) - -end - ---- Route group to mission. --- @param #FLIGHTGROUP self --- @param Ops.Auftrag#AUFTRAG mission The mission table. --- @param #number delay Delay in seconds. -function FLIGHTGROUP:RouteToMission(mission, delay) - - if delay and delay>0 then - -- Delayed call. - self:ScheduleOnce(delay, FLIGHTGROUP.RouteToMission, self, mission) - else - - -- Next waypoint. - local nextwaypoint=self.currentwp+1 - - -- Create waypoint coordinate half way between us and the target. - local waypointcoord=self.group:GetCoordinate():GetIntermediateCoordinate(mission:GetTargetCoordinate(), mission.missionFraction) - local alt=waypointcoord.y - - -- Add some randomization. - waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), 1000):GetRandomCoordinate():SetAltitude(alt, false) - - -- Set altitude of mission waypoint. - if mission.missionAltitude then - waypointcoord:SetAltitude(mission.missionAltitude, true) - end - env.info(string.format("FF mission alt=%d", waypointcoord.y)) - - -- Add enroute tasks. - for _,task in pairs(mission.enrouteTasks) do - self:AddTaskEnroute(task) - end - - -- Add waypoint. - self:AddWaypointAir(waypointcoord, nextwaypoint, UTILS.KmphToKnots(self.speedCruise), false) - - -- Special for Troop transport. - if mission.type==AUFTRAG.Type.TROOPTRANSPORT then - - -- Refresh DCS task with the known controllable. - mission.DCStask=mission:GetDCSMissionTask(self.group) - - -- Add task to embark for the troops. - for _,_group in pairs(mission.transportGroupSet.Set) do - local group=_group --Wrapper.Group#GROUP - - if group and group:IsAlive() then - local DCSTask=group:TaskEmbarkToTransport(mission.transportPickup, 500) - group:SetTask(DCSTask, 5) - end - - end - - end - - -- Add waypoint task. UpdateRoute is called inside. - local waypointtask=self:AddTaskWaypoint(mission.DCStask, nextwaypoint, mission.name, mission.prio, mission.duration) - - -- Set waypoint task. - mission:SetFlightWaypointTask(self, waypointtask) - - -- Set waypoint index. - mission:SetFlightWaypointIndex(self, nextwaypoint) - - --- - -- Mission Specific Settings - --- - - -- ROE - if mission.optionROE then - self:SetOptionROE(mission.optionROE) - end - -- ROT - if mission.optionROT then - self:SetOptionROT(mission.optionROT) - end - -- Radio - if mission.radioFreq then - self:SwitchRadioOn(mission.radioFreq, mission.radioModu) - end - -- TACAN - if mission.tacanChannel then - self:SwitchTACANOn(mission.tacanChannel, mission.tacanMorse) - end - -- Formation - if mission.optionFormation then - self:SwitchFormation(mission.optionFormation) - end - - end -end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Special Task Functions @@ -3408,6 +2755,11 @@ function FLIGHTGROUP:_InitGroup() self:SwitchFormation(self.formationDefault) + -- Add elemets. + for _,unit in pairs(self.group:GetUnits()) do + local element=self:AddElementByName(unit:GetName()) + end + -- Get first unit. This is used to extract other parameters. local unit=self.group:GetUnit(1) @@ -3475,7 +2827,7 @@ function FLIGHTGROUP:AddElementByName(unitname) element.name=unitname element.unit=unit - element.status=FLIGHTGROUP.ElementStatus.INUTERO + element.status=OPSGROUP.ElementStatus.INUTERO element.group=unit:GetGroup() element.modex=element.unit:GetTemplate().onboard_num @@ -3509,23 +2861,6 @@ function FLIGHTGROUP:AddElementByName(unitname) return nil end ---- Check if a unit is and element of the flightgroup. --- @param #FLIGHTGROUP self --- @param #string unitname Name of unit. --- @return #FLIGHTGROUP.Element The element. -function FLIGHTGROUP:GetElementByName(unitname) - - for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element - - if element.name==unitname then - return element - end - - end - - return nil -end --- Check if a unit is and element of the flightgroup. -- @param #FLIGHTGROUP self @@ -3785,13 +3120,6 @@ function FLIGHTGROUP:IsLandingAirbase(wp) end ---- Check if this group is currently "late activated" and needs to be "activated" to appear in the mission. --- @param #FLIGHTGROUP self --- @return #boolean Is this the group late activated? -function FLIGHTGROUP:IsLateActivated() - return self.isLateActivated -end - --- Check if this group is currently "uncontrolled" and needs to be "started" to begin its route. -- @param #FLIGHTGROUP self -- @return #boolean If this group uncontrolled. @@ -3817,8 +3145,6 @@ function FLIGHTGROUP:CheckTaskDescriptionUnique(description) end - - --- Get next waypoint of the flight group. -- @param #FLIGHTGROUP self -- @return Core.Point#COORDINATE Coordinate of the next waypoint. @@ -3848,35 +3174,6 @@ function FLIGHTGROUP:GetWaypointCoordinate(wp) return COORDINATE:New(wp.x, wp.alt, wp.y) end ---- Initialize Mission Editor waypoints. --- @param #FLIGHTGROUP self -function FLIGHTGROUP:_UpdateWaypointTasks() - - local waypoints=self.waypoints - local nwaypoints=#waypoints - - for i,wp in pairs(waypoints) do - - if i>self.currentwp or nwaypoints==1 then - - -- Debug info. - self:T2(self.lid..string.format("Updating waypoint task for waypoint %d/%d. Last waypoint passed %d.", i, nwaypoints, self.currentwp)) - - -- Tasks of this waypoint - local taskswp={} - - -- At each waypoint report passing. - local TaskPassingWaypoint=self.group:TaskFunction("FLIGHTGROUP._PassingWaypoint", self, i) - table.insert(taskswp, TaskPassingWaypoint) - - -- Waypoint task combo. - wp.task=self.group:TaskCombo(taskswp) - - end - end - -end - --- Initialize Mission Editor waypoints. -- @param #FLIGHTGROUP self -- @param #table waypoints Table of waypoints. Default is from group template. @@ -3953,7 +3250,7 @@ function FLIGHTGROUP:AddWaypointAir(coordinate, wpnumber, speed, updateroute) -- Shift all waypoint tasks after the inserted waypoint. for _,_task in pairs(self.taskqueue) do local task=_task --#FLIGHTGROUP.Task - if task.type==FLIGHTGROUP.TaskType.WAYPOINT and task.waypoint and task.waypoint>=wpnumber then + if task.type==OPSGROUP.TaskType.WAYPOINT and task.waypoint and task.waypoint>=wpnumber then task.waypoint=task.waypoint+1 end end @@ -4001,275 +3298,7 @@ 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:_AllSameStatus(status) - for _,_element in pairs(self.elements) 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 - - return true -end - ---- Check if all elements of the flight group have the same status (or are dead). --- @param #FLIGHTGROUP self --- @param #string status Status to check. --- @return #boolean If true, all elements have a similar status. -function FLIGHTGROUP:_AllSimilarStatus(status) - - -- Check if all are dead. - if status==FLIGHTGROUP.ElementStatus.DEAD then - for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element - if element.status~=FLIGHTGROUP.ElementStatus.DEAD then - -- At least one is still alive. - return false - end - end - return true - end - - for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element - - self:T3(self.lid..string.format("Status=%s, element %s status=%s", status, element.name, element.status)) - - -- Dead units dont count ==> We wont return false for those. - if element.status~=FLIGHTGROUP.ElementStatus.DEAD then - - ---------- - -- ALIVE - ---------- - - if status==FLIGHTGROUP.ElementStatus.SPAWNED then - - -- Element SPAWNED: Check that others are not still IN UTERO - if element.status~=status and - element.status==FLIGHTGROUP.ElementStatus.INUTERO then - return false - end - - elseif status==FLIGHTGROUP.ElementStatus.PARKING then - - -- Element PARKING: Check that the other are not still SPAWNED - if element.status~=status or - (element.status==FLIGHTGROUP.ElementStatus.INUTERO or - element.status==FLIGHTGROUP.ElementStatus.SPAWNED) then - return false - end - - elseif status==FLIGHTGROUP.ElementStatus.ENGINEON then - - -- Element TAXIING: Check that the other are not still SPAWNED or PARKING - if element.status~=status and - (element.status==FLIGHTGROUP.ElementStatus.INUTERO or - element.status==FLIGHTGROUP.ElementStatus.SPAWNED or - element.status==FLIGHTGROUP.ElementStatus.PARKING) then - return false - end - - elseif status==FLIGHTGROUP.ElementStatus.TAXIING then - - -- Element TAXIING: Check that the other are not still SPAWNED or PARKING - if element.status~=status and - (element.status==FLIGHTGROUP.ElementStatus.INUTERO or - element.status==FLIGHTGROUP.ElementStatus.SPAWNED or - element.status==FLIGHTGROUP.ElementStatus.PARKING or - element.status==FLIGHTGROUP.ElementStatus.ENGINEON) then - return false - end - - elseif status==FLIGHTGROUP.ElementStatus.TAKEOFF then - - -- Element TAKEOFF: Check that the other are not still SPAWNED, PARKING or TAXIING - if element.status~=status and - (element.status==FLIGHTGROUP.ElementStatus.INUTERO or - element.status==FLIGHTGROUP.ElementStatus.SPAWNED or - element.status==FLIGHTGROUP.ElementStatus.PARKING or - element.status==FLIGHTGROUP.ElementStatus.ENGINEON or - element.status==FLIGHTGROUP.ElementStatus.TAXIING) then - return false - end - - elseif status==FLIGHTGROUP.ElementStatus.AIRBORNE then - - -- Element AIRBORNE: Check that the other are not still SPAWNED, PARKING, TAXIING or TAKEOFF - if element.status~=status and - (element.status==FLIGHTGROUP.ElementStatus.INUTERO or - element.status==FLIGHTGROUP.ElementStatus.SPAWNED or - element.status==FLIGHTGROUP.ElementStatus.PARKING or - element.status==FLIGHTGROUP.ElementStatus.ENGINEON or - element.status==FLIGHTGROUP.ElementStatus.TAXIING or - element.status==FLIGHTGROUP.ElementStatus.TAKEOFF) then - return false - end - - elseif status==FLIGHTGROUP.ElementStatus.LANDED then - - -- Element LANDED: check that the others are not still AIRBORNE or LANDING - if element.status~=status and - (element.status==FLIGHTGROUP.ElementStatus.AIRBORNE or - element.status==FLIGHTGROUP.ElementStatus.LANDING) then - return false - end - - elseif status==FLIGHTGROUP.ElementStatus.ARRIVED then - - -- Element ARRIVED: check that the others are not still AIRBORNE, LANDING, or LANDED (taxiing). - if element.status~=status and - (element.status==FLIGHTGROUP.ElementStatus.AIRBORNE or - element.status==FLIGHTGROUP.ElementStatus.LANDING or - element.status==FLIGHTGROUP.ElementStatus.LANDED) then - return false - end - - end - - else - -- Element is dead. We don't care unless all are dead. - end --DEAD - - end - - -- Debug info. - self:T3(self.lid..string.format("All %d elements have similar status %s ==> returning TRUE", #self.elements, status)) - - return true -end - ---- Check if all elements of the flight group have the same status or are dead. --- @param #FLIGHTGROUP self --- @param #FLIGHTGROUP.Element element Element. --- @param #string newstatus New status of element --- @param Wrapper.Airbase#AIRBASE airbase Airbase if applicable. -function FLIGHTGROUP:_UpdateStatus(element, newstatus, airbase) - - -- Old status. - local oldstatus=element.status - - -- Update status of element. - element.status=newstatus - - -- Debug - self:T3(self.lid..string.format("UpdateStatus element=%s: %s --> %s", element.name, oldstatus, newstatus)) - for _,_element in pairs(self.elements) do - local Element=_element -- #FLIGHTGROUP.Element - self:T3(self.lid..string.format("Element %s: %s", Element.name, Element.status)) - end - - if newstatus==FLIGHTGROUP.ElementStatus.SPAWNED then - --- - -- SPAWNED - --- - - if self:_AllSimilarStatus(newstatus) then - self:__FlightSpawned(-0.5) - end - - elseif newstatus==FLIGHTGROUP.ElementStatus.PARKING then - --- - -- PARKING - --- - - if self:_AllSimilarStatus(newstatus) then - self:__FlightParking(-0.5) - end - - elseif newstatus==FLIGHTGROUP.ElementStatus.ENGINEON then - --- - -- ENGINEON - --- - - -- No FLIGHT status. Waiting for taxiing. - - elseif newstatus==FLIGHTGROUP.ElementStatus.TAXIING then - --- - -- TAXIING - --- - - if self:_AllSimilarStatus(newstatus) then - self:__FlightTaxiing(-0.5) - end - - elseif newstatus==FLIGHTGROUP.ElementStatus.TAKEOFF then - --- - -- TAKEOFF - --- - - if self:_AllSimilarStatus(newstatus) then - -- Trigger takeoff event. Also triggers airborne event. - self:__FlightTakeoff(-0.5, airbase) - end - - elseif newstatus==FLIGHTGROUP.ElementStatus.AIRBORNE then - --- - -- AIRBORNE - --- - - if self:_AllSimilarStatus(newstatus) then - self:__FlightAirborne(-0.5) - end - - elseif newstatus==FLIGHTGROUP.ElementStatus.LANDED then - --- - -- LANDED - --- - - if self:_AllSimilarStatus(newstatus) then - self:FlightLanded(airbase) - end - - elseif newstatus==FLIGHTGROUP.ElementStatus.ARRIVED then - --- - -- ARRIVED - --- - - if self:_AllSimilarStatus(newstatus) then - - if self:IsLanded() then - self:FlightArrived() - elseif self:IsAirborne() then - self:FlightLanded() - self:FlightArrived() - end - - end - - elseif newstatus==FLIGHTGROUP.ElementStatus.DEAD then - --- - -- DEAD - --- - - if self:_AllSimilarStatus(newstatus) then - self:FlightDead() - end - - end -end - ---- Set status for all elements (except dead ones). --- @param #FLIGHTGROUP self --- @param #string status Element status. -function FLIGHTGROUP:_SetElementStatusAll(status) - - for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element - if element.status~=FLIGHTGROUP.ElementStatus.DEAD then - element.status=status - end - end - -end --- Set parking spot of element. -- @param #FLIGHTGROUP self @@ -4313,129 +3342,6 @@ function FLIGHTGROUP:_SetElementParkingFree(Element) end - ---- Check if flight is in zones. --- @param #FLIGHTGROUP self -function FLIGHTGROUP:_CheckInZones() - - if self.checkzones then - - local Ncheck=self.checkzones:Count() - local Ninside=self.inzones:Count() - - -- Debug info. - self:T(self.lid..string.format("Check if flight is in %d zones. Currently it is in %d zones.", self.checkzones:Count(), self.inzones:Count())) - - -- Firstly, check if group is still inside zone it was already in. If not, remove zones and trigger LeaveZone() event. - local leftzones={} - for inzonename, inzone in pairs(self.inzones:GetSet()) do - - -- Check if group is still inside the zone. - local isstillinzone=self.group:IsPartlyOrCompletelyInZone(inzone) - - -- If not, trigger, LeaveZone event. - if not isstillinzone then - table.insert(leftzones, inzone) - end - end - - -- Trigger leave zone event. - for _,leftzone in pairs(leftzones) do - self:LeaveZone(leftzone) - end - - - -- Now, run of all check zones and see if the group entered a zone. - local enterzones={} - for checkzonename,_checkzone in pairs(self.checkzones:GetSet()) do - local checkzone=_checkzone --Core.Zone#ZONE - - -- Is flight currtently in this check zone? - local isincheckzone=self.group:IsPartlyOrCompletelyInZone(checkzone) - - if isincheckzone and not self.inzones:_Find(checkzonename) then - table.insert(enterzones, checkzone) - end - end - - -- Trigger enter zone event. - for _,enterzone in pairs(enterzones) do - self:EnterZone(enterzone) - end - - - end - -end - ---- Check detected units. --- @param #FLIGHTGROUP self -function FLIGHTGROUP:_CheckDetectedUnits() - - if self.group and not self:IsDead() then - - -- Get detected DCS units. - local detectedtargets=self.group:GetDetectedTargets() - - local detected={} - for DetectionObjectID, Detection in pairs(detectedtargets or {}) 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 - - -- Name of detected unit - local unitname=unit:GetName() - - -- Add unit to detected table of this run. - table.insert(detected, unit) - - -- Trigger detected unit event. - self:DetectedUnit(unit) - - if self.detectedunits:FindUnit(unitname) then - -- Unit is already in the detected unit set ==> Trigger "DetectedUnitKnown" event. - self:DetectedUnitKnown(unit) - else - -- Unit is was not detected ==> Trigger "DetectedUnitNew" event. - self:DetectedUnitNew(unit) - end - - end - end - end - - -- Loop over units in detected set. - local lost={} - for _,_unit in pairs(self.detectedunits:GetSet()) do - local unit=_unit --Wrapper.Unit#UNIT - - -- Loop over detected units - local gotit=false - for _,_du in pairs(detected) do - local du=_du --Wrapper.Unit#UNIT - if unit:GetName()==du:GetName() then - gotit=true - end - end - - if not gotit then - table.insert(lost, unit:GetName()) - self:DetectedUnitLost(unit) - end - - end - - -- Remove lost units from detected set. - self.detectedunits:RemoveUnitsByName(lost) - - end - - -end - --- Get onboard number. -- @param #FLIGHTGROUP self -- @param #string unitname Name of the unit. @@ -4785,28 +3691,6 @@ function FLIGHTGROUP:GetParkingTime() return -1 end ---- Get number of elements alive. --- @param #FLIGHTGROUP self --- @param #string status (Optional) Only count number, which are in a special status. --- @return #number Number of elements. -function FLIGHTGROUP:GetNelements(status) - - local n=0 - for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element - if element.status~=FLIGHTGROUP.ElementStatus.DEAD then - if element.unit and element.unit:IsAlive() then - if status==nil or element.status==status then - n=n+1 - end - end - end - end - - - return n -end - --- Search unoccupied parking spots at the airbase for all flight elements. -- @param #FLIGHTGROUP self -- @param Wrapper.Airbase#AIRBASE airbase The airbase where we search for parking spots. @@ -5014,7 +3898,7 @@ function FLIGHTGROUP:_GetAttribute() -- Default local attribute=FLIGHTGROUP.Attribute.OTHER - local group=self.group + local group=self.group --Wrapper.Group#GROUP if group then @@ -5090,67 +3974,7 @@ end -- OPTION FUNCTIONS ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Set the default ROE for the group. This is the ROE state gets when the group is spawned or to which it defaults back after a mission. --- @param #FLIGHTGROUP self --- @param #number roe ROE of group. Default is `ENUMS.ROE.ReturnFire`. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetDefaultROE(roe) - self.roeDefault=roe or ENUMS.ROE.ReturnFire - return self -end ---- Set current ROE for the group. --- @param #FLIGHTGROUP self --- @param #string roe ROE of group. Default is the value defined by :SetDefaultROE(). --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetOptionROE(roe) - - roe=roe or self.roeDefault - - if self:IsAlive() then - - self.group:OptionROE(roe) - - self.roe=roe - - self:T2(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.roe)) - else - -- TODO WARNING - end - - return self -end - ---- Set the default ROT for the group. This is the ROT state gets when the group is spawned or to which it defaults back after a mission. --- @param #FLIGHTGROUP self --- @param #number roe ROE of group. Default is ENUMS.ROT.PassiveDefense. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetDefaultROT(roe) - self.rotDefault=roe or ENUMS.ROT.PassiveDefense - return self -end - ---- Set ROT for the group. --- @param #FLIGHTGROUP self --- @param #string rot ROT of group. Default is the value defined by :SetDefaultROT(). --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetOptionROT(rot) - - rot=rot or self.rotDefault - - if self:IsAlive() then - - self.group:OptionROT(rot) - - self.rot=rot - - self:T2(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.rot)) - else - -- TODO WARNING - end - - return self -end --- Set default TACAN parameters. AA TACANs are always on "Y" band. -- @param #FLIGHTGROUP self diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index dc41b9744..bee1cc9d8 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -68,12 +68,12 @@ function NAVYGROUP:New(GroupName) -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("*", "FullStop", "Holding") -- Hold position. - self:AddTransition("*", "TurnIntoWind", "*") -- Hold position. - self:AddTransition("*", "Cruise", "Cruising") -- Hold position. + self:AddTransition("*", "FullStop", "Holding") -- Hold position. + self:AddTransition("*", "TurnIntoWind", "*") -- Hold position. + self:AddTransition("*", "Cruise", "Cruising") -- Hold position. - self:AddTransition("*", "Dive", "Diving") -- Hold position. - self:AddTransition("Diving", "Surface", "Cruising") -- Hold position. + self:AddTransition("*", "Dive", "Diving") -- Hold position. + self:AddTransition("Diving", "Surface", "Cruising") -- Hold position. ------------------------ --- Pseudo Functions --- @@ -113,9 +113,22 @@ function NAVYGROUP:New(GroupName) BASE:TraceLevel(1) end + -- Handle events: + self:HandleEvent(EVENTS.Birth, self.OnEventBirth) + self:HandleEvent(EVENTS.Dead, self.OnEventDead) + self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) + + -- Initialize the group. self:_InitGroup() - self:Start() + -- Defaults + self:SetDefaultROE() + self:SetDetection() + + -- Start the status monitoring. + self:__CheckZone(-1) + self:__Status(-2) + self:__QueueUpdate(-3) return self end @@ -144,30 +157,24 @@ end -- Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Start NAVYGROUP FSM. Handle events. --- @param #NAVYGROUP self -function NAVYGROUP:onafterStart(From, Event, To) - - -- Info. - self:I(self.lid..string.format("Starting NAVYGROUP v%s for %s", NAVYGROUP.version, self.groupname)) - - -- Update route. - --self:UpdateRoute() - - -- Init status updates. - self:__Status(-1) -end - --- Update status. -- @param #NAVYGROUP self function NAVYGROUP:onafterStatus(From, Event, To) + -- FSM state. local fsmstate=self:GetState() + + -- Current heading and position of the carrier. + local hdg=self:GetHeading() + local pos=self:GetCoordinate() local speed=self.group:GetVelocityKNOTS() + -- Check water is ahead. + local collision=self:_CheckCollisionCoord(pos:Translate(self.collisiondist or 5000, hdg)) + -- Info text. - local text=string.format("State %s: Speed=%.1f knots", fsmstate, speed) + local text=string.format("State %s: Speed=%.1f knots Heading=%03d collision=%s", fsmstate, speed, hdg, tostring(collision)) self:I(self.lid..text) self:__Status(-30) @@ -177,6 +184,40 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- On after "ElementSpawned" event. +-- @param #NAVYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #NAVYGROUP.Element Element The group element. +function NAVYGROUP:onafterElementSpawned(From, Event, To, Element) + self:I(self.lid..string.format("Element spawned %s", Element.name)) + + -- Set element status. + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.SPAWNED) + +end + +--- On after "Spawned" event. +-- @param #NAVYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function NAVYGROUP:onafterSpawned(From, Event, To) + self:I(self.lid..string.format("Group spawned!")) + + if self.ai then + + -- Set default ROE and ROT options. + self:SetOptionROE() + + end + + -- Update route. + self:__UpdateRoute(-1) + +end + --- On after "UpdateRoute" event. -- @param #NAVYGROUP self -- @param #string From From state. @@ -301,41 +342,49 @@ function NAVYGROUP:onafterPassingWaypoint(From, Event, To, n, N) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Routing +-- Events DCS ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Route group along waypoints. Enroute tasks are also applied. +--- Event function handling the birth of a unit. -- @param #NAVYGROUP self --- @param #table waypoints Table of waypoints. --- @return #NAVYGROUP self -function NAVYGROUP:Route(waypoints) +-- @param Core.Event#EVENTDATA EventData Event data. +function NAVYGROUP:OnEventBirth(EventData) - if self:IsAlive() then - - -- DCS task combo. - local Tasks={} + -- 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 - -- Route (Mission) task. - local TaskRoute=self.group:TaskRoute(waypoints) - table.insert(Tasks, TaskRoute) + if self.respawning then + + local function reset() + self.respawning=nil + end + + -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. + -- TODO: Can I do this more rigorously? + self:ScheduleOnce(1, reset) - -- TaskCombo of enroute and mission tasks. - local TaskCombo=self.group:TaskCombo(Tasks) - - -- Set tasks. - if #Tasks>1 then - self:SetTask(TaskCombo) else - self:SetTask(TaskRoute) - end + + -- Get element. + local element=self:GetElementByName(unitname) + + -- Set element to spawned state. + self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", element.name)) + self:ElementSpawned(element) + + end - else - self:E(self.lid.."ERROR: Group is not alive!") end - - return self + end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Routing +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. -- @param #NAVYGROUP self -- @return #NAVYGROUP self @@ -353,6 +402,9 @@ function NAVYGROUP:_InitGroup() -- Helo group. --self.isSubmarine=self.group:IsSubmarine() + -- Ships are always AI. + self.ai=true + -- Is (template) group late activated. self.isLateActivated=self.template.lateActivation @@ -370,9 +422,9 @@ function NAVYGROUP:_InitGroup() self.position=self:GetCoordinate() -- Radio parameters from template. - --self.radioOn=self.template.communication - self.radioFreq=self.template.units[1].frequency - self.radioModu=self.template.units[1].modulation + self.radioOn=true -- Radio is always on for ships. + self.radioFreq=tonumber(self.template.units[1].frequency)/1000000 + self.radioModu=tonumber(self.template.units[1].modulation)/1000000 -- If not set by the use explicitly yet, we take the template values as defaults. if not self.radioFreqDefault then @@ -389,17 +441,25 @@ function NAVYGROUP:_InitGroup() end end - -- Get first unit. This is used to extract other parameters. - local unit=self.group:GetUnit(1) - local units=self.group:GetUnits() + for _,_unit in pairs(units) do - local element={} --#NAVYGROUP.Element local unit=_unit --Wrapper.Unit#UNIT + + local element={} --#NAVYGROUP.Element element.name=unit:GetName() element.typename=unit:GetTypeName() + element.status=OPSGROUP.ElementStatus.INUTERO table.insert(self.elements, element) + + if unit:IsAlive() then + self:ElementSpawned(element) + end + end + + -- Get first unit. This is used to extract other parameters. + local unit=self.group:GetUnit(1) if unit then @@ -418,7 +478,7 @@ function NAVYGROUP:_InitGroup() --text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) text=text..string.format("FSM state = %s\n", self:GetState()) text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) - --text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) + text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) self:I(self.lid..text) -- Init done. @@ -429,103 +489,129 @@ function NAVYGROUP:_InitGroup() return self end ---- Initialize Mission Editor waypoints. --- @param #NAVYGROUP self --- @param #table waypoints Table of waypoints. Default is from group template. --- @return #NAVYGROUP self -function NAVYGROUP:InitWaypoints(waypoints) - - -- Template waypoints. - self.waypoints0=self.group:GetTemplateRoutePoints() - - -- Waypoints of group as defined in the ME. - self.waypoints=waypoints or UTILS.DeepCopy(self.waypoints0) - - -- Debug info. - self:I(self.lid..string.format("Initializing %d waypoints", #self.waypoints)) - - -- Update route. - if #self.waypoints>0 then - - -- Check if only 1 wp? - if #self.waypoints==1 then - self.passedfinalwp=true - end - - -- Update route (when airborne). - self:__UpdateRoute(-1) - end - - return self -end - ---- Initialize Mission Editor waypoints. --- @param #NAVYGROUP self -function NAVYGROUP:_UpdateWaypointTasks() - - local waypoints=self.waypoints - local nwaypoints=#waypoints - - for i,wp in pairs(waypoints) do - - if i>self.currentwp or nwaypoints==1 then - - -- Debug info. - self:T2(self.lid..string.format("Updating waypoint task for waypoint %d/%d. Last waypoint passed %d.", i, nwaypoints, self.currentwp)) - - -- Tasks of this waypoint - local taskswp={} - - -- At each waypoint report passing. - local TaskPassingWaypoint=self.group:TaskFunction("NAVYGROUP._PassingWaypoint", self, i) - table.insert(taskswp, TaskPassingWaypoint) - - -- Waypoint task combo. - wp.task=self.group:TaskCombo(taskswp) - - end - end - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Set rules of engagement. +--- Check for possible collisions between two coordinates. -- @param #NAVYGROUP self --- @param #string roe "Hold", "Free", "Return". -function NAVYGROUP:_SetROE(roe) +-- @param Core.Point#COORDINATE coordto Coordinate to which the collision is check. +-- @param Core.Point#COORDINATE coordfrom Coordinate from which the collision is check. +-- @return #boolean If true, surface type ahead is not deep water. +-- @return #number Max free distance in meters. +function NAVYGROUP:_CheckCollisionCoord(coordto, coordfrom) - if roe=="Hold" then - self.group:OptionROEHoldFire() - elseif roe=="Free" then - self.group:OptionROEOpenFire() - elseif roe=="Return" then - self.group:OptionROEReturnFire() + -- Increment in meters. + local dx=100 + + -- From coordinate. Default 500 in front of the carrier. + local d=0 + if coordfrom then + d=0 + else + d=250 + coordfrom=self:GetCoordinate():Translate(d, self:GetHeading()) end - MESSAGE:New(string.format("ROE set to %s", roe), 5, self.ClassName):ToCoalition(self:GetCoalition()) -end + -- Distance between the two coordinates. + local dmax=coordfrom:Get2DDistance(coordto) ---- Set alarm state. (Not useful/working for ships.) --- @param #NAVYGROUP self --- @param #string state "Green", "Red", "Auto". -function NAVYGROUP:_SetALS(state) + -- Direction. + local direction=coordfrom:HeadingTo(coordto) - if state=="Green" then - self.group:OptionAlarmStateGreen() - elseif state=="Red" then - self.group:OptionAlarmStateRed() - elseif state=="Auto" then - self.group:OptionAlarmStateAuto() + -- Scan path between the two coordinates. + local clear=true + while d<=dmax do + + -- Check point. + local cp=coordfrom:Translate(d, direction) + + -- Check if surface type is water. + if not cp:IsSurfaceTypeWater() then + + -- Debug mark points. + if self.Debug or true then + local st=cp:GetSurfaceType() + cp:MarkToAll(string.format("Collision check surface type %d", st)) + end + + -- Collision WARNING! + clear=false + break + end + + -- Increase distance. + d=d+dx end - - MESSAGE:New(string.format("Alarm state set to %s", state), 5, self.ClassName):ToCoalition(self:GetCoalition()) + + local text="" + if clear then + text=string.format("Path into direction %03d° is clear for the next %.1f NM.", direction, UTILS.MetersToNM(d)) + else + text=string.format("Detected obstacle at distance %.1f NM into direction %03d°.", UTILS.MetersToNM(d), direction) + end + self:T(self.lid..text) + + return not clear, d end +--- Check if group is turning. +-- @param #NAVYGROUP self +function NAVYGROUP:_CheckTurning() + -- Current orientation of carrier. + local vNew=self.group:GetOrientationX() + -- Last orientation from 30 seconds ago. + local vLast=self.Corientlast + + -- We only need the X-Z plane. + vNew.y=0 ; vLast.y=0 + + -- Angle between current heading and last time we checked ~30 seconds ago. + local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast))) + + -- Last orientation becomes new orientation + self.Corientlast=vNew + + -- Carrier is turning when its heading changed by at least one degree since last check. + local turning=math.abs(deltaLast)>=1 + + -- Check if turning stopped. (Carrier was turning but is not any more.) + if self.turning and not turning then + + -- Get final bearing. + local FB=self:GetFinalBearing(true) + + -- Marshal radio call: "99, new final bearing XYZ degrees." + self:_MarshalCallNewFinalBearing(FB) + + end + + -- Check if turning started. (Carrier was not turning and is now.) + if turning and not self.turning then + + -- Get heading. + local hdg + if self.turnintowind then + -- We are now steaming into the wind. + hdg=self:GetHeadingIntoWind(false) + else + -- We turn towards the next waypoint. + hdg=self:GetCoordinate():HeadingTo(self:_GetNextWaypoint()) + end + + -- Magnetic! + hdg=hdg-self.magvar + if hdg<0 then + hdg=360+hdg + end + + end + + -- Update turning. + self.turning=turning +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 59e9d3ee5..e0df5e110 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -14,12 +14,14 @@ -- @field #boolean Debug Debug mode. Messages to all about status. -- @field #number verbose Verbosity level. 0=silent. -- @field #string lid Class id string for output to DCS log file. --- @field #string groupname Name of flight group. --- @field Wrapper.Group#GROUP group Flight group object. +-- @field #string groupname Name of the group. +-- @field Wrapper.Group#GROUP group Group object. +-- @field #boolean isLateActivated Is the group late activated. -- @field #table elements Table of elements, i.e. units of the group. +-- @field #boolean ai If true, group is purely AI. -- @field #table waypoints Table of waypoints. -- @field #table waypoints0 Table of initial waypoints. --- @field #table currentwp Current waypoint. +-- @field #number currentwp Current waypoint. -- @field #table taskqueue Queue of tasks. -- @field #number taskcounter Running number of task ids. -- @field #number taskcurrent ID of current task. If 0, there is no current task assigned. @@ -41,6 +43,35 @@ -- @field Core.Point#COORDINATE position Current position of the group. -- @field #number traveldist Distance traveled in meters. This is a lower bound! -- @field #number traveltime Time. +-- +-- @field #number tacanChannelDefault The default TACAN channel. +-- @field #string tacanMorseDefault The default TACAN morse code. +-- @field #number tacanChannel The currenly used TACAN channel. +-- @field #string tacanMorse The currently used TACAN morse code. +-- @field #boolean tacanOn If true, TACAN is currently active. +-- @field Wrapper.Unit#UNIT tacanBeacon The unit acting as TACAN beacon. +-- +-- @field #number radioFreqDefault Default radio frequency in MHz. +-- @field #number radioFreq Currently used radio frequency in MHz. +-- @field #number radioModuDefault Default Radio modulation `radio.modulation.AM` or `radio.modulation.FM`. +-- @field #number radioModu Currently used radio modulation `radio.modulation.AM` or `radio.modulation.FM`. +-- @field #boolean radioOn If true, radio is currently turned on. +-- @field Core.RadioQueue#RADIOQUEUE radioQueue Radio queue. +-- +-- @field #number CallsignName Call sign name. +-- @field #number CallsignNumber Call sign number. +-- +-- @field #boolean eplrsDefault Default EPLRS data link setting. +-- @field #boolean eplrs If true, EPLRS data link is on. +-- +-- @field #string roeDefault Default ROE setting. +-- @field #string rotDefault Default ROT setting. +-- @field #string roe Current ROE setting. +-- @field #string rot Current ROT setting. +-- +-- @field #number formationDefault Default formation setting. +-- @field #number formation Current formation setting. +-- -- @extends Core.Fsm#FSM --- *Something must be left to chance; nothing is sure in a sea fight above all.* --- Horatio Nelson @@ -65,6 +96,7 @@ OPSGROUP = { groupname = nil, group = nil, template = nil, + isLateActivated = nil, waypoints = nil, waypoints0 = nil, currentwp = 1, @@ -83,6 +115,33 @@ OPSGROUP = { groupinitialized = nil, } +--- Status of group element. +-- @type OPSGROUP.ElementStatus +-- @field #string INUTERO Element was not spawned yet or its status is unknown so far. +-- @field #string SPAWNED Element was spawned into the world. +-- @field #string PARKING Element is parking after spawned on ramp. +-- @field #string ENGINEON Element started its engines. +-- @field #string TAXIING Element is taxiing after engine startup. +-- @field #string TAKEOFF Element took of after takeoff event. +-- @field #string AIRBORNE Element is airborne. Either after takeoff or after air start. +-- @field #string LANDING Element is landing. +-- @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. +OPSGROUP.ElementStatus={ + INUTERO="inutero", + SPAWNED="spawned", + PARKING="parking", + ENGINEON="engineon", + TAXIING="taxiing", + TAKEOFF="takeoff", + AIRBORNE="airborne", + LANDING="landing", + LANDED="landed", + ARRIVED="arrived", + DEAD="dead", +} + --- Flight group task status. -- @type OPSGROUP.TaskStatus -- @field #string SCHEDULED Task is scheduled. @@ -105,7 +164,7 @@ OPSGROUP.TaskType={ WAYPOINT="waypoint", } ---- Flight group task structure. +--- Task structure. -- @type OPSGROUP.Task -- @field #string type Type of task: either SCHEDULED or WAYPOINT. -- @field #number id Task ID. Running number to get the task. @@ -171,11 +230,12 @@ function OPSGROUP:New(Group) self.taskcounter=0 -- Start state. - self:SetStartState("Stopped") + self:SetStartState("InUtero") -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("Stopped", "Start", "InUtero") -- Start FSM. + self:AddTransition("InUtero", "Spawned", "Spawned") -- The whole group was spawned. + self:AddTransition("*", "Dead", "Dead") -- The whole group is dead. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Status", "*") -- Status update. @@ -215,6 +275,11 @@ function OPSGROUP:New(Group) self:AddTransition("*", "UnpauseMission", "*") -- Unpause the the paused mission. self:AddTransition("*", "MissionDone", "*") -- Mission is over. + self:AddTransition("*", "ElementSpawned", "*") -- An element was spawned. + self:AddTransition("*", "ElementDead", "*") -- An element is dead. + + + ------------------------ --- Pseudo Functions --- ------------------------ @@ -269,13 +334,6 @@ function OPSGROUP:GetCoalition() return self.group:GetCoalition() end ---- Get coordinate. --- @param #OPSGROUP self --- @return Core.Point#COORDINATE Carrier coordinate. -function OPSGROUP:GetCoordinate() - return self.group:GetCoordinate() -end - --- Returns the absolute (average) life points of the group. -- @param #OPSGROUP self -- @return #number Life points. If group contains more than one element, the average is given. @@ -349,6 +407,18 @@ function OPSGROUP:GetCoordinate() return nil end +--- Get current heading of the group. +-- @param #OPSGROUP self +-- @return #number Current heading of the group in degrees. +function OPSGROUP:GetHeading() + if self:IsAlive()~=nil then + return self.group:GetHeading() + else + self:E(self.lid.."WARNING: Group is not alive. Cannot get heading!") + end + return nil +end + --- Get waypoint. -- @param #OPSGROUP self -- @param #number indx Waypoint index. @@ -444,6 +514,42 @@ function OPSGROUP:IsAlive() return nil end +--- Check if this group is currently "late activated" and needs to be "activated" to appear in the mission. +-- @param #OPSGROUP self +-- @return #boolean Is this the group late activated? +function OPSGROUP:IsLateActivated() + return self.isLateActivated +end + +--- Check if flight is in state in utero. +-- @param #OPSGROUP self +-- @return #boolean If true, flight is not spawned yet. +function OPSGROUP:IsInUtero() + return self:Is("InUtero") +end + +--- Check if flight is in state spawned. +-- @param #OPSGROUP self +-- @return #boolean If true, flight is spawned. +function OPSGROUP:IsSpawned() + return self:Is("Spawned") +end + +--- Check if flight is dead. +-- @param #OPSGROUP self +-- @return #boolean If true, all units/elements of the flight are dead. +function OPSGROUP:IsDead() + return self:Is("Dead") +end + +--- Check if flight FSM is stopped. +-- @param #OPSGROUP self +-- @return #boolean If true, FSM state is stopped. +function OPSGROUP:IsStopped() + return self:Is("Stopped") +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -889,6 +995,232 @@ function OPSGROUP:GetTaskByID(id, status) return nil end +--- On after TaskExecute event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP.Task Task The task. +function OPSGROUP:onafterTaskExecute(From, Event, To, Task) + + -- Debug message. + local text=string.format("Task %s ID=%d execute.", tostring(Task.description), Task.id) + MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) + self:I(self.lid..text) + + -- Cancel current task if there is any. + if self.taskcurrent>0 then + self:TaskCancel() + end + + -- Set current task. + self.taskcurrent=Task.id + + -- Set time stamp. + Task.timestamp=timer.getAbsTime() + + -- Task status executing. + Task.status=OPSGROUP.TaskStatus.EXECUTING + + if Task.dcstask.id=="Formation" then + + -- Set of group(s) to follow Mother. + local followSet=SET_GROUP:New():AddGroup(self.group) + + local param=Task.dcstask.params + + local followUnit=UNIT:FindByName(param.unitname) + + -- Define AI Formation object. + Task.formation=AI_FORMATION:New(followUnit, followSet, "Formation", "Follow X at given parameters.") + + -- Formation parameters. + Task.formation:FormationCenterWing(-param.offsetX, 50, math.abs(param.altitude), 50, param.offsetZ, 50) + + -- Set follow time interval. + Task.formation:SetFollowTimeInterval(param.dtFollow) + + -- Formation mode. + Task.formation:SetFlightModeFormation(self.group) + + -- Start formation FSM. + Task.formation:Start() + + else + + -- If task is scheduled (not waypoint) set task. + if Task.type==OPSGROUP.TaskType.SCHEDULED then + + local DCStasks={} + if Task.dcstask.id=='ComboTask' then + -- Loop over all combo tasks. + for TaskID, Task in ipairs(Task.dcstask.params.tasks) do + table.insert(DCStasks, Task) + end + else + table.insert(DCStasks, Task.dcstask) + end + + -- Combo task. + local TaskCombo=self.group:TaskCombo(DCStasks) + + -- Stop condition! + local TaskCondition=self.group:TaskCondition(nil, Task.stopflag:GetName(), 1, nil, Task.duration) + + -- Controlled task. + local TaskControlled=self.group:TaskControlled(TaskCombo, TaskCondition) + + -- Task done. + local TaskDone=self.group:TaskFunction("OPSGROUP._TaskDone", self, Task) + + -- Final task. + local TaskFinal=self.group:TaskCombo({TaskControlled, TaskDone}) + + -- Set task for group. + self:SetTask(TaskFinal, 1) + + end + + end + + -- Get mission of this task (if any). + local Mission=self:GetMissionByTaskID(self.taskcurrent) + if Mission then + -- Set AUFTRAG status. + self:MissionExecute(Mission) + end + +end + +--- On after "TaskCancel" event. Cancels the current task or simply sets the status to DONE if the task is not the current one. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.Task Task The task to cancel. Default is the current task (if any). +function OPSGROUP:onafterTaskCancel(From, Event, To, Task) + + -- Get current task. + local currenttask=self:GetTaskCurrent() + + -- If no task, we take the current task. But this could also be *nil*! + Task=Task or currenttask + + if Task then + + -- Check if the task is the current task? + if currenttask and Task.id==currenttask.id then + + -- Current stop flag value. I noticed cases, where setting the flag to 1 would not cancel the task, e.g. when firing HARMS on a dead ship. + local stopflag=Task.stopflag:Get() + + -- Debug info. + local text=string.format("Current task %s ID=%d cancelled (flag %s=%d)", Task.description, Task.id, Task.stopflag:GetName(), stopflag) + MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) + self:I(self.lid..text) + + -- Set stop flag. When the flag is true, the _TaskDone function is executed and calls :TaskDone() + Task.stopflag:Set(1) + + if Task.dcstask.id=="Formation" then + Task.formation:Stop() + self:TaskDone(Task) + elseif stopflag==1 then + -- Manual call TaskDone if setting flag to one was not successful. + self:TaskDone(Task) + end + + else + + -- Debug info. + self:I(self.lid..string.format("TaskCancel: Setting task %s ID=%d to DONE", Task.description, Task.id)) + + -- Call task done function. + self:TaskDone(Task) + + + --[[ + local mission=self:GetMissionByTaskID(Task.id) + + -- Is this a waypoint task? + if Task.type==OPSGROUP.TaskType.WAYPOINT and Task.waypoint then + + -- Check that this is a mission waypoint and no other tasks are defined here. + if mission and #self:GetTasksWaypoint(Task.waypoint)==0 then + self:RemoveWaypoint(Task.waypoint) + end + end + ]] + end + + else + + local text=string.format("WARNING: No (current) task to cancel!") + MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) + self:E(self.lid..text) + + end + +end + +--- On before "TaskDone" event. Deny transition if task status is PAUSED. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.Task Task +function OPSGROUP:onbeforeTaskDone(From, Event, To, Task) + + local allowed=true + + if Task.status==OPSGROUP.TaskStatus.PAUSED then + allowed=false + end + + return allowed +end + +--- On after "TaskDone" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.Task Task +function OPSGROUP:onafterTaskDone(From, Event, To, Task) + + -- Debug message. + local text=string.format("Task done: %s ID=%d", Task.description, Task.id) + MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) + self:I(self.lid..text) + + -- No current task. + if Task.id==self.taskcurrent then + self.taskcurrent=0 + end + + -- Task status done. + Task.status=OPSGROUP.TaskStatus.DONE + + -- Check if this task was the task of the current mission ==> Mission Done! + local Mission=self:GetMissionByTaskID(Task.id) + + if Mission and Mission:IsNotOver() then + + local status=Mission:GetFlightStatus(self) + + if status~=AUFTRAG.FlightStatus.PAUSED then + self:I(self.lid.."FF Task Done ==> Mission Done!") + self:MissionDone(Mission) + else + --Mission paused. Do nothing! + end + else + self:I(self.lid.."FF Task Done but NO mission found ==> _CheckFlightDone in 1 sec") + self:_CheckFlightDone(1) + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Mission Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1061,6 +1393,316 @@ function OPSGROUP:GetMissionCurrent() return self:GetMissionByID(self.currentmission) end +--- On before "MissionStart" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission The mission table. +function OPSGROUP:onbeforeMissionStart(From, Event, To, Mission) + + -- Debug info. + self:I(self.lid..string.format("Starting mission %s, FSM=%s, LateActivated=%s, UnControlled=%s", tostring(Mission.name), self:GetState(), tostring(self:IsLateActivated()), tostring(self:IsUncontrolled()))) + + -- Delay for route to mission. Group needs to be activated and controlled. + local delay=1 + + -- Check if group is spawned. + if self:IsInUtero() then + + -- Activate group if it is late activated. + if self:IsLateActivated() then + self:Activate(delay) + delay=delay+1 + end + + end + + -- Startup group if it is uncontrolled. + if self:IsParking() and self:IsUncontrolled() then + self:StartUncontrolled(delay) + else + --env.info("FF hallo!") + --self:StartUncontrolled(5) + end + + return true +end + +--- On after "MissionStart" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission The mission table. +function OPSGROUP:onafterMissionStart(From, Event, To, Mission) + + -- Debug output. + local text=string.format("Starting %s Mission %s, target %s", Mission.type, tostring(Mission.name), Mission:GetTargetName()) + self:T(self.lid..text) + MESSAGE:New(text, 30, self.groupname):ToAllIf(true) + + -- Set current mission. + self.currentmission=Mission.auftragsnummer + + -- Set flight mission status to STARTED. + Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.STARTED) + + -- Set mission status to STARTED. + Mission:Started() + + -- Route flight to mission zone. + self:RouteToMission(Mission, 3) + +end + +--- On after "MissionExecute" event. Mission execution began. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission The mission table. +function OPSGROUP:onafterMissionExecute(From, Event, To, Mission) + + local text=string.format("Executing %s Mission %s, target %s", Mission.type, tostring(Mission.name), Mission:GetTargetName()) + self:T(self.lid..text) + MESSAGE:New(text, 30, self.groupname):ToAllIf(true) + + -- Set flight mission status to EXECUTING. + Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.EXECUTING) + + -- Set mission status to EXECUTING. + Mission:Executing() + + -- Formation + if Mission.optionFormation then + self:SwitchFormation(Mission.optionFormation) + end + +end + +--- On after "PauseMission" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterPauseMission(From, Event, To) + + local Mission=self:GetMissionCurrent() + + if Mission then + + -- Set flight mission status to PAUSED. + Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.PAUSED) + + -- Get mission waypoint task. + local Task=Mission:GetFlightWaypointTask(self) + + -- Debug message. + self:I(self.lid..string.format("FF pausing current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF"))) + + -- Cancelling the mission is actually cancelling the current task. + self:TaskCancel(Task) + + -- Set mission to pause so we can unpause it later. + self.missionpaused=Mission + + end + +end + +--- On after "UnpauseMission" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterUnpauseMission(From, Event, To) + + self:I(self.lid..string.format("Unpausing mission")) + + if self.missionpaused then + + local mission=self:GetMissionByID(self.missionpaused.auftragsnummer) + + self:MissionStart(mission) + + self.missionpaused=nil + else + self:E(self.lid.."ERROR: No mission to unpause!") + end + +end + + +--- On after "MissionCancel" event. Cancels the mission. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission The mission to be cancelled. +function OPSGROUP:onafterMissionCancel(From, Event, To, Mission) + + if self.currentmission and Mission.auftragsnummer==self.currentmission then + + -- Get mission waypoint task. + local Task=Mission:GetFlightWaypointTask(self) + + -- Debug info. + self:I(self.lid..string.format("FF Cancel current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF"))) + + -- Cancelling the mission is actually cancelling the current task. + -- Note that two things can happen. + -- 1.) Flight is still on the way to the waypoint (status should be STARTED). In this case there would not be a current task! + -- 2.) Flight already passed the mission waypoint (status should be EXECUTING). + + self:TaskCancel(Task) + + else + + -- Not the current mission. + -- TODO: remove mission from queue? + + -- Set mission flight status. + Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.CANCELLED) + + -- Send flight RTB or WAIT if nothing left to do. + self:_CheckFlightDone(1) + + end + +end + +--- On after "MissionDone" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission +function OPSGROUP:onafterMissionDone(From, Event, To, Mission) + + -- Debug info. + local text=string.format("Mission %s DONE!", Mission.name) + self:I(self.lid..text) + MESSAGE:New(text, 30, self.groupname):ToAllIf(true) + + -- Set Flight status. + Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.DONE) + + -- Set current mission to nil. + if self.currentmission and Mission.auftragsnummer==self.currentmission then + self.currentmission=nil + end + + -- Remove mission waypoint. + local wpidx=Mission:GetFlightWaypointIndex(self) + if wpidx then + self:RemoveWaypoint(wpidx) + end + + -- Decrease patrol data. + if Mission.patroldata then + Mission.patroldata.noccupied=Mission.patroldata.noccupied-1 + AIRWING.UpdatePatrolPointMarker(Mission.patroldata) + end + + -- TODO: reset mission specific parameters like radio, ROE etc. + + -- Check if flight is done. + self:_CheckFlightDone(1) + +end + +--- Route group to mission. +-- @param #OPSGROUP self +-- @param Ops.Auftrag#AUFTRAG mission The mission table. +-- @param #number delay Delay in seconds. +function OPSGROUP:RouteToMission(mission, delay) + + if delay and delay>0 then + -- Delayed call. + self:ScheduleOnce(delay, OPSGROUP.RouteToMission, self, mission) + else + + -- Next waypoint. + local nextwaypoint=self.currentwp+1 + + -- Create waypoint coordinate half way between us and the target. + local waypointcoord=self.group:GetCoordinate():GetIntermediateCoordinate(mission:GetTargetCoordinate(), mission.missionFraction) + local alt=waypointcoord.y + + -- Add some randomization. + waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), 1000):GetRandomCoordinate():SetAltitude(alt, false) + + -- Set altitude of mission waypoint. + if mission.missionAltitude then + waypointcoord:SetAltitude(mission.missionAltitude, true) + end + env.info(string.format("FF mission alt=%d", waypointcoord.y)) + + -- Add enroute tasks. + for _,task in pairs(mission.enrouteTasks) do + self:AddTaskEnroute(task) + end + + -- Add waypoint. + self:AddWaypointAir(waypointcoord, nextwaypoint, UTILS.KmphToKnots(self.speedCruise), false) + + -- Special for Troop transport. + if mission.type==AUFTRAG.Type.TROOPTRANSPORT then + + -- Refresh DCS task with the known controllable. + mission.DCStask=mission:GetDCSMissionTask(self.group) + + -- Add task to embark for the troops. + for _,_group in pairs(mission.transportGroupSet.Set) do + local group=_group --Wrapper.Group#GROUP + + if group and group:IsAlive() then + local DCSTask=group:TaskEmbarkToTransport(mission.transportPickup, 500) + group:SetTask(DCSTask, 5) + end + + end + + end + + -- Add waypoint task. UpdateRoute is called inside. + local waypointtask=self:AddTaskWaypoint(mission.DCStask, nextwaypoint, mission.name, mission.prio, mission.duration) + + -- Set waypoint task. + mission:SetFlightWaypointTask(self, waypointtask) + + -- Set waypoint index. + mission:SetFlightWaypointIndex(self, nextwaypoint) + + --- + -- Mission Specific Settings + --- + + -- ROE + if mission.optionROE then + self:SetOptionROE(mission.optionROE) + end + -- ROT + if mission.optionROT then + self:SetOptionROT(mission.optionROT) + end + -- Radio + if mission.radioFreq then + self:SwitchRadioOn(mission.radioFreq, mission.radioModu) + end + -- TACAN + if mission.tacanChannel then + self:SwitchTACANOn(mission.tacanChannel, mission.tacanMorse) + end + -- Formation + if mission.optionFormation then + self:SwitchFormation(mission.optionFormation) + end + + end +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1137,6 +1779,128 @@ function OPSGROUP:onafterCheckZone(From, Event, To) end end +--- Check if flight is in zones. +-- @param #OPSGROUP self +function OPSGROUP:_CheckInZones() + + if self.checkzones then + + local Ncheck=self.checkzones:Count() + local Ninside=self.inzones:Count() + + -- Debug info. + self:T(self.lid..string.format("Check if flight is in %d zones. Currently it is in %d zones.", self.checkzones:Count(), self.inzones:Count())) + + -- Firstly, check if group is still inside zone it was already in. If not, remove zones and trigger LeaveZone() event. + local leftzones={} + for inzonename, inzone in pairs(self.inzones:GetSet()) do + + -- Check if group is still inside the zone. + local isstillinzone=self.group:IsPartlyOrCompletelyInZone(inzone) + + -- If not, trigger, LeaveZone event. + if not isstillinzone then + table.insert(leftzones, inzone) + end + end + + -- Trigger leave zone event. + for _,leftzone in pairs(leftzones) do + self:LeaveZone(leftzone) + end + + + -- Now, run of all check zones and see if the group entered a zone. + local enterzones={} + for checkzonename,_checkzone in pairs(self.checkzones:GetSet()) do + local checkzone=_checkzone --Core.Zone#ZONE + + -- Is flight currtently in this check zone? + local isincheckzone=self.group:IsPartlyOrCompletelyInZone(checkzone) + + if isincheckzone and not self.inzones:_Find(checkzonename) then + table.insert(enterzones, checkzone) + end + end + + -- Trigger enter zone event. + for _,enterzone in pairs(enterzones) do + self:EnterZone(enterzone) + end + + + end + +end + +--- Check detected units. +-- @param #OPSGROUP self +function OPSGROUP:_CheckDetectedUnits() + + if self.group and not self:IsDead() then + + -- Get detected DCS units. + local detectedtargets=self.group:GetDetectedTargets() + + local detected={} + for DetectionObjectID, Detection in pairs(detectedtargets or {}) 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 + + -- Name of detected unit + local unitname=unit:GetName() + + -- Add unit to detected table of this run. + table.insert(detected, unit) + + -- Trigger detected unit event. + self:DetectedUnit(unit) + + if self.detectedunits:FindUnit(unitname) then + -- Unit is already in the detected unit set ==> Trigger "DetectedUnitKnown" event. + self:DetectedUnitKnown(unit) + else + -- Unit is was not detected ==> Trigger "DetectedUnitNew" event. + self:DetectedUnitNew(unit) + end + + end + end + end + + -- Loop over units in detected set. + local lost={} + for _,_unit in pairs(self.detectedunits:GetSet()) do + local unit=_unit --Wrapper.Unit#UNIT + + -- Loop over detected units + local gotit=false + for _,_du in pairs(detected) do + local du=_du --Wrapper.Unit#UNIT + if unit:GetName()==du:GetName() then + gotit=true + end + end + + if not gotit then + table.insert(lost, unit:GetName()) + self:DetectedUnitLost(unit) + end + + end + + -- Remove lost units from detected set. + self.detectedunits:RemoveUnitsByName(lost) + + end + + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoints & Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1215,7 +1979,7 @@ function OPSGROUP:_UpdateWaypointTasks() if i>self.currentwp or nwaypoints==1 then -- Debug info. - self:T2(self.lid..string.format("Updating waypoint task for waypoint %d/%d. Last waypoint passed %d.", i, nwaypoints, self.currentwp)) + self:I(self.lid..string.format("Updating waypoint task for waypoint %d/%d. Last waypoint passed %d", i, nwaypoints, self.currentwp)) -- Tasks of this waypoint local taskswp={} @@ -1246,7 +2010,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, i) -- Debug message. local text=string.format("Group passing waypoint %d of %d", i, final) - opsgroup:T3(opsgroup.lid..text) + opsgroup:I(opsgroup.lid..text) -- Set current waypoint. opsgroup.currentwp=i @@ -1288,6 +2052,386 @@ function OPSGROUP._TaskDone(group, flightgroup, task) end end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- OPTION FUNCTIONS +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set the default ROE for the group. This is the ROE state gets when the group is spawned or to which it defaults back after a mission. +-- @param #OPSGROUP self +-- @param #number roe ROE of group. Default is `ENUMS.ROE.ReturnFire`. +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultROE(roe) + self.roeDefault=roe or ENUMS.ROE.ReturnFire + return self +end + +--- Set current ROE for the group. +-- @param #OPSGROUP self +-- @param #string roe ROE of group. Default is the value defined by :SetDefaultROE(). +-- @return #OPSGROUP self +function OPSGROUP:SetOptionROE(roe) + + roe=roe or self.roeDefault + + if self:IsAlive() then + + self.group:OptionROE(roe) + + self.roe=roe + + self:T2(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.roe)) + else + -- TODO WARNING + end + + return self +end + +--- Set the default ROT for the group. This is the ROT state gets when the group is spawned or to which it defaults back after a mission. +-- @param #OPSGROUP self +-- @param #number roe ROE of group. Default is ENUMS.ROT.PassiveDefense. +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultROT(roe) + self.rotDefault=roe or ENUMS.ROT.PassiveDefense + return self +end + +--- Set ROT for the group. +-- @param #OPSGROUP self +-- @param #string rot ROT of group. Default is the value defined by :SetDefaultROT(). +-- @return #OPSGROUP self +function OPSGROUP:SetOptionROT(rot) + + rot=rot or self.rotDefault + + if self:IsAlive() then + + self.group:OptionROT(rot) + + self.rot=rot + + self:T2(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.rot)) + else + -- TODO WARNING + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Element and Group Status Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check if all elements of the flight group have the same status (or are dead). +-- @param #OPSGROUP self +-- @param #string unitname Name of unit. +function OPSGROUP:_AllSameStatus(status) + + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if element.status==OPSGROUP.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 + + return true +end + +--- Check if all elements of the flight group have the same status (or are dead). +-- @param #OPSGROUP self +-- @param #string status Status to check. +-- @return #boolean If true, all elements have a similar status. +function OPSGROUP:_AllSimilarStatus(status) + + -- Check if all are dead. + if status==OPSGROUP.ElementStatus.DEAD then + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if element.status~=OPSGROUP.ElementStatus.DEAD then + -- At least one is still alive. + return false + end + end + return true + end + + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + self:T2(self.lid..string.format("Status=%s, element %s status=%s", status, element.name, element.status)) + + -- Dead units dont count ==> We wont return false for those. + if element.status~=OPSGROUP.ElementStatus.DEAD then + + ---------- + -- ALIVE + ---------- + + if status==OPSGROUP.ElementStatus.SPAWNED then + + -- Element SPAWNED: Check that others are not still IN UTERO + if element.status~=status and + element.status==OPSGROUP.ElementStatus.INUTERO then + return false + end + + elseif status==OPSGROUP.ElementStatus.PARKING then + + -- Element PARKING: Check that the other are not still SPAWNED + if element.status~=status or + (element.status==OPSGROUP.ElementStatus.INUTERO or + element.status==OPSGROUP.ElementStatus.SPAWNED) then + return false + end + + elseif status==OPSGROUP.ElementStatus.ENGINEON then + + -- Element TAXIING: Check that the other are not still SPAWNED or PARKING + if element.status~=status and + (element.status==OPSGROUP.ElementStatus.INUTERO or + element.status==OPSGROUP.ElementStatus.SPAWNED or + element.status==OPSGROUP.ElementStatus.PARKING) then + return false + end + + elseif status==OPSGROUP.ElementStatus.TAXIING then + + -- Element TAXIING: Check that the other are not still SPAWNED or PARKING + if element.status~=status and + (element.status==OPSGROUP.ElementStatus.INUTERO or + element.status==OPSGROUP.ElementStatus.SPAWNED or + element.status==OPSGROUP.ElementStatus.PARKING or + element.status==OPSGROUP.ElementStatus.ENGINEON) then + return false + end + + elseif status==OPSGROUP.ElementStatus.TAKEOFF then + + -- Element TAKEOFF: Check that the other are not still SPAWNED, PARKING or TAXIING + if element.status~=status and + (element.status==OPSGROUP.ElementStatus.INUTERO or + element.status==OPSGROUP.ElementStatus.SPAWNED or + element.status==OPSGROUP.ElementStatus.PARKING or + element.status==OPSGROUP.ElementStatus.ENGINEON or + element.status==OPSGROUP.ElementStatus.TAXIING) then + return false + end + + elseif status==OPSGROUP.ElementStatus.AIRBORNE then + + -- Element AIRBORNE: Check that the other are not still SPAWNED, PARKING, TAXIING or TAKEOFF + if element.status~=status and + (element.status==OPSGROUP.ElementStatus.INUTERO or + element.status==OPSGROUP.ElementStatus.SPAWNED or + element.status==OPSGROUP.ElementStatus.PARKING or + element.status==OPSGROUP.ElementStatus.ENGINEON or + element.status==OPSGROUP.ElementStatus.TAXIING or + element.status==OPSGROUP.ElementStatus.TAKEOFF) then + return false + end + + elseif status==OPSGROUP.ElementStatus.LANDED then + + -- Element LANDED: check that the others are not still AIRBORNE or LANDING + if element.status~=status and + (element.status==OPSGROUP.ElementStatus.AIRBORNE or + element.status==OPSGROUP.ElementStatus.LANDING) then + return false + end + + elseif status==OPSGROUP.ElementStatus.ARRIVED then + + -- Element ARRIVED: check that the others are not still AIRBORNE, LANDING, or LANDED (taxiing). + if element.status~=status and + (element.status==OPSGROUP.ElementStatus.AIRBORNE or + element.status==OPSGROUP.ElementStatus.LANDING or + element.status==OPSGROUP.ElementStatus.LANDED) then + return false + end + + end + + else + -- Element is dead. We don't care unless all are dead. + end --DEAD + + end + + -- Debug info. + self:T2(self.lid..string.format("All %d elements have similar status %s ==> returning TRUE", #self.elements, status)) + + return true +end + +--- Check if all elements of the flight group have the same status or are dead. +-- @param #OPSGROUP self +-- @param #OPSGROUP.Element element Element. +-- @param #string newstatus New status of element +-- @param Wrapper.Airbase#AIRBASE airbase Airbase if applicable. +function OPSGROUP:_UpdateStatus(element, newstatus, airbase) + + -- Old status. + local oldstatus=element.status + + -- Update status of element. + element.status=newstatus + + -- Debug + self:T3(self.lid..string.format("UpdateStatus element=%s: %s --> %s", element.name, oldstatus, newstatus)) + for _,_element in pairs(self.elements) do + local Element=_element -- #OPSGROUP.Element + self:T3(self.lid..string.format("Element %s: %s", Element.name, Element.status)) + end + + if newstatus==OPSGROUP.ElementStatus.SPAWNED then + --- + -- SPAWNED + --- + + if self:_AllSimilarStatus(newstatus) then + self:__Spawned(-0.5) + end + + elseif newstatus==OPSGROUP.ElementStatus.PARKING then + --- + -- PARKING + --- + + if self:_AllSimilarStatus(newstatus) then + self:__FlightParking(-0.5) + end + + elseif newstatus==OPSGROUP.ElementStatus.ENGINEON then + --- + -- ENGINEON + --- + + -- No FLIGHT status. Waiting for taxiing. + + elseif newstatus==OPSGROUP.ElementStatus.TAXIING then + --- + -- TAXIING + --- + + if self:_AllSimilarStatus(newstatus) then + self:__FlightTaxiing(-0.5) + end + + elseif newstatus==OPSGROUP.ElementStatus.TAKEOFF then + --- + -- TAKEOFF + --- + + if self:_AllSimilarStatus(newstatus) then + -- Trigger takeoff event. Also triggers airborne event. + self:__FlightTakeoff(-0.5, airbase) + end + + elseif newstatus==OPSGROUP.ElementStatus.AIRBORNE then + --- + -- AIRBORNE + --- + + if self:_AllSimilarStatus(newstatus) then + self:__FlightAirborne(-0.5) + end + + elseif newstatus==OPSGROUP.ElementStatus.LANDED then + --- + -- LANDED + --- + + if self:_AllSimilarStatus(newstatus) then + self:FlightLanded(airbase) + end + + elseif newstatus==OPSGROUP.ElementStatus.ARRIVED then + --- + -- ARRIVED + --- + + if self:_AllSimilarStatus(newstatus) then + + if self:IsLanded() then + self:FlightArrived() + elseif self:IsAirborne() then + self:FlightLanded() + self:FlightArrived() + end + + end + + elseif newstatus==OPSGROUP.ElementStatus.DEAD then + --- + -- DEAD + --- + + if self:_AllSimilarStatus(newstatus) then + self:Dead() + end + + end +end + +--- Set status for all elements (except dead ones). +-- @param #OPSGROUP self +-- @param #string status Element status. +function OPSGROUP:_SetElementStatusAll(status) + + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if element.status~=OPSGROUP.ElementStatus.DEAD then + element.status=status + end + end + +end + +--- Get the element of a group. +-- @param #OPSGROUP self +-- @param #string unitname Name of unit. +-- @return #OPSGROUP.Element The element. +function OPSGROUP:GetElementByName(unitname) + + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if element.name==unitname then + return element + end + + end + + return nil +end + +--- Get number of elements alive. +-- @param #OPSGROUP self +-- @param #string status (Optional) Only count number, which are in a special status. +-- @return #number Number of elements. +function OPSGROUP:GetNelements(status) + + local n=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if element.status~=OPSGROUP.ElementStatus.DEAD then + if element.unit and element.unit:IsAlive() then + if status==nil or element.status==status then + n=n+1 + end + end + end + end + + + return n +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------