mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
2098 lines
65 KiB
Lua
2098 lines
65 KiB
Lua
--- **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.3"
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- 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)
|
|
|
|
-- 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 freepath<disttoWP then
|
|
|
|
if not self.collisionwarning then
|
|
-- Issue a collision warning event.
|
|
self:CollisionWarning(freepath)
|
|
end
|
|
|
|
if self.pathfindingOn and not self.ispathfinding then
|
|
self.ispathfinding=self:_FindPathToNextWaypoint()
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- Check into wind queue.
|
|
self:_CheckTurnsIntoWind()
|
|
|
|
-- Check ammo status.
|
|
self:_CheckAmmoStatus()
|
|
|
|
-- Check damage of elements and group.
|
|
self:_CheckDamage()
|
|
|
|
-- Check if group got stuck.
|
|
self:_CheckStuck()
|
|
|
|
-- Check if group is waiting.
|
|
if self:IsWaiting() then
|
|
if self.Twaiting and self.dTwait then
|
|
if timer.getAbsTime()>self.Twaiting+self.dTwait then
|
|
self.Twaiting=nil
|
|
self.dTwait=nil
|
|
self:Cruise()
|
|
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 default EPLRS.
|
|
self:SwitchEPLRS(self.option.EPLRS)
|
|
|
|
-- 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)
|
|
if self:IsWaiting() then
|
|
self:E(self.lid.."Update route denied. Group is WAITING!")
|
|
return false
|
|
elseif self:IsInUtero() then
|
|
self:E(self.lid.."Update route denied. Group is INUTERO!")
|
|
return false
|
|
elseif self:IsDead() then
|
|
self:E(self.lid.."Update route denied. Group is DEAD!")
|
|
return false
|
|
elseif self:IsStopped() then
|
|
self:E(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
|
|
return true
|
|
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()
|
|
|
|
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()
|
|
|
|
-- 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<b.Tstart end)
|
|
|
|
-- Loop over all slots.
|
|
for _,_recovery in pairs(self.Qintowind) do
|
|
local recovery=_recovery --#NAVYGROUP.IntoWind
|
|
|
|
if time>=recovery.Tstart and time<recovery.Tstop and not (recovery.Open or recovery.Over) then
|
|
return recovery
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
--- Get the turn into wind window, which is currently open.
|
|
-- @param #NAVYGROUP self
|
|
-- @return #NAVYGROUP.IntoWind Current into wind data. Could be `nil` if there is no window currenly open.
|
|
function NAVYGROUP:GetTurnIntoWindCurrent()
|
|
return self.intowind
|
|
end
|
|
|
|
--- Get wind direction and speed at current position.
|
|
-- @param #NAVYGROUP self
|
|
-- @return #number Direction the wind is blowing **from** in degrees.
|
|
-- @return #number Wind speed in m/s.
|
|
function NAVYGROUP:GetWind()
|
|
|
|
-- Current position of the carrier or input.
|
|
local coord=self:GetCoordinate()
|
|
|
|
-- Wind direction and speed. By default at 50 meters ASL.
|
|
local Wdir, Wspeed=coord:GetWind(50)
|
|
|
|
return Wdir, Wspeed
|
|
end
|
|
|
|
--- Get heading of group into the wind.
|
|
-- @param #NAVYGROUP self
|
|
-- @param #number Offset Offset angle in degrees, e.g. to account for an angled runway.
|
|
-- @return #number Carrier heading in degrees.
|
|
function NAVYGROUP:GetHeadingIntoWind(Offset)
|
|
|
|
Offset=Offset or 0
|
|
|
|
-- Get direction the wind is blowing from. This is where we want to go.
|
|
local windfrom, vwind=self:GetWind()
|
|
|
|
-- Actually, we want the runway in the wind.
|
|
local intowind=windfrom-Offset
|
|
|
|
-- If no wind, take current heading.
|
|
if vwind<0.1 then
|
|
intowind=self:GetHeading()
|
|
end
|
|
|
|
-- Adjust negative values.
|
|
if intowind<0 then
|
|
intowind=intowind+360
|
|
end
|
|
|
|
return intowind
|
|
end
|
|
|
|
--- Find free path to next waypoint.
|
|
-- @param #NAVYGROUP self
|
|
-- @return #boolean If true, a path was found.
|
|
function NAVYGROUP:_FindPathToNextWaypoint()
|
|
self:T3(self.lid.."Path finding")
|
|
|
|
--TODO: Do not create a new ASTAR object each time this function is called but make it self.astar and reuse. Should be better for performance.
|
|
|
|
-- Pathfinding A*
|
|
local astar=ASTAR:New()
|
|
|
|
-- Current positon of the group.
|
|
local position=self:GetCoordinate()
|
|
|
|
-- Next waypoint.
|
|
local wpnext=self:GetWaypointNext()
|
|
|
|
-- No next waypoint.
|
|
if wpnext==nil then
|
|
return
|
|
end
|
|
|
|
-- Next waypoint coordinate.
|
|
local nextwp=wpnext.coordinate
|
|
|
|
-- If we are currently turning into the wind...
|
|
if wpnext.intowind then
|
|
local hdg=self:GetHeading()
|
|
nextwp=position:Translate(UTILS.NMToMeters(20), hdg, true)
|
|
end
|
|
|
|
local speed=UTILS.MpsToKnots(wpnext.speed)
|
|
|
|
-- Set start coordinate.
|
|
astar:SetStartCoordinate(position)
|
|
|
|
-- Set end coordinate.
|
|
astar:SetEndCoordinate(nextwp)
|
|
|
|
-- Distance to next waypoint.
|
|
local dist=position:Get2DDistance(nextwp)
|
|
|
|
-- Check distance >= 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
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|