mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-07-17 18:22:19 +00:00
Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c335881b2d | |||
| 993fc45e83 | |||
| 130d358f4a | |||
| 49476abb9f | |||
| a02b99036b | |||
| dab6f48fe9 | |||
| 23c9ea0961 | |||
| d0b04ebbbd | |||
| bd64d084e9 | |||
| c91b59f8cd | |||
| a699300f5f | |||
| 6e12c5c1d0 | |||
| 852fc1d2d5 | |||
| c2c957b859 | |||
| f20c22ab49 | |||
| 1e200511cb | |||
| 80f2a0ef39 | |||
| ae156259c0 | |||
| 39e3f8b646 | |||
| ac61c3a55c | |||
| 1054ffa7ac | |||
| 41503ae625 | |||
| 1ee90e67b2 | |||
| d0ce7f1aa6 | |||
| c387749a81 | |||
| b980c5d93a | |||
| 49332fafec | |||
| 3ab3278db4 | |||
| 7a3ea2918d | |||
| 9c0394f035 | |||
| 9536d368b0 | |||
| 9426905959 | |||
| 99b7b4b057 | |||
| 2fdbdb4058 | |||
| 898405e15f | |||
| 7259038b1c | |||
| 3886351490 | |||
| 3aedf8e5d0 | |||
| aed2f1d8a9 | |||
| 3504797147 | |||
| 3cfaa60ebb | |||
| 2d930ff2dd | |||
| 11a8792398 | |||
| f7556fac0e | |||
| 394f3ffd88 | |||
| a3d7b4eba9 | |||
| b94f36dfdf | |||
| 8eae7ac26b | |||
| 7bac1d3b92 | |||
| b31b11cda8 | |||
| d000ad76f8 | |||
| 29b244d843 | |||
| 1eb6dbc7f2 | |||
| 945b9bdf31 | |||
| b75e383956 | |||
| 15de1d58b0 | |||
| 79ce3871ea | |||
| 84c68ccf14 | |||
| 93d0a8b674 | |||
| 8a33ccd356 | |||
| 9473cc2b27 | |||
| 448d668b44 | |||
| 8c0e021e82 | |||
| a72d3afd1c | |||
| 68aab10902 | |||
| b712c44567 | |||
| 12a118d08f | |||
| b1b9db7333 | |||
| 5f920a46fd | |||
| 23e3aefaf1 | |||
| 74e38f9755 | |||
| 69a781a710 | |||
| 2c82513e54 | |||
| 8eeff7efa2 | |||
| 7fcac6159c | |||
| a5eef3087b | |||
| af891f3fc2 | |||
| 8e47a17c96 | |||
| c2c78dfe78 | |||
| 26742432f5 | |||
| 6a13e593ec | |||
| 8d8b886ad6 | |||
| 2e6cc61450 | |||
| 6859dc9142 | |||
| b871f728df | |||
| 87b87fa2ca | |||
| a39f39646d | |||
| 157f9a82b1 | |||
| 795f58dc9b | |||
| 976f92e5b1 | |||
| 25daa67554 | |||
| 33ff723bd8 | |||
| 34f3e24d3a | |||
| b6a90d4633 | |||
| 0e960b77b9 | |||
| 093526f4ff | |||
| 8160aad1bf | |||
| a88750908e | |||
| aca3080c9a | |||
| 948158e85a | |||
| 98539eee87 | |||
| 223c1650d9 | |||
| aa2133e72a | |||
| 498d58599b | |||
| a0c0fe36ba | |||
| 76b977ff98 | |||
| 878eaf4301 | |||
| 240d3460d7 | |||
| ae0d405350 | |||
| 45fc0eb9da | |||
| 3327c3b24d | |||
| c6521d9c38 | |||
| 049529ecf0 | |||
| c2b8eea010 | |||
| da8856c907 | |||
| 04f7bb7cc7 | |||
| 75c01fbe86 | |||
| 03b79351db | |||
| 33429766a0 | |||
| 83464090d6 | |||
| d77b2655b1 | |||
| 31a537748e | |||
| e873b097cf | |||
| 07a3095392 | |||
| e1b5c7f9c2 | |||
| f005cdbc71 | |||
| 7fa360f32c | |||
| 30c6937910 | |||
| 28724f396c | |||
| fb3ca6d9d5 | |||
| 6e75e58952 | |||
| 0abb0db2a3 | |||
| edb42bbbe9 | |||
| 3bebe7f69c | |||
| 7605b582d1 | |||
| 6ed031762b | |||
| 945efc0516 | |||
| 4e342a59be | |||
| 8e517bb9cf | |||
| dacf5125bf |
@@ -105,7 +105,7 @@ jobs:
|
||||
if: ${{ vars.SKIP_LUACHECK != true }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
luacheck --std=lua51c --config=.luacheckrc -gurasqq "Moose Development/Moose"
|
||||
luacheck --std=lua51c --config=.luacheckrc -guras "Moose Development/Moose"
|
||||
|
||||
- name: Run LuaSrcDiet
|
||||
run: |
|
||||
|
||||
@@ -157,8 +157,6 @@ ASTAR = {
|
||||
-- @field #number surfacetype Surface type.
|
||||
-- @field #table valid Cached valid/invalid nodes.
|
||||
-- @field #table cost Cached cost.
|
||||
-- @field Core.Pathline#PATHLINE pathline Pathline that node is part of.
|
||||
-- @field Core.Pathline#PATHLINE.Point pathpoint Pathline point.
|
||||
|
||||
--- ASTAR infinity.
|
||||
-- @field #number INF
|
||||
@@ -166,7 +164,7 @@ ASTAR.INF=1/0
|
||||
|
||||
--- ASTAR class version.
|
||||
-- @field #string version
|
||||
ASTAR.version="0.5.0"
|
||||
ASTAR.version="0.4.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -174,7 +172,6 @@ ASTAR.version="0.5.0"
|
||||
|
||||
-- TODO: Add more valid neighbour functions.
|
||||
-- TODO: Write docs.
|
||||
-- DONE: Add pathlines for seach/valid neighbours.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@@ -247,10 +244,7 @@ end
|
||||
function ASTAR:AddNode(Node)
|
||||
|
||||
self.nodes[Node.id]=Node
|
||||
self.Nnodes=self.Nnodes+1
|
||||
|
||||
self:T3(self.lid..string.format("Adding node UID=%d", Node.id))
|
||||
--Node.coordinate:MarkToAll(string.format("Node ID=%d", Node.id))
|
||||
self.Nnodes=self.Nnodes+1
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -268,47 +262,6 @@ function ASTAR:AddNodeFromCoordinate(Coordinate)
|
||||
return node
|
||||
end
|
||||
|
||||
--- Adds nodes to the table of grid nodes from a PATHLINE.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Pathline#PATHLINE Pathline Pathline or name of pathline. Has to exist.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:AddNodeFromPathlineName(Pathline)
|
||||
|
||||
if type(Pathline)=="string" then
|
||||
Pathline=PATHLINE:FindByName(Pathline)
|
||||
end
|
||||
|
||||
if Pathline then
|
||||
|
||||
for i,_point in pairs(Pathline.points) do
|
||||
local point=_point --Core.Pathline#PATHLINE.Point
|
||||
|
||||
-- Create node from point coordinate.
|
||||
local node=self:AddNodeFromCoordinate(COORDINATE:NewFromVec3(point.vec3))
|
||||
|
||||
-- Add pathline parameters.
|
||||
node.pathline=Pathline
|
||||
node.pathpoint=point
|
||||
|
||||
-- Debug.
|
||||
local name=node.pathline and node.pathline.name or "N/A"
|
||||
local idx=node.pathline and node.pathline:_GetPointIndex(node.pathpoint) or "N/A"
|
||||
|
||||
-- Debug message.
|
||||
self:T(self.lid..string.format("Adding node UID=%d pathline=%s [%s]", node.id, name, tostring(idx)))
|
||||
|
||||
-- Debug mark
|
||||
--node.coordinate:MarkToAll(string.format("Node ID=%d\npathline=%s [%s]", node.id, name, tostring(idx)))
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
env.error("FF error pathline")
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if the coordinate of a node has is at a valid surface type.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added.
|
||||
@@ -387,18 +340,6 @@ function ASTAR:SetValidNeighbourRoad(MaxDistance)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set valid neighbours to be on the same pathline or not further apart than 10 meters to jump from one pathline to another.
|
||||
-- @param #ASTAR self
|
||||
-- @param #number MaxDistance Max allowed distance between nodes of different pathlines in meters. Default is 10 m.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourPathline(MaxDistance)
|
||||
|
||||
self:SetValidNeighbourFunction(ASTAR.Pathline, 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.
|
||||
@@ -443,7 +384,7 @@ end
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostRoad()
|
||||
|
||||
self:SetCostFunction(ASTAR.Road)
|
||||
self:SetCostFunction(ASTAR)
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -603,55 +544,6 @@ function ASTAR.Road(nodeA, nodeB)
|
||||
|
||||
end
|
||||
|
||||
--- Function to check if two nodes are on the same pathline or if nodes are less than 10 meters apart.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @param #number distmax Max distance in meters. Default is 10 m.
|
||||
-- @return #boolean If true, two nodes are connected.
|
||||
function ASTAR.Pathline(nodeA, nodeB, distmax)
|
||||
|
||||
distmax=distmax or 10
|
||||
|
||||
if nodeA.pathline.name==nodeB.pathline.name then
|
||||
|
||||
-- Nodes are on the same pathline. We use the index to check if they are neighbours.
|
||||
|
||||
local pathline=nodeA.pathline
|
||||
|
||||
local idxA=pathline:_GetPointIndex(nodeA.pathpoint)
|
||||
local idxB=pathline:_GetPointIndex(nodeB.pathpoint)
|
||||
|
||||
if math.abs(idxA-idxB)<=1 then
|
||||
return true
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Check if nodeB is close to pathline of nodeA.
|
||||
local c, dist, segA=nodeA.pathline:GetClosestPoint3D(nodeB.coordinate)
|
||||
local seg=segA --Core.Pathline#PATHLINE.Segment
|
||||
|
||||
if dist<distmax and (nodeA.pathpoint.uid==seg.p1.uid or nodeA.pathpoint.uid==seg.p2.uid) then
|
||||
--env.info(string.format("FF NodeB=%d [pathline=%s] is close to NodeA=%d [pathline=%s] ==> valid neighbour", nodeB.id, nodeB.pathline.name, nodeA.id, nodeA.pathline.name))
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
-- Check if nodeA is close to pathline of nodeB.
|
||||
local c, dist, segB=nodeB.pathline:GetClosestPoint3D(nodeA.coordinate)
|
||||
local seg=segB --Core.Pathline#PATHLINE.Segment
|
||||
|
||||
if dist<distmax and (nodeB.pathpoint.uid==seg.p1.uid or nodeB.pathpoint.uid==seg.p2.uid) then
|
||||
--env.info(string.format("FF NodeA=%d [pathline=%s] is close to NodeB=%d [pathline=%s] ==> valid neighbour", nodeA.id, nodeA.pathline.name, nodeB.id, nodeB.pathline.name))
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- Function to check if distance between two nodes is less than a threshold distance.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
@@ -675,9 +567,7 @@ end
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #number Distance between the two nodes.
|
||||
function ASTAR.Dist2D(nodeA, nodeB)
|
||||
local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate)
|
||||
--local text=string.format("FF Cost Dist2D NodeA=%d-->NodeB=%d = %.1f", nodeA.id, nodeB.id, dist)
|
||||
--env.info(text)
|
||||
local dist=nodeA.coordinate:Get2DDistance(nodeB)
|
||||
return dist
|
||||
end
|
||||
|
||||
@@ -704,7 +594,7 @@ function ASTAR.DistRoad(nodeA, nodeB)
|
||||
local dist=0
|
||||
|
||||
for i=2,#path do
|
||||
local b=path[i] --DCS#Vec2
|
||||
local b=path[i] --DCS#Vec2
|
||||
local a=path[i-1] --DCS#Vec2
|
||||
|
||||
dist=dist+UTILS.VecDist2D(a,b)
|
||||
@@ -714,6 +604,7 @@ function ASTAR.DistRoad(nodeA, nodeB)
|
||||
return dist
|
||||
end
|
||||
|
||||
|
||||
return math.huge
|
||||
end
|
||||
|
||||
@@ -723,11 +614,10 @@ end
|
||||
|
||||
--- Find the closest node from a given coordinate.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate Reference coordinate.
|
||||
-- @param #table ExcludeNodes Table of nodes that are excluded.
|
||||
-- @return #ASTAR.Node Closest node to the coordinate.
|
||||
-- @param Core.Point#COORDINATE Coordinate.
|
||||
-- @return #ASTAR.Node Cloest node to the coordinate.
|
||||
-- @return #number Distance to closest node in meters.
|
||||
function ASTAR:FindClosestNode(Coordinate, ExcludeNodes)
|
||||
function ASTAR:FindClosestNode(Coordinate)
|
||||
|
||||
local distMin=math.huge
|
||||
local closeNode=nil
|
||||
@@ -735,15 +625,11 @@ function ASTAR:FindClosestNode(Coordinate, ExcludeNodes)
|
||||
for _,_node in pairs(self.nodes) do
|
||||
local node=_node --#ASTAR.Node
|
||||
|
||||
if ExcludeNodes==nil or self:_IsNodeNotInTable(ExcludeNodes, node) then
|
||||
local dist=node.coordinate:Get2DDistance(Coordinate)
|
||||
|
||||
local dist=node.coordinate:Get2DDistance(Coordinate)
|
||||
|
||||
if dist<distMin then
|
||||
distMin=dist
|
||||
closeNode=node
|
||||
end
|
||||
|
||||
if dist<distMin then
|
||||
distMin=dist
|
||||
closeNode=node
|
||||
end
|
||||
|
||||
end
|
||||
@@ -751,162 +637,38 @@ function ASTAR:FindClosestNode(Coordinate, ExcludeNodes)
|
||||
return closeNode, distMin
|
||||
end
|
||||
|
||||
--- Find the closest pathline to a given reference coordinate.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate Reference coordinate.
|
||||
-- @return Core.Pathline#PATHLINE Closest pathline
|
||||
-- @return #number Distance in meters.
|
||||
-- @return DCS#Vec3 Closest point on pathline to the ref coordinate.
|
||||
-- @return Core.Pathline#PATHLINE.Segment Segment.
|
||||
function ASTAR:FindClosestPathline(Coordinate)
|
||||
|
||||
local pathline=nil --Core.Pathline#PATHLINE
|
||||
local dist=math.huge
|
||||
local vec3=nil
|
||||
local S=nil
|
||||
|
||||
for _,_node in pairs(self.nodes) do
|
||||
local node=_node --#ASTAR.Node
|
||||
|
||||
if node.pathline then
|
||||
|
||||
local vec, d, s=node.pathline:GetClosestPoint3D(Coordinate)
|
||||
|
||||
if d<dist then
|
||||
pathline=node.pathline
|
||||
dist=d
|
||||
vec3=vec
|
||||
S=s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if pathline then
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Closest pathline %s: dist=%.1f", pathline.name, dist))
|
||||
end
|
||||
|
||||
return pathline, dist, vec3, S
|
||||
end
|
||||
|
||||
--- Find the closest node to the given coordinate.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coord Reference coordinate.
|
||||
-- @param #table ExcludeNodes Nodes that are excluded.
|
||||
-- @return #ASTAR.Node The node that was fround
|
||||
function ASTAR:_FindClosestTerminalNode(Coord, ExcludeNodes)
|
||||
|
||||
-- Find the closest pathline to the ref coordinate.
|
||||
local pathline, dist, vec3, s=self:FindClosestPathline(Coord)
|
||||
|
||||
-- Find the closest node to the given start coordinate.
|
||||
local node, dist2=self:FindClosestNode(Coord)
|
||||
|
||||
if pathline and vec3 and dist and dist2>dist then
|
||||
|
||||
-- Create a node on the closest pathline so we first go straight there and then along the pathline.
|
||||
local node=self:AddNodeFromCoordinate(COORDINATE:NewFromVec3(vec3))
|
||||
|
||||
-- We also need the pathline point.
|
||||
local point=pathline:AddPointFromVec3(vec3, nil, s.p1)
|
||||
|
||||
node.pathline=pathline
|
||||
node.pathpoint=point
|
||||
|
||||
self:T2(self.lid..string.format("Added new node=%d, which is closest to start coord. dist=%.1f m", node.id, dist))
|
||||
end
|
||||
|
||||
-- Find the closest node to the given start coordinate.
|
||||
local Node, dist3=self:FindClosestNode(Coord, ExcludeNodes)
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("CLOSEST node ID=%d, distance=%.1f", Node.id, dist3))
|
||||
|
||||
return Node, dist3
|
||||
end
|
||||
|
||||
|
||||
--- Find the start node.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added to the nodes table.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:FindStartNode()
|
||||
|
||||
local node, dist=self:FindClosestNode(self.startCoord)
|
||||
|
||||
-- Find the closest pathline to the
|
||||
local pathline, dist, vec3, s=self:FindClosestPathline(self.startCoord)
|
||||
|
||||
-- Find the closest node to the given start coordinate.
|
||||
local node, dist2=self:FindClosestNode(self.startCoord)
|
||||
self.startNode=node
|
||||
|
||||
if pathline and vec3 and dist and dist2>dist then
|
||||
|
||||
-- Create a node on the closest pathline so we first go straight there and then along the pathline.
|
||||
local node=self:AddNodeFromCoordinate(COORDINATE:NewFromVec3(vec3))
|
||||
|
||||
-- We also need the pathline point.
|
||||
local point=pathline:AddPointFromVec3(vec3, nil, s.p1)
|
||||
|
||||
node.pathline=pathline
|
||||
node.pathpoint=point
|
||||
|
||||
self:T2(self.lid..string.format("Added new node=%d, which is closest to start coord. dist=%.1f m", node.id, dist))
|
||||
if dist>1000 then
|
||||
self:T(self.lid.."Adding start node to node grid!")
|
||||
self:AddNode(node)
|
||||
end
|
||||
|
||||
-- Find the closest node to the given start coordinate.
|
||||
self.startNode, dist2=self:FindClosestNode(self.startCoord)
|
||||
|
||||
--self.startNode.coordinate:MarkToAll("Start Node")
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("START node ID=%d", self.startNode.id))
|
||||
|
||||
-- Not sure why I did this. The node does not need to be added again as it is already contained in self.nodes!
|
||||
-- if dist>1000 then
|
||||
-- self:T(self.lid.."Adding start node to node grid!")
|
||||
-- self:AddNode(node)
|
||||
-- end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Find the end node.
|
||||
--- Add a node.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added to the nodes table.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:FindEndNode()
|
||||
|
||||
local pathline, dist, vec3, s=self:FindClosestPathline(self.endCoord)
|
||||
|
||||
-- Find the closest node to the given start coordinate.
|
||||
local node, dist2=self:FindClosestNode(self.endCoord)
|
||||
|
||||
if pathline and vec3 and dist and dist2>dist then
|
||||
|
||||
-- Create a node on the closest pathline so we first go straight there and then along the pathline.
|
||||
local node=self:AddNodeFromCoordinate(COORDINATE:NewFromVec3(vec3))
|
||||
|
||||
-- We also need the point.
|
||||
local point=pathline:AddPointFromVec3(vec3, nil, s.p1)
|
||||
|
||||
-- Add pathline parameters to node.
|
||||
node.pathline=pathline
|
||||
node.pathpoint=point
|
||||
|
||||
self:T2(self.lid..string.format("Added new node=%d, which is closest to END coord: dist=%.1f m", node.id, dist))
|
||||
local node, dist=self:FindClosestNode(self.endCoord)
|
||||
|
||||
self.endNode=node
|
||||
|
||||
if dist>1000 then
|
||||
self:T(self.lid.."Adding end node to node grid!")
|
||||
self:AddNode(node)
|
||||
end
|
||||
|
||||
-- Find closest node to the end coordinate (exclude the start coordinate.
|
||||
self.endNode, dist=self:FindClosestNode(self.endCoord, {self.startNode})
|
||||
|
||||
--self.endNode.coordinate:MarkToAll("End Node")
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("END node ID=%d", self.endNode.id))
|
||||
|
||||
-- Not sure why I did this. The node does not need to be added again as it is already contained in self.nodes!
|
||||
-- if dist>1000 then
|
||||
-- self:T(self.lid.."Adding end node to node grid!")
|
||||
-- self:AddNode(node)
|
||||
-- end
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -922,21 +684,12 @@ end
|
||||
-- @return #table Table of nodes from start to finish.
|
||||
function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
|
||||
|
||||
-- self:FindStartNode()
|
||||
-- self:FindEndNode()
|
||||
|
||||
-- Find start Node (closest node to start coordinate).
|
||||
self.startNode=self:_FindClosestTerminalNode(self.startCoord)
|
||||
|
||||
-- Find end node, which is not the start node (excluded).
|
||||
self.endNode=self:_FindClosestTerminalNode(self.endCoord, {self.startNode})
|
||||
self:FindStartNode()
|
||||
self:FindEndNode()
|
||||
|
||||
local nodes=self.nodes
|
||||
local start=self.startNode
|
||||
local goal=self.endNode
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("GetPath Start Node=%d, End Node=%d", start.id, goal.id))
|
||||
|
||||
-- Sets.
|
||||
local openset = {}
|
||||
@@ -993,12 +746,7 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
|
||||
text=text..string.format(", OS Time %.6f sec", dT)
|
||||
end
|
||||
text=text..string.format(", Nvalid=%d [%d cached]", self.nvalid, self.nvalidcache)
|
||||
text=text..string.format(", Ncost=%d [%d cached]", self.ncost, self.ncostcache)
|
||||
text=text..string.format("\nNodes:")
|
||||
for i,_node in ipairs(path) do
|
||||
local node=_node --#ASTAR.Node
|
||||
text=text..string.format("\n[%d] Node ID=%d", i, node.id)
|
||||
end
|
||||
text=text..string.format(", Ncost=%d [%d cached]", self.ncost, self.ncostcache)
|
||||
self:T(self.lid..text)
|
||||
|
||||
return path
|
||||
@@ -1011,16 +759,13 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
|
||||
|
||||
-- Get neighbour nodes.
|
||||
local neighbors=self:_NeighbourNodes(current, nodes)
|
||||
|
||||
|
||||
-- Loop over neighbours.
|
||||
for _,neighbor in pairs(neighbors) do
|
||||
|
||||
-- Node is not in closed set.
|
||||
if self:_NotIn(closedset, neighbor.id) then
|
||||
|
||||
-- Calculate tentative_g_score.
|
||||
--local tentative_g_score=g_score[current.id] + self:_DistNodes(current, neighbor)
|
||||
local tentative_g_score=g_score[current.id] + self:_HeuristicCost(current, neighbor)
|
||||
local tentative_g_score=g_score[current.id]+self:_DistNodes(current, neighbor)
|
||||
|
||||
if self:_NotIn(openset, neighbor.id) or tentative_g_score < g_score[neighbor.id] then
|
||||
|
||||
@@ -1048,73 +793,6 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
|
||||
return nil -- no valid path
|
||||
end
|
||||
|
||||
--- A* pathfinding function. This seaches the path along nodes between start and end nodes/coordinates.
|
||||
-- It automatically creates a PATHLINE object that is returned in combination with the nodes of the optimal path.
|
||||
-- @param #ASTAR self
|
||||
-- @param #boolean ExcludeStartNode If *true*, do not include start node in found path. Default is to include it.
|
||||
-- @param #boolean ExcludeEndNode If *true*, do not include end node in found path. Default is to include it.
|
||||
-- @return Core.Pathline#PATHLINE Pathline.
|
||||
-- @return #table Nodes of path.
|
||||
function ASTAR:GetPathline(ExcludeStartNode, ExcludeEndNode)
|
||||
|
||||
local nodes=self:GetPath(ExcludeStartNode, ExcludeEndNode)
|
||||
|
||||
local pathline=nil --Core.Pathline#PATHLINE
|
||||
if nodes then
|
||||
|
||||
pathline=PATHLINE:New("Astar")
|
||||
|
||||
for _,_node in pairs(nodes) do
|
||||
local node=_node --#ASTAR.Node
|
||||
|
||||
local point=pathline:AddPointFromVec3(node.coordinate)
|
||||
point.name=node.pathline.name
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return pathline, nodes
|
||||
end
|
||||
|
||||
--- Get pathlines from nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table Nodes Given nodes.
|
||||
-- @return #table Table of PATHLINES used in the path.
|
||||
function ASTAR:GetPathlinesFromNodes(Nodes)
|
||||
|
||||
local pathlines={}
|
||||
|
||||
--for _,_node in pairs(Nodes or {}) do
|
||||
for i=1,#Nodes do
|
||||
local node=Nodes[i] --#ASTAR.Node
|
||||
|
||||
-- Pathline.
|
||||
local pathline=node.pathline
|
||||
|
||||
if pathline and i>1 and i<#Nodes then
|
||||
|
||||
-- Previous and next nodes.
|
||||
local n=Nodes[i-1] --#ASTAR.Node
|
||||
local N=Nodes[i+1] --#ASTAR.Node
|
||||
|
||||
-- Check if previous and next nodes are on the same pathline.
|
||||
-- If only one point in beteen is of another pathline, this is a junction and we dont actually switch to the other pathline.
|
||||
if n.pathline and N.pathline and n.pathline.name==N.pathline.name and n.pathline.name~=pathline.name then
|
||||
pathline=n.pathline
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- We do not want to add the same pathline two times in a row.
|
||||
if #pathlines==0 or (#pathlines>0 and pathlines[#pathlines].name~=pathline.name) then
|
||||
table.insert(pathlines, pathline)
|
||||
end
|
||||
end
|
||||
|
||||
return pathlines
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- A* pathfinding helper functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1133,18 +811,16 @@ function ASTAR:_HeuristicCost(nodeA, nodeB)
|
||||
local cost=nodeA.cost[nodeB.id]
|
||||
if cost~=nil then
|
||||
self.ncostcache=self.ncostcache+1
|
||||
self:T(self.lid..string.format("Cost nodeA=%d --> nodeB=%d = %.1f (Cashed!)", nodeA.id, nodeB.id, cost))
|
||||
return cost
|
||||
end
|
||||
|
||||
local cost=nil
|
||||
if self.CostFunc then
|
||||
cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg))
|
||||
else
|
||||
cost=self:_DistNodes(nodeA, nodeB)
|
||||
end
|
||||
|
||||
self:T(self.lid..string.format("Cost nodeA=%d --> nodeB=%d = %.1f", nodeA.id, nodeB.id, cost))
|
||||
|
||||
nodeA.cost[nodeB.id]=cost
|
||||
nodeB.cost[nodeA.id]=cost -- Symmetric problem.
|
||||
|
||||
@@ -1158,10 +834,9 @@ end
|
||||
-- @return #boolean If true, transition between nodes is possible.
|
||||
function ASTAR:_IsValidNeighbour(node, neighbor)
|
||||
|
||||
-- Counter of function calls.
|
||||
-- Counter.
|
||||
self.nvalid=self.nvalid+1
|
||||
|
||||
-- Check if neighbour is in cached set.
|
||||
local valid=node.valid[neighbor.id]
|
||||
if valid~=nil then
|
||||
--env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id))
|
||||
@@ -1169,16 +844,13 @@ function ASTAR:_IsValidNeighbour(node, neighbor)
|
||||
return valid
|
||||
end
|
||||
|
||||
-- Check if this is a valid neighbour.
|
||||
local valid=nil
|
||||
if self.ValidNeighbourFunc then
|
||||
valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg))
|
||||
else
|
||||
-- If no valid neighbour function is defined, we assume all nodes are valid neighbours.
|
||||
valid=true
|
||||
end
|
||||
|
||||
-- Cache valid neighbour.
|
||||
node.valid[neighbor.id]=valid
|
||||
neighbor.valid[node.id]=valid -- Symmetric problem.
|
||||
|
||||
@@ -1212,9 +884,6 @@ function ASTAR:_LowestFscore(set, f_score)
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Lowest Fscore=%.1f, Node=%s", lowest, tostring(bestNode)))
|
||||
|
||||
return self.nodes[bestNode]
|
||||
end
|
||||
|
||||
@@ -1259,46 +928,16 @@ 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 )
|
||||
|
||||
local previous_node=map[current_node]
|
||||
|
||||
if previous_node then
|
||||
table.insert(flat_path, 1, previous_node)
|
||||
return self:_UnwindPath(flat_path, map, previous_node)
|
||||
if map [current_node] then
|
||||
table.insert (flat_path, 1, map[current_node])
|
||||
return self:_UnwindPath(flat_path, map, map[current_node])
|
||||
else
|
||||
-- No previous node ==> return path.
|
||||
return flat_path
|
||||
end
|
||||
end
|
||||
|
||||
--- Function to check if a certain node is in a given table.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table Nodes Nodes table.
|
||||
-- @param #ASTAR.Node Node The node to check.
|
||||
-- @return #boolean If true, the node is not in the set.
|
||||
function ASTAR:_IsNodeInTable(Nodes, Node)
|
||||
|
||||
for _,_node in pairs(Nodes) do
|
||||
local node=_node --#ASTAR.Node
|
||||
if node.id==Node.id then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Function to check if a certain node is **not** in a given table.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table Nodes Nodes table.
|
||||
-- @param #ASTAR.Node Node The node to check.
|
||||
-- @return #boolean If true, the node is not in the set.
|
||||
function ASTAR:_IsNodeNotInTable(Nodes, Node)
|
||||
local is=self:_IsNodeInTable(Nodes, Node)
|
||||
return not is
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1820,7 +1820,7 @@ function DATABASE:_EventOnPlayerEnterUnit( Event )
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
-- Player entering a CA slot
|
||||
if Event.IniObjectCategory == 1 and Event.IniGroup and Event.IniGroup:IsGround() then
|
||||
if Event.IniObjectCategory == 1 and Event.IniUnit and Event.IniUnit:IsGround() then
|
||||
|
||||
local IsPlayer = Event.IniDCSUnit:getPlayerName()
|
||||
if IsPlayer then
|
||||
|
||||
@@ -184,6 +184,7 @@ EVENT = {
|
||||
ClassName = "EVENT",
|
||||
ClassID = 0,
|
||||
MissionEnd = false,
|
||||
CreateMarkCoordinateOnEvent = false,
|
||||
}
|
||||
|
||||
world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000
|
||||
@@ -339,14 +340,18 @@ EVENTS = {
|
||||
-- @field #string WeaponName Name of the weapon.
|
||||
-- @field DCS#Unit WeaponTgtDCSUnit Target DCS unit of the weapon.
|
||||
--
|
||||
-- @field Cargo.Cargo#CARGO Cargo The cargo object.
|
||||
-- @field #string CargoName The name of the cargo object.
|
||||
--
|
||||
-- @field Core.Zone#ZONE Zone The zone object.
|
||||
-- @field #string ZoneName The name of the zone.
|
||||
--
|
||||
-- @field Wrapper.DynamicCargo#DYNAMICCARGO IniDynamicCargo The dynamic cargo object.
|
||||
-- @field #string IniDynamicCargoName The dynamic cargo unit name.
|
||||
--
|
||||
-- @field #number MarkCoalition Coalition of a marker (if any)
|
||||
-- @field DCS#Vec3 MarkVec3 Position of a marker
|
||||
-- @field #string MarkText Text content of a marker, if any
|
||||
-- @field #number MarkID ID of the marker, for deletion
|
||||
-- @field #number MarkGroupID Group ID of the group that created the marker
|
||||
-- @field Core.Point#COORDINATE Coordinate object of the marker, only filled if EVENT.CreateMarkCoordinateOnEvent is set to true (off by default)
|
||||
|
||||
|
||||
|
||||
@@ -1063,36 +1068,6 @@ end
|
||||
|
||||
do -- Event Creation
|
||||
|
||||
--- Creation of a New Cargo Event.
|
||||
-- @param #EVENT self
|
||||
-- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created.
|
||||
function EVENT:CreateEventNewCargo( Cargo )
|
||||
self:F( { Cargo } )
|
||||
|
||||
local Event = {
|
||||
id = EVENTS.NewCargo,
|
||||
time = timer.getTime(),
|
||||
cargo = Cargo,
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a Cargo Deletion Event.
|
||||
-- @param #EVENT self
|
||||
-- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created.
|
||||
function EVENT:CreateEventDeleteCargo( Cargo )
|
||||
self:F( { Cargo } )
|
||||
|
||||
local Event = {
|
||||
id = EVENTS.DeleteCargo,
|
||||
time = timer.getTime(),
|
||||
cargo = Cargo,
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a New Zone Event.
|
||||
-- @param #EVENT self
|
||||
-- @param Core.Zone#ZONE_BASE Zone The Zone created.
|
||||
@@ -1361,7 +1336,8 @@ function EVENT:onEvent( Event )
|
||||
Event.IniDynamicCargoName = Event.IniUnitName
|
||||
Event.IniPlayerName = string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+")
|
||||
else
|
||||
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
|
||||
--Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
|
||||
Event.IniUnit = STATIC:FindByName( Event.IniDCSUnitName, false )
|
||||
end
|
||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
@@ -1374,7 +1350,9 @@ function EVENT:onEvent( Event )
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = ( Event.IniDCSUnit and Event.IniDCSUnit.getName ) and Event.IniDCSUnit:getName() or "Scenery no name "..math.random(1,20000)
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
|
||||
local ID = (Event.IniDCSUnit and Event.IniDCSUnit.getID) and Event.IniDCSUnit:getID() or Event.IniDCSUnitName
|
||||
Event.IniUnit = (_SCENERY ~= nil) and _SCENERY[ID] or nil
|
||||
--Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
|
||||
Event.IniCategory = (Event.IniDCSUnit and Event.IniDCSUnit.getDesc ) and Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = (Event.initiator and Event.initiator.isExist
|
||||
and Event.initiator:isExist() and Event.IniDCSUnit and Event.IniDCSUnit.getTypeName) and Event.IniDCSUnit:getTypeName() or "SCENERY"
|
||||
@@ -1478,7 +1456,9 @@ function EVENT:onEvent( Event )
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName() or nil
|
||||
if Event.TgtDCSUnitName~=nil then
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
|
||||
local ID = (Event.TgtDCSUnit and Event.TgtDCSUnit.getID) and Event.TgtDCSUnit:getID() or Event.TgtDCSUnitName
|
||||
--Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
|
||||
Event.TgtUnit = (_SCENERY ~= nil) and _SCENERY[ID] or nil
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
end
|
||||
@@ -1519,7 +1499,9 @@ function EVENT:onEvent( Event )
|
||||
if Event.idx then
|
||||
Event.MarkID=Event.idx
|
||||
Event.MarkVec3=Event.pos
|
||||
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
|
||||
if self.CreateMarkCoordinateOnEvent == true then
|
||||
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
|
||||
end
|
||||
Event.MarkText=Event.text
|
||||
Event.MarkCoalition=Event.coalition
|
||||
Event.IniCoalition=Event.coalition
|
||||
|
||||
@@ -50,7 +50,7 @@ MARKEROPS_BASE = {
|
||||
ClassName = "MARKEROPS",
|
||||
Tag = "mytag",
|
||||
Keywords = {},
|
||||
version = "0.1.4",
|
||||
version = "0.1.5",
|
||||
debug = false,
|
||||
Casesensitive = true,
|
||||
}
|
||||
@@ -59,9 +59,8 @@ MARKEROPS_BASE = {
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string Tagname Name to identify us from the event text.
|
||||
-- @param #table Keywords Table of keywords recognized from the event text.
|
||||
-- @param #boolean Casesensitive (Optional) Switch case sensitive identification of Tagname. Defaults to true.
|
||||
-- @return #MARKEROPS_BASE self
|
||||
function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
function MARKEROPS_BASE:New(Tagname,Keywords)
|
||||
-- Inherit FSM
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE
|
||||
|
||||
@@ -72,11 +71,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too
|
||||
self.debug = false
|
||||
self.Casesensitive = true
|
||||
|
||||
if Casesensitive and Casesensitive == false then
|
||||
self.Casesensitive = false
|
||||
end
|
||||
|
||||
|
||||
-----------------------
|
||||
--- FSM Transitions ---
|
||||
-----------------------
|
||||
@@ -145,7 +140,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
|
||||
end
|
||||
|
||||
--- (internal) Handle events.
|
||||
--- (Internal) Handle events.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function MARKEROPS_BASE:OnEventMark(Event)
|
||||
@@ -201,15 +196,17 @@ function MARKEROPS_BASE:OnEventMark(Event)
|
||||
end
|
||||
end
|
||||
|
||||
--- (internal) Match tag.
|
||||
--- (Internal) Match tag.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #boolean
|
||||
function MARKEROPS_BASE:_MatchTag(Eventtext)
|
||||
local matches = false
|
||||
if not self.Casesensitive then
|
||||
--self:I(self.lid .. "Casesensitive "..tostring(self.Casesensitive))
|
||||
if self.Casesensitive == false then
|
||||
--self:I(self.lid .. "Marker non-casesensitive "..Eventtext)
|
||||
local type = string.lower(self.Tag) -- #string
|
||||
if string.find(string.lower(Eventtext),type) then
|
||||
if string.find(string.lower(Eventtext),type,1,true) then
|
||||
matches = true --event text contains tag
|
||||
end
|
||||
else
|
||||
@@ -221,7 +218,7 @@ function MARKEROPS_BASE:_MatchTag(Eventtext)
|
||||
return matches
|
||||
end
|
||||
|
||||
--- (internal) Match keywords table.
|
||||
--- (Internal) Match keywords table.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #table
|
||||
@@ -286,6 +283,22 @@ function MARKEROPS_BASE:onenterStopped(From,Event,To)
|
||||
self:UnHandleEvent(EVENTS.MarkRemoved)
|
||||
end
|
||||
|
||||
--- Switch off case sensitive matching
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @return self
|
||||
function MARKEROPS_BASE:SwitchCaseSensitiveOff()
|
||||
self.Casesensitive = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch on case sensitive matching
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @return self
|
||||
function MARKEROPS_BASE:SwitchCaseSensitiveOn()
|
||||
self.Casesensitive = true
|
||||
return self
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- MARKEROPS_BASE Class Definition End.
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
-- * Path from A to B
|
||||
-- * Arbitrary number of points
|
||||
-- * Automatically from lines drawtool
|
||||
-- * Draw line or mark points on F10 map
|
||||
-- * Find closest points to path
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
-- @module Core.Pathline
|
||||
-- @image CORE_Pathline.png
|
||||
@@ -23,7 +21,6 @@
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #string name Name of the path line.
|
||||
-- @field #table points List of 3D points defining the path.
|
||||
-- @field #number counter Running number counting the point IDs.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *The shortest distance between two points is a straight line.* -- Archimedes
|
||||
@@ -31,30 +28,30 @@
|
||||
-- ===
|
||||
--
|
||||
-- # The PATHLINE Concept
|
||||
--
|
||||
--
|
||||
-- List of points defining a path from A to B. The pathline can consist of multiple points. Each point holds the information of its position, the surface type, the land height
|
||||
-- and the water depth (if over sea).
|
||||
--
|
||||
--
|
||||
-- Line drawings created in the mission editor are automatically registered as pathlines and stored in the MOOSE database.
|
||||
-- They can be accessed with the @{#PATHLINE.FindByName) function.
|
||||
--
|
||||
--
|
||||
-- # Constructor
|
||||
--
|
||||
--
|
||||
-- The @{PATHLINE.New) function creates a new PATHLINE object. This does not hold any points. Points can be added with the @{#PATHLINE.AddPointFromVec2} and @{#PATHLINE.AddPointFromVec3}
|
||||
--
|
||||
--
|
||||
-- For a given table of 2D or 3D positions, a new PATHLINE object can be created with the @{#PATHLINE.NewFromVec2Array} or @{#PATHLINE.NewFromVec3Array}, respectively.
|
||||
--
|
||||
--
|
||||
-- # Line Drawings
|
||||
--
|
||||
--
|
||||
-- The most convenient way to create a pathline is the draw panel feature in the DCS mission editor. You can select "Line" and then "Segments", "Segment" or "Free" to draw your lines.
|
||||
-- These line drawings are then automatically added to the MOOSE database as PATHLINE objects and can be retrieved with the @{#PATHLINE.FindByName) function, where the name is the one
|
||||
-- you specify in the draw panel.
|
||||
--
|
||||
--
|
||||
-- # Mark on F10 map
|
||||
--
|
||||
-- The points of the PATHLINE can be marked on the F10 map with the @{#PATHLINE.MarkPoints}(`true`) function. The mark points contain information of the surface type, land height and
|
||||
-- water depth.
|
||||
--
|
||||
--
|
||||
-- To remove the marks, use @{#PATHLINE.MarkPoints}(`false`).
|
||||
--
|
||||
-- @field #PATHLINE
|
||||
@@ -62,14 +59,10 @@ PATHLINE = {
|
||||
ClassName = "PATHLINE",
|
||||
lid = nil,
|
||||
points = {},
|
||||
counter = 0,
|
||||
}
|
||||
|
||||
--- Point of line.
|
||||
-- @type PATHLINE.Point
|
||||
-- @field #number uid Unique ID of this point.
|
||||
-- @field #string mother Name of the pathline this point belongs to.
|
||||
-- @field #string name Name of this point.
|
||||
-- @field DCS#Vec3 vec3 3D position.
|
||||
-- @field DCS#Vec2 vec2 2D position.
|
||||
-- @field #number surfaceType Surface type.
|
||||
@@ -87,9 +80,7 @@ PATHLINE.version="0.2.0"
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Read/write to JSON file
|
||||
-- TODO: Translate/rotate pathline
|
||||
-- TODO: Add color.
|
||||
-- TODO: A lot...
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@@ -103,10 +94,10 @@ function PATHLINE:New(Name)
|
||||
|
||||
-- Inherit everything from INTEL class.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#PATHLINE
|
||||
|
||||
|
||||
self.name=Name or "Unknown Path"
|
||||
|
||||
self.lid=string.format("PATHLINE %s | ", self.name)
|
||||
self.lid=string.format("PATHLINE %s | ", Name)
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -161,70 +152,41 @@ end
|
||||
--- Add a point to the path from a given 2D position. The third dimension is determined from the land height.
|
||||
-- @param #PATHLINE self
|
||||
-- @param DCS#Vec2 Vec2 The 2D vector (x,y) to add.
|
||||
-- @param #number Index Index to add this point, *e.g.* 1 for first point or 2 for second point. Default is at the end.
|
||||
-- @param #PATHLINE.Point Point Add point after given point. Default is at the end or at given index.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:AddPointFromVec2(Vec2, Index, Point)
|
||||
function PATHLINE:AddPointFromVec2(Vec2)
|
||||
|
||||
if Vec2 then
|
||||
|
||||
-- Create a new point.
|
||||
|
||||
local point=self:_CreatePoint(Vec2)
|
||||
|
||||
if Index then
|
||||
-- Add at given index.
|
||||
table.insert(self.points, Index, point)
|
||||
else
|
||||
if Point then
|
||||
-- Get index of given point.
|
||||
local i=self:_GetPointIndex(Point)
|
||||
-- Add new point after given point.
|
||||
table.insert(self.points, i+1, point)
|
||||
else
|
||||
-- Add add the end.
|
||||
table.insert(self.points, point)
|
||||
end
|
||||
end
|
||||
table.insert(self.points, point)
|
||||
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a point to the path from a given 3D position.
|
||||
-- @param #PATHLINE self
|
||||
-- @param DCS#Vec3 Vec3 The 3D vector (x,y) to add.
|
||||
-- @param #number Index Index to add this point, *e.g.* 1 for first point or 2 for second point. Default is at the end.
|
||||
-- @param #PATHLINE.Point Point Add point after given point. Default is at the end or at given index.
|
||||
-- @return #PATHLINE.Point Point that was added.
|
||||
function PATHLINE:AddPointFromVec3(Vec3, Index, Point)
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:AddPointFromVec3(Vec3)
|
||||
|
||||
if Vec3 then
|
||||
|
||||
|
||||
local point=self:_CreatePoint(Vec3)
|
||||
|
||||
if Index then
|
||||
-- Add add given index.
|
||||
table.insert(self.points, Index, point)
|
||||
else
|
||||
if Point then
|
||||
local i=self:_GetPointIndex(Point)
|
||||
table.insert(self.points, i+1, point)
|
||||
else
|
||||
-- Add add the end.
|
||||
table.insert(self.points, point)
|
||||
end
|
||||
end
|
||||
|
||||
return point
|
||||
table.insert(self.points, point)
|
||||
|
||||
end
|
||||
|
||||
return nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get name of pathline.
|
||||
-- @param #PATHLINE self
|
||||
-- @return #string Name of the pathline.
|
||||
function PATHLINE:GetName()
|
||||
function PATHLINE:GetName()
|
||||
return self.name
|
||||
end
|
||||
|
||||
@@ -238,35 +200,18 @@ end
|
||||
|
||||
--- Get points of pathline. Not that points are tables, that contain more information as just the 2D or 3D position but also the surface type etc.
|
||||
-- @param #PATHLINE self
|
||||
-- @return #list <Core.Pathline#PATHLINE.Point> List of points.
|
||||
function PATHLINE:GetPoints()
|
||||
-- @return #list <#PATHLINE.Point> List of points.
|
||||
function PATHLINE:GetPoints()
|
||||
return self.points
|
||||
end
|
||||
|
||||
--- Get segments of pathline.
|
||||
-- @param #PATHLINE self
|
||||
-- @return #list <Core.Pathline#PATHLINE.Segment> List of points.
|
||||
function PATHLINE:GetSetments()
|
||||
|
||||
local segments={}
|
||||
|
||||
for i=1,#self.points-1 do
|
||||
local segment={} --#PATHLINE.Segment
|
||||
segment.p1=self.points[i]
|
||||
segment.p2=self.points[i+1]
|
||||
table.insert(segments, segment)
|
||||
end
|
||||
|
||||
return segments
|
||||
end
|
||||
|
||||
--- Get 3D points of pathline.
|
||||
-- @param #PATHLINE self
|
||||
-- @return #list <DCS#Vec3> List of DCS#Vec3 points.
|
||||
-- @return <DCS#Vec3> List of DCS#Vec3 points.
|
||||
function PATHLINE:GetPoints3D()
|
||||
|
||||
local vecs={}
|
||||
|
||||
|
||||
for _,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
table.insert(vecs, point.vec3)
|
||||
@@ -277,11 +222,11 @@ end
|
||||
|
||||
--- Get 2D points of pathline.
|
||||
-- @param #PATHLINE self
|
||||
-- @return #list <DCS#Vec2> List of DCS#Vec2 points.
|
||||
-- @return <DCS#Vec2> List of DCS#Vec2 points.
|
||||
function PATHLINE:GetPoints2D()
|
||||
|
||||
local vecs={}
|
||||
|
||||
|
||||
for _,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
table.insert(vecs, point.vec2)
|
||||
@@ -296,11 +241,11 @@ end
|
||||
function PATHLINE:GetCoordinates()
|
||||
|
||||
local vecs={}
|
||||
|
||||
|
||||
for _,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
local coord=COORDINATE:NewFromVec3(point.vec3)
|
||||
table.insert(vecs, coord)
|
||||
table.insert(vecs,coord)
|
||||
end
|
||||
|
||||
return vecs
|
||||
@@ -313,11 +258,11 @@ end
|
||||
function PATHLINE:GetPointFromIndex(n)
|
||||
|
||||
local N=self:GetNumberOfPoints()
|
||||
|
||||
|
||||
n=n or 1
|
||||
|
||||
local point=nil --#PATHLINE.Point
|
||||
|
||||
|
||||
if n>=1 and n<=N then
|
||||
point=self.points[n]
|
||||
else
|
||||
@@ -334,11 +279,11 @@ end
|
||||
function PATHLINE:GetPoint3DFromIndex(n)
|
||||
|
||||
local point=self:GetPointFromIndex(n)
|
||||
|
||||
|
||||
if point then
|
||||
return point.vec3
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -349,11 +294,11 @@ end
|
||||
function PATHLINE:GetPoint2DFromIndex(n)
|
||||
|
||||
local point=self:GetPointFromIndex(n)
|
||||
|
||||
|
||||
if point then
|
||||
return point.vec2
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -387,10 +332,8 @@ end
|
||||
-- @param #boolean Switch If `true` or nil, set marks. If `false`, remove marks.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:MarkPoints(Switch)
|
||||
|
||||
for i,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
|
||||
if Switch==false then
|
||||
|
||||
if point.markerID then
|
||||
@@ -402,19 +345,13 @@ function PATHLINE:MarkPoints(Switch)
|
||||
if point.markerID then
|
||||
UTILS.RemoveMark(point.markerID)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
if point.markerID then
|
||||
UTILS.RemoveMark(point.markerID)
|
||||
end
|
||||
|
||||
|
||||
point.markerID=UTILS.GetMarkID()
|
||||
|
||||
local text=string.format("Pathline %s: Point #%d [UID=%d]\nSurface Type=%d\nHeight=%.1f m\nDepth=%.1f m", self.name, i, point.uid, point.surfaceType, point.landHeight, point.depth)
|
||||
|
||||
|
||||
local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m\nDepth=%.1f m", self.name, i, point.surfaceType, point.landHeight, point.depth)
|
||||
|
||||
trigger.action.markToAll(point.markerID, text, point.vec3, "")
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
return self
|
||||
@@ -470,283 +407,6 @@ function PATHLINE:UnDrawLine(Delay)
|
||||
end
|
||||
|
||||
|
||||
--- Draw line on F10 map.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #boolean Switch If `true` or nil, draw pathline. If `false`, remove drawing.
|
||||
-- @param #number Coalition Coalition side. Default -1 for all.
|
||||
-- @param #table Color RGB color and alpha `{r, g, b, a}`. Default {0, 1, 0, 0.5}.
|
||||
-- @param #number LineType Line type. Default 1=solid.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:Draw(Switch, Coalition, Color, LineType)
|
||||
|
||||
Coalition=Coalition or -1
|
||||
Color=Color or {0, 1, 0, 0.5}
|
||||
LineType=LineType or 1
|
||||
|
||||
if Switch==false then
|
||||
|
||||
for i,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
|
||||
if point.lineID then
|
||||
UTILS.RemoveMark(point.lineID)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
for i=2,#self.points do
|
||||
|
||||
local p1=self.points[i-1] --#PATHLINE.Point
|
||||
local p2=self.points[i] --#PATHLINE.Point
|
||||
|
||||
if p2.lineID then
|
||||
UTILS.RemoveMark(p2.lineID)
|
||||
end
|
||||
|
||||
p2.lineID=UTILS.GetMarkID()
|
||||
|
||||
trigger.action.lineToAll(Coalition, p2.lineID, p1.vec3, p2.vec3, Color, LineType)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the closest point on the pathline for a given reference point.
|
||||
-- @param #PATHLINE self
|
||||
-- @param DCS#Vec2 Vec2 Reference Point in 2D.
|
||||
-- @return DCS#Vec2 Cloest point on pathline.
|
||||
-- @return #number Distance from closest point to ref point in meters.
|
||||
-- @return #PATHLINE.Segment Closest segment of ref point.
|
||||
function PATHLINE:GetClosestPoint2D(Vec2)
|
||||
|
||||
local P=nil --DCS#Vec2
|
||||
local D=math.huge
|
||||
local S={} --#PATHLINE.Segment
|
||||
|
||||
for i=2,#self.points do
|
||||
|
||||
local A=self.points[i-1] --#PATHLINE.Point
|
||||
local B=self.points[i] --#PATHLINE.Point
|
||||
|
||||
local a=A.vec2
|
||||
local b=B.vec2
|
||||
|
||||
local ab=UTILS.Vec2Substract(b, a)
|
||||
local ap=UTILS.Vec2Substract(Vec2, a)
|
||||
|
||||
local proj=UTILS.Vec2Dot(ap, ab)
|
||||
|
||||
local lab=UTILS.Vec2Norm(ab)
|
||||
|
||||
local f=proj/lab/lab
|
||||
|
||||
-- Debug info.
|
||||
local text=string.format("FF Proj=%.1f, |ab|=%.1f, f=%.1f", proj, lab, f)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Cases for finite segment.
|
||||
local p=nil --DCS#Vec2
|
||||
if f<0 then
|
||||
p=a
|
||||
elseif f>1 then
|
||||
p=b
|
||||
else
|
||||
local r=UTILS.Vec2Mult(ab, f)
|
||||
p=UTILS.Vec2Add(a, r)
|
||||
end
|
||||
|
||||
-- Distance.
|
||||
local d=UTILS.VecDist2D(p, Vec2)
|
||||
|
||||
if d<=D then
|
||||
D=d
|
||||
P=p
|
||||
S.p1=A
|
||||
S.p2=B
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return P, D, S
|
||||
end
|
||||
|
||||
--- Get the closest point on the pathline for a given reference point.
|
||||
-- This point does not necessarily is a node of the pathline. In general it will be somewhere in between the nodes defining the pathline.
|
||||
-- @param #PATHLINE self
|
||||
-- @param DCS#Vec3 Vec3 Reference Point in 3D. Can also be a `COORDINATE`.
|
||||
-- @return DCS#Vec3 Closest point on pathline.
|
||||
-- @return #number Distance from closest point to ref point in meters.
|
||||
-- @return #PATHLINE.Segment Closest segment of ref point.
|
||||
function PATHLINE:GetClosestPoint3D(Vec3)
|
||||
|
||||
local P=nil --DCS#Vec3
|
||||
local D=math.huge
|
||||
local S={} --#PATHLINE.Segment
|
||||
|
||||
if not Vec3 then
|
||||
self:E(self.lid.."ERROR: input Vec3 is nil!")
|
||||
return nil, nil, nil
|
||||
end
|
||||
|
||||
for i=2,#self.points do
|
||||
|
||||
local A=self.points[i-1] --#PATHLINE.Point
|
||||
local B=self.points[i] --#PATHLINE.Point
|
||||
|
||||
local a=A.vec3
|
||||
local b=B.vec3
|
||||
|
||||
local ab=UTILS.VecSubstract(b, a)
|
||||
local ap=UTILS.VecSubstract(Vec3, a)
|
||||
|
||||
local proj=UTILS.VecDot(ap, ab)
|
||||
|
||||
local lab=UTILS.VecNorm(ab)
|
||||
|
||||
local f=proj/lab/lab
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Proj=%.1f, |ab|=%.1f, f=%.1f", proj, lab, f))
|
||||
|
||||
-- Cases for finite segment.
|
||||
local p=nil --DCS#Vec2
|
||||
if f<0 then
|
||||
p=a
|
||||
elseif f>1 then
|
||||
p=b
|
||||
else
|
||||
local r=UTILS.VecMult(ab, f)
|
||||
p=UTILS.VecAdd(a, r)
|
||||
end
|
||||
|
||||
-- Distance.
|
||||
local d=UTILS.VecDist3D(p, Vec3)
|
||||
|
||||
if d<=D then
|
||||
D=d
|
||||
P=p
|
||||
S.p1=A
|
||||
S.p2=B
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return P, D, S
|
||||
end
|
||||
|
||||
|
||||
--- Write PATHLINE to JSON file.
|
||||
-- **NOTE**: Requires `io` and `lfs` to be de-sanitized!
|
||||
-- @param #PATHLINE self
|
||||
-- @param #string FileName Name of the file. Default is the name of the pathline.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:WriteJSON(FileName)
|
||||
|
||||
if io and lfs then
|
||||
|
||||
-- JSON script.
|
||||
local json=loadfile("Scripts\\JSON.lua")()
|
||||
|
||||
local data={}
|
||||
|
||||
-- We store the name and the points.
|
||||
data.name=self.name
|
||||
data.points=self.points
|
||||
for i,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
--point.markerID=nil
|
||||
end
|
||||
|
||||
-- Encode data to raw JSON. Encode converts a lua table into JSON string that can be written to file.
|
||||
local raw_json=json:encode(data)
|
||||
|
||||
-- Debug data.
|
||||
self:T(data)
|
||||
|
||||
-- Write in "User/Saved Games/" Folder.
|
||||
local filepath=lfs.writedir() .. FileName
|
||||
|
||||
-- Open file for writing.
|
||||
local f = io.open(filepath, "wb")
|
||||
if f then
|
||||
f:write(raw_json)
|
||||
f:close()
|
||||
self:T(self.lid .. string.format("Saving PATHLINE %s file %s", self.name, tostring(filepath)))
|
||||
else
|
||||
self:E(self.lid .. string.format( "ERROR: Could not save PATHLINE to file %s", tostring(filepath)))
|
||||
end
|
||||
|
||||
else
|
||||
self:E(self.lid .. string.format( "ERROR: Could not save results because IO and/or LFS are not de-sanitized!"))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Read PATHLINE from JSON file.
|
||||
-- **NOTE**: Requires `io` and `lfs` to be de-sanitized!
|
||||
-- @param #PATHLINE self
|
||||
-- @param #string FileName Name of the file.
|
||||
-- @return #PATHLINE self
|
||||
function PATHLINE:NewFromJSON(FileName)
|
||||
|
||||
if io and lfs then
|
||||
|
||||
-- JSON script.
|
||||
local json=loadfile("Scripts\\JSON.lua")()
|
||||
|
||||
local data={}
|
||||
|
||||
-- Write in "User/Saved Games/" Folder.
|
||||
local filepath=lfs.writedir() .. FileName
|
||||
|
||||
--env.info(filepath)
|
||||
|
||||
-- Open file in binary mode for reading.
|
||||
local f = io.open(filepath, "rb")
|
||||
if f then
|
||||
data = f:read("*all")
|
||||
f:close()
|
||||
else
|
||||
env.info(string.format("WARNING: Could not load PATHLINE from file %s!", tostring(filepath)))
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Decode JSON data to get a lua table.
|
||||
local data=json:decode(data)
|
||||
|
||||
if data and data.name then
|
||||
|
||||
-- Create a new pathline instance.
|
||||
local self=PATHLINE:New(data.name)
|
||||
|
||||
for i=1,#data.points do
|
||||
local point=data.points[i] --#PATHLINE.Point
|
||||
|
||||
-- Create new point from data.
|
||||
local p=self:AddPointFromVec3(point.vec3)
|
||||
|
||||
-- Set name.
|
||||
p.name=point.name
|
||||
|
||||
-- Remove marker ID.
|
||||
p.markerID=nil
|
||||
end
|
||||
|
||||
return self
|
||||
else
|
||||
BASE:E("ERROR: Cannot find pathline name in data from JSON file. File may be corrupted!")
|
||||
end
|
||||
else
|
||||
BASE:E("ERROR: IO and/or LFS not de-sanitized! Cannot read file.")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -754,56 +414,32 @@ end
|
||||
--- Get 3D points of pathline.
|
||||
-- @param #PATHLINE self
|
||||
-- @param DCS#Vec3 Vec Position vector. Can also be a DCS#Vec2 in which case the altitude at landheight is taken.
|
||||
-- @return #PATHLINE.Point Pathline Point.
|
||||
-- @return #PATHLINE.Point
|
||||
function PATHLINE:_CreatePoint(Vec)
|
||||
|
||||
local point={} --#PATHLINE.Point
|
||||
|
||||
self.counter=self.counter+1
|
||||
|
||||
point.uid=self.counter
|
||||
point.mother=self.name
|
||||
|
||||
point.name=string.format("%s #%d", self.name, point.uid)
|
||||
|
||||
if Vec.z then
|
||||
-- Given vec is 3D
|
||||
point.vec3=UTILS.DeepCopy(Vec)
|
||||
point.vec2={x=Vec.x, y=Vec.z}
|
||||
else
|
||||
-- Given vec is 2D
|
||||
-- Given vec is 2D
|
||||
point.vec2=UTILS.DeepCopy(Vec)
|
||||
point.vec3={x=Vec.x, y=land.getHeight(Vec), z=Vec.y}
|
||||
end
|
||||
|
||||
-- Get surface type.
|
||||
point.surfaceType=land.getSurfaceType(point.vec2)
|
||||
|
||||
|
||||
-- Get land height and depth.
|
||||
point.landHeight, point.depth=land.getSurfaceHeightWithSeabed(point.vec2)
|
||||
|
||||
|
||||
point.markerID=nil
|
||||
|
||||
return point
|
||||
end
|
||||
|
||||
--- Get index of point in the lua table.
|
||||
-- @param #PATHLINE self
|
||||
-- @param #PATHLINE.Point Point Given point.
|
||||
-- @return #number index
|
||||
function PATHLINE:_GetPointIndex(Point)
|
||||
|
||||
for i,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
|
||||
if point.uid==Point.uid then
|
||||
return i
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -455,23 +455,6 @@ do -- COORDINATE
|
||||
end
|
||||
|
||||
|
||||
--- Returns the coordinate from the latitude and longitude given in degrees, minutes and seconds (DMS).
|
||||
-- @param #COORDINATE self
|
||||
-- @param #string Latitude Latitude in DMS as string, e.g. "`42° 24' 14.3"`". Not that the characters `°`, `'` and `"` are important.
|
||||
-- @param #string Longitude Longitude in DMS as string, e.g. "`42° 24' 14.3"`". Not that the characters `°`, `'` and `"` are important.
|
||||
-- @param #number Altitude (Optional) Altitude in meters. Default is the land height at the coordinate.
|
||||
-- @return #COORDINATE
|
||||
function COORDINATE:NewFromLLDMS(Latitude, Longitude, Altitude)
|
||||
|
||||
local lat=UTILS.LLDMSstringToDD(Latitude)
|
||||
local lon=UTILS.LLDMSstringToDD(Longitude)
|
||||
|
||||
self=COORDINATE:NewFromLLDD(lat, lon, Altitude)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Returns if the 2 coordinates are at the same 2D position.
|
||||
-- @param #COORDINATE self
|
||||
-- @param #COORDINATE Coordinate
|
||||
@@ -680,7 +663,7 @@ do -- COORDINATE
|
||||
local _,_,_,_,_,scenerys=self:ScanObjects(radius, false, false, true)
|
||||
|
||||
local set={}
|
||||
|
||||
|
||||
for _,_scenery in pairs(scenerys) do
|
||||
local scenery=_scenery --DCS#Object
|
||||
|
||||
@@ -3298,7 +3281,6 @@ do -- COORDINATE
|
||||
return delta/60
|
||||
end
|
||||
|
||||
|
||||
--- Return a BR string from a COORDINATE to the COORDINATE.
|
||||
-- @param #COORDINATE self
|
||||
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
|
||||
@@ -3312,6 +3294,20 @@ do -- COORDINATE
|
||||
local Distance = self:Get2DDistance( FromCoordinate )
|
||||
return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision )
|
||||
end
|
||||
|
||||
--- Return a Bearing string from a COORDINATE to the (self) COORDINATE.
|
||||
-- @param #COORDINATE self
|
||||
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
|
||||
-- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object.
|
||||
-- @param #boolean MagVar If true, also get angle in MagVar for BR/BRA
|
||||
-- @param #number Precision Rounding precision, currently full km as default (=0)
|
||||
-- @return #string The BR text.
|
||||
function COORDINATE:ToStringBearing( FromCoordinate, Settings, MagVar, Precision )
|
||||
local DirectionVec3 = FromCoordinate:GetDirectionVec3( self )
|
||||
local AngleRadians = self:GetAngleRadians( DirectionVec3 )
|
||||
--local Distance = self:Get2DDistance( FromCoordinate )
|
||||
return self:GetBearingText(AngleRadians,Precision,Settings,MagVar)
|
||||
end
|
||||
|
||||
--- Return a BRA string from a COORDINATE to the COORDINATE.
|
||||
-- @param #COORDINATE self
|
||||
|
||||
@@ -89,7 +89,6 @@ do -- SET_BASE
|
||||
Index = {},
|
||||
Database = nil,
|
||||
CallScheduler = nil,
|
||||
Filter = {},
|
||||
FilterCoalitionNumbers = {
|
||||
[coalition.side.RED+1] = "red",
|
||||
[coalition.side.BLUE+1] = "blue",
|
||||
|
||||
@@ -1667,6 +1667,9 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
|
||||
|
||||
-- If RandomizeUnits, then Randomize the formation at the start point.
|
||||
if self.SpawnRandomizeUnits then
|
||||
if self.SpawnRandomizePosition then
|
||||
PointVec3 = COORDINATE:New( SpawnTemplate.x, SpawnTemplate.route.points[1].alt, SpawnTemplate.y )
|
||||
end
|
||||
for UnitID = 1, #SpawnTemplate.units do
|
||||
local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
|
||||
if (SpawnZone) then
|
||||
@@ -3518,6 +3521,11 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
|
||||
max = 18
|
||||
ctable = CALLSIGN.F15E
|
||||
end
|
||||
if SpawnTemplate.units[1].type == "A6E" then
|
||||
min = 4
|
||||
max = 18
|
||||
ctable = CALLSIGN.Intruder
|
||||
end
|
||||
local callsignnr = math.random(min,max)
|
||||
local callsignname = "Enfield"
|
||||
for name, value in pairs(ctable) do
|
||||
|
||||
@@ -334,6 +334,31 @@ function SPAWNSTATIC:InitCargo(IsCargo)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Hide the static on the map view (visible to game master slots!).
|
||||
-- @param #SPAWN self
|
||||
-- @param #boolean OnOff Defaults to true
|
||||
-- @return #SPAWN The SPAWN object
|
||||
function SPAWNSTATIC:InitHiddenOnMap(OnOff)
|
||||
self.SpawnHiddenOnMap = OnOff == false and false or true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Hide the static on MFDs (visible to game master slots!).
|
||||
-- @param #SPAWN self
|
||||
-- @return #SPAWN The SPAWN object
|
||||
function SPAWNSTATIC:InitHiddenOnMFD()
|
||||
self.SpawnHiddenOnMFD = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Hide the static on planner (visible to game master slots!).
|
||||
-- @param #SPAWN self
|
||||
-- @return #SPAWN The SPAWN object
|
||||
function SPAWNSTATIC:InitHiddenOnPlanner()
|
||||
self.SpawnHiddenOnPlanner = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize as dead.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #boolean IsDead If true, this static is dead.
|
||||
@@ -533,6 +558,19 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
Template.mass=self.InitStaticCargoMass
|
||||
end
|
||||
|
||||
-- hiding options
|
||||
if self.SpawnHiddenOnPlanner then
|
||||
Template.hiddenOnPlanner=true
|
||||
end
|
||||
|
||||
if self.SpawnHiddenOnMFD then
|
||||
Template.hiddenOnMFD=true
|
||||
end
|
||||
|
||||
if self.SpawnHiddenOnMap then
|
||||
Template.hidden=self.SpawnHiddenOnMap
|
||||
end
|
||||
|
||||
if self.InitLinkUnit then
|
||||
Template.linkUnit=self.InitLinkUnit:GetID()
|
||||
Template.linkOffset=true
|
||||
|
||||
@@ -306,20 +306,6 @@ function ZONE_BASE:GetCoordinate( Height ) --R2.1
|
||||
return self.Coordinate
|
||||
end
|
||||
|
||||
--- Returns the @{Core.Vector#VECTOR} of the zone.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param DCS#Distance Height The height in meters to add to the land height where the center of the zone is located.
|
||||
-- @return Core.Vector#VECTOR The vector of the zone.
|
||||
function ZONE_BASE:GetVector( Height )
|
||||
self:F2(self.ZoneName)
|
||||
|
||||
local Vec3 = self:GetVec3( Height )
|
||||
|
||||
local vector=VECTOR:NewFromVec(Vec3)
|
||||
|
||||
return vector
|
||||
end
|
||||
|
||||
--- Get 2D distance to a coordinate.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param Core.Point#COORDINATE Coordinate Reference coordinate. Can also be a DCS#Vec2 or DCS#Vec3 object.
|
||||
@@ -797,7 +783,7 @@ end
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string to
|
||||
-- @param #string To
|
||||
-- @return #ZONE_BASE self
|
||||
function ZONE_BASE:onafterTriggerRunCheck(From,Event,To)
|
||||
if self:GetState() ~= "TriggerStopped" then
|
||||
@@ -1172,7 +1158,7 @@ end
|
||||
-- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
|
||||
-- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition )
|
||||
function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
||||
|
||||
|
||||
self.ScanData = {}
|
||||
self.ScanData.Coalitions = {}
|
||||
self.ScanData.Scenery = {}
|
||||
@@ -1182,7 +1168,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
||||
local ZoneCoord = self:GetCoordinate():SetAlt()
|
||||
local ZoneRadius = self:GetRadius()
|
||||
|
||||
--self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
|
||||
--self:I({x = ZoneCoord.x, y=ZoneCoord.y, z=ZoneCoord.z, ZoneRadius = ZoneRadius})
|
||||
|
||||
local SphereSearch = {
|
||||
id = world.VolumeType.SPHERE,
|
||||
@@ -1194,6 +1180,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
||||
|
||||
local function EvaluateZone( ZoneObject )
|
||||
--if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5
|
||||
|
||||
if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint()) then
|
||||
|
||||
-- Get object category.
|
||||
@@ -1279,38 +1266,48 @@ end
|
||||
|
||||
--- Get a set of scanned units.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param #number Coalition (optional) Filter for this coalition only.
|
||||
-- @return Core.Set#SET_UNIT Set of units and statics inside the zone.
|
||||
function ZONE_RADIUS:GetScannedSetUnit()
|
||||
|
||||
local SetUnit = SET_UNIT:New()
|
||||
function ZONE_RADIUS:GetScannedSetUnit(Coalition)
|
||||
|
||||
self.SetUnit = self.SetUnit or SET_UNIT:New()
|
||||
self.SetUnit:Clear(false)
|
||||
self.SetUnit.Set={}
|
||||
|
||||
if self.ScanData then
|
||||
for ObjectID, UnitObject in pairs( self.ScanData.Units ) do
|
||||
local UnitObject = UnitObject -- DCS#Unit
|
||||
if UnitObject:isExist() then
|
||||
local FoundUnit = UNIT:FindByName( UnitObject:getName() )
|
||||
if FoundUnit then
|
||||
SetUnit:AddUnit( FoundUnit )
|
||||
local FoundCoalition = FoundUnit and FoundUnit:GetCoalition() or nil
|
||||
local includeoncoalition = true
|
||||
if Coalition ~= nil and FoundCoalition==Coalition then includeoncoalition = true else includeoncoalition = false end
|
||||
if Coalition == nil then includeoncoalition = true end
|
||||
--self:I(string.format("Unit name %s coalition %s filter coalition = %s include = %s",FoundUnit:GetName(),tostring(FoundCoalition),tostring(Coalition),tostring(includeoncoalition)))
|
||||
if FoundUnit and includeoncoalition then
|
||||
self.SetUnit:AddUnit( FoundUnit )
|
||||
else
|
||||
local FoundStatic = STATIC:FindByName( UnitObject:getName(), false )
|
||||
if FoundStatic then
|
||||
SetUnit:AddUnit( FoundStatic )
|
||||
self.SetUnit:AddUnit( FoundStatic )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return SetUnit
|
||||
return self.SetUnit
|
||||
end
|
||||
|
||||
--- Get a set of scanned groups.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param #number Coalition (optional) Filter for this coalition only.
|
||||
-- @return Core.Set#SET_GROUP Set of groups.
|
||||
function ZONE_RADIUS:GetScannedSetGroup()
|
||||
function ZONE_RADIUS:GetScannedSetGroup(Coalition)
|
||||
|
||||
self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP
|
||||
|
||||
|
||||
self.ScanSetGroup:Clear(false)
|
||||
self.ScanSetGroup.Set={}
|
||||
|
||||
if self.ScanData then
|
||||
@@ -1319,7 +1316,12 @@ function ZONE_RADIUS:GetScannedSetGroup()
|
||||
if UnitObject:isExist() then
|
||||
|
||||
local FoundUnit=UNIT:FindByName(UnitObject:getName())
|
||||
if FoundUnit then
|
||||
local FoundCoalition = FoundUnit and FoundUnit:GetCoalition() or nil
|
||||
local includeoncoalition = true
|
||||
if Coalition ~= nil and FoundCoalition==Coalition then includeoncoalition = true else includeoncoalition = false end
|
||||
if Coalition == nil then includeoncoalition = true end
|
||||
--self:I(string.format("Unit name %s coalition %s filter coalition = %s include = %s",FoundUnit:GetName(),tostring(FoundCoalition),tostring(Coalition),tostring(includeoncoalition)))
|
||||
if FoundUnit and includeoncoalition then
|
||||
local group=FoundUnit:GetGroup()
|
||||
self.ScanSetGroup:AddGroup(group)
|
||||
end
|
||||
@@ -3394,7 +3396,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories )
|
||||
self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {}
|
||||
self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject )
|
||||
table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName])
|
||||
--self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
|
||||
--self:I( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ### Contributions: FlightControl
|
||||
-- ### Contributions: FlightControl, Applevangelist
|
||||
--
|
||||
-- ====
|
||||
-- @module Functional.Artillery
|
||||
@@ -780,7 +780,7 @@ ARTY.db={
|
||||
|
||||
--- Arty script version.
|
||||
-- @field #string version
|
||||
ARTY.version="1.3.3"
|
||||
ARTY.version="1.3.4"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -2268,7 +2268,7 @@ function ARTY._FuncTrack(weapon, self, target)
|
||||
if _dist<target.radius then
|
||||
|
||||
-- Get random coordinate within a certain radius.
|
||||
local _cr=_coord:GetRandomCoordinateInRadius(_data.target.radius)
|
||||
local _cr=_coord:GetRandomCoordinateInRadius(target.radius)
|
||||
|
||||
-- Fire smoke at this coordinate.
|
||||
_cr:Smoke(self.smokeColor)
|
||||
|
||||
@@ -693,7 +693,7 @@ end
|
||||
function AUTOLASE:GetLosFromUnit(Unit)
|
||||
local lasedistance = self.LaseDistance
|
||||
local unitheight = Unit:GetHeight()
|
||||
local coord = Unit:GetCoordinate()
|
||||
local coord = Unit:GetCoord()
|
||||
local landheight = coord:GetLandHeight()
|
||||
local asl = unitheight - landheight
|
||||
if asl > 100 then
|
||||
@@ -845,7 +845,7 @@ function AUTOLASE:ShowStatus(Group,Unit)
|
||||
locationstring = entry.coordinate:ToStringLLDDM(settings)
|
||||
elseif settings:IsA2G_BR() then
|
||||
-- attention this is the distance from the ASKING unit to target, not from RECCE to target!
|
||||
local startcoordinate = Unit:GetCoordinate() or Group:GetCoordinate()
|
||||
local startcoordinate = Unit:GetCoord() or Group:GetCoord()
|
||||
locationstring = entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision)
|
||||
end
|
||||
end
|
||||
@@ -963,8 +963,8 @@ function AUTOLASE:CanLase(Recce,Unit)
|
||||
end
|
||||
end
|
||||
-- calculate LOS
|
||||
local reccecoord = Recce:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoordinate()
|
||||
local reccecoord = Recce:GetCoord()
|
||||
local unitcoord = Unit:GetCoord()
|
||||
local islos = reccecoord:IsLOS(unitcoord,2.5)
|
||||
-- calculate distance
|
||||
local distance = math.floor(reccecoord:Get3DDistance(unitcoord))
|
||||
@@ -1020,8 +1020,8 @@ function AUTOLASE:_Prescient()
|
||||
self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName())
|
||||
for _,_static in pairs(Statics) do -- DCS static object here
|
||||
local static = STATIC:Find(_static)
|
||||
if static and static:GetCoalition() ~= self.coalition and static:GetCoordinate() then
|
||||
local IsLOS = position:IsLOS(static:GetCoordinate())
|
||||
if static and static:GetCoalition() ~= self.coalition and static:GetCoord() then
|
||||
local IsLOS = position:IsLOS(static:GetCoord())
|
||||
if IsLOS then
|
||||
unit:KnowUnit(static,true,true)
|
||||
end
|
||||
@@ -1081,7 +1081,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
local threat = contact.threatlevel or 0
|
||||
local reccegrp = UNIT:FindByName(reccename)
|
||||
if reccegrp then
|
||||
local reccecoord = reccegrp:GetCoordinate()
|
||||
local reccecoord = reccegrp:GetCoord()
|
||||
local distance = math.floor(reccecoord:Get3DDistance(coord))
|
||||
local text = string.format("%s of %s | Distance %d km | Threatlevel %d",contact.attribute, contact.groupname, math.floor(distance/1000), contact.threatlevel)
|
||||
report:Add(text)
|
||||
@@ -1120,7 +1120,6 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
local unit = _unit -- Wrapper.Unit#UNIT
|
||||
if unit and unit:IsAlive() then
|
||||
local threat = unit:GetThreatLevel()
|
||||
local coord = unit:GetCoordinate()
|
||||
if threat >= self.minthreatlevel then
|
||||
local unitname = unit:GetName()
|
||||
-- prefer radar units
|
||||
@@ -1176,16 +1175,16 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
local code = self:GetLaserCode(reccename)
|
||||
local spot = SPOT:New(recce)
|
||||
spot:LaseOn(unit,code,self.LaseDuration)
|
||||
local locationstring = unit:GetCoordinate():ToStringLLDDM()
|
||||
local locationstring = unit:GetCoord():ToStringLLDDM()
|
||||
if _SETTINGS:IsA2G_MGRS() then
|
||||
local precision = _SETTINGS:GetMGRS_Accuracy()
|
||||
local settings = {}
|
||||
settings.MGRS_Accuracy = precision
|
||||
locationstring = unit:GetCoordinate():ToStringMGRS(settings)
|
||||
locationstring = unit:GetCoord():ToStringMGRS(settings)
|
||||
elseif _SETTINGS:IsA2G_LL_DMS() then
|
||||
locationstring = unit:GetCoordinate():ToStringLLDMS(_SETTINGS)
|
||||
locationstring = unit:GetCoord():ToStringLLDMS(_SETTINGS)
|
||||
elseif _SETTINGS:IsA2G_BR() then
|
||||
locationstring = unit:GetCoordinate():ToStringBULLS(self.coalition,_SETTINGS)
|
||||
locationstring = unit:GetCoord():ToStringBULLS(self.coalition,_SETTINGS)
|
||||
end
|
||||
|
||||
local laserspot = { -- #AUTOLASE.LaserSpot
|
||||
@@ -1198,7 +1197,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
unitname = unitname,
|
||||
reccename = reccename,
|
||||
unittype = unit:GetTypeName(),
|
||||
coordinate = unit:GetCoordinate(),
|
||||
coordinate = unit:GetCoord(),
|
||||
}
|
||||
if self.smoketargets then
|
||||
local coord = unit:GetCoordinate()
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
-- @module Functional.Mantis
|
||||
-- @image Functional.Mantis.jpg
|
||||
--
|
||||
-- Last Update: August 2025
|
||||
-- Last Update: January 2026
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **MANTIS** class, extends Core.Base#BASE
|
||||
@@ -61,10 +61,13 @@
|
||||
-- @field #boolean checkforfriendlies If true, do not activate a SAM installation if a friendly aircraft is in firing range.
|
||||
-- @field #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects.
|
||||
-- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range.
|
||||
-- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White
|
||||
-- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White.
|
||||
-- @field #number checkcounter Counter for SAM Table refreshes.
|
||||
-- @field #number DLinkCacheTime Seconds after which cached contacts in DLink will decay.
|
||||
-- @field #boolean logsamstatus Log SAM status in dcs.log every cycle if true
|
||||
-- @field #boolean logsamstatus Log SAM status in dcs.log every cycle if true.
|
||||
-- @field #boolean DetectAccoustic Set if we can also detect units accousticly.
|
||||
-- @field #number DetectAccousticRadius We can hear in this range.
|
||||
-- @field #table DetectAccousticCategories We can hear these categories.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
@@ -113,7 +116,7 @@
|
||||
-- * TOR M2
|
||||
-- * C-RAM
|
||||
-- * Silkworm (though strictly speaking this is a surface to ship missile)
|
||||
-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19
|
||||
-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19, SA-21, S-300VM, S-300V4, S-400
|
||||
-- * From IDF mod: STUNNER IDFA, TAMIR IDFA (Note all caps!)
|
||||
-- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2, SAMP/T Block 1, SAMP/T Block 1INT, SAMP/T Block2
|
||||
-- * Other Mods: Nike
|
||||
@@ -280,7 +283,7 @@
|
||||
MANTIS = {
|
||||
ClassName = "MANTIS",
|
||||
name = "mymantis",
|
||||
version = "0.9.34",
|
||||
version = "0.9.44",
|
||||
SAM_Templates_Prefix = "",
|
||||
SAM_Group = nil,
|
||||
EWR_Templates_Prefix = "",
|
||||
@@ -331,6 +334,11 @@ MANTIS = {
|
||||
checkcounter = 1,
|
||||
DLinkCacheTime = 120,
|
||||
logsamstatus = false,
|
||||
DetectAccoustic = false,
|
||||
DetectAccousticRadius = 2000,
|
||||
DetectAccousticCategories = {Unit.Category.HELICOPTER},
|
||||
ARMWeaponSeen = {},
|
||||
InboundARMs = {},
|
||||
}
|
||||
|
||||
--- Advanced state enumerator
|
||||
@@ -366,24 +374,25 @@ MANTIS.radiusscale[MANTIS.SamType.POINT] = 3
|
||||
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range)
|
||||
-- @field #string Radar Radar typename on unit level (used as key)
|
||||
-- @field #string Point Point defense capable
|
||||
-- @field ARMCapacit ARMCapacity ie how many (H)ARMs the system can defend at the same time
|
||||
MANTIS.SamData = {
|
||||
["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km
|
||||
["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B
|
||||
["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS", ARMCapacity=1 }, -- AIM 120B
|
||||
["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot str" },
|
||||
["Rapier"] = { Range=10, Blindspot=0, Height=3, Type="Short", Radar="rapier" },
|
||||
["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="S_75M_Volhov" },
|
||||
["SA-3"] = { Range=18, Blindspot=6, Height=18, Type="Short", Radar="5p73 s-125 ln" },
|
||||
["SA-5"] = { Range=250, Blindspot=7, Height=40, Type="Long", Radar="5N62V" },
|
||||
["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" },
|
||||
["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"},
|
||||
["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4", ARMCapacity=4},
|
||||
["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" },
|
||||
["Roland"] = { Range=6, Blindspot=0, Height=5, Type="Short", Radar="Roland" },
|
||||
["Roland"] = { Range=6, Blindspot=0, Height=5, Type="Short", Radar="Roland", ARMCapacity=1 },
|
||||
["Gepard"] = { Range=5, Blindspot=0, Height=4, Type="Point", Radar="Gepard" },
|
||||
["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" },
|
||||
["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" },
|
||||
["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" },
|
||||
["SA-19"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Tunguska" },
|
||||
["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true" },
|
||||
["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true", ARMCapacity=2 },
|
||||
["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" },
|
||||
["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" },
|
||||
["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" },
|
||||
@@ -391,19 +400,23 @@ MANTIS.SamData = {
|
||||
["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" },
|
||||
["C-RAM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" },
|
||||
-- units from HDS Mod, multi launcher options is tricky
|
||||
["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"},
|
||||
["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17" },
|
||||
["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"},
|
||||
["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"},
|
||||
["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B", ARMCapacity=4},
|
||||
["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=4 },
|
||||
["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1", ARMCapacity=16},
|
||||
["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2", ARMCapacity=18},
|
||||
["SA-21"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="92N6E"},
|
||||
["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4},
|
||||
["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4},
|
||||
["S-400"] = { Range=250, Blindspot=5, Height=27, Type="Long" , Radar="92N6E", ARMCapacity=4},
|
||||
["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
|
||||
["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" },
|
||||
["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" },
|
||||
["NIKE"] = { Range=155, Blindspot=6, Height=30, Type="Long", Radar="HIPAR" },
|
||||
["Nike"] = { Range=155, Blindspot=6, Height=30, Type="Long", Radar="HIPAR" },
|
||||
["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" },
|
||||
-- CH Added to DCS core 2.9.19.x
|
||||
["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true" },
|
||||
["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" },
|
||||
["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" },
|
||||
["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true", ARMCapacity=3 },
|
||||
["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true", ARMCapacity=4 },
|
||||
["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM", ARMCapacity=12 }, -- 4 per starter, usually 3 starters in a battery
|
||||
}
|
||||
|
||||
--- SAM data HDS
|
||||
@@ -419,13 +432,13 @@ MANTIS.SamDataHDS = {
|
||||
-- group name MUST contain HDS to ID launcher type correctly!
|
||||
["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" },
|
||||
["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" },
|
||||
["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD
|
||||
["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln"}, -- V55RUD
|
||||
["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17 " },
|
||||
["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l"},
|
||||
["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l"},
|
||||
["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME" },
|
||||
["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME" },
|
||||
["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln", ARMCapacity=8}, -- V55RUD
|
||||
["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln", ARMCapacity=3}, -- V55RUD
|
||||
["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=4 },
|
||||
["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l", ARMCapacity=12},
|
||||
["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l", ARMCapacity=12},
|
||||
["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME", ARMCapacity=14 },
|
||||
["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME", ARMCapacity=14 },
|
||||
["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
|
||||
["SAMPT Block 1 HDS"] = { Range=120, Blindspot=1, Height=20, Type="long", Radar="SAMPT_MLT_Blk1" }, -- Block 1 Launcher
|
||||
["SAMPT Block 1INT HDS"] = { Range=150, Blindspot=1, Height=25, Type="long", Radar="SAMPT_MLT_Blk1NT" }, -- Block 1-INT Launcher
|
||||
@@ -469,21 +482,21 @@ MANTIS.SamDataCH = {
|
||||
-- https://www.currenthill.com/
|
||||
-- group name MUST contain CHM to ID launcher type correctly!
|
||||
["2S38 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" },
|
||||
["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true" },
|
||||
["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" },
|
||||
["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true", ARMCapacity=3 },
|
||||
["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2", ARMCapacity=4 },
|
||||
["PGL-625 CHM"] = { Range=10, Blindspot=1, Height=5, Type="Short", Radar="PGL_625" },
|
||||
["HQ-17A CHM"] = { Range=15, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" },
|
||||
["M903PAC2 CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" },
|
||||
["M903PAC3 CHM"] = { Range=160, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" },
|
||||
["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" },
|
||||
["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2K", Point="true" },
|
||||
["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Point", Radar="TorM2M", Point="true" },
|
||||
["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true", ARMCapacity=3 },
|
||||
["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2K", Point="true", ARMCapacity=4 },
|
||||
["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Point", Radar="TorM2M", Point="true", ARMCapacity=4 },
|
||||
["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" },
|
||||
["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" },
|
||||
["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" },
|
||||
["PGZ-09 CHM"] = { Range=4, Blindspot=0.5, Height=3, Type="Point", Radar="CH_PGZ09", Point="true" },
|
||||
["S350-9M100 CHM"] = { Range=15, Blindspot=1, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" },
|
||||
["S350-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" },
|
||||
["S350-9M100 CHM"] = { Range=15, Blindspot=1, Height=8, Type="Short", Radar="CH_S350_50P6_9M100", ARMCapacity=20 },
|
||||
["S350-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D", ARMCapacity=20 },
|
||||
["LAV-AD CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_LAVAD" },
|
||||
["HQ-22 CHM"] = { Range=170, Blindspot=5, Height=27, Type="Long", Radar="CH_HQ22_LN" },
|
||||
["PGZ-95 CHM"] = { Range=2.5, Blindspot=0.5, Height=2, Type="Point", Radar="CH_PGZ95",Point="true" },
|
||||
@@ -495,8 +508,8 @@ MANTIS.SamDataCH = {
|
||||
["Skynex CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_SkynexHX", Point="true" },
|
||||
["Skyshield CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_Skyshield_Gun", Point="true" },
|
||||
["WieselOzelot CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_Wiesel2Ozelot" },
|
||||
["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M" },
|
||||
["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA" },
|
||||
["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M", ARMCapacity=20 },
|
||||
["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA", ARMCapacity=20 },
|
||||
["SkySabre CHM"] = { Range=30, Blindspot=0.5, Height=10, Type="Medium", Radar="CH_SkySabreLN" },
|
||||
["Stormer CHM"] = { Range=7.5, Blindspot=0.3, Height=7, Type="Short", Radar="CH_StormerHVM" },
|
||||
["THAAD CHM"] = { Range=200, Blindspot=40, Height=150, Type="Long", Radar="CH_THAAD_M1120" },
|
||||
@@ -524,7 +537,7 @@ do
|
||||
--@param #string samprefix Prefixes for the SAM groups from the ME, e.g. all groups starting with "Red Sam..."
|
||||
--@param #string ewrprefix Prefixes for the EWR groups from the ME, e.g. all groups starting with "Red EWR..."
|
||||
--@param #string hq Group name of your HQ (optional)
|
||||
--@param #string coalition Coalition side of your setup, e.g. "blue", "red" or "neutral"
|
||||
--@param #string Coalition Coalition side of your setup, e.g. "blue", "red" or "neutral"
|
||||
--@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional)
|
||||
--@param #string awacs Group name of your Awacs (optional)
|
||||
--@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional)
|
||||
@@ -548,7 +561,7 @@ do
|
||||
-- mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
|
||||
-- mybluemantis:Start()
|
||||
--
|
||||
function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs, EmOnOff, Padding, Zones)
|
||||
function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs, EmOnOff, Padding, Zones)
|
||||
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
@@ -561,12 +574,14 @@ do
|
||||
-- DONE: Treat Awacs separately, since they might be >80km off site
|
||||
-- DONE: Allow tables of prefixes for the setup
|
||||
-- DONE: Auto-Mode with range setups for various known SAM types.
|
||||
-- DONE: Added reaction on HIT and UNIT LOST events.
|
||||
|
||||
self.name = name or "mymantis"
|
||||
self.SAM_Templates_Prefix = samprefix or "Red SAM"
|
||||
self.EWR_Templates_Prefix = ewrprefix or "Red EWR"
|
||||
self.HQ_Template_CC = hq or nil
|
||||
self.Coalition = coalition or "red"
|
||||
self.Coalition = Coalition or "red"
|
||||
self.coalition = Coalition == "blue" and coalition.side.BLUE or coalition.side.RED
|
||||
self.SAM_Table = {}
|
||||
self.SAM_Table_Long = {}
|
||||
self.SAM_Table_Medium = {}
|
||||
@@ -613,6 +628,8 @@ do
|
||||
self.autoshorad = true
|
||||
self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP
|
||||
self.FilterZones = Zones
|
||||
self.LastThreatEval = {}
|
||||
self.InboundARMs = {}
|
||||
|
||||
self.SkateZones = nil
|
||||
self.SkateNumber = 3
|
||||
@@ -716,6 +733,8 @@ do
|
||||
self:AddTransition("*", "SeadSuppressionStart", "*") -- SEAD has switched off one group.
|
||||
self:AddTransition("*", "SeadSuppressionEnd", "*") -- SEAD has switched on one group.
|
||||
self:AddTransition("*", "SeadSuppressionPlanned", "*") -- SEAD has planned a suppression.
|
||||
self:AddTransition("*", "SAMUnitHit", "*") -- A SAM unit was hit
|
||||
self:AddTransition("*", "SAMUnitLost", "*") -- A SAM Unit was lost
|
||||
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
||||
|
||||
------------------------
|
||||
@@ -826,6 +845,24 @@ do
|
||||
-- @param Wrapper.Group#GROUP Group The suppressed GROUP object
|
||||
-- @param #string Name Name of the suppressed group
|
||||
|
||||
--- On After "SAMUnitHit" event. A SAM Unit was hit.
|
||||
-- @function [parent=#MANTIS] OnAfterSeadSuppressionEnd
|
||||
-- @param #MANTIS self
|
||||
-- @param #string From The From State
|
||||
-- @param #string Event The Event
|
||||
-- @param #string To The To State
|
||||
-- @param Wrapper.Group#GROUP Group The GROUP of the hit UNIT object
|
||||
-- @param #string Name Name of the suppressed group
|
||||
|
||||
--- On After "SAMUnitLoast" event. A SAM Unit was lost.
|
||||
-- @function [parent=#MANTIS] OnAfterSeadSuppressionEnd
|
||||
-- @param #MANTIS self
|
||||
-- @param #string From The From State
|
||||
-- @param #string Event The Event
|
||||
-- @param #string To The To State
|
||||
-- @param Wrapper.Group#GROUP Group The GROUP of the lost UNIT object
|
||||
-- @param #string Name Name of the suppressed group
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -833,6 +870,138 @@ do
|
||||
-- MANTIS helper functions
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
--- Set to accept accoustic detection. Set this *before* MANTIS starts!
|
||||
-- @param #MANTIS self
|
||||
-- @param #number Radius Radius in which we can "hear" units. Defaults to 2000 meters.
|
||||
-- @param #table UnitCategories Set what Unit Categories we can "hear". Defaults to `{Unit.Category.HELICOPTER}`
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories)
|
||||
self.DetectAccoustic = true
|
||||
self.DetectAccousticRadius = Radius or 2000
|
||||
self.DetectAccousticCategories = UnitCategories or {Unit.Category.HELICOPTER}
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch off accoustic detection.
|
||||
-- @param #MANTIS self
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetAccousticDetectionOff()
|
||||
self.DetectAccoustic = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Function to manage hits on SAM units
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Event#EVENTDATA EventData The EVENT data
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:_EventHandler(EventData)
|
||||
self:T(self.lid .. "_EventHandler")
|
||||
|
||||
local function IsOneOfOurs(name)
|
||||
for _,_name in pairs(self.ewr_templates) do
|
||||
if string.find(name,_name,1,true) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function SwitchSAMOn(Name,Group)
|
||||
local suppressed = self.SuppressedGroups[Name] or false
|
||||
if not suppressed and self.SamStateTracker[Name] == "GREEN" then
|
||||
self.SamStateTracker[Name] = "RED"
|
||||
if self.UseEmOnOff then
|
||||
-- DONE: add emissions on/off
|
||||
Group:EnableEmission(true)
|
||||
elseif (not self.UseEmOnOff) then
|
||||
Group:OptionAlarmStateRed()
|
||||
end
|
||||
self:__RedState(1,Group)
|
||||
if self.SmokeDecoy == true then
|
||||
self:_SmokeUnits(Group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local coordinate -- Core.Point#COORDINATE
|
||||
local Name -- #string
|
||||
local Group -- Wrapper.Group#GROUP
|
||||
local lasthit = 0
|
||||
local firsthit = false
|
||||
local alerton = false
|
||||
|
||||
-- Check if we can get a location
|
||||
|
||||
local data = EventData -- Core.Event#EVENTDATA
|
||||
if data.id == EVENTS.Hit then
|
||||
-- Unit hit, one of ours?
|
||||
if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName) then
|
||||
self:T("Unit hit in group: "..data.TgtGroupName)
|
||||
if data.TgtGroup then
|
||||
lasthit = data.TgtGroup:GetProperty("MANTIS_LASTHIT")
|
||||
firsthit = (lasthit==nil) and true or false
|
||||
if firsthit == true then alerton = true end
|
||||
if lasthit ~= nil and timer.getTime()-lasthit > self.ShoradTime then alerton = true end
|
||||
coordinate = data.TgtGroup:GetCoordinate()
|
||||
Name = data.TgtGroupName
|
||||
Group = data.TgtGroup
|
||||
if alerton == true then
|
||||
self:__SAMUnitHit(1,Group,Name)
|
||||
SwitchSAMOn(Name,Group)
|
||||
end
|
||||
if coordinate and self.debug then
|
||||
local text = coordinate:ToStringMGRS()
|
||||
self:I("Location: "..text)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if data.id == EVENTS.UnitLost then
|
||||
if data.IniGroupName and IsOneOfOurs(data.IniGroupName) then
|
||||
self:T("Unit lost in group: "..data.IniGroupName)
|
||||
if data.IniGroup then
|
||||
lasthit = data.IniGroup:GetProperty("MANTIS_LASTHIT")
|
||||
firsthit = (lasthit==nil) and true or false
|
||||
if firsthit == true then alerton = true end
|
||||
if lasthit ~= nil and timer.getTime()-lasthit > self.ShoradTime then alerton = true end
|
||||
coordinate = data.IniGroup:GetCoordinate()
|
||||
Name = data.IniGroupName
|
||||
Group = data.IniGroup
|
||||
alerton = true
|
||||
SwitchSAMOn(Name,Group)
|
||||
self:__SAMUnitLost(1,Group,Name)
|
||||
if coordinate and self.debug then
|
||||
local text = coordinate:ToStringMGRS()
|
||||
self:I("Location: "..text)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if firsthit == true or alerton == true then
|
||||
Group:SetProperty("MANTIS_LASTHIT",timer.getTime())
|
||||
end
|
||||
|
||||
|
||||
if coordinate ~= nil and Name ~= nil and Group ~=nil and alerton == true then
|
||||
if self.ShoradLink then
|
||||
self:T("Shorad activated for: "..Name)
|
||||
local Shorad = self.Shorad -- Functional.Shorad#SHORAD
|
||||
local radius = self.checkradius
|
||||
local ontime = self.ShoradTime
|
||||
Shorad:WakeUpShorad(Name, radius, ontime, nil, true)
|
||||
self:__ShoradActivated(1,Name, radius, ontime)
|
||||
end
|
||||
if self.autorelocate and Group then
|
||||
Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- [Internal] Function to get the self.SAM_Table
|
||||
-- @param #MANTIS self
|
||||
-- @return #table table
|
||||
@@ -903,13 +1072,111 @@ do
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a single reject zone to MANTIS.
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Zone#ZONE Zone The zone to be added
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:AddRejectZone(Zone)
|
||||
if Zone and Zone:IsInstanceOf("ZONE_BASE") then
|
||||
table.insert(self.RejectZones,Zone)
|
||||
self.usezones = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a single accept zone to MANTIS.
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Zone#ZONE Zone The zone to be added
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:AddAcceptZone(Zone)
|
||||
if Zone and Zone:IsInstanceOf("ZONE_BASE") then
|
||||
table.insert(self.AcceptZones,Zone)
|
||||
self.usezones = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a single conflict zone to MANTIS.
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Zone#ZONE Zone The zone to be added
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:AddConflictZone(Zone)
|
||||
if Zone and Zone:IsInstanceOf("ZONE_BASE") then
|
||||
table.insert(self.ConflictZones,Zone)
|
||||
self.usezones = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zones.
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Set#SET_ZONE CorridorZones Can be handed in as SET\_ZONE or single ZONE object.
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetCorridorZones(CorridorZones)
|
||||
self:T(self.lid .. "SetCorridorZones")
|
||||
if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE") then
|
||||
self.corridorzones = CorridorZones
|
||||
self.usecorridors = true
|
||||
elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE") then
|
||||
if not self.corridorzones then self.corridorzones = SET_ZONE:New() end
|
||||
self.corridorzones:AddZone(CorridorZones)
|
||||
self.usecorridors = true
|
||||
end
|
||||
if self.intelset then
|
||||
for _,_intel in pairs(self.intelset) do
|
||||
_intel:SetCorridorZones(self.corridorzones)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to add one corridor zone.
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Zone#ZONE CorridorZone The ZONE object to be added.
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:AddCorridorZone(CorridorZone)
|
||||
self:T(self.lid .. "AddCorridorZone")
|
||||
self:SetCorridorZones(CorridorZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in FEET.
|
||||
-- @param #MANTIS self
|
||||
-- @param #number Floor Floor altitude ASL in feet.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in feet.
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling)
|
||||
self.corridorfloor = UTILS.FeetToMeters(Floor)
|
||||
self.corridorceiling = UTILS.FeetToMeters(Ceiling)
|
||||
if self.intelset then
|
||||
for _,_intel in pairs(self.intelset) do
|
||||
_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in METERS.
|
||||
-- @param #MANTIS self
|
||||
-- @param #number Floor Floor altitude ASL in meters.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in meters.
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling)
|
||||
self.corridorfloor = Floor
|
||||
self.corridorceiling = Ceiling
|
||||
if self.intelset then
|
||||
for _,_intel in pairs(self.intelset) do
|
||||
_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set the detection radius of the EWR in meters. (Deprecated, SAM range is used)
|
||||
-- @param #MANTIS self
|
||||
-- @param #number radius Radius of the EWR detection zone
|
||||
function MANTIS:SetEWRRange(radius)
|
||||
self:T(self.lid .. "SetEWRRange")
|
||||
--local radius = radius or 80000
|
||||
-- self.acceptrange = radius
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1325,11 +1592,12 @@ do
|
||||
return inzone
|
||||
end
|
||||
|
||||
--- [Internal] Function to prefilter height based
|
||||
--- [Internal] Function to prefilter height based and check for Helo activity.
|
||||
-- @param #MANTIS self
|
||||
-- @param #number height
|
||||
-- @param Core.Point#COORDINATE SamCoordinate
|
||||
-- @return #table set
|
||||
function MANTIS:_PreFilterHeight(height)
|
||||
function MANTIS:_PreFilterHeight(height,SamCoordinate)
|
||||
self:T(self.lid.."_PreFilterHeight")
|
||||
local set = {}
|
||||
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK
|
||||
@@ -1338,8 +1606,22 @@ do
|
||||
local contact = _contact -- Ops.Intel#INTEL.Contact
|
||||
local grp = contact.group -- Wrapper.Group#GROUP
|
||||
if grp:IsAlive() then
|
||||
if grp:GetHeight(true) < height then
|
||||
local coord = grp:GetCoordinate()
|
||||
local coord = grp:GetCoordinate()
|
||||
local dist = 0
|
||||
local include = true
|
||||
if grp:IsGround() then include = false end
|
||||
if grp:GetCoalition() == self.coalition then include = false end
|
||||
if coord and SamCoordinate and grp:IsHelicopter() then
|
||||
dist = coord:Get2DDistance(SamCoordinate) or 0
|
||||
if dist > self.ShoradActDistance then include = false end -- we do not want long range shooting at helos
|
||||
end
|
||||
if self.debug then
|
||||
local text = "Looking at Group: "..grp:GetName() or "N/A"
|
||||
text = text .. " Include = "..tostring(include)
|
||||
MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog()
|
||||
end
|
||||
local grpalt = grp:GetHeight(true)
|
||||
if grpalt < height and grpalt > 10 and include == true then
|
||||
table.insert(set,coord)
|
||||
end
|
||||
end
|
||||
@@ -1363,7 +1645,7 @@ do
|
||||
local set = dectset
|
||||
if dlink then
|
||||
-- DEBUG
|
||||
set = self:_PreFilterHeight(height)
|
||||
set = self:_PreFilterHeight(height,samcoordinate)
|
||||
end
|
||||
--self.friendlyset -- Core.Set#SET_GROUP
|
||||
if self.checkforfriendlies == true and self.friendlyset == nil then
|
||||
@@ -1444,14 +1726,30 @@ do
|
||||
|
||||
self.intelset = {}
|
||||
|
||||
local IntelOne = INTEL:New(groupset,self.Coalition,self.name.." IntelOne")
|
||||
--IntelOne:SetClusterAnalysis(true,true)
|
||||
--IntelOne:SetClusterRadius(5000)
|
||||
local IntelOne = INTEL:New(groupset,self.coalition,self.name.." IntelOne")
|
||||
IntelOne.DetectAccoustic = self.DetectAccoustic
|
||||
IntelOne.DetectAccousticRadius = self.DetectAccousticRadius or 2000
|
||||
IntelOne.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER}
|
||||
--IntelOne:SetClusterAnalysis(true,true,true)
|
||||
if self.usecorridors == true then
|
||||
IntelOne:SetCorridorZones(self.corridorzones)
|
||||
if self.corridorfloor or self.corridorceiling then
|
||||
IntelOne:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
IntelOne:Start()
|
||||
|
||||
local IntelTwo = INTEL:New(samset,self.Coalition,self.name.." IntelTwo")
|
||||
--IntelTwo:SetClusterAnalysis(true,true)
|
||||
--IntelTwo:SetClusterRadius(5000)
|
||||
local IntelTwo = INTEL:New(samset,self.coalition,self.name.." IntelTwo")
|
||||
IntelTwo.DetectAccoustic = self.DetectAccoustic
|
||||
IntelTwo.DetectAccousticRadius = self.DetectAccousticRadius or 2000
|
||||
IntelTwo.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER}
|
||||
--IntelTwo:SetClusterAnalysis(true,true,true)
|
||||
if self.usecorridors == true then
|
||||
IntelTwo:SetCorridorZones(self.corridorzones)
|
||||
if self.corridorfloor or self.corridorceiling then
|
||||
IntelTwo:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
IntelTwo:Start()
|
||||
|
||||
local CacheTime = self.DLinkCacheTime or 120
|
||||
@@ -1511,6 +1809,7 @@ do
|
||||
local group = GROUP:FindByName(grpname) -- Wrapper.Group#GROUP
|
||||
local units = group:GetUnits()
|
||||
local SAMData = self.SamData
|
||||
local ARMCapacity
|
||||
if mod then
|
||||
SAMData = self.SamDataHDS
|
||||
elseif sma then
|
||||
@@ -1518,22 +1817,23 @@ do
|
||||
elseif chm then
|
||||
SAMData = self.SamDataCH
|
||||
end
|
||||
--self:I("Looking to auto-match for "..grpname)
|
||||
self:T("Looking to auto-match for "..grpname)
|
||||
for _,_unit in pairs(units) do
|
||||
local unit = _unit -- Wrapper.Unit#UNIT
|
||||
local type = string.lower(unit:GetTypeName())
|
||||
--self:I(string.format("Matching typename: %s",type))
|
||||
local typename = string.lower(unit:GetTypeName())
|
||||
self:T(string.format("Matching typename: %s",typename))
|
||||
for idx,entry in pairs(SAMData) do
|
||||
local _entry = entry -- #MANTIS.SamData
|
||||
local _radar = string.lower(_entry.Radar)
|
||||
--self:I(string.format("Trying typename: %s",_radar))
|
||||
if string.find(type,_radar,1,true) then
|
||||
self:T(string.format("Trying typename: %s",_radar))
|
||||
if string.find(typename,_radar,1,true) then
|
||||
type = _entry.Type
|
||||
radiusscale = self.radiusscale[type]
|
||||
range = _entry.Range * 1000 * radiusscale -- max firing range used as switch-on
|
||||
height = _entry.Height * 1000 -- max firing height
|
||||
blind = _entry.Blindspot * 100 -- blind spot range
|
||||
--self:I(string.format("Match: %s - %s",_radar,type))
|
||||
blind = _entry.Blindspot * 100 -- blind spot range
|
||||
ARMCapacity = _entry.ARMCapacity
|
||||
self:T(string.format("Match: %s - %s",_radar,type))
|
||||
found = true
|
||||
break
|
||||
end
|
||||
@@ -1554,7 +1854,7 @@ do
|
||||
if not found then
|
||||
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
|
||||
end
|
||||
return range, height, type, blind
|
||||
return range, height, type, blind, ARMCapacity
|
||||
end
|
||||
|
||||
--- [Internal] Function to get SAM firing data
|
||||
@@ -1575,6 +1875,7 @@ do
|
||||
local HDSmod = false
|
||||
local SMAMod = false
|
||||
local CHMod = false
|
||||
local ARMCapacity = 0
|
||||
if string.find(grpname,"HDS",1,true) then
|
||||
HDSmod = true
|
||||
elseif string.find(grpname,"SMA",1,true) then
|
||||
@@ -1592,6 +1893,7 @@ do
|
||||
range = _entry.Range * 1000 * radiusscale -- max firing range
|
||||
height = _entry.Height * 1000 -- max firing height
|
||||
blind = _entry.Blindspot
|
||||
ARMCapacity = _entry.ARMCapacity or 0
|
||||
self:T("Matching Groupname = " .. grpname .. " Range= " .. range)
|
||||
found = true
|
||||
break
|
||||
@@ -1611,14 +1913,14 @@ do
|
||||
end
|
||||
--- Tertiary filter if not found
|
||||
if (not found) or HDSmod or SMAMod or CHMod then
|
||||
range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod)
|
||||
range, height, type, blind, ARMCapacity = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod)
|
||||
elseif not found then
|
||||
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
|
||||
end
|
||||
if found and string.find(grpname,"SHORAD",1,true) then
|
||||
type = MANTIS.SamType.POINT -- force short on match
|
||||
end
|
||||
return range, height, type, blind
|
||||
return range, height, type, blind, ARMCapacity
|
||||
end
|
||||
|
||||
--- [Internal] Function to set the SAM start state
|
||||
@@ -1653,8 +1955,9 @@ do
|
||||
group:OptionEngageRange(engagerange) --default engagement will be 95% of firing range
|
||||
local grpname = group:GetName()
|
||||
local grpcoord = group:GetCoordinate()
|
||||
local grprange,grpheight,type,blind = self:_GetSAMRange(grpname)
|
||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
local grprange,grpheight,type,blind,ARMCapacity = self:_GetSAMRange(grpname)
|
||||
if ARMCapacity and ARMCapacity>0 then _group:SetProperty("ARMCapacity",ARMCapacity) end
|
||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity})
|
||||
--table.insert( SEAD_Grps, grpname )
|
||||
if type == MANTIS.SamType.LONG then
|
||||
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
@@ -1718,12 +2021,15 @@ do
|
||||
if group:IsGround() and group:IsAlive() then
|
||||
local grpname = group:GetName()
|
||||
local grpcoord = group:GetCoordinate()
|
||||
local grprange, grpheight,type,blind = self:_GetSAMRange(grpname)
|
||||
local grprange, grpheight,type,blind, ARMCapacity = self:_GetSAMRange(grpname)
|
||||
-- TODO the below might stop working at some point after some hours, needs testing
|
||||
--local radaralive = group:IsSAM()
|
||||
if ARMCapacity and ARMCapacity>0 then _group:SetProperty("ARMCapacity",ARMCapacity) end
|
||||
local radaralive = true
|
||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) -- make the table lighter, as I don't really use the zone here
|
||||
table.insert( SEAD_Grps, grpname )
|
||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity}) -- make the table lighter, as I don't really use the zone here
|
||||
if type ~= MANTIS.SamType.POINT then
|
||||
table.insert( SEAD_Grps, grpname )
|
||||
end
|
||||
if type == MANTIS.SamType.LONG and radaralive then
|
||||
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
self:T({grpname,grprange, grpheight})
|
||||
@@ -1780,10 +2086,122 @@ do
|
||||
self.ShoradLink = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Function to smoke a group in decoy.
|
||||
-- @param #MANTIS self
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:_SmokeUnits(Group)
|
||||
self:T("Smoking")
|
||||
local LastSmoketime=Group:GetProperty("MANTIS_LASTSMOKE_TIME") or 0
|
||||
local TNow = timer.getTime()
|
||||
if TNow - LastSmoketime > 290 then -- Smoking lasts 5 minutes
|
||||
Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow)
|
||||
local units = Group:GetUnits() or {}
|
||||
local smoke = self.SmokeDecoyColor or SMOKECOLOR.White
|
||||
for _,unit in pairs(units) do
|
||||
if unit and unit:IsAlive() then
|
||||
unit:GetCoordinate():Smoke(smoke)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- MANTIS main functions
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
--- [Internal] Check if a system can and should defend for HARMs itself
|
||||
-- @param #MANTIS self
|
||||
-- @param Wrapper.Group#GROUP targetGroup
|
||||
-- @param #string targetName
|
||||
-- @param Wrapper.Group#GROUP attackerGroup
|
||||
-- @param #string weaponName
|
||||
-- @param Wrapper.Weapon#WEAPON weaponWrapper
|
||||
-- @param #number tti
|
||||
-- @param #number delay
|
||||
-- @return #boolean Outcome
|
||||
function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, weaponName, weaponWrapper, tti, delay)
|
||||
self:T(self.lid.."SeadAllowSuppression")
|
||||
|
||||
--- Thanks to @Goon Jan 2026
|
||||
----------------------------------------------------------------
|
||||
-- LOG INCOMING REQUEST
|
||||
----------------------------------------------------------------
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression REQUEST | target=%s | weapon=%s | tti=%s | delay=%s",tostring(targetName),
|
||||
tostring(weaponName),tostring(tti),tostring(delay)))
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- LOOK UP ARM CAPACITY FOR THIS SAM
|
||||
----------------------------------------------------------------
|
||||
local armcap = targetGroup:GetProperty("ARMCapacity")
|
||||
|
||||
if not armcap then
|
||||
for _, sam in pairs(self.SAM_Table or {}) do
|
||||
if sam[1] == targetName then
|
||||
armcap = sam[7] -- ARMCapacity
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression SAM DATA | target=%s | ARMCapacity=%s",tostring(targetName),armcap and tostring(armcap) or "nil"))
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- TRACK SEAD THREATS (PER TARGET)
|
||||
----------------------------------------------------------------
|
||||
local THREAT_WINDOW = 0.1 -- seconds
|
||||
|
||||
self.LastThreatEval = self.LastThreatEval or {}
|
||||
self.InboundARMs = self.InboundARMs or {}
|
||||
|
||||
local now = timer.getTime()
|
||||
local last = self.LastThreatEval[targetName] or 0
|
||||
|
||||
if (now - last) >= THREAT_WINDOW then
|
||||
self.InboundARMs[targetName] = (self.InboundARMs[targetName] or 0) + 1
|
||||
self.LastThreatEval[targetName] = now
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression NEW threat accepted | Δt=%.3f",now - last))
|
||||
else
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression duplicate evaluation ignored | Δt=%.3f",now - last))
|
||||
end
|
||||
|
||||
local inbound = self.InboundARMs[targetName] or 0
|
||||
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression THREAT COUNT | target=%s | inboundThreats=%d",tostring(targetName),inbound))
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- DECISION GATE
|
||||
----------------------------------------------------------------
|
||||
|
||||
-- No missiles left over → legacy behavior
|
||||
if targetGroup and targetGroup:IsAlive() then
|
||||
local AmmotT, AmmoS, _, _,AmmoM = targetGroup:GetAmmunition()
|
||||
-- TODO Check C-RAM probably needs an exception as it is a gun, need to check its effectiveness
|
||||
if AmmoM and AmmoM == 0 then
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (no MISSILES) | target=%s",tostring(targetName)))
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- No ARM capacity defined → legacy behavior
|
||||
if (not armcap) or armcap == 0 then
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (no ARMCAP) | target=%s",tostring(targetName)))
|
||||
return true
|
||||
end
|
||||
|
||||
-- Suppress only once enough threats accumulated
|
||||
if inbound >= armcap then
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (inbound %d >= cap %d) | target=%s",inbound,armcap,tostring(targetName)))
|
||||
return true
|
||||
end
|
||||
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> DENIED (inbound %d < cap %d) | target=%s",inbound,armcap,tostring(targetName)))
|
||||
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
--- [Internal] Check detection function
|
||||
-- @param #MANTIS self
|
||||
@@ -1807,7 +2225,7 @@ do
|
||||
local radius = _data[3]
|
||||
local height = _data[4]
|
||||
local blind = _data[5] * 1.25 + 1
|
||||
local shortsam = (_data[6] == MANTIS.SamType.SHORT) and true or false
|
||||
local shortsam = (_data[6] ~= MANTIS.SamType.LONG) and true or false
|
||||
if not shortsam then
|
||||
shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false
|
||||
end
|
||||
@@ -1818,6 +2236,7 @@ do
|
||||
if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then
|
||||
activeshorad = true
|
||||
end
|
||||
if samgroup:GetProperty("SHORAD_ACTIVE") == true and activeshorad == false then activeshorad = true end
|
||||
if IsInZone and (not suppressed) and (not activeshorad) then --check any target in zone and not currently managed by SEAD
|
||||
if samgroup:IsAlive() then
|
||||
-- switch on SAM
|
||||
@@ -1833,19 +2252,13 @@ do
|
||||
switch = true
|
||||
end
|
||||
if self.SamStateTracker[name] ~= "RED" and switch then
|
||||
self:__RedState(1,samgroup)
|
||||
self.SamStateTracker[name] = "RED"
|
||||
self:__RedState(1,samgroup)
|
||||
end
|
||||
-- TODO doesn't work
|
||||
if shortsam == true and self.SmokeDecoy == true then
|
||||
-- DONE Restrict on Distance
|
||||
if shortsam == true and self.SmokeDecoy == true and Distance < self.DetectAccousticRadius*1.5 then
|
||||
self:T("Smoking")
|
||||
local units = samgroup:GetUnits() or {}
|
||||
local smoke = self.SmokeDecoyColor or SMOKECOLOR.White
|
||||
for _,unit in pairs(units) do
|
||||
if unit and unit:IsAlive() then
|
||||
unit:GetCoordinate():Smoke(smoke)
|
||||
end
|
||||
end
|
||||
self:_SmokeUnits(samgroup)
|
||||
end
|
||||
-- link in to SHORAD if available
|
||||
-- DONE: Test integration fully
|
||||
@@ -1872,8 +2285,8 @@ do
|
||||
samgroup:OptionAlarmStateGreen()
|
||||
end
|
||||
if self.SamStateTracker[name] ~= "GREEN" then
|
||||
self:__GreenState(1,samgroup)
|
||||
self.SamStateTracker[name] = "GREEN"
|
||||
self:__GreenState(1,samgroup)
|
||||
end
|
||||
if self.debug or self.verbose then
|
||||
local text = string.format("SAM %s in alarm state GREEN!", name)
|
||||
@@ -1883,12 +2296,13 @@ do
|
||||
end --end alive
|
||||
end --end check
|
||||
end --for loop
|
||||
--[[
|
||||
if self.debug or self.verbose or self.logsamstatus then
|
||||
for _,_status in pairs(self.SamStateTracker) do
|
||||
if _status == "GREEN" then
|
||||
instatusgreen=instatusgreen+1
|
||||
elseif _status == "RED" then
|
||||
instatusred=instatusred+1
|
||||
instatusred=instatusred+1
|
||||
end
|
||||
end
|
||||
if self.Shorad then
|
||||
@@ -1897,6 +2311,8 @@ do
|
||||
end
|
||||
end
|
||||
end
|
||||
self:T(self.lid..string.format("SAM State Count: GREEN %d | RED %d | SHORAD %d",instatusred, instatusgreen, activeshorads))
|
||||
--]]
|
||||
return instatusred, instatusgreen, activeshorads
|
||||
end
|
||||
|
||||
@@ -1928,13 +2344,29 @@ do
|
||||
local samset = self.SAM_Table_Short -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
local instatusreds, instatusgreens, activeshoradss = self:_CheckLoop(samset,detset,dlink,self.maxshortrange)
|
||||
local samset = self.SAM_Table_PointDef -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange)
|
||||
local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange)
|
||||
else
|
||||
local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic)
|
||||
local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic)
|
||||
end
|
||||
|
||||
local function GetReport()
|
||||
|
||||
if self.debug or self.verbose or self.logsamstatus then
|
||||
for _,_status in pairs(self.SamStateTracker) do
|
||||
if _status == "GREEN" then
|
||||
instatusgreen=instatusgreen+1
|
||||
elseif _status == "RED" then
|
||||
instatusred=instatusred+1
|
||||
end
|
||||
end
|
||||
if self.Shorad then
|
||||
for _,_name in pairs(self.Shorad.ActiveGroups or {}) do
|
||||
activeshorads=activeshorads+1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local statusreport = REPORT:New("\nMANTIS Status "..self.name)
|
||||
statusreport:Add("+-----------------------------+")
|
||||
statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred))
|
||||
@@ -2030,21 +2462,22 @@ do
|
||||
else
|
||||
self.Detection = self:StartIntelDetection()
|
||||
end
|
||||
--[[
|
||||
if self.advAwacs and not self.automode then
|
||||
self.AWACS_Detection = self:StartAwacsDetection()
|
||||
end
|
||||
--]]
|
||||
|
||||
if self.autoshorad then
|
||||
self.Shorad = SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff)
|
||||
self.Shorad = SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.Coalition,self.UseEmOnOff,self.SmokeDecoy,self.SmokeDecoyColor)
|
||||
self.Shorad:SetDefenseLimits(80,95)
|
||||
self.ShoradLink = true
|
||||
self.Shorad.Groupset=self.ShoradGroupSet
|
||||
self.Shorad.debug = self.debug
|
||||
self.Shorad:AddCallBack(self)
|
||||
end
|
||||
if self.shootandscoot and self.SkateZones and self.Shorad then
|
||||
self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation)
|
||||
end
|
||||
|
||||
self:HandleEvent(EVENTS.Hit,self._EventHandler)
|
||||
self:HandleEvent(EVENTS.UnitLost,self._EventHandler)
|
||||
|
||||
self:__Status(-math.random(1,10))
|
||||
return self
|
||||
end
|
||||
@@ -2242,6 +2675,7 @@ do
|
||||
function MANTIS:onafterSeadSuppressionEnd(From, Event, To, Group, Name)
|
||||
self:T({From, Event, To, Name})
|
||||
self.SuppressedGroups[Name] = false
|
||||
self.InboundARMs[Name] = 0
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ PSEUDOATC.id="PseudoATC | "
|
||||
|
||||
--- PSEUDOATC version.
|
||||
-- @field #number version
|
||||
PSEUDOATC.version="0.10.5"
|
||||
PSEUDOATC.version="0.10.6"
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -579,6 +579,9 @@ function PSEUDOATC:MenuAirports(GID,UID)
|
||||
-- Create menu reporting commands
|
||||
missionCommands.addCommandForGroup(GID, "Weather Report", submenu, self.ReportWeather, self, GID, UID, pos, name)
|
||||
missionCommands.addCommandForGroup(GID, "Request BR", submenu, self.ReportBR, self, GID, UID, pos, name)
|
||||
if self.radios then
|
||||
missionCommands.addCommandForGroup(GID, "Radios", submenu, self.ReportRadios, self, GID, UID, pos, name)
|
||||
end
|
||||
|
||||
-- Debug message.
|
||||
self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, GID))
|
||||
@@ -705,7 +708,30 @@ function PSEUDOATC:ReportWeather(GID, UID, position, location)
|
||||
|
||||
end
|
||||
|
||||
--- Report absolute bearing and range form player unit to airport.
|
||||
--- Report airport radio information.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
-- @param Core.Point#COORDINATE position Coordinate of the airport.
|
||||
-- @param #string location Name of the airport.
|
||||
function PSEUDOATC:ReportRadios(GID, UID, position, location)
|
||||
self:F({GID=GID, UID=UID, position=position, location=location})
|
||||
if self.radios then
|
||||
local Text=""
|
||||
local radio = self.radios:GetClosestRadio(position,9) --Navigation.Radios#RADIOS.Radio
|
||||
if radio then
|
||||
Text=self.radios:_GetMarkerText(radio)
|
||||
else
|
||||
Text=self.group[GID].player[UID].playername..", no radio information found!"
|
||||
end
|
||||
-- Send message
|
||||
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit, Text, self.mdur, true)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Report absolute bearing and range from player unit to airport.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
@@ -979,3 +1005,22 @@ function PSEUDOATC:_myname(unitname)
|
||||
|
||||
return string.format("%s (%s)", csign, pname)
|
||||
end
|
||||
|
||||
--- Returns a string which consits of this callsign and the player name.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #string path Path to map data, e.g. `[[<DCS_Install_Directory>\Mods\terrains\<Map_Name>\Radio.lua]]` (replace with correct path).
|
||||
-- Needs `lfs` and `io` to be desanitized in the `MissionScripting.lua` in `<DCS_Install_Directory>\Scripts`
|
||||
-- @return #PSEUDOATC self
|
||||
-- @usage
|
||||
--
|
||||
-- mypseudoatc:SetUsingRadioInformationFromMap([[C:\Program Files\Eagle Dynamics\DCS World.Openbeta\Mods\terrains\Caucasus\Radio.lua]])
|
||||
--
|
||||
function PSEUDOATC:SetUsingRadioInformationFromMap(path)
|
||||
if RADIOS and lfs and io then
|
||||
self.radios = RADIOS:NewFromFile(path)
|
||||
else
|
||||
self:E("PSEUDOATC:SetUsingRadioInformationFromMap Needs `lfs`and `io` to be desanitized in the `MissionScripting.lua` in `<DCS_Install_Directory>/Scripts`")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -5584,7 +5584,7 @@ end
|
||||
-- @param #string dest Name of the destination airport.
|
||||
function RAT:_ATCAddFlight(name, dest)
|
||||
-- Debug info
|
||||
BASE:I(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.", dest, name, dest))
|
||||
BASE:T(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.", dest, name, dest))
|
||||
|
||||
-- Create new flight
|
||||
local flight={} --#RAT.AtcFlight
|
||||
@@ -5603,7 +5603,7 @@ end
|
||||
function RAT._ATCDelFlight(t,entry)
|
||||
for k,_ in pairs(t) do
|
||||
if k==entry then
|
||||
BASE:I(RAT.id..string.format("Removing flight %s from queue", entry))
|
||||
BASE:T(RAT.id..string.format("Removing flight %s from queue", entry))
|
||||
t[entry]=nil
|
||||
end
|
||||
end
|
||||
@@ -5614,7 +5614,7 @@ end
|
||||
-- @param #string name Group name of the flight.
|
||||
-- @param #number time Time the fight first registered.
|
||||
function RAT._ATCRegisterFlight(name, time)
|
||||
BASE:I(RAT.id..string.format("Flight %s registered at ATC for landing clearance.", name))
|
||||
BASE:T(RAT.id..string.format("Flight %s registered at ATC for landing clearance.", name))
|
||||
RAT.ATC.flight[name].Tarrive=time
|
||||
RAT.ATC.flight[name].holding=0
|
||||
end
|
||||
@@ -5649,15 +5649,16 @@ function RAT._ATCStatus()
|
||||
|
||||
-- Aircraft is holding.
|
||||
local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy)
|
||||
BASE:I(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
|
||||
elseif hold==RAT.ATC.onfinal then
|
||||
|
||||
-- Aircarft is on final approach for landing.
|
||||
local Tfinal=Tnow-flight.Tonfinal
|
||||
local Tonfinal = flight.Tonfinal or timer.getTime()-1
|
||||
local Tfinal=Tnow-Tonfinal
|
||||
|
||||
local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60)
|
||||
BASE:I(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
|
||||
elseif hold==RAT.ATC.unregistered then
|
||||
|
||||
@@ -5711,13 +5712,13 @@ function RAT._ATCCheck()
|
||||
-- Debug message.
|
||||
local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.",
|
||||
airportname, flightname, qID, nqueue, flight.holding/60, flight.holding%60)
|
||||
BASE:I(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
|
||||
else
|
||||
|
||||
local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.",
|
||||
airportname, flightname, flight.holding/60, flight.holding%60)
|
||||
BASE:I(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
|
||||
-- Clear flight for landing.
|
||||
RAT._ATCClearForLanding(airportname, flightname)
|
||||
@@ -5772,7 +5773,7 @@ function RAT._ATCClearForLanding(airportname, flightname)
|
||||
|
||||
|
||||
-- Debug message.
|
||||
BASE:I(RAT.id..string.format("ATC %s: Flight %s cleared for landing", airportname, flightname))
|
||||
BASE:T(RAT.id..string.format("ATC %s: Flight %s cleared for landing", airportname, flightname))
|
||||
|
||||
if string.find(flightname,"#") then
|
||||
flightname = string.match(flightname,"^(.+)#")
|
||||
@@ -5799,8 +5800,9 @@ function RAT._ATCFlightLanded(name)
|
||||
|
||||
-- Times for holding and final approach.
|
||||
local Tnow=timer.getTime()
|
||||
local Tfinal=Tnow-flight.Tonfinal
|
||||
local Thold=flight.Tonfinal-flight.Tarrive
|
||||
local Tonfinal = flight.Tonfinal or timer.getTime()-1
|
||||
local Tfinal=Tnow-Tonfinal
|
||||
local Thold=Tonfinal-flight.Tarrive
|
||||
|
||||
local airport=RAT.ATC.airport[dest] --#RAT.AtcAirport
|
||||
|
||||
@@ -5823,9 +5825,9 @@ function RAT._ATCFlightLanded(name)
|
||||
local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600
|
||||
|
||||
-- Debug info
|
||||
BASE:I(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.", dest, name, Thold/60, Thold%60, Tfinal/60, Tfinal%60))
|
||||
BASE:I(RAT.id..string.format("ATC %s: Number of flights still on final %d.", dest, airport.Nonfinal))
|
||||
BASE:I(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.", dest, airport.traffic, TrafficPerHour))
|
||||
BASE:T(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.", dest, name, Thold/60, Thold%60, Tfinal/60, Tfinal%60))
|
||||
BASE:T(RAT.id..string.format("ATC %s: Number of flights still on final %d.", dest, airport.Nonfinal))
|
||||
BASE:T(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.", dest, airport.traffic, TrafficPerHour))
|
||||
|
||||
if string.find(name,"#") then
|
||||
name = string.match(name,"^(.+)#")
|
||||
|
||||
@@ -109,7 +109,6 @@
|
||||
-- @field Core.Menu#MENU_MISSION menuF10root Specific user defined root F10 menu.
|
||||
-- @field #number ceilingaltitude Range ceiling altitude in ft MSL. Aircraft above this altitude are not considered to be in the range. Default is 20000 ft.
|
||||
-- @field #boolean ceilingenabled Range has a ceiling and is not unlimited. Default is false.
|
||||
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven
|
||||
@@ -1271,6 +1270,9 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
|
||||
self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE)
|
||||
self.controlmsrs:SetLabel("RANGEC")
|
||||
self.controlmsrs:SetVolume(Volume or 1.0)
|
||||
if self.rangezone then
|
||||
self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate())
|
||||
end
|
||||
self.controlsrsQ = MSRSQUEUE:New("CONTROL")
|
||||
|
||||
self.instructmsrs=MSRS:New(PathToSRS or MSRS.path, Frequency or 305, Modulation or radio.modulation.AM)
|
||||
@@ -1278,6 +1280,9 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
|
||||
self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE)
|
||||
self.instructmsrs:SetLabel("RANGEI")
|
||||
self.instructmsrs:SetVolume(Volume or 1.0)
|
||||
if self.rangezone then
|
||||
self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate())
|
||||
end
|
||||
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
|
||||
|
||||
if PathToGoogleKey then
|
||||
@@ -1316,8 +1321,13 @@ function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender
|
||||
self.rangecontrol = true
|
||||
if relayunitname then
|
||||
local unit = UNIT:FindByName(relayunitname)
|
||||
local Coordinate = unit:GetCoordinate()
|
||||
self.rangecontrolrelayname = relayunitname
|
||||
if unit then
|
||||
local Coordinate = unit:GetCoordinate()
|
||||
self.rangecontrolrelayname = relayunitname
|
||||
self.controlmsrs:SetCoordinate(Coordinate)
|
||||
else
|
||||
MESSAGE:New("RANGE: Control Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog()
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
@@ -1345,9 +1355,13 @@ function RANGE:SetSRSRangeInstructor( frequency, modulation, voice, culture, gen
|
||||
self.instructor = true
|
||||
if relayunitname then
|
||||
local unit = UNIT:FindByName(relayunitname)
|
||||
local Coordinate = unit:GetCoordinate()
|
||||
self.instructmsrs:SetCoordinate(Coordinate)
|
||||
self.instructorrelayname = relayunitname
|
||||
if unit then
|
||||
local Coordinate = unit:GetCoordinate()
|
||||
self.instructmsrs:SetCoordinate(Coordinate)
|
||||
self.instructorrelayname = relayunitname
|
||||
else
|
||||
MESSAGE:New("RANGE: Instructor Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog()
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -230,8 +230,10 @@ SCORING = {
|
||||
ClassID = 0,
|
||||
Players = {},
|
||||
AutoSave = true,
|
||||
version = "1.18.4",
|
||||
version = "1.18.5",
|
||||
ScoringScenery = nil, -- Core.Set#SET_SCENERY
|
||||
SceneryHitsInZone = false,
|
||||
LoadSave = false,
|
||||
}
|
||||
|
||||
local _SCORINGCoalition = {
|
||||
@@ -250,15 +252,16 @@ local _SCORINGCategory = {
|
||||
--- Creates a new SCORING object to administer the scoring achieved by players.
|
||||
-- @param #SCORING self
|
||||
-- @param #string GameName The name of the game. This name is also logged in the CSV score file.
|
||||
-- @param #string SavePath (Optional) Path where to save the CSV file, defaults to your **<User>\\Saved Games\\DCS\\Logs** folder.
|
||||
-- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off.
|
||||
-- @param #string SavePath (Optional) Path where to save the CSV files, defaults to your **<User>\\Saved Games\\DCS\\Logs** folder. See next two options.
|
||||
-- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off. This stores a detailed table which will never be loaded again by SCORING (for e.g. Discord purposes).
|
||||
-- @param #boolean LoadSave (Optional) If passed as `true` save summary scores per player, and load at restart of the mission.
|
||||
-- @return #SCORING self
|
||||
-- @usage
|
||||
--
|
||||
-- -- Define a new scoring object for the mission Gori Valley.
|
||||
-- ScoringObject = SCORING:New( "Gori Valley" )
|
||||
--
|
||||
function SCORING:New( GameName, SavePath, AutoSave )
|
||||
function SCORING:New( GameName, SavePath, AutoSave, LoadSave )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SCORING
|
||||
@@ -266,7 +269,7 @@ function SCORING:New( GameName, SavePath, AutoSave )
|
||||
if GameName then
|
||||
self.GameName = GameName
|
||||
else
|
||||
error( "A game name must be given to register the scoring results" )
|
||||
error( "A game name must be given to register the scoring results!" )
|
||||
end
|
||||
|
||||
-- Additional Object scores
|
||||
@@ -301,6 +304,12 @@ function SCORING:New( GameName, SavePath, AutoSave )
|
||||
self.penaltyoncoalitionchange = true
|
||||
|
||||
self:SetDisplayMessagePrefix()
|
||||
|
||||
self.SceneryHitsInZone = false
|
||||
|
||||
if LoadSave then
|
||||
self.LoadSave = LoadSave
|
||||
end
|
||||
|
||||
-- Event handlers
|
||||
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
|
||||
@@ -328,11 +337,61 @@ function SCORING:New( GameName, SavePath, AutoSave )
|
||||
end
|
||||
|
||||
self:I("SCORING "..tostring(GameName).." started! v"..self.version)
|
||||
|
||||
|
||||
if LoadSave == true then
|
||||
self:_LoadPlayerSummaryScore()
|
||||
end
|
||||
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
--- [Internal] Helper to load scores from disk at scoring start
|
||||
-- @param #SCORING self
|
||||
-- @return #SCORING self
|
||||
function SCORING:_LoadPlayerSummaryScore()
|
||||
|
||||
if lfs and io and self.LoadSave == true then
|
||||
local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]]
|
||||
local filename = self.GameName or "PlayerScoresSummary"
|
||||
filename = filename..".csv"
|
||||
if UTILS.CheckFileExists(path,filename) then
|
||||
local ok, data = UTILS.LoadFromFile(path,filename)
|
||||
-- Playername;;Score;;Penalty
|
||||
table.remove(data,1)
|
||||
for _,_data in pairs(data) do
|
||||
local line = UTILS.Split(_data,";;")
|
||||
local playername = tostring(line[1])
|
||||
local score = tonumber(line[2])
|
||||
local penalty = tonumber(line[3])
|
||||
self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty))
|
||||
local PlayerData = self.Players[playername]
|
||||
if not PlayerData then
|
||||
PlayerData = {}
|
||||
PlayerData.Hit = {}
|
||||
PlayerData.Destroy = {}
|
||||
PlayerData.Goals = {}
|
||||
PlayerData.Goals[self.GameName] = {Score = score, Penalty = penalty}
|
||||
PlayerData.Mission = {}
|
||||
PlayerData.HitPlayers = {}
|
||||
PlayerData.Score = score
|
||||
PlayerData.Penalty = penalty
|
||||
PlayerData.PenaltyCoalition = 0
|
||||
PlayerData.PenaltyWarning = 0
|
||||
self.Players[playername] = PlayerData
|
||||
else
|
||||
PlayerData.Score = score
|
||||
PlayerData.Penalty =penalty
|
||||
self.Players[playername] = PlayerData
|
||||
PlayerData.Goals[self.GameName] = {Score = score, Penalty = penalty}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set a prefix string that will be displayed at each scoring message sent.
|
||||
-- @param #SCORING self
|
||||
-- @param #string DisplayMessagePrefix (Default="Scoring: ") The scoring prefix string.
|
||||
@@ -579,6 +638,22 @@ function SCORING:AddZoneScore( ScoreZone, Score )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Allow Scenery hits in Zones to count (no specific(!) scenery targets). NOTE - Allowing this can spam your scoring display!
|
||||
-- @param #SCORING self
|
||||
-- @return #SCORING self
|
||||
function SCORING:EnableSceneryHitsinZones()
|
||||
self.SceneryHitsInZone = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Disallow Scenery hits in Zones to count (no specific(!) scenery targets).
|
||||
-- @param #SCORING self
|
||||
-- @return #SCORING self
|
||||
function SCORING:DisableSceneryHitsinZones()
|
||||
self.SceneryHitsInZone = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a @{Core.Set#SET_ZONE} to define additional scoring when any object is destroyed in that zone.
|
||||
-- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced!
|
||||
-- This allows for a dynamic destruction zone evolution within your mission.
|
||||
@@ -877,6 +952,20 @@ function SCORING:AddGoalScorePlayer( PlayerName, GoalTag, Text, Score )
|
||||
-- PlayerName can be nil, if the Unit with the player crashed or due to another reason.
|
||||
if PlayerName then
|
||||
local PlayerData = self.Players[PlayerName]
|
||||
if not PlayerData then
|
||||
PlayerData = {}
|
||||
PlayerData.Goals = {}
|
||||
PlayerData.Hit = {}
|
||||
PlayerData.Destroy = {}
|
||||
PlayerData.Goals = {}
|
||||
PlayerData.Mission = {}
|
||||
PlayerData.HitPlayers = {}
|
||||
PlayerData.Score = 0
|
||||
PlayerData.Penalty = 0
|
||||
PlayerData.PenaltyCoalition = 0
|
||||
PlayerData.PenaltyWarning = 0
|
||||
self.Players[PlayerName] = PlayerData
|
||||
end
|
||||
|
||||
PlayerData.Goals[GoalTag] = PlayerData.Goals[GoalTag] or { Score = 0 }
|
||||
PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score
|
||||
@@ -1153,7 +1242,7 @@ function SCORING:_EventOnHit( Event )
|
||||
TargetType = "Scenery"
|
||||
TargetSceneryObject = TargetUNIT
|
||||
self:T("***** Target is Scenery and TargetUNIT is SCENERY object!")
|
||||
UTILS.PrintTableToLog(TargetSceneryObject)
|
||||
--UTILS.PrintTableToLog(TargetSceneryObject)
|
||||
end
|
||||
|
||||
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
|
||||
@@ -1374,17 +1463,18 @@ function SCORING:_EventOnHit( Event )
|
||||
-- Player contains the score data from self.Players[WeaponPlayerName]
|
||||
local PlayerName = Event.IniPlayerName or "Ghost"
|
||||
local Player = self.Players[PlayerName]
|
||||
|
||||
Player.Score = Player.Score + Score
|
||||
Player.Score = Player.Score + self.ScoreIncrementOnHit
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "hit in zone '" .. ScoreZone:GetName() .. "'." ..
|
||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
|
||||
|
||||
self:ScoreCSV( PlayerName, "", "HIT_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Zone", TargetUnitType )
|
||||
end
|
||||
if Player then
|
||||
Player.Score = Player.Score + Score
|
||||
Player.Score = Player.Score + self.ScoreIncrementOnHit
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "hit in zone '" .. ScoreZone:GetName() .. "'." ..
|
||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
|
||||
|
||||
self:ScoreCSV( PlayerName, "", "HIT_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Zone", TargetUnitType )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1567,22 +1657,24 @@ function SCORING:_EventOnDeadOrCrash( Event )
|
||||
|
||||
end
|
||||
else
|
||||
-- Check if there are Zones where the destruction happened.
|
||||
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
|
||||
self:F( { ScoringZone = ScoreZoneData } )
|
||||
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
|
||||
local Score = ScoreZoneData.Score
|
||||
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
|
||||
Player.Score = Player.Score + Score
|
||||
TargetDestroy.Score = TargetDestroy.Score + Score
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
|
||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
|
||||
|
||||
self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType )
|
||||
Destroyed = true
|
||||
if self.SceneryHitsInZone == true then
|
||||
-- Check if there are Zones where the destruction happened.
|
||||
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
|
||||
self:F( { ScoringZone = ScoreZoneData } )
|
||||
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
|
||||
local Score = ScoreZoneData.Score
|
||||
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
|
||||
Player.Score = Player.Score + Score
|
||||
TargetDestroy.Score = TargetDestroy.Score + Score
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
|
||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
|
||||
|
||||
self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType )
|
||||
Destroyed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1926,9 +2018,12 @@ end
|
||||
--- Report all players score
|
||||
-- @param #SCORING self
|
||||
-- @param Wrapper.Group#GROUP PlayerGroup The player group.
|
||||
function SCORING:ReportScoreAllSummary( PlayerGroup )
|
||||
-- @param #boolean JustScore If this is true, return just a table with playernames and overall scores.
|
||||
-- @return #table ReportTable Table returned if JustScore is true.
|
||||
function SCORING:ReportScoreAllSummary( PlayerGroup, JustScore )
|
||||
|
||||
local PlayerMessage = ""
|
||||
local ReportTable = {}
|
||||
|
||||
self:T( { "Summary Score Report of All Players", Players = self.Players } )
|
||||
|
||||
@@ -1960,20 +2055,28 @@ function SCORING:ReportScoreAllSummary( PlayerGroup )
|
||||
|
||||
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
|
||||
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
|
||||
|
||||
PlayerMessage =
|
||||
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
|
||||
PlayerName,
|
||||
PlayerScore - PlayerPenalty,
|
||||
PlayerScore,
|
||||
PlayerPenalty
|
||||
)
|
||||
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
|
||||
|
||||
if JustScore~=true then
|
||||
PlayerMessage =
|
||||
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
|
||||
PlayerName,
|
||||
PlayerScore - PlayerPenalty,
|
||||
PlayerScore,
|
||||
PlayerPenalty
|
||||
)
|
||||
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
|
||||
else
|
||||
ReportTable[PlayerName] = {["Score"]=PlayerScore,["Penalty"]=PlayerPenalty}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ReportTable
|
||||
end
|
||||
|
||||
--- Opens a score CSV file to log the scores.
|
||||
-- @param #SCORING self
|
||||
-- @param #number sSeconds
|
||||
-- @return #string ClockString
|
||||
function SCORING:SecondsToClock( sSeconds )
|
||||
local nSeconds = sSeconds
|
||||
if nSeconds == 0 then
|
||||
@@ -2101,6 +2204,23 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes,
|
||||
|
||||
self.CSVFile:write( "\n" )
|
||||
end
|
||||
|
||||
if lfs and io and self.LoadSave == true then
|
||||
local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]]
|
||||
local filename = self.GameName or "PlayerScoresSummary"
|
||||
filename = filename..".csv"
|
||||
local data = self:ReportScoreAllSummary("",true)
|
||||
local text = "-- Playername;;Score;;Penalty\n"
|
||||
for _playername,_data in pairs(data or {}) do
|
||||
-- ReportTable[PlayerName] = {["Score"]=PlayerScore,["Penalty"]=PlayerPenalty}
|
||||
local Playername = _playername or "Ghost"
|
||||
local Score = _data.Score or 0
|
||||
local Penalty = _data.Penalty or 0
|
||||
text = text..string.format("%s;;%d;;%d\n",Playername,Score,Penalty)
|
||||
end
|
||||
UTILS.SaveToFile(path,filename,text)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Close CSV file
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
--
|
||||
-- ### Authors: **applevangelist**, **FlightControl**
|
||||
--
|
||||
-- Last Update: Dec 2024
|
||||
-- Last Update: January 2026
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -157,7 +157,7 @@ function SEAD:New( SEADGroupPrefixes, Padding )
|
||||
self:AddTransition("*", "ManageEvasion", "*")
|
||||
self:AddTransition("*", "CalculateHitZone", "*")
|
||||
|
||||
self:I("*** SEAD - Started Version 0.4.9")
|
||||
self:I("*** SEAD - Started Version 0.4.11")
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -318,7 +318,7 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG
|
||||
elseif height <= 12500 then
|
||||
Ropt = Ropt * 0.98
|
||||
end
|
||||
|
||||
local WeaponWrapper = WEAPON:New(SEADWeapon)
|
||||
-- look at a couple of zones across the trajectory
|
||||
for n=1,3 do
|
||||
local dist = Ropt - ((n-1)*20000)
|
||||
@@ -342,7 +342,7 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG
|
||||
_targetgroupname = tgtgrp:GetName() -- group name
|
||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||
self:T("*** Found Target = ".. _targetgroupname)
|
||||
self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20)
|
||||
self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20, WeaponWrapper)
|
||||
end
|
||||
--end
|
||||
end
|
||||
@@ -442,7 +442,18 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP
|
||||
local SuppressionStartTime = timer.getTime() + delay
|
||||
local SuppressionEndTime = timer.getTime() + delay + _tti + self.Padding + delay
|
||||
local _targetgroupname = _targetgroup:GetName()
|
||||
if not self.SuppressedGroups[_targetgroupname] then
|
||||
local shoradactive = _targetgroup:GetProperty("SHORAD_ACTIVE")
|
||||
if not self.SuppressedGroups[_targetgroupname] and shoradactive ~= true then
|
||||
-- TODO: ask callback if suppression is allowed BEFORE scheduling timers
|
||||
local allow = true
|
||||
if self.UseCallBack and self.CallBack and self.CallBack.SeadAllowSuppression then
|
||||
allow = self.CallBack:SeadAllowSuppression(_targetgroup,_targetgroupname,SEADGroup,SEADWeaponName,Weapon,_tti,delay)
|
||||
end
|
||||
if not allow then
|
||||
self:T(string.format("*** SEAD - %s | Suppression vetoed by callback", _targetgroupname))
|
||||
-- Important: do NOT schedule SuppressionStart/Stop and do NOT flip SuppressedGroups true.
|
||||
return self
|
||||
end
|
||||
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
|
||||
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime)
|
||||
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: Nov 2021
|
||||
-- Last Update: Jan 2025
|
||||
-- Last Update: Jan 2026
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
@@ -48,7 +48,9 @@
|
||||
-- @field #number minscootdist Min distance of the next zone
|
||||
-- @field #number maxscootdist Max distance of the next zone
|
||||
-- @field #boolean scootrandomcoord If true, use a random coordinate in the zone and not the center
|
||||
-- @field #string scootformation Formation to take for scooting, e.g. "Vee" or "Cone"
|
||||
-- @field #string scootformation Formation to take for scooting, e.g. "Vee" or "Cone"
|
||||
-- @field #boolean SmokeDecoy = false,
|
||||
-- @field #number SmokeDecoyColor = SMOKECOLOR.White
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
@@ -114,7 +116,9 @@ SHORAD = {
|
||||
SkateZones = nil,
|
||||
minscootdist = 100,
|
||||
maxscootdist = 3000,
|
||||
scootrandomcoord = false,
|
||||
scootrandomcoord = false,
|
||||
SmokeDecoy = false,
|
||||
SmokeDecoyColor = SMOKECOLOR.White
|
||||
}
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
@@ -161,8 +165,10 @@ do
|
||||
-- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call
|
||||
-- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral"
|
||||
-- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch)
|
||||
-- @param #boolean SmokeDecoy Throw smoke decoy when getting activated. Defaults to false.
|
||||
-- @param #number SmokeDecoyColor SMOLECOLOR to use. Defaults to SMOLECOLOR.White
|
||||
-- @return #SHORAD self
|
||||
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff)
|
||||
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff, SmokeDecoy, SmokeDecoyColor)
|
||||
local self = BASE:Inherit( self, FSM:New() )
|
||||
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
|
||||
|
||||
@@ -171,6 +177,7 @@ do
|
||||
self.name = Name or "MyShorad"
|
||||
self.Prefixes = ShoradPrefix or "SAM SHORAD"
|
||||
self.Radius = Radius or 20000
|
||||
if type(Coalition) == "number" then Coalition = string.lower(UTILS.GetCoalitionName(Coalition)) end
|
||||
self.Coalition = Coalition or "blue"
|
||||
self.Samset = Samset or GroupSet
|
||||
self.ActiveTimer = ActiveTimer or 600
|
||||
@@ -181,8 +188,15 @@ do
|
||||
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
|
||||
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
|
||||
self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||
|
||||
if UseEmOnOff == false then self.UseEmOnOff = UseEmOnOff end
|
||||
self:I("*** SHORAD - Started Version 0.3.4")
|
||||
|
||||
if SmokeDecoy then
|
||||
self.SmokeDecoy = SmokeDecoy
|
||||
self.SmokeDecoyColor = SmokeDecoyColor or SMOKECOLOR.White
|
||||
end
|
||||
|
||||
self:I("*** SHORAD - Started Version 0.3.6")
|
||||
-- Set the string id for output to DCS.log file.
|
||||
self.lid=string.format("SHORAD %s | ", self.name)
|
||||
self:_InitState()
|
||||
@@ -451,6 +465,43 @@ do
|
||||
return returnname
|
||||
end
|
||||
|
||||
--- Set an object to call back when going evasive.
|
||||
-- @param #SHORAD self
|
||||
-- @param #table Object The object to call.
|
||||
-- @return #SHORAD self
|
||||
function SHORAD:AddCallBack(Object)
|
||||
self:T({Class=Object.ClassName})
|
||||
self.CallBack = Object
|
||||
self.UseCallBack = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Smoke a SHORAD Group
|
||||
-- @param #SHORAD self
|
||||
-- @param Wrapper.Group#GROUP Group The Shorad Group to Smoke
|
||||
-- @return self
|
||||
function SHORAD:_SmokeUnits(Group)
|
||||
if self.SmokeDecoy == true then
|
||||
if Group and Group:IsAlive() then
|
||||
local units = Group:GetUnits()
|
||||
for _,_unit in pairs(units) do
|
||||
local unit = _unit -- Wrapper.Unit#UNIT
|
||||
if unit and unit:IsAlive() then
|
||||
local coordinate = unit:GetCoordinate()
|
||||
if coordinate then
|
||||
coordinate:SwitchSmokeOffsetOn()
|
||||
coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,1,20)
|
||||
coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,180,20)
|
||||
coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,270,20)
|
||||
coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,90,20)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Calculate if the missile shot is detected
|
||||
-- @param #SHORAD self
|
||||
-- @return #boolean Returns true for a detection, else false
|
||||
@@ -484,7 +535,56 @@ do
|
||||
-- mymantis:Start()
|
||||
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat, ShotAt)
|
||||
self:T(self.lid .. " WakeUpShorad")
|
||||
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
--self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
|
||||
local TDiff = 4
|
||||
|
||||
-- local function to switch off shorad again
|
||||
local function SleepShorad(group)
|
||||
if group and group:IsAlive() then
|
||||
local groupname = group:GetName()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
if self.UseEmOnOff then
|
||||
group:EnableEmission(false)
|
||||
else
|
||||
group:OptionAlarmStateGreen()
|
||||
end
|
||||
group:SetProperty("SHORAD_ACTIVE",false)
|
||||
local text = string.format("Sleeping SHORAD %s", group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
--Shoot and Scoot
|
||||
if self.shootandscoot then
|
||||
self:__ShootAndScoot(1,group)
|
||||
else
|
||||
--group:RelocateGroundRandomInRadius(30,500,false,true,"Diamond",true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function WakeUp(_group,groupname)
|
||||
-- shot at a group we protect
|
||||
local text = string.format("Waking up SHORAD %s", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
if self.UseEmOnOff then
|
||||
_group:EnableEmission(true)
|
||||
end
|
||||
_group:OptionAlarmStateRed()
|
||||
_group:SetProperty("SHORAD_ACTIVE",true)
|
||||
self:_SmokeUnits(_group)
|
||||
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
|
||||
self.ActiveGroups[groupname] = { Timing = ActiveTimer }
|
||||
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
|
||||
self.ActiveGroups[groupname].Timer = TIMER:New(SleepShorad,_group):Start(endtime)
|
||||
--Shoot and Scoot
|
||||
if self.shootandscoot then
|
||||
self:__ShootAndScoot(TDiff,_group)
|
||||
TDiff=TDiff+1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local targetcat = TargetCat or Object.Category.UNIT
|
||||
local targetgroup = TargetGroup
|
||||
local targetvec2 = nil
|
||||
@@ -501,69 +601,41 @@ do
|
||||
local groupset = self.Groupset --Core.Set#SET_GROUP
|
||||
local shoradset = groupset:GetAliveSet() --#table
|
||||
|
||||
-- local function to switch off shorad again
|
||||
local function SleepShorad(group)
|
||||
if group and group:IsAlive() then
|
||||
local groupname = group:GetName()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
if self.UseEmOnOff then
|
||||
group:EnableEmission(false)
|
||||
else
|
||||
group:OptionAlarmStateGreen()
|
||||
end
|
||||
local text = string.format("Sleeping SHORAD %s", group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
--Shoot and Scoot
|
||||
if self.shootandscoot then
|
||||
self:__ShootAndScoot(1,group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- go through set and find the one(s) to activate
|
||||
local TDiff = 4
|
||||
|
||||
for _,_group in pairs (shoradset) do
|
||||
|
||||
local groupname = _group:GetName()
|
||||
|
||||
if groupname == TargetGroup and ShotAt==true then
|
||||
-- Shot at a SHORAD group
|
||||
if self.UseEmOnOff then
|
||||
_group:EnableEmission(false)
|
||||
local allow = false
|
||||
if self.CallBack and self.UseCallBack == true then
|
||||
allow = self.CallBack:SeadAllowSuppression(_group,groupname)
|
||||
end
|
||||
_group:OptionAlarmStateGreen()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
|
||||
--Shoot and Scoot
|
||||
if self.shootandscoot then
|
||||
self:__ShootAndScoot(1,_group)
|
||||
end
|
||||
|
||||
elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then
|
||||
-- shot at a group we protect
|
||||
local text = string.format("Waking up SHORAD %s", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
if self.UseEmOnOff then
|
||||
_group:EnableEmission(true)
|
||||
end
|
||||
_group:OptionAlarmStateRed()
|
||||
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
|
||||
self.ActiveGroups[groupname] = { Timing = ActiveTimer }
|
||||
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
|
||||
self.ActiveGroups[groupname].Timer = TIMER:New(SleepShorad,_group):Start(endtime)
|
||||
if allow == true then
|
||||
if self.UseEmOnOff then
|
||||
_group:EnableEmission(false)
|
||||
end
|
||||
_group:OptionAlarmStateGreen()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
self:_SmokeUnits(_group)
|
||||
--Shoot and Scoot
|
||||
if self.shootandscoot then
|
||||
self:__ShootAndScoot(TDiff,_group)
|
||||
TDiff=TDiff+1
|
||||
self:__ShootAndScoot(1,_group)
|
||||
else
|
||||
_group:RelocateGroundRandomInRadius(30,500,false,true,"Diamond",true)
|
||||
end
|
||||
else
|
||||
WakeUp(_group,groupname)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then
|
||||
WakeUp(_group,groupname)
|
||||
end -- end if
|
||||
end -- end in pairs
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -679,11 +751,12 @@ do
|
||||
-- @param Core.Event#EVENTDATA EventData The event details table data set
|
||||
-- @return #SHORAD self
|
||||
function SHORAD:HandleEventShot( EventData )
|
||||
self:T( { EventData } )
|
||||
--self:T( { EventData.id } )
|
||||
self:T(self.lid .. " HandleEventShot")
|
||||
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local ShootingWeaponName = EventData.WeaponName -- return weapon type
|
||||
-- get firing coalition
|
||||
if not EventData.IniGroup then return self end
|
||||
local weaponcoalition = EventData.IniGroup:GetCoalition()
|
||||
-- get detection probability
|
||||
if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire
|
||||
@@ -703,7 +776,7 @@ do
|
||||
-- Is there target data?
|
||||
if not targetdata or self.debug then
|
||||
if string.find(ShootingWeaponName,"AGM_88",1,true) then
|
||||
self:I("**** Tracking AGM-88 with no target data.")
|
||||
self:T("**** Tracking AGM-88 with no target data.")
|
||||
local pos0 = EventData.IniUnit:GetCoordinate()
|
||||
local fheight = EventData.IniUnit:GetHeight()
|
||||
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
|
||||
|
||||
@@ -4112,10 +4112,10 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay,
|
||||
|
||||
-- Get name of template group.
|
||||
local templategroupname=group:GetName()
|
||||
|
||||
local Descriptors=group:GetUnit(1):GetDesc()
|
||||
local unit = group:GetUnit(1)
|
||||
local Descriptors= (unit and unit:IsAlive()) and unit:GetDesc() or {}
|
||||
local Category=group:GetCategory()
|
||||
local TypeName=group:GetTypeName()
|
||||
local TypeName=group:GetTypeName() or "none"
|
||||
local SpeedMax=group:GetSpeedMax()
|
||||
local RangeMin=group:GetRange()
|
||||
local smax,sx,sy,sz=_GetObjectSize(Descriptors)
|
||||
@@ -6132,6 +6132,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
|
||||
unit.onboard_num=asset.modex[i]
|
||||
end
|
||||
if asset.callsign then
|
||||
--UTILS.PrintTableToLog(asset.callsign)
|
||||
unit.callsign=asset.callsign[i]
|
||||
end
|
||||
|
||||
@@ -8596,8 +8597,8 @@ function WAREHOUSE:_DeleteStockItem(stockitem)
|
||||
local item=self.stock[i] --#WAREHOUSE.Assetitem
|
||||
if item.uid==stockitem.uid then
|
||||
table.remove(self.stock,i)
|
||||
-- remove also from warehouse DB
|
||||
_WAREHOUSEDB.Assets[stockitem.uid]=nil
|
||||
-- remove also from warehouse DB (not good! causes an error if the asset needs to be added to a requesting wherehouse)
|
||||
--_WAREHOUSEDB.Assets[stockitem.uid]=nil
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
@@ -118,6 +118,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/PlayerRecce.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Squadron.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Target.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyGCICAP.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyA2G.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Balancer.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air.lua' )
|
||||
@@ -188,10 +189,8 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_Dispatcher
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Zone.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Dispatcher.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Beacons.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Point.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Procedure.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/FlightPlan.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Beacons.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Radios.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Towns.lua' )
|
||||
|
||||
|
||||
@@ -1,481 +0,0 @@
|
||||
--- **NAVIGATION** - Flight Plan.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Manage navigation aids
|
||||
-- * VOR, NDB
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops%20-%20FlightPlan).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Navigation.FlightPlan
|
||||
-- @image NAVIGATION_FlightPlan.png
|
||||
|
||||
|
||||
--- FLIGHTPLAN class.
|
||||
-- @type FLIGHTPLAN
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @field #table fixes Navigation fixes.
|
||||
-- @field Core.Pathline#PATHLINE pathline Pathline of the plan.
|
||||
-- @field Wrapper.Airbase#AIRBASE departureAirbase Departure airbase.
|
||||
-- @field Wrapper.Airbase#AIRBASE destinationAirbase Destination airbase.
|
||||
-- @field #number altitudeCruiseMin Minimum cruise altitude in feet MSL.
|
||||
-- @field #number altitudeCruiseMax Maximum cruise altitude in feet MSL.
|
||||
-- @extends Core.Pathline#PATHLINE
|
||||
|
||||
--- *Life is what happens to us while we are making other plans.* -- Allen Saunders
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The FLIGHTPLAN Concept
|
||||
--
|
||||
-- This class has a great concept!
|
||||
--
|
||||
-- # Basic Setup
|
||||
--
|
||||
-- A new `FLIGHTPLAN` object can be created with the @{#FLIGHTPLAN.New}() function.
|
||||
--
|
||||
-- myFlightplan=FLIGHTPLAN:New("Plan A")
|
||||
-- myFleet:SetPortZone(ZonePort1stFleet)
|
||||
-- myFleet:Start()
|
||||
--
|
||||
-- A fleet needs a *port zone*, which is set via the @{#FLIGHTPLAN.SetPortZone}(`PortZone`) function. This is the zone where the naval assets are spawned and return to.
|
||||
--
|
||||
-- Finally, the fleet needs to be started using the @{#FLIGHTPLAN.Start}() function. If the fleet is not started, it will not process any requests.
|
||||
--
|
||||
-- @field #FLIGHTPLAN
|
||||
FLIGHTPLAN = {
|
||||
ClassName = "FLIGHTPLAN",
|
||||
verbose = 0,
|
||||
fixes = {}
|
||||
}
|
||||
|
||||
--- Type of flightplan.
|
||||
-- @type FLIGHTPLAN.Type
|
||||
-- @field #string IFRH Instrument Flying Rules High Altitude.
|
||||
-- @field #string IFRL Instrument Flying Rules Low Altitude.
|
||||
-- @field #string VFR Visual Flight Rules.
|
||||
FLIGHTPLAN.Type={
|
||||
IFRH = "IFR High",
|
||||
IFRL = "IFR Low",
|
||||
VFR = "VFR",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- FLIGHTPLAN class version.
|
||||
-- @field #string version
|
||||
FLIGHTPLAN.version="0.0.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: How to connect SID, STAR, ENROUTE, TRANSITION, APPROACH. Typical flightplan SID --> ENROUTE --> STAR --> APPROACH
|
||||
-- TODO: Add approach.
|
||||
-- DONE: How to handle the FLIGHTGROUP:_LandAtAirBase
|
||||
-- TODO: Do we always need a holding pattern? https://www.faa.gov/air_traffic/publications/atpubs/aip_html/part2_enr_section_1.5.html#:~:text=If%20no%20holding%20pattern%20is,than%20that%20desired%20by%20ATC.
|
||||
-- DOEN: Read from MSFS file.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new FLIGHTPLAN instance.
|
||||
-- @param #FLIGHTPLAN self
|
||||
-- @param #string Name Name of this flight plan.
|
||||
-- @return #FLIGHTPLAN self
|
||||
function FLIGHTPLAN:New(Name)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
self=BASE:Inherit(self, PATHLINE:New(Name)) -- #FLIGHTPLAN
|
||||
|
||||
-- Set alias.
|
||||
self.alias=tostring(Name)
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("FLIGHTPLAN %s | ", self.alias)
|
||||
|
||||
--self.pathline=PATHLINE:New(Name)
|
||||
|
||||
-- Debug info.
|
||||
self:I(self.lid..string.format("Created FLIGHTPLAN!"))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new FLIGHTPLAN instance from another FLIGHTPLAN acting as blue print.
|
||||
-- The newly created flight plan is deep copied from the given one.
|
||||
-- @param #FLIGHTPLAN self
|
||||
-- @param #FLIGHTPLAN FlightPlan Blue print of the flight plan to copy.
|
||||
-- @return #FLIGHTPLAN self
|
||||
function FLIGHTPLAN:NewFromFlightPlan(FlightPlan)
|
||||
self=UTILS.DeepCopy(FlightPlan)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new FLIGHTPLAN instance from a given file.
|
||||
-- Currently, the file has to be an MSFS 2020 .pln file as, *e.g.*, exported from [Navigraph](https://navigraph.com/).
|
||||
--
|
||||
-- **Note** that the flight plan does only cover the departure, enroute and arrival portions but **not the approach** part!
|
||||
-- @param #FLIGHTPLAN self
|
||||
-- @param #string FileName Full path to file.
|
||||
-- @return #FLIGHTPLAN self
|
||||
function FLIGHTPLAN:NewFromFile(FileName)
|
||||
|
||||
if UTILS.FileExists(FileName) then
|
||||
|
||||
self=FLIGHTPLAN._ReadFileMSFS(FileName)
|
||||
|
||||
else
|
||||
error(string.format("ERROR: File not found! File name=%s", tostring(FileName)))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add navigation fix to the flight plan.
|
||||
-- @param #FLIGHTPLAN self
|
||||
-- @param Navigation.Point#NAVPOINT NavFix The nav fix.
|
||||
-- @return #FLIGHTPLAN self
|
||||
function FLIGHTPLAN:AddNavFix(NavFix)
|
||||
|
||||
table.insert(self.fixes, NavFix)
|
||||
|
||||
local point=self:AddPointFromVec3(NavFix.vector:GetVec3(true))
|
||||
|
||||
point.navpoint=NavFix
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set departure airbase.
|
||||
-- @param #FLIGHTPLAN self
|
||||
-- @param #string AirbaseName Name of the airbase or AIRBASE object.
|
||||
-- @return #FLIGHTPLAN self
|
||||
function FLIGHTPLAN:SetDepartureAirbase(AirbaseName)
|
||||
|
||||
self.departureAirbase=AIRBASE:FindByName(AirbaseName)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set destination airbase.
|
||||
-- @param #FLIGHTPLAN self
|
||||
-- @param #string AirbaseName Name of the airbase or AIRBASE object.
|
||||
-- @return #FLIGHTPLAN self
|
||||
function FLIGHTPLAN:SetDestinationAirbase(AirbaseName)
|
||||
|
||||
self.destinationAirbase=AIRBASE:FindByName(AirbaseName)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set cruise altitude.
|
||||
-- @param #FLIGHTPLAN self
|
||||
-- @param #number AltMin Minimum altitude in feet MSL.
|
||||
-- @param #number AltMax Maximum altitude in feet MSL. Default is `AltMin`.
|
||||
-- @return #FLIGHTPLAN self
|
||||
function FLIGHTPLAN:SetCruiseAltitude(AltMin, AltMax)
|
||||
|
||||
self.altitudeCruiseMin=AltMin
|
||||
self.altitudeCruiseMax=AltMax or self.altitudeCruiseMin
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set cruise speed.
|
||||
-- @param #FLIGHTPLAN self
|
||||
-- @param #number SpeedMin Minimum speed in knots.
|
||||
-- @param #number SpeedMax Maximum speed in knots. Default is `SpeedMin`.
|
||||
-- @return #FLIGHTPLAN self
|
||||
function FLIGHTPLAN:SetCruiseSpeed(SpeedMin, SpeedMax)
|
||||
|
||||
self.speedCruiseMin=SpeedMin
|
||||
self.speedCruiseMax=SpeedMax or self.speedCruiseMin
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get the name of this flight plan.
|
||||
-- @param #FLIGHTPLAN self
|
||||
-- @return #string The name.
|
||||
function FLIGHTPLAN:GetName()
|
||||
return self.alias
|
||||
end
|
||||
|
||||
|
||||
--- Get cruise altitude. This returns a random altitude between the set min/max cruise altitudes.
|
||||
-- @param #FLIGHTPLAN self
|
||||
-- @return #number Cruise altitude in feet MSL.
|
||||
function FLIGHTPLAN:GetCruiseAltitude()
|
||||
|
||||
local alt=10000
|
||||
if self.altitudeCruiseMin and self.altitudeCruiseMax then
|
||||
alt=math.random(self.altitudeCruiseMin, self.altitudeCruiseMax)
|
||||
elseif self.altitudeCruiseMin then
|
||||
alt=self.altitudeCruiseMin
|
||||
elseif self.altitudeCruiseMax then
|
||||
alt=self.altitudeCruiseMax
|
||||
end
|
||||
|
||||
return alt
|
||||
end
|
||||
|
||||
--- Get cruise speed. This returns a random speed between the set min/max cruise speeds.
|
||||
-- @param #FLIGHTPLAN self
|
||||
-- @return #number Cruise speed in knots.
|
||||
function FLIGHTPLAN:GetCruiseSpeed()
|
||||
|
||||
local speed=250
|
||||
|
||||
if self.speedCruiseMin and self.speedCruiseMax then
|
||||
speed=math.random(self.speedCruiseMin, self.speedCruiseMax)
|
||||
elseif self.speedCruiseMin then
|
||||
speed=self.speedCruiseMin
|
||||
elseif self.altitudeCruiseMax then
|
||||
speed=self.speedCruiseMax
|
||||
end
|
||||
|
||||
return speed
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Read flight plan from a given MSFS 2020 .plt file.
|
||||
-- @param #string FileName Name of the file.
|
||||
-- @return #FLIGHTPLAN The flight plan.
|
||||
function FLIGHTPLAN._ReadFileMSFS(FileName)
|
||||
|
||||
local function readfile(filename)
|
||||
|
||||
local lines = {}
|
||||
|
||||
-- Open file in read binary mode.
|
||||
local file=assert(io.open(filename, "rb"), string.format("File not found! File name = %s", tostring(filename)))
|
||||
|
||||
for line in file:lines() do
|
||||
lines[#lines+1] = line
|
||||
end
|
||||
|
||||
-- Close file.
|
||||
file:close()
|
||||
|
||||
-- Return data
|
||||
return lines
|
||||
end
|
||||
|
||||
|
||||
--- This function returns an XML element, i.e. the string between <...> and </...>.
|
||||
local function getXMLelement(line)
|
||||
local element=string.match(line, ">(.+)<")
|
||||
return element
|
||||
end
|
||||
|
||||
--- This function returns Latitude and Longitude
|
||||
local function getLatLong(line)
|
||||
local latlong=getXMLelement(line)
|
||||
-- The format is "N41° 38' 20.00",E41° 33' 19.00",+000000.00" so we still need to process that.
|
||||
local lat,long=string.match(latlong, "(.+),(.+),")
|
||||
return lat,long
|
||||
end
|
||||
|
||||
-- Read data from file.
|
||||
local data=readfile(FileName)
|
||||
|
||||
local flightplan={}
|
||||
local waypoints={}
|
||||
local wp=nil
|
||||
|
||||
local gotwaypoint=false
|
||||
for i,line in pairs(data) do
|
||||
|
||||
|
||||
--print(line)
|
||||
|
||||
-- Title
|
||||
if string.find(line, "<Title>") then
|
||||
flightplan.title=getXMLelement(line)
|
||||
end
|
||||
|
||||
-- Departure ICAO
|
||||
if string.find(line, "<DepartureID>") then
|
||||
flightplan.departureICAO=getXMLelement(line)
|
||||
end
|
||||
|
||||
-- Destination ICAO
|
||||
if string.find(line, "<DestinationID>") then
|
||||
flightplan.destinationICAO=getXMLelement(line)
|
||||
end
|
||||
|
||||
-- FPType
|
||||
if string.find(line, "<FPType>") then
|
||||
flightplan.plantype=getXMLelement(line)
|
||||
end
|
||||
|
||||
-- Route type
|
||||
if string.find(line, "<RouteType>") then
|
||||
flightplan.routetype=getXMLelement(line)
|
||||
end
|
||||
|
||||
-- Cruise alt in feet
|
||||
if string.find(line, "<CruisingAlt>") then
|
||||
flightplan.altCruise=getXMLelement(line)
|
||||
end
|
||||
|
||||
-- Departure LLA
|
||||
if string.find(line, "<DepartureLLA>") then
|
||||
local lat,long=getLatLong(line)
|
||||
end
|
||||
|
||||
-- Destination LLA
|
||||
if string.find(line, "<DestinationLLA>") then
|
||||
local lat,long=getLatLong(line)
|
||||
end
|
||||
|
||||
-- Departure Name
|
||||
if string.find(line, "<DepartureName>") then
|
||||
local DepartureName=getXMLelement(line)
|
||||
end
|
||||
|
||||
-- DestinationName
|
||||
if string.find(line, "<DestinationName>") then
|
||||
local DestinationName=getXMLelement(line)
|
||||
end
|
||||
|
||||
---
|
||||
-- Waypoint stuff
|
||||
---
|
||||
|
||||
-- New waypoint starts.
|
||||
if string.find(line, "ATCWaypoint id") then
|
||||
|
||||
--Get string inside quotes " and ".
|
||||
local wpid=string.match(line, [["(.+)"]])
|
||||
|
||||
-- Create a new wp table.
|
||||
wp={}
|
||||
|
||||
-- Set waypoint name.
|
||||
wp.name=wpid
|
||||
end
|
||||
|
||||
-- Waypoint info ends.
|
||||
if string.find(line, "</ATCWaypoint>") then
|
||||
-- This is the end of the waypoint.
|
||||
|
||||
-- Add info to waypoints table.
|
||||
table.insert(waypoints, wp)
|
||||
|
||||
-- Set waypoint to nil. We create an empty table if the next wp starts.
|
||||
wp=nil
|
||||
end
|
||||
|
||||
-- Waypoint type (Airport, Intersection, NDB, VORTAC)
|
||||
if string.find(line, "<ATCWaypointType>") then
|
||||
local wptype=getXMLelement(line)
|
||||
wp.type=wptype
|
||||
end
|
||||
|
||||
-- Waypoint position.
|
||||
if string.find(line, "<WorldPosition>") then
|
||||
wp.lat, wp.long=getLatLong(line)
|
||||
end
|
||||
|
||||
-- Runway should exist for initial and final WP if it is an airport.
|
||||
if string.find(line, "RunwayNumberFP") then
|
||||
wp.runway=getXMLelement(line)
|
||||
end
|
||||
|
||||
-- Runway designator: LEFT, RIGHT, CENTER
|
||||
if string.find(line, "RunwayDesignatorFP") then
|
||||
wp.runwayDesignator=getXMLelement(line)
|
||||
end
|
||||
|
||||
-- Segment is Departure
|
||||
if string.find(line, "<DepartureFP>") then
|
||||
wp.segment="Departure"
|
||||
end
|
||||
|
||||
-- Segment is Arrival
|
||||
if string.find(line, "<ArrivalFP>") then
|
||||
wp.segment="Arrival"
|
||||
end
|
||||
|
||||
-- Segment is Enroute
|
||||
if string.find(line, "<ATCAirway>") then
|
||||
wp.segment="Enroute"
|
||||
end
|
||||
|
||||
-- Approach type: VORDME, LOCALIZER
|
||||
if string.find(line, "ApproachTypeFP") then
|
||||
flightplan.approachtype=getXMLelement(line)
|
||||
end
|
||||
|
||||
-- Approach type suffic: Z
|
||||
if string.find(line, "SuffixFP") then
|
||||
local SuffixFP=getXMLelement(line)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
for key, value in pairs(flightplan) do
|
||||
env.info(string.format("Flightplan %s=%s", key, tostring(value)))
|
||||
end
|
||||
|
||||
env.info(string.format("Number of waypoints=%d", #waypoints))
|
||||
for i,wp in pairs(waypoints) do
|
||||
env.info(string.format("Waypoint name=%s type=%s segment=%s runway=%s lat=%s long=%s", wp.name, wp.type, tostring(wp.segment), tostring(wp.runway)..tostring(wp.runwayDesignator or ""), wp.lat, wp.long))
|
||||
end
|
||||
|
||||
-- Create a new flightplan.
|
||||
local fp=FLIGHTPLAN:New(flightplan.title)
|
||||
|
||||
-- Set cruise altitude.
|
||||
fp:SetCruiseAltitude(flightplan.altCruise)
|
||||
|
||||
-- Set departure and destination airports.
|
||||
fp:SetDepartureAirbase(flightplan.departureICAO)
|
||||
fp:SetDestinationAirbase(flightplan.destinationICAO)
|
||||
|
||||
--TODO: Remove first and last waypoint if they are identical to the departure/destination airport!
|
||||
|
||||
for i,wp in pairs(waypoints) do
|
||||
|
||||
-- Create a navpoint.
|
||||
local navpoint=NAVPOINT:NewFromLLDMS(wp.name, wp.type, wp.lat, wp.long)
|
||||
|
||||
navpoint:SetAltMin(flightplan.altCruise)
|
||||
|
||||
-- Add point to flightplan.
|
||||
-- TODO: section departure, enroute, arrival.
|
||||
fp:AddNavFix(navpoint)
|
||||
end
|
||||
|
||||
|
||||
|
||||
return fp
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1,333 +0,0 @@
|
||||
--- **NAVIGATION** - Prodedures for Departure (*e.g.* SID), Enroute, Arrival (*e.g.* STAR) and Approach.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Stuff
|
||||
-- * More Stuff
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Navigation%20-%20Template).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Navigation.Procedure
|
||||
-- @image NAVIGATION_Procedure.png
|
||||
|
||||
|
||||
--- APPROACH class.
|
||||
-- @type APPROACH
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @field #string apptype Approach type (ILS, VOR, LOC).
|
||||
-- @field Wrapper.Airbase#AIRBASE airbase Airbase of this approach.
|
||||
-- @field Wrapper.Airbase#AIRBASE.Runway runway Runway of this approach.
|
||||
-- @field Navigation.Point#NAVAID navaid Primary navigation aid.
|
||||
-- @field #number wpcounter Running number counting the waypoints to generate its UID.
|
||||
-- @list <#APPROACH.Waypoint> path Path of approach consisting of waypoints.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The APPROACH Concept
|
||||
--
|
||||
-- A typical approach has (up to) four segments. It starts with the initial approach segment, followed by the intermediate approach segment, followed
|
||||
-- by the final approach segment. In case something goes wrong during the final approach, the missed approach segment kicks in.
|
||||
--
|
||||
-- The initial approach segment starts at the initial approach fix (IAF). The segment can contain multiple other fixes, that need to be passed.
|
||||
-- Note, that an approach procedure can have more than one intitial approach segment and IAF.
|
||||
--
|
||||
-- The intermediate approach segment starts at the intermediate fix (IF). The intermediate approach segment blends the initial approach segment into the final approach segment.
|
||||
-- It is the segment in which aircraft configuration, speed, and positioning adjustments are made for entry into the final approach segment.
|
||||
--
|
||||
--
|
||||
-- https://en.wikipedia.org/wiki/Visual_approach
|
||||
-- https://en.wikipedia.org/wiki/Instrument_approach
|
||||
--
|
||||
-- # Basic Setup
|
||||
--
|
||||
-- A new `APPROACH` object can be created with the @{#APPROACH.New}() function.
|
||||
--
|
||||
-- myTemplate=APPROACH:New()
|
||||
-- myTemplate:SetXYZ(X, Y, Z)
|
||||
--
|
||||
-- This is how it works.
|
||||
--
|
||||
-- @field #APPROACH
|
||||
APPROACH = {
|
||||
ClassName = "APPROACH",
|
||||
verbose = 0,
|
||||
wpcounter = 0,
|
||||
}
|
||||
|
||||
--- Type of approach.
|
||||
-- @type APPROACH.Type
|
||||
-- @field #string VFR Visual Flight Rules.
|
||||
-- @field #string VOR VOR
|
||||
-- @field #string NDB NDB
|
||||
APPROACH.Type={
|
||||
VFR="VFR",
|
||||
VOR="VOR",
|
||||
ILS="ILS",
|
||||
}
|
||||
|
||||
|
||||
--- Setments of approach.
|
||||
-- @type APPROACH.Segment
|
||||
-- @field #string INITIAL Initial approach segment.
|
||||
-- @field #string INTERMEDIATE Intermediate approach segment.
|
||||
-- @field #string FINAL Final approach segment.
|
||||
-- @field #string MISSED Missed approach segment.
|
||||
APPROACH.Segment={
|
||||
INITIAL="Initial",
|
||||
INTERMEDIATE="Intermediate",
|
||||
FINAL="Final",
|
||||
MISSED="Missed",
|
||||
}
|
||||
|
||||
--- Waypoint of the approach.
|
||||
-- @type APPROACH.Waypoint
|
||||
-- @field #number uid Unique ID of the point.
|
||||
-- @field #string segment The segment this point belongs to.
|
||||
-- @field Navigation.Point#NAVFIX navfix The navigation fix that determines the coordinates of this point.
|
||||
|
||||
--- APPROACH class version.
|
||||
-- @field #string version
|
||||
APPROACH.version="0.0.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot...
|
||||
-- Initial approach segment --> Intermediate approach segment: starts at IF --> Final approach segment
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new APPROACH class instance.
|
||||
-- @param #APPROACH self
|
||||
-- @param #string Type Type of approach (ILS, VOR, LOC).
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase The airbase or name of the airbase.
|
||||
-- @param Wrapper.Airbase#AIRBASE.Runway Runway The runway or name of the runway.
|
||||
-- @return #APPROACH self
|
||||
function APPROACH:New(Type, Airbase, Runway)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
self=BASE:Inherit(self, BASE:New()) -- #APPROACH
|
||||
|
||||
-- Set approach type.
|
||||
-- TODO: Check if this is a valid/known approach type.
|
||||
self.apptype=Type
|
||||
|
||||
if type(Airbase)=="string" then
|
||||
self.airbase=AIRBASE:FindByName(Airbase)
|
||||
else
|
||||
self.airbase=Airbase
|
||||
end
|
||||
|
||||
if type(Runway)=="string" then
|
||||
self.runway=self.airbase:GetRunwayByName(Runway)
|
||||
else
|
||||
self.runway=Runway
|
||||
end
|
||||
|
||||
-- Debug info.
|
||||
self:I("Created new approach for airbase %s: type=%s, runway=%s", self.airbase:GetName(), self.apptype, self.runway.name)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set the primary navigation aid used in the approach.
|
||||
-- @param #APPROACH self
|
||||
-- @param Navigation.Point#NAVAID NavAid The NAVAID.
|
||||
-- @return #APPROACH self
|
||||
function APPROACH:SetNavAid(NavAid)
|
||||
|
||||
self.navaid=NavAid
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a waypoint to the path of the approach.
|
||||
-- @param #APPROACH self
|
||||
-- @param Navigation.Point#NAVFIX NavFix The navigation fix.
|
||||
-- @param #string Segment The approach segment this fix belongs to.
|
||||
-- @return #APPROACH.Waypoint The waypoint data table.
|
||||
function APPROACH:AddNavFix(NavFix, Segment)
|
||||
|
||||
self.wpcounter=self.wpcounter+1
|
||||
|
||||
local point={} --#APPROACH.Waypoint
|
||||
point.uid=self.wpcounter
|
||||
point.segment=Segment
|
||||
point.navfix=NavFix
|
||||
|
||||
table.insert(self.path, point)
|
||||
|
||||
return point
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- Add private functions here.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- DEPARTURE class.
|
||||
-- @type DEPARTURE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @field #string apptype DEPARTURE type (ILS, VOR, LOC).
|
||||
-- @field Wrapper.Airbase#AIRBASE airbase Airbase of this DEPARTURE.
|
||||
-- @field Wrapper.Airbase#AIRBASE.Runway runway Runway of this DEPARTURE.
|
||||
-- @field Navigation.Point#NAVAID navaid Primary navigation aid.
|
||||
-- @field #number wpcounter Running number counting the waypoints to generate its UID.
|
||||
-- @list <#DEPARTURE.Waypoint> path Path of DEPARTURE consisting of waypoints.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The DEPARTURE Concept
|
||||
--
|
||||
-- Bla.
|
||||
--
|
||||
-- @field #DEPARTURE
|
||||
DEPARTURE = {
|
||||
ClassName = "DEPARTURE",
|
||||
verbose = 0,
|
||||
wpcounter = 0,
|
||||
}
|
||||
|
||||
--- Type of DEPARTURE.
|
||||
-- @type DEPARTURE.Type
|
||||
-- @field #string VOR VOR
|
||||
-- @field #string NDB NDB
|
||||
DEPARTURE.Type={
|
||||
VOR="VOR",
|
||||
ILS="ILS",
|
||||
}
|
||||
|
||||
|
||||
--- Setments of DEPARTURE.
|
||||
-- @type DEPARTURE.Segment
|
||||
-- @field #string INITIAL Initial DEPARTURE segment.
|
||||
-- @field #string INTERMEDIATE Intermediate DEPARTURE segment.
|
||||
-- @field #string FINAL Final DEPARTURE segment.
|
||||
-- @field #string MISSED Missed DEPARTURE segment.
|
||||
DEPARTURE.Segment={
|
||||
INITIAL="Initial",
|
||||
INTERMEDIATE="Intermediate",
|
||||
FINAL="Final",
|
||||
MISSED="Missed",
|
||||
}
|
||||
|
||||
--- Waypoint of the DEPARTURE.
|
||||
-- @type DEPARTURE.Waypoint
|
||||
-- @field #number uid Unique ID of the point.
|
||||
-- @field #string segment The segment this point belongs to.
|
||||
-- @field Navigation.Point#NAVFIX navfix The navigation fix that determines the coordinates of this point.
|
||||
|
||||
--- DEPARTURE class version.
|
||||
-- @field #string version
|
||||
DEPARTURE.version="0.0.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot...
|
||||
-- Initial DEPARTURE segment --> Intermediate DEPARTURE segment: starts at IF --> Final DEPARTURE segment
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new DEPARTURE class instance.
|
||||
-- @param #DEPARTURE self
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase The airbase or name of the airbase.
|
||||
-- @param Wrapper.Airbase#AIRBASE.Runway Runway The runway or name of the runway.
|
||||
-- @param #string Type Type of DEPARTURE (ILS, VOR, LOC).
|
||||
-- @return #DEPARTURE self
|
||||
function DEPARTURE:New(Airbase, Runway)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
self=BASE:Inherit(self, BASE:New()) -- #DEPARTURE
|
||||
|
||||
if type(Airbase)=="string" then
|
||||
self.airbase=AIRBASE:FindByName(Airbase)
|
||||
else
|
||||
self.airbase=Airbase
|
||||
end
|
||||
|
||||
if type(Runway)=="string" then
|
||||
self.runway=self.airbase:GetRunwayByName(Runway)
|
||||
else
|
||||
self.runway=Runway
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set the primary navigation aid used in the DEPARTURE.
|
||||
-- @param #DEPARTURE self
|
||||
-- @param Navigation.Point#NAVAID NavAid The NAVAID.
|
||||
-- @return #DEPARTURE self
|
||||
function DEPARTURE:SetNavAid(NavAid)
|
||||
|
||||
self.navaid=NavAid
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a waypoint to the path of the DEPARTURE.
|
||||
-- @param #DEPARTURE self
|
||||
-- @param Navigation.Point#NAVFIX NavFix The navigation fix.
|
||||
-- @param #string Segment The DEPARTURE segment this fix belongs to.
|
||||
-- @return #DEPARTURE.Waypoint The waypoint data.
|
||||
function DEPARTURE:AddWaypoint(NavFix, Segment)
|
||||
|
||||
self.wpcounter=self.wpcounter+1
|
||||
|
||||
local point={} --#DEPARTURE.Waypoint
|
||||
point.uid=self.wpcounter
|
||||
point.segment=Segment
|
||||
point.navfix=NavFix
|
||||
|
||||
table.insert(self.path, point)
|
||||
|
||||
return point
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- Add DEPARTURE private functions here.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
--
|
||||
-- A new `RADIOS` object can be created with the @{#RADIOS.NewFromFile}(*radio_lua_file*) function.
|
||||
--
|
||||
-- local radios=RADIOS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\radio.lua")
|
||||
-- local radios=RADIOS:NewFromFile("<DCS_Install_Directory>\Mods\terrains\<Map_Name>\Radio.lua")
|
||||
-- radios:MarkerShow()
|
||||
--
|
||||
-- This will load the radios from the `<DCS_Install_Directory>` for the specific map and place markers on the F10 map. This is the first step you should do to ensure that the file
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
--- **NAVIGATION** - Template.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Stuff
|
||||
-- * More Stuff
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Navigation%20-%20Template).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Navigation.Template
|
||||
-- @image NAVIGATION_Template.png
|
||||
|
||||
|
||||
--- TEMPLATE class.
|
||||
-- @type TEMPLATE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The TEMPLATE Concept
|
||||
--
|
||||
-- The TEMPLATE class has a great concept!
|
||||
--
|
||||
-- # Basic Setup
|
||||
--
|
||||
-- A new `TEMPLATE` object can be created with the @{#TEMPLATE.New}() function.
|
||||
--
|
||||
-- myTemplate=TEMPLATE:New()
|
||||
-- myTemplate:SetXYZ(X, Y, Z)
|
||||
--
|
||||
-- This is how it works.
|
||||
--
|
||||
-- @field #TEMPLATE
|
||||
TEMPLATE = {
|
||||
ClassName = "TEMPLATE",
|
||||
verbose = 0,
|
||||
}
|
||||
|
||||
--- Type of navaid
|
||||
-- @type TEMPLATE.Type
|
||||
-- @field #string VOR VOR
|
||||
-- @field #string NDB NDB
|
||||
TEMPLATE.TYPE={
|
||||
VOR="VOR",
|
||||
NDB="NDB",
|
||||
}
|
||||
|
||||
--- TEMPLATE class version.
|
||||
-- @field #string version
|
||||
TEMPLATE.version="0.0.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot...
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new TEMPLATE class instance.
|
||||
-- @param #TEMPLATE self
|
||||
-- @return #TEMPLATE self
|
||||
function TEMPLATE:New()
|
||||
|
||||
-- Inherit everything from SCENERY class.
|
||||
self=BASE:Inherit(self, BASE:New()) -- #TEMPLATE
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set frequency.
|
||||
-- @param #TEMPLATE self
|
||||
-- @param #number Frequency Frequency in Hz.
|
||||
-- @return #TEMPLATE self
|
||||
function TEMPLATE:SetFrequency(Frequency)
|
||||
|
||||
self.frequency=Frequency
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -326,6 +326,42 @@ function AIRWING:AddSquadron(Squadron)
|
||||
if Squadron:IsStopped() then
|
||||
Squadron:Start()
|
||||
end
|
||||
|
||||
-- if storage is limited, add the amount of aircraft needed
|
||||
local airbasename = self:GetAirbaseName()
|
||||
|
||||
if airbasename then
|
||||
local group = Squadron.templategroup
|
||||
if group then
|
||||
local Nunits = 1
|
||||
local units
|
||||
if group then units = group:GetUnits() end
|
||||
if units then Nunits = #units end
|
||||
local typename = Squadron.aircrafttype or "none"
|
||||
local NAssets = Squadron.Ngroups * Nunits
|
||||
local storage = STORAGE:New(airbasename)
|
||||
|
||||
self:T(self.lid.."Adding "..typename.." #"..NAssets)
|
||||
if storage and storage.warehouse and storage:IsLimitedAircraft() and typename ~= "none" then
|
||||
local NInStore = storage:GetItemAmount(typename) or 0
|
||||
if NAssets > NInStore then
|
||||
storage:AddItem(typename,NAssets)
|
||||
end
|
||||
end
|
||||
|
||||
local unit = group:GetUnit(1)
|
||||
-- if storage is limited, add the amount of liquids needed
|
||||
if unit and storage and storage.warehouse and storage:IsLimitedLiquids() and typename ~= "none" then
|
||||
local fuel = unit:GetFuelMassMax()
|
||||
local neededfuel = (fuel*NAssets) -- kgs of fuel
|
||||
local NInStore = storage:GetLiquidAmount(STORAGE.Liquid.JETFUEL) or 0
|
||||
self:T(string.format(self.lid.."Fuel Needed: %dt | Fuel in store: %dt",neededfuel/1000,NInStore/1000))
|
||||
if neededfuel > NInStore then
|
||||
storage:AddLiquid(STORAGE.Liquid.JETFUEL,neededfuel)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -509,7 +509,7 @@ do
|
||||
-- @field #AWACS
|
||||
AWACS = {
|
||||
ClassName = "AWACS", -- #string
|
||||
version = "0.2.73", -- #string
|
||||
version = "0.2.74", -- #string
|
||||
lid = "", -- #string
|
||||
coalition = coalition.side.BLUE, -- #number
|
||||
coalitiontxt = "blue", -- #string
|
||||
@@ -2060,6 +2060,55 @@ function AWACS:SetRejectionZone(Zone,Draw)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zones.
|
||||
-- @param #AWACS self
|
||||
-- @param Core.Set#SET_ZONE CorridorZones Can be handed in as SET\_ZONE or single ZONE object.
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetCorridorZones(CorridorZones)
|
||||
self:T(self.lid .. "SetCorridorZones")
|
||||
if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE") then
|
||||
self.corridorzones = CorridorZones
|
||||
self.usecorridors = true
|
||||
elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE") then
|
||||
if not self.corridorzones then self.corridorzones = SET_ZONE:New() end
|
||||
self.corridorzones:AddZone(CorridorZones)
|
||||
self.usecorridors = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to add one corridor zone.
|
||||
-- @param #AWACS self
|
||||
-- @param Core.Zone#ZONE CorridorZone The ZONE object to be added.
|
||||
-- @return #AWACS self
|
||||
function AWACS:AddCorridorZone(CorridorZone)
|
||||
self:T(self.lid .. "AddCorridorZone")
|
||||
self:SetCorridorZones(CorridorZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in FEET.
|
||||
-- @param #AWACS self
|
||||
-- @param #number Floor Floor altitude ASL in feet.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in feet.
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling)
|
||||
self.corridorfloor = UTILS.FeetToMeters(Floor)
|
||||
self.corridorceiling = UTILS.FeetToMeters(Ceiling)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in METERS.
|
||||
-- @param #AWACS self
|
||||
-- @param #number Floor Floor altitude ASL in meters.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in meters.
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling)
|
||||
self.corridorfloor = Floor
|
||||
self.corridorceiling = Ceiling
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Draw a line around the FEZ on the F10 map.
|
||||
-- @param #AWACS self
|
||||
-- @return #AWACS self
|
||||
@@ -4336,6 +4385,14 @@ function AWACS:_StartIntel(awacs)
|
||||
intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
|
||||
end
|
||||
|
||||
-- Corridors
|
||||
if self.usecorridors == true then
|
||||
intel:SetCorridorZones(self.corridorzones)
|
||||
if self.corridorceiling or self.corridorfloor then
|
||||
intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
|
||||
-- Callbacks
|
||||
local function NewCluster(Cluster)
|
||||
self:__NewCluster(5,Cluster)
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
-- @image OPS_CSAR.jpg
|
||||
|
||||
---
|
||||
-- Last Update Oct 2025
|
||||
-- Last Update Jan 2026
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
|
||||
@@ -81,7 +81,7 @@
|
||||
--
|
||||
-- mycsar.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms.
|
||||
-- mycsar.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only!
|
||||
-- mycsar.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued.
|
||||
-- mycsar.FARPRescueDistance = 500 -- you need to be this close to a FARP or Airport for the pilot to be rescued.
|
||||
-- mycsar.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near.
|
||||
-- mycsar.autosmokedistance = 1000 -- distance for autosmoke
|
||||
-- mycsar.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates.
|
||||
@@ -119,6 +119,7 @@
|
||||
-- mycsar.PilotWeight = 80 -- Loaded pilots weigh 80kgs each
|
||||
-- mycsar.AllowIRStrobe = false -- Allow a menu item to request an IR strobe to find a downed pilot at night (requires NVGs to see it).
|
||||
-- mycsar.IRStrobeRuntime = 300 -- If an IR Strobe is activated, it runs for 300 seconds (5 mins).
|
||||
-- mycsar.EnableMenuSmokeMASH = true -- Allow a menu item to request smoke at the closest MASH/AFB for rescue.
|
||||
--
|
||||
-- ## 2.1 Create own SET_GROUP to manage CTLD Pilot groups
|
||||
--
|
||||
@@ -272,6 +273,8 @@ CSAR = {
|
||||
UserSetGroup = nil,
|
||||
AllowIRStrobe = false,
|
||||
IRStrobeRuntime = 300,
|
||||
FARPRescueDistance = 500,
|
||||
EnableMenuSmokeMASH = true,
|
||||
}
|
||||
|
||||
--- Downed pilots info.
|
||||
@@ -312,10 +315,12 @@ CSAR.AircraftType["MH-60R"] = 10
|
||||
CSAR.AircraftType["OH-6A"] = 2
|
||||
CSAR.AircraftType["OH58D"] = 2
|
||||
CSAR.AircraftType["CH-47Fbl1"] = 31
|
||||
CSAR.AircraftType["AH-6J"] = 2
|
||||
CSAR.AircraftType["MH-6J"] = 2
|
||||
|
||||
--- CSAR class version.
|
||||
-- @field #string version
|
||||
CSAR.version="1.0.34"
|
||||
CSAR.version="1.0.36"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@@ -431,12 +436,13 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK!
|
||||
self.beaconRefresher = 29 -- seconds
|
||||
self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase
|
||||
self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued.
|
||||
self.FARPRescueDistance = 500 -- you need to be this close to a FARP or Airport for the pilot to be rescued.
|
||||
self.max_units = 6 --max number of pilots that can be carried
|
||||
self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below
|
||||
self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names!
|
||||
self.template = Template or "generic" -- template for downed pilot
|
||||
self.mashprefix = {"MASH"} -- prefixes used to find MASHes
|
||||
self.EnableMenuSmokeMASH = true
|
||||
|
||||
self.autosmoke = false -- automatically smoke location when heli is near
|
||||
self.autosmokedistance = 2000 -- distance for autosmoke
|
||||
@@ -673,7 +679,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript
|
||||
DownedPilot.alive = true
|
||||
DownedPilot.wetfeet = Wetfeet or false
|
||||
DownedPilot.BeaconName = BeaconName
|
||||
|
||||
|
||||
-- Add Pilot
|
||||
local PilotTable = self.downedPilots
|
||||
local counter = self.downedpilotcounter
|
||||
@@ -851,6 +857,10 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
|
||||
else
|
||||
BeaconName = "Ghost-1-1"..math.random(1,10000)
|
||||
end
|
||||
|
||||
if _playerName == nil or _playerName == "" then
|
||||
_playerName = "AI MIA"
|
||||
end
|
||||
|
||||
if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0
|
||||
self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName)
|
||||
@@ -1232,7 +1242,7 @@ function CSAR:_EventHandler(EventData)
|
||||
local _place = _event.Place -- Wrapper.Airbase#AIRBASE
|
||||
|
||||
if _place == nil then
|
||||
self:T(self.lid .. " Landing Place Nil")
|
||||
self:T(self.lid .. " Landing Place nil")
|
||||
return self -- error!
|
||||
end
|
||||
|
||||
@@ -1267,7 +1277,7 @@ function CSAR:_EventHandler(EventData)
|
||||
self:T("Country = ".._country.." Coalition = ".._coalition)
|
||||
if _coalition == self.coalition then
|
||||
local _freq = self:_GenerateADFFrequency()
|
||||
self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq})
|
||||
self:T({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq})
|
||||
self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, self.suppressmessages, "none")--shagrat add CSAR at Parachute location.
|
||||
|
||||
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
|
||||
@@ -1761,7 +1771,14 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule, I
|
||||
end
|
||||
|
||||
self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000))
|
||||
|
||||
|
||||
if self.verbose>0 then
|
||||
local debugtext = string.format("Distance %dm | Rescuedist %dm | IsAirport %s | IsInAir %s | IsHeloBase %s\n",_dist,self.FARPRescueDistance,tostring(isairport),tostring(_heliUnit:InAir()),tostring(IsHeloBase))
|
||||
self:T("*******************************")
|
||||
self:T(debugtext)
|
||||
self:T("*******************************")
|
||||
end
|
||||
|
||||
if ( _dist < self.FARPRescueDistance or isairport ) and ((_heliUnit:InAir() == false) or (IsHeloBase == true)) then
|
||||
self:T(self.lid.."[Drop off debug] Distance ok, door check")
|
||||
if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then
|
||||
@@ -2138,15 +2155,52 @@ function CSAR:_Reqsmoke( _unitName )
|
||||
return self
|
||||
end
|
||||
|
||||
---(Internal) Request smoke at closest MASH/AFB.
|
||||
--@param #CSAR self
|
||||
--@param #string _unitName Name of the helicopter
|
||||
function CSAR:_ReqsmokeMash( _unitName )
|
||||
self:T(self.lid .. " _ReqsmokeMash")
|
||||
local _heli = self:_GetSARHeli(_unitName)
|
||||
if _heli == nil then
|
||||
return
|
||||
end
|
||||
local smokedist = 8000
|
||||
if smokedist < self.approachdist_far then smokedist = self.approachdist_far end
|
||||
local distance, name, coordinate = self:_GetClosestMASH(_heli)
|
||||
if coordinate and distance then
|
||||
local disttext
|
||||
if _SETTINGS:IsImperial() then
|
||||
disttext = string.format("%.1fnm",UTILS.MetersToNM(distance))
|
||||
else
|
||||
disttext = string.format("%.1fkm",distance/1000)
|
||||
end
|
||||
local _msg = string.format("%s - Popping smoke at the closest rescue point: %s", self:_GetCustomCallSign(_unitName), disttext)
|
||||
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true)
|
||||
local color = self.smokecolor
|
||||
coordinate:Smoke(color)
|
||||
else
|
||||
local _distance = string.format("%.1fkm",smokedist/1000)
|
||||
if _SETTINGS:IsImperial() then
|
||||
_distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist))
|
||||
else
|
||||
_distance = string.format("%.1fkm",smokedist/1000)
|
||||
end
|
||||
self:_DisplayMessageToSAR(_heli, string.format("No rescue point within %s",_distance), self.messageTime, false, false, true)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Determine distance to closest MASH.
|
||||
-- @param #CSAR self
|
||||
-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT
|
||||
-- @return #number Distance in meters
|
||||
-- @return #string MASH Name as string
|
||||
-- @return Core.Point#COORDINATE Coordinate The MASH/AFB Coordinate (for smoke)
|
||||
function CSAR:_GetClosestMASH(_heli)
|
||||
self:T(self.lid .. " _GetClosestMASH")
|
||||
local _mashset = self.mash -- Core.Set#SET_GROUP
|
||||
local MashSets = {}
|
||||
local MashSets = {}
|
||||
|
||||
--local _mashes = _mashset.Set-- #table
|
||||
table.insert(MashSets,_mashset.Set)
|
||||
table.insert(MashSets,self.zonemashes.Set)
|
||||
@@ -2155,12 +2209,21 @@ function CSAR:_GetClosestMASH(_heli)
|
||||
local _distance = 0
|
||||
local _helicoord = _heli:GetCoordinate()
|
||||
local MashName = nil
|
||||
local Coordinate = nil -- Core.Point#COORDINATE
|
||||
|
||||
if self.allowFARPRescue then
|
||||
local position = _heli:GetCoordinate()
|
||||
local afb,distance = position:GetClosestAirbase(nil,self.coalition)
|
||||
local afb,distance = _helicoord:GetClosestAirbase(nil,self.coalition)
|
||||
_shortestDistance = distance
|
||||
MashName = (afb ~= nil) and afb:GetName() or "Unknown"
|
||||
Coordinate = (afb ~= nil) and afb:GetCoordinate()
|
||||
if afb then
|
||||
local afbzone = afb:GetMinimumBoundingCircleFromParkingSpots()
|
||||
if afbzone then
|
||||
if afbzone:IsCoordinateInZone(_helicoord) and distance > self.FARPRescueDistance*1.1 then
|
||||
_shortestDistance = 100
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _,_mashes in pairs(MashSets) do
|
||||
@@ -2175,12 +2238,13 @@ function CSAR:_GetClosestMASH(_heli)
|
||||
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then
|
||||
_shortestDistance = _distance
|
||||
MashName = _mashUnit:GetName() or "Unknown"
|
||||
Coordinate = _mashcoord
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if _shortestDistance ~= -1 then
|
||||
return _shortestDistance, MashName
|
||||
return _shortestDistance, MashName, Coordinate
|
||||
else
|
||||
return -1
|
||||
end
|
||||
@@ -2249,6 +2313,9 @@ function CSAR:_AddMedevacMenuItem()
|
||||
local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName)
|
||||
if self.AllowIRStrobe then
|
||||
local _rootMenu5 = MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath, self._ReqIRStrobe,self,_unitName):Refresh()
|
||||
end
|
||||
if self.EnableMenuSmokeMASH then
|
||||
local _rootMenu6 = MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath, self._ReqsmokeMash,self,_unitName)
|
||||
else
|
||||
_rootMenu4:Refresh()
|
||||
end
|
||||
@@ -2269,6 +2336,7 @@ function CSAR:_GetDistance(_point1, _point2)
|
||||
if _point1 and _point2 then
|
||||
local distance1 = _point1:Get2DDistance(_point2)
|
||||
local distance2 = _point1:DistanceFromPointVec2(_point2)
|
||||
MESSAGE:New(string.format("_GetDistance: d1 = %dm | d2 = %dm",distance1,distance2)):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1)
|
||||
if distance1 and type(distance1) == "number" then
|
||||
return distance1
|
||||
elseif distance2 and type(distance2) == "number" then
|
||||
@@ -2368,7 +2436,7 @@ function CSAR:_AddBeaconToGroup(_group, _freq, BeaconName)
|
||||
--local name = _radioUnit:GetName()
|
||||
local Sound = "l10n/DEFAULT/"..self.radioSound
|
||||
local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0}
|
||||
self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z))
|
||||
self:T(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z))
|
||||
trigger.action.radioTransmission(Sound, vec3, 0, true, Frequency, self.ADFRadioPwr or 500,BeaconName) -- Beacon in MP only runs for exactly 30secs straight
|
||||
end
|
||||
end
|
||||
@@ -2496,22 +2564,6 @@ function CSAR:onafterStart(From, Event, To)
|
||||
self.staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart()
|
||||
self.zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterStart()
|
||||
|
||||
--[[
|
||||
if staticmashes:Count() > 0 then
|
||||
for _,_mash in pairs(staticmashes.Set) do
|
||||
self.mash:AddObject(_mash)
|
||||
end
|
||||
end
|
||||
|
||||
if zonemashes:Count() > 0 then
|
||||
self:T("Adding zones to self.mash SET")
|
||||
for _,_mash in pairs(zonemashes.Set) do
|
||||
self.mash:AddObject(_mash)
|
||||
end
|
||||
self:T("Objects in SET: "..self.mash:Count())
|
||||
end
|
||||
--]]
|
||||
|
||||
if not self.coordinate then
|
||||
local csarhq = self.mash:GetRandom()
|
||||
if csarhq then
|
||||
@@ -2854,6 +2906,7 @@ function CSAR:onafterSave(From, Event, To, path, filename)
|
||||
if DownedPilot and DownedPilot.alive then
|
||||
-- get downed pilot data for saving
|
||||
local playerName = DownedPilot.player
|
||||
if playerName == nil or playerName == "" then playerName = "AI MIA" end
|
||||
local group = DownedPilot.group
|
||||
local coalition = group:GetCoalition()
|
||||
local country = group:GetCountry()
|
||||
@@ -2864,7 +2917,7 @@ function CSAR:onafterSave(From, Event, To, path, filename)
|
||||
local unitName = DownedPilot.originalUnit
|
||||
local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq)
|
||||
|
||||
self:I(self.lid.."Saving to CSAR File: " .. txt)
|
||||
self:T(self.lid.."Saving to CSAR File: " .. txt)
|
||||
|
||||
data = data .. txt
|
||||
end
|
||||
@@ -2979,8 +3032,8 @@ function CSAR:onafterLoad(From, Event, To, path, filename)
|
||||
|
||||
-- Info message.
|
||||
local text=string.format("Loading CSAR state from file %s", filename)
|
||||
MESSAGE:New(text,10):ToAllIf(self.Debug)
|
||||
self:I(self.lid..text)
|
||||
MESSAGE:New(text,10):ToAllIf(self.verbose>0)
|
||||
self:T(self.lid..text)
|
||||
|
||||
local file=assert(io.open(filename, "rb"))
|
||||
|
||||
|
||||
+1229
-305
File diff suppressed because it is too large
Load Diff
@@ -396,7 +396,8 @@ function CHIEF:New(Coalition, AgentSet, Alias)
|
||||
self.TransportCategories = {Group.Category.HELICOPTER}
|
||||
|
||||
-- Create a new COMMANDER.
|
||||
self.commander=COMMANDER:New(Coalition)
|
||||
self.commander=COMMANDER:New(Coalition, Alias)
|
||||
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
@@ -3357,6 +3358,15 @@ function CHIEF._CheckAssetProperties(Asset, Properties)
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- Checks whether or not any of the legions con run a mission.
|
||||
-- @param #CHIEF self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
-- @return #boolean If `true`, one of the cohorts can run the mission.
|
||||
function CHIEF:CanMission(Mission)
|
||||
return self.commander and self.commander:CanMission(Mission)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -1748,6 +1748,70 @@ function COMMANDER:_GetCohorts(Legions, Cohorts, Operation)
|
||||
return cohorts
|
||||
end
|
||||
|
||||
--- Checks whether or not any of the legions con run a mission.
|
||||
-- @param #COMMANDER self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
-- @return #boolean If `true`, one of the cohorts can run the mission.
|
||||
function COMMANDER:CanMission(Mission)
|
||||
local commander = self
|
||||
|
||||
-- Target position.
|
||||
local TargetVec2 = Mission:GetTargetVec2()
|
||||
|
||||
local MaxWeight = nil
|
||||
|
||||
if Mission.NcarriersMin then
|
||||
|
||||
local legions = commander.legions
|
||||
local cohorts = nil
|
||||
if Mission.transportLegions or Mission.transportCohorts then
|
||||
legions = Mission.transportLegions
|
||||
cohorts = Mission.transportCohorts
|
||||
end
|
||||
|
||||
-- Get transport cohorts.
|
||||
local Cohorts = LEGION._GetCohorts(legions, cohorts)
|
||||
|
||||
-- Filter cohorts that can actually perform transport missions.
|
||||
local transportcohorts = {}
|
||||
for _, _cohort in pairs(Cohorts) do
|
||||
local cohort = _cohort --Ops.Cohort#COHORT
|
||||
|
||||
-- Check if cohort can perform transport to target.
|
||||
local can = LEGION._CohortCan(cohort, AUFTRAG.Type.OPSTRANSPORT, Mission.carrierCategories, Mission.carrierAttributes, Mission.carrierProperties, nil, TargetVec2)
|
||||
|
||||
-- MaxWeight of cargo assets is limited by the largets available cargo bay. We don't want to select, e.g., tanks that cannot be transported by APCs or helos.
|
||||
if can and (MaxWeight == nil or cohort.cargobayLimit > MaxWeight) then
|
||||
MaxWeight = cohort.cargobayLimit
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local legions = commander.legions
|
||||
local cohorts = nil
|
||||
if Mission.specialLegions or Mission.specialCohorts then
|
||||
legions = Mission.specialLegions
|
||||
cohorts = Mission.specialCohorts
|
||||
end
|
||||
|
||||
-- Get cohorts.
|
||||
local Cohorts = LEGION._GetCohorts(legions, cohorts, Mission.operation, commander.opsqueue)
|
||||
|
||||
for _, _cohort in pairs(Cohorts) do
|
||||
local cohort = _cohort --Ops.Cohort#COHORT
|
||||
|
||||
-- Check if cohort can do the mission.
|
||||
local can = LEGION._CohortCan(cohort, Mission.type, nil, Mission.attributes, Mission.properties, { Mission.engageWeaponType }, TargetVec2, Mission.engageRange, Mission.refuelSystem, nil, MaxWeight)
|
||||
if can then
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Recruit assets for a given mission.
|
||||
-- @param #COMMANDER self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
@@ -0,0 +1,950 @@
|
||||
--- **Ops** - Create your A2G Defenses.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Automatically create and manage A2G defenses using an AirWing and Squadrons for one coalition
|
||||
-- * Easy set-up
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-------------------------------------------------------------------------
|
||||
-- Easy A2G Engagement Class, based on OPS classes
|
||||
-------------------------------------------------------------------------
|
||||
--
|
||||
-- ## Documentation:
|
||||
--
|
||||
-- https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Ops.EasyAG.html
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/EasyAG).
|
||||
--
|
||||
-------------------------------------------------------------------------
|
||||
-- Date: Dec 2025
|
||||
-- Last Update: Jan 2026
|
||||
-------------------------------------------------------------------------
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Automatically create and manage A2G defenses using an AirWing and Squadrons for one coalition
|
||||
-- * Easy set-up
|
||||
-- * Add additional AirWings on other airbases
|
||||
-- * Each wing can have more than one Squadron - tasking to Squadrons is done on a random basis per AirWing
|
||||
-- * Create borders and zones of engagement
|
||||
-- * Detection can be ground based and/or via AWACS
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### AUTHOR: **applevangelist**
|
||||
--
|
||||
-- @module Ops.EasyAG
|
||||
-- @image AI_Air_To_Ground_Dispatching.JPG
|
||||
|
||||
|
||||
--- EASYA2G Class
|
||||
-- @type EASYA2G
|
||||
-- @field #string ClassName
|
||||
-- @field #number overhead
|
||||
-- @field #number engagerange
|
||||
-- @field #number capgrouping
|
||||
-- @field #string airbasename
|
||||
-- @field Wrapper.Airbase#AIRBASE airbase
|
||||
-- @field #number coalition
|
||||
-- @field #string alias
|
||||
-- @field #table wings
|
||||
-- @field Ops.Intel#INTEL Intel
|
||||
-- @field #number resurrection
|
||||
-- @field #number capspeed
|
||||
-- @field #number capalt
|
||||
-- @field #number capdir
|
||||
-- @field #number capleg
|
||||
-- @field #number maxinterceptsize
|
||||
-- @field #number missionrange
|
||||
-- @field #number noalert5
|
||||
-- @field #table ManagedAW
|
||||
-- @field #table ManagedSQ
|
||||
-- @field #table ManagedCP
|
||||
-- @field #table ManagedTK
|
||||
-- @field #table ManagedEWR
|
||||
-- @field #table ManagedREC
|
||||
-- @field #number MaxAliveMissions
|
||||
-- @field #boolean debug
|
||||
-- @field #number repeatsonfailure
|
||||
-- @field Core.Set#SET_ZONE GoZoneSet
|
||||
-- @field Core.Set#SET_ZONE NoGoZoneSet
|
||||
-- @field Core.Set#SET_ZONE ConflictZoneSet
|
||||
-- @field #boolean Monitor
|
||||
-- @field #boolean TankerInvisible
|
||||
-- @field #number CapFormation
|
||||
-- @field #table ReadyFlightGroups
|
||||
-- @field #boolean DespawnAfterLanding
|
||||
-- @field #boolean DespawnAfterHolding
|
||||
-- @field #list<Ops.Auftrag#AUFTRAG> ListOfAuftrag
|
||||
-- @field #string defaulttakeofftype Take off type
|
||||
-- @field #number FuelLowThreshold
|
||||
-- @field #number FuelCriticalThreshold
|
||||
-- @field #boolean showpatrolpointmarks
|
||||
-- @field #table EngageTargetTypes
|
||||
-- @extends Ops.EasyGCICAP#EASYGCICAP
|
||||
|
||||
--- *“High-Threat Close-Air-Support is a Myth.”* -- Mike “Starbaby” Pietrucha.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The EasyAG Concept
|
||||
--
|
||||
-- The idea of this class is partially to make the OPS classes easier operational for an A2G defense network, and to replace the legacy AI_A2G_Dispatcher system - not to it's
|
||||
-- full extent, but make a basic system work very quickly.
|
||||
--
|
||||
-- # Setup
|
||||
--
|
||||
-- ## Basic understanding
|
||||
--
|
||||
-- The basics are, there is **one** and only **one** AirWing per airbase. Each AirWing has **at least** one Squadron, who will do A2G tasks. Squadrons will be randomly chosen for the task at hand.
|
||||
-- Each AirWing has **at least** one Conflict Zone that it manages. COnflict Zones will be covered by the AirWing automatically as long as airframes are available. Detected enemy ground forces will be assigned to **one**
|
||||
-- AirWing based on proximity (that is, if you have more than one).
|
||||
--
|
||||
-- ## Assignment of tasks for enemies
|
||||
--
|
||||
-- An exisiting plane or a newly spawned plane will take care of the intruders. Standard overhead is 0.1, i.e. a group of 10 intrudes will
|
||||
-- be managed by one planes from the assigned AirWing. There is an maximum missions limitation per AirWing, so we do not spam the skies.
|
||||
--
|
||||
-- ## Basic set-up code
|
||||
--
|
||||
-- ### Prerequisites
|
||||
--
|
||||
-- You have to put a **STATIC WAREHOUSE** object on the airbase with the UNIT name according to the name of the airbase. **Do not put any other static type or it creates a conflict with the airbase name!**
|
||||
-- E.g. for Kutaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ.
|
||||
-- Next put a late activated template group for your A2G Squadron on the map. Last, put a zone on the map for the Defense operations, let's name it "Blue Zone 1". Size of the zone plays no role.
|
||||
-- Put a scout system on the map and name it aptly, like "Blue SCOUT".
|
||||
--
|
||||
-- ### Zones
|
||||
--
|
||||
-- For our example, you create a RED and a BLUE border, as a closed polygonal zone representing the borderlines. You can also have conflict zone, where - for our example - BLUE will attack
|
||||
-- RED groups, despite being on or close to RED territory. Think of a no-fly zone or an limited area of engagement. Conflict zones take precedence over borders, i.e. they can overlap all borders.
|
||||
--
|
||||
-- ### Code it
|
||||
--
|
||||
-- -- Set up a basic system for the blue side, we'll reside on Kutaisi, and use GROUP objects with "Blue SCOUT" in the name as Detecting Systems.
|
||||
-- local mywing = EASYA2G:New("A2G",AIRBASE.Caucasus.Kutaisi,"blue","SCOUT")
|
||||
--
|
||||
-- -- Add a holding/ingress point belonging to our airbase, we'll be at 5k ft doing 250 kn, initial direction 225 degrees (West), leg 5NM
|
||||
-- -- This will effectively be the ingress coordinate into the cnflict zone
|
||||
-- local Coordinate = ZONE:New("A2G Loitering"):GetCoordinate()
|
||||
-- mywing:AddHoldingPointA2G(AIRBASE.Caucasus.Kutaisi,Coordinate,5000,250,225,5)
|
||||
--
|
||||
-- -- Add a recon point over the conflict zone, we'll use a reaper for recon
|
||||
-- local Coordinate2 = ZONE:New("A2G Recon"):GetCoordinate()
|
||||
-- mywing:AddPatrolPointRecon(AIRBASE.Caucasus.Kutaisi,Coordinate2,15000,225,225,5)
|
||||
--
|
||||
-- -- Add three Squadrons with templates "Hero 1" and "Hero 2", 20 airframes, skill as set
|
||||
-- mywing:AddSquadron("A2G Flight", "Hero 1", AIRBASE.Caucasus.Kutaisi, 5, AI.Skill.GOOD, Modex, Livery)
|
||||
-- mywing:AddSquadron("A2G Helo", "Hero 2", AIRBASE.Caucasus.Kutaisi, 5, AI.Skill.HIGH, Modex, Livery)
|
||||
-- mywing:AddReconSquadron("Recon Drone", "SpyInTheSky SCOUT", AIRBASE.Caucasus.Kutaisi, 5, AI.Skill.EXCELLENT, Modex, Livery)
|
||||
--
|
||||
-- -- Ensure our reaper doesn't get immediately killed
|
||||
-- mywing:SetTankerAndScoutsInvisible(true)
|
||||
--
|
||||
-- -- Add a couple of zones
|
||||
-- -- We'll defend our own border
|
||||
-- mywing:AddAcceptZone(ZONE_POLYGON:New( "Blue Border", GROUP:FindByName( "Blue Border" ) ))
|
||||
-- -- We'll attack intruders also here - conflictzones can overlap borders(!) - limited zone of engagement
|
||||
-- mywing:AddConflictZone(ZONE_POLYGON:New("Red Defense Zone", GROUP:FindByName( "Red Defense Zone" )))
|
||||
-- -- We'll leave the reds alone on their turf
|
||||
-- mywing:AddRejectZone(ZONE_POLYGON:New( "Red Border", GROUP:FindByName( "Red Border" ) ))
|
||||
--
|
||||
-- -- Optional - Draw the borders on the map so we see what's going on
|
||||
-- -- Set up borders on map
|
||||
-- local BlueBorder = ZONE_POLYGON:New( "Blue Border", GROUP:FindByName( "Blue Border" ) )
|
||||
-- BlueBorder:DrawZone(-1,{0,0,1},1,FillColor,FillAlpha,1,true)
|
||||
-- local ConflictZone = ZONE_POLYGON:New("Red Defense Zone", GROUP:FindByName( "Red Defense Zone" ))
|
||||
-- ConflictZone:DrawZone(-1,{1,1,0},1,FillColor,FillAlpha,2,true)
|
||||
-- local BlueNoGoZone = ZONE_POLYGON:New( "Red Border", GROUP:FindByName( "Red Border" ) )
|
||||
-- BlueNoGoZone:DrawZone(-1,{1,0,0},1,FillColor,FillAlpha,4,true)
|
||||
--
|
||||
-- ### Add a second airwing with squads and own patrol point (optional)
|
||||
--
|
||||
-- -- Set this up at Sukhumi
|
||||
-- mywing:AddAirwing(AIRBASE.Caucasus.Sukhumi_Babushara,"Blue A2G Sukhumi")
|
||||
-- -- A2G Point "Blue Zone 2"
|
||||
-- mywing:AddPatrolPointA2G(AIRBASE.Caucasus.Sukhumi_Babushara,ZONE:FindByName("Blue Zone 2"):GetCoordinate(),30000,400,90,20)
|
||||
--
|
||||
-- -- This one has two squadrons to choose from
|
||||
-- mywing:AddSquadron("Blue Sq3 F16","A2G Sukhumi II",AIRBASE.Caucasus.Sukhumi_Babushara,20,AI.Skill.GOOD,402,"JASDF 6th TFS 43-8526 Skull Riders")
|
||||
-- mywing:AddSquadron("Blue Sq2 F15","A2G Sukhumi I",AIRBASE.Caucasus.Sukhumi_Babushara,20,AI.Skill.GOOD,202,"390th Fighter SQN")
|
||||
--
|
||||
-- ### Add a tanker (optional)
|
||||
--
|
||||
-- -- **Note** If you need different tanker types, i.e. Boom and Drogue, set them up at different AirWings!
|
||||
-- -- Add a tanker point
|
||||
-- mywing:AddPatrolPointTanker(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone Tanker"):GetCoordinate(),20000,280,270,50)
|
||||
-- -- Add a tanker squad - Radio 251 AM, TACAN 51Y
|
||||
-- mywing:AddTankerSquadron("Blue Tanker","Tanker Ops Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.EXCELLENT,602,nil,251,radio.modulation.AM,51)
|
||||
--
|
||||
-- ### Add an AWACS (optional)
|
||||
--
|
||||
-- -- Add an AWACS point
|
||||
-- mywing:AddPatrolPointAwacs(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone AWACS"):GetCoordinate(),25000,300,270,50)
|
||||
-- -- Add an AWACS squad - Radio 251 AM, TACAN 51Y
|
||||
-- mywing:AddAWACSSquadron("Blue AWACS","AWACS Ops Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.AVERAGE,702,nil,271,radio.modulation.AM)
|
||||
--
|
||||
-- # Fine-Tuning
|
||||
--
|
||||
-- ## Change Defaults
|
||||
--
|
||||
-- * @{#EASYA2G.SetDefaultResurrection}: Set how many seconds the AirWing stays inoperable after the AirWing STATIC HQ ist destroyed, default 900 secs.
|
||||
-- * @{#EASYA2G.SetDefaultA2GSpeed}: Set how many knots the A2G flights should do (will be altitude corrected), default 225 kn.
|
||||
-- * @{#EASYA2G.SetDefaultA2GAlt}: Set at which altitude (ASL) the A2G planes will fly, default 10,000 ft.
|
||||
-- * @{#EASYA2G.SetDefaultA2GDirection}: Set the initial direction from the A2G point the planes will fly in degrees, default is 90°.
|
||||
-- * @{#EASYA2G.SetDefaultA2GLeg}: Set the length of the A2G leg, default is 5 NM.
|
||||
-- * @{#EASYA2G.SetDefaultA2GGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 1.
|
||||
-- * @{#EASYA2G.SetDefaultMissionRange}: Set how many NM the planes can go from the home base, defaults to 100.
|
||||
-- * @{#EASYA2G.SetDefaultNumberAlert5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2.
|
||||
-- * @{#EASYA2G.SetDefaultEngageRange}: Set max engage range for A2G flights if they detect intruders, defaults to 50.
|
||||
-- * @{#EASYA2G.SetMaxAliveMissions}: Set max parallel missions can be done (A2G+GCI+Alert5+Tanker+AWACS), defaults to 8.
|
||||
-- * @{#EASYA2G.SetDefaultRepeatOnFailure}: Set max repeats on failure for intercepting/killing intruders, defaults to 3.
|
||||
-- * @{#EASYA2G.SetTankerAndScoutsInvisible}: Set Tanker and Scouts to be invisible to enemy AI eyes. Is set to `true` by default.
|
||||
--
|
||||
-- ## Debug and Monitor
|
||||
--
|
||||
-- mywing.debug = true -- log information
|
||||
-- mywing.Monitor = true -- show some statistics on screen
|
||||
--
|
||||
--
|
||||
-- @field #EASYA2G
|
||||
EASYA2G = {
|
||||
ClassName = "EASYA2G",
|
||||
overhead = 0.2,
|
||||
capgrouping = 1,
|
||||
airbasename = nil,
|
||||
airbase = nil,
|
||||
coalition = "blue",
|
||||
alias = nil,
|
||||
wings = {},
|
||||
Intel = nil,
|
||||
resurrection = 900,
|
||||
capspeed = 300,
|
||||
capalt = 25000,
|
||||
capdir = 45,
|
||||
capleg = 5,
|
||||
maxinterceptsize = 2,
|
||||
missionrange = 100,
|
||||
noalert5 = 2,
|
||||
ManagedAW = {},
|
||||
ManagedSQ = {},
|
||||
ManagedCP = {},
|
||||
ManagedTK = {},
|
||||
ManagedEWR = {},
|
||||
ManagedREC = {},
|
||||
MaxAliveMissions = 8,
|
||||
debug = false,
|
||||
engagerange = 50,
|
||||
repeatsonfailure = 3,
|
||||
GoZoneSet = nil,
|
||||
NoGoZoneSet = nil,
|
||||
ConflictZoneSet = nil,
|
||||
Monitor = false,
|
||||
TankerInvisible = true,
|
||||
CapFormation = nil,
|
||||
ReadyFlightGroups = {},
|
||||
DespawnAfterLanding = false,
|
||||
DespawnAfterHolding = true,
|
||||
ListOfAuftrag = {},
|
||||
defaulttakeofftype = "hot",
|
||||
FuelLowThreshold = 25,
|
||||
FuelCriticalThreshold = 10,
|
||||
showpatrolpointmarks = false,
|
||||
EngageTargetTypes = {"Ground"},
|
||||
}
|
||||
|
||||
--- Internal Squadron data type
|
||||
-- @type EASYA2G.Squad
|
||||
-- @field #string TemplateName
|
||||
-- @field #string SquadName
|
||||
-- @field #string AirbaseName
|
||||
-- @field #number AirFrames
|
||||
-- @field #string Skill
|
||||
-- @field #string Modex
|
||||
-- @field #string Livery
|
||||
-- @field #boolean Tanker
|
||||
-- @field #boolean AWACS
|
||||
-- @field #boolean RECON
|
||||
-- @field #number Frequency
|
||||
-- @field #number Modulation
|
||||
-- @field #number TACAN
|
||||
|
||||
--- Internal Wing data type
|
||||
-- @type EASYA2G.Wing
|
||||
-- @field #string AirbaseName
|
||||
-- @field #string Alias
|
||||
-- @field #string CapZoneName
|
||||
|
||||
--- Internal CapPoint data type
|
||||
-- @type EASYA2G.CapPoint
|
||||
-- @field #string AirbaseName
|
||||
-- @field Core.Point#COORDINATE Coordinate
|
||||
-- @field #number Altitude
|
||||
-- @field #number Speed
|
||||
-- @field #number Heading
|
||||
-- @field #number LegLength
|
||||
-- @field Core.Zone#ZONE_BASE Zone
|
||||
|
||||
--- EASYA2G class version.
|
||||
-- @field #string version
|
||||
EASYA2G.version="0.1.4"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: TBD
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new GCIA2G Manager
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string Alias A Name for this A2G Setup
|
||||
-- @param #string AirbaseName Name of the Home Airbase
|
||||
-- @param #string Coalition Coalition, e.g. "blue" or "red"
|
||||
-- @param #string ScoutName (Partial) group name of the detection system of the coalition, e.g. "Red SCOUT", can be handed in as table of names, e.g.{"SCOUT","DRONE","SAM"}
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:New(Alias, AirbaseName, Coalition, ScoutName)
|
||||
-- Inherit everything from FSM class.
|
||||
|
||||
local self=BASE:Inherit(self, EASYGCICAP:New(Alias, AirbaseName, Coalition, ScoutName)) -- #EASYA2G
|
||||
|
||||
-- defaults
|
||||
self.alias = Alias or AirbaseName.." A2G Wing"
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("EASYA2G %s | ", self.alias)
|
||||
|
||||
self.coalitionname = string.lower(Coalition) or "blue"
|
||||
self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED
|
||||
self.wings = {}
|
||||
if type(ScoutName) == "string" then ScoutName = {ScoutName} end
|
||||
self.EWRName = ScoutName --or self.coalitionname.." EWR"
|
||||
--self.CapZoneName = CapZoneName
|
||||
self.airbasename = AirbaseName
|
||||
self.airbase = AIRBASE:FindByName(self.airbasename)
|
||||
self.GoZoneSet = SET_ZONE:New()
|
||||
self.NoGoZoneSet = SET_ZONE:New()
|
||||
self.ConflictZoneSet = SET_ZONE:New()
|
||||
self.resurrection = 900
|
||||
self.capspeed = 225
|
||||
self.capalt = 5000
|
||||
self.capdir = 90
|
||||
self.capleg = 5
|
||||
self.capgrouping = 2
|
||||
self.missionrange = 100
|
||||
self.noalert5 = 2
|
||||
self.MaxAliveMissions = 8
|
||||
self.engagerange = 50
|
||||
self.repeatsonfailure = 3
|
||||
self.Monitor = false
|
||||
self.TankerInvisible = true
|
||||
self.CapFormation = ENUMS.Formation.FixedWing.FingerFour.Group
|
||||
self.DespawnAfterLanding = false
|
||||
self.DespawnAfterHolding = true
|
||||
self.ListOfAuftrag = {}
|
||||
self.defaulttakeofftype = "hot"
|
||||
self.FuelLowThreshold = 25
|
||||
self.FuelCriticalThreshold = 10
|
||||
self.showpatrolpointmarks = false
|
||||
self.EngageTargetTypes = {"Ground"}
|
||||
self:SetDefaultTurnoverTime()
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:SetStartState("Stopped")
|
||||
self:AddTransition("Stopped", "Start", "Running")
|
||||
self:AddTransition("Running", "Stop", "Stopped")
|
||||
self:AddTransition("*", "Status", "*")
|
||||
|
||||
--- On Before "Start" event.
|
||||
-- @function [parent=#EASYA2G] OnBeforeStart
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On After "Start" event.
|
||||
-- @function [parent=#EASYA2G] OnAfterStart
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On Before "Status" event.
|
||||
-- @function [parent=#EASYA2G] OnBeforeStatus
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On After "Status" event.
|
||||
-- @function [parent=#EASYA2G] OnAfterStatus
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
self:AddAirwing(self.airbasename,self.alias,self.CapZoneName)
|
||||
|
||||
self:I(self.lid.."Created new instance (v"..self.version..")")
|
||||
|
||||
self:__Start(math.random(6,12))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- Functions
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
--- Set Tanker and Scouts to be invisible to enemy AI eyes
|
||||
-- @param #EASYA2G self
|
||||
-- @param #boolean Switch Set to true or false, by default this is set to true already
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetTankerAndScoutsInvisible(Switch)
|
||||
self:T(self.lid.."SetTankerAndScoutsInvisible")
|
||||
self.TankerInvisible = Switch
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default A2G Speed in knots
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Speed Speed defaults to 300
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetDefaultA2GSpeed(Speed)
|
||||
self:T(self.lid.."SetDefaultSpeed")
|
||||
self.capspeed = Speed or 300
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set A2G Flight formation.
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Formation Formation to fly, defaults to ENUMS.Formation.FixedWing.FingerFour.Group
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetA2GFormation(Formation)
|
||||
self:T(self.lid.."SetA2GFormation")
|
||||
self.CapFormation = Formation
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default A2G Altitude in feet
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Altitude Altitude defaults to 25000
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetDefaultA2GAlt(Altitude)
|
||||
self:T(self.lid.."SetDefaultAltitude")
|
||||
self.capalt = Altitude or 25000
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default A2G lieg initial direction in degrees
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Direction Direction defaults to 90 (East)
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetDefaultA2GDirection(Direction)
|
||||
self:T(self.lid.."SetDefaultDirection")
|
||||
self.capdir = Direction or 90
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default leg length in NM
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Leg Leg defaults to 5
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetDefaultA2GLeg(Leg)
|
||||
self:T(self.lid.."SetDefaultLeg")
|
||||
self.capleg = Leg or 5
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default grouping, i.e. how many airplanes per A2G point
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Grouping Grouping defaults to 2
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetDefaultA2GGrouping(Grouping)
|
||||
self:T(self.lid.."SetDefaultA2GGrouping")
|
||||
self.capgrouping = Grouping or 2
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set A2G mission start to vary randomly between Start end End seconds.
|
||||
-- @param #EASYA2G self
|
||||
-- @param #number Start
|
||||
-- @param #number End
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetA2GStartTimeVariation(Start, End)
|
||||
self.capOptionVaryStartTime = Start or 5
|
||||
self.capOptionVaryEndTime = End or 60
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set which target types A2G flights will prefer to engage, defaults to {"Ground"}
|
||||
-- @param #EASYA2G self
|
||||
-- @param #table types Table of comma separated #string entries, defaults to {"Ground"} (everything that is ground and is not a weapon). Useful other options are e.g. {"Armored vehicles"}, {"Tanks"},
|
||||
-- or {"APC"} or combinations like {"APC", "Tanks", "Artillery"}. See [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes).
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:SetA2GEngageTargetTypes(types)
|
||||
self.EngageTargetTypes = types or {"Ground"}
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a A2G patrol/holding point to a Wing
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string AirbaseName Name of the Wing's airbase
|
||||
-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
|
||||
-- @param #number Altitude Defaults to 25000 feet ASL.
|
||||
-- @param #number Speed Defaults to 300 knots TAS.
|
||||
-- @param #number Heading Defaults to 90 degrees (East).
|
||||
-- @param #number LegLength Defaults to 15 NM.
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:AddHoldingPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
|
||||
self:T(self.lid.."AddHoldingPointA2G")--..Coordinate:ToStringLLDDM())
|
||||
local coordinate = Coordinate
|
||||
local EntryCAP = {} -- #EASYGCICAP.CapPoint
|
||||
if Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
-- adjust coordinate and get the coordinate from the zone
|
||||
coordinate = Coordinate:GetCoordinate()
|
||||
EntryCAP.Zone = Coordinate
|
||||
end
|
||||
EntryCAP.AirbaseName = AirbaseName
|
||||
EntryCAP.Coordinate = coordinate
|
||||
EntryCAP.Altitude = Altitude or 25000
|
||||
EntryCAP.Speed = Speed or 300
|
||||
EntryCAP.Heading = Heading or 90
|
||||
EntryCAP.LegLength = LegLength or 5
|
||||
self.ManagedCP[#self.ManagedCP+1] = EntryCAP
|
||||
if self.debug then
|
||||
local mark = MARKER:New(coordinate,self.lid.."Holding Point"):ToAll()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- (Internal) Add a Squadron to an Airwing of the manager
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string TemplateName Name of the group template.
|
||||
-- @param #string SquadName Squadron name - must be unique!
|
||||
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
|
||||
-- @param #number AirFrames Number of available airframes, e.g. 20.
|
||||
-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE
|
||||
-- @param #string Modex (optional) Modex to be used,e.g. 402.
|
||||
-- @param #string Livery (optional) Livery name to be used.
|
||||
-- @param #number Frequency (optional) Radio Frequency to be used.
|
||||
-- @param #number Modulation (optional) Radio Modulation to be used, e.g. radio.modulation.AM or radio.modulation.FM
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames, Skill, Modex, Livery, Frequency, Modulation)
|
||||
self:T(self.lid.."_AddSquadron "..SquadName)
|
||||
-- Add Squadrons
|
||||
local Squadron_One = SQUADRON:New(TemplateName,AirFrames,SquadName)
|
||||
Squadron_One:AddMissionCapability({AUFTRAG.Type.CAS, AUFTRAG.Type.CASENHANCED, AUFTRAG.Type.BAI, AUFTRAG.Type.ALERT5, AUFTRAG.Type.BOMBING, AUFTRAG.Type.STRIKE})
|
||||
--Squadron_One:SetFuelLowRefuel(true)
|
||||
Squadron_One:SetFuelLowThreshold(0.3)
|
||||
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||
Squadron_One:SetModex(Modex)
|
||||
Squadron_One:SetLivery(Livery)
|
||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
||||
Squadron_One:SetMissionRange(self.missionrange)
|
||||
|
||||
local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING
|
||||
|
||||
wing:AddSquadron(Squadron_One)
|
||||
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.CAS, AUFTRAG.Type.CASENHANCED, AUFTRAG.Type.BAI, AUFTRAG.Type.ALERT5, AUFTRAG.Type.BOMBING, AUFTRAG.Type.STRIKE},75)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Try to assign the intercept to a FlightGroup already in air and ready.
|
||||
-- @param #EASYA2G self
|
||||
-- @param #table ReadyFlightGroups ReadyFlightGroups
|
||||
-- @param Ops.Auftrag#AUFTRAG Auftrag The Auftrag
|
||||
-- @param Wrapper.Group#GROUP Group The Target
|
||||
-- @param #number WingSize Calculated number of Flights
|
||||
-- @return #boolean assigned
|
||||
-- @return #number leftover
|
||||
function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize)
|
||||
self:T("_TryAssignMission for size "..WingSize or 1)
|
||||
local assigned = false
|
||||
local wingsize = WingSize or 1
|
||||
local mindist = 0
|
||||
local disttable = {}
|
||||
if Group and Group:IsAlive() then
|
||||
local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0)
|
||||
self:T(self.lid..string.format("Assignment for %s",Group:GetName()))
|
||||
for _name,_FG in pairs(ReadyFlightGroups or {}) do
|
||||
local FG = _FG -- Ops.FlightGroup#FLIGHTGROUP
|
||||
local fcoord = FG:GetCoordinate()
|
||||
local dist = math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1))
|
||||
self:T(self.lid..string.format("FG %s Distance %dkm",_name,dist))
|
||||
disttable[#disttable+1] = { FG=FG, dist=dist}
|
||||
if dist>mindist then mindist=dist end
|
||||
end
|
||||
|
||||
local function sortDistance(a, b)
|
||||
return a.dist < b.dist
|
||||
end
|
||||
|
||||
table.sort(disttable, sortDistance)
|
||||
|
||||
for _,_entry in ipairs(disttable) do
|
||||
local FG = _entry.FG -- Ops.FlightGroup#FLIGHTGROUP
|
||||
FG:AddMission(Auftrag)
|
||||
local cm = FG:GetMissionCurrent()
|
||||
if cm then cm:Cancel() end
|
||||
wingsize = wingsize - 1
|
||||
self:T(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist))
|
||||
if wingsize == 0 then
|
||||
assigned = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return assigned, wingsize
|
||||
end
|
||||
|
||||
--- Find a holding point closest to the group to be attacked (if any set)
|
||||
-- @param #EASYA2G self
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
-- @return Core.Point#COORDINATE Point (can be nil!)
|
||||
function EASYA2G:_GetClosestHoldingPoint(Group)
|
||||
local point = nil
|
||||
local mindist = 0
|
||||
if Group and Group:IsAlive() then
|
||||
local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0)
|
||||
for _,_data in pairs(self.ManagedCP or {}) do
|
||||
local data = _data -- #EASYGCICAP.CapPoint
|
||||
--data.Coordinate
|
||||
local dist = math.floor(UTILS.Round(data.Coordinate:Get2DDistance(gcoord)/1000,1))
|
||||
self:T(self.lid..string.format("Holding Point Distance %dkm",dist))
|
||||
if dist>mindist then
|
||||
mindist=dist
|
||||
point=data.Coordinate
|
||||
end
|
||||
end
|
||||
end
|
||||
return point
|
||||
end
|
||||
|
||||
--- Here, we'll decide if we need to launch an attacking flight, and from where
|
||||
-- @param #EASYA2G self
|
||||
-- @param Ops.Intel#INTEL.Cluster Cluster
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:_AssignMission(Cluster)
|
||||
self:I(self.lid.."_AssignMission")
|
||||
-- Here, we'll decide if we need to launch an attacking flight, and from where
|
||||
local overhead = self.overhead
|
||||
local capspeed = self.capspeed + 100
|
||||
local capalt = self.capalt or 5000
|
||||
local maxsize = self.maxinterceptsize
|
||||
local repeatsonfailure = self.repeatsonfailure
|
||||
|
||||
local wings = self.wings
|
||||
local ctlpts = self.ManagedCP
|
||||
local MaxAliveMissions = self.MaxAliveMissions --* self.capgrouping
|
||||
local nogozoneset = self.NoGoZoneSet
|
||||
local conflictzoneset = self.ConflictZoneSet
|
||||
local ReadyFlightGroups = self.ReadyFlightGroups
|
||||
|
||||
-- Aircraft?
|
||||
if Cluster.ctype == INTEL.Ctype.AIRCRAFT then return end
|
||||
-- Threatlevel 0..10
|
||||
local contact = self.Intel:GetHighestThreatContact(Cluster)
|
||||
local name = contact.groupname --#string
|
||||
local threat = contact.threatlevel --#number
|
||||
local position = self.Intel:CalcClusterFuturePosition(Cluster,300)
|
||||
-- calculate closest zone
|
||||
local bestdistance = 2000*1000 -- 2000km
|
||||
local targetairwing = nil -- Ops.Airwing#AIRWING
|
||||
local targetawname = "" -- #string
|
||||
local clustersize = self.Intel:ClusterCountUnits(Cluster) or 1
|
||||
local wingsize = math.abs(overhead * (clustersize+1))
|
||||
if wingsize > maxsize then wingsize = maxsize end
|
||||
-- existing mission, and if so - done?
|
||||
local retrymission = true
|
||||
if Cluster.mission and (not Cluster.mission:IsOver()) then
|
||||
retrymission = false
|
||||
end
|
||||
if (retrymission) and (wingsize >= 1) then
|
||||
MESSAGE:New(string.format("**** %s Attackers need wingsize %d", UTILS.GetCoalitionName(self.coalition), wingsize),15,"A2G"):ToAllIf(self.debug):ToLog()
|
||||
for _,_data in pairs (wings) do
|
||||
local airwing = _data[1] -- Ops.Airwing#AIRWING
|
||||
local zone = _data[2] -- Core.Zone#ZONE
|
||||
local zonecoord = zone:GetCoordinate()
|
||||
local name = _data[3] -- #string
|
||||
local coa = AIRBASE:FindByName(name):GetCoalition()
|
||||
local distance = position:DistanceFromPointVec2(zonecoord)
|
||||
local airframes = airwing:CountAssets(true)
|
||||
local samecoalitionab = coa == self.coalition and true or false
|
||||
if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then
|
||||
bestdistance = distance
|
||||
targetairwing = airwing
|
||||
targetawname = name
|
||||
end
|
||||
end
|
||||
for _,_data in pairs (ctlpts) do
|
||||
--local airwing = _data[1] -- Ops.Airwing#AIRWING
|
||||
--local zone = _data[2] -- Core.Zone#ZONE
|
||||
--local zonecoord = zone:GetCoordinate()
|
||||
--local name = _data[3] -- #string
|
||||
|
||||
local data = _data -- #EASYGCICAP.CapPoint
|
||||
local name = data.AirbaseName
|
||||
local zonecoord = data.Coordinate
|
||||
if data.Zone then
|
||||
-- refresh coordinate in case we have a (moving) zone
|
||||
zonecoord = data.Zone:GetCoordinate()
|
||||
end
|
||||
local airwing = wings[name][1]
|
||||
local coa = AIRBASE:FindByName(name):GetCoalition()
|
||||
local samecoalitionab = coa == self.coalition and true or false
|
||||
local distance = position:DistanceFromPointVec2(zonecoord)
|
||||
local airframes = airwing:CountAssets(true)
|
||||
if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then
|
||||
bestdistance = distance
|
||||
targetairwing = airwing -- Ops.Airwing#AIRWING
|
||||
targetawname = name
|
||||
end
|
||||
end
|
||||
local text = string.format("Closest Airwing is %s", targetawname)
|
||||
local m = MESSAGE:New(text,10,"EasyA2G"):ToAllIf(self.debug):ToLog()
|
||||
-- Do we have a matching airwing?
|
||||
if targetairwing then
|
||||
local AssetCount = targetairwing:CountAssetsOnMission(MissionTypes,Cohort)
|
||||
local missioncount = self:_CountAliveAuftrags()
|
||||
-- Enough airframes on mission already?
|
||||
self:T(self.lid.." Assets on Mission "..AssetCount)
|
||||
if missioncount < MaxAliveMissions then
|
||||
local repeats = repeatsonfailure
|
||||
local Vec1 = contact.group:GetVec2()
|
||||
local Vec2 = targetairwing:GetVec2()
|
||||
--local HoldingVec2 = UTILS.FindNearestPointOnCircle(Vec1,UTILS.NMToMeters(10),Vec2)
|
||||
local IngressCoordinate = self:_GetClosestHoldingPoint(contact.group)
|
||||
if IngressCoordinate == nil then
|
||||
local IngressVec2 = UTILS.FindNearestPointOnCircle(Vec1,UTILS.NMToMeters(10),Vec2)
|
||||
IngressCoordinate = COORDINATE:NewFromVec2(IngressVec2)
|
||||
end
|
||||
local InterceptAuftrag = AUFTRAG:NewBAI(contact.group,capalt)
|
||||
:SetMissionRange(150)
|
||||
:SetPriority(1,true,1)
|
||||
:SetRepeatDelay(300)
|
||||
--:SetRequiredAssets(wingsize)
|
||||
:SetRepeatOnFailure(repeats)
|
||||
:SetMissionSpeed(UTILS.KnotsToAltKIAS(capspeed,capalt))
|
||||
:SetMissionAltitude(capalt)
|
||||
-- TODO: Refine this
|
||||
--:SetMissionHoldingCoord(COORDINATE:NewFromVec2(HoldingVec2),capalt,capspeed,120)
|
||||
:SetMissionIngressCoord(IngressCoordinate,capalt,capspeed)
|
||||
--:SetMissionEgressCoord(COORDINATE:NewFromVec2(HoldingVec2),capalt,capspeed)
|
||||
|
||||
if nogozoneset:Count() > 0 then
|
||||
InterceptAuftrag:AddConditionSuccess(
|
||||
function(group,zoneset,conflictset)
|
||||
local success = false
|
||||
if group and group:IsAlive() then
|
||||
local coord = group:GetCoordinate()
|
||||
if coord and zoneset:Count() > 0 and zoneset:IsCoordinateInZone(coord) then
|
||||
success = true
|
||||
end
|
||||
if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then
|
||||
success = false
|
||||
end
|
||||
else
|
||||
success = true -- target dead
|
||||
end
|
||||
return success
|
||||
end,
|
||||
contact.group,
|
||||
nogozoneset,
|
||||
conflictzoneset
|
||||
)
|
||||
end
|
||||
|
||||
InterceptAuftrag:AddConditionFailure(
|
||||
function()
|
||||
local failure = false
|
||||
if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting() then failure = true end
|
||||
return failure
|
||||
end
|
||||
)
|
||||
|
||||
table.insert(self.ListOfAuftrag,InterceptAuftrag)
|
||||
local assigned, rest = self:_TryAssignMission(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize)
|
||||
if not assigned then
|
||||
InterceptAuftrag:SetRequiredAssets(rest)
|
||||
targetairwing:AddMission(InterceptAuftrag)
|
||||
end
|
||||
Cluster.mission = InterceptAuftrag
|
||||
end
|
||||
else
|
||||
MESSAGE:New("**** Not enough airframes available or max mission limit reached!",15,"EasyA2G"):ToAllIf(self.debug):ToLog()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- (Internal) Start detection.
|
||||
-- @param #EASYA2G self
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:_StartIntel()
|
||||
self:T(self.lid.."_StartIntel")
|
||||
-- Border GCI Detection
|
||||
local BlueAir_DetectionSetGroup = SET_GROUP:New()
|
||||
BlueAir_DetectionSetGroup:FilterPrefixes( self.EWRName )
|
||||
BlueAir_DetectionSetGroup:FilterStart()
|
||||
|
||||
-- Intel type detection
|
||||
local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.alias)
|
||||
BlueIntel:SetClusterAnalysis(true,false,false)
|
||||
BlueIntel:SetForgetTime(300)
|
||||
BlueIntel:SetAcceptZones(self.GoZoneSet)
|
||||
BlueIntel:SetRejectZones(self.NoGoZoneSet)
|
||||
BlueIntel:SetConflictZones(self.ConflictZoneSet)
|
||||
BlueIntel:SetVerbosity(0)
|
||||
|
||||
if self.usecorridors == true then
|
||||
BlueIntel:SetCorridorZones(self.corridorzones)
|
||||
if self.corridorfloor or self.corridorceiling then
|
||||
BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
|
||||
BlueIntel:Start()
|
||||
|
||||
if self.debug then
|
||||
BlueIntel.debug = true
|
||||
end
|
||||
|
||||
local function AssignCluster(Cluster)
|
||||
self:_AssignMission(Cluster)
|
||||
end
|
||||
|
||||
function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster)
|
||||
AssignCluster(Cluster)
|
||||
end
|
||||
|
||||
self.Intel = BlueIntel
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
-- TODO FSM Functions
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
--- (Internal) FSM Function onafterStart
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:onafterStart(From,Event,To)
|
||||
self:T({From,Event,To})
|
||||
self:_StartIntel()
|
||||
self:_CreateAirwings()
|
||||
self:_CreateSquads()
|
||||
--self:_SetCAPPatrolPoints()
|
||||
self:_SetTankerPatrolPoints()
|
||||
self:_SetAwacsPatrolPoints()
|
||||
self:_SetReconPatrolPoints()
|
||||
self:__Status(-10)
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onafterStatus
|
||||
-- @param #EASYA2G self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #EASYA2G self
|
||||
function EASYA2G:onafterStatus(From,Event,To)
|
||||
self:T({From,Event,To})
|
||||
-- cleanup
|
||||
local cleaned = false
|
||||
local cleanlist = {}
|
||||
for _,_auftrag in pairs(self.ListOfAuftrag) do
|
||||
local auftrag = _auftrag -- Ops.Auftrag#AUFTRAG
|
||||
if auftrag and (not (auftrag:IsCancelled() or auftrag:IsDone() or auftrag:IsOver())) then
|
||||
table.insert(cleanlist,auftrag)
|
||||
cleaned = true
|
||||
end
|
||||
end
|
||||
if cleaned == true then
|
||||
self.ListOfAuftrag = nil
|
||||
self.ListOfAuftrag = cleanlist
|
||||
end
|
||||
-- Gather Some Stats
|
||||
local function counttable(tbl)
|
||||
local count = 0
|
||||
for _,_data in pairs(tbl) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
local wings = counttable(self.ManagedAW)
|
||||
local squads = counttable(self.ManagedSQ)
|
||||
local caps = counttable(self.ManagedCP)
|
||||
local assets = 0
|
||||
local instock = 0
|
||||
local capmission = 0
|
||||
local interceptmission = 0
|
||||
local reconmission = 0
|
||||
local awacsmission = 0
|
||||
local tankermission = 0
|
||||
local alert5mission = 0
|
||||
for _,_wing in pairs(self.wings) do
|
||||
local count = _wing[1]:CountAssetsOnMission(MissionTypes,Cohort)
|
||||
local count2 = _wing[1]:CountAssets(true,MissionTypes,Attributes)
|
||||
--capmission = capmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.PATROLRACETRACK})
|
||||
interceptmission = interceptmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.BAI})
|
||||
reconmission = reconmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON})
|
||||
awacsmission = awacsmission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS})
|
||||
tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER})
|
||||
alert5mission = alert5mission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.ALERT5})
|
||||
assets = assets + count
|
||||
instock = instock + count2
|
||||
local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5})
|
||||
-- update ready groups
|
||||
self.ReadyFlightGroups = nil
|
||||
self.ReadyFlightGroups = {}
|
||||
for _,_asset in pairs(assetsonmission or {}) do
|
||||
local asset = _asset -- Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
local FG = asset.flightgroup -- Ops.FlightGroup#FLIGHTGROUP
|
||||
if FG then
|
||||
local name = FG:GetName()
|
||||
local engage = FG:IsEngaging()
|
||||
local hasmissiles = FG:CanAirToGround()
|
||||
--self:T("Is Alert5? "..tostring(FG:GetMissionCurrent().type))
|
||||
local isalert5 = (FG:GetMissionCurrent() ~= nil and FG:GetMissionCurrent().type == AUFTRAG.Type.ALERT5) and true or false
|
||||
local ready = hasmissiles and FG:IsFuelGood() and (FG:IsAirborne() or isalert5)
|
||||
self:T(string.format("Flightgroup %s Engaging = %s Ready = %s (HasAmmo = %s HasFuel = %s Alert5 = %s)",tostring(name),tostring(engage),tostring(ready),tostring(hasmissiles),tostring(FG:IsFuelGood()), tostring(isalert5)))
|
||||
if ready then
|
||||
self.ReadyFlightGroups[name] = FG
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if self.Monitor then
|
||||
local threatcount = #self.Intel.Clusters or 0
|
||||
local text = self.alias
|
||||
text = text.."\nWings: "..wings.."\nSquads: "..squads.."\nHoldPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock
|
||||
text = text.."\nThreats: "..threatcount
|
||||
text = text.."\nAirWing alive Missions: "..capmission+awacsmission+tankermission+reconmission+interceptmission+alert5mission
|
||||
--text = text.."\n - A2G Holding: "..capmission
|
||||
text = text.."\n - A2G Attack: "..interceptmission
|
||||
text = text.."\n - AWACS: "..awacsmission
|
||||
text = text.."\n - TANKER: "..tankermission
|
||||
text = text.."\n - Recon: "..reconmission
|
||||
text = text.."\n - Alert5 "..alert5mission
|
||||
text = text.."\nMission Limit: "..self.MaxAliveMissions
|
||||
MESSAGE:New(text,15,"A2G"):ToAll():ToLogIf(self.debug)
|
||||
end
|
||||
self:__Status(30)
|
||||
return self
|
||||
end
|
||||
@@ -1,3 +1,12 @@
|
||||
--- **Ops** - Create your A2A Defenses.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Automatically create and manage A2A defenses using an AirWing and Squadrons for one coalition
|
||||
-- * Easy set-up
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-------------------------------------------------------------------------
|
||||
-- Easy CAP/GCI Class, based on OPS classes
|
||||
-------------------------------------------------------------------------
|
||||
@@ -12,7 +21,7 @@
|
||||
--
|
||||
-------------------------------------------------------------------------
|
||||
-- Date: September 2023
|
||||
-- Last Update: Aug 2025
|
||||
-- Last Update: Jan 2026
|
||||
-------------------------------------------------------------------------
|
||||
--
|
||||
--- **Ops** - Easy GCI & CAP Manager
|
||||
@@ -80,6 +89,8 @@
|
||||
-- @field #number FuelCriticalThreshold
|
||||
-- @field #boolean showpatrolpointmarks
|
||||
-- @field #table EngageTargetTypes
|
||||
-- @field #number maintenancetime
|
||||
-- @field #number repairtime
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown.
|
||||
@@ -109,7 +120,7 @@
|
||||
-- ### Prerequisites
|
||||
--
|
||||
-- You have to put a **STATIC WAREHOUSE** object on the airbase with the UNIT name according to the name of the airbase. **Do not put any other static type or it creates a conflict with the airbase name!**
|
||||
-- E.g. for Kuitaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ.
|
||||
-- E.g. for Kutaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ.
|
||||
-- Next put a late activated template group for your CAP/GCI Squadron on the map. Last, put a zone on the map for the CAP operations, let's name it "Blue Zone 1". Size of the zone plays no role.
|
||||
-- Put an EW radar system on the map and name it aptly, like "Blue EWR".
|
||||
--
|
||||
@@ -205,7 +216,7 @@ EASYGCICAP = {
|
||||
coalition = "blue",
|
||||
alias = nil,
|
||||
wings = {},
|
||||
Intel = nil,
|
||||
Intel = nil, -- Ops.Intel#INTEL
|
||||
resurrection = 900,
|
||||
capspeed = 300,
|
||||
capalt = 25000,
|
||||
@@ -275,10 +286,11 @@ EASYGCICAP = {
|
||||
|
||||
--- EASYGCICAP class version.
|
||||
-- @field #string version
|
||||
EASYGCICAP.version="0.1.30"
|
||||
EASYGCICAP.version="0.1.34"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
--
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: TBD
|
||||
@@ -300,6 +312,10 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
|
||||
-- defaults
|
||||
self.alias = Alias or AirbaseName.." CAP Wing"
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("EASYGCICAP %s | ", self.alias)
|
||||
|
||||
self.coalitionname = string.lower(Coalition) or "blue"
|
||||
self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED
|
||||
self.wings = {}
|
||||
@@ -333,10 +349,8 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
self.FuelCriticalThreshold = 10
|
||||
self.showpatrolpointmarks = false
|
||||
self.EngageTargetTypes = {"Air"}
|
||||
self:SetDefaultTurnoverTime()
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("EASYGCICAP %s | ", self.alias)
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:SetStartState("Stopped")
|
||||
@@ -350,6 +364,34 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
|
||||
self:__Start(math.random(6,12))
|
||||
|
||||
--- On Before "Start" event.
|
||||
-- @function [parent=#EASYGCICAP] OnBeforeStart
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On After "Start" event.
|
||||
-- @function [parent=#EASYGCICAP] OnAfterStart
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On Before "Status" event.
|
||||
-- @function [parent=#EASYGCICAP] OnBeforeStatus
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On After "Status" event.
|
||||
-- @function [parent=#EASYGCICAP] OnAfterStatus
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -369,6 +411,28 @@ function EASYGCICAP:GetAirwing(AirbaseName)
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Add an agent to the underlying INTEL detection - caution, we need to be started first for this to work!
|
||||
-- Normally this isn't necessary when the Group name is correctly filled (see EWRName in `New()`).
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param Wrapper.Group#GROUP Group The group object to be added as Intel Agent.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:AddAgent(Group)
|
||||
self:T(self.lid.."AddAgent")
|
||||
if Group:IsInstanceOf("GROUP") and self.Intel ~= nil then
|
||||
self.Intel:AddAgent(Group)
|
||||
if self.TankerInvisible == true then
|
||||
Group:SetCommandInvisible(true)
|
||||
Group:OptionROEHoldFire()
|
||||
if Group:IsAir() then
|
||||
Group:OptionROTEvadeFire()
|
||||
else
|
||||
Group:OptionDisperseOnAttack(30)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get a table of all managed AirWings
|
||||
-- @param #EASYGCICAP self
|
||||
-- @return #table Table of Ops.AirWing#AIRWING Airwings
|
||||
@@ -548,6 +612,18 @@ function EASYGCICAP:SetDefaultMissionRange(Range)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default turnover times for squadrons in minutes
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number MaintenanceTime Time in minutes it takes until a flight is combat ready again. Default is 5 min.
|
||||
-- @param #number RepairTime Time in minutes it takes to repair a flight for each life point taken. Default is 10 min.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetDefaultTurnoverTime(MaintenanceTime,RepairTime)
|
||||
self:T(self.lid.."SetDefaultTurnoverTime")
|
||||
self.maintenancetime=MaintenanceTime or 5
|
||||
self.repairtime=RepairTime or 10
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default number of airframes standing by for intercept tasks (visible on the airfield)
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Airframes defaults to 2
|
||||
@@ -611,7 +687,6 @@ function EASYGCICAP:SetCapStartTimeVariation(Start, End)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set which target types CAP flights will prefer to engage, defaults to {"Air"}
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #table types Table of comma separated #string entries, defaults to {"Air"} (everything that flies and is not a weapon). Useful other options are e.g. {"Bombers"}, {"Fighters"},
|
||||
@@ -656,7 +731,7 @@ function EASYGCICAP:_CreateAirwings()
|
||||
return self
|
||||
end
|
||||
|
||||
--- (internal) Create and add another AirWing to the manager
|
||||
--- (Internal) Create and add another AirWing to the manager
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #string Airbasename
|
||||
-- @param #string Alias
|
||||
@@ -763,8 +838,13 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
end
|
||||
end
|
||||
|
||||
if self.noalert5 > 0 then
|
||||
local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
|
||||
if self.noalert5 > 0 then
|
||||
local alert
|
||||
if self.ClassName == "EASYGCICAP" then
|
||||
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
|
||||
elseif self.ClassName == "EASYA2G" then
|
||||
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.BAI)
|
||||
end
|
||||
alert:SetRequiredAssets(self.noalert5)
|
||||
alert:SetRepeat(99)
|
||||
CAP_Wing:AddMission(alert)
|
||||
@@ -1153,7 +1233,7 @@ function EASYGCICAP:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames
|
||||
Squadron_One:AddMissionCapability({AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROLRACETRACK, AUFTRAG.Type.ALERT5})
|
||||
--Squadron_One:SetFuelLowRefuel(true)
|
||||
Squadron_One:SetFuelLowThreshold(0.3)
|
||||
Squadron_One:SetTurnoverTime(10,20)
|
||||
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||
Squadron_One:SetModex(Modex)
|
||||
Squadron_One:SetLivery(Livery)
|
||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
||||
@@ -1184,7 +1264,7 @@ function EASYGCICAP:_AddReconSquadron(TemplateName, SquadName, AirbaseName, AirF
|
||||
Squadron_One:AddMissionCapability({AUFTRAG.Type.RECON})
|
||||
--Squadron_One:SetFuelLowRefuel(true)
|
||||
Squadron_One:SetFuelLowThreshold(0.3)
|
||||
Squadron_One:SetTurnoverTime(10,20)
|
||||
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||
Squadron_One:SetModex(Modex)
|
||||
Squadron_One:SetLivery(Livery)
|
||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
||||
@@ -1218,7 +1298,7 @@ function EASYGCICAP:_AddTankerSquadron(TemplateName, SquadName, AirbaseName, Air
|
||||
Squadron_One:AddMissionCapability({AUFTRAG.Type.TANKER})
|
||||
--Squadron_One:SetFuelLowRefuel(true)
|
||||
Squadron_One:SetFuelLowThreshold(0.3)
|
||||
Squadron_One:SetTurnoverTime(10,20)
|
||||
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||
Squadron_One:SetModex(Modex)
|
||||
Squadron_One:SetLivery(Livery)
|
||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
||||
@@ -1255,7 +1335,7 @@ function EASYGCICAP:_AddAWACSSquadron(TemplateName, SquadName, AirbaseName, AirF
|
||||
Squadron_One:AddMissionCapability({AUFTRAG.Type.AWACS})
|
||||
--Squadron_One:SetFuelLowRefuel(true)
|
||||
Squadron_One:SetFuelLowThreshold(0.3)
|
||||
Squadron_One:SetTurnoverTime(10,20)
|
||||
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||
Squadron_One:SetModex(Modex)
|
||||
Squadron_One:SetLivery(Livery)
|
||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
||||
@@ -1300,6 +1380,54 @@ function EASYGCICAP:AddConflictZone(Zone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zones.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param Core.Set#SET_ZONE CorridorZones Can be handed in as SET\_ZONE or single ZONE object.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetCorridorZones(CorridorZones)
|
||||
self:T(self.lid .. "SetCorridorZones")
|
||||
if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE") then
|
||||
self.corridorzones = CorridorZones
|
||||
self.usecorridors = true
|
||||
elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE") then
|
||||
if not self.corridorzones then self.corridorzones = SET_ZONE:New() end
|
||||
self.corridorzones:AddZone(CorridorZones)
|
||||
self.usecorridors = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to add one corridor zone.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param Core.Zone#ZONE CorridorZone The ZONE object to be added.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:AddCorridorZone(CorridorZone)
|
||||
self:T(self.lid .. "AddCorridorZone")
|
||||
self:SetCorridorZones(CorridorZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in FEET.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Floor Floor altitude ASL in feet.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in feet.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetCorridorZoneFloorAndCeiling(Floor,Ceiling)
|
||||
self.corridorfloor = UTILS.FeetToMeters(Floor)
|
||||
self.corridorceiling = UTILS.FeetToMeters(Ceiling)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in METERS.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Floor Floor altitude ASL in meters.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in meters.
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling)
|
||||
self.corridorfloor = Floor
|
||||
self.corridorceiling = Ceiling
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Try to assign the intercept to a FlightGroup already in air and ready.
|
||||
-- @param #EASYGCICAP self
|
||||
@@ -1459,6 +1587,8 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then
|
||||
success = false
|
||||
end
|
||||
else
|
||||
success = true
|
||||
end
|
||||
return success
|
||||
end,
|
||||
@@ -1468,6 +1598,14 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
)
|
||||
end
|
||||
|
||||
InterceptAuftrag:AddConditionFailure(
|
||||
function()
|
||||
local failure = false
|
||||
if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting() then failure = true end
|
||||
return failure
|
||||
end
|
||||
)
|
||||
|
||||
table.insert(self.ListOfAuftrag,InterceptAuftrag)
|
||||
local assigned, rest = self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize)
|
||||
if not assigned then
|
||||
@@ -1500,6 +1638,14 @@ function EASYGCICAP:_StartIntel()
|
||||
BlueIntel:SetRejectZones(self.NoGoZoneSet)
|
||||
BlueIntel:SetConflictZones(self.ConflictZoneSet)
|
||||
BlueIntel:SetVerbosity(0)
|
||||
|
||||
if self.usecorridors == true then
|
||||
BlueIntel:SetCorridorZones(self.corridorzones)
|
||||
if self.corridorfloor or self.corridorceiling then
|
||||
BlueIntel:SetCorridorLimits(self.corridorfloor,self.corridorceiling)
|
||||
end
|
||||
end
|
||||
|
||||
BlueIntel:Start()
|
||||
|
||||
if self.debug then
|
||||
@@ -1603,7 +1749,7 @@ function EASYGCICAP:onafterStatus(From,Event,To)
|
||||
tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER})
|
||||
assets = assets + count
|
||||
instock = instock + count2
|
||||
local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK})
|
||||
local assetsonmission = _wing[1]:GetAssetsOnMission({AUFTRAG.Type.ALERT5, AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK})
|
||||
-- update ready groups
|
||||
self.ReadyFlightGroups = nil
|
||||
self.ReadyFlightGroups = {}
|
||||
@@ -1614,7 +1760,8 @@ function EASYGCICAP:onafterStatus(From,Event,To)
|
||||
local name = FG:GetName()
|
||||
local engage = FG:IsEngaging()
|
||||
local hasmissiles = FG:IsOutOfMissiles() == nil and true or false
|
||||
local ready = hasmissiles and FG:IsFuelGood() and FG:IsAirborne()
|
||||
local isalert5 = (FG:GetMissionCurrent() ~= nil and FG:GetMissionCurrent().type == AUFTRAG.Type.ALERT5) and true or false
|
||||
local ready = hasmissiles and FG:IsFuelGood() and (FG:IsAirborne() or isalert5)
|
||||
--self:T(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready)))
|
||||
if ready then
|
||||
self.ReadyFlightGroups[name] = FG
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,8 +60,6 @@
|
||||
-- @field #boolean jettisonEmptyTanks Allow (true) or disallow (false) AI to jettison empty fuel tanks.
|
||||
-- @field #boolean jettisonWeapons Allow (true) or disallow (false) AI to jettison weapons if in danger.
|
||||
-- @field #number holdtime Time [s] flight is holding before going on final. Set to nil for indefinitely.
|
||||
-- @field #table flightplans Flight plans for this group.
|
||||
-- @field Navigation.FlightPlan#FLIGHTPLAN flightplan Currently active flight plan.
|
||||
--
|
||||
-- @extends Ops.OpsGroup#OPSGROUP
|
||||
|
||||
@@ -153,8 +151,7 @@ FLIGHTGROUP = {
|
||||
playerWarnings = {},
|
||||
prohibitAB = false,
|
||||
jettisonEmptyTanks = true,
|
||||
jettisonWeapons = true, -- that's actually a negative option like prohibitAB
|
||||
flightplans = {},
|
||||
jettisonWeapons = true, -- that's actually a negative option like prohibitAB
|
||||
}
|
||||
|
||||
|
||||
@@ -888,17 +885,6 @@ function FLIGHTGROUP:GetFlightControl()
|
||||
return self.flightcontrol
|
||||
end
|
||||
|
||||
--- Add flightplan.
|
||||
-- @param #FLIGHTGROUP self
|
||||
-- @param Navigation.FlightPlan#FLIGHTPLAN FlightPlan The flight plan.
|
||||
-- @return #FLIGHTGROUP self
|
||||
function FLIGHTGROUP:AddFlightPlan(FlightPlan)
|
||||
|
||||
self:T(self.lid..string.format("Adding flight plan %s", FlightPlan:GetName() ))
|
||||
table.insert(self.flightplans, FlightPlan)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the homebase.
|
||||
-- @param #FLIGHTGROUP self
|
||||
@@ -1723,10 +1709,6 @@ function FLIGHTGROUP:Status()
|
||||
-- TODO: Not sure why I introduced this here.
|
||||
self:_CheckGroupDone()
|
||||
end
|
||||
|
||||
if not (self.cargoTransport or mission) then
|
||||
self:_CheckFlightPlans()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -3817,56 +3799,6 @@ function FLIGHTGROUP:onafterFuelCritical(From, Event, To)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Flightplan Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Check flightplans.
|
||||
-- @param #FLIGHTGROUP self
|
||||
-- @param Navigation.FlightPlan#FLIGHTPLAN FlightPlan Flight plan.
|
||||
-- @return #FLIGHTGROUP self
|
||||
function FLIGHTGROUP:_SetFlightPlan(FlightPlan)
|
||||
|
||||
self.flightplan=FlightPlan
|
||||
|
||||
for i,_fix in pairs(FlightPlan.fixes) do
|
||||
local fix=_fix --Navigation.NavFix#NAVFIX
|
||||
|
||||
--fix.coordinate
|
||||
|
||||
local speed=fix.altMin
|
||||
|
||||
local altitude=(fix.altMin or fix.altMax)~=nil and fix:GetAltitude() or FlightPlan:GetCruiseAltitude()
|
||||
|
||||
local wp=self:AddWaypoint(fix.coordinate, Speed, AfterWaypointWithID, altitude or 10000, false)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Check flightplans.
|
||||
-- @param #FLIGHTGROUP self
|
||||
-- @return #FLIGHTGROUP self
|
||||
function FLIGHTGROUP:_CheckFlightPlans()
|
||||
|
||||
if self.flightplan then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
for i,_flightplan in pairs(self.flightplans) do
|
||||
local flightplan=_flightplan --Navigation.FlightPlan#FLIGHTPLAN
|
||||
|
||||
if flightplan then
|
||||
|
||||
self:_SetFlightPlan(flightplan)
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Task functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
-- @field Core.Set#SET_ZONE acceptzoneset Set of accept zones. If defined, only contacts in these zones are considered.
|
||||
-- @field Core.Set#SET_ZONE rejectzoneset Set of reject zones. Contacts in these zones are not considered, even if they are in accept zones.
|
||||
-- @field Core.Set#SET_ZONE conflictzoneset Set of conflict zones. Contacts in these zones are considered, even if they are not in accept zones or if they are in reject zones.
|
||||
-- @field Core.Set#SET_ZONE corridorzoneset Set of corridor zones. Contacts in these zones are never considered. Also see corridorfloorheight and corridorfloorceiling.
|
||||
-- @field #number corridorfloor [Air] Contacts below this height (ASL!) are considered, even if they are in a corridor zone.
|
||||
-- @field #number corridorceiling [Air] Contacts above this height (ASL!) are considered, even if they are in a corridor zone.
|
||||
-- @field #table Contacts Table of detected items.
|
||||
-- @field #table ContactsLost Table of lost detected items.
|
||||
-- @field #table ContactsUnknown Table of new detected items.
|
||||
@@ -39,6 +42,9 @@
|
||||
-- @field #number prediction Seconds default to be used with CalcClusterFuturePosition.
|
||||
-- @field #boolean detectStatics If `true`, detect STATIC objects. Default `false`.
|
||||
-- @field #number statusupdate Time interval in seconds after which the status is refreshed. Default 60 sec. Should be negative.
|
||||
-- @field #boolean DetectAccoustic If true, also detect by sound (ie proximity).
|
||||
-- @field #number DetectAccousticRadius Radius dfor accoustic detection, defaults to 2000 meters.
|
||||
-- @field #table DetectAccousticUnitTypes Types of units we can detect accousticly. Defaults to {Unit.Category.HELICOPTER}
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Top Secret!
|
||||
@@ -102,6 +108,9 @@ INTEL = {
|
||||
clusterarrows = false,
|
||||
prediction = 300,
|
||||
detectStatics = false,
|
||||
DetectAccoustic = false,
|
||||
DetectAccousticRadius = 1000,
|
||||
DetectAccousticUnitTypes = {Unit.Category.HELICOPTER},
|
||||
}
|
||||
|
||||
--- Detected item info.
|
||||
@@ -160,14 +169,14 @@ INTEL.Ctype={
|
||||
|
||||
--- INTEL class version.
|
||||
-- @field #string version
|
||||
INTEL.version="0.3.6"
|
||||
INTEL.version="0.3.10"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Add min cluster size. Only create new clusters if they have a certain group size.
|
||||
-- TODO: process detected set asynchroniously for better performance.
|
||||
-- NODO: process detected set asynchroniously for better performance.
|
||||
-- DONE: Add statics.
|
||||
-- DONE: Filter detection methods.
|
||||
-- DONE: Accept zones.
|
||||
@@ -175,6 +184,7 @@ INTEL.version="0.3.6"
|
||||
-- NOGO: SetAttributeZone --> return groups of generalized attributes in a zone.
|
||||
-- DONE: Loose units only if they remain undetected for a given time interval. We want to avoid fast oscillation between detected/lost states. Maybe 1-5 min would be a good time interval?!
|
||||
-- DONE: Combine units to groups for all, new and lost.
|
||||
-- DONE: Add corridor zones.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@@ -266,6 +276,7 @@ function INTEL:New(DetectionSet, Coalition, Alias)
|
||||
self:SetForgetTime()
|
||||
self:SetAcceptZones()
|
||||
self:SetRejectZones()
|
||||
self:SetCorridorZones()
|
||||
self:SetConflictZones()
|
||||
|
||||
------------------------
|
||||
@@ -398,6 +409,26 @@ function INTEL:SetAcceptZones(AcceptZoneSet)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set to accept accoustic detection.
|
||||
-- @param #INTEL self
|
||||
-- @param #number Radius Radius in which we can "hear" units. Defaults to 1000 meters.
|
||||
-- @param #table UnitCategories Set what Unit Categories we can "hear". Defaults to `{Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER}`
|
||||
-- @return #INTEL self
|
||||
function INTEL:SetAccousticDetectionOn(Radius,UnitCategories)
|
||||
self.DetectAccoustic = true
|
||||
self.DetectAccousticRadius = Radius or 1000
|
||||
self.DetectAccousticUnitTypes = UnitCategories or {Unit.Category.HELICOPTER}
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch off accoustic detection.
|
||||
-- @param #INTEL self
|
||||
-- @return #INTEL self
|
||||
function INTEL:SetAccousticDetectionOff()
|
||||
self.DetectAccoustic = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an accept zone. Only contacts detected in this zone are considered.
|
||||
-- @param #INTEL self
|
||||
-- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set.
|
||||
@@ -475,7 +506,69 @@ function INTEL:RemoveConflictZone(ConflictZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- **OBSOLETE, will be removed in next version!** Set forget contacts time interval.
|
||||
--- Set corrdidor zones. Contacts detected in this/these zone(s) are never reported by the detection.
|
||||
-- Note that corrdidor zones overrule all other zones, for exceptions see corridor floor and corridor ceiling heights.
|
||||
-- @param #INTEL self
|
||||
-- @param Core.Set#SET_ZONE CorridorZoneSet Set of corrdidor zone(s).
|
||||
-- @return #INTEL self
|
||||
function INTEL:SetCorridorZones(CorridorZoneSet)
|
||||
self.corridorzoneset=CorridorZoneSet or SET_ZONE:New()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a corrdidor zone. Contacts detected in this zone are corrdidored and not reported by the detection.
|
||||
-- Note that corrdidor zones overrule all other zones, for exceptions see corridor floor and corridor ceiling heights.
|
||||
-- @param #INTEL self
|
||||
-- @param Core.Zone#ZONE CorridorZone Add a zone to the corrdidor zone set.
|
||||
-- @return #INTEL self
|
||||
function INTEL:AddCorridorZone(CorridorZone)
|
||||
self.corridorzoneset:AddZone(CorridorZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove a corrdidor zone from the corrdidor zone set.
|
||||
-- Note that corrdidor zones overrule all other zones, for exceptions see corridor floor and corridor ceiling heights.
|
||||
-- @param #INTEL self
|
||||
-- @param Core.Zone#ZONE CorridorZone Remove a zone from the corrdidor zone set.
|
||||
-- @return #INTEL self
|
||||
function INTEL:RemoveCorridorZone(CorridorZone)
|
||||
self.corridorzoneset:Remove(CorridorZone:GetName(), true)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Air] Add corrdidor zone floor and height. This is generally applicable to all(!) corridor zones. Considered as ASL (above sea level or barometric) values.
|
||||
-- Overrides corridor exception for objects flying outside this limitations.
|
||||
-- To set an individual ceiling/floor on any Core.Zone#ZONE you wish to use, set these properties on the Core.Zone#ZONE object:
|
||||
-- `mycorridorzone:SetProperty("CorridorFloor",500)` -- meters, case sensitivity matters!
|
||||
-- `mycorridorzone:SetProperty("CorridorCeiling",10000)` -- meters, case sensitivity matters!
|
||||
-- @param #INTEL self
|
||||
-- @param #number Floor Floor altitude in meters.
|
||||
-- @param #number Ceiling Ceiling altitude in meters.
|
||||
-- @return #INTEL self
|
||||
function INTEL:SetCorridorLimits(Floor,Ceiling)
|
||||
self.corridorceiling = Ceiling or 10000
|
||||
self.corridorfloor = Floor or 1
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Air] Add corrdidor zone floor and height. This is generally applicable to all(!) corridor zones. Considered as ASL (above sea level or barometric) values.
|
||||
-- Overrides corridor exception for objects flying outside this limitations.
|
||||
-- To set an individual ceiling/floor on any Core.Zone#ZONE you wish to use, set these properties on the Core.Zone#ZONE object:
|
||||
-- `mycorridorzone:SetProperty("CorridorFloor",UTILS.FeetToMeters(5000))` -- feet, case sensitivity matters!
|
||||
-- `mycorridorzone:SetProperty("CorridorCeiling",UTILS.FeetToMeters(20000))` -- feet, case sensitivity matters!
|
||||
-- @param #INTEL self
|
||||
-- @param #number Floor Floor altitude in feet.
|
||||
-- @param #number Ceiling Ceiling altitude in feet.
|
||||
-- @return #INTEL self
|
||||
function INTEL:SetCorridorLimitsFeet(Floor,Ceiling)
|
||||
local Ceiling = Ceiling or 25000
|
||||
local Floor = Floor or 15000
|
||||
self.corridorceiling = UTILS.FeetToMeters(Ceiling)
|
||||
self.corridorfloor = UTILS.FeetToMeters(Floor)
|
||||
return self
|
||||
end
|
||||
|
||||
--- **OBSOLETE, not functional!** Set forget contacts time interval.
|
||||
-- Previously known contacts that are not detected any more, are "lost" after this time.
|
||||
-- This avoids fast oscillations between a contact being detected and undetected.
|
||||
-- @param #INTEL self
|
||||
@@ -831,6 +924,18 @@ function INTEL:UpdateIntel()
|
||||
self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting, self.DetectVisual, self.DetectOptical, self.DetectRadar, self.DetectIRST, self.DetectRWR, self.DetectDLINK)
|
||||
|
||||
end
|
||||
|
||||
if self.DetectAccoustic then
|
||||
local recce = group:GetFirstUnitAlive()
|
||||
local detectionzone = group:GetProperty("INTEL_DETECT_ACCZONE")
|
||||
if not detectionzone then
|
||||
detectionzone = ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000)
|
||||
group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone)
|
||||
end
|
||||
if recce and recce:IsGround() then
|
||||
self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -884,6 +989,36 @@ function INTEL:UpdateIntel()
|
||||
table.insert(remove, unitname)
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if unit is in any of the corridor zones.
|
||||
if self.corridorzoneset:Count()>0 then
|
||||
self:T("Corridorzone Check for unit "..unit:GetName())
|
||||
local inzone = false
|
||||
for _,_zone in pairs(self.corridorzoneset.Set) do
|
||||
local zone=_zone --Core.Zone#ZONE
|
||||
if unit:IsInZone(zone) then
|
||||
local corridorfloor = zone:GetProperty("CorridorFloor") or self.corridorfloor
|
||||
local corridorceiling = zone:GetProperty("CorridorCeiling") or self.corridorceiling
|
||||
local debugtext = "Corridorzone Check for unit "..unit:GetName().."\n"
|
||||
debugtext = debugtext .. string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())),
|
||||
tonumber(UTILS.MetersToFeet(corridorfloor)),tonumber(UTILS.MetersToFeet(corridorceiling)))
|
||||
MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1)
|
||||
if unit:IsAir() and (corridorfloor ~= nil or corridorceiling ~= nil) then
|
||||
local alt = unit:GetAltitude()
|
||||
if corridorfloor and alt > corridorfloor then inzone = true end
|
||||
if corridorceiling and (inzone == true or corridorfloor == nil) and alt < corridorceiling then inzone = true else inzone = false end
|
||||
if inzone == true then break end
|
||||
else
|
||||
inzone=true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Unit is inside a corridor zone ==> remove!
|
||||
if inzone then
|
||||
table.insert(remove, unitname)
|
||||
end
|
||||
end
|
||||
|
||||
-- Filter unit categories. Added check that we have a UNIT and not a STATIC object because :GetUnitCategory() is only available for units.
|
||||
if #self.filterCategory>0 and unit:IsInstanceOf("UNIT") then
|
||||
@@ -1040,7 +1175,7 @@ function INTEL:_CreateContact(Positionable, RecceName)
|
||||
item.category=3 --static:GetCategory()
|
||||
item.categoryname=static:GetCategoryName() or "Unknown"
|
||||
item.threatlevel=static:GetThreatLevel() or 0
|
||||
item.position=static:GetCoordinate()
|
||||
item.position=static:GetCoord()
|
||||
item.velocity=static:GetVelocityVec3()
|
||||
item.speed=0
|
||||
item.recce=RecceName
|
||||
@@ -1106,7 +1241,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, DetectedStatics, RecceDetecti
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Return the detected target groups of the controllable as a @{Core.Set#SET_GROUP}.
|
||||
--- (Internal) Return the detected target groups of the controllable as a table.
|
||||
-- The optional parameters specify the detection methods that can be applied.
|
||||
-- If no detection method is given, the detection will use all the available methods by default.
|
||||
-- @param #INTEL self
|
||||
@@ -1147,15 +1282,15 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua
|
||||
local DetectionAccepted = true
|
||||
|
||||
if self.RadarAcceptRange then
|
||||
local reccecoord = Unit:GetCoordinate()
|
||||
local coord = unit:GetCoordinate()
|
||||
local reccecoord = Unit:GetCoord()
|
||||
local coord = unit:GetCoord()
|
||||
local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km
|
||||
if dist > self.RadarAcceptRangeKilometers then DetectionAccepted = false end
|
||||
end
|
||||
|
||||
if self.RadarBlur then
|
||||
local reccecoord = Unit:GetCoordinate()
|
||||
local coord = unit:GetCoordinate()
|
||||
local reccecoord = Unit:GetCoord()
|
||||
local coord = unit:GetCoord()
|
||||
local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km
|
||||
local AGL = unit:GetAltitude(true)
|
||||
local minheight = self.RadarBlurMinHeight or 250 -- meters
|
||||
@@ -1202,6 +1337,33 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua
|
||||
end
|
||||
end
|
||||
|
||||
--- (Internal) Return the detected target groups of the controllable as a @{Core.Set#SET_GROUP}.
|
||||
-- @param #INTEL self
|
||||
-- @param Wrapper.Unit#UNIT Recce The unit detecting.
|
||||
-- @param #table DetectedUnits Table of detected units to be filled.
|
||||
-- @param #table RecceDetecting Table of recce per unit to be filled.
|
||||
-- @param Core.Zone#ZONE_GROUP detectionzone The zone where to look.
|
||||
function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone)
|
||||
local othercoalition = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
|
||||
self:T("Other coalition = "..othercoalition)
|
||||
if detectionzone then
|
||||
-- Get detected units
|
||||
local reccename = Recce:GetName()
|
||||
local DetectAccousticUnitTypes = self.DetectAccousticUnitTypes or {Unit.Category.HELICOPTER}
|
||||
detectionzone:Scan({Object.Category.UNIT},DetectAccousticUnitTypes)
|
||||
local unitset = detectionzone:GetScannedSetUnit(othercoalition) -- Core.Set#SET_UNIT
|
||||
self:T("Accoustic detection found #Units "..unitset:CountAlive())
|
||||
for _,_unit in pairs(unitset.Set or {}) do
|
||||
if _unit and _unit:IsAlive() and _unit:GetCoalition() ~= self.coalition then
|
||||
local name = _unit:GetName() or "none"
|
||||
DetectedUnits[name]=_unit
|
||||
RecceDetecting[name]=reccename
|
||||
self:T("Unit name = "..name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Events
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -2144,7 +2306,7 @@ function INTEL:GetClusterCoordinate(Cluster, Update)
|
||||
return Cluster.coordinate
|
||||
end
|
||||
|
||||
--- Check if the coorindate of the cluster changed.
|
||||
--- Check if the coordindate of the cluster changed.
|
||||
-- @param #INTEL self
|
||||
-- @param #INTEL.Cluster Cluster The cluster.
|
||||
-- @param #number Threshold in meters. Default 100 m.
|
||||
|
||||
@@ -447,7 +447,6 @@ OPSGROUP.TaskType={
|
||||
-- @field Wrapper.Marker#MARKER marker Marker on the F10 map.
|
||||
-- @field #string formation Ground formation. Similar to action but on/off road.
|
||||
-- @field #number missionUID Mission UID (Auftragsnr) this waypoint belongs to.
|
||||
-- @field Navigation.FlightPlan#FLIGHTPLAN flightplan Flightplan this waypoint belongs to.
|
||||
|
||||
--- Cargo Carrier status.
|
||||
-- @type OPSGROUP.CarrierStatus
|
||||
@@ -576,11 +575,6 @@ function OPSGROUP:New(group)
|
||||
|
||||
-- Set DCS group and controller.
|
||||
self.dcsgroup=self:GetDCSGroup()
|
||||
|
||||
if not self.dcsgroup then
|
||||
return
|
||||
end
|
||||
|
||||
self.controller=self.dcsgroup:getController()
|
||||
|
||||
-- Category.
|
||||
@@ -11417,9 +11411,9 @@ function OPSGROUP:_SimpleTaskFunction(Function, uid)
|
||||
return DCSTask
|
||||
end
|
||||
|
||||
--- Enhanced waypoint table.
|
||||
--- Enhance waypoint table.
|
||||
-- @param #OPSGROUP self
|
||||
-- @param #OPSGROUP.Waypoint waypoint Waypoint data.
|
||||
-- @param #OPSGROUP.Waypoint Waypoint data.
|
||||
-- @return #OPSGROUP.Waypoint Modified waypoint data.
|
||||
function OPSGROUP:_CreateWaypoint(waypoint)
|
||||
|
||||
@@ -12847,7 +12841,7 @@ function OPSGROUP:_UpdatePosition()
|
||||
self.positionLast=self.position or self:GetVec3()
|
||||
self.headingLast=self.heading or self:GetHeading()
|
||||
self.orientXLast=self.orientX or self:GetOrientationX()
|
||||
self.velocityLast=self.velocity or self.group:GetVelocityMPS()
|
||||
self.velocityLast=self.velocity or self.group:GetVelocityMPS()
|
||||
|
||||
-- Current state.
|
||||
self.position=self:GetVec3()
|
||||
@@ -13693,24 +13687,20 @@ end
|
||||
-- @return Core.Point#COORDINATE The coordinate of the object.
|
||||
function OPSGROUP:_CoordinateFromObject(Object)
|
||||
|
||||
env.info("FF coordfrom object")
|
||||
if Object then
|
||||
if VECTOR._IsVector(Object) then
|
||||
env.info("FF VECTOR")
|
||||
return Object:GetCoordinate()
|
||||
elseif Object:IsInstanceOf("COORDINATE") then
|
||||
if Object:IsInstanceOf("COORDINATE") then
|
||||
return Object
|
||||
else
|
||||
if Object:IsInstanceOf("POSITIONABLE") or Object:IsInstanceOf("ZONE_BASE") then
|
||||
self:E(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate")
|
||||
self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate")
|
||||
local coord=Object:GetCoordinate()
|
||||
return coord
|
||||
else
|
||||
self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!")
|
||||
self:T(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!")
|
||||
end
|
||||
end
|
||||
else
|
||||
self:E(self.lid.."ERROR: Object passed is nil!")
|
||||
self:T(self.lid.."ERROR: Object passed is nil!")
|
||||
end
|
||||
|
||||
return nil
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
-- ===
|
||||
-- @module Ops.PlayerTask
|
||||
-- @image OPS_PlayerTask.jpg
|
||||
-- @date Last Update Oct 2025
|
||||
-- @date Last Update Dec 2025
|
||||
|
||||
|
||||
do
|
||||
@@ -61,6 +61,7 @@ do
|
||||
-- @field #number PreviousCount
|
||||
-- @field #boolean CanSmoke
|
||||
-- @field #boolean ShowThreatDetails
|
||||
-- @field #boolean PersistMe
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
@@ -98,11 +99,12 @@ PLAYERTASK = {
|
||||
PreviousCount = 0,
|
||||
CanSmoke = true,
|
||||
ShowThreatDetails = true,
|
||||
PersistMe = false,
|
||||
}
|
||||
|
||||
--- PLAYERTASK class version.
|
||||
-- @field #string version
|
||||
PLAYERTASK.version="0.1.29"
|
||||
PLAYERTASK.version="0.1.31"
|
||||
|
||||
--- Generic task condition.
|
||||
-- @type PLAYERTASK.Condition
|
||||
@@ -235,7 +237,7 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType)
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #boolen Silent If true, suppress message output on cancel.
|
||||
-- @param #boolean Silent If true, suppress message output on cancel.
|
||||
|
||||
--- On After "Planned" event. Task has been planned.
|
||||
-- @function [parent=#PLAYERTASK] OnAfterPilotPlanned
|
||||
@@ -401,8 +403,17 @@ function PLAYERTASK:CanJoinTask(Group, Client)
|
||||
return true
|
||||
end
|
||||
|
||||
--- [User] Set this task for persistance, if persistance is enabled on the PLAYERTASKCONTROLLER instance.
|
||||
-- @param #PLAYERTASK self
|
||||
-- @return #PLAYERTASK self
|
||||
function PLAYERTASK:EnablePersistance()
|
||||
self.PersistMe = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Add a PLAYERTASKCONTROLLER for this task
|
||||
-- @param #PLAYERTASK self
|
||||
--
|
||||
-- @param Ops.PlayerTask#PLAYERTASKCONTROLLER Controller
|
||||
-- @return #PLAYERTASK self
|
||||
function PLAYERTASK:_SetController(Controller)
|
||||
@@ -1088,7 +1099,6 @@ function PLAYERTASK:onafterStatus(From, Event, To)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- [Internal] On after progress call
|
||||
-- @param #PLAYERTASK self
|
||||
-- @param #string From
|
||||
@@ -1363,6 +1373,12 @@ do
|
||||
-- @field Core.ClientMenu#CLIENTMENU MenuNoTask
|
||||
-- @field #boolean InformationMenu Show Radio Info Menu
|
||||
-- @field #number TaskInfoDuration How long to show the briefing info on the screen
|
||||
-- @field #table TaskPersistance Table for persistance data
|
||||
-- @field #boolean TaskPersistanceSwitch Switch for persisting tasks
|
||||
-- @field #string TaskPersistancePath File path for persisting tasks
|
||||
-- @field #string TaskPersistanceFilename File name for persisting tasks
|
||||
-- @field #table TasksPersistable List of persistable tasks
|
||||
-- @field #number SceneryExplosivesAmount Kgs of TNT to explode scenery on task persistance loading
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
---
|
||||
@@ -1583,6 +1599,7 @@ do
|
||||
-- ELEVATION = "\nTarget Elevation: %s %s",
|
||||
-- METER = "meter",
|
||||
-- FEET = "feet",
|
||||
-- INTERCEPTCOURSE = "Intercept course",
|
||||
-- },
|
||||
--
|
||||
-- e.g.
|
||||
@@ -1669,8 +1686,28 @@ do
|
||||
--
|
||||
-- Set a marker on the map and add the following text to create targets from it: "TARGET". This is effectively the same as adding a COORDINATE object as target.
|
||||
-- The marker can be deleted any time.
|
||||
--
|
||||
-- ## 9 Discussion
|
||||
--
|
||||
-- ## 9 Single Task Persistence for mission designer added tasks
|
||||
--
|
||||
-- The class can persist the state of single tasks of type BOMBING, PRECISIONBOMBING, ARTY and SEAD, i.e. tasks which have a GROUND(!) GROUP, UNIT, STATIC or SCENERY as target.
|
||||
-- This requires the task to have a unique(!) menu name set, a TARGET which already exists on the map at mission start(!), and a flag that this task is actually to be persisted.
|
||||
-- Also, you need to desanitize the mission scripting environment, i.e. "lfs" and "io" must be available so we can write to disk.
|
||||
--
|
||||
-- -- First, we need to enable on the PLAYERTASKCONTROLLER itself
|
||||
-- taskmanager:EnableTaskPersistance([[C:\Users\myname\Saved Games\DCS\Missions\MyMisionFolder\]],"Mission Tasks.csv") -- Path and Filename
|
||||
--
|
||||
-- -- Then, we can design a task marking mission progress that we want to persist
|
||||
-- local RussianRadios = SET_STATIC:New():FilterPrefixes("Comms Tower Russia"):FilterOnce()
|
||||
--
|
||||
-- local RadioTask = PLAYERTASK:New(AUFTRAG.Type.BOMBING,RussianRadios,true,5,"Bombing")
|
||||
-- RadioTask:SetMenuName("Neutralize Comms Towers") -- UNIQUE menu name so we can find the task later!
|
||||
-- RadioTask:AddFreetext("Find and neutralize the two communication towers near NB70 East of Fulda on Streufelsberg!")
|
||||
-- RadioTask:AddFreetextTTS("Find and neutralize the two communication towers naer N;B;7;zero; East of Fulda on Streufelsberg!")
|
||||
-- RadioTask:EnablePersistance() -- Enable persistence for this task
|
||||
--
|
||||
-- taskmanager:AddPlayerTaskToQueue(RadioTask,true,false)
|
||||
--
|
||||
-- ## 10 Discussion
|
||||
--
|
||||
-- If you have questions or suggestions, please visit the [MOOSE Discord](https://discord.gg/AeYAkHP) #ops-playertask channel.
|
||||
--
|
||||
@@ -1720,6 +1757,12 @@ PLAYERTASKCONTROLLER = {
|
||||
MenuNoTask = nil,
|
||||
InformationMenu = false,
|
||||
TaskInfoDuration = 30,
|
||||
TaskPersistance = {},
|
||||
TaskPersistanceSwitch = false,
|
||||
TaskPersistancePath = nil,
|
||||
TaskPersistanceFilename = nil,
|
||||
TasksPersistable = {},
|
||||
SceneryExplosivesAmount = 300,
|
||||
}
|
||||
|
||||
---
|
||||
@@ -1740,6 +1783,7 @@ AUFTRAG.Type.PRECISIONBOMBING = "Precision Bombing"
|
||||
AUFTRAG.Type.CTLD = "Combat Transport"
|
||||
AUFTRAG.Type.CSAR = "Combat Rescue"
|
||||
AUFTRAG.Type.CONQUER = "Conquer"
|
||||
|
||||
---
|
||||
-- @type Scores
|
||||
PLAYERTASKCONTROLLER.Scores = {
|
||||
@@ -1759,6 +1803,24 @@ PLAYERTASKCONTROLLER.Scores = {
|
||||
[AUFTRAG.Type.CAP] = 100,
|
||||
[AUFTRAG.Type.CAPTUREZONE] = 100,
|
||||
}
|
||||
|
||||
---
|
||||
-- @type TasksPersistable
|
||||
PLAYERTASKCONTROLLER.TasksPersistable = {
|
||||
[AUFTRAG.Type.PRECISIONBOMBING] = true,
|
||||
[AUFTRAG.Type.BOMBING] = true,
|
||||
[AUFTRAG.Type.ARTY] = true,
|
||||
[AUFTRAG.Type.SEAD] = true,
|
||||
}
|
||||
|
||||
---
|
||||
-- @type PersistenceData
|
||||
-- @field #number ID
|
||||
-- @field #string Name
|
||||
-- @field #string Type
|
||||
-- @field #number InitialTargets
|
||||
-- @field #number Targetsleft
|
||||
-- @field #boolean updated
|
||||
|
||||
---
|
||||
-- @type SeadAttributes
|
||||
@@ -1860,6 +1922,7 @@ PLAYERTASKCONTROLLER.Messages = {
|
||||
DESTROYER = "Destroyer",
|
||||
CARRIER = "Aircraft Carrier",
|
||||
RADIOS = "Radios",
|
||||
INTERCEPTCOURSE = "Intercept course",
|
||||
},
|
||||
DE = {
|
||||
TASKABORT = "Auftrag abgebrochen!",
|
||||
@@ -1947,12 +2010,13 @@ PLAYERTASKCONTROLLER.Messages = {
|
||||
DESTROYER = "Zerstörer",
|
||||
CARRIER = "Flugzeugträger",
|
||||
RADIOS = "Frequenzen",
|
||||
INTERCEPTCOURSE = "Abfangkurs",
|
||||
},
|
||||
}
|
||||
|
||||
--- PLAYERTASK class version.
|
||||
-- @field #string version
|
||||
PLAYERTASKCONTROLLER.version="0.1.71"
|
||||
PLAYERTASKCONTROLLER.version="0.1.73"
|
||||
|
||||
--- Create and run a new TASKCONTROLLER instance.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
@@ -2059,7 +2123,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
|
||||
self:AddTransition("*", "Stop", "Stopped")
|
||||
|
||||
self:__Start(2)
|
||||
local starttime = math.random(5,10)
|
||||
local starttime = math.random(10,15)
|
||||
self:__Status(starttime)
|
||||
|
||||
self:I(self.lid..self.version.." Started.")
|
||||
@@ -2174,6 +2238,30 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
|
||||
|
||||
end
|
||||
|
||||
--- [User] Enable Task persistance (for specific gound target tasks)
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param #string Path Path where to save the task data
|
||||
-- @param #string Filename File name under which to save the task data
|
||||
-- @param #number KgsOfTNT (Optional) Explosives kgs used to remove scenery for persistence, defaults to 300
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:EnableTaskPersistance(Path,Filename,KgsOfTNT)
|
||||
self.TaskPersistanceSwitch = true
|
||||
self.TaskPersistancePath = Path
|
||||
self.TaskPersistanceFilename = Filename
|
||||
self.SceneryExplosivesAmount = KgsOfTNT or 300
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Disable Task persistance (for specific gound target tasks)
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:DisableTaskPersistance()
|
||||
self.TaskPersistanceSwitch = false
|
||||
self.TaskPersistancePath = nil
|
||||
self.TaskPersistanceFilename = nil
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set or create a SCORING object for this taskcontroller
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Functional.Scoring#SCORING Scoring (optional) the Scoring object
|
||||
@@ -2861,15 +2949,6 @@ function PLAYERTASKCONTROLLER:_GetTasksPerType()
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
for _type,_data in pairs(tasktypes) do
|
||||
self:I("Task Type: ".._type)
|
||||
for _id,_task in pairs(_data) do
|
||||
self:I("Task Name: ".._task.Target:GetName())
|
||||
end
|
||||
end
|
||||
--]]
|
||||
|
||||
return tasktypes
|
||||
end
|
||||
|
||||
@@ -3566,9 +3645,9 @@ function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask,Silent,TaskFilter)
|
||||
PlayerTask:_SetController(self)
|
||||
PlayerTask:SetCoalition(self.Coalition)
|
||||
self.TaskQueue:Push(PlayerTask)
|
||||
if not Silent then
|
||||
self:__TaskAdded(10,PlayerTask)
|
||||
end
|
||||
--if not Silent then
|
||||
self:__TaskAdded(10,PlayerTask,Silent)
|
||||
--end
|
||||
else
|
||||
self:E(self.lid.."***** NO valid PAYERTASK object sent!")
|
||||
end
|
||||
@@ -3700,6 +3779,41 @@ function PLAYERTASKCONTROLLER:_ShowRadioInfo(Group, Client)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Calculate group future position after given seconds.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Wrapper.Group#GROUP group The group to calculate for.
|
||||
-- @param #number seconds Time interval in seconds. Default is `self.prediction`.
|
||||
-- @return Core.Point#COORDINATE Calculated future position of the cluster.
|
||||
function PLAYERTASKCONTROLLER:_CalcGroupFuturePosition(group, seconds)
|
||||
|
||||
-- Get current position of the cluster.
|
||||
local p=group:GetCoordinate()
|
||||
|
||||
-- Velocity vector in m/s.
|
||||
local v=group:GetVelocityVec3()
|
||||
|
||||
-- Time in seconds.
|
||||
local t=seconds or self.prediction
|
||||
|
||||
-- Extrapolated vec3.
|
||||
local Vec3={x=p.x+v.x*t, y=p.y+v.y*t, z=p.z+v.z*t}
|
||||
|
||||
-- Future position.
|
||||
local futureposition=COORDINATE:NewFromVec3(Vec3)
|
||||
|
||||
-- Create an arrow pointing in the direction of the movement.
|
||||
if self.verbose == true then
|
||||
local markerID = group:GetProperty("PLAYERTASK_ARROW")
|
||||
if markerID then
|
||||
COORDINATE:RemoveMark(markerID)
|
||||
end
|
||||
markerID = p:ArrowToAll(futureposition, self.coalition, {1,0,0}, 1, {1,1,0}, 0.5, 2, true, "Position Calc")
|
||||
group:SetProperty("PLAYERTASK_ARROW",markerID)
|
||||
end
|
||||
|
||||
return futureposition
|
||||
end
|
||||
|
||||
--- [Internal] Flashing directional info for a client
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
@@ -3709,16 +3823,34 @@ function PLAYERTASKCONTROLLER:_FlashInfo()
|
||||
if _client and _client:IsAlive() then
|
||||
if self.TasksPerPlayer:HasUniqueID(_playername) then
|
||||
local task = self.TasksPerPlayer:ReadByID(_playername) -- Ops.PlayerTask#PLAYERTASK
|
||||
local Coordinate = task.Target:GetCoordinate()
|
||||
local Coordinate = task.Target:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local CoordText = ""
|
||||
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then
|
||||
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then
|
||||
CoordText = Coordinate:ToStringA2G(_client, nil, self.ShowMagnetic)
|
||||
local targettxt = self.gettext:GetEntry("TARGET",self.locale)
|
||||
local text = targettxt..": "..CoordText
|
||||
local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client)
|
||||
else
|
||||
CoordText = Coordinate:ToStringA2A(_client, nil, self.ShowMagnetic)
|
||||
local targettxt = self.gettext:GetEntry("TARGET",self.locale)
|
||||
local text = targettxt..": "..CoordText
|
||||
-- calc intercept position
|
||||
local name=task.Target:GetName()
|
||||
local group = GROUP:FindByName(name)
|
||||
local clientcoord = _client:GetCoordinate()
|
||||
if group and clientcoord and group:IsAlive() and task.Type==AUFTRAG.Type.INTERCEPT then
|
||||
local speed = math.max(UTILS.KnotsToMps(350) or _client:GetVelocityMPS())
|
||||
local dist = Coordinate:Get3DDistance(clientcoord)
|
||||
local iTime = math.floor(dist/speed)+5
|
||||
if iTime < 10 then iTime = 10
|
||||
elseif iTime > 600 then iTime = 600 end
|
||||
local npos = self:_CalcGroupFuturePosition(group,iTime)
|
||||
local BR = npos:ToStringBearing(clientcoord,nil,self.ShowMagnetic,0 )
|
||||
local Intercepttext = self.gettext:GetEntry("INTERCEPTCOURSE",self.locale)
|
||||
text = text .. "\n"..Intercepttext.." "..BR
|
||||
end
|
||||
local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client)
|
||||
end
|
||||
local targettxt = self.gettext:GetEntry("TARGET",self.locale)
|
||||
local text = "Target: "..CoordText
|
||||
local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3770,7 +3902,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
|
||||
local CoordTextLLDM = nil
|
||||
local ShowThreatInfo = task.ShowThreatDetails
|
||||
local LasingDrone = self:_FindLasingDroneForTaskID(task.PlayerTaskNr)
|
||||
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then
|
||||
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then
|
||||
CoordText = Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic)
|
||||
else
|
||||
CoordText = Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic)
|
||||
@@ -3807,8 +3939,10 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
|
||||
Elevation = math.floor(UTILS.MetersToFeet(Elevation))
|
||||
end
|
||||
-- ELEVATION = "\nTarget Elevation: %s %s",
|
||||
local elev = self.gettext:GetEntry("ELEVATION",self.locale)
|
||||
text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure)
|
||||
if task.Type ~= AUFTRAG.Type.INTERCEPT then
|
||||
local elev = self.gettext:GetEntry("ELEVATION",self.locale)
|
||||
text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure)
|
||||
end
|
||||
-- Prec bombing
|
||||
if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
|
||||
if LasingDrone and LasingDrone.playertask then
|
||||
@@ -4584,6 +4718,76 @@ function PLAYERTASKCONTROLLER:RemoveConflictZone(ConflictZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Add an corridor zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Core.Zone#ZONE CorridorZone Add a zone to the corridor zone set.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:AddCorridorZone(CorridorZone)
|
||||
self:T(self.lid.."AddCorridorZone")
|
||||
if self.Intel then
|
||||
self.Intel:AddCorridorZone(CorridorZone)
|
||||
else
|
||||
self:E(self.lid.."*****NO detection has been set up (yet)!")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Add an corridor SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Core.Set#SET_ZONE CorridorZoneSet Add a SET_ZONE to the corridor zone set.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:AddCorridorZoneSet(CorridorZoneSet)
|
||||
self:T(self.lid.."AddCorridorZoneSet")
|
||||
if self.Intel then
|
||||
self.Intel.corridorzoneset:AddSet(CorridorZoneSet)
|
||||
else
|
||||
self:E(self.lid.."*****NO detection has been set up (yet)!")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Remove an corridor zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Core.Zone#ZONE CorridorZone Remove this zone from the corridor zone set.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:RemoveCorridorZone(CorridorZone)
|
||||
self:T(self.lid.."RemoveCorridorZone")
|
||||
if self.Intel then
|
||||
self.Intel:RemoveCorridorZone(CorridorZone)
|
||||
else
|
||||
self:E(self.lid.."*****NO detection has been set up (yet)!")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in FEET.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param #number Floor Floor altitude ASL in feet.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in feet.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeiling(Floor,Ceiling)
|
||||
if self.Intel then
|
||||
self.Intel:SetCorridorLimitsFeet(Floor,Ceiling)
|
||||
else
|
||||
self:E(self.lid.."*****NO detection has been set up (yet)!")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set corridor zone floor and ceiling in METERS.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param #number Floor Floor altitude ASL in meters.
|
||||
-- @param #number Ceiling Ceiling altitude ASL in meters.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling)
|
||||
if self.Intel then
|
||||
self.Intel:SetCorridorLimits(Floor,Ceiling)
|
||||
else
|
||||
self:E(self.lid.."*****NO detection has been set up (yet)!")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set the top menu name to a custom string.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param #string Name The name to use as the top menu designation.
|
||||
@@ -4765,6 +4969,128 @@ function PLAYERTASKCONTROLLER:SetSRSBroadcast(Frequency,Modulation)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Ops.PlayerTask#PLASERTASK Task
|
||||
-- @param #number TargetsLeft
|
||||
function PLAYERTASKCONTROLLER:_UpdateTargetsAlive(Task,TargetsLeft)
|
||||
self:T(self.lid.."_UpdateTargetsAlive")
|
||||
local delta = Task.Target:CountTargets() - TargetsLeft
|
||||
if delta > 0 then
|
||||
self:T("Delta targets to be removed: "..delta)
|
||||
local count = 0
|
||||
local targets = Task.Target:GetObjects()
|
||||
for _,_object in pairs(targets or {}) do
|
||||
if _object and _object.ClassName and (_object:IsInstanceOf("GROUP") or _object:IsInstanceOf("UNIT") or _object:IsInstanceOf("STATIC") or _object:IsInstanceOf("SCENERY")) then
|
||||
if count < delta then
|
||||
count = count + 1
|
||||
if not _object:IsInstanceOf("SCENERY") then
|
||||
_object:Destroy(true)
|
||||
else
|
||||
_object:Explode(self.SceneryExplosivesAmount)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:_LoadTasksPersisted()
|
||||
self:T(self.lid.."_LoadTasksPersisted")
|
||||
|
||||
local function MatchTask(Type,Name)
|
||||
local foundtask
|
||||
self.TaskQueue:ForEach(
|
||||
function(_task)
|
||||
local task = _task -- #PLAYERTASK
|
||||
if task.Type == Type and task.Target.name and task.Target.name == Name then
|
||||
foundtask = task
|
||||
end
|
||||
end
|
||||
)
|
||||
return foundtask
|
||||
end
|
||||
|
||||
if lfs and io then
|
||||
local ok,data = UTILS.LoadFromFile(self.TaskPersistancePath,self.TaskPersistanceFilename)
|
||||
if ok == true then
|
||||
table.remove(data, 1)
|
||||
for _,_entry in pairs(data) do
|
||||
-- "--ID;;Name;;InitialTargets;;Targetsleft;;Type\n"
|
||||
local dataset = UTILS.Split(_entry,";;")
|
||||
local Taskdata = {} -- #PersistenceData
|
||||
Taskdata.ID = tonumber(dataset[1])
|
||||
Taskdata.Name = tostring(dataset[2])
|
||||
Taskdata.InitialTargets = tonumber(dataset[3])
|
||||
Taskdata.Targetsleft = tonumber(dataset[4])
|
||||
Taskdata.Type = tostring(dataset[5])
|
||||
Taskdata.Task = MatchTask(Taskdata.Type,Taskdata.Name)
|
||||
if Taskdata.Task == nil then
|
||||
self:E(self.lid.."No actual task found for "..Taskdata.Name)
|
||||
else
|
||||
self:T(self.lid.."Task loaded and match found for "..Taskdata.Name)
|
||||
end
|
||||
Taskdata.updated = Taskdata.InitialTargets == Taskdata.Targetsleft and true or false
|
||||
if Taskdata.Task and Taskdata.updated == false then
|
||||
self:_UpdateTargetsAlive(Taskdata.Task,Taskdata.Targetsleft)
|
||||
Taskdata.updated = true
|
||||
end
|
||||
self.TaskPersistance[Taskdata.ID] = Taskdata
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Clear persisted data on disk.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:ClearPersistedData()
|
||||
if lfs and io then
|
||||
local text = "-- Data Cleared\n"
|
||||
UTILS.SaveToFile(self.TaskPersistancePath,self.TaskPersistanceFilename,text)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:_SaveTasksPersisted()
|
||||
if lfs and io then
|
||||
local text = "--ID;;Name;;InitialTargets;;Targetsleft;;Type\n"
|
||||
for _,_data in pairs(self.TaskPersistance) do
|
||||
local data = _data -- #PersistenceData
|
||||
data.Targetsleft = data.Task.Target:CountTargets() -- recount
|
||||
if data.Task and data.Task:IsDone() then data.Targetsleft = 0 end
|
||||
local tasktext = string.format("%d;;%s;;%d;;%d;;%s\n",data.ID,data.Name,data.InitialTargets,data.Targetsleft,data.Type)
|
||||
text = text..tasktext
|
||||
end
|
||||
UTILS.SaveToFile(self.TaskPersistancePath,self.TaskPersistanceFilename,text)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param #PLAYERTASK Task
|
||||
function PLAYERTASKCONTROLLER:_AddPersistenceData(Task)
|
||||
local Taskdata = {} -- #PersistenceData
|
||||
if not self.TaskPersistance[Task.PlayerTaskNr] then
|
||||
Taskdata.ID = Task.PlayerTaskNr
|
||||
Taskdata.Name = Task.Target.name or "none"
|
||||
Taskdata.InitialTargets = Task.Target:CountTargets()
|
||||
Taskdata.Targetsleft = Taskdata.InitialTargets
|
||||
Taskdata.Type = Task.Type
|
||||
Taskdata.updated = true
|
||||
Taskdata.Task = Task
|
||||
self.TaskPersistance[Task.PlayerTaskNr] = Taskdata
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions PLAYERTASKCONTROLLER
|
||||
-- TODO: FSM Functions PLAYERTASKCONTROLLER
|
||||
@@ -4788,7 +5114,12 @@ function PLAYERTASKCONTROLLER:onafterStart(From, Event, To)
|
||||
self:HandleEvent(EVENTS.PilotDead, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.UnitLost, self._EventHandler)
|
||||
self:SetEventPriority(5)
|
||||
self:SetEventPriority(5)
|
||||
-- Persistence
|
||||
if self.TaskPersistanceSwitch == true then
|
||||
self:ScheduleOnce(5,self._LoadTasksPersisted,self)
|
||||
--self:_LoadTasksPersisted()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -4828,6 +5159,11 @@ function PLAYERTASKCONTROLLER:onafterStatus(From, Event, To)
|
||||
self:I(text)
|
||||
end
|
||||
|
||||
-- Persistence
|
||||
if self.TaskPersistanceSwitch == true then
|
||||
self:_SaveTasksPersisted()
|
||||
end
|
||||
|
||||
if self:GetState() ~= "Stopped" then
|
||||
self:__Status(-30)
|
||||
end
|
||||
@@ -4983,19 +5319,26 @@ end
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Ops.PlayerTask#PLAYERTASK Task
|
||||
-- @param #boolean Silent
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:onafterTaskAdded(From, Event, To, Task)
|
||||
function PLAYERTASKCONTROLLER:onafterTaskAdded(From, Event, To, Task, Silent)
|
||||
self:T({From, Event, To})
|
||||
self:T(self.lid.."TaskAdded")
|
||||
local addtxt = self.gettext:GetEntry("TASKADDED",self.locale)
|
||||
local taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.Type))
|
||||
if not self.NoScreenOutput then
|
||||
self:_SendMessageToClients(taskname,15)
|
||||
--local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition)
|
||||
if not Silent then
|
||||
if not self.NoScreenOutput then
|
||||
self:_SendMessageToClients(taskname,15)
|
||||
--local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition)
|
||||
end
|
||||
if self.UseSRS then
|
||||
taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.TTSType))
|
||||
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
|
||||
end
|
||||
end
|
||||
if self.UseSRS then
|
||||
taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.TTSType))
|
||||
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
|
||||
self:T(self.lid..string.format("Pers = %s | Type = %s | TypePers = %s | TaskFlag = %s",tostring(self.TaskPersistanceSwitch),tostring(Task.Type),tostring(self.TasksPersistable[Task.Type]),tostring(Task.PersistMe)))
|
||||
if self.TaskPersistanceSwitch == true and self.TasksPersistable[Task.Type] == true and Task.PersistMe == true then
|
||||
self:_AddPersistenceData(Task)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -310,7 +310,7 @@ _RECOVERYTANKERID=0
|
||||
|
||||
--- Class version.
|
||||
-- @field #string version
|
||||
RECOVERYTANKER.version="1.0.10"
|
||||
RECOVERYTANKER.version="1.0.11"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -923,6 +923,22 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
|
||||
Spawn:InitRadioCommsOnOff(true)
|
||||
Spawn:InitRadioFrequency(self.RadioFreq)
|
||||
Spawn:InitRadioModulation(self.RadioModu)
|
||||
|
||||
if self.callsignname and self.callsignnumber then
|
||||
local grp = GROUP:FindByName(self.tankergroupname)
|
||||
if grp then
|
||||
local typename = grp:GetTypeName() or ""
|
||||
--self:I(self.lid.."Typename: "..typename)
|
||||
local Name
|
||||
local enumerator = CALLSIGN.Tanker
|
||||
if typename == "A6E" then
|
||||
enumerator = CALLSIGN.Intruder
|
||||
end
|
||||
Name = self:_GetCallsignName(self.callsignname,enumerator)
|
||||
--self:I(self.lid.."CallsignName: "..Name)
|
||||
Spawn:InitCallSign(self.callsignname,Name,self.callsignnumber,self.callsignnumber)
|
||||
end
|
||||
end
|
||||
Spawn:InitModex(self.modex)
|
||||
|
||||
-- Spawn on carrier.
|
||||
@@ -1190,9 +1206,9 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To)
|
||||
-- Task combo.
|
||||
|
||||
-- Be a tanker or be an AWACS.
|
||||
local taskroll = self.tanker:EnRouteTaskTanker()
|
||||
local taskrole = self.tanker:EnRouteTaskTanker()
|
||||
if self.awacs then
|
||||
taskroll=self.tanker:EnRouteTaskAWACS()
|
||||
taskrole=self.tanker:EnRouteTaskAWACS()
|
||||
end
|
||||
|
||||
--local taskeplrs=self.tanker:TaskEPLRS(true, 2)
|
||||
@@ -1201,7 +1217,7 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To)
|
||||
local taskroute = self.tanker:TaskRoute(wp)
|
||||
|
||||
-- Note that the order is important here! tasktanker has to come first. Otherwise it does not work.
|
||||
local taskcombo = self.tanker:TaskCombo({taskroll, taskroute})
|
||||
local taskcombo = self.tanker:TaskCombo({taskrole, taskroute})
|
||||
|
||||
-- Set task.
|
||||
self.tanker:SetTask(taskcombo, 1)
|
||||
@@ -1450,6 +1466,20 @@ function RECOVERYTANKER:_RefuelingStop(EventData)
|
||||
|
||||
end
|
||||
|
||||
--- Get clear name callsign for spawn from enumerator
|
||||
-- @param #RECOVERYTANKER self
|
||||
-- @param #number Callsign
|
||||
-- @param #table Enumerator The table of callsigns, e.g. `CALLSIGN.Tanker`
|
||||
-- @return #string Name Name or "" if not found
|
||||
function RECOVERYTANKER:_GetCallsignName(Callsign, Enumerator)
|
||||
for name, value in pairs(Enumerator or {}) do
|
||||
if value==Callsign then
|
||||
return name
|
||||
end
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
--- A unit crashed or died.
|
||||
-- @param #RECOVERYTANKER self
|
||||
-- @param Core.Event#EVENTDATA EventData Event data.
|
||||
|
||||
@@ -884,7 +884,7 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
||||
else
|
||||
|
||||
-- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine.
|
||||
local Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias)
|
||||
Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias)
|
||||
|
||||
-- Set modex for spawn.
|
||||
Spawn:InitModex(self.modex)
|
||||
@@ -933,8 +933,8 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
||||
self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!", self.helogroupname))
|
||||
return
|
||||
end
|
||||
|
||||
elseif UsesAliveGroup==false then
|
||||
end
|
||||
elseif UsesAliveGroup==false then
|
||||
|
||||
-- Spawn at airbase.
|
||||
self.helo=Spawn:SpawnAtAirbase(self.airbase, self.takeoff, nil, AIRBASE.TerminalType.HelicopterUsable)
|
||||
@@ -948,9 +948,8 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
||||
delay=60
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
-- Set of group(s) to follow Mother.
|
||||
self.followset=SET_GROUP:New()
|
||||
|
||||
@@ -515,7 +515,8 @@ ENUMS.ReportingName =
|
||||
Atlas = "A400",
|
||||
Lancer = "B1-B",
|
||||
Stratofortress = "B-52H",
|
||||
Hercules = "C-130",
|
||||
Herc = "C-130",
|
||||
Hercules = "C-130J-30",
|
||||
Super_Hercules = "Hercules",
|
||||
Globemaster = "C-17",
|
||||
Greyhound = "C-2A",
|
||||
@@ -528,6 +529,7 @@ ENUMS.ReportingName =
|
||||
Orion = "P-3C",
|
||||
Viking = "S-3B",
|
||||
Osprey = "V-22",
|
||||
Intruder = "A6E",
|
||||
-- Bomber Rus
|
||||
Badger = "H6-J",
|
||||
Bear_J = "Tu-142", -- also Tu-95
|
||||
@@ -1772,6 +1774,297 @@ ENUMS.Storage.weapons.adapters.lau105 = 'weapons.adapters.lau-105'
|
||||
ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_WHITE = 'weapons.containers.{US_M10_SMOKE_TANK_WHITE}'
|
||||
ENUMS.Storage.weapons.bombs.Mk_82 = 'weapons.bombs.Mk_82'
|
||||
ENUMS.Storage.weapons.adapters.BRU42_LS_SUU25 = 'weapons.adapters.BRU-42_LS_(SUU-25)'
|
||||
ENUMS.Storage.weapons.missiles.Aster_30_Blk_1 = 'weapons.missiles.Aster 30 Blk 1'
|
||||
ENUMS.Storage.weapons.missiles.Aster_30_Blk_1NT = 'weapons.missiles.Aster 30 Blk 1NT'
|
||||
ENUMS.Storage.weapons.missiles.Aster_30_Blk_2 = 'weapons.missiles.Aster 30 Blk 2'
|
||||
ENUMS.Storage.weapons.missiles.SA9M83M = 'weapons.missiles.SA9M83M'
|
||||
ENUMS.Storage.weapons.gunmounts.C130_M4_Rifle = 'weapons.gunmounts.C130_M4_Rifle'
|
||||
ENUMS.Storage.weapons.gunmounts.C130_M18_Sidearm_ = 'weapons.gunmounts.{C130-M18-Sidearm}'
|
||||
ENUMS.Storage.weapons.gunmounts.C130_Cargo_Bay_M4 = 'weapons.gunmounts.{C130-Cargo-Bay-M4}'
|
||||
ENUMS.Storage.weapons.gunmounts.C130_M18_Sidearm = 'weapons.gunmounts.C130_M18_Sidearm'
|
||||
ENUMS.Storage.weapons.droptanks.C130J_Ext_Tank_R = 'weapons.droptanks.C130J_Ext_Tank_R'
|
||||
ENUMS.Storage.weapons.droptanks.C130J_Ext_Tank_L = 'weapons.droptanks.C130J_Ext_Tank_L'
|
||||
ENUMS.Storage.weapons.missiles.SAHQ2 = 'weapons.missiles.SAHQ2'
|
||||
ENUMS.Storage.weapons.missiles.Strela_2 = 'weapons.missiles.Strela-2'
|
||||
ENUMS.Storage.weapons.missiles.Strela_2M = 'weapons.missiles.Strela-2M'
|
||||
ENUMS.Storage.weapons.missiles.Strela_3 = 'weapons.missiles.Strela-3'
|
||||
ENUMS.Storage.weapons.missiles.SA9M83 = 'weapons.missiles.SA9M83'
|
||||
ENUMS.Storage.weapons.missiles.SAV601P = 'weapons.missiles.SAV601P'
|
||||
ENUMS.Storage.weapons.missiles.SA2V759 = 'weapons.missiles.SA2V759'
|
||||
ENUMS.Storage.weapons.missiles.SA9M317 = 'weapons.missiles.SA9M317'
|
||||
ENUMS.Storage.weapons.missiles.SA9M82M = 'weapons.missiles.SA9M82M'
|
||||
ENUMS.Storage.weapons.missiles.SA9M82 = 'weapons.missiles.SA9M82'
|
||||
ENUMS.Storage.weapons.missiles.Igla_S = 'weapons.missiles.Igla_S'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.{AKAN_NO_TRC}'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.{AKAN}'
|
||||
ENUMS.Storage.weapons.shells.M882_9x19 = 'weapons.shells.9x19_m882'
|
||||
ENUMS.Storage.weapons.adapters.HB_F_4E_ORD_LAU_77 = 'weapons.adapters.HB_F-4E_ORD_LAU_77'
|
||||
ENUMS.Storage.weapons.adapters.hb_a_6e_lau7_adu299 = 'weapons.adapters.hb_a-6e_lau7_adu299'
|
||||
|
||||
ENUMS.Storage.weapons.bombs.AH6_SMOKE_BLUE = 'weapons.bombs.AH6_SMOKE_BLUE'
|
||||
ENUMS.Storage.weapons.bombs.AH6_SMOKE_GREEN = 'weapons.bombs.AH6_SMOKE_GREEN'
|
||||
ENUMS.Storage.weapons.bombs.AH6_SMOKE_RED = 'weapons.bombs.AH6_SMOKE_RED'
|
||||
ENUMS.Storage.weapons.bombs.AH6_SMOKE_YELLOW = 'weapons.bombs.AH6_SMOKE_YELLOW'
|
||||
|
||||
ENUMS.Storage.weapons.missiles.HB_AGM_78 = 'weapons.missiles.HB_AGM_78'
|
||||
ENUMS.Storage.weapons.missiles.V_1 = 'weapons.missiles.V-1'
|
||||
|
||||
ENUMS.Storage.weapons.shells.Oerlikon_20mm_HE = 'weapons.shells.Oerlikon_20mm_HE'
|
||||
ENUMS.Storage.weapons.shells.RM_15cm_HE = 'weapons.shells.RM_15cm_HE'
|
||||
ENUMS.Storage.weapons.shells.HE_T_MkII_40mm = 'weapons.shells.HE_T_MkII_40mm'
|
||||
ENUMS.Storage.weapons.shells.APCBC = 'weapons.shells.APCBC'
|
||||
ENUMS.Storage.weapons.shells.Sprgr_43_L71 = 'weapons.shells.Sprgr_43_L71'
|
||||
ENUMS.Storage.weapons.shells.Mk_20_HE_shell = 'weapons.shells.Mk_20_HE_shell'
|
||||
ENUMS.Storage.weapons.shells.Pzgr_39_42 = 'weapons.shells.Pzgr_39/42'
|
||||
ENUMS.Storage.weapons.shells.M1_37mm_37AP_T = 'weapons.shells.M1_37mm_37AP-T'
|
||||
ENUMS.Storage.weapons.shells.Sprgr_38 = 'weapons.shells.Sprgr_38'
|
||||
ENUMS.Storage.weapons.shells.Sprgr_39 = 'weapons.shells.Sprgr_39'
|
||||
ENUMS.Storage.weapons.shells.Pzgr_39_43 = 'weapons.shells.Pzgr_39/43'
|
||||
ENUMS.Storage.weapons.shells.Pzgr_39_5cm = 'weapons.shells.Pzgr_39_5cm'
|
||||
ENUMS.Storage.weapons.shells.AP_2A20_115mm = 'weapons.shells.2A20_115mm_AP'
|
||||
ENUMS.Storage.weapons.shells.AP_T_MkI_40mm = 'weapons.shells.AP_T_MkI_40mm'
|
||||
ENUMS.Storage.weapons.shells.AP_37x263 = 'weapons.shells.37x263_AP'
|
||||
ENUMS.Storage.weapons.shells.AP_20x138B = 'weapons.shells.20x138B_AP'
|
||||
ENUMS.Storage.weapons.shells.Besa7_92x57T = 'weapons.shells.Besa7_92x57T'
|
||||
ENUMS.Storage.weapons.shells.Sprgr_34_L70 = 'weapons.shells.Sprgr_34_L70'
|
||||
ENUMS.Storage.weapons.shells.QF94_AA_HE = 'weapons.shells.QF94_AA_HE'
|
||||
ENUMS.Storage.weapons.shells.AH_6762x51mm_M62 = 'weapons.shells.AH-6 7.62x51mm M62'
|
||||
ENUMS.Storage.weapons.shells.AH_6762x51mm_M80 = 'weapons.shells.AH-6 7.62x51mm M80'
|
||||
ENUMS.Storage.weapons.shells.AH_6_762x51mm_M61 = 'weapons.shells.AH-6 7.62x51mm M61'
|
||||
ENUMS.Storage.weapons.shells.leFH18_105HE = 'weapons.shells.leFH18_105HE'
|
||||
ENUMS.Storage.weapons.shells.M63_37HE = 'weapons.shells.M63_37HE'
|
||||
ENUMS.Storage.weapons.shells.QF95_206R_fixed = 'weapons.shells.QF95_206R_fixed'
|
||||
ENUMS.Storage.weapons.shells.UBR_365_85AP = 'weapons.shells.UBR_365_85AP'
|
||||
ENUMS.Storage.weapons.shells.M101 = 'weapons.shells.M101'
|
||||
ENUMS.Storage.weapons.shells.HE_M1_Shell = 'weapons.shells.HE_M1_Shell'
|
||||
ENUMS.Storage.weapons.shells.UO_365K_85HE = 'weapons.shells.UO_365K_85HE'
|
||||
ENUMS.Storage.weapons.shells.Flak41_Sprgr_39 = 'weapons.shells.Flak41_Sprgr_39'
|
||||
ENUMS.Storage.weapons.shells.M1_37mm_HE_T = 'weapons.shells.M1_37mm_HE-T'
|
||||
ENUMS.Storage.weapons.shells.QF17_HE = 'weapons.shells.QF17_HE'
|
||||
ENUMS.Storage.weapons.shells.Pzgr_39 = 'weapons.shells.Pzgr_39'
|
||||
ENUMS.Storage.weapons.shells.Besa7_92x57 = 'weapons.shells.Besa7_92x57'
|
||||
ENUMS.Storage.weapons.shells.I_Gr_33 = 'weapons.shells.I_Gr_33'
|
||||
ENUMS.Storage.weapons.shells.M62_APC = 'weapons.shells.M62_APC'
|
||||
ENUMS.Storage.weapons.shells.Mk_12_HE_shell = 'weapons.shells.Mk_12_HE_shell'
|
||||
ENUMS.Storage.weapons.shells.M51_37AP = 'weapons.shells.M51_37AP'
|
||||
ENUMS.Storage.weapons.shells.M42A1_HE = 'weapons.shells.M42A1_HE'
|
||||
ENUMS.Storage.weapons.shells.HE_20x138B = 'weapons.shells.20x138B_HE'
|
||||
ENUMS.Storage.weapons.shells.HE_37x263 = 'weapons.shells.37x263_HE'
|
||||
ENUMS.Storage.weapons.shells.HE_2A20_115mm = 'weapons.shells.2A20_115mm_HE'
|
||||
|
||||
ENUMS.Storage.weapons.gunmounts.B17_TailTurret_M2_L = 'weapons.gunmounts.B17_TailTurret_M2_L'
|
||||
ENUMS.Storage.weapons.gunmounts.AH6_M134L = 'weapons.gunmounts.{AH6_M134L}'
|
||||
ENUMS.Storage.weapons.gunmounts.B17_Left_Nose_M2 = 'weapons.gunmounts.B17_Left_Nose_M2'
|
||||
ENUMS.Storage.weapons.gunmounts.Ju88_Turret_Top_Right_MG_81 = 'weapons.gunmounts.Ju88_Turret_Top_Right_MG_81'
|
||||
ENUMS.Storage.weapons.gunmounts.Ju88_Turret_Bottom_MG_81_L = 'weapons.gunmounts.Ju88_Turret_Bottom_MG_81_L'
|
||||
ENUMS.Storage.weapons.gunmounts.B17_ChinTurret_M2_R = 'weapons.gunmounts.B17_ChinTurret_M2_R'
|
||||
ENUMS.Storage.weapons.gunmounts.B17_Waist_Right_M2 = 'weapons.gunmounts.B17_Waist_Right_M2'
|
||||
ENUMS.Storage.weapons.gunmounts.AH_6_Door_Gun = 'weapons.gunmounts.AH-6_Door_Gun'
|
||||
ENUMS.Storage.weapons.gunmounts.B17_BallTurret_M2_L = 'weapons.gunmounts.B17_BallTurret_M2_L'
|
||||
ENUMS.Storage.weapons.gunmounts.B17_BallTurret_M2_R = 'weapons.gunmounts.B17_BallTurret_M2_R'
|
||||
ENUMS.Storage.weapons.gunmounts.B17_TopTurret_M2_R = 'weapons.gunmounts.B17_TopTurret_M2_R'
|
||||
ENUMS.Storage.weapons.gunmounts.B17_Right_Nose_M2 = 'weapons.gunmounts.B17_Right_Nose_M2'
|
||||
ENUMS.Storage.weapons.gunmounts.Ju88_Turret_Bottom_MG_81_R = 'weapons.gunmounts.Ju88_Turret_Bottom_MG_81_R'
|
||||
ENUMS.Storage.weapons.gunmounts.B17_TopTurret_M2_L = 'weapons.gunmounts.B17_TopTurret_M2_L'
|
||||
ENUMS.Storage.weapons.gunmounts.B17_Waist_Left_M2 = 'weapons.gunmounts.B17_Waist_Left_M2'
|
||||
ENUMS.Storage.weapons.gunmounts.Ju88_Turret_ahead_MG_81 = 'weapons.gunmounts.Ju88_Turret_ahead_MG_81'
|
||||
ENUMS.Storage.weapons.gunmounts.AH6_M134R = 'weapons.gunmounts.{AH6_M134R}'
|
||||
ENUMS.Storage.weapons.gunmounts.Ju88_Turret_Top_Left_MG_81 = 'weapons.gunmounts.Ju88_Turret_Top_Left_MG_81'
|
||||
ENUMS.Storage.weapons.gunmounts.B17_TailTurret_M2_R = 'weapons.gunmounts.B17_TailTurret_M2_R'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.AKAN_NO_TRC'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.AKAN'
|
||||
ENUMS.Storage.weapons.gunmounts.B17_ChinTurret_M2_L = 'weapons.gunmounts.B17_ChinTurret_M2_L'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.AKAN'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.AKAN_NO_TRC'
|
||||
ENUMS.Storage.weapons.gunmounts.AH_6_Door = 'weapons.gunmounts.{AH-6_Door}'
|
||||
ENUMS.Storage.weapons.gunmounts.AH_6_FN_HMP400 = 'weapons.gunmounts.{AH-6_FN_HMP400}'
|
||||
ENUMS.Storage.weapons.gunmounts.AH_6_M134L = 'weapons.gunmounts.AH-6_M134L'
|
||||
ENUMS.Storage.weapons.gunmounts.AH_6_M134R = 'weapons.gunmounts.AH-6_M134R'
|
||||
ENUMS.Storage.weapons.gunmounts.AH_6_HMP400 = 'weapons.gunmounts.AH-6_HMP400'
|
||||
|
||||
ENUMS.Storage.weapons.droptanks.PTB_800 = 'weapons.droptanks.PTB-800'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_275 = 'weapons.droptanks.PTB-275'
|
||||
ENUMS.Storage.weapons.droptanks.HB_A6E_AERO1D_EMPTY = 'weapons.droptanks.HB_A6E_AERO1D_EMPTY'
|
||||
ENUMS.Storage.weapons.droptanks.Drop_tank_75gal = 'weapons.droptanks.Drop tank 75gal'
|
||||
ENUMS.Storage.weapons.droptanks.S_3_PTB = 'weapons.droptanks.S-3-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_3000 = 'weapons.droptanks.PTB-3000'
|
||||
ENUMS.Storage.weapons.droptanks.HB_A6E_D704 = 'weapons.droptanks.HB_A6E_D704'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_1150_29 = 'weapons.droptanks.PTB-1150-29'
|
||||
ENUMS.Storage.weapons.droptanks.f_18c_ptb = 'weapons.droptanks.f-18c-ptb'
|
||||
ENUMS.Storage.weapons.droptanks.F4_BAK_L = 'weapons.droptanks.F4-BAK-L'
|
||||
ENUMS.Storage.weapons.droptanks.HB_A6E_AERO1D = 'weapons.droptanks.HB_A6E_AERO1D'
|
||||
ENUMS.Storage.weapons.droptanks.IAFS_ComboPak_100 = 'weapons.droptanks.{IAFS_ComboPak_100}'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_2000 = 'weapons.droptanks.PTB-2000'
|
||||
ENUMS.Storage.weapons.droptanks.M2000_PTB = 'weapons.droptanks.M2000-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_150 = 'weapons.droptanks.PTB-150'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_1150 = 'weapons.droptanks.PTB-1150'
|
||||
ENUMS.Storage.weapons.droptanks.fuel_tank_300gal = 'weapons.droptanks.fuel_tank_300gal'
|
||||
ENUMS.Storage.weapons.droptanks.MIG_25_PTB = 'weapons.droptanks.MIG-25-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_1500 = 'weapons.droptanks.PTB-1500'
|
||||
ENUMS.Storage.weapons.droptanks.MIG_23_PTB = 'weapons.droptanks.MIG-23-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.FT600 = 'weapons.droptanks.FT600'
|
||||
ENUMS.Storage.weapons.droptanks.F15_PTB = 'weapons.droptanks.F15-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.ah6_auxtank = 'weapons.droptanks.ah6_auxtank'
|
||||
ENUMS.Storage.weapons.droptanks.T_PTB = 'weapons.droptanks.T-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.fuel_tank_370gal = 'weapons.droptanks.fuel_tank_370gal'
|
||||
ENUMS.Storage.weapons.droptanks.F4_BAK_C = 'weapons.droptanks.F4-BAK-C'
|
||||
ENUMS.Storage.weapons.droptanks.F16_PTB_N2 = 'weapons.droptanks.F-16-PTB-N2'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_800 = 'weapons.droptanks.PTB-800'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_275 = 'weapons.droptanks.PTB-275'
|
||||
ENUMS.Storage.weapons.droptanks.T_PTB = 'weapons.droptanks.T-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.F16_PTB_N2 = 'weapons.droptanks.F-16-PTB-N2'
|
||||
ENUMS.Storage.weapons.droptanks.F4_BAK_C = 'weapons.droptanks.F4-BAK-C'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_1150_29 = 'weapons.droptanks.PTB-1150-29'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_1150 = 'weapons.droptanks.PTB-1150'
|
||||
ENUMS.Storage.weapons.droptanks.fuel_tank_300gal = 'weapons.droptanks.fuel_tank_300gal'
|
||||
ENUMS.Storage.weapons.droptanks.MIG_25_PTB = 'weapons.droptanks.MIG-25-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_1500 = 'weapons.droptanks.PTB-1500'
|
||||
ENUMS.Storage.weapons.droptanks.FT600 = 'weapons.droptanks.FT600'
|
||||
ENUMS.Storage.weapons.droptanks.Drop_tank_75gal = 'weapons.droptanks.Drop tank 75gal'
|
||||
ENUMS.Storage.weapons.droptanks.F15_PTB = 'weapons.droptanks.F15-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.M2000_PTB = 'weapons.droptanks.M2000-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_150 = 'weapons.droptanks.PTB-150'
|
||||
ENUMS.Storage.weapons.droptanks.F4_BAK_L = 'weapons.droptanks.F4-BAK-L'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_3000 = 'weapons.droptanks.PTB-3000'
|
||||
ENUMS.Storage.weapons.droptanks.PTB_2000 = 'weapons.droptanks.PTB-2000'
|
||||
ENUMS.Storage.weapons.droptanks.S_3_PTB = 'weapons.droptanks.S-3-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.fuel_tank_370gal = 'weapons.droptanks.fuel_tank_370gal'
|
||||
ENUMS.Storage.weapons.droptanks.MIG_23_PTB = 'weapons.droptanks.MIG-23-PTB'
|
||||
ENUMS.Storage.weapons.droptanks.f_18c_ptb = 'weapons.droptanks.f-18c-ptb'
|
||||
|
||||
ENUMS.Storage.weapons.containers.FN_HMP400_100 = 'weapons.containers.{FN_HMP400_100}'
|
||||
ENUMS.Storage.weapons.containers.AN_M3 = 'weapons.containers.{AN-M3}'
|
||||
ENUMS.Storage.weapons.containers.OH58D_M3P_L500 = 'weapons.containers.OH58D_M3P_L500'
|
||||
ENUMS.Storage.weapons.containers.GIAT_M621_HE = 'weapons.containers.{GIAT_M621_HE}'
|
||||
ENUMS.Storage.weapons.containers.KORD_12_7_MI24_L = 'weapons.containers.KORD_12_7_MI24_L'
|
||||
ENUMS.Storage.weapons.containers.CH47_STBD_M240H = 'weapons.containers.{CH47_STBD_M240H}'
|
||||
ENUMS.Storage.weapons.containers.CH47_AFT_M60D = 'weapons.containers.{CH47_AFT_M60D}'
|
||||
ENUMS.Storage.weapons.containers.UPK_23_250_MiG_21 = 'weapons.containers.{UPK-23-250 MiG-21}'
|
||||
ENUMS.Storage.weapons.containers.CH47_STBD_M134D = 'weapons.containers.{CH47_STBD_M134D}'
|
||||
ENUMS.Storage.weapons.containers.GAU_12_Equalizer = 'weapons.containers.{GAU_12_Equalizer}'
|
||||
ENUMS.Storage.weapons.containers.PKT_7_62 = 'weapons.containers.PKT_7_62'
|
||||
ENUMS.Storage.weapons.containers.CH47_AFT_M240H = 'weapons.containers.{CH47_AFT_M240H}'
|
||||
ENUMS.Storage.weapons.containers.GIAT_M621_SAPHEI = 'weapons.containers.{GIAT_M621_SAPHEI}'
|
||||
ENUMS.Storage.weapons.containers.ADEN_GUNPOD = 'weapons.containers.{ADEN_GUNPOD}'
|
||||
ENUMS.Storage.weapons.containers.GAU_12_Equalizer_HE = 'weapons.containers.{GAU_12_Equalizer_HE}'
|
||||
ENUMS.Storage.weapons.containers.M60_SIDE_L = 'weapons.containers.M60_SIDE_L'
|
||||
ENUMS.Storage.weapons.containers.KORD_12_7_MI24_R = 'weapons.containers.KORD_12_7_MI24_R'
|
||||
ENUMS.Storage.weapons.containers.GIAT_M621_APHE = 'weapons.containers.{GIAT_M621_APHE}'
|
||||
ENUMS.Storage.weapons.containers.MB339_ANM3_L = 'weapons.containers.{MB339_ANM3_L}'
|
||||
ENUMS.Storage.weapons.containers.FN_HMP400 = 'weapons.containers.{FN_HMP400}'
|
||||
ENUMS.Storage.weapons.containers.AH_6_Door = 'weapons.containers.{AH-6_Door}'
|
||||
ENUMS.Storage.weapons.containers.MB339_DEFA553_L = 'weapons.containers.{MB339_DEFA553_L}'
|
||||
ENUMS.Storage.weapons.containers.MB339_ANM3_R = 'weapons.containers.{MB339_ANM3_R}'
|
||||
ENUMS.Storage.weapons.containers.PK_3 = 'weapons.containers.{PK-3}'
|
||||
ENUMS.Storage.weapons.containers.GUV_VOG = 'weapons.containers.GUV_VOG'
|
||||
ENUMS.Storage.weapons.containers.SA342_M134_SIDE_R = 'weapons.containers.{SA342_M134_SIDE_R}'
|
||||
ENUMS.Storage.weapons.containers.OH58D_M3P_L100 = 'weapons.containers.OH58D_M3P_L100'
|
||||
ENUMS.Storage.weapons.containers.MXU_648 = 'weapons.containers.MXU-648'
|
||||
ENUMS.Storage.weapons.containers.OH58D_M3P_L400 = 'weapons.containers.OH58D_M3P_L400'
|
||||
ENUMS.Storage.weapons.containers.FN_HMP400_200 = 'weapons.containers.{FN_HMP400_200}'
|
||||
ENUMS.Storage.weapons.containers.GIAT_M621_HE = 'weapons.containers.{GIAT_M621_HE}'
|
||||
ENUMS.Storage.weapons.containers.M134_L = 'weapons.containers.M134_L'
|
||||
ENUMS.Storage.weapons.containers.OH58D_M3P_L200 = 'weapons.containers.OH58D_M3P_L200'
|
||||
ENUMS.Storage.weapons.containers.GIAT_M621_AP = 'weapons.containers.{GIAT_M621_AP}'
|
||||
ENUMS.Storage.weapons.containers.CH47_STBD_M134D = 'weapons.containers.{CH47_STBD_M134D}'
|
||||
ENUMS.Storage.weapons.containers.PKT_7_62 = 'weapons.containers.PKT_7_62'
|
||||
ENUMS.Storage.weapons.containers.OH58D_M3P_L300 = 'weapons.containers.OH58D_M3P_L300'
|
||||
ENUMS.Storage.weapons.containers.GIAT_M621_SAPHEI = 'weapons.containers.{GIAT_M621_SAPHEI}'
|
||||
ENUMS.Storage.weapons.containers.M60_SIDE_L = 'weapons.containers.M60_SIDE_L'
|
||||
ENUMS.Storage.weapons.containers.CH47_PORT_M134D = 'weapons.containers.{CH47_PORT_M134D}'
|
||||
ENUMS.Storage.weapons.containers.CH47_PORT_M60D = 'weapons.containers.{CH47_PORT_M60D}'
|
||||
ENUMS.Storage.weapons.containers.ADEN_GUNPOD = 'weapons.containers.{ADEN_GUNPOD}'
|
||||
ENUMS.Storage.weapons.containers.C_101_DEFA553 = 'weapons.containers.{C-101-DEFA553}'
|
||||
ENUMS.Storage.weapons.containers.MB339_ANM3_R = 'weapons.containers.{MB339_ANM3_R}'
|
||||
ENUMS.Storage.weapons.containers.CH47_AFT_M60D = 'weapons.containers.{CH47_AFT_M60D}'
|
||||
ENUMS.Storage.weapons.containers.KORD_12_7 = 'weapons.containers.KORD_12_7'
|
||||
ENUMS.Storage.weapons.containers.GIAT_M621_HEAP = 'weapons.containers.{GIAT_M621_HEAP}'
|
||||
ENUMS.Storage.weapons.containers.CH47_AFT_M3M = 'weapons.containers.{CH47_AFT_M3M}'
|
||||
ENUMS.Storage.weapons.containers.GUV_YakB_GSHP = 'weapons.containers.GUV_YakB_GSHP'
|
||||
ENUMS.Storage.weapons.containers.R_73U = 'weapons.containers.R-73U'
|
||||
ENUMS.Storage.weapons.containers.GUV_VOG = 'weapons.containers.GUV_VOG'
|
||||
ENUMS.Storage.weapons.containers.KORD_12_7_MI24_L = 'weapons.containers.KORD_12_7_MI24_L'
|
||||
ENUMS.Storage.weapons.containers.OH58D_M3P_L100 = 'weapons.containers.OH58D_M3P_L100'
|
||||
ENUMS.Storage.weapons.containers.OH58D_M3P_L400 = 'weapons.containers.OH58D_M3P_L400'
|
||||
ENUMS.Storage.weapons.containers.CH47_STBD_M240H = 'weapons.containers.{CH47_STBD_M240H}'
|
||||
ENUMS.Storage.weapons.containers.CH47_PORT_M240H = 'weapons.containers.{CH47_PORT_M240H}'
|
||||
ENUMS.Storage.weapons.containers.CC420_GUN_POD = 'weapons.containers.{CC420_GUN_POD}'
|
||||
ENUMS.Storage.weapons.containers.FN_HMP400_100 = 'weapons.containers.{FN_HMP400_100}'
|
||||
ENUMS.Storage.weapons.containers.MB339_ANM3_L = 'weapons.containers.{MB339_ANM3_L}'
|
||||
ENUMS.Storage.weapons.containers.MB339_DEFA553_R = 'weapons.containers.{MB339_DEFA553_R}'
|
||||
ENUMS.Storage.weapons.containers.GAU_12_Equalizer_HE = 'weapons.containers.{GAU_12_Equalizer_HE}'
|
||||
ENUMS.Storage.weapons.containers.OH58D_M3P_L500 = 'weapons.containers.OH58D_M3P_L500'
|
||||
ENUMS.Storage.weapons.containers.SUU_23_POD = 'weapons.containers.{SUU_23_POD}'
|
||||
ENUMS.Storage.weapons.containers.PK_3 = 'weapons.containers.{PK-3}'
|
||||
ENUMS.Storage.weapons.containers.AKAN = 'weapons.containers.{AKAN}'
|
||||
ENUMS.Storage.weapons.containers.CH47_STBD_M60D = 'weapons.containers.{CH47_STBD_M60D}'
|
||||
ENUMS.Storage.weapons.containers.SA342_M134_SIDE_R = 'weapons.containers.{SA342_M134_SIDE_R}'
|
||||
ENUMS.Storage.weapons.containers.M60_SIDE_R = 'weapons.containers.M60_SIDE_R'
|
||||
ENUMS.Storage.weapons.containers.GAU_12_Equalizer_AP = 'weapons.containers.{GAU_12_Equalizer_AP}'
|
||||
ENUMS.Storage.weapons.containers.GAU_12_Equalizer = 'weapons.containers.{GAU_12_Equalizer}'
|
||||
ENUMS.Storage.weapons.containers.KORD_12_7_MI24_R = 'weapons.containers.KORD_12_7_MI24_R'
|
||||
ENUMS.Storage.weapons.containers.M134_SIDE_R = 'weapons.containers.M134_SIDE_R'
|
||||
ENUMS.Storage.weapons.containers.AKAN_NO_TRC = 'weapons.containers.{AKAN_NO_TRC}'
|
||||
ENUMS.Storage.weapons.containers.oh_58_brauning = 'weapons.containers.oh-58-brauning'
|
||||
ENUMS.Storage.weapons.containers.MXU_648 = 'weapons.containers.MXU-648'
|
||||
ENUMS.Storage.weapons.containers.M134_R = 'weapons.containers.M134_R'
|
||||
ENUMS.Storage.weapons.containers.AN_M3 = 'weapons.containers.{AN-M3}'
|
||||
ENUMS.Storage.weapons.containers.GIAT_M621_APHE = 'weapons.containers.{GIAT_M621_APHE}'
|
||||
ENUMS.Storage.weapons.containers.AIM_9S = 'weapons.containers.AIM-9S'
|
||||
ENUMS.Storage.weapons.containers.CH47_AFT_M240H = 'weapons.containers.{CH47_AFT_M240H}'
|
||||
ENUMS.Storage.weapons.containers.FN_HMP400 = 'weapons.containers.{FN_HMP400}'
|
||||
ENUMS.Storage.weapons.containers.M134_SIDE_L = 'weapons.containers.M134_SIDE_L'
|
||||
ENUMS.Storage.weapons.containers.MB339_DEFA553_L = 'weapons.containers.{MB339_DEFA553_L}'
|
||||
ENUMS.Storage.weapons.containers.MISC_1 = 'weapons.containers.{05544F1A-C39C-466b-BC37-5BD1D52E57BB}'
|
||||
ENUMS.Storage.weapons.containers.MISC_2 = 'weapons.containers.{E92CBFE5-C153-11d8-9897-000476191836}'
|
||||
ENUMS.Storage.weapons.containers.hvar_SmokeGenerator = 'weapons.containers.hvar_SmokeGenerator'
|
||||
ENUMS.Storage.weapons.containers.INV_SMOKE_RED = 'weapons.containers.{INV-SMOKE-RED}'
|
||||
ENUMS.Storage.weapons.containers.INV_SMOKE_YELLOW = 'weapons.containers.{INV-SMOKE-YELLOW}'
|
||||
ENUMS.Storage.weapons.containers.INV_SMOKE_BLUE = 'weapons.containers.{INV-SMOKE-BLUE}'
|
||||
ENUMS.Storage.weapons.containers.INV_SMOKE_GREEN = 'weapons.containers.{INV-SMOKE-GREEN}'
|
||||
ENUMS.Storage.weapons.containers.INV_SMOKE_WHITE = 'weapons.containers.{INV-SMOKE-WHITE}'
|
||||
ENUMS.Storage.weapons.containers.INV_SMOKE_ORANGE = 'weapons.containers.{INV-SMOKE-ORANGE}'
|
||||
ENUMS.Storage.weapons.containers.GAU_12_Equalizer_AP = 'weapons.containers.{GAU_12_Equalizer_AP}'
|
||||
ENUMS.Storage.weapons.containers.AH_6_Gunners = 'weapons.containers.{AH-6_Gunners}'
|
||||
ENUMS.Storage.weapons.containers.AH_6_FN_HMP400 = 'weapons.containers.{AH-6_FN_HMP400}'
|
||||
ENUMS.Storage.weapons.containers.R_73U = 'weapons.containers.R-73U'
|
||||
ENUMS.Storage.weapons.containers.M60_SIDE_R = 'weapons.containers.M60_SIDE_R'
|
||||
ENUMS.Storage.weapons.containers.CH47_AFT_M3M = 'weapons.containers.{CH47_AFT_M3M}'
|
||||
ENUMS.Storage.weapons.containers.GUV_YakB_GSHP = 'weapons.containers.GUV_YakB_GSHP'
|
||||
ENUMS.Storage.weapons.containers.RKL609_L = 'weapons.containers.{RKL609_L}'
|
||||
ENUMS.Storage.weapons.containers.CH47_PORT_M134D = 'weapons.containers.{CH47_PORT_M134D}'
|
||||
ENUMS.Storage.weapons.containers.CH47_PORT_M60D = 'weapons.containers.{CH47_PORT_M60D}'
|
||||
ENUMS.Storage.weapons.containers.RKL609_R = 'weapons.containers.{RKL609_R}'
|
||||
ENUMS.Storage.weapons.containers.C_101_DEFA553 = 'weapons.containers.{C-101-DEFA553}'
|
||||
ENUMS.Storage.weapons.containers.AH6_M134L = 'weapons.containers.{AH6_M134L}'
|
||||
ENUMS.Storage.weapons.containers.KORD_12_7 = 'weapons.containers.KORD_12_7'
|
||||
ENUMS.Storage.weapons.containers.C130_M18_Sidearm = 'weapons.containers.{C130-M18-Sidearm}'
|
||||
ENUMS.Storage.weapons.containers.GIAT_M621_HEAP = 'weapons.containers.{GIAT_M621_HEAP}'
|
||||
ENUMS.Storage.weapons.containers.BRU_42_LS = 'weapons.containers.BRU-42_LS'
|
||||
ENUMS.Storage.weapons.containers.CH47_STBD_M60D = 'weapons.containers.{CH47_STBD_M60D}'
|
||||
ENUMS.Storage.weapons.containers.SUU_23_POD = 'weapons.containers.{SUU_23_POD}'
|
||||
ENUMS.Storage.weapons.containers.M134_SIDE_R = 'weapons.containers.M134_SIDE_R'
|
||||
ENUMS.Storage.weapons.containers.AKAN_NO_TRC = 'weapons.containers.{AKAN_NO_TRC}'
|
||||
ENUMS.Storage.weapons.containers.oh_58_brauning = 'weapons.containers.oh-58-brauning'
|
||||
ENUMS.Storage.weapons.containers.AH_6_DOORS = 'weapons.containers.{AH-6_DOORS}'
|
||||
ENUMS.Storage.weapons.containers.CC420_GUN_POD = 'weapons.containers.{CC420_GUN_POD}'
|
||||
ENUMS.Storage.weapons.containers.CH47_PORT_M240H = 'weapons.containers.{CH47_PORT_M240H}'
|
||||
ENUMS.Storage.weapons.containers.MB339_DEFA553_R = 'weapons.containers.{MB339_DEFA553_R}'
|
||||
ENUMS.Storage.weapons.containers.AIM_9S = 'weapons.containers.AIM-9S'
|
||||
ENUMS.Storage.weapons.containers.hvar_SmokeGenerator = 'weapons.containers.hvar_SmokeGenerator'
|
||||
ENUMS.Storage.weapons.containers.M134_L = 'weapons.containers.M134_L'
|
||||
ENUMS.Storage.weapons.containers.AKAN = 'weapons.containers.{AKAN}'
|
||||
ENUMS.Storage.weapons.containers.C130_Cargo_Bay_M4 = 'weapons.containers.{C130-Cargo-Bay-M4}'
|
||||
ENUMS.Storage.weapons.containers.M134_SIDE_L = 'weapons.containers.M134_SIDE_L'
|
||||
ENUMS.Storage.weapons.containers.FN_HMP400_200 = 'weapons.containers.{FN_HMP400_200}'
|
||||
ENUMS.Storage.weapons.containers.OH58D_M3P_L200 = 'weapons.containers.OH58D_M3P_L200'
|
||||
ENUMS.Storage.weapons.containers.GIAT_M621_AP = 'weapons.containers.{GIAT_M621_AP}'
|
||||
ENUMS.Storage.weapons.containers.M134_R = 'weapons.containers.M134_R'
|
||||
ENUMS.Storage.weapons.containers.OH58D_M3P_L300 = 'weapons.containers.OH58D_M3P_L300'
|
||||
ENUMS.Storage.weapons.containers.AH6_M134R = 'weapons.containers.{AH6_M134R}'
|
||||
|
||||
ENUMS.Storage.weapons.torpedoes.G7A_T1 = 'weapons.torpedoes.G7A_T1'
|
||||
|
||||
|
||||
-- UH-60L Mod
|
||||
ENUMS.Storage.weapons.gunmounts.UH60LGAU19 = 'weapons.gunmounts.UH-60L GAU-19'
|
||||
@@ -1788,6 +2081,15 @@ ENUMS.Storage.weapons.gunmounts.UH60L_M60_GUNNER = 'weapons.gunmounts.{UH60L_M60
|
||||
ENUMS.Storage.weapons.gunmounts.UH60L_M2_GUNNER = 'weapons.gunmounts.{UH60L_M2_GUNNER}'
|
||||
ENUMS.Storage.weapons.gunmounts.UH60_M230_LEFT = 'weapons.gunmounts.{UH60_M230_LEFT}'
|
||||
ENUMS.Storage.weapons.gunmounts.UH60_M230_RIGHT = 'weapons.gunmounts.{UH60_M230_RIGHT}'
|
||||
ENUMS.Storage.weapons.containers.UH60_M134_RIGHT = 'weapons.containers.{UH60_M134_RIGHT}'
|
||||
ENUMS.Storage.weapons.containers.UH60_M134_LEFT = 'weapons.containers.{UH60_M134_LEFT}'
|
||||
ENUMS.Storage.weapons.containers.UH60_M230_LEFT = 'weapons.containers.{UH60_M230_LEFT}'
|
||||
ENUMS.Storage.weapons.containers.UH60L_M134_GUNNER = 'weapons.containers.{UH60L_M134_GUNNER}'
|
||||
ENUMS.Storage.weapons.containers.UH60_GAU19_LEFT = 'weapons.containers.{UH60_GAU19_LEFT}'
|
||||
ENUMS.Storage.weapons.containers.UH60L_M60_GUNNER = 'weapons.containers.{UH60L_M60_GUNNER}'
|
||||
ENUMS.Storage.weapons.containers.UH60_GAU19_RIGHT = 'weapons.containers.{UH60_GAU19_RIGHT}'
|
||||
ENUMS.Storage.weapons.containers.UH60_M230_RIGHT = 'weapons.containers.{UH60_M230_RIGHT}'
|
||||
ENUMS.Storage.weapons.containers.UH60L_M2_GUNNER = 'weapons.containers.{UH60L_M2_GUNNER}'
|
||||
|
||||
---
|
||||
-- @type ENUMS.FARPType
|
||||
|
||||
@@ -249,7 +249,23 @@ CALLSIGN={
|
||||
Stetson = 22,
|
||||
Wrath = 23,
|
||||
},
|
||||
|
||||
Intruder = {
|
||||
Raygun = 4,
|
||||
Heartless = 5,
|
||||
Viceroy = 6,
|
||||
Cupcake = 7,
|
||||
["Flying Tiger"] = 8,
|
||||
["Flying Ace"] = 9,
|
||||
Buckeye = 10,
|
||||
Goldplate = 11,
|
||||
Phoenix = 12,
|
||||
Electron = 13,
|
||||
Rustler = 14,
|
||||
Vixen = 15,
|
||||
Jackal = 16,
|
||||
Milestone = 17,
|
||||
Devil = 18,
|
||||
},
|
||||
} --#CALLSIGN
|
||||
|
||||
--- Utilities static class.
|
||||
@@ -805,33 +821,6 @@ UTILS.kg2lbs = function( kg )
|
||||
return kg * 2.20462
|
||||
end
|
||||
|
||||
--- Convert latitude or longitude from degrees, minutes, seconds (DMS) to decimal degrees (DD).
|
||||
-- @param #number Degrees Degrees in grad.
|
||||
-- @param #number Minutes Minutes.
|
||||
-- @param #number Seconds Seconds.
|
||||
-- @return #number Latitude or Longitude in decimal degrees.
|
||||
UTILS.LLDMSToDD = function(Degrees, Minutes, Seconds)
|
||||
|
||||
local dd=tonumber(Degrees or 0) + tonumber(Minutes or 0)/60 + tonumber(Seconds or 0)/3600
|
||||
|
||||
return dd
|
||||
end
|
||||
|
||||
--- Convert latitude or longitude from degrees, minutes, seconds (DMS) given in text form to decimal degrees (DD).
|
||||
-- @param #string LatOrLongString Latitude or longitude passed as ttring in format `DD°MM'SS.SS"`.
|
||||
-- @return #number Latitude or Longitude in decimal degrees.
|
||||
UTILS.LLDMSstringToDD = function(LatOrLongString)
|
||||
|
||||
local hem=string.match(LatOrLongString, "(%a)")
|
||||
local Degrees=string.match(LatOrLongString, "(%d+)°")
|
||||
local Minutes=string.match(LatOrLongString, "(%d+)'")
|
||||
local Seconds=string.match(LatOrLongString, "([%d\.]+)\"")
|
||||
|
||||
local dd=UTILS.LLDMSToDD(Degrees, Minutes, Seconds)
|
||||
|
||||
return dd
|
||||
end
|
||||
|
||||
--[[acc:
|
||||
in DM: decimal point of minutes.
|
||||
In DMS: decimal point of seconds.
|
||||
@@ -1518,6 +1507,14 @@ function UTILS.VecSubstract(a, b)
|
||||
return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z}
|
||||
end
|
||||
|
||||
--- Scale a 3D vectors by multiplication with s.
|
||||
-- @param DCS#Vec3 v A Vector in 3D with x, y, z components.
|
||||
-- @param #number s Scale
|
||||
-- @return DCS#Vec3 Vec3
|
||||
function UTILS.VecScale(v, s)
|
||||
return {x = v.x * s, y = v.y * s, z = v.z * s}
|
||||
end
|
||||
|
||||
--- Substract is not a word, don't want to rename the original function because it's been around since forever
|
||||
function UTILS.VecSubtract(a, b)
|
||||
return UTILS.VecSubstract(a, b)
|
||||
@@ -1531,6 +1528,14 @@ function UTILS.Vec2Substract(a, b)
|
||||
return {x=a.x-b.x, y=a.y-b.y}
|
||||
end
|
||||
|
||||
--- Calculate the difference between two 3D vectors by substracting the x,y,z components from each other.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
|
||||
-- @return DCS#Vec3 Vector in 3D.
|
||||
function UTILS.Vec3Substract(a, b)
|
||||
return {x = a.x - b.x, y = a.y - b.y, z = a.z - b.z}
|
||||
end
|
||||
|
||||
--- Substract is not a word, don't want to rename the original function because it's been around since forever
|
||||
function UTILS.Vec2Subtract(a, b)
|
||||
return UTILS.Vec2Substract(a, b)
|
||||
@@ -1552,22 +1557,6 @@ function UTILS.Vec2Add(a, b)
|
||||
return {x=a.x+b.x, y=a.y+b.y}
|
||||
end
|
||||
|
||||
--- Multiply 2D vector by a scalar value.
|
||||
-- @param DCS#Vec2 a Vector in 2D with x, y components.
|
||||
-- @param #number c Scalar value.
|
||||
-- @return DCS#Vec2 Vector
|
||||
function UTILS.Vec2Mult(a, c)
|
||||
return {x=c*a.x, y=c*a.y}
|
||||
end
|
||||
|
||||
--- Multiply 3D vector by a scalar value.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||
-- @param #number c Scalar value.
|
||||
-- @return DCS#Vec3 Vector
|
||||
function UTILS.VecMult(a, c)
|
||||
return {x=c*a.x, y=c*a.y, z=c*a.z}
|
||||
end
|
||||
|
||||
--- Calculate the angle between two 3D vectors.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
|
||||
@@ -1588,24 +1577,6 @@ function UTILS.VecAngle(a, b)
|
||||
return math.deg(alpha)
|
||||
end
|
||||
|
||||
--- Calculate the angle between two 3D vectors.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
|
||||
-- @return #number Angle alpha between and b in degrees.
|
||||
function UTILS.VecAngleSigned(a, b)
|
||||
|
||||
local a=UTILS.VecSubstract(s1.p2.vec3, s1.p1.vec3)
|
||||
|
||||
local b=UTILS.VecSubstract(s2.p2.vec3, s2.p1.vec3)
|
||||
|
||||
local h1=UTILS.VecHdg(a)
|
||||
local h2=UTILS.VecHdg(b)
|
||||
|
||||
local angle=h1-h2 --UTILS.VecAngle(a, b)
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Calculate "heading" of a 3D vector in the X-Z plane.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||
-- @return #number Heading in degrees in [0,360).
|
||||
@@ -1628,20 +1599,6 @@ function UTILS.Vec2Hdg(a)
|
||||
return h
|
||||
end
|
||||
|
||||
--- Calculate the difference between two "heading", i.e. angles in [0,360) deg.
|
||||
-- @param DCS#Vec3 a Vector a.
|
||||
-- @param DCS#Vec3 a Vector b.
|
||||
-- @return #number Heading difference in degrees.
|
||||
function UTILS.VecHdgDiff(a, b)
|
||||
|
||||
local ha=math.deg(math.atan2(a.z, a.x))
|
||||
local hb=math.deg(math.atan2(b.z, b.x))
|
||||
|
||||
local angle=ha-hb
|
||||
|
||||
return angle
|
||||
end
|
||||
|
||||
--- Calculate the difference between two "heading", i.e. angles in [0,360) deg.
|
||||
-- @param #number h1 Heading one.
|
||||
-- @param #number h2 Heading two.
|
||||
@@ -1708,6 +1665,13 @@ function UTILS.Vec2Translate(a, distance, angle)
|
||||
return {x=TX, y=TY}
|
||||
end
|
||||
|
||||
--- Calculate the lenght of a 3D vector
|
||||
-- @param DCS#Vec3 v A Vector in 3D with x, y, z components.
|
||||
-- @return #number length
|
||||
function UTILS.Vec3Length(v)
|
||||
return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
|
||||
end
|
||||
|
||||
--- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||
-- @param #number angle Rotation angle in degrees.
|
||||
@@ -2089,6 +2053,12 @@ function UTILS.GetCallsignName(Callsign)
|
||||
end
|
||||
end
|
||||
|
||||
for name, value in pairs(CALLSIGN.Intruder) do
|
||||
if value==Callsign then
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
return "Ghostrider"
|
||||
end
|
||||
|
||||
@@ -2375,6 +2345,21 @@ function UTILS.IsLoadingDoorOpen( unit_name )
|
||||
return true
|
||||
end
|
||||
|
||||
if type_name=="C-130J-30" and (unit:getDrawArgumentValue(86)==1)then
|
||||
BASE:T(unit_name.." rear doors are open")
|
||||
return true
|
||||
end
|
||||
|
||||
if type_name=="C-130J-30" and (unit:getDrawArgumentValue(87)==1)then
|
||||
BASE:T(unit_name.." Side door(s) are open")
|
||||
return true
|
||||
end
|
||||
|
||||
if type_name=="C-130J-30" and (unit:getDrawArgumentValue(88)==1)then
|
||||
BASE:T(unit_name.." Paratroop door(s) are open")
|
||||
return true
|
||||
end
|
||||
|
||||
if string.find(type_name, "Hercules") and (unit:getDrawArgumentValue(1215) == 1 and unit:getDrawArgumentValue(1216) == 1) then
|
||||
BASE:T(unit_name .. " rear doors are open")
|
||||
return true
|
||||
@@ -2938,16 +2923,27 @@ end
|
||||
-- @param #boolean Cinematic (Optional, needs Structured = true) If true, place a fire/smoke effect on the dead static position.
|
||||
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
|
||||
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
|
||||
-- @param #boolean Resurrection If true, dead units can be restored on load. Defaults to false.
|
||||
-- @param #number ResurrectPercentage Use this percentage of probability to resurrect a unit. [0..100], defaults to 25.
|
||||
-- @param #number Healmin If set, life points of a resurrected unit will be randomly restored to min this percentage. [0..100], defaults to 25.
|
||||
-- @param #number Healmax If set, life points of a resurrected unit will be randomly restored to max this percentage. [0..100], defaults to 75.
|
||||
-- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read.
|
||||
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
|
||||
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density)
|
||||
|
||||
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density,Resurrection,ResurrectPercentage,Healmin,Healmax)
|
||||
|
||||
local healmin = Healmin or 25
|
||||
local healmax = Healmax or 75
|
||||
local resurrection = (Resurrection == true) and true or false
|
||||
local resurrectpercentage = ResurrectPercentage or 25
|
||||
|
||||
local fires = {}
|
||||
|
||||
---
|
||||
-- @param Core.Point#COORDINATE coord
|
||||
local function Smokers(name,coord,effect,density)
|
||||
local eff = math.random(8)
|
||||
if type(effect) == "number" then eff = effect end
|
||||
coord:BigSmokeAndFire(eff,density,name)
|
||||
coord:BigSmokeAndFire( eff, Density, 300, 1, name )
|
||||
table.insert(fires,name)
|
||||
end
|
||||
|
||||
@@ -2962,7 +2958,16 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema
|
||||
local name = _unit:GetName()
|
||||
Smokers(name,coordinate,Effect,Density)
|
||||
end
|
||||
_unit:Destroy(false)
|
||||
-- TODO Resurrection logic
|
||||
local resurectok = math.random(1,100)
|
||||
BASE:E(string.format("Load Group | Resurrection | Resurrect %s | Thresh %d | Random %d",tostring(resurrection),resurrectpercentage,resurectok))
|
||||
if resurrection == true and (resurectok < resurrectpercentage) then
|
||||
local heallife = math.random(healmin,healmax)
|
||||
BASE:E("Load Group | Resurrection | Life "..heallife)
|
||||
_unit:SetLife(heallife)
|
||||
else
|
||||
_unit:Destroy(false)
|
||||
end
|
||||
reduced = reduced + 1
|
||||
if reduced == anzahl then break end
|
||||
end
|
||||
@@ -4297,25 +4302,27 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
|
||||
|
||||
local function PopulateStorage(Name,liquids,equip,airframes)
|
||||
local newWH = STORAGE:New(Name)
|
||||
if liquids and liquids > 0 then
|
||||
-- Storage fill-up
|
||||
newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons
|
||||
newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids)
|
||||
newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids)
|
||||
newWH:SetLiquid(STORAGE.Liquid.MW50,liquids)
|
||||
end
|
||||
|
||||
if equip and equip > 0 then
|
||||
for cat,nitem in pairs(ENUMS.Storage.weapons) do
|
||||
for name,item in pairs(nitem) do
|
||||
newWH:SetItem(item,equip)
|
||||
if newWH then
|
||||
if liquids and liquids > 0 then
|
||||
-- Storage fill-up
|
||||
newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons
|
||||
newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids)
|
||||
newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids)
|
||||
newWH:SetLiquid(STORAGE.Liquid.MW50,liquids)
|
||||
end
|
||||
|
||||
if equip and equip > 0 then
|
||||
for cat,nitem in pairs(ENUMS.Storage.weapons) do
|
||||
for name,item in pairs(nitem) do
|
||||
newWH:SetItem(item,equip)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if airframes and airframes > 0 then
|
||||
for typename in pairs (CSAR.AircraftType) do
|
||||
newWH:SetItem(typename,airframes)
|
||||
|
||||
if airframes and airframes > 0 then
|
||||
for typename in pairs (CSAR.AircraftType) do
|
||||
newWH:SetItem(typename,airframes)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4394,8 +4401,6 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
|
||||
}
|
||||
-- Create BIRTH event.
|
||||
world.onEvent(Event)
|
||||
|
||||
PopulateStorage(Name.."-1",liquids,equip,airframes)
|
||||
else
|
||||
-- Spawn FARP
|
||||
local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP"
|
||||
@@ -4403,10 +4408,11 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
|
||||
newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart)
|
||||
local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name)
|
||||
table.insert(ReturnObjects,spawnedfarp)
|
||||
|
||||
PopulateStorage(Name,liquids,equip,airframes)
|
||||
|
||||
end
|
||||
|
||||
|
||||
PopulateStorage(Name,liquids,equip,airframes)
|
||||
|
||||
-- Spawn Objects
|
||||
local FARPStaticObjectsNato = {
|
||||
["FUEL"] = { TypeName = "FARP Fuel Depot", ShapeName = "GSM Rus", Category = "Fortifications"},
|
||||
@@ -4612,6 +4618,18 @@ function UTILS.Vec2toVec3(vec,y)
|
||||
end
|
||||
end
|
||||
|
||||
-- Converts a Vec3 to a Vec2
|
||||
-- @param vec3 the Vec3 to convert
|
||||
-- @return Vec2 The Vec2 output
|
||||
function UTILS.Vec3toVec2(Vec3)
|
||||
if Vec3 and type(Vec3)=="table" then
|
||||
local Vec2 = {}
|
||||
Vec2.x = Vec3.x or 0
|
||||
Vec2.y = Vec3.z or 0
|
||||
return Vec2
|
||||
end
|
||||
end
|
||||
|
||||
--- Get the correction needed for true north in radians
|
||||
-- @param gPoint The map point vec2 or vec3
|
||||
-- @return number correction
|
||||
@@ -5349,3 +5367,241 @@ function UTILS.CreateAirbaseEnum()
|
||||
_savefile(string.format("%s-enums.txt", env.mission.theatre), text)
|
||||
--env.info(text)
|
||||
end
|
||||
|
||||
--- Calculate then center and radius of a circle enclosing a list if DCS#Vec2 points.
|
||||
-- @param #table points Table of DCS#Vec2 entries
|
||||
-- @return DCS#Vec2 center DCS#Vec2
|
||||
-- @return #number radius
|
||||
function UTILS.GetCenterAndRadius(points)
|
||||
if #points == 0 then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
-- Calculate centroid (average of all points)
|
||||
local sumX, sumY = 0, 0
|
||||
for _, p in ipairs(points) do
|
||||
sumX = sumX + p.x
|
||||
sumY = sumY + p.y
|
||||
end
|
||||
|
||||
local center = {
|
||||
x = sumX / #points,
|
||||
y = sumY / #points
|
||||
}
|
||||
|
||||
-- Find maximum distance from center to any point
|
||||
local maxDist = 0
|
||||
for _, p in ipairs(points) do
|
||||
local dx = p.x - center.x
|
||||
local dy = p.y - center.y
|
||||
local dist = math.sqrt(dx*dx + dy*dy)
|
||||
if dist > maxDist then
|
||||
maxDist = dist
|
||||
end
|
||||
end
|
||||
|
||||
return center, maxDist
|
||||
end
|
||||
|
||||
--- More accurate: Minimum bounding circle (Welzl's algorithm), calculate then center and radius of a circle enclosing a list if DCS#Vec2 points.
|
||||
-- @param #table points Table of DCS#Vec2 entries
|
||||
-- @return DCS#Vec2 center DCS#Vec2
|
||||
-- @return #number radius
|
||||
function UTILS.GetMinimumBoundingCircle(points)
|
||||
if #points == 0 then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
-- Calculate distance between two points
|
||||
local function distance(p1, p2)
|
||||
local dx = p2.x - p1.x
|
||||
local dy = p2.y - p1.y
|
||||
return math.sqrt(dx*dx + dy*dy)
|
||||
end
|
||||
|
||||
-- Circle from 2 points (diameter)
|
||||
local function circleFrom2Points(p1, p2)
|
||||
local center = {
|
||||
x = (p1.x + p2.x) / 2,
|
||||
y = (p1.y + p2.y) / 2
|
||||
}
|
||||
local radius = distance(p1, p2) / 2
|
||||
return center, radius
|
||||
end
|
||||
|
||||
-- Circle from 3 points (circumcircle)
|
||||
local function circleFrom3Points(p1, p2, p3)
|
||||
local ax, ay = p1.x, p1.y
|
||||
local bx, by = p2.x, p2.y
|
||||
local cx, cy = p3.x, p3.y
|
||||
|
||||
local d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by))
|
||||
|
||||
if math.abs(d) < 0.0001 then
|
||||
-- Points are collinear, use 2-point circle
|
||||
return circleFrom2Points(p1, p3)
|
||||
end
|
||||
|
||||
local aSq = ax*ax + ay*ay
|
||||
local bSq = bx*bx + by*by
|
||||
local cSq = cx*cx + cy*cy
|
||||
|
||||
local ux = (aSq * (by - cy) + bSq * (cy - ay) + cSq * (ay - by)) / d
|
||||
local uy = (aSq * (cx - bx) + bSq * (ax - cx) + cSq * (bx - ax)) / d
|
||||
|
||||
local center = {x = ux, y = uy}
|
||||
local radius = distance(center, p1)
|
||||
|
||||
return center, radius
|
||||
end
|
||||
|
||||
-- Check if point is inside circle
|
||||
local function isInside(center, radius, point, tolerance)
|
||||
tolerance = tolerance or 0.0001
|
||||
return distance(center, point) <= radius + tolerance
|
||||
end
|
||||
|
||||
-- Welzl's algorithm (recursive)
|
||||
local function welzlHelper(pts, n, boundary)
|
||||
-- Base cases
|
||||
if n == 0 or #boundary == 3 then
|
||||
if #boundary == 0 then
|
||||
return {x = 0, y = 0}, 0
|
||||
elseif #boundary == 1 then
|
||||
return {x = boundary[1].x, y = boundary[1].y}, 0
|
||||
elseif #boundary == 2 then
|
||||
return circleFrom2Points(boundary[1], boundary[2])
|
||||
else
|
||||
return circleFrom3Points(boundary[1], boundary[2], boundary[3])
|
||||
end
|
||||
end
|
||||
|
||||
-- Pick a random point
|
||||
local p = pts[n]
|
||||
|
||||
-- Get circle without this point
|
||||
local center, radius = welzlHelper(pts, n - 1, boundary)
|
||||
|
||||
-- If point is inside, we're done
|
||||
if isInside(center, radius, p) then
|
||||
return center, radius
|
||||
end
|
||||
|
||||
-- Otherwise, point must be on the boundary
|
||||
local newBoundary = {}
|
||||
for i = 1, #boundary do
|
||||
newBoundary[i] = boundary[i]
|
||||
end
|
||||
table.insert(newBoundary, p)
|
||||
|
||||
return welzlHelper(pts, n - 1, newBoundary)
|
||||
end
|
||||
|
||||
-- Shuffle points for better average performance
|
||||
local pts = {}
|
||||
for i, p in ipairs(points) do
|
||||
pts[i] = {x = p.x, y = p.y}
|
||||
end
|
||||
|
||||
-- Simple shuffle
|
||||
for i = #pts, 2, -1 do
|
||||
local j = math.random(1, i)
|
||||
pts[i], pts[j] = pts[j], pts[i]
|
||||
end
|
||||
|
||||
return welzlHelper(pts, #pts, {})
|
||||
end
|
||||
|
||||
--- Calculated optimal intercepting course (Bearing)
|
||||
-- @param DCS#Vec3 A1 Position of flight one
|
||||
-- @param DCS#Vec3 V1 VelocityVector of flight one
|
||||
-- @param DCS#Vec3 A2 Position of flight two
|
||||
-- @param #number V2_speed Speed of A2 in m/s
|
||||
-- @return #number Bearing Bearing course for A2 to take for an intercept of A1 or nil if aspect is hot/cold.
|
||||
function UTILS.CalculateInterceptBearing(A1, V1, A2, V2_speed)
|
||||
|
||||
local function berechne_bearing(richtung)
|
||||
local bearing = math.deg(math.atan2(richtung.x, richtung.y))
|
||||
if bearing < 0 then
|
||||
bearing = bearing + 360
|
||||
end
|
||||
return bearing
|
||||
end
|
||||
|
||||
local function vec_normalize(v)
|
||||
local len = UTILS.Vec3Length(v)
|
||||
if len == 0 then return {x = 0, y = 0, z = 0} end
|
||||
return {x = v.x / len, y = v.y / len, z = v.z / len}
|
||||
end
|
||||
|
||||
|
||||
-- Relative Position von F1 zu F2
|
||||
local rel_pos = UTILS.Vec3Substract(A1, A2)
|
||||
local distance = UTILS.Vec3Length(rel_pos)
|
||||
|
||||
if distance == 0 then
|
||||
return nil -- Bereits am gleichen Ort
|
||||
end
|
||||
|
||||
-- Richtungsvektor von F2 zu F1 (normalisiert)
|
||||
local richtung_zu_f1 = vec_normalize(rel_pos)
|
||||
|
||||
-- Prüfe HOT: F1 fliegt direkt auf F2 zu
|
||||
-- Das ist der Fall wenn V1 in die entgegengesetzte Richtung von rel_pos zeigt
|
||||
local v1_normalisiert = vec_normalize(V1)
|
||||
local annaeherung = UTILS.VecDot(v1_normalisiert, richtung_zu_f1)
|
||||
|
||||
if annaeherung < -0.95 then -- F1 fliegt fast direkt auf F2 zu (Winkel > ~162°)
|
||||
return nil -- Hot
|
||||
end
|
||||
|
||||
-- Prüfe COLD: F1 fliegt parallel weg von F2
|
||||
-- Berechne die Geschwindigkeitsdifferenz
|
||||
local rel_velocity = UTILS.VecSubstract(V1, {x=0, y=0, z=0}) -- V1 relativ zu F2 (falls F2 stillsteht)
|
||||
local flucht_komponente = UTILS.VecDot(vec_normalize(rel_velocity), richtung_zu_f1)
|
||||
|
||||
if flucht_komponente > 0.95 then -- F1 fliegt fast parallel weg (Winkel < ~18°)
|
||||
return nil -- Cold
|
||||
end
|
||||
|
||||
-- Geschwindigkeiten
|
||||
local v1 = UTILS.Vec3Length(V1)
|
||||
local v2 = V2_speed
|
||||
|
||||
-- Löse quadratische Gleichung für Abfangzeit t
|
||||
local a = UTILS.VecDot(V1, V1) - v2 * v2
|
||||
local b = 2 * UTILS.VecDot(rel_pos, V1)
|
||||
local c = UTILS.VecDot(rel_pos, rel_pos)
|
||||
|
||||
local discriminant = b * b - 4 * a * c
|
||||
|
||||
if discriminant < 0 then
|
||||
return nil -- Keine Lösung möglich
|
||||
end
|
||||
|
||||
-- Wähle die positive, kleinste Lösung
|
||||
local t1 = (-b + math.sqrt(discriminant)) / (2 * a)
|
||||
local t2 = (-b - math.sqrt(discriminant)) / (2 * a)
|
||||
|
||||
local t = nil
|
||||
if t1 > 0 and t2 > 0 then
|
||||
t = math.min(t1, t2)
|
||||
elseif t1 > 0 then
|
||||
t = t1
|
||||
elseif t2 > 0 then
|
||||
t = t2
|
||||
else
|
||||
return nil -- Keine positive Lösung
|
||||
end
|
||||
|
||||
-- Berechne Treffpunkt
|
||||
local treffpunkt = UTILS.VecAdd(A1, UTILS.VecScale(V1, t))
|
||||
|
||||
-- Berechne Richtung zum Treffpunkt
|
||||
local richtung = UTILS.VecSubstract(treffpunkt, A2)
|
||||
|
||||
-- Berechne Bearing
|
||||
local bearing = berechne_bearing(richtung)
|
||||
|
||||
return UTILS.Round(bearing,0)
|
||||
end
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
-- @field #table parkingByID Parking spot data table with ID as key.
|
||||
-- @field #table parkingWhitelist List of parking spot terminal IDs considered for spawning.
|
||||
-- @field #table parkingBlacklist List of parking spot terminal IDs **not** considered for spawning.
|
||||
-- @field Core.Zone#ZONE_RADIUS parkingCircle Minimum bounding circle enclosing all parking spots.
|
||||
-- @field #table runways Runways of airdromes.
|
||||
-- @field #AIRBASE.Runway runwayLanding Runway used for landing.
|
||||
-- @field #AIRBASE.Runway runwayTakeoff Runway used for takeoff.
|
||||
-- @field Wrapper.Storage#STORAGE storage The DCS warehouse storage.
|
||||
-- @field #table taxiways Taxiways stored as PATHLINEs.
|
||||
-- @extends Wrapper.Positionable#POSITIONABLE
|
||||
|
||||
--- Wrapper class to handle the DCS Airbase objects:
|
||||
@@ -79,7 +79,6 @@ AIRBASE = {
|
||||
[Airbase.Category.SHIP] = "Ship",
|
||||
},
|
||||
activerwyno = nil,
|
||||
taxiways = {},
|
||||
}
|
||||
|
||||
--- Airbases of the Caucasus map
|
||||
@@ -719,62 +718,62 @@ AIRBASE.SouthAtlantic = {
|
||||
|
||||
--- Airbases of the Sinai map
|
||||
--
|
||||
-- * `AIRBASE.SinaiMap.Abu_Rudeis` Abu Rudeis
|
||||
-- * `AIRBASE.SinaiMap.Abu_Suwayr` Abu Suwayr
|
||||
-- * `AIRBASE.SinaiMap.Al_Bahr_al_Ahmar` Al Bahr al Ahmar
|
||||
-- * `AIRBASE.SinaiMap.Al_Ismailiyah` Al Ismailiyah
|
||||
-- * `AIRBASE.SinaiMap.Al_Khatatbah` Al Khatatbah
|
||||
-- * `AIRBASE.SinaiMap.Al_Mansurah` Al Mansurah
|
||||
-- * `AIRBASE.SinaiMap.Al_Rahmaniyah_Air_Base` Al Rahmaniyah Air Base
|
||||
-- * `AIRBASE.SinaiMap.As_Salihiyah` As Salihiyah
|
||||
-- * `AIRBASE.SinaiMap.AzZaqaziq` AzZaqaziq
|
||||
-- * `AIRBASE.SinaiMap.Baluza` Baluza
|
||||
-- * `AIRBASE.SinaiMap.Ben_Gurion` Ben-Gurion
|
||||
-- * `AIRBASE.SinaiMap.Beni_Suef` Beni Suef
|
||||
-- * `AIRBASE.SinaiMap.Bilbeis_Air_Base` Bilbeis Air Base
|
||||
-- * `AIRBASE.SinaiMap.Bir_Hasanah` Bir Hasanah
|
||||
-- * `AIRBASE.SinaiMap.Birma_Air_Base` Birma Air Base
|
||||
-- * `AIRBASE.SinaiMap.Borg_El_Arab_International_Airport` Borg El Arab International Airport
|
||||
-- * `AIRBASE.SinaiMap.Cairo_International_Airport` Cairo International Airport
|
||||
-- * `AIRBASE.SinaiMap.Cairo_West` Cairo West
|
||||
-- * `AIRBASE.SinaiMap.Damascus_Intl` Damascus Intl
|
||||
-- * `AIRBASE.SinaiMap.Difarsuwar_Airfield` Difarsuwar Airfield
|
||||
-- * `AIRBASE.SinaiMap.Ein_Shamer` Ein Shamer
|
||||
-- * `AIRBASE.SinaiMap.El_Arish` El Arish
|
||||
-- * `AIRBASE.SinaiMap.El_Gora` El Gora
|
||||
-- * `AIRBASE.SinaiMap.El_Minya` El Minya
|
||||
-- * `AIRBASE.SinaiMap.Fayed` Fayed
|
||||
-- * `AIRBASE.SinaiMap.Gebel_El_Basur_Air_Base` Gebel El Basur Air Base
|
||||
-- * `AIRBASE.SinaiMap.Hatzerim` Hatzerim
|
||||
-- * `AIRBASE.SinaiMap.Hatzor` Hatzor
|
||||
-- * `AIRBASE.SinaiMap.Hurghada_International_Airport` Hurghada International Airport
|
||||
-- * `AIRBASE.SinaiMap.Inshas_Airbase` Inshas Airbase
|
||||
-- * `AIRBASE.SinaiMap.Jiyanklis_Air_Base` Jiyanklis Air Base
|
||||
-- * `AIRBASE.SinaiMap.Kedem` Kedem
|
||||
-- * `AIRBASE.SinaiMap.Khalkhalah_Air_Base` Khalkhalah Air Base
|
||||
-- * `AIRBASE.SinaiMap.Kibrit_Air_Base` Kibrit Air Base
|
||||
-- * `AIRBASE.SinaiMap.King_Feisal_Air_Base` King Feisal Air Base
|
||||
-- * `AIRBASE.SinaiMap.Kom_Awshim` Kom Awshim
|
||||
-- * `AIRBASE.SinaiMap.Megiddo` Megiddo
|
||||
-- * `AIRBASE.SinaiMap.Melez` Melez
|
||||
-- * `AIRBASE.SinaiMap.Mezzeh_Air_Base` Mezzeh Air Base
|
||||
-- * `AIRBASE.SinaiMap.Nevatim` Nevatim
|
||||
-- * `AIRBASE.SinaiMap.Ovda` Ovda
|
||||
-- * `AIRBASE.SinaiMap.Palmachim` Palmachim
|
||||
-- * `AIRBASE.SinaiMap.Quwaysina` Quwaysina
|
||||
-- * `AIRBASE.SinaiMap.Rafic_Hariri_Intl` Rafic Hariri Intl
|
||||
-- * `AIRBASE.SinaiMap.Ramat_David` Ramat David
|
||||
-- * `AIRBASE.SinaiMap.Ramon_Airbase` Ramon Airbase
|
||||
-- * `AIRBASE.SinaiMap.Ramon_International_Airport` Ramon International Airport
|
||||
-- * `AIRBASE.SinaiMap.Sde_Dov` Sde Dov
|
||||
-- * `AIRBASE.SinaiMap.Sharm_El_Sheikh_International_Airport` Sharm El Sheikh International Airport
|
||||
-- * `AIRBASE.SinaiMap.St_Catherine` St Catherine
|
||||
-- * `AIRBASE.SinaiMap.Taba_International_Airport` Taba International Airport
|
||||
-- * `AIRBASE.SinaiMap.Tabuk` Tabuk
|
||||
-- * `AIRBASE.SinaiMap.TabukHeliBase` TabukHeliBase
|
||||
-- * `AIRBASE.SinaiMap.Tel_Nof` Tel Nof
|
||||
-- * `AIRBASE.SinaiMap.Wadi_Abu_Rish` Wadi Abu Rish
|
||||
-- * `AIRBASE.SinaiMap.Wadi_al_Jandali` Wadi al Jandali
|
||||
-- * `AIRBASE.Sinai.Abu_Rudeis` Abu Rudeis
|
||||
-- * `AIRBASE.Sinai.Abu_Suwayr` Abu Suwayr
|
||||
-- * `AIRBASE.Sinai.Al_Bahr_al_Ahmar` Al Bahr al Ahmar
|
||||
-- * `AIRBASE.Sinai.Al_Ismailiyah` Al Ismailiyah
|
||||
-- * `AIRBASE.Sinai.Al_Khatatbah` Al Khatatbah
|
||||
-- * `AIRBASE.Sinai.Al_Mansurah` Al Mansurah
|
||||
-- * `AIRBASE.Sinai.Al_Rahmaniyah_Air_Base` Al Rahmaniyah Air Base
|
||||
-- * `AIRBASE.Sinai.As_Salihiyah` As Salihiyah
|
||||
-- * `AIRBASE.Sinai.AzZaqaziq` AzZaqaziq
|
||||
-- * `AIRBASE.Sinai.Baluza` Baluza
|
||||
-- * `AIRBASE.Sinai.Ben_Gurion` Ben-Gurion
|
||||
-- * `AIRBASE.Sinai.Beni_Suef` Beni Suef
|
||||
-- * `AIRBASE.Sinai.Bilbeis_Air_Base` Bilbeis Air Base
|
||||
-- * `AIRBASE.Sinai.Bir_Hasanah` Bir Hasanah
|
||||
-- * `AIRBASE.Sinai.Birma_Air_Base` Birma Air Base
|
||||
-- * `AIRBASE.Sinai.Borg_El_Arab_International_Airport` Borg El Arab International Airport
|
||||
-- * `AIRBASE.Sinai.Cairo_International_Airport` Cairo International Airport
|
||||
-- * `AIRBASE.Sinai.Cairo_West` Cairo West
|
||||
-- * `AIRBASE.Sinai.Damascus_Intl` Damascus Intl
|
||||
-- * `AIRBASE.Sinai.Difarsuwar_Airfield` Difarsuwar Airfield
|
||||
-- * `AIRBASE.Sinai.Ein_Shamer` Ein Shamer
|
||||
-- * `AIRBASE.Sinai.El_Arish` El Arish
|
||||
-- * `AIRBASE.Sinai.El_Gora` El Gora
|
||||
-- * `AIRBASE.Sinai.El_Minya` El Minya
|
||||
-- * `AIRBASE.Sinai.Fayed` Fayed
|
||||
-- * `AIRBASE.Sinai.Gebel_El_Basur_Air_Base` Gebel El Basur Air Base
|
||||
-- * `AIRBASE.Sinai.Hatzerim` Hatzerim
|
||||
-- * `AIRBASE.Sinai.Hatzor` Hatzor
|
||||
-- * `AIRBASE.Sinai.Hurghada_International_Airport` Hurghada International Airport
|
||||
-- * `AIRBASE.Sinai.Inshas_Airbase` Inshas Airbase
|
||||
-- * `AIRBASE.Sinai.Jiyanklis_Air_Base` Jiyanklis Air Base
|
||||
-- * `AIRBASE.Sinai.Kedem` Kedem
|
||||
-- * `AIRBASE.Sinai.Khalkhalah_Air_Base` Khalkhalah Air Base
|
||||
-- * `AIRBASE.Sinai.Kibrit_Air_Base` Kibrit Air Base
|
||||
-- * `AIRBASE.Sinai.King_Feisal_Air_Base` King Feisal Air Base
|
||||
-- * `AIRBASE.Sinai.Kom_Awshim` Kom Awshim
|
||||
-- * `AIRBASE.Sinai.Megiddo` Megiddo
|
||||
-- * `AIRBASE.Sinai.Melez` Melez
|
||||
-- * `AIRBASE.Sinai.Mezzeh_Air_Base` Mezzeh Air Base
|
||||
-- * `AIRBASE.Sinai.Nevatim` Nevatim
|
||||
-- * `AIRBASE.Sinai.Ovda` Ovda
|
||||
-- * `AIRBASE.Sinai.Palmachim` Palmachim
|
||||
-- * `AIRBASE.Sinai.Quwaysina` Quwaysina
|
||||
-- * `AIRBASE.Sinai.Rafic_Hariri_Intl` Rafic Hariri Intl
|
||||
-- * `AIRBASE.Sinai.Ramat_David` Ramat David
|
||||
-- * `AIRBASE.Sinai.Ramon_Airbase` Ramon Airbase
|
||||
-- * `AIRBASE.Sinai.Ramon_International_Airport` Ramon International Airport
|
||||
-- * `AIRBASE.Sinai.Sde_Dov` Sde Dov
|
||||
-- * `AIRBASE.Sinai.Sharm_El_Sheikh_International_Airport` Sharm El Sheikh International Airport
|
||||
-- * `AIRBASE.Sinai.St_Catherine` St Catherine
|
||||
-- * `AIRBASE.Sinai.Taba_International_Airport` Taba International Airport
|
||||
-- * `AIRBASE.Sinai.Tabuk` Tabuk
|
||||
-- * `AIRBASE.Sinai.TabukHeliBase` TabukHeliBase
|
||||
-- * `AIRBASE.Sinai.Tel_Nof` Tel Nof
|
||||
-- * `AIRBASE.Sinai.Wadi_Abu_Rish` Wadi Abu Rish
|
||||
-- * `AIRBASE.Sinai.Wadi_al_Jandali` Wadi al Jandali
|
||||
--
|
||||
-- @field Sinai
|
||||
AIRBASE.Sinai = {
|
||||
@@ -835,6 +834,66 @@ AIRBASE.Sinai = {
|
||||
["Wadi_Abu_Rish"] = "Wadi Abu Rish",
|
||||
["Wadi_al_Jandali"] = "Wadi al Jandali",
|
||||
}
|
||||
---
|
||||
-- @field SinaiMap
|
||||
AIRBASE.SinaiMap = {
|
||||
["Abu_Rudeis"] = "Abu Rudeis",
|
||||
["Abu_Suwayr"] = "Abu Suwayr",
|
||||
["Al_Bahr_al_Ahmar"] = "Al Bahr al Ahmar",
|
||||
["Al_Ismailiyah"] = "Al Ismailiyah",
|
||||
["Al_Khatatbah"] = "Al Khatatbah",
|
||||
["Al_Mansurah"] = "Al Mansurah",
|
||||
["Al_Rahmaniyah_Air_Base"] = "Al Rahmaniyah Air Base",
|
||||
["As_Salihiyah"] = "As Salihiyah",
|
||||
["AzZaqaziq"] = "AzZaqaziq",
|
||||
["Baluza"] = "Baluza",
|
||||
["Ben_Gurion"] = "Ben-Gurion",
|
||||
["Beni_Suef"] = "Beni Suef",
|
||||
["Bilbeis_Air_Base"] = "Bilbeis Air Base",
|
||||
["Bir_Hasanah"] = "Bir Hasanah",
|
||||
["Birma_Air_Base"] = "Birma Air Base",
|
||||
["Borg_El_Arab_International_Airport"] = "Borg El Arab International Airport",
|
||||
["Cairo_International_Airport"] = "Cairo International Airport",
|
||||
["Cairo_West"] = "Cairo West",
|
||||
["Damascus_Intl"] = "Damascus Intl",
|
||||
["Difarsuwar_Airfield"] = "Difarsuwar Airfield",
|
||||
["Ein_Shamer"] = "Ein Shamer",
|
||||
["El_Arish"] = "El Arish",
|
||||
["El_Gora"] = "El Gora",
|
||||
["El_Minya"] = "El Minya",
|
||||
["Fayed"] = "Fayed",
|
||||
["Gebel_El_Basur_Air_Base"] = "Gebel El Basur Air Base",
|
||||
["Hatzerim"] = "Hatzerim",
|
||||
["Hatzor"] = "Hatzor",
|
||||
["Hurghada_International_Airport"] = "Hurghada International Airport",
|
||||
["Inshas_Airbase"] = "Inshas Airbase",
|
||||
["Jiyanklis_Air_Base"] = "Jiyanklis Air Base",
|
||||
["Kedem"] = "Kedem",
|
||||
["Khalkhalah_Air_Base"] = "Khalkhalah Air Base",
|
||||
["Kibrit_Air_Base"] = "Kibrit Air Base",
|
||||
["King_Feisal_Air_Base"] = "King Feisal Air Base",
|
||||
["Kom_Awshim"] = "Kom Awshim",
|
||||
["Megiddo"] = "Megiddo",
|
||||
["Melez"] = "Melez",
|
||||
["Mezzeh_Air_Base"] = "Mezzeh Air Base",
|
||||
["Nevatim"] = "Nevatim",
|
||||
["Ovda"] = "Ovda",
|
||||
["Palmachim"] = "Palmachim",
|
||||
["Quwaysina"] = "Quwaysina",
|
||||
["Rafic_Hariri_Intl"] = "Rafic Hariri Intl",
|
||||
["Ramat_David"] = "Ramat David",
|
||||
["Ramon_Airbase"] = "Ramon Airbase",
|
||||
["Ramon_International_Airport"] = "Ramon International Airport",
|
||||
["Sde_Dov"] = "Sde Dov",
|
||||
["Sharm_El_Sheikh_International_Airport"] = "Sharm El Sheikh International Airport",
|
||||
["St_Catherine"] = "St Catherine",
|
||||
["Taba_International_Airport"] = "Taba International Airport",
|
||||
["Tabuk"] = "Tabuk",
|
||||
["TabukHeliBase"] = "TabukHeliBase",
|
||||
["Tel_Nof"] = "Tel Nof",
|
||||
["Wadi_Abu_Rish"] = "Wadi Abu Rish",
|
||||
["Wadi_al_Jandali"] = "Wadi al Jandali",
|
||||
}
|
||||
|
||||
--- Airbases of the Kola map
|
||||
--
|
||||
@@ -1551,36 +1610,10 @@ AIRBASE.SpotStatus = {
|
||||
RESERVED="Reserved",
|
||||
}
|
||||
|
||||
--- ICAO Codes.
|
||||
-- @type AIRBASE.ICAO
|
||||
AIRBASE.ICAO = {
|
||||
UGTB=AIRBASE.Caucasus.Tbilisi_Lochini,
|
||||
UGKO=AIRBASE.Caucasus.Kutaisi,
|
||||
UG5X=AIRBASE.Caucasus.Kobuleti,
|
||||
UG24=AIRBASE.Caucasus.Soganlug,
|
||||
UG27=AIRBASE.Caucasus.Vaziani,
|
||||
UGSS=AIRBASE.Caucasus.Sukhumi_Babushara,
|
||||
UG23=AIRBASE.Caucasus.Gudauta,
|
||||
URSS=AIRBASE.Caucasus.Sochi_Adler,
|
||||
URMO=AIRBASE.Caucasus.Beslan,
|
||||
URMN=AIRBASE.Caucasus.Nalchik,
|
||||
XRMF=AIRBASE.Caucasus.Mozdok,
|
||||
URMM=AIRBASE.Caucasus.Mineralnye_Vody,
|
||||
URKH=AIRBASE.Caucasus.Maykop_Khanskaya,
|
||||
URKK=AIRBASE.Caucasus.Krasnodar_Pashkovsky,
|
||||
URKL=AIRBASE.Caucasus.Krasnodar_Center,
|
||||
URKG=AIRBASE.Caucasus.Gelendzhik,
|
||||
URKN=AIRBASE.Caucasus.Novorossiysk,
|
||||
URKW=AIRBASE.Caucasus.Krymsk,
|
||||
URKA=AIRBASE.Caucasus.Anapa_Vityazevo,
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Runway data.
|
||||
-- @type AIRBASE.Runway
|
||||
-- @field #string name Runway name.
|
||||
-- @field #string idx Runway ID: heading 070° ==> idx="07".
|
||||
-- @field #string idx Runway ID: heading 070° ==> idx="07". Mostly same as magheading.
|
||||
-- @field #number heading True heading of the runway in degrees.
|
||||
-- @field #number magheading Magnetic heading of the runway in degrees. This is what is marked on the runway.
|
||||
-- @field #number length Length of runway in meters.
|
||||
@@ -1690,6 +1723,10 @@ end
|
||||
else
|
||||
self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName))
|
||||
end
|
||||
|
||||
if Nrunways>0 then
|
||||
self:GetMinimumBoundingCircleFromParkingSpots()
|
||||
end
|
||||
|
||||
-- Debug info.
|
||||
self:T2(string.format("Registered airbase %s", tostring(self.AirbaseName)))
|
||||
@@ -1765,47 +1802,14 @@ end
|
||||
|
||||
--- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase.
|
||||
-- @param #AIRBASE self
|
||||
-- @param #string AirbaseName The name of the airbase. Can also be the ICAO code or the airbase ID.
|
||||
-- @param #string AirbaseName The Airbase Name.
|
||||
-- @return #AIRBASE self
|
||||
function AIRBASE:FindByName( AirbaseName )
|
||||
|
||||
local AirbaseFound = _DATABASE:FindAirbase( AirbaseName )
|
||||
|
||||
if not AirbaseFound then
|
||||
AirbaseFound=self:FindByICAO(AirbaseName)
|
||||
end
|
||||
|
||||
if not AirbaseFound then
|
||||
AirbaseFound=self:FindByID(AirbaseName)
|
||||
end
|
||||
|
||||
return AirbaseFound
|
||||
end
|
||||
|
||||
|
||||
--- Find an AIRBASE in the _DATABASE using the [International Civil Aviation Organization](https://en.wikipedia.org/wiki/ICAO_airport_code) (ICAO) airport code.
|
||||
-- The code consists of four characters. Typically, the first one or two letters of the ICAO code indicate the country and the remaining letters identify the airport.
|
||||
--
|
||||
-- **NOTE** that the ICAO code cannot be retrieved via the DCS API and has to be hard coded into the MOOSE code. Therefore, it is a rare occasion where
|
||||
-- @param #AIRBASE self
|
||||
-- @param #string AirbaseICAO The Airbase ICAO code.
|
||||
-- @return #AIRBASE self
|
||||
function AIRBASE:FindByICAO( AirbaseICAO )
|
||||
|
||||
if AirbaseICAO then
|
||||
|
||||
local name=AIRBASE.ICAO[AirbaseICAO]
|
||||
|
||||
env.info(string.format("FF ICAO=%s, Name=%s", tostring(AirbaseICAO), tostring(name)))
|
||||
|
||||
local Airbase=self:FindByName(name)
|
||||
return Airbase
|
||||
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Find a AIRBASE in the _DATABASE by its ID.
|
||||
-- @param #AIRBASE self
|
||||
-- @param #number id Airbase ID.
|
||||
@@ -2118,129 +2122,6 @@ function AIRBASE:IsShip()
|
||||
return self.isShip
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Taxi ways
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add a taxiway from a given PATHLINE.
|
||||
-- @param #AIRBASE self
|
||||
-- @param Core.Pathline#PATHLINE TaxiPathline Pathline of the taxi way or name of pathline as #string.
|
||||
-- @param #string Name Name of the taxi way, *e.g.* "Alpha", or "Alpha-Kilo". Default is name of pathline.
|
||||
-- @return #AIRBASE self
|
||||
function AIRBASE:AddTaxiway(TaxiPathline, Name)
|
||||
|
||||
-- If passed as string, get pathline.
|
||||
if type(TaxiPathline)=="string" then
|
||||
TaxiPathline=PATHLINE:FindByName(TaxiPathline)
|
||||
end
|
||||
|
||||
-- Set name.
|
||||
Name=Name or TaxiPathline:GetName()
|
||||
|
||||
-- Create a deep copy.
|
||||
local taxiway=UTILS.DeepCopy(TaxiPathline) --Core.Pathline#PATHLINE
|
||||
|
||||
-- Set name.
|
||||
taxiway.name=Name
|
||||
|
||||
-- Add to taxiways.
|
||||
self.taxiways[Name]=taxiway
|
||||
|
||||
|
||||
--self:I(self.taxiways)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Find the shortest path using taxiways to get from given coodinates A to B on the airbase.
|
||||
-- Note that the taxi ways have to be manually added with the `AIRBASE:AddTaxiway()` function.
|
||||
-- @param #AIRBASE self
|
||||
-- @param Core.Point#COORDINATE StartCoord Start coordinate.
|
||||
-- @param Core.Point#COORDINATE EndCoord End coordinate.
|
||||
-- @return Core.Pathline#PATHLINE Shortest path on taxiways from `StartCoord` to `EndCoord`.
|
||||
-- @return #table Table of used taxi way pathlines.
|
||||
function AIRBASE:FindTaxiwaysFromAtoB(StartCoord, EndCoord)
|
||||
|
||||
-- Create A* pathfinding.
|
||||
local astar=ASTAR:New()
|
||||
|
||||
-- Add pathlines of taxiways of airport.
|
||||
for _,_taxiway in pairs(self.taxiways) do
|
||||
local taxiway=_taxiway --Core.Pathline#PATHLINE
|
||||
|
||||
astar:AddNodeFromPathlineName(taxiway)
|
||||
end
|
||||
|
||||
-- Set cost function.
|
||||
astar:SetCostDist2D()
|
||||
|
||||
-- Set valid neighbours to be on the same pathline or at most 10 meters between nodes to jump from one pathline/taxiway to another.
|
||||
astar:SetValidNeighbourPathline(10)
|
||||
|
||||
-- Set start and end coordinates.
|
||||
astar:SetStartCoordinate(StartCoord)
|
||||
astar:SetEndCoordinate(EndCoord)
|
||||
|
||||
-- Get pathline.
|
||||
local taxipath, nodes=astar:GetPathline()
|
||||
|
||||
local taxiways=astar:GetPathlinesFromNodes(nodes)
|
||||
|
||||
return taxipath, taxiways
|
||||
end
|
||||
|
||||
--- Get closest taxiway from a given reference coordinate.
|
||||
-- @param #AIRBASE self
|
||||
-- @param Core.Point#COORDINATE Coord Reference coordinate.
|
||||
-- @return Core.Pathline#PATHLINE Taxiway.
|
||||
-- @return #number Distance to taxiway in meters.
|
||||
-- @return Core.Point#COORDINATE Coordinate on taxiway closest to reference coordinate.
|
||||
-- @return Core.Pathline#PATHLINE.Segment Segment of the taxiway closest to the reference coordinate.
|
||||
function AIRBASE:GetClosestTaxiway(Coord)
|
||||
|
||||
local taxipath=nil
|
||||
local distmin=math.huge
|
||||
local coordmin=nil
|
||||
local segmin=nil
|
||||
|
||||
for name,_pathline in pairs(self.taxiways) do
|
||||
local pathline=_pathline --Core.Pathline#PATHLINE
|
||||
|
||||
local coord, dist, segment=pathline:GetClosestPoint3D(Coord)
|
||||
|
||||
if dist<distmin then
|
||||
taxipath=pathline
|
||||
coordmin=coord
|
||||
distmin=dist
|
||||
segmin=segment
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return taxipath, distmin, coordmin, segmin
|
||||
end
|
||||
|
||||
|
||||
--- Find the shortest path using taxiways to get from given parking spot to the starting point of a runway.
|
||||
-- Note that the taxi ways have to be manually added with the `AIRBASE:AddTaxiway()` function.
|
||||
-- @param #AIRBASE self
|
||||
-- @param #AIRBASE.ParkingSpot ParkingSpot Parking spot.
|
||||
-- @param #AIRBASE.Runway Runway The runway. If none is given, we take the active runway for takeoff.
|
||||
-- @return Core.Pathline#PATHLINE Shortest path on taxiways from `StartCoord` to `EndCoord`.
|
||||
-- @return #table Table of used taxi way pathlines.
|
||||
function AIRBASE:FindTaxiwaysParkingToRunway(ParkingSpot, Runway)
|
||||
|
||||
Runway=Runway or self:GetActiveRunwayTakeoff()
|
||||
|
||||
local StartCoord=ParkingSpot.Coordinate
|
||||
local EndCoord=Runway.position
|
||||
|
||||
local taxipath, taxiways=self:FindTaxiwaysFromAtoB(StartCoord,EndCoord)
|
||||
|
||||
return taxipath, taxiways
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Parking
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -2288,7 +2169,7 @@ end
|
||||
function AIRBASE:GetParkingSpotsNumber(termtype)
|
||||
|
||||
-- Get free parking spots data.
|
||||
local parkingdata=self:GetParkingData(false)
|
||||
local parkingdata=self:GetParkingData(false) or {}
|
||||
|
||||
local nspots=0
|
||||
for _,parkingspot in pairs(parkingdata) do
|
||||
@@ -2308,7 +2189,7 @@ end
|
||||
function AIRBASE:GetFreeParkingSpotsNumber(termtype, allowTOAC)
|
||||
|
||||
-- Get free parking spots data.
|
||||
local parkingdata=self:GetParkingData(true)
|
||||
local parkingdata=self:GetParkingData(true) or {}
|
||||
|
||||
local nfree=0
|
||||
for _,parkingspot in pairs(parkingdata) do
|
||||
@@ -2331,7 +2212,7 @@ end
|
||||
function AIRBASE:GetFreeParkingSpotsCoordinates(termtype, allowTOAC)
|
||||
|
||||
-- Get free parking spots data.
|
||||
local parkingdata=self:GetParkingData(true)
|
||||
local parkingdata=self:GetParkingData(true) or {}
|
||||
|
||||
-- Put coordinates of free spots into table.
|
||||
local spots={}
|
||||
@@ -2375,13 +2256,62 @@ function AIRBASE:GetParkingSpotsCoordinates(termtype)
|
||||
return spots
|
||||
end
|
||||
|
||||
--- Get the DCS#Vec2s of all parking spots at an airbase. Optionally only those of a specific terminal type. Spots on runways are excluded if not explicitly requested by terminal type.
|
||||
-- @param #AIRBASE self
|
||||
-- @param #AIRBASE.TerminalType termtype (Optional) Terminal type. Default all.
|
||||
-- @return #table Table of DCS#Vec2 of parking spots.
|
||||
function AIRBASE:GetParkingSpotsVec2s(termtype)
|
||||
|
||||
-- Get all parking spots data.
|
||||
local parkingdata=self:GetParkingData(false)
|
||||
|
||||
-- Put coordinates of free spots into table.
|
||||
local spots={}
|
||||
for _,parkingspot in ipairs(parkingdata) do
|
||||
|
||||
-- Coordinates on runway are not returned unless explicitly requested.
|
||||
if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then
|
||||
|
||||
-- Get coordinate from Vec3 terminal position.
|
||||
local vec2 = { x = parkingspot.vTerminalPos.x, y = parkingspot.vTerminalPos.z }
|
||||
|
||||
-- Add to table.
|
||||
table.insert(spots, vec2)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return spots
|
||||
end
|
||||
|
||||
--- Get the the bounding circular zone around all parking spots of an airbase.
|
||||
-- @param #AIRBASE self
|
||||
-- @param #boolean mark (Optional) Draw zone on map on first call of this function.
|
||||
-- @return Core.Zone#ZONE_RADIUS BoundingZone
|
||||
function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark)
|
||||
if self.isAirdrome then
|
||||
if not self.parkingCircle then
|
||||
local spots = self:GetParkingSpotsVec2s()
|
||||
if #spots == 0 then return self.AirbaseZone end
|
||||
local center, radius = UTILS.GetMinimumBoundingCircle(spots)
|
||||
self.parkingCircle = ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle",center,radius+50)
|
||||
if mark == true then
|
||||
self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3)
|
||||
end
|
||||
end
|
||||
return self.parkingCircle
|
||||
else
|
||||
return self.AirbaseZone
|
||||
end
|
||||
end
|
||||
|
||||
--- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase.
|
||||
-- @param #AIRBASE self
|
||||
-- @return#AIRBASE self
|
||||
function AIRBASE:_InitParkingSpots()
|
||||
|
||||
-- Get parking data of all spots (free or occupied)
|
||||
local parkingdata=self:GetParkingData(false)
|
||||
local parkingdata=self:GetParkingData(false) or {}
|
||||
|
||||
-- Init table.
|
||||
self.parking={}
|
||||
@@ -2457,10 +2387,10 @@ end
|
||||
function AIRBASE:GetParkingSpotsTable(termtype)
|
||||
|
||||
-- Get parking data of all spots (free or occupied)
|
||||
local parkingdata=self:GetParkingData(false)
|
||||
local parkingdata=self:GetParkingData(false) or {}
|
||||
|
||||
-- Get parking data of all free spots.
|
||||
local parkingfree=self:GetParkingData(true)
|
||||
local parkingfree=self:GetParkingData(true) or {}
|
||||
|
||||
-- Function to ckeck if any parking spot is free.
|
||||
local function _isfree(_tocheck)
|
||||
@@ -2509,7 +2439,7 @@ end
|
||||
function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
|
||||
|
||||
-- Get parking data of all free spots.
|
||||
local parkingfree=self:GetParkingData(true)
|
||||
local parkingfree=self:GetParkingData(true) or {}
|
||||
|
||||
-- Put coordinates of free spots into table.
|
||||
local freespots={}
|
||||
@@ -2539,7 +2469,7 @@ end
|
||||
function AIRBASE:GetParkingSpotData(TerminalID)
|
||||
|
||||
-- Get parking data.
|
||||
local parkingdata=self:GetParkingSpotsTable()
|
||||
local parkingdata=self:GetParkingSpotsTable() or {}
|
||||
|
||||
for _,_spot in pairs(parkingdata) do
|
||||
local spot=_spot --#AIRBASE.ParkingSpot
|
||||
@@ -2565,7 +2495,7 @@ function AIRBASE:MarkParkingSpots(termtype, mark)
|
||||
end
|
||||
|
||||
-- Get parking data from getParking() wrapper function.
|
||||
local parkingdata=self:GetParkingSpotsTable(termtype)
|
||||
local parkingdata=self:GetParkingSpotsTable(termtype) or {}
|
||||
|
||||
-- Get airbase name.
|
||||
local airbasename=self:GetName()
|
||||
@@ -2680,7 +2610,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
|
||||
local markobstacles=false
|
||||
|
||||
-- Loop over all known parking spots
|
||||
for _,parkingspot in pairs(parkingdata) do
|
||||
for _,parkingspot in pairs(parkingdata or {}) do
|
||||
|
||||
-- Coordinate of the parking spot.
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
|
||||
@@ -2439,7 +2439,7 @@ end
|
||||
-- @return #table The mission route defined by points.
|
||||
function GROUP:GetTaskRoute()
|
||||
--self:F2( self.GroupName )
|
||||
if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then
|
||||
if _DATABASE.Templates.Groups[self.GroupName] and _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then
|
||||
return UTILS.DeepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points )
|
||||
else
|
||||
return {}
|
||||
@@ -2467,7 +2467,7 @@ function GROUP:CopyRoute( Begin, End, Randomize, Radius )
|
||||
|
||||
--self:T3( { GroupName } )
|
||||
|
||||
local Template = _DATABASE.Templates.Groups[GroupName].Template
|
||||
local Template = _DATABASE.Templates.Groups[GroupName] and _DATABASE.Templates.Groups[GroupName].Template or nil
|
||||
|
||||
if Template then
|
||||
if not Begin then
|
||||
@@ -2491,7 +2491,7 @@ function GROUP:CopyRoute( Begin, End, Randomize, Radius )
|
||||
end
|
||||
return Points
|
||||
else
|
||||
error( "Template not found for Group : " .. GroupName )
|
||||
BASE:E( "Template not found for Group : " .. GroupName )
|
||||
end
|
||||
|
||||
return nil
|
||||
@@ -2944,6 +2944,16 @@ function GROUP:EnableEmission(switch)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set a formation for this group.
|
||||
-- @param #GROUP self
|
||||
-- @param #number Formation See. ENUMS.Formation or [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) for options.
|
||||
-- @return #GROUP self
|
||||
function GROUP:SetFormation(Formation)
|
||||
self:SetOption(AI.Option.Air.id.FORMATION,Formation)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch on/off invisible flag for the group.
|
||||
-- @param #GROUP self
|
||||
-- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled.
|
||||
|
||||
@@ -485,7 +485,7 @@ function NET:GetPlayerIDByName(Name)
|
||||
if not Name then return nil end
|
||||
local playerList = net.get_player_list()
|
||||
for i=1,#playerList do
|
||||
local playerName = net.get_name(i)
|
||||
local playerName = net.get_name(playerList[i])
|
||||
if playerName == Name then
|
||||
return playerList[i]
|
||||
end
|
||||
|
||||
@@ -729,16 +729,23 @@ end
|
||||
|
||||
--- Returns the altitude above sea level of the POSITIONABLE.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level.
|
||||
-- @return DCS#Distance The altitude of the POSITIONABLE.
|
||||
-- @return #nil The POSITIONABLE is not existing or alive.
|
||||
function POSITIONABLE:GetAltitude()
|
||||
function POSITIONABLE:GetAltitude(FromGround)
|
||||
self:F2()
|
||||
|
||||
local DCSPositionable = self:GetDCSObject()
|
||||
|
||||
if DCSPositionable then
|
||||
local PositionablePointVec3 = DCSPositionable:getPoint() -- DCS#Vec3
|
||||
return PositionablePointVec3.y
|
||||
local altitude = 0
|
||||
local point = DCSPositionable:getPoint() --DCS#Vec3
|
||||
altitude = point.y
|
||||
if FromGround then
|
||||
local land = land.getHeight({ x = point.x, y = point.z }) or 0
|
||||
altitude = altitude - land
|
||||
end
|
||||
return altitude
|
||||
end
|
||||
|
||||
BASE:E( { "Cannot GetAltitude", Positionable = self, Alive = self:IsAlive() } )
|
||||
|
||||
@@ -75,7 +75,7 @@ function SCENERY:Register( SceneryName, SceneryObject, SceneryZone )
|
||||
self.Vector = (self.Vec3 and VECTOR) and VECTOR:NewFromVec(self.Vec3) or nil
|
||||
end
|
||||
|
||||
if SceneryObject then
|
||||
if SceneryObject and SceneryObject.getPoint then
|
||||
local vec3 = SceneryObject:getPoint()
|
||||
self.Vec3 = { x = vec3.x, y = vec3.y, z = vec3.z }
|
||||
self.Vec2 = { x = vec3.x, y = vec3.z }
|
||||
|
||||
@@ -1352,9 +1352,7 @@ function UNIT:GetThreatLevel()
|
||||
end
|
||||
|
||||
ThreatText = ThreatLevels[ThreatLevel + 1]
|
||||
end
|
||||
|
||||
if self:IsAir() then
|
||||
elseif self:IsAir() then
|
||||
|
||||
local ThreatLevels = {
|
||||
[1] = "Unarmed",
|
||||
@@ -1397,9 +1395,7 @@ function UNIT:GetThreatLevel()
|
||||
end
|
||||
|
||||
ThreatText = ThreatLevels[ThreatLevel + 1]
|
||||
end
|
||||
|
||||
if self:IsShip() then
|
||||
elseif self:IsShip() then
|
||||
|
||||
--["Aircraft Carriers"] = {"Heavy armed ships",},
|
||||
--["Cruisers"] = {"Heavy armed ships",},
|
||||
@@ -1953,3 +1949,26 @@ end
|
||||
function UNIT:SetValidateAndRepositionGroundUnits(Enabled)
|
||||
self.ValidateAndRepositionGroundUnits = Enabled
|
||||
end
|
||||
|
||||
--- Get the max kgs of fuel this unit can hold in its *internal* tank(s) and overall (with external tanks) in kgs.
|
||||
-- @param #UNIT self
|
||||
-- @return #number InternalFuel Max internal fuel in kgs. Zero if it cannot be determined.
|
||||
-- @return #number Overall Fuel Overall max in case there are external tanks in kgs. Zero if it cannot be determined.
|
||||
function UNIT:GetFuelMassMax()
|
||||
local Desc = self:GetDesc() or {}
|
||||
local massFuelMax=Desc.fuelMassMax or 0
|
||||
local relFuel=math.min(self:GetFuel() or 1.0, 1.0) -- We take 1.0 as max in case of external fuel tanks.
|
||||
local massFuel=massFuelMax*relFuel
|
||||
return massFuel, massFuelMax
|
||||
end
|
||||
|
||||
--- Get the current kgs of fuel this unit holds in all of its tanks.
|
||||
-- @param #UNIT self
|
||||
-- @param #number Filling Fuel in kgs.
|
||||
function UNIT:GetCurrentFuelKgs()
|
||||
local fuel, maxfuel = self:GetFuelMassMax()
|
||||
local relfuel = self:GetFuel()
|
||||
local maxfilling = math.max(fuel,maxfuel)
|
||||
local mass = maxfilling*relfuel
|
||||
return mass
|
||||
end
|
||||
|
||||
@@ -350,7 +350,7 @@ end
|
||||
-- myweapon:SetFuncImpact(OnImpact)
|
||||
--
|
||||
-- -- Start tracking.
|
||||
-- myweapon:Track()
|
||||
-- myweapon:StartTrack()
|
||||
--
|
||||
function WEAPON:SetFuncImpact(FuncImpact, ...)
|
||||
self.impactFunc=FuncImpact
|
||||
|
||||
@@ -104,6 +104,7 @@ Ops/FlightControl.lua
|
||||
Ops/PlayerTask.lua
|
||||
Ops/PlayerRecce.lua
|
||||
Ops/EasyGCICAP.lua
|
||||
Ops/EasyA2G.lua
|
||||
Ops/OpsZone.lua
|
||||
Ops/ArmyGroup.lua
|
||||
Ops/OpsTransport.lua
|
||||
|
||||
Reference in New Issue
Block a user