From 2f6d9b640fc4221bdf40e066ce44dda79db38e0e Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 29 Jul 2020 01:10:13 +0200 Subject: [PATCH] NAVY --- Moose Development/Moose/Core/Astar.lua | 237 ++++++++-- Moose Development/Moose/Core/Point.lua | 32 +- Moose Development/Moose/Ops/ArmyGroup.lua | 131 +++--- Moose Development/Moose/Ops/FlightGroup.lua | 2 + Moose Development/Moose/Ops/NavyGroup.lua | 419 +++++++++++++---- Moose Development/Moose/Ops/OpsGroup.lua | 476 +++++++++++++------- Moose Development/Moose/Utilities/Enums.lua | 9 + 7 files changed, 954 insertions(+), 352 deletions(-) diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index 58459db13..f4107f4f9 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -2,13 +2,16 @@ -- -- **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** -- @module Core.Astar --- @image CORE_Atar.png +-- @image CORE_Astar.png --- ASTAR class. @@ -21,20 +24,110 @@ -- @field #ASTAR.Node endNode End node. -- @field Core.Point#COORDINATE startCoord Start 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 #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 --- When nothing goes right... Go left! -- -- === -- --- ![Banner Image](..\Presentations\WingCommander\ASTAR_Main.jpg) +-- ![Banner Image](..\Presentations\Astar\ASTAR_Main.jpg) -- -- # The ASTAR Concept -- -- 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 @@ -45,13 +138,13 @@ ASTAR = { nodes = {}, } ---- Defence condition. +--- Node data. -- @type ASTAR.Node -- @field Core.Point#COORDINATE coordinate Coordinate of the node. -- @field #number surfacetype Surface type. ---- ASTAR infinity --- @field #string INF +--- ASTAR infinity. +-- @field #number INF ASTAR.INF=1/0 --- ASTAR class version. @@ -137,14 +230,14 @@ end --- Add a node to the table of grid nodes specifying its coordinate. -- @param #ASTAR self -- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created. --- @return #ASTAR self +-- @return #ASTAR.Node The node. function ASTAR:AddNodeFromCoordinate(Coordinate) local node=self:GetNodeFromCoordinate(Coordinate) self:AddNode(node) - return self + return node end --- Check if the coordinate of a node has is at a valid surface type. @@ -214,8 +307,44 @@ function ASTAR:SetValidNeighbourDistance(MaxDistance) return self 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 @@ -369,6 +498,26 @@ function ASTAR.DistMax(nodeA, nodeB, distmax) return dist<=distmax 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -458,7 +607,7 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) local g_score, f_score = {}, {} 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. local T0=timer.getAbsTime() @@ -470,12 +619,12 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) 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. if current==goal then - local path=self:UnwindPath({}, came_from, goal) + local path=self:_UnwindPath({}, came_from, goal) if not ExcludeEndNode then table.insert(path, goal) @@ -493,26 +642,26 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) return path end - self:RemoveNode(openset, current) + self:_RemoveNode(openset, current) table.insert(closedset, current) - local neighbors=self:NeighbourNodes(current, nodes) + local neighbors=self:_NeighbourNodes(current, nodes) -- Loop over neighbours. 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 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) end @@ -533,22 +682,18 @@ end -- 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.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 +-- @return #number "Cost" to go from node A to node B. +function ASTAR:_HeuristicCost(nodeA, nodeB) ---- Heuristic cost function to go from node A to node B. That is simply the distance here. --- @param #ASTAR self --- @param #ASTAR.Node nodeA Node A. --- @param #ASTAR.Node nodeB Node B. --- @return #number Distance between nodes in meters. -function ASTAR:HeuristicCost(nodeA, nodeB) - return self:DistNodes(nodeA, nodeB) + if self.CostFunc then + return self.CostFunc(nodeA, nodeB, unpack(self.CostArg)) + else + return self:_DistNodes(nodeA, nodeB) + end end --- 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 neighbor Neighbour node. -- @return #boolean If true, transition between nodes is possible. -function ASTAR:IsValidNeighbour(node, neighbor) +function ASTAR:_IsValidNeighbour(node, neighbor) if self.ValidNeighbourFunc then @@ -568,9 +713,21 @@ function ASTAR:IsValidNeighbour(node, neighbor) end ---- Function +--- Calculate 2D distance between two nodes. -- @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 @@ -591,14 +748,14 @@ end -- @param #ASTAR.Node theNode The node. -- @param #table nodes Possible neighbours. -- @param #table Valid neighbour nodes. -function ASTAR:NeighbourNodes(theNode, nodes) +function ASTAR:_NeighbourNodes(theNode, nodes) local neighbors = {} for _, node in ipairs ( nodes ) do if theNode~=node then - local isvalid=self:IsValidNeighbour(theNode, node) + local isvalid=self:_IsValidNeighbour(theNode, node) if isvalid then table.insert(neighbors, node) @@ -616,7 +773,7 @@ end -- @param #table set Set of nodes. -- @param #ASTAR.Node theNode The node to check. -- @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 if node == theNode then @@ -631,7 +788,7 @@ end -- @param #ASTAR self -- @param #table set Set of nodes. -- @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 if node == theNode then @@ -649,11 +806,11 @@ end -- @param #table map Map. -- @param #ASTAR.Node current_node The current node. -- @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 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 return flat_path end diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 4b7b9a7ef..2fe7a3fa2 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -175,10 +175,6 @@ do -- COORDINATE -- 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. -- - -- - -- - -- - -- -- ## 8) Metric or imperial system -- -- * @{#COORDINATE.IsMetric}(): Returns if the 3D point is Metric or Nautical Miles. @@ -204,23 +200,23 @@ do -- COORDINATE --- @field COORDINATE.WaypointAction COORDINATE.WaypointAction = { - TurningPoint = "Turning Point", - FlyoverPoint = "Fly Over Point", - FromParkingArea = "From Parking Area", + TurningPoint = "Turning Point", + FlyoverPoint = "Fly Over Point", + FromParkingArea = "From Parking Area", FromParkingAreaHot = "From Parking Area Hot", - FromRunway = "From Runway", - Landing = "Landing", - LandingReFuAr = "LandingReFuAr", + FromRunway = "From Runway", + Landing = "Landing", + LandingReFuAr = "LandingReFuAr", } --- @field COORDINATE.WaypointType COORDINATE.WaypointType = { - TakeOffParking = "TakeOffParking", + TakeOffParking = "TakeOffParking", TakeOffParkingHot = "TakeOffParkingHot", - TakeOff = "TakeOffParkingHot", - TurningPoint = "Turning Point", - Land = "Land", - LandingReFuAr = "LandingReFuAr", + TakeOff = "TakeOffParkingHot", + TurningPoint = "Turning Point", + Land = "Land", + LandingReFuAr = "LandingReFuAr", } @@ -1290,8 +1286,10 @@ do -- COORDINATE RoutePoint.x = self.x RoutePoint.y = self.z - RoutePoint.alt = self:GetLandHeight()+1 -- self.y + RoutePoint.alt = self:GetLandHeight()+1 RoutePoint.alt_type = COORDINATE.WaypointAltType.BARO + + RoutePoint.type = "Turning Point" RoutePoint.action = Formation or "Off Road" RoutePoint.formation_template="" @@ -1921,7 +1919,7 @@ do -- COORDINATE --- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate. -- @param #COORDINATE self -- @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. function COORDINATE:IsLOS( ToCoordinate, Offset ) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 86171b2af..6e9cba673 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -16,7 +16,7 @@ -- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. -- @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 self:SetDefaultROE() + self:SetDefaultAlarmstate() self:SetDetection() - self:SetPatrolAdInfinitum(true) + self:SetPatrolAdInfinitum(false) -- Add FSM transitions. -- From State --> Event --> To State @@ -134,7 +135,7 @@ function ARMYGROUP:SetPatrolAdInfinitum(switch) return self 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 #number Speed Speed in knots. Default 70% of max speed. -- @return #ARMYGROUP self @@ -225,8 +226,8 @@ function ARMYGROUP:onafterStatus(From, Event, To) local nMissions=self:CountRemainingMissison() -- Info text. - local text=string.format("State %s: Wp=%d/%d Speed=%.1f Heading=%03d Tasks=%d Missions=%d", - fsmstate, self.currentwp, #self.waypoints, speed, hdg, nTaskTot, nMissions) + 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, self:GetWaypointIndexNext(), speed, UTILS.MpsToKnots(self.speed), hdg, self.roe, self.alarmstate, self.formation, nTaskTot, nMissions) self:I(self.lid..text) else @@ -338,9 +339,12 @@ function ARMYGROUP:onafterSpawned(From, Event, To) if self.ai then - -- Set default ROE and ROT options. + -- Set default ROE option. self:SetOptionROE(self.roe) + -- Set default Alarm State option. + self:SetOptionAlarmstate(self.alarmstate) + end -- Update route. @@ -355,38 +359,33 @@ end -- @param #string To To state. -- @param #number n Waypoint number. Default is next waypoint. -- @param #number Speed Speed in knots. Default cruise speed. --- @param #number Depth Depth in meters. Default 0 meters. -function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) +-- @param #number Formation Formation of the group. +function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) -- Update route from this waypoint number onwards. n=n or self:GetWaypointIndexNext(self.adinfinitum) -- 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. self:_UpdateWaypointTasks(n) -- 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. 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 + --- + -- Next Waypoint + --- + if Speed then wp.speed=UTILS.KnotsToMps(Speed) elseif self.speedCruise then @@ -394,8 +393,22 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) else -- Take default waypoint speed. 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 + + --- + -- Later Waypoint(s) + --- if self.speedCruise then wp.speed=UTILS.KmphToMps(self.speedCruise) @@ -405,17 +418,23 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) end - -- Set depth. - wp.alt=-depth --Depth and -Depth or wp.alt - + -- Debug info. + 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. table.insert(waypoints, wp) 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, 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, Formation=%s", + self.currentwp, n, #self.waypoints, #waypoints-2, UTILS.MpsToKnots(self.speed), tostring(self.formation))) -- Route group to all defined waypoints remaining. 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")) - 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 @@ -444,27 +458,24 @@ end -- @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 Formation Formation the group will use. -- @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. 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():WaypointGround(Speed, Formation, DCSTasks) + local current=self:GetCoordinate():WaypointGround(Speed, Formation) table.insert(waypoints, current) -- At each waypoint report passing. 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) self:Route(waypoints) @@ -482,21 +493,21 @@ end --- Function called when a group is passing a 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. -function ARMYGROUP._DetourReached(group, navygroup, resume) +function ARMYGROUP._DetourReached(group, armygroup, resume) -- Debug message. local text=string.format("Group reached detour coordinate") - navygroup:I(navygroup.lid..text) + armygroup:I(armygroup.lid..text) if resume then - local indx=navygroup:GetWaypointIndexNext(true) - local speed=navygroup:GetSpeedToWaypoint(indx) - navygroup:UpdateRoute(indx, speed, navygroup.depth) + local indx=armygroup:GetWaypointIndexNext(true) + local speed=armygroup:GetSpeedToWaypoint(indx) + armygroup:__UpdateRoute(-1, indx, speed, armygroup.formation) end - navygroup:DetourReached() + armygroup:DetourReached() end @@ -511,7 +522,7 @@ function ARMYGROUP:onafterFullStop(From, Event, To) local pos=self:GetCoordinate() -- Create a new waypoint. - local wp=pos:WaypointNaval(0) + local wp=pos:WaypointGround(0) -- Create new route consisting of only this position ==> Stop! self:Route({wp}) @@ -524,9 +535,10 @@ end -- @param #string Event Event. -- @param #string To To state. -- @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 @@ -675,9 +687,10 @@ end -- @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 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. -- @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. wpnumber=wpnumber or #self.waypoints+1 @@ -693,13 +706,16 @@ function ARMYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) local speedkmh=UTILS.KnotsToKmph(speed) -- Create a Naval waypoint. - local wp=coordinate:WaypointNaval(speedkmh) + local wp=coordinate:WaypointGround(speedkmh, formation) - -- Add to table. - table.insert(self.waypoints, wpnumber, wp) + -- Create waypoint data table. + local waypoint=self:_CreateWaypoint(wp) + + -- Add waypoint to table. + self:_AddWaypoint(waypoint, wpnumber) -- 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. @@ -735,7 +751,7 @@ function ARMYGROUP:_InitGroup() -- Is (template) group late activated. self.isLateActivated=self.template.lateActivation - -- Naval groups cannot be uncontrolled. + -- Ground groups cannot be uncontrolled. self.isUncontrolled=false -- Max speed in km/h. @@ -766,6 +782,7 @@ function ARMYGROUP:_InitGroup() end end + -- Units of the group. local units=self.group:GetUnits() for _,_unit in pairs(units) do @@ -847,13 +864,13 @@ function ARMYGROUP:_CheckGroupDone(delay) local speed=self:GetSpeedToWaypoint(1) -- Start route at first waypoint. - self:__UpdateRoute(-1, 1, speed, self.depth) + self:__UpdateRoute(-1, 1, speed, self.formation) end else - self:UpdateRoute(nil, nil, self.depth) + self:UpdateRoute(nil, nil, self.formation) end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index c08bf7a22..caef3f605 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2537,6 +2537,8 @@ function FLIGHTGROUP:_InitGroup() self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") end + -- Switch to default formation. + -- TODO: Should this be moved to onafterspawned? self:SwitchFormation(self.formationDefault) -- Add elemets. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index bd5a83106..58aebfd15 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -20,6 +20,7 @@ -- @field #NAVYGROUP.IntoWind intowind Into wind info. -- @field #table Qintowind Queue of "into wind" turns. -- @field #number depth Ordered depth in meters. +-- @field #boolean collisionwarning If true, collition warning. -- @extends Ops.OpsGroup#OPSGROUP --- *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 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. @@ -99,8 +101,8 @@ function NAVYGROUP:New(GroupName) self:AddTransition("*", "FullStop", "Holding") -- Hold position. self:AddTransition("*", "Cruise", "Cruising") -- Hold position. - self:AddTransition("*", "TurnIntoWind", "*") -- Command the group to turn into the wind. - self:AddTransition("*", "TurnIntoWindOver", "*") -- Turn into wind is over. + self:AddTransition("*", "TurnIntoWind", "IntoWind") -- Command the group to turn into the wind. + self:AddTransition("*", "TurnIntoWindOver", "Cruising") -- Turn into wind is over. self:AddTransition("*", "TurningStarted", "*") -- Group started 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("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. + self:AddTransition("*", "CollitionWarning", "*") -- Collision warning. + self:AddTransition("*", "Dive", "Diving") -- Command a submarine to dive. self:AddTransition("Diving", "Surface", "Cruising") -- Command a submarine to go to the surface. @@ -170,7 +174,7 @@ function NAVYGROUP:SetPatrolAdInfinitum(switch) return self 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 #number Speed Speed in knots. Default 70% of max speed. -- @return #NAVYGROUP self @@ -290,6 +294,36 @@ function NAVYGROUP:AddTurnIntoWind(starttime, stoptime, speed, uturn, offset) return recovery 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. @@ -354,28 +388,41 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check if group started or stopped turning. self:_CheckTurning() - -- Check water is ahead. - local collision=self:_CheckCollisionCoord(pos:Translate(self.collisiondist or 5000, hdg)) + local freepath=10000 + local collision=false - self:_CheckTurnsIntoWind() - - if self.intowind then + if not self:IsTurning() 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 - + + -- Check water is ahead. + --collision=self:_CheckCollisionCoord(pos:Translate(freepath+100, hdg)) + end + + -- Check into wind queue. + self:_CheckTurnsIntoWind() + + -- Check if group got stuck. + self:_CheckStuck() -- Get number of tasks and missions. local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() local nMissions=self:CountRemainingMissison() + + local intowind=self:IsSteamingIntoWind() and UTILS.SecondsToClock(self.intowind.Tstop-timer.getAbsTime(), true) or "N/A" -- 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", - fsmstate, self.currentwp, #self.waypoints, speed, hdg, tostring(self:IsSteamingIntoWind()), tostring(self:IsTurning()), tostring(collision), nTaskTot, nMissions) + 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, 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) else @@ -444,7 +491,7 @@ function NAVYGROUP:onafterStatus(From, Event, To) end - + -- Next status update in 10 seconds. self:__Status(-10) end @@ -510,8 +557,8 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #number n Waypoint number. Default is next waypoint. --- @param #number Speed Speed in knots. Default cruise speed. --- @param #number Depth Depth in meters. Default 0 meters. +-- @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, Speed, Depth) -- Update route from this waypoint number onwards. @@ -526,52 +573,63 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) -- 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. + local depth=nil for i=n, #self.waypoints do + + -- Waypoint. local wp=self.waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint - -- Set speed. + -- Check if next wp. if i==n then + -- Speed. if Speed then + -- Take speed specified. 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 -- Take default waypoint speed. 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 - - -- Set depth. - wp.alt=-depth --Depth and -Depth or wp.alt - + -- Add waypoint. table.insert(waypoints, wp) end + + -- Current waypoint. + local current=self:GetCoordinate():WaypointNaval(UTILS.MpsToKmph(self.speed), depth) + table.insert(waypoints, 1, current) 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. 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")) - 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 @@ -601,7 +654,7 @@ end -- @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. +-- @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) -- Waypoints. @@ -610,20 +663,18 @@ function NAVYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Depth, Resu -- 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() + -- Speed in knots. + Speed=Speed or self:GetSpeedCruise() - -- Current waypoint. - local current=self:GetCoordinate():WaypointNaval(speed, depth) - table.insert(waypoints, current) + local wpindx=self.currentwp+1 - -- At each waypoint report passing. - local Task=self.group:TaskFunction("NAVYGROUP._DetourReached", self, ResumeRoute) + local wp=self:AddWaypoint(Coordinate, Speed, wpindx, true) - local detour=Coordinate:WaypointNaval(speed, depth, {Task}) - table.insert(waypoints, detour) - - self:Route(waypoints) + if ResumeRoute then + wp.detour=1 + else + wp.detour=0 + end end @@ -662,9 +713,6 @@ end -- @param #string Event Event. -- @param #string To To state. -- @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) IntoWind.Heading=self:GetHeadingIntoWind(IntoWind.Offset) @@ -681,7 +729,7 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) -- Convert to knots. 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) -- Debug info. @@ -689,16 +737,28 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) local distance=UTILS.NMToMeters(1000) - local wp={} - local coord=self:GetCoordinate() 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[2]=Coord:WaypointNaval(UTILS.KnotsToKmph(speed)) self:Route(wp) - + ]] + end --- On after "TurnIntoWindOver" event. @@ -711,15 +771,22 @@ end -- @param #boolean Uturn Return to the place we came from. function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To) + env.info("FF Turn Into Wind Over!") + 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 + env.info("FF Turn Into Wind Over Uturn!") self:Detour(self.intowind.Coordinate, self:GetSpeedCruise(), 0, true) 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) - self:UpdateRoute(indx, speed, self.depth) + self:__UpdateRoute(-1, indx, speed) end self.intowind=nil @@ -749,10 +816,13 @@ end -- @param #string From From state. -- @param #string Event Event. -- @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) - self:UpdateRoute(nil, Speed, self.depth) + -- + self.depth=nil + + self:__UpdateRoute(-1, nil, Speed) end @@ -762,7 +832,8 @@ end -- @param #string Event Event. -- @param #string To To state. -- @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 @@ -770,7 +841,7 @@ function NAVYGROUP:onafterDive(From, Event, To, Depth) self.depth=Depth - self:UpdateRoute(nil, nil, self.depth) + self:__UpdateRoute(-1, nil, Speed) end @@ -779,11 +850,12 @@ end -- @param #string From From state. -- @param #string Event Event. -- @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:UpdateRoute(nil, nil, self.depth) + self:__UpdateRoute(-1, nil, Speed) end @@ -959,7 +1031,7 @@ function NAVYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) self:_AddWaypoint(waypoint, wpnumber) -- 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. if updateroute==nil or updateroute==true then @@ -1089,6 +1161,73 @@ end -- 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. -- @param #NAVYGROUP self -- @param Core.Point#COORDINATE coordto Coordinate to which the collision is check. @@ -1197,6 +1336,32 @@ function NAVYGROUP:_CheckTurning() 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. -- -- * passed the final waypoint, @@ -1284,6 +1449,14 @@ function NAVYGROUP:_CheckTurnsIntoWind() end end + + -- If into wind, check if over. + if self.intowind then + if timer.getAbsTime()>=self.intowind.Tstop then + self:TurnIntoWindOver() + end + end + end --- Get default cruise speed. @@ -1308,6 +1481,19 @@ function NAVYGROUP:GetSpeedToWaypoint(indx) return speed 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. -- @param #NAVYGROUP self -- @return #NAVYGROUP.IntoWind Next into wind data. @@ -1363,6 +1549,85 @@ function NAVYGROUP:GetHeadingIntoWind(Offset) return intowind 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 + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 237ab2316..ee8c0bc1c 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1,5 +1,6 @@ --- **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 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 traveltime Time. +-- @field #boolean ispathfinding If true, group is on pathfinding route. -- -- @field #number tacanChannelDefault The default TACAN channel. -- @field #string tacanMorseDefault The default TACAN morse code. @@ -70,16 +72,22 @@ -- @field #boolean eplrs If true, EPLRS data link is on. -- -- @field #string roeDefault Default ROE setting. --- @field #string rotDefault Default ROT setting. -- @field #string roe Current ROE setting. +-- +-- @field #string rotDefault Default 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 formation Current formation setting. -- +-- @field Core.Astar#ASTAR Astar path finding. +-- -- @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 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. -- @@ -206,16 +214,18 @@ OPSGROUP.TaskType={ --- Waypoint data. -- @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 #boolean onroad If true, ground group takes a road. --- @field #number formation The formation for this waypoint. --- @field #boolean detour If true, this waypoint is not part of the normal route. +-- @field #number speed Speed in m/s. +-- @field #number alt Altitude in meters. For submaries use negative sign for depth. -- @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. -- @field #string version @@ -433,79 +443,7 @@ function OPSGROUP:GetHeading() return nil 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. -- @param #OPSGROUP self @@ -524,29 +462,6 @@ function OPSGROUP:CheckTaskDescriptionUnique(description) return true 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. -- @param #OPSGROUP self @@ -707,6 +622,111 @@ function OPSGROUP:GetWaypointIndex(uid) return nil 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. -- @param #OPSGROUP self -- @param #number uid Waypoint UID. @@ -716,8 +736,7 @@ function OPSGROUP:RemoveWaypointByID(uid) local index=self:GetWaypointIndex(uid) if index then - self:RemoveWaypoint(index) - + self:RemoveWaypoint(index) end return self @@ -741,24 +760,47 @@ function OPSGROUP:RemoveWaypoint(wpindex) local n=#self.waypoints -- 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. 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 self.passedfinalwp=true 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 -- 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! - self.currentwp=self.currentwp-1 + + -- If current wp = 1 it stays 1. Otherwise decrease current wp. + + 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 @@ -995,9 +1037,9 @@ end --- Get the unfinished waypoint tasks -- @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 {}. -function OPSGROUP:GetTasksWaypoint(n) +function OPSGROUP:GetTasksWaypoint(id) -- Tasks table. local tasks={} @@ -1008,7 +1050,7 @@ function OPSGROUP:GetTasksWaypoint(n) -- Look for first task that SCHEDULED. for _,_task in pairs(self.taskqueue) do 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) end end @@ -1913,19 +1955,48 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #number n Waypoint passed. --- @param #number N Total number of waypoints. -- @param #OPSGROUP.Waypoint Waypoint Waypoint data passed. -function OPSGROUP:onafterPassingWaypoint(From, Event, To, n, N, 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) +function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) + -- 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. local tasks=self:GetTasksWaypoint(Waypoint.uid) - + -- 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 for i,_task in pairs(tasks) do local task=_task --#OPSGROUP.Task @@ -1963,20 +2034,8 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, n, N, Waypoint) if #taskswp>0 then self:SetTask(self.group:TaskCombo(taskswp)) end - - -- Final AIR waypoint reached? - if n==N 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 #taskswp==0 then - self:_CheckGroupDone(1) - end - - end + return #taskswp end --- 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. -- @param #OPSGROUP self --- @param #table waypoint DCS waypoint data table. +-- @param #OPSGROUP.Waypoint waypoint DCS waypoint data table. -- @return #OPSGROUP.Waypoint Waypoint data. -function OPSGROUP:_CreateWaypoint(waypoint, detour, onroad, formation) +function OPSGROUP:_CreateWaypoint(waypoint, formation, detour) waypoint.uid=self.wpcounter waypoint.coordinate=COORDINATE:New(waypoint.x, waypoint.alt, waypoint.y) waypoint.detour=detour and detour or false waypoint.formation=formation + if formation then + waypoint.action=formation + end waypoint.onroad=onroad and onroad or false self.wpcounter=self.wpcounter+1 @@ -2218,7 +2280,7 @@ function OPSGROUP:_AddWaypoint(waypoint, wpnumber) 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. table.insert(self.waypoints, wpnumber, waypoint) @@ -2263,7 +2325,7 @@ end --- Route group along waypoints. -- @param #OPSGROUP self -- @param #table waypoints Table of waypoints. --- @default +-- @param #number delay Delay in seconds. -- @return #OPSGROUP self function OPSGROUP:Route(waypoints, delay) @@ -2314,13 +2376,13 @@ function OPSGROUP:_UpdateWaypointTasks(n) if i>=n or nwaypoints==1 then -- 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 local taskswp={} -- 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) -- Waypoint task combo. @@ -2337,26 +2399,72 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- 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 #number i Waypoint number that has been reached. --@param #number uid Waypoint UID. -function OPSGROUP._PassingWaypoint(group, opsgroup, i, 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 +function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Get waypoint data. local waypoint=opsgroup:GetWaypointByID(uid) - - -- Trigger PassingWaypoint event. - opsgroup:PassingWaypoint(i, final, waypoint) + + if waypoint then + + -- 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 @@ -2461,6 +2569,52 @@ function OPSGROUP:SetOptionROT(rot) return self 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index eaef38b47..947b23299 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -222,6 +222,15 @@ ENUMS.Formation.RotaryWing.EchelonLeft={} ENUMS.Formation.RotaryWing.EchelonLeft.D70 =590081 ENUMS.Formation.RotaryWing.EchelonLeft.D300=590082 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. -- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki.