--- **Ops** - Enhanced Naval Group. -- -- ## Main Features: -- -- * Let the group steam into the wind -- * Command a full stop -- * Patrol waypoints *ad infinitum* -- * Collision warning, if group is heading towards a land mass or another obstacle -- * Automatic pathfinding, e.g. around islands -- * Let a submarine dive and surface -- * Manage TACAN and ICLS beacons -- * Dynamically add and remove waypoints -- * Sophisticated task queueing system (know when DCS tasks start and end) -- * Convenient checks when the group enters or leaves a zone -- * Detection events for new, known and lost units -- * Simple LASER and IR-pointer setup -- * Compatible with AUFTRAG class -- * Many additional events that the mission designer can hook into -- -- === -- -- ## Example Missions: -- -- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Navygroup) -- -- === -- -- ### Author: **funkyfranky** -- -- === -- @module Ops.NavyGroup -- @image OPS_NavyGroup.png --- NAVYGROUP class. -- @type NAVYGROUP -- @field #boolean turning If true, group is currently turning. -- @field #NAVYGROUP.IntoWind intowind Into wind info. -- @field #table Qintowind Queue of "into wind" turns. -- @field #number intowindcounter Counter of into wind IDs. -- @field #number depth Ordered depth in meters. -- @field #boolean collisionwarning If true, collition warning. -- @field #boolean pathfindingOn If true, enable pathfining. -- @field #number pathCorridor Path corrdidor width in meters. -- @field #boolean ispathfinding If true, group is currently path finding. -- @field #NAVYGROUP.Target engage Engage target. -- @extends Ops.OpsGroup#OPSGROUP --- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson -- -- === -- -- # The NAVYGROUP Concept -- -- This class enhances naval groups. -- -- @field #NAVYGROUP NAVYGROUP = { ClassName = "NAVYGROUP", turning = false, intowind = nil, intowindcounter = 0, Qintowind = {}, pathCorridor = 400, engage = {}, } --- Turn into wind parameters. -- @type NAVYGROUP.IntoWind -- @field #number Tstart Time to start. -- @field #number Tstop Time to stop. -- @field #boolean Uturn U-turn. -- @field #number Speed Speed in knots. -- @field #number Offset Offset angle in degrees. -- @field #number Id Unique ID of the turn. -- @field Ops.OpsGroup#OPSGROUP.Waypoint waypoint Turn into wind waypoint. -- @field Core.Point#COORDINATE Coordinate Coordinate where we left the route. -- @field #number Heading Heading the boat will take in degrees. -- @field #boolean Open Currently active. -- @field #boolean Over This turn is over. -- @field #boolean Recovery If `true` this is a recovery window. If `false`, this is a launch window. If `nil` this is just a turn into the wind. --- Engage Target. -- @type NAVYGROUP.Target -- @field Ops.Target#TARGET Target The target. -- @field Core.Point#COORDINATE Coordinate Last known coordinate of the target. -- @field Ops.OpsGroup#OPSGROUP.Waypoint Waypoint the waypoint created to go to the target. -- @field #number roe ROE backup. -- @field #number alarmstate Alarm state backup. --- NavyGroup version. -- @field #string version NAVYGROUP.version="0.7.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Add Retreat. -- TODO: Submaries. -- TODO: Extend, shorten turn into wind windows. -- TODO: Skipper menu. -- DONE: Add EngageTarget. -- DONE: Add RTZ. -- DONE: Collision warning. -- DONE: Detour, add temporary waypoint and resume route. -- DONE: Stop and resume route. -- DONE: Add waypoints. -- DONE: Add tasks. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create a new NAVYGROUP class object. -- @param #NAVYGROUP self -- @param Wrapper.Group#GROUP group The group object. Can also be given by its group name as `#string`. -- @return #NAVYGROUP self function NAVYGROUP:New(group) -- First check if we already have an OPS group for this group. local og=_DATABASE:GetOpsGroup(group) if og then og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) return og end -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #NAVYGROUP -- Set some string id for output to DCS.log file. self.lid=string.format("NAVYGROUP %s | ", self.groupname) -- Defaults self:SetDefaultROE() self:SetDefaultAlarmstate() self:SetDefaultEPLRS(self.isEPLRS) self:SetDefaultEmission() self:SetDetection() self:SetPatrolAdInfinitum(true) self:SetPathfinding(false) -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("*", "FullStop", "Holding") -- Hold position. self:AddTransition("*", "Cruise", "Cruising") -- Hold position. self:AddTransition("*", "RTZ", "Returning") -- Group is returning to (home) zone. self:AddTransition("Returning", "Returned", "Returned") -- Group is returned to (home) zone. self:AddTransition("*", "Detour", "Cruising") -- Make a detour to a coordinate and resume route afterwards. self:AddTransition("*", "DetourReached", "*") -- Group reached the detour coordinate. self:AddTransition("*", "Retreat", "Retreating") -- Order a retreat. self:AddTransition("Retreating", "Retreated", "Retreated") -- Group retreated. self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target from Cruising state self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target from Holding state self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target from OnDetour state self:AddTransition("Engaging", "Disengage", "Cruising") -- Disengage and back to cruising. self:AddTransition("*", "TurnIntoWind", "Cruising") -- Command the group to turn into the wind. self:AddTransition("*", "TurnedIntoWind", "*") -- Group turned into wind. self:AddTransition("*", "TurnIntoWindStop", "*") -- Stop a turn into wind. self:AddTransition("*", "TurnIntoWindOver", "*") -- Turn into wind is over. self:AddTransition("*", "TurningStarted", "*") -- Group started turning. self:AddTransition("*", "TurningStopped", "*") -- Group stopped turning. self:AddTransition("*", "CollisionWarning", "*") -- Collision warning. self:AddTransition("*", "ClearAhead", "*") -- Clear ahead. self:AddTransition("Cruising", "Dive", "Cruising") -- Command a submarine to dive. self:AddTransition("Engaging", "Dive", "Engaging") -- Command a submarine to dive. self:AddTransition("Cruising", "Surface", "Cruising") -- Command a submarine to go to the surface. self:AddTransition("Engaging", "Surface", "Engaging") -- Command a submarine to go to the surface. ------------------------ --- Pseudo Functions --- ------------------------ --- Triggers the FSM event "Cruise". -- @function [parent=#NAVYGROUP] Cruise -- @param #NAVYGROUP self -- @param #number Speed Speed in knots until next waypoint is reached. --- Triggers the FSM event "Cruise" after a delay. -- @function [parent=#NAVYGROUP] __Cruise -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. -- @param #number Speed Speed in knots until next waypoint is reached. --- On after "Cruise" event. -- @function [parent=#NAVYGROUP] OnAfterCruise -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number Speed Speed in knots until next waypoint is reached. --- Triggers the FSM event "TurnIntoWind". -- @function [parent=#NAVYGROUP] TurnIntoWind -- @param #NAVYGROUP self -- @param #NAVYGROUP.IntoWind Into wind parameters. --- Triggers the FSM event "TurnIntoWind" after a delay. -- @function [parent=#NAVYGROUP] __TurnIntoWind -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. -- @param #NAVYGROUP.IntoWind Into wind parameters. --- On after "TurnIntoWind" event. -- @function [parent=#NAVYGROUP] OnAfterTurnIntoWind -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #NAVYGROUP.IntoWind Into wind parameters. --- Triggers the FSM event "TurnedIntoWind". -- @function [parent=#NAVYGROUP] TurnedIntoWind -- @param #NAVYGROUP self --- Triggers the FSM event "TurnedIntoWind" after a delay. -- @function [parent=#NAVYGROUP] __TurnedIntoWind -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. --- On after "TurnedIntoWind" event. -- @function [parent=#NAVYGROUP] OnAfterTurnedIntoWind -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "TurnIntoWindStop". -- @function [parent=#NAVYGROUP] TurnIntoWindStop -- @param #NAVYGROUP self --- Triggers the FSM event "TurnIntoWindStop" after a delay. -- @function [parent=#NAVYGROUP] __TurnIntoWindStop -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. --- On after "TurnIntoWindStop" event. -- @function [parent=#NAVYGROUP] OnAfterTurnIntoWindStop -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "TurnIntoWindOver". -- @function [parent=#NAVYGROUP] TurnIntoWindOver -- @param #NAVYGROUP self -- @param #NAVYGROUP.IntoWind IntoWindData Data table. --- Triggers the FSM event "TurnIntoWindOver" after a delay. -- @function [parent=#NAVYGROUP] __TurnIntoWindOver -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. -- @param #NAVYGROUP.IntoWind IntoWindData Data table. --- On after "TurnIntoWindOver" event. -- @function [parent=#NAVYGROUP] OnAfterTurnIntoWindOver -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #NAVYGROUP.IntoWind IntoWindData Data table. --- Triggers the FSM event "TurningStarted". -- @function [parent=#NAVYGROUP] TurningStarted -- @param #NAVYGROUP self --- Triggers the FSM event "TurningStarted" after a delay. -- @function [parent=#NAVYGROUP] __TurningStarted -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. --- On after "TurningStarted" event. -- @function [parent=#NAVYGROUP] OnAfterTurningStarted -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "TurningStopped". -- @function [parent=#NAVYGROUP] TurningStopped -- @param #NAVYGROUP self --- Triggers the FSM event "TurningStopped" after a delay. -- @function [parent=#NAVYGROUP] __TurningStopped -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. --- On after "TurningStopped" event. -- @function [parent=#NAVYGROUP] OnAfterTurningStopped -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "CollisionWarning". -- @function [parent=#NAVYGROUP] CollisionWarning -- @param #NAVYGROUP self --- Triggers the FSM event "CollisionWarning" after a delay. -- @function [parent=#NAVYGROUP] __CollisionWarning -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. --- On after "CollisionWarning" event. -- @function [parent=#NAVYGROUP] OnAfterCollisionWarning -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "ClearAhead". -- @function [parent=#NAVYGROUP] ClearAhead -- @param #NAVYGROUP self --- Triggers the FSM event "ClearAhead" after a delay. -- @function [parent=#NAVYGROUP] __ClearAhead -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. --- On after "ClearAhead" event. -- @function [parent=#NAVYGROUP] OnAfterClearAhead -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- Triggers the FSM event "Dive". -- @function [parent=#NAVYGROUP] Dive -- @param #NAVYGROUP self -- @param #number Depth Dive depth in meters. Default 50 meters. -- @param #number Speed Speed in knots until next waypoint is reached. --- Triggers the FSM event "Dive" after a delay. -- @function [parent=#NAVYGROUP] __Dive -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. -- @param #number Depth Dive depth in meters. Default 50 meters. -- @param #number Speed Speed in knots until next waypoint is reached. --- On after "Dive" event. -- @function [parent=#NAVYGROUP] OnAfterDive -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number Depth Dive depth in meters. Default 50 meters. -- @param #number Speed Speed in knots until next waypoint is reached. --- Triggers the FSM event "Surface". -- @function [parent=#NAVYGROUP] Surface -- @param #NAVYGROUP self -- @param #number Speed Speed in knots until next waypoint is reached. --- Triggers the FSM event "Surface" after a delay. -- @function [parent=#NAVYGROUP] __Surface -- @param #NAVYGROUP self -- @param #number delay Delay in seconds. -- @param #number Speed Speed in knots until next waypoint is reached. --- On after "Surface" event. -- @function [parent=#NAVYGROUP] OnAfterSurface -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number Speed Speed in knots until next waypoint is reached. -- Init waypoints. self:_InitWaypoints() -- Initialize the group. self:_InitGroup() -- Handle events: self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.Dead, self.OnEventDead) self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) -- Start the status monitoring. self.timerStatus=TIMER:New(self.Status, self):Start(1, 30) -- Start queue update timer. self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(2, 5) -- Start check zone timer. self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 60) -- Add OPSGROUP to _DATABASE. _DATABASE:AddOpsGroup(self) return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Group patrols ad inifintum. If the last waypoint is reached, it will go to waypoint one and repeat its route. -- @param #NAVYGROUP self -- @param #boolean switch If true or nil, patrol until the end of time. If false, go along the waypoints once and stop. -- @return #NAVYGROUP self function NAVYGROUP:SetPatrolAdInfinitum(switch) if switch==false then self.adinfinitum=false else self.adinfinitum=true end return self end --- Enable/disable pathfinding. -- @param #NAVYGROUP self -- @param #boolean Switch If true, enable pathfinding. -- @param #number CorridorWidth Corridor with in meters. Default 400 m. -- @return #NAVYGROUP self function NAVYGROUP:SetPathfinding(Switch, CorridorWidth) self.pathfindingOn=Switch self.pathCorridor=CorridorWidth or 400 return self end --- Enable pathfinding. -- @param #NAVYGROUP self -- @param #number CorridorWidth Corridor with in meters. Default 400 m. -- @return #NAVYGROUP self function NAVYGROUP:SetPathfindingOn(CorridorWidth) self:SetPathfinding(true, CorridorWidth) return self end --- Disable pathfinding. -- @param #NAVYGROUP self -- @return #NAVYGROUP self function NAVYGROUP:SetPathfindingOff() self:SetPathfinding(false, self.pathCorridor) return self end --- Add a *scheduled* task. -- @param #NAVYGROUP self -- @param Core.Point#COORDINATE Coordinate Coordinate of the target. -- @param #string Clock Time when to start the attack. -- @param #number Radius Radius in meters. Default 100 m. -- @param #number Nshots Number of shots to fire. Default 3. -- @param #number WeaponType Type of weapon. Default auto. -- @param #number Prio Priority of the task. -- @return Ops.OpsGroup#OPSGROUP.Task The task data. function NAVYGROUP:AddTaskFireAtPoint(Coordinate, Clock, Radius, Nshots, WeaponType, Prio) local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) local task=self:AddTask(DCStask, Clock, nil, Prio) return task end --- Add a *waypoint* task. -- @param #NAVYGROUP self -- @param Core.Point#COORDINATE Coordinate Coordinate of the target. -- @param Ops.OpsGroup#OPSGROUP.Waypoint Waypoint Where the task is executed. Default is next waypoint. -- @param #number Radius Radius in meters. Default 100 m. -- @param #number Nshots Number of shots to fire. Default 3. -- @param #number WeaponType Type of weapon. Default auto. -- @param #number Prio Priority of the task. -- @param #number Duration Duration in seconds after which the task is cancelled. Default *never*. -- @return Ops.OpsGroup#OPSGROUP.Task The task table. function NAVYGROUP:AddTaskWaypointFireAtPoint(Coordinate, Waypoint, Radius, Nshots, WeaponType, Prio, Duration) Waypoint=Waypoint or self:GetWaypointNext() local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) local task=self:AddTaskWaypoint(DCStask, Waypoint, nil, Prio, Duration) return task end --- Add a *scheduled* task. -- @param #NAVYGROUP self -- @param Wrapper.Group#GROUP TargetGroup Target group. -- @param #number WeaponExpend How much weapons does are used. -- @param #number WeaponType Type of weapon. Default auto. -- @param #string Clock Time when to start the attack. -- @param #number Prio Priority of the task. -- @return Ops.OpsGroup#OPSGROUP.Task The task data. function NAVYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clock, Prio) local DCStask=CONTROLLABLE.TaskAttackGroup(nil, TargetGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit, GroupAttack) local task=self:AddTask(DCStask, Clock, nil, Prio) return task end --- Create a turn into wind window. Note that this is not executed as it not added to the queue. -- @param #NAVYGROUP self -- @param #string starttime Start time, e.g. "8:00" for eight o'clock. Default now. -- @param #string stoptime Stop time, e.g. "9:00" for nine o'clock. Default 90 minutes after start time. -- @param #number speed Speed in knots during turn into wind leg. -- @param #boolean uturn If true (or nil), carrier wil perform a U-turn and go back to where it came from before resuming its route to the next waypoint. If false, it will go directly to the next waypoint. -- @param #number offset Offset angle in degrees, e.g. to account for an angled runway. Default 0 deg. -- @return #NAVYGROUP.IntoWind Recovery window. function NAVYGROUP:_CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset) -- Absolute mission time in seconds. local Tnow=timer.getAbsTime() -- Convert number to Clock. if starttime and type(starttime)=="number" then starttime=UTILS.SecondsToClock(Tnow+starttime) end -- Input or now. starttime=starttime or UTILS.SecondsToClock(Tnow) -- Set start time. local Tstart=UTILS.ClockToSeconds(starttime) if uturn==nil then uturn=true end -- Set stop time. local Tstop=Tstart+90*60 if stoptime==nil then Tstop=Tstart+90*60 elseif type(stoptime)=="number" then Tstop=Tstart+stoptime else Tstop=UTILS.ClockToSeconds(stoptime) end -- Consistancy check for timing. if Tstart>Tstop then self:E(string.format("ERROR:Into wind stop time %s lies before start time %s. Input rejected!", UTILS.SecondsToClock(Tstart), UTILS.SecondsToClock(Tstop))) return self end if Tstop<=Tnow then self:E(string.format("WARNING: Into wind stop time %s already over. Tnow=%s! Input rejected.", UTILS.SecondsToClock(Tstop), UTILS.SecondsToClock(Tnow))) return self end -- Increase counter. self.intowindcounter=self.intowindcounter+1 -- Recovery window. local recovery={} --#NAVYGROUP.IntoWind recovery.Tstart=Tstart recovery.Tstop=Tstop recovery.Open=false recovery.Over=false recovery.Speed=speed or 20 recovery.Uturn=uturn and uturn or false recovery.Offset=offset or 0 recovery.Id=self.intowindcounter return recovery end --- Add a time window, where the groups steams into the wind. -- @param #NAVYGROUP self -- @param #string starttime Start time, e.g. "8:00" for eight o'clock. Default now. -- @param #string stoptime Stop time, e.g. "9:00" for nine o'clock. Default 90 minutes after start time. -- @param #number speed Wind speed on deck in knots during turn into wind leg. Default 20 knots. -- @param #boolean uturn If `true` (or `nil`), carrier wil perform a U-turn and go back to where it came from before resuming its route to the next waypoint. If false, it will go directly to the next waypoint. -- @param #number offset Offset angle in degrees, e.g. to account for an angled runway. Default 0 deg. -- @return #NAVYGROUP.IntoWind Turn into window data table. function NAVYGROUP:AddTurnIntoWind(starttime, stoptime, speed, uturn, offset) local recovery=self:_CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset) --TODO: check if window is overlapping with an other and if extend the window. -- Add to table table.insert(self.Qintowind, recovery) return recovery end --- Remove steam into wind window from queue. If the window is currently active, it is stopped first. -- @param #NAVYGROUP self -- @param #NAVYGROUP.IntoWind IntoWindData Turn into window data table. -- @return #NAVYGROUP self function NAVYGROUP:RemoveTurnIntoWind(IntoWindData) -- Check if this is a window currently open. if self.intowind and self.intowind.Id==IntoWindData.Id then self:TurnIntoWindStop() return end for i,_tiw in pairs(self.Qintowind) do local tiw=_tiw --#NAVYGROUP.IntoWind if tiw.Id==IntoWindData.Id then --env.info("FF removing window "..tiw.Id) table.remove(self.Qintowind, i) break end end return self end --- Check if the group is currently holding its positon. -- @param #NAVYGROUP self -- @return #boolean If true, group was ordered to hold. function NAVYGROUP:IsHolding() return self:Is("Holding") end --- Check if the group is currently cruising. -- @param #NAVYGROUP self -- @return #boolean If true, group cruising. function NAVYGROUP:IsCruising() return self:Is("Cruising") end --- Check if the group is currently on a detour. -- @param #NAVYGROUP self -- @return #boolean If true, group is on a detour function NAVYGROUP:IsOnDetour() return self:Is("OnDetour") end --- Check if the group is currently diving. -- @param #NAVYGROUP self -- @return #boolean If true, group is currently diving. function NAVYGROUP:IsDiving() return self:Is("Diving") end --- Check if the group is currently turning. -- @param #NAVYGROUP self -- @return #boolean If true, group is currently turning. function NAVYGROUP:IsTurning() return self.turning end --- Check if the group is currently steaming into the wind. -- @param #NAVYGROUP self -- @return #boolean If true, group is currently steaming into the wind. function NAVYGROUP:IsSteamingIntoWind() if self.intowind then return true else return false end end --- Check if the group is currently recovering aircraft. -- @param #NAVYGROUP self -- @return #boolean If true, group is currently recovering. function NAVYGROUP:IsRecovering() if self.intowind then if self.intowind.Recovery==true then return true else return false end else return false end end --- Check if the group is currently launching aircraft. -- @param #NAVYGROUP self -- @return #boolean If true, group is currently launching. function NAVYGROUP:IsLaunching() if self.intowind then if self.intowind.Recovery==false then return true else return false end else return false end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Update status. -- @param #NAVYGROUP self function NAVYGROUP:Status(From, Event, To) -- FSM state. local fsmstate=self:GetState() -- Is group alive? local alive=self:IsAlive() -- Free path. local freepath=0 -- Check if group is exists and is active. if alive then -- Update last known position, orientation, velocity. self:_UpdatePosition() -- Check if group has detected any units. self:_CheckDetectedUnits() -- Check if group started or stopped turning. self:_CheckTurning() -- Distance to next Waypoint. local disttoWP=math.min(self:GetDistanceToWaypoint(), UTILS.NMToMeters(10)) freepath=disttoWP -- Only check if not currently turning. if not self:IsTurning() then -- Check free path ahead. freepath=self:_CheckFreePath(freepath, 100) if disttoWP>1 and freepathself.Twaiting+self.dTwait then self.Twaiting=nil self.dTwait=nil if self:_CountPausedMissions()>0 then self:UnpauseMission() else self:Cruise() end end end end else -- Check damage of elements and group. self:_CheckDamage() end -- Group exists but can also be inactive. if alive~=nil then if self.verbose>=1 then -- Number of elements. local nelem=self:CountElements() local Nelem=#self.elements -- Get number of tasks and missions. local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() local nMissions=self:CountRemainingMissison() -- ROE and Alarm State. local roe=self:GetROE() or -1 local als=self:GetAlarmstate() or -1 -- Waypoint stuff. local wpidxCurr=self.currentwp local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr) or 0 local wpidxNext=self:GetWaypointIndexNext() or 0 local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext) or 0 local wpN=#self.waypoints or 0 local wpF=tostring(self.passedfinalwp) -- Speed. local speed=UTILS.MpsToKnots(self.velocity or 0) local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) -- Altitude. local alt=self.position and self.position.y or 0 -- Heading in degrees. local hdg=self.heading or 0 -- Life points. local life=self.life or 0 -- Total ammo. local ammo=self:GetAmmoTot().Total -- Detected units. local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "Off" -- Get cargo weight. local cargo=0 for _,_element in pairs(self.elements) do local element=_element --Ops.OpsGroup#OPSGROUP.Element cargo=cargo+element.weightCargo end -- Into wind and turning status. local intowind=self:IsSteamingIntoWind() and UTILS.SecondsToClock(self.intowind.Tstop-timer.getAbsTime(), true) or "N/A" local turning=tostring(self:IsTurning()) -- Info text. local text=string.format("%s [%d/%d]: ROE/AS=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f | Turn=%s Collision=%d IntoWind=%s", fsmstate, nelem, Nelem, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, wpN, wpF, life, speed, speedEx, hdg, ammo, ndetected, cargo, turning, freepath, intowind) self:I(self.lid..text) end else -- Info text. local text=string.format("State %s: Alive=%s", fsmstate, tostring(self:IsAlive())) self:T(self.lid..text) end --- -- Recovery Windows --- if alive and self.verbose>=2 and #self.Qintowind>0 then -- Debug output: local text=string.format(self.lid.."Turn into wind time windows:") -- Handle case with no recoveries. if #self.Qintowind==0 then text=text.." none!" end -- Loop over all slots. for i,_recovery in pairs(self.Qintowind) do local recovery=_recovery --#NAVYGROUP.IntoWind -- Get start/stop clock strings. local Cstart=UTILS.SecondsToClock(recovery.Tstart) local Cstop=UTILS.SecondsToClock(recovery.Tstop) -- Debug text. text=text..string.format("\n[%d] ID=%d Start=%s Stop=%s Open=%s Over=%s", i, recovery.Id, Cstart, Cstop, tostring(recovery.Open), tostring(recovery.Over)) end -- Debug output. self:I(self.lid..text) end --- -- Engage Detected Targets --- if self:IsCruising() and self.detectionOn and self.engagedetectedOn then local targetgroup, targetdist=self:_GetDetectedTarget() -- If we found a group, we engage it. if targetgroup then self:I(self.lid..string.format("Engaging target group %s at distance %d meters", targetgroup:GetName(), targetdist)) self:EngageTarget(targetgroup) end end --- -- Cargo --- self:_CheckCargoTransport() --- -- Tasks & Missions --- self:_PrintTaskAndMissionStatus() end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- DCS Events ==> See OPSGROUP ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- See OPSGROUP! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On after "ElementSpawned" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.OpsGroup#OPSGROUP.Element Element The group element. function NAVYGROUP:onafterElementSpawned(From, Event, To, Element) self:T(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:T(self.lid..string.format("Group spawned!")) -- Debug info. if self.verbose>=1 then local text=string.format("Initialized Navy Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) text=text..string.format("Has EPLRS = %s\n", tostring(self.isEPLRS)) text=text..string.format("Is Submarine = %s\n", tostring(self.isSubmarine)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos) text=text..string.format("FSM state = %s\n", self:GetState()) text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) self:I(self.lid..text) end -- Update position. self:_UpdatePosition() -- Not dead or destroyed yet. self.isDead=false self.isDestroyed=false if self.isAI then -- Set default ROE. self:SwitchROE(self.option.ROE) -- Set default Alarm State. self:SwitchAlarmstate(self.option.Alarm) -- Set emission. self:SwitchEmission(self.option.Emission) -- Set default EPLRS. self:SwitchEPLRS(self.option.EPLRS) -- Set default Invisible. self:SwitchInvisible(self.option.Invisible) -- Set default Immortal. self:SwitchImmortal(self.option.Immortal) -- Set TACAN beacon. self:_SwitchTACAN() -- Turn ICLS on. self:_SwitchICLS() -- Set radio. if self.radioDefault then -- CAREFUL: This makes DCS crash for some ships like speed boats or Higgins boats! (On a respawn for example). Looks like the command SetFrequency is causing this. --self:SwitchRadio() else self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, false) end -- Update route. if #self.waypoints>1 then self:__Cruise(-0.1) else self:FullStop() end end end --- On before "UpdateRoute" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number n Next waypoint index. Default is the one coming after that one that has been passed last. -- @param #number N Waypoint Max waypoint index to be included in the route. Default is the final waypoint. -- @param #number Speed Speed in knots to the next waypoint. -- @param #number Depth Depth in meters to the next waypoint. function NAVYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Depth) -- Is transition allowed? We assume yes until proven otherwise. local allowed=true local trepeat=nil if self:IsWaiting() then self:T(self.lid.."Update route denied. Group is WAITING!") return false elseif self:IsInUtero() then self:T(self.lid.."Update route denied. Group is INUTERO!") return false elseif self:IsDead() then self:T(self.lid.."Update route denied. Group is DEAD!") return false elseif self:IsStopped() then self:T(self.lid.."Update route denied. Group is STOPPED!") return false elseif self:IsHolding() then self:T(self.lid.."Update route denied. Group is holding position!") return false end -- Check for a current task. if self.taskcurrent>0 then -- Get the current task. Must not be executing already. local task=self:GetTaskByID(self.taskcurrent) if task then if task.dcstask.id=="PatrolZone" then -- For patrol zone, we need to allow the update as we insert new waypoints. self:T2(self.lid.."Allowing update route for Task: PatrolZone") elseif task.dcstask.id=="ReconMission" then -- For recon missions, we need to allow the update as we insert new waypoints. self:T2(self.lid.."Allowing update route for Task: ReconMission") elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then -- For relocate self:T2(self.lid.."Allowing update route for Task: Relocate Cohort") else local taskname=task and task.description or "No description" self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s", self.taskcurrent, tostring(taskname))) allowed=false end else -- Now this can happen, if we directly use TaskExecute as the task is not in the task queue and cannot be removed. Therefore, also directly executed tasks should be added to the queue! self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d (>0!) but no task?!", self.taskcurrent)) -- Anyhow, a task is running so we do not allow to update the route! allowed=false end end -- Not good, because mission will never start. Better only check if there is a current task! --if self.currentmission then --end -- Only AI flights. if not self.isAI then allowed=false end -- Debug info. self:T2(self.lid..string.format("Onbefore Updateroute in state %s: allowed=%s (repeat in %s)", self:GetState(), tostring(allowed), tostring(trepeat))) -- Try again? if trepeat then self:__UpdateRoute(trepeat, n) end return allowed end --- On after "UpdateRoute" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number n Next waypoint index. Default is the one coming after that one that has been passed last. -- @param #number N Waypoint Max waypoint index to be included in the route. Default is the final waypoint. -- @param #number Speed Speed in knots to the next waypoint. -- @param #number Depth Depth in meters to the next waypoint. function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Depth) -- Update route from this waypoint number onwards. n=n or self:GetWaypointIndexNext() -- Max index. N=N or #self.waypoints N=math.min(N, #self.waypoints) -- Waypoints. local waypoints={} for i=n, N do -- Waypoint. local wp=UTILS.DeepCopy(self.waypoints[i]) --Ops.OpsGroup#OPSGROUP.Waypoint --env.info(string.format("FF i=%d UID=%d n=%d, N=%d", i, wp.uid, n, N)) -- Speed. if Speed then -- Take speed specified. wp.speed=UTILS.KnotsToMps(Speed) else -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. if wp.speed<0.1 then --self.adinfinitum and wp.speed=UTILS.KmphToMps(self.speedCruise) end end -- Depth. if Depth then wp.alt=-Depth elseif self.depth then wp.alt=-self.depth else -- Take default waypoint alt. wp.alt=wp.alt or 0 end -- Current set speed in m/s. if i==n then self.speedWp=wp.speed self.altWp=wp.alt end -- Add waypoint. table.insert(waypoints, wp) end -- Current waypoint. local current=self:GetCoordinate():WaypointNaval(UTILS.MpsToKmph(self.speedWp), self.altWp) table.insert(waypoints, 1, current) if self:IsEngaging() or not self.passedfinalwp then if self.verbose>=10 then for i=1,#waypoints do local wp=waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint local text=string.format("%s Waypoint [%d] UID=%d speed=%d", self.groupname, i-1, wp.uid or -1, wp.speed) self:I(self.lid..text) COORDINATE:NewFromWaypoint(wp):MarkToAll(text) end end -- Debug info. self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Depth=%d m", self.currentwp, n, #waypoints, #self.waypoints, UTILS.MpsToKnots(self.speedWp), self.altWp)) -- Route group to all defined waypoints remaining. self:Route(waypoints) else --- -- Passed final WP ==> Full Stop --- self:E(self.lid..string.format("WARNING: Passed final WP ==> Full Stop!")) self:FullStop() end end --- On after "Detour" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Core.Point#COORDINATE Coordinate Coordinate where to go. -- @param #number Speed Speed in knots. Default cruise speed. -- @param #number Depth Depth in meters. Default 0 meters. -- @param #number ResumeRoute If true, resume route after detour point was reached. If false, the group will stop at the detour point and wait for futher commands. function NAVYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Depth, ResumeRoute) -- Depth for submarines. Depth=Depth or 0 -- Speed in knots. Speed=Speed or self:GetSpeedCruise() -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid -- Add waypoint after current. local wp=self:AddWaypoint(Coordinate, Speed, uid, Depth, true) -- Set if we want to resume route after reaching the detour waypoint. if ResumeRoute then wp.detour=1 else wp.detour=0 end end --- On after "DetourReached" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function NAVYGROUP:onafterDetourReached(From, Event, To) self:T(self.lid.."Group reached detour coordinate.") end --- On after "TurnIntoWind" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #NAVYGROUP.IntoWind Into wind parameters. function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) IntoWind.Heading=self:GetHeadingIntoWind(IntoWind.Offset) IntoWind.Open=true IntoWind.Coordinate=self:GetCoordinate(true) self.intowind=IntoWind -- Wind speed in m/s. local _,vwind=self:GetWind() -- Convert to knots. vwind=UTILS.MpsToKnots(vwind) -- Speed of carrier relative to wind but at least 2 knots. local speed=math.max(IntoWind.Speed-vwind, 2) -- Debug info. self:T(self.lid..string.format("Steaming into wind: Heading=%03d Speed=%.1f Vwind=%.1f Vtot=%.1f knots, Tstart=%d Tstop=%d", IntoWind.Heading, speed, vwind, speed+vwind, IntoWind.Tstart, IntoWind.Tstop)) local distance=UTILS.NMToMeters(1000) local coord=self:GetCoordinate() local Coord=coord:Translate(distance, IntoWind.Heading) -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid local wptiw=self:AddWaypoint(Coord, speed, uid) wptiw.intowind=true IntoWind.waypoint=wptiw if IntoWind.Uturn and false then IntoWind.Coordinate:MarkToAll("Return coord") end end --- On before "TurnIntoWindStop" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function NAVYGROUP:onbeforeTurnIntoWindStop(From, Event, To) if self.intowind then return true else return false end end --- On after "TurnIntoWindStop" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function NAVYGROUP:onafterTurnIntoWindStop(From, Event, To) self:TurnIntoWindOver(self.intowind) end --- On after "TurnIntoWindOver" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #NAVYGROUP.IntoWind IntoWindData Data table. function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To, IntoWindData) if IntoWindData and self.intowind and IntoWindData.Id==self.intowind.Id then -- Debug message. self:T2(self.lid.."Turn Into Wind Over!") -- Window over and not open anymore. self.intowind.Over=true self.intowind.Open=false -- Remove additional waypoint. self:RemoveWaypointByID(self.intowind.waypoint.uid) if self.intowind.Uturn then --- -- U-turn ==> Go to coordinate where we left the route. --- -- Detour to where we left the route. self:T(self.lid.."FF Turn Into Wind Over ==> Uturn!") -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid -- Add temp waypoint. local wp=self:AddWaypoint(self.intowind.Coordinate, self:GetSpeedCruise(), uid) ; wp.temp=true else --- -- Go directly to next waypoint. --- -- Next waypoint index and speed. local indx=self:GetWaypointIndexNext() local speed=self:GetSpeedToWaypoint(indx) -- Update route. self:T(self.lid..string.format("FF Turn Into Wind Over ==> Next WP Index=%d at %.1f knots via update route!", indx, speed)) self:__UpdateRoute(-1, indx, nil, speed) end -- Set current window to nil. self.intowind=nil -- Remove window from queue. self:RemoveTurnIntoWind(IntoWindData) end end --- On after "FullStop" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function NAVYGROUP:onafterFullStop(From, Event, To) self:T(self.lid.."Full stop ==> holding") -- Get current position. local pos=self:GetCoordinate() -- Create a new waypoint. local wp=pos:WaypointNaval(0) -- Create new route consisting of only this position ==> Stop! self:Route({wp}) end --- On after "Cruise" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number Speed Speed in knots until next waypoint is reached. Default is speed set for waypoint. function NAVYGROUP:onafterCruise(From, Event, To, Speed) -- Not waiting anymore. self.Twaiting=nil self.dTwait=nil -- No set depth. self.depth=nil self:__UpdateRoute(-0.1, nil, nil, Speed) end --- On after "Dive" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number Depth Dive depth in meters. Default 50 meters. -- @param #number Speed Speed in knots until next waypoint is reached. function NAVYGROUP:onafterDive(From, Event, To, Depth, Speed) Depth=Depth or 50 self:I(self.lid..string.format("Diving to %d meters", Depth)) self.depth=Depth self:__UpdateRoute(-1, nil, nil, Speed) end --- On after "Surface" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number Speed Speed in knots until next waypoint is reached. function NAVYGROUP:onafterSurface(From, Event, To, Speed) self.depth=0 self:__UpdateRoute(-1, nil, nil, Speed) end --- On after "TurningStarted" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function NAVYGROUP:onafterTurningStarted(From, Event, To) self.turning=true end --- On after "TurningStarted" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function NAVYGROUP:onafterTurningStopped(From, Event, To) self.turning=false self.collisionwarning=false if self:IsSteamingIntoWind() then self:TurnedIntoWind() end end --- On after "CollisionWarning" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param #number Distance Distance in meters where obstacle was detected. function NAVYGROUP:onafterCollisionWarning(From, Event, To, Distance) self:T(self.lid..string.format("Iceberg ahead in %d meters!", Distance or -1)) self.collisionwarning=true end --- On after "EngageTarget" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Group#GROUP Group the group to be engaged. function NAVYGROUP:onafterEngageTarget(From, Event, To, Target) self:T(self.lid.."Engaging Target") if Target:IsInstanceOf("TARGET") then self.engage.Target=Target else self.engage.Target=TARGET:New(Target) end -- Target coordinate. self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9) -- Backup ROE and alarm state. self.engage.roe=self:GetROE() self.engage.alarmstate=self:GetAlarmstate() -- Switch ROE and alarm state. self:SwitchAlarmstate(ENUMS.AlarmState.Auto) self:SwitchROE(ENUMS.ROE.OpenFire) -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid -- Add waypoint after current. self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true) -- Set if we want to resume route after reaching the detour waypoint. self.engage.Waypoint.detour=1 end --- Update engage target. -- @param #NAVYGROUP self function NAVYGROUP:_UpdateEngageTarget() if self.engage.Target and self.engage.Target:IsAlive() then -- Get current position vector. local vec3=self.engage.Target:GetVec3() if vec3 then -- Distance to last known position of target. local dist=UTILS.VecDist3D(vec3, self.engage.Coordinate:GetVec3()) -- Check if target moved more than 100 meters. if dist>100 then --env.info("FF Update Engage Target Moved "..self.engage.Target:GetName()) -- Update new position. self.engage.Coordinate:UpdateFromVec3(vec3) -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid -- Remove current waypoint self:RemoveWaypointByID(self.engage.Waypoint.uid) local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9) -- Add waypoint after current. self.engage.Waypoint=self:AddWaypoint(intercoord, nil, uid, Formation, true) -- Set if we want to resume route after reaching the detour waypoint. self.engage.Waypoint.detour=0 end else -- Could not get position of target (not alive any more?) ==> Disengage. self:Disengage() end else -- Target not alive any more ==> Disengage. self:Disengage() end end --- On after "Disengage" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function NAVYGROUP:onafterDisengage(From, Event, To) self:T(self.lid.."Disengage Target") -- Restore previous ROE and alarm state. self:SwitchROE(self.engage.roe) self:SwitchAlarmstate(self.engage.alarmstate) -- Get current task local task=self:GetTaskCurrent() -- Get if current task is ground attack. if task and task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK then self:T(self.lid.."Disengage with current task GROUNDATTACK ==> Task Done!") self:TaskDone(task) end -- Remove current waypoint if self.engage.Waypoint then self:RemoveWaypointByID(self.engage.Waypoint.uid) end -- Check group is done self:_CheckGroupDone(1) end --- On after "OutOfAmmo" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function NAVYGROUP:onafterOutOfAmmo(From, Event, To) self:T(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) -- Check if we want to retreat once out of ammo. if self.retreatOnOutOfAmmo then self:__Retreat(-1) return end -- Third, check if we want to RTZ once out of ammo. if self.rtzOnOutOfAmmo then self:__RTZ(-1) end -- Get current task. local task=self:GetTaskCurrent() if task then if task.dcstask.id=="FireAtPoint" or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then self:T(self.lid..string.format("Cancelling current %s task because out of ammo!", task.dcstask.id)) self:TaskCancel(task) end end end --- On after "RTZ" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Core.Zone#ZONE Zone The zone to return to. -- @param #number Formation Formation of the group. function NAVYGROUP:onafterRTZ(From, Event, To, Zone, Formation) -- Zone. local zone=Zone or self.homezone -- Cancel all missions in the queue. self:CancelAllMissions() if zone then if self:IsInZone(zone) then self:Returned() else -- Debug info. self:T(self.lid..string.format("RTZ to Zone %s", zone:GetName())) local Coordinate=zone:GetRandomCoordinate() -- ID of current waypoint. local uid=self:GetWaypointCurrentUID() -- Add waypoint after current. local wp=self:AddWaypoint(Coordinate, nil, uid, Formation, true) -- Set if we want to resume route after reaching the detour waypoint. wp.detour=0 end else self:T(self.lid.."ERROR: No RTZ zone given!") end end --- On after "Returned" event. -- @param #NAVYGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function NAVYGROUP:onafterReturned(From, Event, To) -- Debug info. self:T(self.lid..string.format("Group returned")) if self.legion then -- Debug info. self:T(self.lid..string.format("Adding group back to warehouse stock")) -- Add asset back in 10 seconds. self.legion:__AddAsset(10, self.group, 1) end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Add an a waypoint to the route. -- @param #NAVYGROUP self -- @param Core.Point#COORDINATE Coordinate The coordinate of the waypoint. Use `COORDINATE:SetAltitude()` to define the altitude. -- @param #number Speed Speed in knots. Default is default cruise speed or 70% of max speed. -- @param #number AfterWaypointWithID Insert waypoint after waypoint given ID. Default is to insert as last waypoint. -- @param #number Depth Depth at waypoint in feet. Only for submarines. -- @param #boolean Updateroute If true or nil, call UpdateRoute. If false, no call. -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Updateroute) -- Create coordinate. local coordinate=self:_CoordinateFromObject(Coordinate) -- Set waypoint index. local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) -- Speed in knots. Speed=Speed or self:GetSpeedCruise() -- Create a Naval waypoint. local wp=coordinate:WaypointNaval(UTILS.KnotsToKmph(Speed), Depth) -- Create waypoint data table. local waypoint=self:_CreateWaypoint(wp) -- Set altitude. if Depth then waypoint.alt=UTILS.FeetToMeters(Depth) end -- Add waypoint to table. self:_AddWaypoint(waypoint, wpnumber) -- Debug info. self:T(self.lid..string.format("Adding NAVAL waypoint index=%d uid=%d, speed=%.1f knots. Last waypoint passed was #%d. Total waypoints #%d", wpnumber, waypoint.uid, Speed, self.currentwp, #self.waypoints)) -- Update route. if Updateroute==nil or Updateroute==true then self:__UpdateRoute(-0.01) end return waypoint end --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. -- @param #NAVYGROUP self -- @param #table Template Template used to init the group. Default is `self.template`. -- @return #NAVYGROUP self function NAVYGROUP:_InitGroup(Template) -- First check if group was already initialized. if self.groupinitialized then self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") return end -- Get template of group. local template=Template or self:_GetTemplate() -- Ships are always AI. self.isAI=true -- Is (template) group late activated. self.isLateActivated=template.lateActivation -- Naval groups cannot be uncontrolled. self.isUncontrolled=false -- Max speed in km/h. self.speedMax=self.group:GetSpeedMax() -- Is group mobile? if self.speedMax>3.6 then self.isMobile=true else self.isMobile=false end -- Cruise speed: 70% of max speed. self.speedCruise=self.speedMax*0.7 -- Group ammo. self.ammo=self:GetAmmoTot() -- Radio parameters from template. Default is set on spawn if not modified by the user. self.radio.On=true -- Radio is always on for ships. self.radio.Freq=tonumber(template.units[1].frequency)/1000000 self.radio.Modu=tonumber(template.units[1].modulation) -- Set default formation. No really applicable for ships. self.optionDefault.Formation="Off Road" self.option.Formation=self.optionDefault.Formation -- Default TACAN off. self:SetDefaultTACAN(nil, nil, nil, nil, true) self.tacan=UTILS.DeepCopy(self.tacanDefault) -- Default ICLS off. self:SetDefaultICLS(nil, nil, nil, true) self.icls=UTILS.DeepCopy(self.iclsDefault) -- Get all units of the group. local units=self.group:GetUnits() -- DCS group. local dcsgroup=Group.getByName(self.groupname) local size0=dcsgroup:getInitialSize() -- Quick check. if #units~=size0 then self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0)) end -- Add elemets. for _,unit in pairs(units) do self:_AddElementByName(unit:GetName()) end -- Init done. self.groupinitialized=true return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Option Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Check for possible collisions between two coordinates. -- @param #NAVYGROUP self -- @param #number DistanceMax Max distance in meters ahead to check. Default 5000. -- @param #number dx -- @return #number Free distance in meters. function NAVYGROUP:_CheckFreePath(DistanceMax, dx) local distance=DistanceMax or 5000 local dx=dx or 100 -- If the group is turning, we cannot really tell anything about a possible collision. if self:IsTurning() then return distance end -- Offset above sea level. local offsetY=0.1 -- Current bug on Caucasus. LoS returns false. if UTILS.GetDCSMap()==DCSMAP.Caucasus then offsetY=5.01 end local vec3=self:GetVec3() vec3.y=offsetY -- Current heading. local heading=self:GetHeading() local function LoS(dist) local checkvec3=UTILS.VecTranslate(vec3, dist, heading) local los=land.isVisible(vec3, checkvec3) return los end -- First check if everything is clear. if LoS(DistanceMax) then return DistanceMax end local function check() local xmin=0 local xmax=DistanceMax local Nmax=100 local eps=100 local N=1 while N<=Nmax do local d=xmax-xmin local x=xmin+d/2 local los=LoS(x) -- Debug message. self:T(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los))) if los and d<=eps then return x end if los then xmin=x else xmax=x end N=N+1 end return 0 end local _check=check() return _check end --- Check if group is turning. -- @param #NAVYGROUP self function NAVYGROUP:_CheckTurning() local unit=self.group:GetUnit(1) if unit and unit:IsAlive() then -- Current orientation of carrier. local vNew=self.orientX --unit:GetOrientationX() -- Last orientation from 30 seconds ago. local vLast=self.orientXLast -- 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))) -- Carrier is turning when its heading changed by at least two degrees since last check. local turning=math.abs(deltaLast)>=2 -- Check if turning stopped. if self.turning and not turning then -- Carrier was turning but is not any more. self:TurningStopped() elseif turning and not self.turning then -- Carrier was not turning but is now. self:TurningStarted() end -- Update turning. self.turning=turning end end --- Check queued turns into wind. -- @param #NAVYGROUP self function NAVYGROUP:_CheckTurnsIntoWind() -- Get current abs time. local time=timer.getAbsTime() if self.intowind then -- Check if time is over. if time>=self.intowind.Tstop then self:TurnIntoWindOver(self.intowind) end else -- Get next window. local IntoWind=self:GetTurnIntoWindNext() -- Start turn into wind. if IntoWind then self:TurnIntoWind(IntoWind) end end end --- Get the next turn into wind window, which is not yet running. -- @param #NAVYGROUP self -- @return #NAVYGROUP.IntoWind Next into wind data. Could be `nil` if there is not next window. function NAVYGROUP:GetTurnIntoWindNext() if #self.Qintowind>0 then -- Get current abs time. local time=timer.getAbsTime() -- Sort windows wrt to start time. table.sort(self.Qintowind, function(a, b) return a.Tstart=recovery.Tstart and time= 5 meters. if dist<5 then return end local boxwidth=dist*2 local spacex=dist*0.1 local delta=dist/10 -- Create a grid of nodes. We only want nodes of surface type water. astar:CreateGrid({land.SurfaceType.WATER}, boxwidth, spacex, delta, delta, self.verbose>10) -- Valid neighbour nodes need to have line of sight. astar:SetValidNeighbourLoS(self.pathCorridor) --- Function to find a path and add waypoints to the group. local function findpath() -- Calculate path from start to end node. local path=astar:GetPath(true, true) if path then -- Loop over nodes in found path. local uid=self:GetWaypointCurrent().uid -- ID of current waypoint. for i,_node in ipairs(path) do local node=_node --Core.Astar#ASTAR.Node -- Add waypoints along detour path to next waypoint. local wp=self:AddWaypoint(node.coordinate, speed, uid) wp.astar=true -- Update id so the next wp is added after this one. uid=wp.uid -- Debug: smoke and mark path. if self.verbose>=10 then node.coordinate:MarkToAll(string.format("Path node #%d", i)) end end return #path>0 else return false end end -- Return if path was found. return findpath() end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------