This commit is contained in:
Frank 2020-07-29 01:10:13 +02:00
parent 04923b65b2
commit 2f6d9b640f
7 changed files with 954 additions and 352 deletions

View File

@ -2,13 +2,16 @@
-- --
-- **Main Features:** -- **Main Features:**
-- --
-- * Stuff -- * Find path from A to B.
-- * Pre-defined as well as custom valid neighbour functions.
-- * Pre-defined as well as custom cost functions.
-- * Easy rectangular grid setup.
-- --
-- === -- ===
-- --
-- ### Author: **funkyfranky** -- ### Author: **funkyfranky**
-- @module Core.Astar -- @module Core.Astar
-- @image CORE_Atar.png -- @image CORE_Astar.png
--- ASTAR class. --- ASTAR class.
@ -21,20 +24,110 @@
-- @field #ASTAR.Node endNode End node. -- @field #ASTAR.Node endNode End node.
-- @field Core.Point#COORDINATE startCoord Start coordinate. -- @field Core.Point#COORDINATE startCoord Start coordinate.
-- @field Core.Point#COORDINATE endCoord End coordinate. -- @field Core.Point#COORDINATE endCoord End coordinate.
-- @field #func ValidNeighbourFunc Function to check if a node is valid. -- @field #function ValidNeighbourFunc Function to check if a node is valid.
-- @field #table ValidNeighbourArg Optional arguments passed to the valid neighbour function. -- @field #table ValidNeighbourArg Optional arguments passed to the valid neighbour function.
-- @field #function CostFunc Function to calculate the heuristic "cost" to go from one node to another.
-- @field #table CostArg Optional arguments passed to the cost function.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- When nothing goes right... Go left! --- When nothing goes right... Go left!
-- --
-- === -- ===
-- --
-- ![Banner Image](..\Presentations\WingCommander\ASTAR_Main.jpg) -- ![Banner Image](..\Presentations\Astar\ASTAR_Main.jpg)
-- --
-- # The ASTAR Concept -- # The ASTAR Concept
-- --
-- Pathfinding algorithm. -- Pathfinding algorithm.
-- --
--
-- # Start and Goal
--
-- The first thing we need to define is obviously the place where we want to start and where we want to go eventually.
--
-- ## Start
--
-- The start
--
-- ## Goal
--
--
-- # Nodes
--
-- ## Rectangular Grid
--
-- A rectangular grid can be created using the @{#ASTAR.CreateGrid}(*ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid*), where
--
-- * *ValidSurfaceTypes* is a table of valid surface types. By default all surface types are valid.
-- * *BoxXY* is the width of the grid perpendicular the the line between start and end node. Default is 40,000 meters (40 km).
-- * *SpaceX* is the additional space behind the start and end nodes. Default is 20,000 meters (20 km).
-- * *deltaX* is the grid spacing between nodes in the direction of start and end node. Default is 2,000 meters (2 km).
-- * *deltaY* is the grid spacing perpendicular to the direction of start and end node. Default is the same as *deltaX*.
-- * *MarkGrid* If set to *true*, this places marker on the F10 map on each grid node. Note that this can stall DCS if too many nodes are created.
--
-- ## Valid Surfaces
--
-- Certain unit types can only travel on certain surfaces types, for example
--
-- * Naval units can only travel on water (that also excludes shallow water in DCS currently),
-- * Ground units can only traval on land.
--
-- By restricting the surface type in the grid construction, we also reduce the number of nodes, which makes the algorithm more efficient.
--
-- ## Box Width (BoxHY)
--
-- The box width needs to be large enough to capture all paths you want to consider.
--
-- ## Space in X
--
-- The space in X value is important if the algorithm needs to to backwards from the start node or needs to extend even further than the end node.
--
-- ## Grid Spacing
--
-- The grid spacing is an important factor as it determines the number of nodes and hence the performance of the algorithm. It should be as large as possible.
-- However, if the value is too large, the algorithm might fail to get a valid path.
--
-- A good estimate of the grid spacing is to set it to be smaller (~ half the size) of the smallest gap you need to path.
--
-- # Valid Neighbours
--
-- The A* algorithm needs to know if a transition from one node to another is allowed or not. By default, hopping from one node to another is always possible.
--
-- ## Line of Sight
--
-- For naval
--
--
-- # Heuristic Cost
--
-- In order to determine the optimal path, the pathfinding algorithm needs to know, how costly it is to go from one node to another.
-- Often, this can simply be determined by the distance between two nodes. Therefore, the default cost function is set to be the 2D distance between two nodes.
--
--
-- # Calculate the Path
--
-- Finally, we have to calculate the path. This is done by the @{ASTAR.GetPath}(*ExcludeStart, ExcludeEnd*) function. This function returns a table of nodes, which
-- describe the optimal path from the start node to the end node.
--
-- By default, the start and end node are include in the table that is returned.
--
-- Note that a valid path must not always exist. So you should check if the function returns *nil*.
--
-- Common reasons that a path cannot be found are:
--
-- * The grid is too small ==> increase grid size, e.g. *BoxHY* and/or *SpaceX* if you use a rectangular grid.
-- * The grid spacing is too large ==> decrease *deltaX* and/or *deltaY*
-- * There simply is no valid path ==> you are screwed :(
--
--
-- # Examples
--
-- ## Strait of Hormuz
--
-- Carrier Group finds its way through the Stait of Hormuz.
--
-- ##
--
-- --
-- --
-- @field #ASTAR -- @field #ASTAR
@ -45,13 +138,13 @@ ASTAR = {
nodes = {}, nodes = {},
} }
--- Defence condition. --- Node data.
-- @type ASTAR.Node -- @type ASTAR.Node
-- @field Core.Point#COORDINATE coordinate Coordinate of the node. -- @field Core.Point#COORDINATE coordinate Coordinate of the node.
-- @field #number surfacetype Surface type. -- @field #number surfacetype Surface type.
--- ASTAR infinity --- ASTAR infinity.
-- @field #string INF -- @field #number INF
ASTAR.INF=1/0 ASTAR.INF=1/0
--- ASTAR class version. --- ASTAR class version.
@ -137,14 +230,14 @@ end
--- Add a node to the table of grid nodes specifying its coordinate. --- Add a node to the table of grid nodes specifying its coordinate.
-- @param #ASTAR self -- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created. -- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created.
-- @return #ASTAR self -- @return #ASTAR.Node The node.
function ASTAR:AddNodeFromCoordinate(Coordinate) function ASTAR:AddNodeFromCoordinate(Coordinate)
local node=self:GetNodeFromCoordinate(Coordinate) local node=self:GetNodeFromCoordinate(Coordinate)
self:AddNode(node) self:AddNode(node)
return self return node
end end
--- Check if the coordinate of a node has is at a valid surface type. --- Check if the coordinate of a node has is at a valid surface type.
@ -214,8 +307,44 @@ function ASTAR:SetValidNeighbourDistance(MaxDistance)
return self return self
end end
--- Set the function which calculates the "cost" to go from one to another node.
-- The first to arguments of this function are always the two nodes under consideration. But you can add optional arguments.
-- Very often the distance between nodes is a good measure for the cost.
-- @param #ASTAR self
-- @param #function CostFunction Function that returns the "cost".
-- @param ... Condition function arguments if any.
-- @return #ASTAR self
function ASTAR:SetCostFunction(CostFunction, ...)
self.CostFunc=CostFunction
self.CostArg={}
if arg then
self.CostArg=arg
end
return self
end
--- Set heuristic cost to go from one node to another to be their 2D distance.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:SetCostDist2D()
self:SetCostFunction(ASTAR.Dist2D)
return self
end
--- Set heuristic cost to go from one node to another to be their 3D distance.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:SetCostDist3D()
self:SetCostFunction(ASTAR.Dist3D)
return self
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Grid functions -- Grid functions
@ -369,6 +498,26 @@ function ASTAR.DistMax(nodeA, nodeB, distmax)
return dist<=distmax return dist<=distmax
end end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Heuristic cost functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Heuristic cost is given by the 2D distance between the nodes.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #number Distance between the two nodes.
function ASTAR.Dist2D(nodeA, nodeB)
return nodeA.coordinate:Get2DDistance(nodeB)
end
--- Heuristic cost is given by the 3D distance between the nodes.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #number Distance between the two nodes.
function ASTAR.Dist3D(nodeA, nodeB)
return nodeA.coordinate:Get3DDistance(nodeB.coordinate)
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc functions -- Misc functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -458,7 +607,7 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
local g_score, f_score = {}, {} local g_score, f_score = {}, {}
g_score[start]=0 g_score[start]=0
f_score[start]=g_score[start]+self:HeuristicCost(start, goal) f_score[start]=g_score[start]+self:_HeuristicCost(start, goal)
-- Set start time. -- Set start time.
local T0=timer.getAbsTime() local T0=timer.getAbsTime()
@ -470,12 +619,12 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
while #openset > 0 do while #openset > 0 do
local current=self:LowestFscore(openset, f_score) local current=self:_LowestFscore(openset, f_score)
-- Check if we are at the end node. -- Check if we are at the end node.
if current==goal then if current==goal then
local path=self:UnwindPath({}, came_from, goal) local path=self:_UnwindPath({}, came_from, goal)
if not ExcludeEndNode then if not ExcludeEndNode then
table.insert(path, goal) table.insert(path, goal)
@ -493,26 +642,26 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
return path return path
end end
self:RemoveNode(openset, current) self:_RemoveNode(openset, current)
table.insert(closedset, current) table.insert(closedset, current)
local neighbors=self:NeighbourNodes(current, nodes) local neighbors=self:_NeighbourNodes(current, nodes)
-- Loop over neighbours. -- Loop over neighbours.
for _,neighbor in ipairs(neighbors) do for _,neighbor in ipairs(neighbors) do
if self:NotIn(closedset, neighbor) then if self:_NotIn(closedset, neighbor) then
local tentative_g_score=g_score[current]+self:DistNodes(current, neighbor) local tentative_g_score=g_score[current]+self:_DistNodes(current, neighbor)
if self:NotIn(openset, neighbor) or tentative_g_score < g_score[neighbor] then if self:_NotIn(openset, neighbor) or tentative_g_score < g_score[neighbor] then
came_from[neighbor]=current came_from[neighbor]=current
g_score[neighbor]=tentative_g_score g_score[neighbor]=tentative_g_score
f_score[neighbor]=g_score[neighbor]+self:HeuristicCost(neighbor, goal) f_score[neighbor]=g_score[neighbor]+self:_HeuristicCost(neighbor, goal)
if self:NotIn(openset, neighbor) then if self:_NotIn(openset, neighbor) then
table.insert(openset, neighbor) table.insert(openset, neighbor)
end end
@ -533,22 +682,18 @@ end
-- A* pathfinding helper functions -- A* pathfinding helper functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Calculate 2D distance between two nodes. --- Heuristic "cost" function to go from node A to node B. Default is the distance between the nodes.
-- @param #ASTAR self -- @param #ASTAR self
-- @param #ASTAR.Node nodeA Node A. -- @param #ASTAR.Node nodeA Node A.
-- @param #ASTAR.Node nodeB Node B. -- @param #ASTAR.Node nodeB Node B.
-- @return #number Distance between nodes in meters. -- @return #number "Cost" to go from node A to node B.
function ASTAR:DistNodes(nodeA, nodeB) function ASTAR:_HeuristicCost(nodeA, nodeB)
return nodeA.coordinate:Get2DDistance(nodeB.coordinate)
end
--- Heuristic cost function to go from node A to node B. That is simply the distance here. if self.CostFunc then
-- @param #ASTAR self return self.CostFunc(nodeA, nodeB, unpack(self.CostArg))
-- @param #ASTAR.Node nodeA Node A. else
-- @param #ASTAR.Node nodeB Node B. return self:_DistNodes(nodeA, nodeB)
-- @return #number Distance between nodes in meters. end
function ASTAR:HeuristicCost(nodeA, nodeB)
return self:DistNodes(nodeA, nodeB)
end end
--- Check if going from a node to a neighbour is possible. --- Check if going from a node to a neighbour is possible.
@ -556,7 +701,7 @@ end
-- @param #ASTAR.Node node A node. -- @param #ASTAR.Node node A node.
-- @param #ASTAR.Node neighbor Neighbour node. -- @param #ASTAR.Node neighbor Neighbour node.
-- @return #boolean If true, transition between nodes is possible. -- @return #boolean If true, transition between nodes is possible.
function ASTAR:IsValidNeighbour(node, neighbor) function ASTAR:_IsValidNeighbour(node, neighbor)
if self.ValidNeighbourFunc then if self.ValidNeighbourFunc then
@ -568,9 +713,21 @@ function ASTAR:IsValidNeighbour(node, neighbor)
end end
--- Function --- Calculate 2D distance between two nodes.
-- @param #ASTAR self -- @param #ASTAR self
function ASTAR:LowestFscore(set, f_score) -- @param #ASTAR.Node nodeA Node A.
-- @param #ASTAR.Node nodeB Node B.
-- @return #number Distance between nodes in meters.
function ASTAR:_DistNodes(nodeA, nodeB)
return nodeA.coordinate:Get2DDistance(nodeB.coordinate)
end
--- Function that calculates the lowest F score.
-- @param #ASTAR self
-- @param #table set The set of nodes.
-- @param #number f_score F score.
-- @return #ASTAR.Node Best node.
function ASTAR:_LowestFscore(set, f_score)
local lowest, bestNode = ASTAR.INF, nil local lowest, bestNode = ASTAR.INF, nil
@ -591,14 +748,14 @@ end
-- @param #ASTAR.Node theNode The node. -- @param #ASTAR.Node theNode The node.
-- @param #table nodes Possible neighbours. -- @param #table nodes Possible neighbours.
-- @param #table Valid neighbour nodes. -- @param #table Valid neighbour nodes.
function ASTAR:NeighbourNodes(theNode, nodes) function ASTAR:_NeighbourNodes(theNode, nodes)
local neighbors = {} local neighbors = {}
for _, node in ipairs ( nodes ) do for _, node in ipairs ( nodes ) do
if theNode~=node then if theNode~=node then
local isvalid=self:IsValidNeighbour(theNode, node) local isvalid=self:_IsValidNeighbour(theNode, node)
if isvalid then if isvalid then
table.insert(neighbors, node) table.insert(neighbors, node)
@ -616,7 +773,7 @@ end
-- @param #table set Set of nodes. -- @param #table set Set of nodes.
-- @param #ASTAR.Node theNode The node to check. -- @param #ASTAR.Node theNode The node to check.
-- @return #boolean If true, the node is not in the set. -- @return #boolean If true, the node is not in the set.
function ASTAR:NotIn(set, theNode) function ASTAR:_NotIn(set, theNode)
for _, node in ipairs ( set ) do for _, node in ipairs ( set ) do
if node == theNode then if node == theNode then
@ -631,7 +788,7 @@ end
-- @param #ASTAR self -- @param #ASTAR self
-- @param #table set Set of nodes. -- @param #table set Set of nodes.
-- @param #ASTAR.Node theNode The node to check. -- @param #ASTAR.Node theNode The node to check.
function ASTAR:RemoveNode(set, theNode) function ASTAR:_RemoveNode(set, theNode)
for i, node in ipairs ( set ) do for i, node in ipairs ( set ) do
if node == theNode then if node == theNode then
@ -649,11 +806,11 @@ end
-- @param #table map Map. -- @param #table map Map.
-- @param #ASTAR.Node current_node The current node. -- @param #ASTAR.Node current_node The current node.
-- @return #table Unwinded path. -- @return #table Unwinded path.
function ASTAR:UnwindPath( flat_path, map, current_node ) function ASTAR:_UnwindPath( flat_path, map, current_node )
if map [ current_node ] then if map [ current_node ] then
table.insert ( flat_path, 1, map [ current_node ] ) table.insert ( flat_path, 1, map [ current_node ] )
return self:UnwindPath ( flat_path, map, map [ current_node ] ) return self:_UnwindPath ( flat_path, map, map [ current_node ] )
else else
return flat_path return flat_path
end end

View File

@ -175,10 +175,6 @@ do -- COORDINATE
-- In order to use the most optimal road system to transport vehicles, the method @{#COORDINATE.GetPathOnRoad}() will calculate -- In order to use the most optimal road system to transport vehicles, the method @{#COORDINATE.GetPathOnRoad}() will calculate
-- the most optimal path following the road between two coordinates. -- the most optimal path following the road between two coordinates.
-- --
--
--
--
--
-- ## 8) Metric or imperial system -- ## 8) Metric or imperial system
-- --
-- * @{#COORDINATE.IsMetric}(): Returns if the 3D point is Metric or Nautical Miles. -- * @{#COORDINATE.IsMetric}(): Returns if the 3D point is Metric or Nautical Miles.
@ -204,23 +200,23 @@ do -- COORDINATE
--- @field COORDINATE.WaypointAction --- @field COORDINATE.WaypointAction
COORDINATE.WaypointAction = { COORDINATE.WaypointAction = {
TurningPoint = "Turning Point", TurningPoint = "Turning Point",
FlyoverPoint = "Fly Over Point", FlyoverPoint = "Fly Over Point",
FromParkingArea = "From Parking Area", FromParkingArea = "From Parking Area",
FromParkingAreaHot = "From Parking Area Hot", FromParkingAreaHot = "From Parking Area Hot",
FromRunway = "From Runway", FromRunway = "From Runway",
Landing = "Landing", Landing = "Landing",
LandingReFuAr = "LandingReFuAr", LandingReFuAr = "LandingReFuAr",
} }
--- @field COORDINATE.WaypointType --- @field COORDINATE.WaypointType
COORDINATE.WaypointType = { COORDINATE.WaypointType = {
TakeOffParking = "TakeOffParking", TakeOffParking = "TakeOffParking",
TakeOffParkingHot = "TakeOffParkingHot", TakeOffParkingHot = "TakeOffParkingHot",
TakeOff = "TakeOffParkingHot", TakeOff = "TakeOffParkingHot",
TurningPoint = "Turning Point", TurningPoint = "Turning Point",
Land = "Land", Land = "Land",
LandingReFuAr = "LandingReFuAr", LandingReFuAr = "LandingReFuAr",
} }
@ -1290,8 +1286,10 @@ do -- COORDINATE
RoutePoint.x = self.x RoutePoint.x = self.x
RoutePoint.y = self.z RoutePoint.y = self.z
RoutePoint.alt = self:GetLandHeight()+1 -- self.y RoutePoint.alt = self:GetLandHeight()+1
RoutePoint.alt_type = COORDINATE.WaypointAltType.BARO RoutePoint.alt_type = COORDINATE.WaypointAltType.BARO
RoutePoint.type = "Turning Point"
RoutePoint.action = Formation or "Off Road" RoutePoint.action = Formation or "Off Road"
RoutePoint.formation_template="" RoutePoint.formation_template=""
@ -1921,7 +1919,7 @@ do -- COORDINATE
--- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate. --- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param #COORDINATE ToCoordinate -- @param #COORDINATE ToCoordinate
-- @param #number OFfset Height offset in meters. Default 2 m. -- @param #number Offset Height offset in meters. Default 2 m.
-- @return #boolean true If the ToCoordinate has LOS with the Coordinate, otherwise false. -- @return #boolean true If the ToCoordinate has LOS with the Coordinate, otherwise false.
function COORDINATE:IsLOS( ToCoordinate, Offset ) function COORDINATE:IsLOS( ToCoordinate, Offset )

View File

@ -16,7 +16,7 @@
-- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. -- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached.
-- @extends Ops.OpsGroup#OPSGROUP -- @extends Ops.OpsGroup#OPSGROUP
--- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson --- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge
-- --
-- === -- ===
-- --
@ -64,8 +64,9 @@ function ARMYGROUP:New(GroupName)
-- Defaults -- Defaults
self:SetDefaultROE() self:SetDefaultROE()
self:SetDefaultAlarmstate()
self:SetDetection() self:SetDetection()
self:SetPatrolAdInfinitum(true) self:SetPatrolAdInfinitum(false)
-- Add FSM transitions. -- Add FSM transitions.
-- From State --> Event --> To State -- From State --> Event --> To State
@ -134,7 +135,7 @@ function ARMYGROUP:SetPatrolAdInfinitum(switch)
return self return self
end end
--- Group patrols ad inifintum. If the last waypoint is reached, it will go to waypoint one and repeat its route. --- Set default cruise speed. This is the speed a group will take by default if no speed is specified explicitly.
-- @param #ARMYGROUP self -- @param #ARMYGROUP self
-- @param #number Speed Speed in knots. Default 70% of max speed. -- @param #number Speed Speed in knots. Default 70% of max speed.
-- @return #ARMYGROUP self -- @return #ARMYGROUP self
@ -225,8 +226,8 @@ function ARMYGROUP:onafterStatus(From, Event, To)
local nMissions=self:CountRemainingMissison() local nMissions=self:CountRemainingMissison()
-- Info text. -- Info text.
local text=string.format("State %s: Wp=%d/%d Speed=%.1f Heading=%03d Tasks=%d Missions=%d", local text=string.format("State %s: Wp=%d/%d-->%d Speed=%.1f (%d) Heading=%03d ROE=%d Alarm=%d Formation=%s Tasks=%d Missions=%d",
fsmstate, self.currentwp, #self.waypoints, speed, hdg, nTaskTot, nMissions) fsmstate, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), speed, UTILS.MpsToKnots(self.speed), hdg, self.roe, self.alarmstate, self.formation, nTaskTot, nMissions)
self:I(self.lid..text) self:I(self.lid..text)
else else
@ -338,9 +339,12 @@ function ARMYGROUP:onafterSpawned(From, Event, To)
if self.ai then if self.ai then
-- Set default ROE and ROT options. -- Set default ROE option.
self:SetOptionROE(self.roe) self:SetOptionROE(self.roe)
-- Set default Alarm State option.
self:SetOptionAlarmstate(self.alarmstate)
end end
-- Update route. -- Update route.
@ -355,38 +359,33 @@ end
-- @param #string To To state. -- @param #string To To state.
-- @param #number n Waypoint number. Default is next waypoint. -- @param #number n Waypoint number. Default is next waypoint.
-- @param #number Speed Speed in knots. Default cruise speed. -- @param #number Speed Speed in knots. Default cruise speed.
-- @param #number Depth Depth in meters. Default 0 meters. -- @param #number Formation Formation of the group.
function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation)
-- Update route from this waypoint number onwards. -- Update route from this waypoint number onwards.
n=n or self:GetWaypointIndexNext(self.adinfinitum) n=n or self:GetWaypointIndexNext(self.adinfinitum)
-- Debug info. -- Debug info.
self:T(self.lid..string.format("FF Update route n=%d", n)) self:I(self.lid..string.format("FF Update route n=%d", n))
-- Update waypoint tasks, i.e. inject WP tasks into waypoint table. -- Update waypoint tasks, i.e. inject WP tasks into waypoint table.
self:_UpdateWaypointTasks(n) self:_UpdateWaypointTasks(n)
-- Waypoints. -- Waypoints.
local waypoints={} local waypoints={}
-- Depth for submarines.
local depth=Depth or 0
-- Get current speed in km/h.
local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH()
-- Current waypoint.
local current=self:GetCoordinate():WaypointNaval(speed, depth)
table.insert(waypoints, current)
-- Add remaining waypoints to route. -- Add remaining waypoints to route.
for i=n, #self.waypoints do for i=n, #self.waypoints do
local wp=self.waypoints[i]
-- Copy waypoint.
local wp=UTILS.DeepCopy(self.waypoints[i]) --Ops.OpsGroup#OPSGROUP.Waypoint
-- Set speed.
if i==n then if i==n then
---
-- Next Waypoint
---
if Speed then if Speed then
wp.speed=UTILS.KnotsToMps(Speed) wp.speed=UTILS.KnotsToMps(Speed)
elseif self.speedCruise then elseif self.speedCruise then
@ -394,8 +393,22 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth)
else else
-- Take default waypoint speed. -- Take default waypoint speed.
end end
if Formation then
wp.action=Formation
end
-- Current set formation.
self.formation=wp.action
-- Current set speed in m/s.
self.speed=wp.speed
else else
---
-- Later Waypoint(s)
---
if self.speedCruise then if self.speedCruise then
wp.speed=UTILS.KmphToMps(self.speedCruise) wp.speed=UTILS.KmphToMps(self.speedCruise)
@ -405,17 +418,23 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth)
end end
-- Set depth. -- Debug info.
wp.alt=-depth --Depth and -Depth or wp.alt self:I(string.format("WP %d %s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.type, wp.speed, wp.alt, wp.action))
-- Add waypoint. -- Add waypoint.
table.insert(waypoints, wp) table.insert(waypoints, wp)
end end
-- Current waypoint.
local current=self:GetCoordinate():WaypointGround(UTILS.MpsToKmph(self.speed), self.formation)
table.insert(waypoints, 1, current)
table.insert(waypoints, 1, current) -- Seems to be better to add this twice. Otherwise, the passing waypoint functions is triggered to early!
if #waypoints>2 then
if #waypoints>1 then self:I(self.lid..string.format("Updateing route: WP %d-->%d-->%d (#%d), Speed=%.1f knots, Formation=%s",
self.currentwp, n, #self.waypoints, #waypoints-2, UTILS.MpsToKnots(self.speed), tostring(self.formation)))
self:I(self.lid..string.format("Updateing route: WP=%d, Speed=%.1f knots, depth=%d meters", #self.waypoints-n+1, UTILS.KmphToKnots(speed), depth))
-- Route group to all defined waypoints remaining. -- Route group to all defined waypoints remaining.
self:Route(waypoints) self:Route(waypoints)
@ -428,11 +447,6 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth)
self:I(self.lid..string.format("No waypoints left")) self:I(self.lid..string.format("No waypoints left"))
if #self.waypoints>1 then
self:I(self.lid..string.format("Resuming route at first waypoint"))
self:__UpdateRoute(-1, 1, nil, self.depth)
end
end end
end end
@ -444,27 +458,24 @@ end
-- @param #string To To state. -- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate Coordinate where to go. -- @param Core.Point#COORDINATE Coordinate Coordinate where to go.
-- @param #number Speed Speed in knots. Default cruise speed. -- @param #number Speed Speed in knots. Default cruise speed.
-- @param #number Depth Depth in meters. Default 0 meters. -- @param #number Formation Formation the group will use.
-- @param #number ResumeRoute If true, resume route after detour point was reached. -- @param #number ResumeRoute If true, resume route after detour point was reached.
function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Depth, ResumeRoute) function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Formation, ResumeRoute)
-- Waypoints. -- Waypoints.
local waypoints={} local waypoints={}
-- Depth for submarines.
local depth=Depth or 0
-- Get current speed in km/h. -- Get current speed in km/h.
local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH() local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH()
-- Current waypoint. -- Current waypoint.
local current=self:GetCoordinate():WaypointGround(Speed, Formation, DCSTasks) local current=self:GetCoordinate():WaypointGround(Speed, Formation)
table.insert(waypoints, current) table.insert(waypoints, current)
-- At each waypoint report passing. -- At each waypoint report passing.
local Task=self.group:TaskFunction("ARMYGROUP._DetourReached", self, ResumeRoute) local Task=self.group:TaskFunction("ARMYGROUP._DetourReached", self, ResumeRoute)
local detour=Coordinate:WaypointNaval(speed, depth, {Task}) local detour=Coordinate:WaypointGround(Speed, Formation, {Task})
table.insert(waypoints, detour) table.insert(waypoints, detour)
self:Route(waypoints) self:Route(waypoints)
@ -482,21 +493,21 @@ end
--- Function called when a group is passing a waypoint. --- Function called when a group is passing a waypoint.
--@param Wrapper.Group#GROUP group Group that passed the waypoint --@param Wrapper.Group#GROUP group Group that passed the waypoint
--@param #ARMYGROUP navygroup Navy group object. --@param #ARMYGROUP armygroup Army group object.
--@param #boolean resume Resume route. --@param #boolean resume Resume route.
function ARMYGROUP._DetourReached(group, navygroup, resume) function ARMYGROUP._DetourReached(group, armygroup, resume)
-- Debug message. -- Debug message.
local text=string.format("Group reached detour coordinate") local text=string.format("Group reached detour coordinate")
navygroup:I(navygroup.lid..text) armygroup:I(armygroup.lid..text)
if resume then if resume then
local indx=navygroup:GetWaypointIndexNext(true) local indx=armygroup:GetWaypointIndexNext(true)
local speed=navygroup:GetSpeedToWaypoint(indx) local speed=armygroup:GetSpeedToWaypoint(indx)
navygroup:UpdateRoute(indx, speed, navygroup.depth) armygroup:__UpdateRoute(-1, indx, speed, armygroup.formation)
end end
navygroup:DetourReached() armygroup:DetourReached()
end end
@ -511,7 +522,7 @@ function ARMYGROUP:onafterFullStop(From, Event, To)
local pos=self:GetCoordinate() local pos=self:GetCoordinate()
-- Create a new waypoint. -- Create a new waypoint.
local wp=pos:WaypointNaval(0) local wp=pos:WaypointGround(0)
-- Create new route consisting of only this position ==> Stop! -- Create new route consisting of only this position ==> Stop!
self:Route({wp}) self:Route({wp})
@ -524,9 +535,10 @@ end
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
-- @param #number Speed Speed in knots. -- @param #number Speed Speed in knots.
function ARMYGROUP:onafterCruise(From, Event, To, Speed) -- @param #number Formation Formation.
function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation)
self:UpdateRoute(nil, Speed, self.depth) self:__UpdateRoute(-1, nil, Speed, Formation)
end end
@ -675,9 +687,10 @@ end
-- @param Core.Point#COORDINATE coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. -- @param Core.Point#COORDINATE coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude.
-- @param #number speed Speed in knots. Default is default cruise speed or 70% of max speed. -- @param #number speed Speed in knots. Default is default cruise speed or 70% of max speed.
-- @param #number wpnumber Waypoint number. Default at the end. -- @param #number wpnumber Waypoint number. Default at the end.
-- @param #number formation Formation the group will use.
-- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call. -- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call.
-- @return #number Waypoint index. -- @return #number Waypoint index.
function ARMYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) function ARMYGROUP:AddWaypoint(coordinate, speed, wpnumber, formation, updateroute)
-- Waypoint number. Default is at the end. -- Waypoint number. Default is at the end.
wpnumber=wpnumber or #self.waypoints+1 wpnumber=wpnumber or #self.waypoints+1
@ -693,13 +706,16 @@ function ARMYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute)
local speedkmh=UTILS.KnotsToKmph(speed) local speedkmh=UTILS.KnotsToKmph(speed)
-- Create a Naval waypoint. -- Create a Naval waypoint.
local wp=coordinate:WaypointNaval(speedkmh) local wp=coordinate:WaypointGround(speedkmh, formation)
-- Add to table. -- Create waypoint data table.
table.insert(self.waypoints, wpnumber, wp) local waypoint=self:_CreateWaypoint(wp)
-- Add waypoint to table.
self:_AddWaypoint(waypoint, wpnumber)
-- Debug info. -- Debug info.
self:T(self.lid..string.format("Adding NAVAL waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints)) self:T(self.lid..string.format("Adding GROUND waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints))
-- Update route. -- Update route.
@ -735,7 +751,7 @@ function ARMYGROUP:_InitGroup()
-- Is (template) group late activated. -- Is (template) group late activated.
self.isLateActivated=self.template.lateActivation self.isLateActivated=self.template.lateActivation
-- Naval groups cannot be uncontrolled. -- Ground groups cannot be uncontrolled.
self.isUncontrolled=false self.isUncontrolled=false
-- Max speed in km/h. -- Max speed in km/h.
@ -766,6 +782,7 @@ function ARMYGROUP:_InitGroup()
end end
end end
-- Units of the group.
local units=self.group:GetUnits() local units=self.group:GetUnits()
for _,_unit in pairs(units) do for _,_unit in pairs(units) do
@ -847,13 +864,13 @@ function ARMYGROUP:_CheckGroupDone(delay)
local speed=self:GetSpeedToWaypoint(1) local speed=self:GetSpeedToWaypoint(1)
-- Start route at first waypoint. -- Start route at first waypoint.
self:__UpdateRoute(-1, 1, speed, self.depth) self:__UpdateRoute(-1, 1, speed, self.formation)
end end
else else
self:UpdateRoute(nil, nil, self.depth) self:UpdateRoute(nil, nil, self.formation)
end end

View File

@ -2537,6 +2537,8 @@ function FLIGHTGROUP:_InitGroup()
self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC")
end end
-- Switch to default formation.
-- TODO: Should this be moved to onafterspawned?
self:SwitchFormation(self.formationDefault) self:SwitchFormation(self.formationDefault)
-- Add elemets. -- Add elemets.

View File

@ -20,6 +20,7 @@
-- @field #NAVYGROUP.IntoWind intowind Into wind info. -- @field #NAVYGROUP.IntoWind intowind Into wind info.
-- @field #table Qintowind Queue of "into wind" turns. -- @field #table Qintowind Queue of "into wind" turns.
-- @field #number depth Ordered depth in meters. -- @field #number depth Ordered depth in meters.
-- @field #boolean collisionwarning If true, collition warning.
-- @extends Ops.OpsGroup#OPSGROUP -- @extends Ops.OpsGroup#OPSGROUP
--- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson --- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson
@ -54,6 +55,7 @@ NAVYGROUP = {
-- @field #number Speed Speed in knots. -- @field #number Speed Speed in knots.
-- @field #number Offset Offset angle in degrees. -- @field #number Offset Offset angle in degrees.
-- @field #number Id Unique ID of the turn. -- @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 Core.Point#COORDINATE Coordinate Coordinate where we left the route.
-- @field #number Heading Heading the boat will take in degrees. -- @field #number Heading Heading the boat will take in degrees.
-- @field #boolean Open Currently active. -- @field #boolean Open Currently active.
@ -99,8 +101,8 @@ function NAVYGROUP:New(GroupName)
self:AddTransition("*", "FullStop", "Holding") -- Hold position. self:AddTransition("*", "FullStop", "Holding") -- Hold position.
self:AddTransition("*", "Cruise", "Cruising") -- Hold position. self:AddTransition("*", "Cruise", "Cruising") -- Hold position.
self:AddTransition("*", "TurnIntoWind", "*") -- Command the group to turn into the wind. self:AddTransition("*", "TurnIntoWind", "IntoWind") -- Command the group to turn into the wind.
self:AddTransition("*", "TurnIntoWindOver", "*") -- Turn into wind is over. self:AddTransition("*", "TurnIntoWindOver", "Cruising") -- Turn into wind is over.
self:AddTransition("*", "TurningStarted", "*") -- Group started turning. self:AddTransition("*", "TurningStarted", "*") -- Group started turning.
self:AddTransition("*", "TurningStopped", "*") -- Group stopped turning. self:AddTransition("*", "TurningStopped", "*") -- Group stopped turning.
@ -108,6 +110,8 @@ function NAVYGROUP:New(GroupName)
self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards. self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards.
self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate.
self:AddTransition("*", "CollitionWarning", "*") -- Collision warning.
self:AddTransition("*", "Dive", "Diving") -- Command a submarine to dive. self:AddTransition("*", "Dive", "Diving") -- Command a submarine to dive.
self:AddTransition("Diving", "Surface", "Cruising") -- Command a submarine to go to the surface. self:AddTransition("Diving", "Surface", "Cruising") -- Command a submarine to go to the surface.
@ -170,7 +174,7 @@ function NAVYGROUP:SetPatrolAdInfinitum(switch)
return self return self
end end
--- Group patrols ad inifintum. If the last waypoint is reached, it will go to waypoint one and repeat its route. --- Set default cruise speed. This is the speed a group will take by default if no speed is specified explicitly.
-- @param #NAVYGROUP self -- @param #NAVYGROUP self
-- @param #number Speed Speed in knots. Default 70% of max speed. -- @param #number Speed Speed in knots. Default 70% of max speed.
-- @return #NAVYGROUP self -- @return #NAVYGROUP self
@ -290,6 +294,36 @@ function NAVYGROUP:AddTurnIntoWind(starttime, stoptime, speed, uturn, offset)
return recovery return recovery
end 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. --- Check if the group is currently turning.
-- @param #NAVYGROUP self -- @param #NAVYGROUP self
-- @return #boolean If true, group is currently turning. -- @return #boolean If true, group is currently turning.
@ -354,28 +388,41 @@ function NAVYGROUP:onafterStatus(From, Event, To)
-- Check if group started or stopped turning. -- Check if group started or stopped turning.
self:_CheckTurning() self:_CheckTurning()
-- Check water is ahead. local freepath=10000
local collision=self:_CheckCollisionCoord(pos:Translate(self.collisiondist or 5000, hdg)) local collision=false
self:_CheckTurnsIntoWind() if not self:IsTurning() then
if self.intowind then
if timer.getAbsTime()>=self.intowind.Tstop then if not self.ispathfinding then
self:TurnIntoWindOver() freepath=self:_CheckFreePath(freepath, 100)
if freepath<5000 then
self.ispathfinding=self:_FindPathToNextWaypoint()
end
end end
-- Check water is ahead.
--collision=self:_CheckCollisionCoord(pos:Translate(freepath+100, hdg))
end end
-- Check into wind queue.
self:_CheckTurnsIntoWind()
-- Check if group got stuck.
self:_CheckStuck()
-- Get number of tasks and missions. -- Get number of tasks and missions.
local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks()
local nMissions=self:CountRemainingMissison() local nMissions=self:CountRemainingMissison()
local intowind=self:IsSteamingIntoWind() and UTILS.SecondsToClock(self.intowind.Tstop-timer.getAbsTime(), true) or "N/A"
-- Info text. -- Info text.
local text=string.format("State %s: Wp=%d/%d Speed=%.1f Heading=%03d intowind=%s turning=%s collision=%s Tasks=%d Missions=%d", local text=string.format("%s (T=%d,M=%d): Wp=%d-->%d (of %d) Speed=%.1f (%.1f) Depth=%.1f Hdg=%03d Turn=%s Collision=%d IntoWind=%s ROE=%d AS=%d",
fsmstate, self.currentwp, #self.waypoints, speed, hdg, tostring(self:IsSteamingIntoWind()), tostring(self:IsTurning()), tostring(collision), nTaskTot, nMissions) fsmstate, nTaskTot, nMissions, self.currentwp, self:GetWaypointIndexNext(), #self.waypoints, speed, UTILS.MpsToKnots(self.speed or 0), pos.y, hdg, tostring(self:IsTurning()), freepath, intowind, 0, 0)
self:I(self.lid..text) self:I(self.lid..text)
else else
@ -444,7 +491,7 @@ function NAVYGROUP:onafterStatus(From, Event, To)
end end
-- Next status update in 10 seconds.
self:__Status(-10) self:__Status(-10)
end end
@ -510,8 +557,8 @@ end
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
-- @param #number n Waypoint number. Default is next waypoint. -- @param #number n Waypoint number. Default is next waypoint.
-- @param #number Speed Speed in knots. Default cruise speed. -- @param #number Speed Speed in knots to the next waypoint.
-- @param #number Depth Depth in meters. Default 0 meters. -- @param #number Depth Depth in meters to the next waypoint.
function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth)
-- Update route from this waypoint number onwards. -- Update route from this waypoint number onwards.
@ -526,52 +573,63 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth)
-- Waypoints. -- Waypoints.
local waypoints={} local waypoints={}
-- Depth for submarines.
local depth=Depth or 0
-- Get current speed in km/h.
local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH()
-- Current waypoint.
local current=self:GetCoordinate():WaypointNaval(speed, depth)
table.insert(waypoints, current)
-- Add remaining waypoints to route. -- Add remaining waypoints to route.
local depth=nil
for i=n, #self.waypoints do for i=n, #self.waypoints do
-- Waypoint.
local wp=self.waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint local wp=self.waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint
-- Set speed. -- Check if next wp.
if i==n then if i==n then
-- Speed.
if Speed then if Speed then
-- Take speed specified.
wp.speed=UTILS.KnotsToMps(Speed) wp.speed=UTILS.KnotsToMps(Speed)
elseif self.speedCruise then
wp.speed=UTILS.KmphToMps(self.speedCruise)
else
-- Take default waypoint speed.
end
else
if self.speedCruise then
wp.speed=UTILS.KmphToMps(self.speedCruise)
else else
-- Take default waypoint speed. -- Take default waypoint speed.
end end
if Depth then
wp.alt=-Depth
elseif self.depth then
wp.alt=-self.depth
else
-- Take default waypoint alt.
end
-- Current set speed in m/s.
self.speed=wp.speed
-- Current set depth.
depth=wp.alt
else
-- Dive depth is applied to all other waypoints.
if self.depth then
wp.alt=-self.depth
else
-- Take default waypoint depth.
end
end end
-- Set depth.
wp.alt=-depth --Depth and -Depth or wp.alt
-- Add waypoint. -- Add waypoint.
table.insert(waypoints, wp) table.insert(waypoints, wp)
end end
-- Current waypoint.
local current=self:GetCoordinate():WaypointNaval(UTILS.MpsToKmph(self.speed), depth)
table.insert(waypoints, 1, current)
if #waypoints>1 then if #waypoints>1 then
self:I(self.lid..string.format("Updateing route: WP=%d, Speed=%.1f knots, depth=%d meters", #self.waypoints-n+1, UTILS.KmphToKnots(speed), depth)) self:I(self.lid..string.format("Updateing route: WP %d-->%d-->%d (#%d), Speed=%.1f knots, Depth=%d m",
self.currentwp, n, #self.waypoints, #waypoints-1, UTILS.MpsToKnots(self.speed), depth))
-- Route group to all defined waypoints remaining. -- Route group to all defined waypoints remaining.
self:Route(waypoints) self:Route(waypoints)
@ -584,11 +642,6 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth)
self:I(self.lid..string.format("No waypoints left")) self:I(self.lid..string.format("No waypoints left"))
if #self.waypoints>1 then
self:I(self.lid..string.format("Resuming route at first waypoint"))
self:__UpdateRoute(-1, 1, nil, self.depth)
end
end end
end end
@ -601,7 +654,7 @@ end
-- @param Core.Point#COORDINATE Coordinate Coordinate where to go. -- @param Core.Point#COORDINATE Coordinate Coordinate where to go.
-- @param #number Speed Speed in knots. Default cruise speed. -- @param #number Speed Speed in knots. Default cruise speed.
-- @param #number Depth Depth in meters. Default 0 meters. -- @param #number Depth Depth in meters. Default 0 meters.
-- @param #number ResumeRoute If true, resume route after detour point was reached. -- @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) function NAVYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Depth, ResumeRoute)
-- Waypoints. -- Waypoints.
@ -610,20 +663,18 @@ function NAVYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Depth, Resu
-- Depth for submarines. -- Depth for submarines.
local depth=Depth or 0 local depth=Depth or 0
-- Get current speed in km/h. -- Speed in knots.
local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH() Speed=Speed or self:GetSpeedCruise()
-- Current waypoint. local wpindx=self.currentwp+1
local current=self:GetCoordinate():WaypointNaval(speed, depth)
table.insert(waypoints, current)
-- At each waypoint report passing. local wp=self:AddWaypoint(Coordinate, Speed, wpindx, true)
local Task=self.group:TaskFunction("NAVYGROUP._DetourReached", self, ResumeRoute)
local detour=Coordinate:WaypointNaval(speed, depth, {Task}) if ResumeRoute then
table.insert(waypoints, detour) wp.detour=1
else
self:Route(waypoints) wp.detour=0
end
end end
@ -662,9 +713,6 @@ end
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
-- @param #NAVYGROUP.IntoWind Into wind parameters. -- @param #NAVYGROUP.IntoWind Into wind parameters.
-- @param #number Duration Duration in seconds.
-- @param #number Speed Speed in knots.
-- @param #boolean Uturn Return to the place we came from.
function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind)
IntoWind.Heading=self:GetHeadingIntoWind(IntoWind.Offset) IntoWind.Heading=self:GetHeadingIntoWind(IntoWind.Offset)
@ -681,7 +729,7 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind)
-- Convert to knots. -- Convert to knots.
vwind=UTILS.MpsToKnots(vwind) vwind=UTILS.MpsToKnots(vwind)
-- Speed of carrier in m/s but at least 2 knots. -- Speed of carrier relative to wind but at least 2 knots.
local speed=math.max(IntoWind.Speed-vwind, 2) local speed=math.max(IntoWind.Speed-vwind, 2)
-- Debug info. -- Debug info.
@ -689,16 +737,28 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind)
local distance=UTILS.NMToMeters(1000) local distance=UTILS.NMToMeters(1000)
local wp={}
local coord=self:GetCoordinate() local coord=self:GetCoordinate()
local Coord=coord:Translate(distance, IntoWind.Heading) local Coord=coord:Translate(distance, IntoWind.Heading)
local wpindex=self.currentwp+1 --self:GetWaypointIndexNext(false)
local wptiw=self:AddWaypoint(Coord, speed, wpindex, true)
wptiw.intowind=true
IntoWind.waypoint=wptiw
if IntoWind.Uturn then
IntoWind.Coordinate:MarkToAll("Return coord")
end
--[[
local wp={}
wp[1]=coord:WaypointNaval(UTILS.KnotsToKmph(speed)) wp[1]=coord:WaypointNaval(UTILS.KnotsToKmph(speed))
wp[2]=Coord:WaypointNaval(UTILS.KnotsToKmph(speed)) wp[2]=Coord:WaypointNaval(UTILS.KnotsToKmph(speed))
self:Route(wp) self:Route(wp)
]]
end end
--- On after "TurnIntoWindOver" event. --- On after "TurnIntoWindOver" event.
@ -711,15 +771,22 @@ end
-- @param #boolean Uturn Return to the place we came from. -- @param #boolean Uturn Return to the place we came from.
function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To) function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To)
env.info("FF Turn Into Wind Over!")
self.intowind.Over=true self.intowind.Over=true
self.intowind.Open=false self.intowind.Open=false
-- Remove additional waypoint.
self:RemoveWaypointByID(self.intowind.waypoint.uid)
if self.intowind.Uturn then if self.intowind.Uturn then
env.info("FF Turn Into Wind Over Uturn!")
self:Detour(self.intowind.Coordinate, self:GetSpeedCruise(), 0, true) self:Detour(self.intowind.Coordinate, self:GetSpeedCruise(), 0, true)
else else
local indx=self:GetWaypointIndexNext(self.adinfinitum) env.info("FF Turn Into Wind Over Next WP!")
local indx=self:GetWaypointIndexNext()
local speed=self:GetWaypointSpeed(indx) local speed=self:GetWaypointSpeed(indx)
self:UpdateRoute(indx, speed, self.depth) self:__UpdateRoute(-1, indx, speed)
end end
self.intowind=nil self.intowind=nil
@ -749,10 +816,13 @@ end
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
-- @param #number Speed Speed in knots. -- @param #number Speed Speed in knots until next waypoint is reached.
function NAVYGROUP:onafterCruise(From, Event, To, Speed) function NAVYGROUP:onafterCruise(From, Event, To, Speed)
self:UpdateRoute(nil, Speed, self.depth) --
self.depth=nil
self:__UpdateRoute(-1, nil, Speed)
end end
@ -762,7 +832,8 @@ end
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
-- @param #number Depth Dive depth in meters. Default 50 meters. -- @param #number Depth Dive depth in meters. Default 50 meters.
function NAVYGROUP:onafterDive(From, Event, To, Depth) -- @param #number Speed Speed in knots until next waypoint is reached.
function NAVYGROUP:onafterDive(From, Event, To, Depth, Speed)
Depth=Depth or 50 Depth=Depth or 50
@ -770,7 +841,7 @@ function NAVYGROUP:onafterDive(From, Event, To, Depth)
self.depth=Depth self.depth=Depth
self:UpdateRoute(nil, nil, self.depth) self:__UpdateRoute(-1, nil, Speed)
end end
@ -779,11 +850,12 @@ end
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
function NAVYGROUP:onafterSurface(From, Event, To) -- @param #number Speed Speed in knots until next waypoint is reached.
function NAVYGROUP:onafterSurface(From, Event, To, Speed)
self.depth=0 self.depth=0
self:UpdateRoute(nil, nil, self.depth) self:__UpdateRoute(-1, nil, Speed)
end end
@ -959,7 +1031,7 @@ function NAVYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute)
self:_AddWaypoint(waypoint, wpnumber) self:_AddWaypoint(waypoint, wpnumber)
-- Debug info. -- Debug info.
self:T(self.lid..string.format("Adding NAVAL waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints)) self:I(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. -- Update route.
if updateroute==nil or updateroute==true then if updateroute==nil or updateroute==true then
@ -1089,6 +1161,73 @@ end
-- Misc Functions -- Misc Functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check for possible collisions between two coordinates.
-- @param #NAVYGROUP self
-- @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
-- Current coordinate.
local coordinate=self:GetCoordinate():SetAltitude(0, true)
-- Current heading.
local heading=self:GetHeading()
local function LoS(dist)
local checkcoord=coordinate:Translate(dist, heading, true)
return coordinate:IsLOS(checkcoord, 0.001)
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)
env.info(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
return check()
end
--- Check for possible collisions between two coordinates. --- Check for possible collisions between two coordinates.
-- @param #NAVYGROUP self -- @param #NAVYGROUP self
-- @param Core.Point#COORDINATE coordto Coordinate to which the collision is check. -- @param Core.Point#COORDINATE coordto Coordinate to which the collision is check.
@ -1197,6 +1336,32 @@ function NAVYGROUP:_CheckTurning()
end end
--- Check if group got stuck.
-- @param #NAVYGROUP self
function NAVYGROUP:_CheckStuck()
if self:IsHolding() then
return
end
local holdtime=0
if self.holdtimestamp then
holdtime=timer.getTime()-self.holdtimestamp
end
local ExpectedSpeed=self:GetExpectedSpeed()
local speed=self.group:GetVelocityMPS()
if speed<0.5 and ExpectedSpeed>0 then
if not self.holdtimestamp then
self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f expected=%.1f knots", speed, ExpectedSpeed))
self.holdtimestamp=timer.getTime()
end
end
end
--- Check if group is done, i.e. --- Check if group is done, i.e.
-- --
-- * passed the final waypoint, -- * passed the final waypoint,
@ -1284,6 +1449,14 @@ function NAVYGROUP:_CheckTurnsIntoWind()
end end
end end
-- If into wind, check if over.
if self.intowind then
if timer.getAbsTime()>=self.intowind.Tstop then
self:TurnIntoWindOver()
end
end
end end
--- Get default cruise speed. --- Get default cruise speed.
@ -1308,6 +1481,19 @@ function NAVYGROUP:GetSpeedToWaypoint(indx)
return speed return speed
end end
--- Returns the currently expected speed.
-- @param #NAVYGROUP self
-- @return #number Expected speed in m/s.
function NAVYGROUP:GetExpectedSpeed()
if self:IsHolding() then
return 0
else
return self.speed or 0
end
end
--- Check queued turns into wind. --- Check queued turns into wind.
-- @param #NAVYGROUP self -- @param #NAVYGROUP self
-- @return #NAVYGROUP.IntoWind Next into wind data. -- @return #NAVYGROUP.IntoWind Next into wind data.
@ -1363,6 +1549,85 @@ function NAVYGROUP:GetHeadingIntoWind(Offset)
return intowind return intowind
end end
--- Find free path to next waypoint.
-- @param #NAVYGROUP self
-- @return #boolean If true, a path was found.
function NAVYGROUP:_FindPathToNextWaypoint()
-- Pathfinding A*
local astar=ASTAR:New()
-- Current positon of the group.
local position=self:GetCoordinate()
-- Next waypoint.
local wpnext=self:GetWaypointNext()
-- 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)
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*2, false)
-- Valid neighbour nodes need to have line of sight.
astar:SetValidNeighbourLoS(400)
--- 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.
for i,_node in ipairs(path) do
local node=_node --Core.Astar#ASTAR.Node
-- Waypoint index.
local wpindex=self:GetWaypointIndexCurrent()+i
-- Add waypoints along detour path to next waypoint.
local wp=self:AddWaypoint(node.coordinate, speed, wpindex)
wp.astar=true
-- Debug: smoke and mark path.
node.coordinate:MarkToAll(string.format("Path node #%d", i))
end
return #path>0
else
return false
end
end
return findpath()
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -1,5 +1,6 @@
--- **Ops** - Generic group enhancement functions. --- **Ops** - Generic group enhancement functions.
-- --
-- This class is **not** meant to be used itself by the end user.
-- --
-- === -- ===
-- --
@ -48,9 +49,10 @@
-- @field #boolean detectionOn If true, detected units of the group are analyzed. -- @field #boolean detectionOn If true, detected units of the group are analyzed.
-- @field Ops.Auftrag#AUFTRAG missionpaused Paused mission. -- @field Ops.Auftrag#AUFTRAG missionpaused Paused mission.
-- --
-- @field Core.Point#COORDINATE position Current position of the group. -- @field Core.Point#COORDINATE position Position of the group at last status check.
-- @field #number traveldist Distance traveled in meters. This is a lower bound! -- @field #number traveldist Distance traveled in meters. This is a lower bound!
-- @field #number traveltime Time. -- @field #number traveltime Time.
-- @field #boolean ispathfinding If true, group is on pathfinding route.
-- --
-- @field #number tacanChannelDefault The default TACAN channel. -- @field #number tacanChannelDefault The default TACAN channel.
-- @field #string tacanMorseDefault The default TACAN morse code. -- @field #string tacanMorseDefault The default TACAN morse code.
@ -70,16 +72,22 @@
-- @field #boolean eplrs If true, EPLRS data link is on. -- @field #boolean eplrs If true, EPLRS data link is on.
-- --
-- @field #string roeDefault Default ROE setting. -- @field #string roeDefault Default ROE setting.
-- @field #string rotDefault Default ROT setting.
-- @field #string roe Current ROE setting. -- @field #string roe Current ROE setting.
--
-- @field #string rotDefault Default ROT setting.
-- @field #string rot Current ROT setting. -- @field #string rot Current ROT setting.
-- --
-- @field #string alarmstateDefault Default Alarm State setting.
-- @field #string alarmstate Current Alarm State setting.
--
-- @field #number formationDefault Default formation setting. -- @field #number formationDefault Default formation setting.
-- @field #number formation Current formation setting. -- @field #number formation Current formation setting.
-- --
-- @field Core.Astar#ASTAR Astar path finding.
--
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- *Something must be left to chance; nothing is sure in a sea fight above all.* --- Horatio Nelson --- *A small group of determined and like-minded people can change the course of history.* --- Mahatma Gandhi
-- --
-- === -- ===
-- --
@ -87,7 +95,7 @@
-- --
-- # The OPSGROUP Concept -- # The OPSGROUP Concept
-- --
-- The OPSGROUP class contains common functions used by other classes such as FLIGHGROUP and NAVYGROUP. -- The OPSGROUP class contains common functions used by other classes such as FLIGHGROUP, NAVYGROUP.
-- --
-- This class is **not** meant to be used itself by the end user. -- This class is **not** meant to be used itself by the end user.
-- --
@ -206,16 +214,18 @@ OPSGROUP.TaskType={
--- Waypoint data. --- Waypoint data.
-- @type OPSGROUP.Waypoint -- @type OPSGROUP.Waypoint
-- @field #table wp DCS waypoint table.
-- @field Core.Point#COORDINATE coordinate Waypoint coordinate.
-- @field #number speed Speed in m/s.
-- @field #number altitude Altitude in meters. For submaries use negative sign for depth.
-- @field #number index Waypoint index. This might change as waypoints are added and removed.
-- @field #number uid Waypoint's unit id, which is a running number. -- @field #number uid Waypoint's unit id, which is a running number.
-- @field #boolean onroad If true, ground group takes a road. -- @field #number speed Speed in m/s.
-- @field #number formation The formation for this waypoint. -- @field #number alt Altitude in meters. For submaries use negative sign for depth.
-- @field #boolean detour If true, this waypoint is not part of the normal route.
-- @field #string action Waypoint action (turning point, etc.). Ground groups have the formation here. -- @field #string action Waypoint action (turning point, etc.). Ground groups have the formation here.
-- @field #table task Waypoint task combo.
-- @field #string type Waypoint type.
-- @field #number x Waypoint x-coordinate.
-- @field #number y Waypoint y-coordinate.
-- @field #boolean detour If true, this waypoint is not part of the normal route.
-- @field #boolean intowind If true, this waypoint is a turn into wind route point.
-- @field #boolean astar If true, this waypint was found by A* pathfinding algorithm.
-- @field Core.Point#COORDINATE coordinate Waypoint coordinate.
--- NavyGroup version. --- NavyGroup version.
-- @field #string version -- @field #string version
@ -433,79 +443,7 @@ function OPSGROUP:GetHeading()
return nil return nil
end end
--- Get next waypoint index.
-- @param #OPSGROUP self
-- @param #boolean cyclic If true, return first waypoint if last waypoint was reached. Default is patrol ad infinitum value set.
-- @return #number Next waypoint index.
function OPSGROUP:GetWaypointIndexNext(cyclic)
cyclic=cyclic or self.adinfinitum
local N=#self.waypoints
local n=math.min(self.currentwp+1, N)
if cyclic and self.currentwp==N then
n=1
end
return n
end
--- Get current waypoint index. This is the index of the last passed waypoint.
-- @param #OPSGROUP self
-- @return #number Current waypoint index.
function OPSGROUP:GetWaypointIndexCurrent()
return self.currentwp or 1
end
--- Get waypoint speed.
-- @param #OPSGROUP self
-- @param #number indx Waypoint index.
-- @return #number Speed set at waypoint in knots.
function OPSGROUP:GetWaypointSpeed(indx)
local waypoint=self:GetWaypoint(indx)
if waypoint then
return UTILS.MpsToKnots(waypoint.speed)
end
return nil
end
--- Get waypoint.
-- @param #OPSGROUP self
-- @param #number indx Waypoint index.
-- @return #OPSGROUP.Waypoint Waypoint table.
function OPSGROUP:GetWaypoint(indx)
return self.waypoints[indx]
end
--- Get final waypoint.
-- @param #OPSGROUP self
-- @return #OPSGROUP.Waypoint Final waypoint table.
function OPSGROUP:GetWaypointFinal()
return self.waypoints[#self.waypoints]
end
--- Get next waypoint.
-- @param #OPSGROUP self
-- @param #boolean cyclic If true, return first waypoint if last waypoint was reached.
-- @return #OPSGROUP.Waypoint Next waypoint table.
function OPSGROUP:GetWaypointNext(cyclic)
local n=self:GetWaypointIndexNext(cyclic)
return self.waypoints[n]
end
--- Get current waypoint.
-- @param #OPSGROUP self
-- @return #OPSGROUP.Waypoint Current waypoint table.
function OPSGROUP:GetWaypointCurrent()
return self.waypoints[self.currentwp]
end
--- Check if task description is unique. --- Check if task description is unique.
-- @param #OPSGROUP self -- @param #OPSGROUP self
@ -524,29 +462,6 @@ function OPSGROUP:CheckTaskDescriptionUnique(description)
return true return true
end end
--- Get coordinate of next waypoint of the group.
-- @param #OPSGROUP self
-- @return Core.Point#COORDINATE Coordinate of the next waypoint.
-- @return #number Number of waypoint.
function OPSGROUP:GetNextWaypointCoordinate()
-- Next waypoint.
local n=self:GetWaypointIndexNext(cyclic)
-- Next waypoint.
local wp=self.waypoints[n]
return self:GetWaypointCoordinate(wp)
end
--- Get next waypoint coordinates.
-- @param #OPSGROUP self
-- @param #table wp Waypoint table.
-- @return Core.Point#COORDINATE Coordinate of the next waypoint.
function OPSGROUP:GetWaypointCoordinate(wp)
-- TODO: move this to COORDINATE class.
return COORDINATE:New(wp.x, wp.alt, wp.y)
end
--- Activate a *late activated* group. --- Activate a *late activated* group.
-- @param #OPSGROUP self -- @param #OPSGROUP self
@ -707,6 +622,111 @@ function OPSGROUP:GetWaypointIndex(uid)
return nil return nil
end end
--- Get next waypoint index.
-- @param #OPSGROUP self
-- @param #boolean cyclic If true, return first waypoint if last waypoint was reached. Default is patrol ad infinitum value set.
-- @return #number Next waypoint index.
function OPSGROUP:GetWaypointIndexNext(cyclic)
if cyclic==nil then
cyclic=self.adinfinitum
end
--env.info("FF cyclic = "..tostring(cyclic))
local N=#self.waypoints
local n=math.min(self.currentwp+1, N)
--env.info("FF n = "..tostring(n))
if cyclic and self.currentwp==N then
n=1
--env.info("FF cyclic n = "..tostring(n))
end
return n
end
--- Get current waypoint index. This is the index of the last passed waypoint.
-- @param #OPSGROUP self
-- @return #number Current waypoint index.
function OPSGROUP:GetWaypointIndexCurrent()
return self.currentwp or 1
end
--- Get waypoint.
-- @param #OPSGROUP self
-- @param #number indx Waypoint index.
-- @return #OPSGROUP.Waypoint Waypoint table.
function OPSGROUP:GetWaypoint(indx)
return self.waypoints[indx]
end
--- Get final waypoint.
-- @param #OPSGROUP self
-- @return #OPSGROUP.Waypoint Final waypoint table.
function OPSGROUP:GetWaypointFinal()
return self.waypoints[#self.waypoints]
end
--- Get next waypoint.
-- @param #OPSGROUP self
-- @param #boolean cyclic If true, return first waypoint if last waypoint was reached.
-- @return #OPSGROUP.Waypoint Next waypoint table.
function OPSGROUP:GetWaypointNext(cyclic)
local n=self:GetWaypointIndexNext(cyclic)
return self.waypoints[n]
end
--- Get current waypoint.
-- @param #OPSGROUP self
-- @return #OPSGROUP.Waypoint Current waypoint table.
function OPSGROUP:GetWaypointCurrent()
return self.waypoints[self.currentwp]
end
--- Get coordinate of next waypoint of the group.
-- @param #OPSGROUP self
-- @return Core.Point#COORDINATE Coordinate of the next waypoint.
function OPSGROUP:GetNextWaypointCoordinate()
-- Get next waypoint
local waypoint=self:GetWaypointNext(cyclic)
return waypoint.coordinate
end
--- Get waypoint coordinates.
-- @param #OPSGROUP self
-- @param #number index Waypoint index.
-- @return Core.Point#COORDINATE Coordinate of the next waypoint.
function OPSGROUP:GetWaypointCoordinate(index)
local waypoint=self:GetWaypoint(index)
if waypoint then
return waypoint.coordinate
end
return nil
end
--- Get waypoint speed.
-- @param #OPSGROUP self
-- @param #number indx Waypoint index.
-- @return #number Speed set at waypoint in knots.
function OPSGROUP:GetWaypointSpeed(indx)
local waypoint=self:GetWaypoint(indx)
if waypoint then
return UTILS.MpsToKnots(waypoint.speed)
end
return nil
end
--- Remove a waypoint with a ceratin UID. --- Remove a waypoint with a ceratin UID.
-- @param #OPSGROUP self -- @param #OPSGROUP self
-- @param #number uid Waypoint UID. -- @param #number uid Waypoint UID.
@ -716,8 +736,7 @@ function OPSGROUP:RemoveWaypointByID(uid)
local index=self:GetWaypointIndex(uid) local index=self:GetWaypointIndex(uid)
if index then if index then
self:RemoveWaypoint(index) self:RemoveWaypoint(index)
end end
return self return self
@ -741,24 +760,47 @@ function OPSGROUP:RemoveWaypoint(wpindex)
local n=#self.waypoints local n=#self.waypoints
-- Debug info. -- Debug info.
self:I(self.lid..string.format("Removing waypoint %d. N %d-->%d", wpindex, N, n)) self:I(self.lid..string.format("Removing waypoint index %d, current wp index %d. N %d-->%d", wpindex, self.currentwp, N, n))
-- Waypoint was not reached yet. -- Waypoint was not reached yet.
if wpindex > self.currentwp then if wpindex > self.currentwp then
-- Could be that we just removed the only remaining waypoint ==> passedfinalwp=true so we RTB or wait. -- Could be that we just removed the only remaining waypoint ==> passedfinalwp=true.
-- TODO: patrol adinfinitum.
if self.currentwp>=n then if self.currentwp>=n then
self.passedfinalwp=true self.passedfinalwp=true
end end
self:_CheckGroupDone()
env.info("FF passed final waypoint after remove current wp = "..self.currentwp)
self:_CheckGroupDone(1)
--elseif wpindex==self.currentwp then
-- Removed the waypoint we just passed.
else else
-- If an already passed waypoint was deleted, we do not need to update the route. -- If an already passed waypoint was deleted, we do not need to update the route.
-- TODO: But what about the self.currentwp number. This is now incorrect! -- If current wp = 1 it stays 1. Otherwise decrease current wp.
self.currentwp=self.currentwp-1
if self.currentwp==1 then
if self.adinfinitum then
self.currentwp=#self.waypoints
else
self.currentwp=1
end
else
self.currentwp=self.currentwp-1
end
--self.currentwp=math.max(1, self.currentwp-1)
env.info("FF current waypoint after remove "..self.currentwp)
end end
@ -995,9 +1037,9 @@ end
--- Get the unfinished waypoint tasks --- Get the unfinished waypoint tasks
-- @param #OPSGROUP self -- @param #OPSGROUP self
-- @param #number n Waypoint index. Counting starts at one. -- @param #number id Unique waypoint ID.
-- @return #table Table of tasks. Table could also be empty {}. -- @return #table Table of tasks. Table could also be empty {}.
function OPSGROUP:GetTasksWaypoint(n) function OPSGROUP:GetTasksWaypoint(id)
-- Tasks table. -- Tasks table.
local tasks={} local tasks={}
@ -1008,7 +1050,7 @@ function OPSGROUP:GetTasksWaypoint(n)
-- Look for first task that SCHEDULED. -- Look for first task that SCHEDULED.
for _,_task in pairs(self.taskqueue) do for _,_task in pairs(self.taskqueue) do
local task=_task --#OPSGROUP.Task local task=_task --#OPSGROUP.Task
if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED and task.waypoint==n then if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED and task.waypoint==id then
table.insert(tasks, task) table.insert(tasks, task)
end end
end end
@ -1913,19 +1955,48 @@ end
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
-- @param #number n Waypoint passed.
-- @param #number N Total number of waypoints.
-- @param #OPSGROUP.Waypoint Waypoint Waypoint data passed. -- @param #OPSGROUP.Waypoint Waypoint Waypoint data passed.
function OPSGROUP:onafterPassingWaypoint(From, Event, To, n, N, Waypoint) function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint)
local text=string.format("Group passed waypoint %d/%d", n, N)
self:T(self.lid..text)
MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug)
-- Apply tasks of this waypoint.
local ntasks=self:_SetWaypointTasks(Waypoint)
-- Get waypoint index.
local wpindex=self:GetWaypointIndex(Waypoint.uid)
-- Final waypoint reached?
if wpindex==nil or wpindex==#self.waypoints then
-- Set switch to true.
self.passedfinalwp=true
-- Check if all tasks/mission are done? If so, RTB or WAIT.
-- Note, we delay it for a second to let the OnAfterPassingwaypoint function to be executed in case someone wants to add another waypoint there.
if ntasks==0 then
self:_CheckGroupDone(1)
end
end
-- Debug info.
local text=string.format("Group passed waypoint %s/%d ID=%d: final=%s detour=%s astar=%s",
tostring(wpindex), #self.waypoints, Waypoint.uid, tostring(self.passedfinalwp), tostring(Waypoint.detour), tostring(Waypoint.astar))
self:I(self.lid..text)
MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug)
end
--- On after "GotoWaypoint" event. Group will got to the given waypoint and execute its route from there.
-- @param #OPSGROUP self
-- @param #OPSGROUP.Waypoint Waypoint The waypoint.
-- @return #number Number of tasks.
function OPSGROUP:_SetWaypointTasks(Waypoint)
-- Get all waypoint tasks. -- Get all waypoint tasks.
local tasks=self:GetTasksWaypoint(Waypoint.uid) local tasks=self:GetTasksWaypoint(Waypoint.uid)
-- Debug info. -- Debug info.
local text=string.format("WP %d/%d tasks:", n, N) local text=string.format("WP uid=%d tasks:", Waypoint.uid)
if #tasks>0 then if #tasks>0 then
for i,_task in pairs(tasks) do for i,_task in pairs(tasks) do
local task=_task --#OPSGROUP.Task local task=_task --#OPSGROUP.Task
@ -1963,20 +2034,8 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, n, N, Waypoint)
if #taskswp>0 then if #taskswp>0 then
self:SetTask(self.group:TaskCombo(taskswp)) self:SetTask(self.group:TaskCombo(taskswp))
end end
-- Final AIR waypoint reached?
if n==N then
-- Set switch to true. return #taskswp
self.passedfinalwp=true
-- Check if all tasks/mission are done? If so, RTB or WAIT.
-- Note, we delay it for a second to let the OnAfterPassingwaypoint function to be executed in case someone wants to add another waypoint there.
if #taskswp==0 then
self:_CheckGroupDone(1)
end
end
end end
--- On after "GotoWaypoint" event. Group will got to the given waypoint and execute its route from there. --- On after "GotoWaypoint" event. Group will got to the given waypoint and execute its route from there.
@ -2195,14 +2254,17 @@ end
--- Initialize Mission Editor waypoints. --- Initialize Mission Editor waypoints.
-- @param #OPSGROUP self -- @param #OPSGROUP self
-- @param #table waypoint DCS waypoint data table. -- @param #OPSGROUP.Waypoint waypoint DCS waypoint data table.
-- @return #OPSGROUP.Waypoint Waypoint data. -- @return #OPSGROUP.Waypoint Waypoint data.
function OPSGROUP:_CreateWaypoint(waypoint, detour, onroad, formation) function OPSGROUP:_CreateWaypoint(waypoint, formation, detour)
waypoint.uid=self.wpcounter waypoint.uid=self.wpcounter
waypoint.coordinate=COORDINATE:New(waypoint.x, waypoint.alt, waypoint.y) waypoint.coordinate=COORDINATE:New(waypoint.x, waypoint.alt, waypoint.y)
waypoint.detour=detour and detour or false waypoint.detour=detour and detour or false
waypoint.formation=formation waypoint.formation=formation
if formation then
waypoint.action=formation
end
waypoint.onroad=onroad and onroad or false waypoint.onroad=onroad and onroad or false
self.wpcounter=self.wpcounter+1 self.wpcounter=self.wpcounter+1
@ -2218,7 +2280,7 @@ function OPSGROUP:_AddWaypoint(waypoint, wpnumber)
wpnumber=wpnumber or #self.waypoints+1 wpnumber=wpnumber or #self.waypoints+1
env.info(string.format("adding waypoint at index=%d", wpnumber)) self:I(self.lid..string.format("Adding waypoint at index=%d id=%d", wpnumber, waypoint.uid))
-- Add waypoint to table. -- Add waypoint to table.
table.insert(self.waypoints, wpnumber, waypoint) table.insert(self.waypoints, wpnumber, waypoint)
@ -2263,7 +2325,7 @@ end
--- Route group along waypoints. --- Route group along waypoints.
-- @param #OPSGROUP self -- @param #OPSGROUP self
-- @param #table waypoints Table of waypoints. -- @param #table waypoints Table of waypoints.
-- @default -- @param #number delay Delay in seconds.
-- @return #OPSGROUP self -- @return #OPSGROUP self
function OPSGROUP:Route(waypoints, delay) function OPSGROUP:Route(waypoints, delay)
@ -2314,13 +2376,13 @@ function OPSGROUP:_UpdateWaypointTasks(n)
if i>=n or nwaypoints==1 then if i>=n or nwaypoints==1 then
-- Debug info. -- Debug info.
self:T(self.lid..string.format("Updating waypoint task for waypoint %d/%d. Last waypoint passed %d", i, nwaypoints, self.currentwp)) self:I(self.lid..string.format("Updating waypoint task for waypoint %d/%d ID=%d. Last waypoint passed %d", i, nwaypoints, wp.uid, self.currentwp))
-- Tasks of this waypoint -- Tasks of this waypoint
local taskswp={} local taskswp={}
-- At each waypoint report passing. -- At each waypoint report passing.
local TaskPassingWaypoint=self.group:TaskFunction("OPSGROUP._PassingWaypoint", self, i, wp.uid) local TaskPassingWaypoint=self.group:TaskFunction("OPSGROUP._PassingWaypoint", self, wp.uid)
table.insert(taskswp, TaskPassingWaypoint) table.insert(taskswp, TaskPassingWaypoint)
-- Waypoint task combo. -- Waypoint task combo.
@ -2337,26 +2399,72 @@ end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Function called when a group is passing a waypoint. --- Function called when a group is passing a waypoint.
--@param Wrapper.Group#GROUP group Group that passed the waypoint --@param Wrapper.Group#GROUP group Group that passed the waypoint.
--@param #OPSGROUP opsgroup Ops group object. --@param #OPSGROUP opsgroup Ops group object.
--@param #number i Waypoint number that has been reached.
--@param #number uid Waypoint UID. --@param #number uid Waypoint UID.
function OPSGROUP._PassingWaypoint(group, opsgroup, i, uid) function OPSGROUP._PassingWaypoint(group, opsgroup, uid)
local final=#opsgroup.waypoints or 1
-- Debug message.
local text=string.format("Group passing waypoint %d of %d, uid=%d", i, final, uid)
opsgroup:I(opsgroup.lid..text)
-- Set current waypoint.
opsgroup.currentwp=i
-- Get waypoint data. -- Get waypoint data.
local waypoint=opsgroup:GetWaypointByID(uid) local waypoint=opsgroup:GetWaypointByID(uid)
-- Trigger PassingWaypoint event. if waypoint then
opsgroup:PassingWaypoint(i, final, waypoint)
-- Get the current waypoint index.
opsgroup.currentwp=opsgroup:GetWaypointIndex(uid)
-- Set expected speed and formation from the next WP.
local wpnext=opsgroup:GetWaypointNext()
if wpnext then
-- Set formation.
if opsgroup.isGround then
opsgroup.formation=wpnext.action
end
-- Set speed.
opsgroup.speed=wpnext.speed
end
-- Check if the group is still pathfinding.
if opsgroup.ispathfinding and not waypoint.astar then
opsgroup.ispathfinding=false
end
-- Check special waypoints.
if waypoint.astar then
env.info("FF removing Astar waypoint "..uid)
opsgroup:RemoveWaypointByID(uid)
elseif waypoint.detour then
env.info("FF removing Detour waypoint "..uid)
opsgroup:RemoveWaypointByID(uid)
-- Trigger event.
opsgroup:DetourReached()
if waypoint.detour==0 then
opsgroup:FullStop()
elseif waypoint.detour==1 then
opsgroup:Cruise()
else
opsgroup:E("ERROR: waypoint.detour should be 0 or 1")
end
end
-- Debug message.
local text=string.format("Group passing waypoint uid=%d", uid)
opsgroup:I(opsgroup.lid..text)
-- Trigger PassingWaypoint event.
if not (waypoint.astar or waypoint.detour) then
opsgroup:PassingWaypoint(waypoint)
end
end
end end
@ -2461,6 +2569,52 @@ function OPSGROUP:SetOptionROT(rot)
return self return self
end end
--- Set the default Alarm State for the group. This is the state gets when the group is spawned or to which it defaults back after a mission.
-- @param #OPSGROUP self
-- @param #number alarmstate Alarm state of group. Default is `AI.Option.Ground.val.ALARM_STATE.AUTO` (0).
-- @return #OPSGROUP self
function OPSGROUP:SetDefaultAlarmstate(alarmstate)
self.alarmstateDefault=alarmstate or 0
return self
end
--- Set current Alarm State of the group.
-- @param #OPSGROUP self
-- @param #string alarmstate Alarm state of group. Default is the value defined by :SetDefaultAlarmstate().
-- @return #OPSGROUP self
function OPSGROUP:SetOptionAlarmstate(alarmstate)
self.alarmstate=alarmstate or self.alarmstateDefault
if self:IsAlive() then
if self.alarmstate==0 then
self.group:OptionAlarmStateAuto()
elseif self.alarmstate==1 then
self.group:OptionAlarmStateGreen()
elseif self.alarmstate==2 then
self.group:OptionAlarmStateRed()
else
self:E("ERROR: Unknown Alarm State! Setting to AUTO.")
self.group:OptionAlarmStateAuto()
end
self:I(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)", self.alarmstate))
else
-- TODO WARNING
end
return self
end
--- Get current Alarm State of the group.
-- @param #OPSGROUP self
-- @return #number Current Alarm State.
function OPSGROUP:GetAlarmstate()
return self.alarmstate
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Element and Group Status Functions -- Element and Group Status Functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -222,6 +222,15 @@ ENUMS.Formation.RotaryWing.EchelonLeft={}
ENUMS.Formation.RotaryWing.EchelonLeft.D70 =590081 ENUMS.Formation.RotaryWing.EchelonLeft.D70 =590081
ENUMS.Formation.RotaryWing.EchelonLeft.D300=590082 ENUMS.Formation.RotaryWing.EchelonLeft.D300=590082
ENUMS.Formation.RotaryWing.EchelonLeft.D600=590083 ENUMS.Formation.RotaryWing.EchelonLeft.D600=590083
ENUMS.Formation.Vehicle={}
ENUMS.Formation.Vehicle.Vee="Vee"
ENUMS.Formation.Vehicle.EchelonRight="EchelonR"
ENUMS.Formation.Vehicle.OffRoad="Off Road"
ENUMS.Formation.Vehicle.Rank="Rank"
ENUMS.Formation.Vehicle.EchelonLeft="EchelonL"
ENUMS.Formation.Vehicle.OnRoad="On Road"
ENUMS.Formation.Vehicle.Cone="Cone"
ENUMS.Formation.Vehicle.Diamond="Diamond"
--- Formations (old). The old format is a simplified version of the new formation enums, which allow more sophisticated settings. --- Formations (old). The old format is a simplified version of the new formation enums, which allow more sophisticated settings.
-- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki. -- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki.