mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-07-20 01:34:05 +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 }}
|
if: ${{ vars.SKIP_LUACHECK != true }}
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
luacheck --std=lua51c --config=.luacheckrc -gurasqq "Moose Development/Moose"
|
luacheck --std=lua51c --config=.luacheckrc -guras "Moose Development/Moose"
|
||||||
|
|
||||||
- name: Run LuaSrcDiet
|
- name: Run LuaSrcDiet
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -157,8 +157,6 @@ ASTAR = {
|
|||||||
-- @field #number surfacetype Surface type.
|
-- @field #number surfacetype Surface type.
|
||||||
-- @field #table valid Cached valid/invalid nodes.
|
-- @field #table valid Cached valid/invalid nodes.
|
||||||
-- @field #table cost Cached cost.
|
-- @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.
|
--- ASTAR infinity.
|
||||||
-- @field #number INF
|
-- @field #number INF
|
||||||
@@ -166,7 +164,7 @@ ASTAR.INF=1/0
|
|||||||
|
|
||||||
--- ASTAR class version.
|
--- ASTAR class version.
|
||||||
-- @field #string version
|
-- @field #string version
|
||||||
ASTAR.version="0.5.0"
|
ASTAR.version="0.4.0"
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- TODO list
|
-- TODO list
|
||||||
@@ -174,7 +172,6 @@ ASTAR.version="0.5.0"
|
|||||||
|
|
||||||
-- TODO: Add more valid neighbour functions.
|
-- TODO: Add more valid neighbour functions.
|
||||||
-- TODO: Write docs.
|
-- TODO: Write docs.
|
||||||
-- DONE: Add pathlines for seach/valid neighbours.
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- Constructor
|
-- Constructor
|
||||||
@@ -247,10 +244,7 @@ end
|
|||||||
function ASTAR:AddNode(Node)
|
function ASTAR:AddNode(Node)
|
||||||
|
|
||||||
self.nodes[Node.id]=Node
|
self.nodes[Node.id]=Node
|
||||||
self.Nnodes=self.Nnodes+1
|
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))
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -268,47 +262,6 @@ function ASTAR:AddNodeFromCoordinate(Coordinate)
|
|||||||
return node
|
return node
|
||||||
end
|
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.
|
--- Check if the coordinate of a node has is at a valid surface type.
|
||||||
-- @param #ASTAR self
|
-- @param #ASTAR self
|
||||||
-- @param #ASTAR.Node Node The node to be added.
|
-- @param #ASTAR.Node Node The node to be added.
|
||||||
@@ -387,18 +340,6 @@ function ASTAR:SetValidNeighbourRoad(MaxDistance)
|
|||||||
return self
|
return self
|
||||||
end
|
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.
|
--- 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.
|
-- 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.
|
-- Very often the distance between nodes is a good measure for the cost.
|
||||||
@@ -443,7 +384,7 @@ end
|
|||||||
-- @return #ASTAR self
|
-- @return #ASTAR self
|
||||||
function ASTAR:SetCostRoad()
|
function ASTAR:SetCostRoad()
|
||||||
|
|
||||||
self:SetCostFunction(ASTAR.Road)
|
self:SetCostFunction(ASTAR)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -603,55 +544,6 @@ function ASTAR.Road(nodeA, nodeB)
|
|||||||
|
|
||||||
end
|
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.
|
--- Function to check if distance between two nodes is less than a threshold distance.
|
||||||
-- @param #ASTAR.Node nodeA First node.
|
-- @param #ASTAR.Node nodeA First node.
|
||||||
-- @param #ASTAR.Node nodeB Other node.
|
-- @param #ASTAR.Node nodeB Other node.
|
||||||
@@ -675,9 +567,7 @@ end
|
|||||||
-- @param #ASTAR.Node nodeB Other node.
|
-- @param #ASTAR.Node nodeB Other node.
|
||||||
-- @return #number Distance between the two nodes.
|
-- @return #number Distance between the two nodes.
|
||||||
function ASTAR.Dist2D(nodeA, nodeB)
|
function ASTAR.Dist2D(nodeA, nodeB)
|
||||||
local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate)
|
local dist=nodeA.coordinate:Get2DDistance(nodeB)
|
||||||
--local text=string.format("FF Cost Dist2D NodeA=%d-->NodeB=%d = %.1f", nodeA.id, nodeB.id, dist)
|
|
||||||
--env.info(text)
|
|
||||||
return dist
|
return dist
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -704,7 +594,7 @@ function ASTAR.DistRoad(nodeA, nodeB)
|
|||||||
local dist=0
|
local dist=0
|
||||||
|
|
||||||
for i=2,#path do
|
for i=2,#path do
|
||||||
local b=path[i] --DCS#Vec2
|
local b=path[i] --DCS#Vec2
|
||||||
local a=path[i-1] --DCS#Vec2
|
local a=path[i-1] --DCS#Vec2
|
||||||
|
|
||||||
dist=dist+UTILS.VecDist2D(a,b)
|
dist=dist+UTILS.VecDist2D(a,b)
|
||||||
@@ -714,6 +604,7 @@ function ASTAR.DistRoad(nodeA, nodeB)
|
|||||||
return dist
|
return dist
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
return math.huge
|
return math.huge
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -723,11 +614,10 @@ end
|
|||||||
|
|
||||||
--- Find the closest node from a given coordinate.
|
--- Find the closest node from a given coordinate.
|
||||||
-- @param #ASTAR self
|
-- @param #ASTAR self
|
||||||
-- @param Core.Point#COORDINATE Coordinate Reference coordinate.
|
-- @param Core.Point#COORDINATE Coordinate.
|
||||||
-- @param #table ExcludeNodes Table of nodes that are excluded.
|
-- @return #ASTAR.Node Cloest node to the coordinate.
|
||||||
-- @return #ASTAR.Node Closest node to the coordinate.
|
|
||||||
-- @return #number Distance to closest node in meters.
|
-- @return #number Distance to closest node in meters.
|
||||||
function ASTAR:FindClosestNode(Coordinate, ExcludeNodes)
|
function ASTAR:FindClosestNode(Coordinate)
|
||||||
|
|
||||||
local distMin=math.huge
|
local distMin=math.huge
|
||||||
local closeNode=nil
|
local closeNode=nil
|
||||||
@@ -735,15 +625,11 @@ function ASTAR:FindClosestNode(Coordinate, ExcludeNodes)
|
|||||||
for _,_node in pairs(self.nodes) do
|
for _,_node in pairs(self.nodes) do
|
||||||
local node=_node --#ASTAR.Node
|
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
|
||||||
if dist<distMin then
|
closeNode=node
|
||||||
distMin=dist
|
|
||||||
closeNode=node
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -751,162 +637,38 @@ function ASTAR:FindClosestNode(Coordinate, ExcludeNodes)
|
|||||||
return closeNode, distMin
|
return closeNode, distMin
|
||||||
end
|
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.
|
--- Find the start node.
|
||||||
-- @param #ASTAR self
|
-- @param #ASTAR self
|
||||||
|
-- @param #ASTAR.Node Node The node to be added to the nodes table.
|
||||||
-- @return #ASTAR self
|
-- @return #ASTAR self
|
||||||
function ASTAR:FindStartNode()
|
function ASTAR:FindStartNode()
|
||||||
|
|
||||||
|
local node, dist=self:FindClosestNode(self.startCoord)
|
||||||
|
|
||||||
-- Find the closest pathline to the
|
self.startNode=node
|
||||||
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)
|
|
||||||
|
|
||||||
if pathline and vec3 and dist and dist2>dist then
|
if dist>1000 then
|
||||||
|
self:T(self.lid.."Adding start node to node grid!")
|
||||||
-- Create a node on the closest pathline so we first go straight there and then along the pathline.
|
self:AddNode(node)
|
||||||
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
|
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
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Find the end node.
|
--- Add a node.
|
||||||
-- @param #ASTAR self
|
-- @param #ASTAR self
|
||||||
|
-- @param #ASTAR.Node Node The node to be added to the nodes table.
|
||||||
-- @return #ASTAR self
|
-- @return #ASTAR self
|
||||||
function ASTAR:FindEndNode()
|
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, dist=self:FindClosestNode(self.endCoord)
|
||||||
local node=self:AddNodeFromCoordinate(COORDINATE:NewFromVec3(vec3))
|
|
||||||
|
self.endNode=node
|
||||||
-- We also need the point.
|
|
||||||
local point=pathline:AddPointFromVec3(vec3, nil, s.p1)
|
if dist>1000 then
|
||||||
|
self:T(self.lid.."Adding end node to node grid!")
|
||||||
-- Add pathline parameters to node.
|
self:AddNode(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))
|
|
||||||
end
|
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
|
return self
|
||||||
end
|
end
|
||||||
@@ -922,21 +684,12 @@ end
|
|||||||
-- @return #table Table of nodes from start to finish.
|
-- @return #table Table of nodes from start to finish.
|
||||||
function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
|
function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
|
||||||
|
|
||||||
-- self:FindStartNode()
|
self:FindStartNode()
|
||||||
-- self:FindEndNode()
|
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})
|
|
||||||
|
|
||||||
local nodes=self.nodes
|
local nodes=self.nodes
|
||||||
local start=self.startNode
|
local start=self.startNode
|
||||||
local goal=self.endNode
|
local goal=self.endNode
|
||||||
|
|
||||||
-- Debug info.
|
|
||||||
self:T(self.lid..string.format("GetPath Start Node=%d, End Node=%d", start.id, goal.id))
|
|
||||||
|
|
||||||
-- Sets.
|
-- Sets.
|
||||||
local openset = {}
|
local openset = {}
|
||||||
@@ -993,12 +746,7 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
|
|||||||
text=text..string.format(", OS Time %.6f sec", dT)
|
text=text..string.format(", OS Time %.6f sec", dT)
|
||||||
end
|
end
|
||||||
text=text..string.format(", Nvalid=%d [%d cached]", self.nvalid, self.nvalidcache)
|
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(", 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
|
|
||||||
self:T(self.lid..text)
|
self:T(self.lid..text)
|
||||||
|
|
||||||
return path
|
return path
|
||||||
@@ -1011,16 +759,13 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
|
|||||||
|
|
||||||
-- Get neighbour nodes.
|
-- Get neighbour nodes.
|
||||||
local neighbors=self:_NeighbourNodes(current, nodes)
|
local neighbors=self:_NeighbourNodes(current, nodes)
|
||||||
|
|
||||||
-- Loop over neighbours.
|
-- Loop over neighbours.
|
||||||
for _,neighbor in pairs(neighbors) do
|
for _,neighbor in pairs(neighbors) do
|
||||||
|
|
||||||
-- Node is not in closed set.
|
|
||||||
if self:_NotIn(closedset, neighbor.id) then
|
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:_DistNodes(current, neighbor)
|
|
||||||
local tentative_g_score=g_score[current.id] + self:_HeuristicCost(current, neighbor)
|
|
||||||
|
|
||||||
if self:_NotIn(openset, neighbor.id) or tentative_g_score < g_score[neighbor.id] then
|
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
|
return nil -- no valid path
|
||||||
end
|
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
|
-- A* pathfinding helper functions
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -1133,18 +811,16 @@ function ASTAR:_HeuristicCost(nodeA, nodeB)
|
|||||||
local cost=nodeA.cost[nodeB.id]
|
local cost=nodeA.cost[nodeB.id]
|
||||||
if cost~=nil then
|
if cost~=nil then
|
||||||
self.ncostcache=self.ncostcache+1
|
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
|
return cost
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local cost=nil
|
||||||
if self.CostFunc then
|
if self.CostFunc then
|
||||||
cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg))
|
cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg))
|
||||||
else
|
else
|
||||||
cost=self:_DistNodes(nodeA, nodeB)
|
cost=self:_DistNodes(nodeA, nodeB)
|
||||||
end
|
end
|
||||||
|
|
||||||
self:T(self.lid..string.format("Cost nodeA=%d --> nodeB=%d = %.1f", nodeA.id, nodeB.id, cost))
|
|
||||||
|
|
||||||
nodeA.cost[nodeB.id]=cost
|
nodeA.cost[nodeB.id]=cost
|
||||||
nodeB.cost[nodeA.id]=cost -- Symmetric problem.
|
nodeB.cost[nodeA.id]=cost -- Symmetric problem.
|
||||||
|
|
||||||
@@ -1158,10 +834,9 @@ end
|
|||||||
-- @return #boolean If true, transition between nodes is possible.
|
-- @return #boolean If true, transition between nodes is possible.
|
||||||
function ASTAR:_IsValidNeighbour(node, neighbor)
|
function ASTAR:_IsValidNeighbour(node, neighbor)
|
||||||
|
|
||||||
-- Counter of function calls.
|
-- Counter.
|
||||||
self.nvalid=self.nvalid+1
|
self.nvalid=self.nvalid+1
|
||||||
|
|
||||||
-- Check if neighbour is in cached set.
|
|
||||||
local valid=node.valid[neighbor.id]
|
local valid=node.valid[neighbor.id]
|
||||||
if valid~=nil then
|
if valid~=nil then
|
||||||
--env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id))
|
--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
|
return valid
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if this is a valid neighbour.
|
|
||||||
local valid=nil
|
local valid=nil
|
||||||
if self.ValidNeighbourFunc then
|
if self.ValidNeighbourFunc then
|
||||||
valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg))
|
valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg))
|
||||||
else
|
else
|
||||||
-- If no valid neighbour function is defined, we assume all nodes are valid neighbours.
|
|
||||||
valid=true
|
valid=true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Cache valid neighbour.
|
|
||||||
node.valid[neighbor.id]=valid
|
node.valid[neighbor.id]=valid
|
||||||
neighbor.valid[node.id]=valid -- Symmetric problem.
|
neighbor.valid[node.id]=valid -- Symmetric problem.
|
||||||
|
|
||||||
@@ -1212,9 +884,6 @@ function ASTAR:_LowestFscore(set, f_score)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Debug info.
|
|
||||||
self:T(self.lid..string.format("Lowest Fscore=%.1f, Node=%s", lowest, tostring(bestNode)))
|
|
||||||
|
|
||||||
return self.nodes[bestNode]
|
return self.nodes[bestNode]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1259,46 +928,16 @@ end
|
|||||||
-- @param #table map Map.
|
-- @param #table map Map.
|
||||||
-- @param #ASTAR.Node current_node The current node.
|
-- @param #ASTAR.Node current_node The current node.
|
||||||
-- @return #table Unwinded path.
|
-- @return #table Unwinded path.
|
||||||
function ASTAR:_UnwindPath(flat_path, map, current_node)
|
function ASTAR:_UnwindPath( flat_path, map, current_node )
|
||||||
|
|
||||||
local previous_node=map[current_node]
|
if map [current_node] then
|
||||||
|
table.insert (flat_path, 1, map[current_node])
|
||||||
if previous_node then
|
return self:_UnwindPath(flat_path, map, map[current_node])
|
||||||
table.insert(flat_path, 1, previous_node)
|
|
||||||
return self:_UnwindPath(flat_path, map, previous_node)
|
|
||||||
else
|
else
|
||||||
-- No previous node ==> return path.
|
|
||||||
return flat_path
|
return flat_path
|
||||||
end
|
end
|
||||||
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
|
if Event.IniDCSUnit then
|
||||||
-- Player entering a CA slot
|
-- 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()
|
local IsPlayer = Event.IniDCSUnit:getPlayerName()
|
||||||
if IsPlayer then
|
if IsPlayer then
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ EVENT = {
|
|||||||
ClassName = "EVENT",
|
ClassName = "EVENT",
|
||||||
ClassID = 0,
|
ClassID = 0,
|
||||||
MissionEnd = false,
|
MissionEnd = false,
|
||||||
|
CreateMarkCoordinateOnEvent = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000
|
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 #string WeaponName Name of the weapon.
|
||||||
-- @field DCS#Unit WeaponTgtDCSUnit Target DCS unit 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 Core.Zone#ZONE Zone The zone object.
|
||||||
-- @field #string ZoneName The name of the zone.
|
-- @field #string ZoneName The name of the zone.
|
||||||
--
|
--
|
||||||
-- @field Wrapper.DynamicCargo#DYNAMICCARGO IniDynamicCargo The dynamic cargo object.
|
-- @field Wrapper.DynamicCargo#DYNAMICCARGO IniDynamicCargo The dynamic cargo object.
|
||||||
-- @field #string IniDynamicCargoName The dynamic cargo unit name.
|
-- @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
|
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.
|
--- Creation of a New Zone Event.
|
||||||
-- @param #EVENT self
|
-- @param #EVENT self
|
||||||
-- @param Core.Zone#ZONE_BASE Zone The Zone created.
|
-- @param Core.Zone#ZONE_BASE Zone The Zone created.
|
||||||
@@ -1361,7 +1336,8 @@ function EVENT:onEvent( Event )
|
|||||||
Event.IniDynamicCargoName = Event.IniUnitName
|
Event.IniDynamicCargoName = Event.IniUnitName
|
||||||
Event.IniPlayerName = string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+")
|
Event.IniPlayerName = string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+")
|
||||||
else
|
else
|
||||||
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
|
--Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
|
||||||
|
Event.IniUnit = STATIC:FindByName( Event.IniDCSUnitName, false )
|
||||||
end
|
end
|
||||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||||
@@ -1374,7 +1350,9 @@ function EVENT:onEvent( Event )
|
|||||||
Event.IniDCSUnit = Event.initiator
|
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.IniDCSUnitName = ( Event.IniDCSUnit and Event.IniDCSUnit.getName ) and Event.IniDCSUnit:getName() or "Scenery no name "..math.random(1,20000)
|
||||||
Event.IniUnitName = Event.IniDCSUnitName
|
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.IniCategory = (Event.IniDCSUnit and Event.IniDCSUnit.getDesc ) and Event.IniDCSUnit:getDesc().category
|
||||||
Event.IniTypeName = (Event.initiator and Event.initiator.isExist
|
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"
|
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
|
Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName() or nil
|
||||||
if Event.TgtDCSUnitName~=nil then
|
if Event.TgtDCSUnitName~=nil then
|
||||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
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.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||||
end
|
end
|
||||||
@@ -1519,7 +1499,9 @@ function EVENT:onEvent( Event )
|
|||||||
if Event.idx then
|
if Event.idx then
|
||||||
Event.MarkID=Event.idx
|
Event.MarkID=Event.idx
|
||||||
Event.MarkVec3=Event.pos
|
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.MarkText=Event.text
|
||||||
Event.MarkCoalition=Event.coalition
|
Event.MarkCoalition=Event.coalition
|
||||||
Event.IniCoalition=Event.coalition
|
Event.IniCoalition=Event.coalition
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ MARKEROPS_BASE = {
|
|||||||
ClassName = "MARKEROPS",
|
ClassName = "MARKEROPS",
|
||||||
Tag = "mytag",
|
Tag = "mytag",
|
||||||
Keywords = {},
|
Keywords = {},
|
||||||
version = "0.1.4",
|
version = "0.1.5",
|
||||||
debug = false,
|
debug = false,
|
||||||
Casesensitive = true,
|
Casesensitive = true,
|
||||||
}
|
}
|
||||||
@@ -59,9 +59,8 @@ MARKEROPS_BASE = {
|
|||||||
-- @param #MARKEROPS_BASE self
|
-- @param #MARKEROPS_BASE self
|
||||||
-- @param #string Tagname Name to identify us from the event text.
|
-- @param #string Tagname Name to identify us from the event text.
|
||||||
-- @param #table Keywords Table of keywords recognized 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
|
-- @return #MARKEROPS_BASE self
|
||||||
function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
function MARKEROPS_BASE:New(Tagname,Keywords)
|
||||||
-- Inherit FSM
|
-- Inherit FSM
|
||||||
local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE
|
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.Keywords = Keywords or {} -- #table - might want to use lua regex here, too
|
||||||
self.debug = false
|
self.debug = false
|
||||||
self.Casesensitive = true
|
self.Casesensitive = true
|
||||||
|
|
||||||
if Casesensitive and Casesensitive == false then
|
|
||||||
self.Casesensitive = false
|
|
||||||
end
|
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
--- FSM Transitions ---
|
--- FSM Transitions ---
|
||||||
-----------------------
|
-----------------------
|
||||||
@@ -145,7 +140,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- (internal) Handle events.
|
--- (Internal) Handle events.
|
||||||
-- @param #MARKEROPS_BASE self
|
-- @param #MARKEROPS_BASE self
|
||||||
-- @param Core.Event#EVENTDATA Event
|
-- @param Core.Event#EVENTDATA Event
|
||||||
function MARKEROPS_BASE:OnEventMark(Event)
|
function MARKEROPS_BASE:OnEventMark(Event)
|
||||||
@@ -201,15 +196,17 @@ function MARKEROPS_BASE:OnEventMark(Event)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- (internal) Match tag.
|
--- (Internal) Match tag.
|
||||||
-- @param #MARKEROPS_BASE self
|
-- @param #MARKEROPS_BASE self
|
||||||
-- @param #string Eventtext Text added to the marker.
|
-- @param #string Eventtext Text added to the marker.
|
||||||
-- @return #boolean
|
-- @return #boolean
|
||||||
function MARKEROPS_BASE:_MatchTag(Eventtext)
|
function MARKEROPS_BASE:_MatchTag(Eventtext)
|
||||||
local matches = false
|
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
|
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
|
matches = true --event text contains tag
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -221,7 +218,7 @@ function MARKEROPS_BASE:_MatchTag(Eventtext)
|
|||||||
return matches
|
return matches
|
||||||
end
|
end
|
||||||
|
|
||||||
--- (internal) Match keywords table.
|
--- (Internal) Match keywords table.
|
||||||
-- @param #MARKEROPS_BASE self
|
-- @param #MARKEROPS_BASE self
|
||||||
-- @param #string Eventtext Text added to the marker.
|
-- @param #string Eventtext Text added to the marker.
|
||||||
-- @return #table
|
-- @return #table
|
||||||
@@ -286,6 +283,22 @@ function MARKEROPS_BASE:onenterStopped(From,Event,To)
|
|||||||
self:UnHandleEvent(EVENTS.MarkRemoved)
|
self:UnHandleEvent(EVENTS.MarkRemoved)
|
||||||
end
|
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.
|
-- MARKEROPS_BASE Class Definition End.
|
||||||
--------------------------------------------------------------------------
|
--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -5,13 +5,11 @@
|
|||||||
-- * Path from A to B
|
-- * Path from A to B
|
||||||
-- * Arbitrary number of points
|
-- * Arbitrary number of points
|
||||||
-- * Automatically from lines drawtool
|
-- * Automatically from lines drawtool
|
||||||
-- * Draw line or mark points on F10 map
|
|
||||||
-- * Find closest points to path
|
|
||||||
--
|
--
|
||||||
-- ===
|
-- ===
|
||||||
--
|
--
|
||||||
-- ### Author: **funkyfranky**
|
-- ### Author: **funkyfranky**
|
||||||
--
|
--
|
||||||
-- ===
|
-- ===
|
||||||
-- @module Core.Pathline
|
-- @module Core.Pathline
|
||||||
-- @image CORE_Pathline.png
|
-- @image CORE_Pathline.png
|
||||||
@@ -23,7 +21,6 @@
|
|||||||
-- @field #string lid Class id string for output to DCS log file.
|
-- @field #string lid Class id string for output to DCS log file.
|
||||||
-- @field #string name Name of the path line.
|
-- @field #string name Name of the path line.
|
||||||
-- @field #table points List of 3D points defining the path.
|
-- @field #table points List of 3D points defining the path.
|
||||||
-- @field #number counter Running number counting the point IDs.
|
|
||||||
-- @extends Core.Base#BASE
|
-- @extends Core.Base#BASE
|
||||||
|
|
||||||
--- *The shortest distance between two points is a straight line.* -- Archimedes
|
--- *The shortest distance between two points is a straight line.* -- Archimedes
|
||||||
@@ -31,30 +28,30 @@
|
|||||||
-- ===
|
-- ===
|
||||||
--
|
--
|
||||||
-- # The PATHLINE Concept
|
-- # 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
|
-- 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).
|
-- 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.
|
-- 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.
|
-- They can be accessed with the @{#PATHLINE.FindByName) function.
|
||||||
--
|
--
|
||||||
-- # Constructor
|
-- # 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}
|
-- 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.
|
-- 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
|
-- # 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.
|
-- 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
|
-- 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.
|
-- you specify in the draw panel.
|
||||||
--
|
--
|
||||||
-- # Mark on F10 map
|
-- # 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
|
-- 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.
|
-- water depth.
|
||||||
--
|
--
|
||||||
-- To remove the marks, use @{#PATHLINE.MarkPoints}(`false`).
|
-- To remove the marks, use @{#PATHLINE.MarkPoints}(`false`).
|
||||||
--
|
--
|
||||||
-- @field #PATHLINE
|
-- @field #PATHLINE
|
||||||
@@ -62,14 +59,10 @@ PATHLINE = {
|
|||||||
ClassName = "PATHLINE",
|
ClassName = "PATHLINE",
|
||||||
lid = nil,
|
lid = nil,
|
||||||
points = {},
|
points = {},
|
||||||
counter = 0,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Point of line.
|
--- Point of line.
|
||||||
-- @type PATHLINE.Point
|
-- @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#Vec3 vec3 3D position.
|
||||||
-- @field DCS#Vec2 vec2 2D position.
|
-- @field DCS#Vec2 vec2 2D position.
|
||||||
-- @field #number surfaceType Surface type.
|
-- @field #number surfaceType Surface type.
|
||||||
@@ -87,9 +80,7 @@ PATHLINE.version="0.2.0"
|
|||||||
-- TODO list
|
-- TODO list
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- TODO: Read/write to JSON file
|
-- TODO: A lot...
|
||||||
-- TODO: Translate/rotate pathline
|
|
||||||
-- TODO: Add color.
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- Constructor
|
-- Constructor
|
||||||
@@ -103,10 +94,10 @@ function PATHLINE:New(Name)
|
|||||||
|
|
||||||
-- Inherit everything from INTEL class.
|
-- Inherit everything from INTEL class.
|
||||||
local self=BASE:Inherit(self, BASE:New()) --#PATHLINE
|
local self=BASE:Inherit(self, BASE:New()) --#PATHLINE
|
||||||
|
|
||||||
self.name=Name or "Unknown Path"
|
self.name=Name or "Unknown Path"
|
||||||
|
|
||||||
self.lid=string.format("PATHLINE %s | ", self.name)
|
self.lid=string.format("PATHLINE %s | ", Name)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
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.
|
--- Add a point to the path from a given 2D position. The third dimension is determined from the land height.
|
||||||
-- @param #PATHLINE self
|
-- @param #PATHLINE self
|
||||||
-- @param DCS#Vec2 Vec2 The 2D vector (x,y) to add.
|
-- @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
|
-- @return #PATHLINE self
|
||||||
function PATHLINE:AddPointFromVec2(Vec2, Index, Point)
|
function PATHLINE:AddPointFromVec2(Vec2)
|
||||||
|
|
||||||
if Vec2 then
|
if Vec2 then
|
||||||
|
|
||||||
-- Create a new point.
|
|
||||||
local point=self:_CreatePoint(Vec2)
|
local point=self:_CreatePoint(Vec2)
|
||||||
|
|
||||||
if Index then
|
table.insert(self.points, point)
|
||||||
-- 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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Add a point to the path from a given 3D position.
|
--- Add a point to the path from a given 3D position.
|
||||||
-- @param #PATHLINE self
|
-- @param #PATHLINE self
|
||||||
-- @param DCS#Vec3 Vec3 The 3D vector (x,y) to add.
|
-- @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.
|
-- @return #PATHLINE self
|
||||||
-- @param #PATHLINE.Point Point Add point after given point. Default is at the end or at given index.
|
function PATHLINE:AddPointFromVec3(Vec3)
|
||||||
-- @return #PATHLINE.Point Point that was added.
|
|
||||||
function PATHLINE:AddPointFromVec3(Vec3, Index, Point)
|
|
||||||
|
|
||||||
if Vec3 then
|
if Vec3 then
|
||||||
|
|
||||||
local point=self:_CreatePoint(Vec3)
|
local point=self:_CreatePoint(Vec3)
|
||||||
|
|
||||||
if Index then
|
table.insert(self.points, point)
|
||||||
-- 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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get name of pathline.
|
--- Get name of pathline.
|
||||||
-- @param #PATHLINE self
|
-- @param #PATHLINE self
|
||||||
-- @return #string Name of the pathline.
|
-- @return #string Name of the pathline.
|
||||||
function PATHLINE:GetName()
|
function PATHLINE:GetName()
|
||||||
return self.name
|
return self.name
|
||||||
end
|
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.
|
--- 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
|
-- @param #PATHLINE self
|
||||||
-- @return #list <Core.Pathline#PATHLINE.Point> List of points.
|
-- @return #list <#PATHLINE.Point> List of points.
|
||||||
function PATHLINE:GetPoints()
|
function PATHLINE:GetPoints()
|
||||||
return self.points
|
return self.points
|
||||||
end
|
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.
|
--- Get 3D points of pathline.
|
||||||
-- @param #PATHLINE self
|
-- @param #PATHLINE self
|
||||||
-- @return #list <DCS#Vec3> List of DCS#Vec3 points.
|
-- @return <DCS#Vec3> List of DCS#Vec3 points.
|
||||||
function PATHLINE:GetPoints3D()
|
function PATHLINE:GetPoints3D()
|
||||||
|
|
||||||
local vecs={}
|
local vecs={}
|
||||||
|
|
||||||
for _,_point in pairs(self.points) do
|
for _,_point in pairs(self.points) do
|
||||||
local point=_point --#PATHLINE.Point
|
local point=_point --#PATHLINE.Point
|
||||||
table.insert(vecs, point.vec3)
|
table.insert(vecs, point.vec3)
|
||||||
@@ -277,11 +222,11 @@ end
|
|||||||
|
|
||||||
--- Get 2D points of pathline.
|
--- Get 2D points of pathline.
|
||||||
-- @param #PATHLINE self
|
-- @param #PATHLINE self
|
||||||
-- @return #list <DCS#Vec2> List of DCS#Vec2 points.
|
-- @return <DCS#Vec2> List of DCS#Vec2 points.
|
||||||
function PATHLINE:GetPoints2D()
|
function PATHLINE:GetPoints2D()
|
||||||
|
|
||||||
local vecs={}
|
local vecs={}
|
||||||
|
|
||||||
for _,_point in pairs(self.points) do
|
for _,_point in pairs(self.points) do
|
||||||
local point=_point --#PATHLINE.Point
|
local point=_point --#PATHLINE.Point
|
||||||
table.insert(vecs, point.vec2)
|
table.insert(vecs, point.vec2)
|
||||||
@@ -296,11 +241,11 @@ end
|
|||||||
function PATHLINE:GetCoordinates()
|
function PATHLINE:GetCoordinates()
|
||||||
|
|
||||||
local vecs={}
|
local vecs={}
|
||||||
|
|
||||||
for _,_point in pairs(self.points) do
|
for _,_point in pairs(self.points) do
|
||||||
local point=_point --#PATHLINE.Point
|
local point=_point --#PATHLINE.Point
|
||||||
local coord=COORDINATE:NewFromVec3(point.vec3)
|
local coord=COORDINATE:NewFromVec3(point.vec3)
|
||||||
table.insert(vecs, coord)
|
table.insert(vecs,coord)
|
||||||
end
|
end
|
||||||
|
|
||||||
return vecs
|
return vecs
|
||||||
@@ -313,11 +258,11 @@ end
|
|||||||
function PATHLINE:GetPointFromIndex(n)
|
function PATHLINE:GetPointFromIndex(n)
|
||||||
|
|
||||||
local N=self:GetNumberOfPoints()
|
local N=self:GetNumberOfPoints()
|
||||||
|
|
||||||
n=n or 1
|
n=n or 1
|
||||||
|
|
||||||
local point=nil --#PATHLINE.Point
|
local point=nil --#PATHLINE.Point
|
||||||
|
|
||||||
if n>=1 and n<=N then
|
if n>=1 and n<=N then
|
||||||
point=self.points[n]
|
point=self.points[n]
|
||||||
else
|
else
|
||||||
@@ -334,11 +279,11 @@ end
|
|||||||
function PATHLINE:GetPoint3DFromIndex(n)
|
function PATHLINE:GetPoint3DFromIndex(n)
|
||||||
|
|
||||||
local point=self:GetPointFromIndex(n)
|
local point=self:GetPointFromIndex(n)
|
||||||
|
|
||||||
if point then
|
if point then
|
||||||
return point.vec3
|
return point.vec3
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -349,11 +294,11 @@ end
|
|||||||
function PATHLINE:GetPoint2DFromIndex(n)
|
function PATHLINE:GetPoint2DFromIndex(n)
|
||||||
|
|
||||||
local point=self:GetPointFromIndex(n)
|
local point=self:GetPointFromIndex(n)
|
||||||
|
|
||||||
if point then
|
if point then
|
||||||
return point.vec2
|
return point.vec2
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -387,10 +332,8 @@ end
|
|||||||
-- @param #boolean Switch If `true` or nil, set marks. If `false`, remove marks.
|
-- @param #boolean Switch If `true` or nil, set marks. If `false`, remove marks.
|
||||||
-- @return #PATHLINE self
|
-- @return #PATHLINE self
|
||||||
function PATHLINE:MarkPoints(Switch)
|
function PATHLINE:MarkPoints(Switch)
|
||||||
|
|
||||||
for i,_point in pairs(self.points) do
|
for i,_point in pairs(self.points) do
|
||||||
local point=_point --#PATHLINE.Point
|
local point=_point --#PATHLINE.Point
|
||||||
|
|
||||||
if Switch==false then
|
if Switch==false then
|
||||||
|
|
||||||
if point.markerID then
|
if point.markerID then
|
||||||
@@ -402,19 +345,13 @@ function PATHLINE:MarkPoints(Switch)
|
|||||||
if point.markerID then
|
if point.markerID then
|
||||||
UTILS.RemoveMark(point.markerID)
|
UTILS.RemoveMark(point.markerID)
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
if point.markerID then
|
|
||||||
UTILS.RemoveMark(point.markerID)
|
|
||||||
end
|
|
||||||
|
|
||||||
point.markerID=UTILS.GetMarkID()
|
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, "")
|
trigger.action.markToAll(point.markerID, text, point.vec3, "")
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return self
|
return self
|
||||||
@@ -470,283 +407,6 @@ function PATHLINE:UnDrawLine(Delay)
|
|||||||
end
|
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
|
-- Private functions
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -754,56 +414,32 @@ end
|
|||||||
--- Get 3D points of pathline.
|
--- Get 3D points of pathline.
|
||||||
-- @param #PATHLINE self
|
-- @param #PATHLINE self
|
||||||
-- @param DCS#Vec3 Vec Position vector. Can also be a DCS#Vec2 in which case the altitude at landheight is taken.
|
-- @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)
|
function PATHLINE:_CreatePoint(Vec)
|
||||||
|
|
||||||
local point={} --#PATHLINE.Point
|
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
|
if Vec.z then
|
||||||
-- Given vec is 3D
|
-- Given vec is 3D
|
||||||
point.vec3=UTILS.DeepCopy(Vec)
|
point.vec3=UTILS.DeepCopy(Vec)
|
||||||
point.vec2={x=Vec.x, y=Vec.z}
|
point.vec2={x=Vec.x, y=Vec.z}
|
||||||
else
|
else
|
||||||
-- Given vec is 2D
|
-- Given vec is 2D
|
||||||
point.vec2=UTILS.DeepCopy(Vec)
|
point.vec2=UTILS.DeepCopy(Vec)
|
||||||
point.vec3={x=Vec.x, y=land.getHeight(Vec), z=Vec.y}
|
point.vec3={x=Vec.x, y=land.getHeight(Vec), z=Vec.y}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get surface type.
|
-- Get surface type.
|
||||||
point.surfaceType=land.getSurfaceType(point.vec2)
|
point.surfaceType=land.getSurfaceType(point.vec2)
|
||||||
|
|
||||||
-- Get land height and depth.
|
-- Get land height and depth.
|
||||||
point.landHeight, point.depth=land.getSurfaceHeightWithSeabed(point.vec2)
|
point.landHeight, point.depth=land.getSurfaceHeightWithSeabed(point.vec2)
|
||||||
|
|
||||||
point.markerID=nil
|
point.markerID=nil
|
||||||
|
|
||||||
return point
|
return point
|
||||||
end
|
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
|
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.
|
--- Returns if the 2 coordinates are at the same 2D position.
|
||||||
-- @param #COORDINATE self
|
-- @param #COORDINATE self
|
||||||
-- @param #COORDINATE Coordinate
|
-- @param #COORDINATE Coordinate
|
||||||
@@ -680,7 +663,7 @@ do -- COORDINATE
|
|||||||
local _,_,_,_,_,scenerys=self:ScanObjects(radius, false, false, true)
|
local _,_,_,_,_,scenerys=self:ScanObjects(radius, false, false, true)
|
||||||
|
|
||||||
local set={}
|
local set={}
|
||||||
|
|
||||||
for _,_scenery in pairs(scenerys) do
|
for _,_scenery in pairs(scenerys) do
|
||||||
local scenery=_scenery --DCS#Object
|
local scenery=_scenery --DCS#Object
|
||||||
|
|
||||||
@@ -3298,7 +3281,6 @@ do -- COORDINATE
|
|||||||
return delta/60
|
return delta/60
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--- Return a BR string from a COORDINATE to the COORDINATE.
|
--- Return a BR string from a COORDINATE to the COORDINATE.
|
||||||
-- @param #COORDINATE self
|
-- @param #COORDINATE self
|
||||||
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
|
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
|
||||||
@@ -3312,6 +3294,20 @@ do -- COORDINATE
|
|||||||
local Distance = self:Get2DDistance( FromCoordinate )
|
local Distance = self:Get2DDistance( FromCoordinate )
|
||||||
return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision )
|
return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision )
|
||||||
end
|
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.
|
--- Return a BRA string from a COORDINATE to the COORDINATE.
|
||||||
-- @param #COORDINATE self
|
-- @param #COORDINATE self
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ do -- SET_BASE
|
|||||||
Index = {},
|
Index = {},
|
||||||
Database = nil,
|
Database = nil,
|
||||||
CallScheduler = nil,
|
CallScheduler = nil,
|
||||||
Filter = {},
|
|
||||||
FilterCoalitionNumbers = {
|
FilterCoalitionNumbers = {
|
||||||
[coalition.side.RED+1] = "red",
|
[coalition.side.RED+1] = "red",
|
||||||
[coalition.side.BLUE+1] = "blue",
|
[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 RandomizeUnits, then Randomize the formation at the start point.
|
||||||
if self.SpawnRandomizeUnits then
|
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
|
for UnitID = 1, #SpawnTemplate.units do
|
||||||
local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
|
local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
|
||||||
if (SpawnZone) then
|
if (SpawnZone) then
|
||||||
@@ -3518,6 +3521,11 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
|
|||||||
max = 18
|
max = 18
|
||||||
ctable = CALLSIGN.F15E
|
ctable = CALLSIGN.F15E
|
||||||
end
|
end
|
||||||
|
if SpawnTemplate.units[1].type == "A6E" then
|
||||||
|
min = 4
|
||||||
|
max = 18
|
||||||
|
ctable = CALLSIGN.Intruder
|
||||||
|
end
|
||||||
local callsignnr = math.random(min,max)
|
local callsignnr = math.random(min,max)
|
||||||
local callsignname = "Enfield"
|
local callsignname = "Enfield"
|
||||||
for name, value in pairs(ctable) do
|
for name, value in pairs(ctable) do
|
||||||
|
|||||||
@@ -334,6 +334,31 @@ function SPAWNSTATIC:InitCargo(IsCargo)
|
|||||||
return self
|
return self
|
||||||
end
|
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.
|
--- Initialize as dead.
|
||||||
-- @param #SPAWNSTATIC self
|
-- @param #SPAWNSTATIC self
|
||||||
-- @param #boolean IsDead If true, this static is dead.
|
-- @param #boolean IsDead If true, this static is dead.
|
||||||
@@ -533,6 +558,19 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
|||||||
Template.mass=self.InitStaticCargoMass
|
Template.mass=self.InitStaticCargoMass
|
||||||
end
|
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
|
if self.InitLinkUnit then
|
||||||
Template.linkUnit=self.InitLinkUnit:GetID()
|
Template.linkUnit=self.InitLinkUnit:GetID()
|
||||||
Template.linkOffset=true
|
Template.linkOffset=true
|
||||||
|
|||||||
@@ -306,20 +306,6 @@ function ZONE_BASE:GetCoordinate( Height ) --R2.1
|
|||||||
return self.Coordinate
|
return self.Coordinate
|
||||||
end
|
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.
|
--- Get 2D distance to a coordinate.
|
||||||
-- @param #ZONE_BASE self
|
-- @param #ZONE_BASE self
|
||||||
-- @param Core.Point#COORDINATE Coordinate Reference coordinate. Can also be a DCS#Vec2 or DCS#Vec3 object.
|
-- @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 #ZONE_BASE self
|
||||||
-- @param #string From
|
-- @param #string From
|
||||||
-- @param #string Event
|
-- @param #string Event
|
||||||
-- @param #string to
|
-- @param #string To
|
||||||
-- @return #ZONE_BASE self
|
-- @return #ZONE_BASE self
|
||||||
function ZONE_BASE:onafterTriggerRunCheck(From,Event,To)
|
function ZONE_BASE:onafterTriggerRunCheck(From,Event,To)
|
||||||
if self:GetState() ~= "TriggerStopped" then
|
if self:GetState() ~= "TriggerStopped" then
|
||||||
@@ -1172,7 +1158,7 @@ end
|
|||||||
-- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
|
-- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
|
||||||
-- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition )
|
-- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition )
|
||||||
function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
||||||
|
|
||||||
self.ScanData = {}
|
self.ScanData = {}
|
||||||
self.ScanData.Coalitions = {}
|
self.ScanData.Coalitions = {}
|
||||||
self.ScanData.Scenery = {}
|
self.ScanData.Scenery = {}
|
||||||
@@ -1182,7 +1168,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
|||||||
local ZoneCoord = self:GetCoordinate():SetAlt()
|
local ZoneCoord = self:GetCoordinate():SetAlt()
|
||||||
local ZoneRadius = self:GetRadius()
|
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 = {
|
local SphereSearch = {
|
||||||
id = world.VolumeType.SPHERE,
|
id = world.VolumeType.SPHERE,
|
||||||
@@ -1194,6 +1180,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
|||||||
|
|
||||||
local function EvaluateZone( ZoneObject )
|
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: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
|
if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint()) then
|
||||||
|
|
||||||
-- Get object category.
|
-- Get object category.
|
||||||
@@ -1279,38 +1266,48 @@ end
|
|||||||
|
|
||||||
--- Get a set of scanned units.
|
--- Get a set of scanned units.
|
||||||
-- @param #ZONE_RADIUS self
|
-- @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.
|
-- @return Core.Set#SET_UNIT Set of units and statics inside the zone.
|
||||||
function ZONE_RADIUS:GetScannedSetUnit()
|
function ZONE_RADIUS:GetScannedSetUnit(Coalition)
|
||||||
|
|
||||||
local SetUnit = SET_UNIT:New()
|
|
||||||
|
|
||||||
|
self.SetUnit = self.SetUnit or SET_UNIT:New()
|
||||||
|
self.SetUnit:Clear(false)
|
||||||
|
self.SetUnit.Set={}
|
||||||
|
|
||||||
if self.ScanData then
|
if self.ScanData then
|
||||||
for ObjectID, UnitObject in pairs( self.ScanData.Units ) do
|
for ObjectID, UnitObject in pairs( self.ScanData.Units ) do
|
||||||
local UnitObject = UnitObject -- DCS#Unit
|
local UnitObject = UnitObject -- DCS#Unit
|
||||||
if UnitObject:isExist() then
|
if UnitObject:isExist() then
|
||||||
local FoundUnit = UNIT:FindByName( UnitObject:getName() )
|
local FoundUnit = UNIT:FindByName( UnitObject:getName() )
|
||||||
if FoundUnit then
|
local FoundCoalition = FoundUnit and FoundUnit:GetCoalition() or nil
|
||||||
SetUnit:AddUnit( FoundUnit )
|
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
|
else
|
||||||
local FoundStatic = STATIC:FindByName( UnitObject:getName(), false )
|
local FoundStatic = STATIC:FindByName( UnitObject:getName(), false )
|
||||||
if FoundStatic then
|
if FoundStatic then
|
||||||
SetUnit:AddUnit( FoundStatic )
|
self.SetUnit:AddUnit( FoundStatic )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return SetUnit
|
return self.SetUnit
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get a set of scanned groups.
|
--- Get a set of scanned groups.
|
||||||
-- @param #ZONE_RADIUS self
|
-- @param #ZONE_RADIUS self
|
||||||
|
-- @param #number Coalition (optional) Filter for this coalition only.
|
||||||
-- @return Core.Set#SET_GROUP Set of groups.
|
-- @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=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP
|
||||||
|
|
||||||
|
self.ScanSetGroup:Clear(false)
|
||||||
self.ScanSetGroup.Set={}
|
self.ScanSetGroup.Set={}
|
||||||
|
|
||||||
if self.ScanData then
|
if self.ScanData then
|
||||||
@@ -1319,7 +1316,12 @@ function ZONE_RADIUS:GetScannedSetGroup()
|
|||||||
if UnitObject:isExist() then
|
if UnitObject:isExist() then
|
||||||
|
|
||||||
local FoundUnit=UNIT:FindByName(UnitObject:getName())
|
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()
|
local group=FoundUnit:GetGroup()
|
||||||
self.ScanSetGroup:AddGroup(group)
|
self.ScanSetGroup:AddGroup(group)
|
||||||
end
|
end
|
||||||
@@ -3394,7 +3396,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories )
|
|||||||
self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {}
|
self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {}
|
||||||
self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject )
|
self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject )
|
||||||
table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName])
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
--
|
--
|
||||||
-- ### Author: **funkyfranky**
|
-- ### Author: **funkyfranky**
|
||||||
--
|
--
|
||||||
-- ### Contributions: FlightControl
|
-- ### Contributions: FlightControl, Applevangelist
|
||||||
--
|
--
|
||||||
-- ====
|
-- ====
|
||||||
-- @module Functional.Artillery
|
-- @module Functional.Artillery
|
||||||
@@ -780,7 +780,7 @@ ARTY.db={
|
|||||||
|
|
||||||
--- Arty script version.
|
--- Arty script version.
|
||||||
-- @field #string 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
|
if _dist<target.radius then
|
||||||
|
|
||||||
-- Get random coordinate within a certain radius.
|
-- 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.
|
-- Fire smoke at this coordinate.
|
||||||
_cr:Smoke(self.smokeColor)
|
_cr:Smoke(self.smokeColor)
|
||||||
|
|||||||
@@ -693,7 +693,7 @@ end
|
|||||||
function AUTOLASE:GetLosFromUnit(Unit)
|
function AUTOLASE:GetLosFromUnit(Unit)
|
||||||
local lasedistance = self.LaseDistance
|
local lasedistance = self.LaseDistance
|
||||||
local unitheight = Unit:GetHeight()
|
local unitheight = Unit:GetHeight()
|
||||||
local coord = Unit:GetCoordinate()
|
local coord = Unit:GetCoord()
|
||||||
local landheight = coord:GetLandHeight()
|
local landheight = coord:GetLandHeight()
|
||||||
local asl = unitheight - landheight
|
local asl = unitheight - landheight
|
||||||
if asl > 100 then
|
if asl > 100 then
|
||||||
@@ -845,7 +845,7 @@ function AUTOLASE:ShowStatus(Group,Unit)
|
|||||||
locationstring = entry.coordinate:ToStringLLDDM(settings)
|
locationstring = entry.coordinate:ToStringLLDDM(settings)
|
||||||
elseif settings:IsA2G_BR() then
|
elseif settings:IsA2G_BR() then
|
||||||
-- attention this is the distance from the ASKING unit to target, not from RECCE to target!
|
-- 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)
|
locationstring = entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -963,8 +963,8 @@ function AUTOLASE:CanLase(Recce,Unit)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- calculate LOS
|
-- calculate LOS
|
||||||
local reccecoord = Recce:GetCoordinate()
|
local reccecoord = Recce:GetCoord()
|
||||||
local unitcoord = Unit:GetCoordinate()
|
local unitcoord = Unit:GetCoord()
|
||||||
local islos = reccecoord:IsLOS(unitcoord,2.5)
|
local islos = reccecoord:IsLOS(unitcoord,2.5)
|
||||||
-- calculate distance
|
-- calculate distance
|
||||||
local distance = math.floor(reccecoord:Get3DDistance(unitcoord))
|
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())
|
self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName())
|
||||||
for _,_static in pairs(Statics) do -- DCS static object here
|
for _,_static in pairs(Statics) do -- DCS static object here
|
||||||
local static = STATIC:Find(_static)
|
local static = STATIC:Find(_static)
|
||||||
if static and static:GetCoalition() ~= self.coalition and static:GetCoordinate() then
|
if static and static:GetCoalition() ~= self.coalition and static:GetCoord() then
|
||||||
local IsLOS = position:IsLOS(static:GetCoordinate())
|
local IsLOS = position:IsLOS(static:GetCoord())
|
||||||
if IsLOS then
|
if IsLOS then
|
||||||
unit:KnowUnit(static,true,true)
|
unit:KnowUnit(static,true,true)
|
||||||
end
|
end
|
||||||
@@ -1081,7 +1081,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
|||||||
local threat = contact.threatlevel or 0
|
local threat = contact.threatlevel or 0
|
||||||
local reccegrp = UNIT:FindByName(reccename)
|
local reccegrp = UNIT:FindByName(reccename)
|
||||||
if reccegrp then
|
if reccegrp then
|
||||||
local reccecoord = reccegrp:GetCoordinate()
|
local reccecoord = reccegrp:GetCoord()
|
||||||
local distance = math.floor(reccecoord:Get3DDistance(coord))
|
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)
|
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)
|
report:Add(text)
|
||||||
@@ -1120,7 +1120,6 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
|||||||
local unit = _unit -- Wrapper.Unit#UNIT
|
local unit = _unit -- Wrapper.Unit#UNIT
|
||||||
if unit and unit:IsAlive() then
|
if unit and unit:IsAlive() then
|
||||||
local threat = unit:GetThreatLevel()
|
local threat = unit:GetThreatLevel()
|
||||||
local coord = unit:GetCoordinate()
|
|
||||||
if threat >= self.minthreatlevel then
|
if threat >= self.minthreatlevel then
|
||||||
local unitname = unit:GetName()
|
local unitname = unit:GetName()
|
||||||
-- prefer radar units
|
-- prefer radar units
|
||||||
@@ -1176,16 +1175,16 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
|||||||
local code = self:GetLaserCode(reccename)
|
local code = self:GetLaserCode(reccename)
|
||||||
local spot = SPOT:New(recce)
|
local spot = SPOT:New(recce)
|
||||||
spot:LaseOn(unit,code,self.LaseDuration)
|
spot:LaseOn(unit,code,self.LaseDuration)
|
||||||
local locationstring = unit:GetCoordinate():ToStringLLDDM()
|
local locationstring = unit:GetCoord():ToStringLLDDM()
|
||||||
if _SETTINGS:IsA2G_MGRS() then
|
if _SETTINGS:IsA2G_MGRS() then
|
||||||
local precision = _SETTINGS:GetMGRS_Accuracy()
|
local precision = _SETTINGS:GetMGRS_Accuracy()
|
||||||
local settings = {}
|
local settings = {}
|
||||||
settings.MGRS_Accuracy = precision
|
settings.MGRS_Accuracy = precision
|
||||||
locationstring = unit:GetCoordinate():ToStringMGRS(settings)
|
locationstring = unit:GetCoord():ToStringMGRS(settings)
|
||||||
elseif _SETTINGS:IsA2G_LL_DMS() then
|
elseif _SETTINGS:IsA2G_LL_DMS() then
|
||||||
locationstring = unit:GetCoordinate():ToStringLLDMS(_SETTINGS)
|
locationstring = unit:GetCoord():ToStringLLDMS(_SETTINGS)
|
||||||
elseif _SETTINGS:IsA2G_BR() then
|
elseif _SETTINGS:IsA2G_BR() then
|
||||||
locationstring = unit:GetCoordinate():ToStringBULLS(self.coalition,_SETTINGS)
|
locationstring = unit:GetCoord():ToStringBULLS(self.coalition,_SETTINGS)
|
||||||
end
|
end
|
||||||
|
|
||||||
local laserspot = { -- #AUTOLASE.LaserSpot
|
local laserspot = { -- #AUTOLASE.LaserSpot
|
||||||
@@ -1198,7 +1197,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
|||||||
unitname = unitname,
|
unitname = unitname,
|
||||||
reccename = reccename,
|
reccename = reccename,
|
||||||
unittype = unit:GetTypeName(),
|
unittype = unit:GetTypeName(),
|
||||||
coordinate = unit:GetCoordinate(),
|
coordinate = unit:GetCoord(),
|
||||||
}
|
}
|
||||||
if self.smoketargets then
|
if self.smoketargets then
|
||||||
local coord = unit:GetCoordinate()
|
local coord = unit:GetCoordinate()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
-- @module Functional.Mantis
|
-- @module Functional.Mantis
|
||||||
-- @image Functional.Mantis.jpg
|
-- @image Functional.Mantis.jpg
|
||||||
--
|
--
|
||||||
-- Last Update: August 2025
|
-- Last Update: January 2026
|
||||||
|
|
||||||
-------------------------------------------------------------------------
|
-------------------------------------------------------------------------
|
||||||
--- **MANTIS** class, extends Core.Base#BASE
|
--- **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 #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 #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 #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 checkcounter Counter for SAM Table refreshes.
|
||||||
-- @field #number DLinkCacheTime Seconds after which cached contacts in DLink will decay.
|
-- @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
|
-- @extends Core.Base#BASE
|
||||||
|
|
||||||
|
|
||||||
@@ -113,7 +116,7 @@
|
|||||||
-- * TOR M2
|
-- * TOR M2
|
||||||
-- * C-RAM
|
-- * C-RAM
|
||||||
-- * Silkworm (though strictly speaking this is a surface to ship missile)
|
-- * 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 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
|
-- * 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
|
-- * Other Mods: Nike
|
||||||
@@ -280,7 +283,7 @@
|
|||||||
MANTIS = {
|
MANTIS = {
|
||||||
ClassName = "MANTIS",
|
ClassName = "MANTIS",
|
||||||
name = "mymantis",
|
name = "mymantis",
|
||||||
version = "0.9.34",
|
version = "0.9.44",
|
||||||
SAM_Templates_Prefix = "",
|
SAM_Templates_Prefix = "",
|
||||||
SAM_Group = nil,
|
SAM_Group = nil,
|
||||||
EWR_Templates_Prefix = "",
|
EWR_Templates_Prefix = "",
|
||||||
@@ -331,6 +334,11 @@ MANTIS = {
|
|||||||
checkcounter = 1,
|
checkcounter = 1,
|
||||||
DLinkCacheTime = 120,
|
DLinkCacheTime = 120,
|
||||||
logsamstatus = false,
|
logsamstatus = false,
|
||||||
|
DetectAccoustic = false,
|
||||||
|
DetectAccousticRadius = 2000,
|
||||||
|
DetectAccousticCategories = {Unit.Category.HELICOPTER},
|
||||||
|
ARMWeaponSeen = {},
|
||||||
|
InboundARMs = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Advanced state enumerator
|
--- 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 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 Radar Radar typename on unit level (used as key)
|
||||||
-- @field #string Point Point defense capable
|
-- @field #string Point Point defense capable
|
||||||
|
-- @field ARMCapacit ARMCapacity ie how many (H)ARMs the system can defend at the same time
|
||||||
MANTIS.SamData = {
|
MANTIS.SamData = {
|
||||||
["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km
|
["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" },
|
["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot str" },
|
||||||
["Rapier"] = { Range=10, Blindspot=0, Height=3, Type="Short", Radar="rapier" },
|
["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-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-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-5"] = { Range=250, Blindspot=7, Height=40, Type="Long", Radar="5N62V" },
|
||||||
["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" },
|
["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" },
|
["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" },
|
["Gepard"] = { Range=5, Blindspot=0, Height=4, Type="Point", Radar="Gepard" },
|
||||||
["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" },
|
["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-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-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-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" },
|
["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" },
|
["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" },
|
||||||
["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" },
|
["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" },
|
["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" },
|
["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
|
-- units from HDS Mod, multi launcher options is tricky
|
||||||
["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"},
|
["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" },
|
["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"},
|
["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"},
|
["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" },
|
["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" },
|
["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" },
|
["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" },
|
["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" },
|
||||||
-- CH Added to DCS core 2.9.19.x
|
-- CH Added to DCS core 2.9.19.x
|
||||||
["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true" },
|
["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" },
|
["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" },
|
["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
|
--- SAM data HDS
|
||||||
@@ -419,13 +432,13 @@ MANTIS.SamDataHDS = {
|
|||||||
-- group name MUST contain HDS to ID launcher type correctly!
|
-- group name MUST contain HDS to ID launcher type correctly!
|
||||||
["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" },
|
["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-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-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"}, -- 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 " },
|
["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"},
|
["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"},
|
["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" },
|
["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" },
|
["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" },
|
["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 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
|
["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/
|
-- https://www.currenthill.com/
|
||||||
-- group name MUST contain CHM to ID launcher type correctly!
|
-- group name MUST contain CHM to ID launcher type correctly!
|
||||||
["2S38 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" },
|
["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" },
|
["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" },
|
["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" },
|
["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" },
|
["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" },
|
["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" },
|
["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" },
|
["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" },
|
["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" },
|
["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-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" },
|
["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" },
|
["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" },
|
["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-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" },
|
["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" },
|
["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" },
|
["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" },
|
["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" },
|
["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" },
|
["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" },
|
["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-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" },
|
["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" },
|
["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" },
|
["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" },
|
["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 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 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 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 #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional)
|
||||||
--@param #string awacs Group name of your Awacs (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)
|
--@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 = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
|
||||||
-- mybluemantis:Start()
|
-- 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.
|
-- Inherit everything from BASE class.
|
||||||
@@ -561,12 +574,14 @@ do
|
|||||||
-- DONE: Treat Awacs separately, since they might be >80km off site
|
-- DONE: Treat Awacs separately, since they might be >80km off site
|
||||||
-- DONE: Allow tables of prefixes for the setup
|
-- DONE: Allow tables of prefixes for the setup
|
||||||
-- DONE: Auto-Mode with range setups for various known SAM types.
|
-- 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.name = name or "mymantis"
|
||||||
self.SAM_Templates_Prefix = samprefix or "Red SAM"
|
self.SAM_Templates_Prefix = samprefix or "Red SAM"
|
||||||
self.EWR_Templates_Prefix = ewrprefix or "Red EWR"
|
self.EWR_Templates_Prefix = ewrprefix or "Red EWR"
|
||||||
self.HQ_Template_CC = hq or nil
|
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 = {}
|
||||||
self.SAM_Table_Long = {}
|
self.SAM_Table_Long = {}
|
||||||
self.SAM_Table_Medium = {}
|
self.SAM_Table_Medium = {}
|
||||||
@@ -613,6 +628,8 @@ do
|
|||||||
self.autoshorad = true
|
self.autoshorad = true
|
||||||
self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP
|
self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP
|
||||||
self.FilterZones = Zones
|
self.FilterZones = Zones
|
||||||
|
self.LastThreatEval = {}
|
||||||
|
self.InboundARMs = {}
|
||||||
|
|
||||||
self.SkateZones = nil
|
self.SkateZones = nil
|
||||||
self.SkateNumber = 3
|
self.SkateNumber = 3
|
||||||
@@ -716,6 +733,8 @@ do
|
|||||||
self:AddTransition("*", "SeadSuppressionStart", "*") -- SEAD has switched off one group.
|
self:AddTransition("*", "SeadSuppressionStart", "*") -- SEAD has switched off one group.
|
||||||
self:AddTransition("*", "SeadSuppressionEnd", "*") -- SEAD has switched on one group.
|
self:AddTransition("*", "SeadSuppressionEnd", "*") -- SEAD has switched on one group.
|
||||||
self:AddTransition("*", "SeadSuppressionPlanned", "*") -- SEAD has planned a suppression.
|
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.
|
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
||||||
|
|
||||||
------------------------
|
------------------------
|
||||||
@@ -826,6 +845,24 @@ do
|
|||||||
-- @param Wrapper.Group#GROUP Group The suppressed GROUP object
|
-- @param Wrapper.Group#GROUP Group The suppressed GROUP object
|
||||||
-- @param #string Name Name of the suppressed group
|
-- @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
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -833,6 +870,138 @@ do
|
|||||||
-- MANTIS helper functions
|
-- 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
|
--- [Internal] Function to get the self.SAM_Table
|
||||||
-- @param #MANTIS self
|
-- @param #MANTIS self
|
||||||
-- @return #table table
|
-- @return #table table
|
||||||
@@ -903,13 +1072,111 @@ do
|
|||||||
return self
|
return self
|
||||||
end
|
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)
|
--- Function to set the detection radius of the EWR in meters. (Deprecated, SAM range is used)
|
||||||
-- @param #MANTIS self
|
-- @param #MANTIS self
|
||||||
-- @param #number radius Radius of the EWR detection zone
|
-- @param #number radius Radius of the EWR detection zone
|
||||||
function MANTIS:SetEWRRange(radius)
|
function MANTIS:SetEWRRange(radius)
|
||||||
self:T(self.lid .. "SetEWRRange")
|
self:T(self.lid .. "SetEWRRange")
|
||||||
--local radius = radius or 80000
|
|
||||||
-- self.acceptrange = radius
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1325,11 +1592,12 @@ do
|
|||||||
return inzone
|
return inzone
|
||||||
end
|
end
|
||||||
|
|
||||||
--- [Internal] Function to prefilter height based
|
--- [Internal] Function to prefilter height based and check for Helo activity.
|
||||||
-- @param #MANTIS self
|
-- @param #MANTIS self
|
||||||
-- @param #number height
|
-- @param #number height
|
||||||
|
-- @param Core.Point#COORDINATE SamCoordinate
|
||||||
-- @return #table set
|
-- @return #table set
|
||||||
function MANTIS:_PreFilterHeight(height)
|
function MANTIS:_PreFilterHeight(height,SamCoordinate)
|
||||||
self:T(self.lid.."_PreFilterHeight")
|
self:T(self.lid.."_PreFilterHeight")
|
||||||
local set = {}
|
local set = {}
|
||||||
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK
|
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK
|
||||||
@@ -1338,8 +1606,22 @@ do
|
|||||||
local contact = _contact -- Ops.Intel#INTEL.Contact
|
local contact = _contact -- Ops.Intel#INTEL.Contact
|
||||||
local grp = contact.group -- Wrapper.Group#GROUP
|
local grp = contact.group -- Wrapper.Group#GROUP
|
||||||
if grp:IsAlive() then
|
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)
|
table.insert(set,coord)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1363,7 +1645,7 @@ do
|
|||||||
local set = dectset
|
local set = dectset
|
||||||
if dlink then
|
if dlink then
|
||||||
-- DEBUG
|
-- DEBUG
|
||||||
set = self:_PreFilterHeight(height)
|
set = self:_PreFilterHeight(height,samcoordinate)
|
||||||
end
|
end
|
||||||
--self.friendlyset -- Core.Set#SET_GROUP
|
--self.friendlyset -- Core.Set#SET_GROUP
|
||||||
if self.checkforfriendlies == true and self.friendlyset == nil then
|
if self.checkforfriendlies == true and self.friendlyset == nil then
|
||||||
@@ -1444,14 +1726,30 @@ do
|
|||||||
|
|
||||||
self.intelset = {}
|
self.intelset = {}
|
||||||
|
|
||||||
local IntelOne = INTEL:New(groupset,self.Coalition,self.name.." IntelOne")
|
local IntelOne = INTEL:New(groupset,self.coalition,self.name.." IntelOne")
|
||||||
--IntelOne:SetClusterAnalysis(true,true)
|
IntelOne.DetectAccoustic = self.DetectAccoustic
|
||||||
--IntelOne:SetClusterRadius(5000)
|
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()
|
IntelOne:Start()
|
||||||
|
|
||||||
local IntelTwo = INTEL:New(samset,self.Coalition,self.name.." IntelTwo")
|
local IntelTwo = INTEL:New(samset,self.coalition,self.name.." IntelTwo")
|
||||||
--IntelTwo:SetClusterAnalysis(true,true)
|
IntelTwo.DetectAccoustic = self.DetectAccoustic
|
||||||
--IntelTwo:SetClusterRadius(5000)
|
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()
|
IntelTwo:Start()
|
||||||
|
|
||||||
local CacheTime = self.DLinkCacheTime or 120
|
local CacheTime = self.DLinkCacheTime or 120
|
||||||
@@ -1511,6 +1809,7 @@ do
|
|||||||
local group = GROUP:FindByName(grpname) -- Wrapper.Group#GROUP
|
local group = GROUP:FindByName(grpname) -- Wrapper.Group#GROUP
|
||||||
local units = group:GetUnits()
|
local units = group:GetUnits()
|
||||||
local SAMData = self.SamData
|
local SAMData = self.SamData
|
||||||
|
local ARMCapacity
|
||||||
if mod then
|
if mod then
|
||||||
SAMData = self.SamDataHDS
|
SAMData = self.SamDataHDS
|
||||||
elseif sma then
|
elseif sma then
|
||||||
@@ -1518,22 +1817,23 @@ do
|
|||||||
elseif chm then
|
elseif chm then
|
||||||
SAMData = self.SamDataCH
|
SAMData = self.SamDataCH
|
||||||
end
|
end
|
||||||
--self:I("Looking to auto-match for "..grpname)
|
self:T("Looking to auto-match for "..grpname)
|
||||||
for _,_unit in pairs(units) do
|
for _,_unit in pairs(units) do
|
||||||
local unit = _unit -- Wrapper.Unit#UNIT
|
local unit = _unit -- Wrapper.Unit#UNIT
|
||||||
local type = string.lower(unit:GetTypeName())
|
local typename = string.lower(unit:GetTypeName())
|
||||||
--self:I(string.format("Matching typename: %s",type))
|
self:T(string.format("Matching typename: %s",typename))
|
||||||
for idx,entry in pairs(SAMData) do
|
for idx,entry in pairs(SAMData) do
|
||||||
local _entry = entry -- #MANTIS.SamData
|
local _entry = entry -- #MANTIS.SamData
|
||||||
local _radar = string.lower(_entry.Radar)
|
local _radar = string.lower(_entry.Radar)
|
||||||
--self:I(string.format("Trying typename: %s",_radar))
|
self:T(string.format("Trying typename: %s",_radar))
|
||||||
if string.find(type,_radar,1,true) then
|
if string.find(typename,_radar,1,true) then
|
||||||
type = _entry.Type
|
type = _entry.Type
|
||||||
radiusscale = self.radiusscale[type]
|
radiusscale = self.radiusscale[type]
|
||||||
range = _entry.Range * 1000 * radiusscale -- max firing range used as switch-on
|
range = _entry.Range * 1000 * radiusscale -- max firing range used as switch-on
|
||||||
height = _entry.Height * 1000 -- max firing height
|
height = _entry.Height * 1000 -- max firing height
|
||||||
blind = _entry.Blindspot * 100 -- blind spot range
|
blind = _entry.Blindspot * 100 -- blind spot range
|
||||||
--self:I(string.format("Match: %s - %s",_radar,type))
|
ARMCapacity = _entry.ARMCapacity
|
||||||
|
self:T(string.format("Match: %s - %s",_radar,type))
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -1554,7 +1854,7 @@ do
|
|||||||
if not found then
|
if not found then
|
||||||
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
|
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
|
||||||
end
|
end
|
||||||
return range, height, type, blind
|
return range, height, type, blind, ARMCapacity
|
||||||
end
|
end
|
||||||
|
|
||||||
--- [Internal] Function to get SAM firing data
|
--- [Internal] Function to get SAM firing data
|
||||||
@@ -1575,6 +1875,7 @@ do
|
|||||||
local HDSmod = false
|
local HDSmod = false
|
||||||
local SMAMod = false
|
local SMAMod = false
|
||||||
local CHMod = false
|
local CHMod = false
|
||||||
|
local ARMCapacity = 0
|
||||||
if string.find(grpname,"HDS",1,true) then
|
if string.find(grpname,"HDS",1,true) then
|
||||||
HDSmod = true
|
HDSmod = true
|
||||||
elseif string.find(grpname,"SMA",1,true) then
|
elseif string.find(grpname,"SMA",1,true) then
|
||||||
@@ -1592,6 +1893,7 @@ do
|
|||||||
range = _entry.Range * 1000 * radiusscale -- max firing range
|
range = _entry.Range * 1000 * radiusscale -- max firing range
|
||||||
height = _entry.Height * 1000 -- max firing height
|
height = _entry.Height * 1000 -- max firing height
|
||||||
blind = _entry.Blindspot
|
blind = _entry.Blindspot
|
||||||
|
ARMCapacity = _entry.ARMCapacity or 0
|
||||||
self:T("Matching Groupname = " .. grpname .. " Range= " .. range)
|
self:T("Matching Groupname = " .. grpname .. " Range= " .. range)
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
@@ -1611,14 +1913,14 @@ do
|
|||||||
end
|
end
|
||||||
--- Tertiary filter if not found
|
--- Tertiary filter if not found
|
||||||
if (not found) or HDSmod or SMAMod or CHMod then
|
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
|
elseif not found then
|
||||||
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
|
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
|
||||||
end
|
end
|
||||||
if found and string.find(grpname,"SHORAD",1,true) then
|
if found and string.find(grpname,"SHORAD",1,true) then
|
||||||
type = MANTIS.SamType.POINT -- force short on match
|
type = MANTIS.SamType.POINT -- force short on match
|
||||||
end
|
end
|
||||||
return range, height, type, blind
|
return range, height, type, blind, ARMCapacity
|
||||||
end
|
end
|
||||||
|
|
||||||
--- [Internal] Function to set the SAM start state
|
--- [Internal] Function to set the SAM start state
|
||||||
@@ -1653,8 +1955,9 @@ do
|
|||||||
group:OptionEngageRange(engagerange) --default engagement will be 95% of firing range
|
group:OptionEngageRange(engagerange) --default engagement will be 95% of firing range
|
||||||
local grpname = group:GetName()
|
local grpname = group:GetName()
|
||||||
local grpcoord = group:GetCoordinate()
|
local grpcoord = group:GetCoordinate()
|
||||||
local grprange,grpheight,type,blind = self:_GetSAMRange(grpname)
|
local grprange,grpheight,type,blind,ARMCapacity = self:_GetSAMRange(grpname)
|
||||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type})
|
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 )
|
--table.insert( SEAD_Grps, grpname )
|
||||||
if type == MANTIS.SamType.LONG then
|
if type == MANTIS.SamType.LONG then
|
||||||
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
|
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||||
@@ -1718,12 +2021,15 @@ do
|
|||||||
if group:IsGround() and group:IsAlive() then
|
if group:IsGround() and group:IsAlive() then
|
||||||
local grpname = group:GetName()
|
local grpname = group:GetName()
|
||||||
local grpcoord = group:GetCoordinate()
|
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
|
-- TODO the below might stop working at some point after some hours, needs testing
|
||||||
--local radaralive = group:IsSAM()
|
--local radaralive = group:IsSAM()
|
||||||
|
if ARMCapacity and ARMCapacity>0 then _group:SetProperty("ARMCapacity",ARMCapacity) end
|
||||||
local radaralive = true
|
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( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity}) -- make the table lighter, as I don't really use the zone here
|
||||||
table.insert( SEAD_Grps, grpname )
|
if type ~= MANTIS.SamType.POINT then
|
||||||
|
table.insert( SEAD_Grps, grpname )
|
||||||
|
end
|
||||||
if type == MANTIS.SamType.LONG and radaralive then
|
if type == MANTIS.SamType.LONG and radaralive then
|
||||||
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
|
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||||
self:T({grpname,grprange, grpheight})
|
self:T({grpname,grprange, grpheight})
|
||||||
@@ -1780,10 +2086,122 @@ do
|
|||||||
self.ShoradLink = false
|
self.ShoradLink = false
|
||||||
return self
|
return self
|
||||||
end
|
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
|
-- 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
|
--- [Internal] Check detection function
|
||||||
-- @param #MANTIS self
|
-- @param #MANTIS self
|
||||||
@@ -1807,7 +2225,7 @@ do
|
|||||||
local radius = _data[3]
|
local radius = _data[3]
|
||||||
local height = _data[4]
|
local height = _data[4]
|
||||||
local blind = _data[5] * 1.25 + 1
|
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
|
if not shortsam then
|
||||||
shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false
|
shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false
|
||||||
end
|
end
|
||||||
@@ -1818,6 +2236,7 @@ do
|
|||||||
if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then
|
if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then
|
||||||
activeshorad = true
|
activeshorad = true
|
||||||
end
|
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 IsInZone and (not suppressed) and (not activeshorad) then --check any target in zone and not currently managed by SEAD
|
||||||
if samgroup:IsAlive() then
|
if samgroup:IsAlive() then
|
||||||
-- switch on SAM
|
-- switch on SAM
|
||||||
@@ -1833,19 +2252,13 @@ do
|
|||||||
switch = true
|
switch = true
|
||||||
end
|
end
|
||||||
if self.SamStateTracker[name] ~= "RED" and switch then
|
if self.SamStateTracker[name] ~= "RED" and switch then
|
||||||
self:__RedState(1,samgroup)
|
|
||||||
self.SamStateTracker[name] = "RED"
|
self.SamStateTracker[name] = "RED"
|
||||||
|
self:__RedState(1,samgroup)
|
||||||
end
|
end
|
||||||
-- TODO doesn't work
|
-- DONE Restrict on Distance
|
||||||
if shortsam == true and self.SmokeDecoy == true then
|
if shortsam == true and self.SmokeDecoy == true and Distance < self.DetectAccousticRadius*1.5 then
|
||||||
self:T("Smoking")
|
self:T("Smoking")
|
||||||
local units = samgroup:GetUnits() or {}
|
self:_SmokeUnits(samgroup)
|
||||||
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
|
end
|
||||||
-- link in to SHORAD if available
|
-- link in to SHORAD if available
|
||||||
-- DONE: Test integration fully
|
-- DONE: Test integration fully
|
||||||
@@ -1872,8 +2285,8 @@ do
|
|||||||
samgroup:OptionAlarmStateGreen()
|
samgroup:OptionAlarmStateGreen()
|
||||||
end
|
end
|
||||||
if self.SamStateTracker[name] ~= "GREEN" then
|
if self.SamStateTracker[name] ~= "GREEN" then
|
||||||
self:__GreenState(1,samgroup)
|
|
||||||
self.SamStateTracker[name] = "GREEN"
|
self.SamStateTracker[name] = "GREEN"
|
||||||
|
self:__GreenState(1,samgroup)
|
||||||
end
|
end
|
||||||
if self.debug or self.verbose then
|
if self.debug or self.verbose then
|
||||||
local text = string.format("SAM %s in alarm state GREEN!", name)
|
local text = string.format("SAM %s in alarm state GREEN!", name)
|
||||||
@@ -1883,12 +2296,13 @@ do
|
|||||||
end --end alive
|
end --end alive
|
||||||
end --end check
|
end --end check
|
||||||
end --for loop
|
end --for loop
|
||||||
|
--[[
|
||||||
if self.debug or self.verbose or self.logsamstatus then
|
if self.debug or self.verbose or self.logsamstatus then
|
||||||
for _,_status in pairs(self.SamStateTracker) do
|
for _,_status in pairs(self.SamStateTracker) do
|
||||||
if _status == "GREEN" then
|
if _status == "GREEN" then
|
||||||
instatusgreen=instatusgreen+1
|
instatusgreen=instatusgreen+1
|
||||||
elseif _status == "RED" then
|
elseif _status == "RED" then
|
||||||
instatusred=instatusred+1
|
instatusred=instatusred+1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if self.Shorad then
|
if self.Shorad then
|
||||||
@@ -1897,6 +2311,8 @@ do
|
|||||||
end
|
end
|
||||||
end
|
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
|
return instatusred, instatusgreen, activeshorads
|
||||||
end
|
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 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 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
|
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
|
else
|
||||||
local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
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
|
end
|
||||||
|
|
||||||
local function GetReport()
|
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)
|
local statusreport = REPORT:New("\nMANTIS Status "..self.name)
|
||||||
statusreport:Add("+-----------------------------+")
|
statusreport:Add("+-----------------------------+")
|
||||||
statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred))
|
statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred))
|
||||||
@@ -2030,21 +2462,22 @@ do
|
|||||||
else
|
else
|
||||||
self.Detection = self:StartIntelDetection()
|
self.Detection = self:StartIntelDetection()
|
||||||
end
|
end
|
||||||
--[[
|
|
||||||
if self.advAwacs and not self.automode then
|
|
||||||
self.AWACS_Detection = self:StartAwacsDetection()
|
|
||||||
end
|
|
||||||
--]]
|
|
||||||
if self.autoshorad then
|
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.Shorad:SetDefenseLimits(80,95)
|
||||||
self.ShoradLink = true
|
self.ShoradLink = true
|
||||||
self.Shorad.Groupset=self.ShoradGroupSet
|
self.Shorad.Groupset=self.ShoradGroupSet
|
||||||
self.Shorad.debug = self.debug
|
self.Shorad.debug = self.debug
|
||||||
|
self.Shorad:AddCallBack(self)
|
||||||
end
|
end
|
||||||
if self.shootandscoot and self.SkateZones and self.Shorad then
|
if self.shootandscoot and self.SkateZones and self.Shorad then
|
||||||
self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation)
|
self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self:HandleEvent(EVENTS.Hit,self._EventHandler)
|
||||||
|
self:HandleEvent(EVENTS.UnitLost,self._EventHandler)
|
||||||
|
|
||||||
self:__Status(-math.random(1,10))
|
self:__Status(-math.random(1,10))
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -2242,6 +2675,7 @@ do
|
|||||||
function MANTIS:onafterSeadSuppressionEnd(From, Event, To, Group, Name)
|
function MANTIS:onafterSeadSuppressionEnd(From, Event, To, Group, Name)
|
||||||
self:T({From, Event, To, Name})
|
self:T({From, Event, To, Name})
|
||||||
self.SuppressedGroups[Name] = false
|
self.SuppressedGroups[Name] = false
|
||||||
|
self.InboundARMs[Name] = 0
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ PSEUDOATC.id="PseudoATC | "
|
|||||||
|
|
||||||
--- PSEUDOATC version.
|
--- PSEUDOATC version.
|
||||||
-- @field #number 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
|
-- Create menu reporting commands
|
||||||
missionCommands.addCommandForGroup(GID, "Weather Report", submenu, self.ReportWeather, self, GID, UID, pos, name)
|
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)
|
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.
|
-- Debug message.
|
||||||
self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, GID))
|
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
|
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 #PSEUDOATC self
|
||||||
-- @param #number GID Group id of player unit.
|
-- @param #number GID Group id of player unit.
|
||||||
-- @param #number UID Unit id of player.
|
-- @param #number UID Unit id of player.
|
||||||
@@ -979,3 +1005,22 @@ function PSEUDOATC:_myname(unitname)
|
|||||||
|
|
||||||
return string.format("%s (%s)", csign, pname)
|
return string.format("%s (%s)", csign, pname)
|
||||||
end
|
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.
|
-- @param #string dest Name of the destination airport.
|
||||||
function RAT:_ATCAddFlight(name, dest)
|
function RAT:_ATCAddFlight(name, dest)
|
||||||
-- Debug info
|
-- 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
|
-- Create new flight
|
||||||
local flight={} --#RAT.AtcFlight
|
local flight={} --#RAT.AtcFlight
|
||||||
@@ -5603,7 +5603,7 @@ end
|
|||||||
function RAT._ATCDelFlight(t,entry)
|
function RAT._ATCDelFlight(t,entry)
|
||||||
for k,_ in pairs(t) do
|
for k,_ in pairs(t) do
|
||||||
if k==entry then
|
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
|
t[entry]=nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -5614,7 +5614,7 @@ end
|
|||||||
-- @param #string name Group name of the flight.
|
-- @param #string name Group name of the flight.
|
||||||
-- @param #number time Time the fight first registered.
|
-- @param #number time Time the fight first registered.
|
||||||
function RAT._ATCRegisterFlight(name, time)
|
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].Tarrive=time
|
||||||
RAT.ATC.flight[name].holding=0
|
RAT.ATC.flight[name].holding=0
|
||||||
end
|
end
|
||||||
@@ -5649,15 +5649,16 @@ function RAT._ATCStatus()
|
|||||||
|
|
||||||
-- Aircraft is holding.
|
-- Aircraft is holding.
|
||||||
local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy)
|
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
|
elseif hold==RAT.ATC.onfinal then
|
||||||
|
|
||||||
-- Aircarft is on final approach for landing.
|
-- 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)
|
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
|
elseif hold==RAT.ATC.unregistered then
|
||||||
|
|
||||||
@@ -5711,13 +5712,13 @@ function RAT._ATCCheck()
|
|||||||
-- Debug message.
|
-- 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.",
|
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)
|
airportname, flightname, qID, nqueue, flight.holding/60, flight.holding%60)
|
||||||
BASE:I(RAT.id..text)
|
BASE:T(RAT.id..text)
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.",
|
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)
|
airportname, flightname, flight.holding/60, flight.holding%60)
|
||||||
BASE:I(RAT.id..text)
|
BASE:T(RAT.id..text)
|
||||||
|
|
||||||
-- Clear flight for landing.
|
-- Clear flight for landing.
|
||||||
RAT._ATCClearForLanding(airportname, flightname)
|
RAT._ATCClearForLanding(airportname, flightname)
|
||||||
@@ -5772,7 +5773,7 @@ function RAT._ATCClearForLanding(airportname, flightname)
|
|||||||
|
|
||||||
|
|
||||||
-- Debug message.
|
-- 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
|
if string.find(flightname,"#") then
|
||||||
flightname = string.match(flightname,"^(.+)#")
|
flightname = string.match(flightname,"^(.+)#")
|
||||||
@@ -5799,8 +5800,9 @@ function RAT._ATCFlightLanded(name)
|
|||||||
|
|
||||||
-- Times for holding and final approach.
|
-- Times for holding and final approach.
|
||||||
local Tnow=timer.getTime()
|
local Tnow=timer.getTime()
|
||||||
local Tfinal=Tnow-flight.Tonfinal
|
local Tonfinal = flight.Tonfinal or timer.getTime()-1
|
||||||
local Thold=flight.Tonfinal-flight.Tarrive
|
local Tfinal=Tnow-Tonfinal
|
||||||
|
local Thold=Tonfinal-flight.Tarrive
|
||||||
|
|
||||||
local airport=RAT.ATC.airport[dest] --#RAT.AtcAirport
|
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
|
local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600
|
||||||
|
|
||||||
-- Debug info
|
-- 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: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:I(RAT.id..string.format("ATC %s: Number of flights still on final %d.", dest, airport.Nonfinal))
|
BASE:T(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: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.", dest, airport.traffic, TrafficPerHour))
|
||||||
|
|
||||||
if string.find(name,"#") then
|
if string.find(name,"#") then
|
||||||
name = string.match(name,"^(.+)#")
|
name = string.match(name,"^(.+)#")
|
||||||
|
|||||||
@@ -109,7 +109,6 @@
|
|||||||
-- @field Core.Menu#MENU_MISSION menuF10root Specific user defined root F10 menu.
|
-- @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 #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.
|
-- @field #boolean ceilingenabled Range has a ceiling and is not unlimited. Default is false.
|
||||||
|
|
||||||
-- @extends Core.Fsm#FSM
|
-- @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
|
--- *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:SetCoalition(Coalition or coalition.side.BLUE)
|
||||||
self.controlmsrs:SetLabel("RANGEC")
|
self.controlmsrs:SetLabel("RANGEC")
|
||||||
self.controlmsrs:SetVolume(Volume or 1.0)
|
self.controlmsrs:SetVolume(Volume or 1.0)
|
||||||
|
if self.rangezone then
|
||||||
|
self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate())
|
||||||
|
end
|
||||||
self.controlsrsQ = MSRSQUEUE:New("CONTROL")
|
self.controlsrsQ = MSRSQUEUE:New("CONTROL")
|
||||||
|
|
||||||
self.instructmsrs=MSRS:New(PathToSRS or MSRS.path, Frequency or 305, Modulation or radio.modulation.AM)
|
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:SetCoalition(Coalition or coalition.side.BLUE)
|
||||||
self.instructmsrs:SetLabel("RANGEI")
|
self.instructmsrs:SetLabel("RANGEI")
|
||||||
self.instructmsrs:SetVolume(Volume or 1.0)
|
self.instructmsrs:SetVolume(Volume or 1.0)
|
||||||
|
if self.rangezone then
|
||||||
|
self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate())
|
||||||
|
end
|
||||||
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
|
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
|
||||||
|
|
||||||
if PathToGoogleKey then
|
if PathToGoogleKey then
|
||||||
@@ -1316,8 +1321,13 @@ function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender
|
|||||||
self.rangecontrol = true
|
self.rangecontrol = true
|
||||||
if relayunitname then
|
if relayunitname then
|
||||||
local unit = UNIT:FindByName(relayunitname)
|
local unit = UNIT:FindByName(relayunitname)
|
||||||
local Coordinate = unit:GetCoordinate()
|
if unit then
|
||||||
self.rangecontrolrelayname = relayunitname
|
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
|
end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -1345,9 +1355,13 @@ function RANGE:SetSRSRangeInstructor( frequency, modulation, voice, culture, gen
|
|||||||
self.instructor = true
|
self.instructor = true
|
||||||
if relayunitname then
|
if relayunitname then
|
||||||
local unit = UNIT:FindByName(relayunitname)
|
local unit = UNIT:FindByName(relayunitname)
|
||||||
local Coordinate = unit:GetCoordinate()
|
if unit then
|
||||||
self.instructmsrs:SetCoordinate(Coordinate)
|
local Coordinate = unit:GetCoordinate()
|
||||||
self.instructorrelayname = relayunitname
|
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
|
end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -230,8 +230,10 @@ SCORING = {
|
|||||||
ClassID = 0,
|
ClassID = 0,
|
||||||
Players = {},
|
Players = {},
|
||||||
AutoSave = true,
|
AutoSave = true,
|
||||||
version = "1.18.4",
|
version = "1.18.5",
|
||||||
ScoringScenery = nil, -- Core.Set#SET_SCENERY
|
ScoringScenery = nil, -- Core.Set#SET_SCENERY
|
||||||
|
SceneryHitsInZone = false,
|
||||||
|
LoadSave = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
local _SCORINGCoalition = {
|
local _SCORINGCoalition = {
|
||||||
@@ -250,15 +252,16 @@ local _SCORINGCategory = {
|
|||||||
--- Creates a new SCORING object to administer the scoring achieved by players.
|
--- Creates a new SCORING object to administer the scoring achieved by players.
|
||||||
-- @param #SCORING self
|
-- @param #SCORING self
|
||||||
-- @param #string GameName The name of the game. This name is also logged in the CSV score file.
|
-- @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 #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.
|
-- @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
|
-- @return #SCORING self
|
||||||
-- @usage
|
-- @usage
|
||||||
--
|
--
|
||||||
-- -- Define a new scoring object for the mission Gori Valley.
|
-- -- Define a new scoring object for the mission Gori Valley.
|
||||||
-- ScoringObject = SCORING:New( "Gori Valley" )
|
-- ScoringObject = SCORING:New( "Gori Valley" )
|
||||||
--
|
--
|
||||||
function SCORING:New( GameName, SavePath, AutoSave )
|
function SCORING:New( GameName, SavePath, AutoSave, LoadSave )
|
||||||
|
|
||||||
-- Inherits from BASE
|
-- Inherits from BASE
|
||||||
local self = BASE:Inherit( self, BASE:New() ) -- #SCORING
|
local self = BASE:Inherit( self, BASE:New() ) -- #SCORING
|
||||||
@@ -266,7 +269,7 @@ function SCORING:New( GameName, SavePath, AutoSave )
|
|||||||
if GameName then
|
if GameName then
|
||||||
self.GameName = GameName
|
self.GameName = GameName
|
||||||
else
|
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
|
end
|
||||||
|
|
||||||
-- Additional Object scores
|
-- Additional Object scores
|
||||||
@@ -301,6 +304,12 @@ function SCORING:New( GameName, SavePath, AutoSave )
|
|||||||
self.penaltyoncoalitionchange = true
|
self.penaltyoncoalitionchange = true
|
||||||
|
|
||||||
self:SetDisplayMessagePrefix()
|
self:SetDisplayMessagePrefix()
|
||||||
|
|
||||||
|
self.SceneryHitsInZone = false
|
||||||
|
|
||||||
|
if LoadSave then
|
||||||
|
self.LoadSave = LoadSave
|
||||||
|
end
|
||||||
|
|
||||||
-- Event handlers
|
-- Event handlers
|
||||||
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
|
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
|
||||||
@@ -328,11 +337,61 @@ function SCORING:New( GameName, SavePath, AutoSave )
|
|||||||
end
|
end
|
||||||
|
|
||||||
self:I("SCORING "..tostring(GameName).." started! v"..self.version)
|
self:I("SCORING "..tostring(GameName).." started! v"..self.version)
|
||||||
|
|
||||||
|
if LoadSave == true then
|
||||||
|
self:_LoadPlayerSummaryScore()
|
||||||
|
end
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
end
|
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.
|
--- Set a prefix string that will be displayed at each scoring message sent.
|
||||||
-- @param #SCORING self
|
-- @param #SCORING self
|
||||||
-- @param #string DisplayMessagePrefix (Default="Scoring: ") The scoring prefix string.
|
-- @param #string DisplayMessagePrefix (Default="Scoring: ") The scoring prefix string.
|
||||||
@@ -579,6 +638,22 @@ function SCORING:AddZoneScore( ScoreZone, Score )
|
|||||||
return self
|
return self
|
||||||
end
|
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.
|
--- 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!
|
-- 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.
|
-- 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.
|
-- PlayerName can be nil, if the Unit with the player crashed or due to another reason.
|
||||||
if PlayerName then
|
if PlayerName then
|
||||||
local PlayerData = self.Players[PlayerName]
|
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] = PlayerData.Goals[GoalTag] or { Score = 0 }
|
||||||
PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score
|
PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score
|
||||||
@@ -1153,7 +1242,7 @@ function SCORING:_EventOnHit( Event )
|
|||||||
TargetType = "Scenery"
|
TargetType = "Scenery"
|
||||||
TargetSceneryObject = TargetUNIT
|
TargetSceneryObject = TargetUNIT
|
||||||
self:T("***** Target is Scenery and TargetUNIT is SCENERY object!")
|
self:T("***** Target is Scenery and TargetUNIT is SCENERY object!")
|
||||||
UTILS.PrintTableToLog(TargetSceneryObject)
|
--UTILS.PrintTableToLog(TargetSceneryObject)
|
||||||
end
|
end
|
||||||
|
|
||||||
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
|
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
|
||||||
@@ -1374,17 +1463,18 @@ function SCORING:_EventOnHit( Event )
|
|||||||
-- Player contains the score data from self.Players[WeaponPlayerName]
|
-- Player contains the score data from self.Players[WeaponPlayerName]
|
||||||
local PlayerName = Event.IniPlayerName or "Ghost"
|
local PlayerName = Event.IniPlayerName or "Ghost"
|
||||||
local Player = self.Players[PlayerName]
|
local Player = self.Players[PlayerName]
|
||||||
|
if Player then
|
||||||
Player.Score = Player.Score + Score
|
Player.Score = Player.Score + Score
|
||||||
Player.Score = Player.Score + self.ScoreIncrementOnHit
|
Player.Score = Player.Score + self.ScoreIncrementOnHit
|
||||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "hit in zone '" .. ScoreZone:GetName() .. "'." ..
|
MESSAGE:NewType( self.DisplayMessagePrefix .. "hit in zone '" .. ScoreZone:GetName() .. "'." ..
|
||||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
|
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
|
||||||
MESSAGE.Type.Information )
|
MESSAGE.Type.Information )
|
||||||
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
|
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
|
||||||
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
|
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
|
||||||
|
|
||||||
self:ScoreCSV( PlayerName, "", "HIT_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Zone", TargetUnitType )
|
self:ScoreCSV( PlayerName, "", "HIT_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Zone", TargetUnitType )
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1567,22 +1657,24 @@ function SCORING:_EventOnDeadOrCrash( Event )
|
|||||||
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Check if there are Zones where the destruction happened.
|
if self.SceneryHitsInZone == true then
|
||||||
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
|
-- Check if there are Zones where the destruction happened.
|
||||||
self:F( { ScoringZone = ScoreZoneData } )
|
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
|
||||||
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
|
self:F( { ScoringZone = ScoreZoneData } )
|
||||||
local Score = ScoreZoneData.Score
|
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
|
||||||
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
|
local Score = ScoreZoneData.Score
|
||||||
Player.Score = Player.Score + Score
|
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
|
||||||
TargetDestroy.Score = TargetDestroy.Score + Score
|
Player.Score = Player.Score + Score
|
||||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
|
TargetDestroy.Score = TargetDestroy.Score + Score
|
||||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
|
MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
|
||||||
MESSAGE.Type.Information )
|
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty,
|
||||||
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
|
MESSAGE.Type.Information )
|
||||||
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
|
: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
|
self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType )
|
||||||
|
Destroyed = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1926,9 +2018,12 @@ end
|
|||||||
--- Report all players score
|
--- Report all players score
|
||||||
-- @param #SCORING self
|
-- @param #SCORING self
|
||||||
-- @param Wrapper.Group#GROUP PlayerGroup The player group.
|
-- @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 PlayerMessage = ""
|
||||||
|
local ReportTable = {}
|
||||||
|
|
||||||
self:T( { "Summary Score Report of All Players", Players = self.Players } )
|
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 PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
|
||||||
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
|
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
|
||||||
|
|
||||||
PlayerMessage =
|
if JustScore~=true then
|
||||||
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
|
PlayerMessage =
|
||||||
PlayerName,
|
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
|
||||||
PlayerScore - PlayerPenalty,
|
PlayerName,
|
||||||
PlayerScore,
|
PlayerScore - PlayerPenalty,
|
||||||
PlayerPenalty
|
PlayerScore,
|
||||||
)
|
PlayerPenalty
|
||||||
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
|
)
|
||||||
|
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
|
||||||
|
else
|
||||||
|
ReportTable[PlayerName] = {["Score"]=PlayerScore,["Penalty"]=PlayerPenalty}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
return ReportTable
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Opens a score CSV file to log the scores.
|
||||||
|
-- @param #SCORING self
|
||||||
|
-- @param #number sSeconds
|
||||||
|
-- @return #string ClockString
|
||||||
function SCORING:SecondsToClock( sSeconds )
|
function SCORING:SecondsToClock( sSeconds )
|
||||||
local nSeconds = sSeconds
|
local nSeconds = sSeconds
|
||||||
if nSeconds == 0 then
|
if nSeconds == 0 then
|
||||||
@@ -2101,6 +2204,23 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes,
|
|||||||
|
|
||||||
self.CSVFile:write( "\n" )
|
self.CSVFile:write( "\n" )
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
--- Close CSV file
|
--- Close CSV file
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
--
|
--
|
||||||
-- ### Authors: **applevangelist**, **FlightControl**
|
-- ### 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("*", "ManageEvasion", "*")
|
||||||
self:AddTransition("*", "CalculateHitZone", "*")
|
self:AddTransition("*", "CalculateHitZone", "*")
|
||||||
|
|
||||||
self:I("*** SEAD - Started Version 0.4.9")
|
self:I("*** SEAD - Started Version 0.4.11")
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG
|
|||||||
elseif height <= 12500 then
|
elseif height <= 12500 then
|
||||||
Ropt = Ropt * 0.98
|
Ropt = Ropt * 0.98
|
||||||
end
|
end
|
||||||
|
local WeaponWrapper = WEAPON:New(SEADWeapon)
|
||||||
-- look at a couple of zones across the trajectory
|
-- look at a couple of zones across the trajectory
|
||||||
for n=1,3 do
|
for n=1,3 do
|
||||||
local dist = Ropt - ((n-1)*20000)
|
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
|
_targetgroupname = tgtgrp:GetName() -- group name
|
||||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||||
self:T("*** Found Target = ".. _targetgroupname)
|
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
|
--end
|
||||||
end
|
end
|
||||||
@@ -442,7 +442,18 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP
|
|||||||
local SuppressionStartTime = timer.getTime() + delay
|
local SuppressionStartTime = timer.getTime() + delay
|
||||||
local SuppressionEndTime = timer.getTime() + delay + _tti + self.Padding + delay
|
local SuppressionEndTime = timer.getTime() + delay + _tti + self.Padding + delay
|
||||||
local _targetgroupname = _targetgroup:GetName()
|
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))
|
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(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime)
|
||||||
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
|
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
-- @image Functional.Shorad.jpg
|
-- @image Functional.Shorad.jpg
|
||||||
--
|
--
|
||||||
-- Date: Nov 2021
|
-- Date: Nov 2021
|
||||||
-- Last Update: Jan 2025
|
-- Last Update: Jan 2026
|
||||||
|
|
||||||
-------------------------------------------------------------------------
|
-------------------------------------------------------------------------
|
||||||
--- **SHORAD** class, extends Core.Base#BASE
|
--- **SHORAD** class, extends Core.Base#BASE
|
||||||
@@ -48,7 +48,9 @@
|
|||||||
-- @field #number minscootdist Min distance of the next zone
|
-- @field #number minscootdist Min distance of the next zone
|
||||||
-- @field #number maxscootdist Max 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 #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
|
-- @extends Core.Base#BASE
|
||||||
|
|
||||||
|
|
||||||
@@ -114,7 +116,9 @@ SHORAD = {
|
|||||||
SkateZones = nil,
|
SkateZones = nil,
|
||||||
minscootdist = 100,
|
minscootdist = 100,
|
||||||
maxscootdist = 3000,
|
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 #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 #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 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
|
-- @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() )
|
local self = BASE:Inherit( self, FSM:New() )
|
||||||
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
|
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
|
||||||
|
|
||||||
@@ -171,6 +177,7 @@ do
|
|||||||
self.name = Name or "MyShorad"
|
self.name = Name or "MyShorad"
|
||||||
self.Prefixes = ShoradPrefix or "SAM SHORAD"
|
self.Prefixes = ShoradPrefix or "SAM SHORAD"
|
||||||
self.Radius = Radius or 20000
|
self.Radius = Radius or 20000
|
||||||
|
if type(Coalition) == "number" then Coalition = string.lower(UTILS.GetCoalitionName(Coalition)) end
|
||||||
self.Coalition = Coalition or "blue"
|
self.Coalition = Coalition or "blue"
|
||||||
self.Samset = Samset or GroupSet
|
self.Samset = Samset or GroupSet
|
||||||
self.ActiveTimer = ActiveTimer or 600
|
self.ActiveTimer = ActiveTimer or 600
|
||||||
@@ -181,8 +188,15 @@ do
|
|||||||
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
|
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
|
||||||
self.DefenseHighProb = 90 -- probability to detect a missile shot, high 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
|
self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||||
|
|
||||||
if UseEmOnOff == false then self.UseEmOnOff = UseEmOnOff end
|
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.
|
-- Set the string id for output to DCS.log file.
|
||||||
self.lid=string.format("SHORAD %s | ", self.name)
|
self.lid=string.format("SHORAD %s | ", self.name)
|
||||||
self:_InitState()
|
self:_InitState()
|
||||||
@@ -451,6 +465,43 @@ do
|
|||||||
return returnname
|
return returnname
|
||||||
end
|
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
|
--- Calculate if the missile shot is detected
|
||||||
-- @param #SHORAD self
|
-- @param #SHORAD self
|
||||||
-- @return #boolean Returns true for a detection, else false
|
-- @return #boolean Returns true for a detection, else false
|
||||||
@@ -484,7 +535,56 @@ do
|
|||||||
-- mymantis:Start()
|
-- mymantis:Start()
|
||||||
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat, ShotAt)
|
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat, ShotAt)
|
||||||
self:T(self.lid .. " WakeUpShorad")
|
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 targetcat = TargetCat or Object.Category.UNIT
|
||||||
local targetgroup = TargetGroup
|
local targetgroup = TargetGroup
|
||||||
local targetvec2 = nil
|
local targetvec2 = nil
|
||||||
@@ -501,69 +601,41 @@ do
|
|||||||
local groupset = self.Groupset --Core.Set#SET_GROUP
|
local groupset = self.Groupset --Core.Set#SET_GROUP
|
||||||
local shoradset = groupset:GetAliveSet() --#table
|
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
|
-- go through set and find the one(s) to activate
|
||||||
local TDiff = 4
|
|
||||||
for _,_group in pairs (shoradset) do
|
for _,_group in pairs (shoradset) do
|
||||||
|
|
||||||
local groupname = _group:GetName()
|
local groupname = _group:GetName()
|
||||||
|
|
||||||
if groupname == TargetGroup and ShotAt==true then
|
if groupname == TargetGroup and ShotAt==true then
|
||||||
-- Shot at a SHORAD group
|
-- Shot at a SHORAD group
|
||||||
if self.UseEmOnOff then
|
local allow = false
|
||||||
_group:EnableEmission(false)
|
if self.CallBack and self.UseCallBack == true then
|
||||||
|
allow = self.CallBack:SeadAllowSuppression(_group,groupname)
|
||||||
end
|
end
|
||||||
_group:OptionAlarmStateGreen()
|
if allow == true then
|
||||||
self.ActiveGroups[groupname] = nil
|
if self.UseEmOnOff then
|
||||||
local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName())
|
_group:EnableEmission(false)
|
||||||
self:T(text)
|
end
|
||||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
_group:OptionAlarmStateGreen()
|
||||||
|
self.ActiveGroups[groupname] = nil
|
||||||
--Shoot and Scoot
|
local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName())
|
||||||
if self.shootandscoot then
|
self:T(text)
|
||||||
self:__ShootAndScoot(1,_group)
|
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||||
end
|
self:_SmokeUnits(_group)
|
||||||
|
|
||||||
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)
|
|
||||||
--Shoot and Scoot
|
--Shoot and Scoot
|
||||||
if self.shootandscoot then
|
if self.shootandscoot then
|
||||||
self:__ShootAndScoot(TDiff,_group)
|
self:__ShootAndScoot(1,_group)
|
||||||
TDiff=TDiff+1
|
else
|
||||||
|
_group:RelocateGroundRandomInRadius(30,500,false,true,"Diamond",true)
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
WakeUp(_group,groupname)
|
||||||
end
|
end
|
||||||
end
|
elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then
|
||||||
end
|
WakeUp(_group,groupname)
|
||||||
|
end -- end if
|
||||||
|
end -- end in pairs
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -679,11 +751,12 @@ do
|
|||||||
-- @param Core.Event#EVENTDATA EventData The event details table data set
|
-- @param Core.Event#EVENTDATA EventData The event details table data set
|
||||||
-- @return #SHORAD self
|
-- @return #SHORAD self
|
||||||
function SHORAD:HandleEventShot( EventData )
|
function SHORAD:HandleEventShot( EventData )
|
||||||
self:T( { EventData } )
|
--self:T( { EventData.id } )
|
||||||
self:T(self.lid .. " HandleEventShot")
|
self:T(self.lid .. " HandleEventShot")
|
||||||
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired
|
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired
|
||||||
local ShootingWeaponName = EventData.WeaponName -- return weapon type
|
local ShootingWeaponName = EventData.WeaponName -- return weapon type
|
||||||
-- get firing coalition
|
-- get firing coalition
|
||||||
|
if not EventData.IniGroup then return self end
|
||||||
local weaponcoalition = EventData.IniGroup:GetCoalition()
|
local weaponcoalition = EventData.IniGroup:GetCoalition()
|
||||||
-- get detection probability
|
-- get detection probability
|
||||||
if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire
|
if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire
|
||||||
@@ -703,7 +776,7 @@ do
|
|||||||
-- Is there target data?
|
-- Is there target data?
|
||||||
if not targetdata or self.debug then
|
if not targetdata or self.debug then
|
||||||
if string.find(ShootingWeaponName,"AGM_88",1,true) 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 pos0 = EventData.IniUnit:GetCoordinate()
|
||||||
local fheight = EventData.IniUnit:GetHeight()
|
local fheight = EventData.IniUnit:GetHeight()
|
||||||
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
|
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
|
||||||
|
|||||||
@@ -4112,10 +4112,10 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay,
|
|||||||
|
|
||||||
-- Get name of template group.
|
-- Get name of template group.
|
||||||
local templategroupname=group:GetName()
|
local templategroupname=group:GetName()
|
||||||
|
local unit = group:GetUnit(1)
|
||||||
local Descriptors=group:GetUnit(1):GetDesc()
|
local Descriptors= (unit and unit:IsAlive()) and unit:GetDesc() or {}
|
||||||
local Category=group:GetCategory()
|
local Category=group:GetCategory()
|
||||||
local TypeName=group:GetTypeName()
|
local TypeName=group:GetTypeName() or "none"
|
||||||
local SpeedMax=group:GetSpeedMax()
|
local SpeedMax=group:GetSpeedMax()
|
||||||
local RangeMin=group:GetRange()
|
local RangeMin=group:GetRange()
|
||||||
local smax,sx,sy,sz=_GetObjectSize(Descriptors)
|
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]
|
unit.onboard_num=asset.modex[i]
|
||||||
end
|
end
|
||||||
if asset.callsign then
|
if asset.callsign then
|
||||||
|
--UTILS.PrintTableToLog(asset.callsign)
|
||||||
unit.callsign=asset.callsign[i]
|
unit.callsign=asset.callsign[i]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -8596,8 +8597,8 @@ function WAREHOUSE:_DeleteStockItem(stockitem)
|
|||||||
local item=self.stock[i] --#WAREHOUSE.Assetitem
|
local item=self.stock[i] --#WAREHOUSE.Assetitem
|
||||||
if item.uid==stockitem.uid then
|
if item.uid==stockitem.uid then
|
||||||
table.remove(self.stock,i)
|
table.remove(self.stock,i)
|
||||||
-- remove also from warehouse DB
|
-- 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
|
--_WAREHOUSEDB.Assets[stockitem.uid]=nil
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
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/Squadron.lua' )
|
||||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Target.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/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_Balancer.lua' )
|
||||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air.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_Zone.lua' )
|
||||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Dispatcher.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/Point.lua' )
|
||||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Procedure.lua' )
|
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Beacons.lua' )
|
||||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/FlightPlan.lua' )
|
|
||||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Radios.lua' )
|
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Radios.lua' )
|
||||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Towns.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.
|
-- 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()
|
-- 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
|
-- 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
|
if Squadron:IsStopped() then
|
||||||
Squadron:Start()
|
Squadron:Start()
|
||||||
end
|
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
|
return self
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -509,7 +509,7 @@ do
|
|||||||
-- @field #AWACS
|
-- @field #AWACS
|
||||||
AWACS = {
|
AWACS = {
|
||||||
ClassName = "AWACS", -- #string
|
ClassName = "AWACS", -- #string
|
||||||
version = "0.2.73", -- #string
|
version = "0.2.74", -- #string
|
||||||
lid = "", -- #string
|
lid = "", -- #string
|
||||||
coalition = coalition.side.BLUE, -- #number
|
coalition = coalition.side.BLUE, -- #number
|
||||||
coalitiontxt = "blue", -- #string
|
coalitiontxt = "blue", -- #string
|
||||||
@@ -2060,6 +2060,55 @@ function AWACS:SetRejectionZone(Zone,Draw)
|
|||||||
return self
|
return self
|
||||||
end
|
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.
|
--- [User] Draw a line around the FEZ on the F10 map.
|
||||||
-- @param #AWACS self
|
-- @param #AWACS self
|
||||||
-- @return #AWACS self
|
-- @return #AWACS self
|
||||||
@@ -4336,6 +4385,14 @@ function AWACS:_StartIntel(awacs)
|
|||||||
intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
|
intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
|
||||||
end
|
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
|
-- Callbacks
|
||||||
local function NewCluster(Cluster)
|
local function NewCluster(Cluster)
|
||||||
self:__NewCluster(5,Cluster)
|
self:__NewCluster(5,Cluster)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
-- @image OPS_CSAR.jpg
|
-- @image OPS_CSAR.jpg
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Last Update Oct 2025
|
-- Last Update Jan 2026
|
||||||
|
|
||||||
-------------------------------------------------------------------------
|
-------------------------------------------------------------------------
|
||||||
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
|
--- **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.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.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.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near.
|
||||||
-- mycsar.autosmokedistance = 1000 -- distance for autosmoke
|
-- 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.
|
-- 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.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.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.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
|
-- ## 2.1 Create own SET_GROUP to manage CTLD Pilot groups
|
||||||
--
|
--
|
||||||
@@ -272,6 +273,8 @@ CSAR = {
|
|||||||
UserSetGroup = nil,
|
UserSetGroup = nil,
|
||||||
AllowIRStrobe = false,
|
AllowIRStrobe = false,
|
||||||
IRStrobeRuntime = 300,
|
IRStrobeRuntime = 300,
|
||||||
|
FARPRescueDistance = 500,
|
||||||
|
EnableMenuSmokeMASH = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Downed pilots info.
|
--- Downed pilots info.
|
||||||
@@ -312,10 +315,12 @@ CSAR.AircraftType["MH-60R"] = 10
|
|||||||
CSAR.AircraftType["OH-6A"] = 2
|
CSAR.AircraftType["OH-6A"] = 2
|
||||||
CSAR.AircraftType["OH58D"] = 2
|
CSAR.AircraftType["OH58D"] = 2
|
||||||
CSAR.AircraftType["CH-47Fbl1"] = 31
|
CSAR.AircraftType["CH-47Fbl1"] = 31
|
||||||
|
CSAR.AircraftType["AH-6J"] = 2
|
||||||
|
CSAR.AircraftType["MH-6J"] = 2
|
||||||
|
|
||||||
--- CSAR class version.
|
--- CSAR class version.
|
||||||
-- @field #string version
|
-- @field #string version
|
||||||
CSAR.version="1.0.34"
|
CSAR.version="1.0.36"
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- ToDo list
|
-- 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.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.beaconRefresher = 29 -- seconds
|
||||||
self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase
|
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.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.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.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names!
|
||||||
self.template = Template or "generic" -- template for downed pilot
|
self.template = Template or "generic" -- template for downed pilot
|
||||||
self.mashprefix = {"MASH"} -- prefixes used to find MASHes
|
self.mashprefix = {"MASH"} -- prefixes used to find MASHes
|
||||||
|
self.EnableMenuSmokeMASH = true
|
||||||
|
|
||||||
self.autosmoke = false -- automatically smoke location when heli is near
|
self.autosmoke = false -- automatically smoke location when heli is near
|
||||||
self.autosmokedistance = 2000 -- distance for autosmoke
|
self.autosmokedistance = 2000 -- distance for autosmoke
|
||||||
@@ -673,7 +679,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript
|
|||||||
DownedPilot.alive = true
|
DownedPilot.alive = true
|
||||||
DownedPilot.wetfeet = Wetfeet or false
|
DownedPilot.wetfeet = Wetfeet or false
|
||||||
DownedPilot.BeaconName = BeaconName
|
DownedPilot.BeaconName = BeaconName
|
||||||
|
|
||||||
-- Add Pilot
|
-- Add Pilot
|
||||||
local PilotTable = self.downedPilots
|
local PilotTable = self.downedPilots
|
||||||
local counter = self.downedpilotcounter
|
local counter = self.downedpilotcounter
|
||||||
@@ -851,6 +857,10 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
|
|||||||
else
|
else
|
||||||
BeaconName = "Ghost-1-1"..math.random(1,10000)
|
BeaconName = "Ghost-1-1"..math.random(1,10000)
|
||||||
end
|
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
|
if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0
|
||||||
self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName)
|
self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName)
|
||||||
@@ -1232,7 +1242,7 @@ function CSAR:_EventHandler(EventData)
|
|||||||
local _place = _event.Place -- Wrapper.Airbase#AIRBASE
|
local _place = _event.Place -- Wrapper.Airbase#AIRBASE
|
||||||
|
|
||||||
if _place == nil then
|
if _place == nil then
|
||||||
self:T(self.lid .. " Landing Place Nil")
|
self:T(self.lid .. " Landing Place nil")
|
||||||
return self -- error!
|
return self -- error!
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1267,7 +1277,7 @@ function CSAR:_EventHandler(EventData)
|
|||||||
self:T("Country = ".._country.." Coalition = ".._coalition)
|
self:T("Country = ".._country.." Coalition = ".._coalition)
|
||||||
if _coalition == self.coalition then
|
if _coalition == self.coalition then
|
||||||
local _freq = self:_GenerateADFFrequency()
|
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.
|
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
|
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
|
||||||
@@ -1761,7 +1771,14 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule, I
|
|||||||
end
|
end
|
||||||
|
|
||||||
self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000))
|
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
|
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")
|
self:T(self.lid.."[Drop off debug] Distance ok, door check")
|
||||||
if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then
|
if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then
|
||||||
@@ -2138,15 +2155,52 @@ function CSAR:_Reqsmoke( _unitName )
|
|||||||
return self
|
return self
|
||||||
end
|
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.
|
--- (Internal) Determine distance to closest MASH.
|
||||||
-- @param #CSAR self
|
-- @param #CSAR self
|
||||||
-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT
|
-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT
|
||||||
-- @return #number Distance in meters
|
-- @return #number Distance in meters
|
||||||
-- @return #string MASH Name as string
|
-- @return #string MASH Name as string
|
||||||
|
-- @return Core.Point#COORDINATE Coordinate The MASH/AFB Coordinate (for smoke)
|
||||||
function CSAR:_GetClosestMASH(_heli)
|
function CSAR:_GetClosestMASH(_heli)
|
||||||
self:T(self.lid .. " _GetClosestMASH")
|
self:T(self.lid .. " _GetClosestMASH")
|
||||||
local _mashset = self.mash -- Core.Set#SET_GROUP
|
local _mashset = self.mash -- Core.Set#SET_GROUP
|
||||||
local MashSets = {}
|
local MashSets = {}
|
||||||
|
|
||||||
--local _mashes = _mashset.Set-- #table
|
--local _mashes = _mashset.Set-- #table
|
||||||
table.insert(MashSets,_mashset.Set)
|
table.insert(MashSets,_mashset.Set)
|
||||||
table.insert(MashSets,self.zonemashes.Set)
|
table.insert(MashSets,self.zonemashes.Set)
|
||||||
@@ -2155,12 +2209,21 @@ function CSAR:_GetClosestMASH(_heli)
|
|||||||
local _distance = 0
|
local _distance = 0
|
||||||
local _helicoord = _heli:GetCoordinate()
|
local _helicoord = _heli:GetCoordinate()
|
||||||
local MashName = nil
|
local MashName = nil
|
||||||
|
local Coordinate = nil -- Core.Point#COORDINATE
|
||||||
|
|
||||||
if self.allowFARPRescue then
|
if self.allowFARPRescue then
|
||||||
local position = _heli:GetCoordinate()
|
local afb,distance = _helicoord:GetClosestAirbase(nil,self.coalition)
|
||||||
local afb,distance = position:GetClosestAirbase(nil,self.coalition)
|
|
||||||
_shortestDistance = distance
|
_shortestDistance = distance
|
||||||
MashName = (afb ~= nil) and afb:GetName() or "Unknown"
|
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
|
end
|
||||||
|
|
||||||
for _,_mashes in pairs(MashSets) do
|
for _,_mashes in pairs(MashSets) do
|
||||||
@@ -2175,12 +2238,13 @@ function CSAR:_GetClosestMASH(_heli)
|
|||||||
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then
|
if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then
|
||||||
_shortestDistance = _distance
|
_shortestDistance = _distance
|
||||||
MashName = _mashUnit:GetName() or "Unknown"
|
MashName = _mashUnit:GetName() or "Unknown"
|
||||||
|
Coordinate = _mashcoord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if _shortestDistance ~= -1 then
|
if _shortestDistance ~= -1 then
|
||||||
return _shortestDistance, MashName
|
return _shortestDistance, MashName, Coordinate
|
||||||
else
|
else
|
||||||
return -1
|
return -1
|
||||||
end
|
end
|
||||||
@@ -2249,6 +2313,9 @@ function CSAR:_AddMedevacMenuItem()
|
|||||||
local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName)
|
local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName)
|
||||||
if self.AllowIRStrobe then
|
if self.AllowIRStrobe then
|
||||||
local _rootMenu5 = MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath, self._ReqIRStrobe,self,_unitName):Refresh()
|
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
|
else
|
||||||
_rootMenu4:Refresh()
|
_rootMenu4:Refresh()
|
||||||
end
|
end
|
||||||
@@ -2269,6 +2336,7 @@ function CSAR:_GetDistance(_point1, _point2)
|
|||||||
if _point1 and _point2 then
|
if _point1 and _point2 then
|
||||||
local distance1 = _point1:Get2DDistance(_point2)
|
local distance1 = _point1:Get2DDistance(_point2)
|
||||||
local distance2 = _point1:DistanceFromPointVec2(_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
|
if distance1 and type(distance1) == "number" then
|
||||||
return distance1
|
return distance1
|
||||||
elseif distance2 and type(distance2) == "number" then
|
elseif distance2 and type(distance2) == "number" then
|
||||||
@@ -2368,7 +2436,7 @@ function CSAR:_AddBeaconToGroup(_group, _freq, BeaconName)
|
|||||||
--local name = _radioUnit:GetName()
|
--local name = _radioUnit:GetName()
|
||||||
local Sound = "l10n/DEFAULT/"..self.radioSound
|
local Sound = "l10n/DEFAULT/"..self.radioSound
|
||||||
local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0}
|
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
|
trigger.action.radioTransmission(Sound, vec3, 0, true, Frequency, self.ADFRadioPwr or 500,BeaconName) -- Beacon in MP only runs for exactly 30secs straight
|
||||||
end
|
end
|
||||||
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.staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart()
|
||||||
self.zonemashes = SET_ZONE:New():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
|
if not self.coordinate then
|
||||||
local csarhq = self.mash:GetRandom()
|
local csarhq = self.mash:GetRandom()
|
||||||
if csarhq then
|
if csarhq then
|
||||||
@@ -2854,6 +2906,7 @@ function CSAR:onafterSave(From, Event, To, path, filename)
|
|||||||
if DownedPilot and DownedPilot.alive then
|
if DownedPilot and DownedPilot.alive then
|
||||||
-- get downed pilot data for saving
|
-- get downed pilot data for saving
|
||||||
local playerName = DownedPilot.player
|
local playerName = DownedPilot.player
|
||||||
|
if playerName == nil or playerName == "" then playerName = "AI MIA" end
|
||||||
local group = DownedPilot.group
|
local group = DownedPilot.group
|
||||||
local coalition = group:GetCoalition()
|
local coalition = group:GetCoalition()
|
||||||
local country = group:GetCountry()
|
local country = group:GetCountry()
|
||||||
@@ -2864,7 +2917,7 @@ function CSAR:onafterSave(From, Event, To, path, filename)
|
|||||||
local unitName = DownedPilot.originalUnit
|
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)
|
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
|
data = data .. txt
|
||||||
end
|
end
|
||||||
@@ -2979,8 +3032,8 @@ function CSAR:onafterLoad(From, Event, To, path, filename)
|
|||||||
|
|
||||||
-- Info message.
|
-- Info message.
|
||||||
local text=string.format("Loading CSAR state from file %s", filename)
|
local text=string.format("Loading CSAR state from file %s", filename)
|
||||||
MESSAGE:New(text,10):ToAllIf(self.Debug)
|
MESSAGE:New(text,10):ToAllIf(self.verbose>0)
|
||||||
self:I(self.lid..text)
|
self:T(self.lid..text)
|
||||||
|
|
||||||
local file=assert(io.open(filename, "rb"))
|
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}
|
self.TransportCategories = {Group.Category.HELICOPTER}
|
||||||
|
|
||||||
-- Create a new COMMANDER.
|
-- Create a new COMMANDER.
|
||||||
self.commander=COMMANDER:New(Coalition)
|
self.commander=COMMANDER:New(Coalition, Alias)
|
||||||
|
|
||||||
|
|
||||||
-- Add FSM transitions.
|
-- Add FSM transitions.
|
||||||
-- From State --> Event --> To State
|
-- From State --> Event --> To State
|
||||||
@@ -3357,6 +3358,15 @@ function CHIEF._CheckAssetProperties(Asset, Properties)
|
|||||||
return false
|
return false
|
||||||
end
|
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
|
return cohorts
|
||||||
end
|
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.
|
--- Recruit assets for a given mission.
|
||||||
-- @param #COMMANDER self
|
-- @param #COMMANDER self
|
||||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
-- @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
|
-- Easy CAP/GCI Class, based on OPS classes
|
||||||
-------------------------------------------------------------------------
|
-------------------------------------------------------------------------
|
||||||
@@ -12,7 +21,7 @@
|
|||||||
--
|
--
|
||||||
-------------------------------------------------------------------------
|
-------------------------------------------------------------------------
|
||||||
-- Date: September 2023
|
-- Date: September 2023
|
||||||
-- Last Update: Aug 2025
|
-- Last Update: Jan 2026
|
||||||
-------------------------------------------------------------------------
|
-------------------------------------------------------------------------
|
||||||
--
|
--
|
||||||
--- **Ops** - Easy GCI & CAP Manager
|
--- **Ops** - Easy GCI & CAP Manager
|
||||||
@@ -80,6 +89,8 @@
|
|||||||
-- @field #number FuelCriticalThreshold
|
-- @field #number FuelCriticalThreshold
|
||||||
-- @field #boolean showpatrolpointmarks
|
-- @field #boolean showpatrolpointmarks
|
||||||
-- @field #table EngageTargetTypes
|
-- @field #table EngageTargetTypes
|
||||||
|
-- @field #number maintenancetime
|
||||||
|
-- @field #number repairtime
|
||||||
-- @extends Core.Fsm#FSM
|
-- @extends Core.Fsm#FSM
|
||||||
|
|
||||||
--- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown.
|
--- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown.
|
||||||
@@ -109,7 +120,7 @@
|
|||||||
-- ### Prerequisites
|
-- ### 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!**
|
-- 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.
|
-- 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".
|
-- Put an EW radar system on the map and name it aptly, like "Blue EWR".
|
||||||
--
|
--
|
||||||
@@ -205,7 +216,7 @@ EASYGCICAP = {
|
|||||||
coalition = "blue",
|
coalition = "blue",
|
||||||
alias = nil,
|
alias = nil,
|
||||||
wings = {},
|
wings = {},
|
||||||
Intel = nil,
|
Intel = nil, -- Ops.Intel#INTEL
|
||||||
resurrection = 900,
|
resurrection = 900,
|
||||||
capspeed = 300,
|
capspeed = 300,
|
||||||
capalt = 25000,
|
capalt = 25000,
|
||||||
@@ -275,10 +286,11 @@ EASYGCICAP = {
|
|||||||
|
|
||||||
--- EASYGCICAP class version.
|
--- EASYGCICAP class version.
|
||||||
-- @field #string version
|
-- @field #string version
|
||||||
EASYGCICAP.version="0.1.30"
|
EASYGCICAP.version="0.1.34"
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- TODO list
|
--
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- TODO: TBD
|
-- TODO: TBD
|
||||||
@@ -300,6 +312,10 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
|||||||
|
|
||||||
-- defaults
|
-- defaults
|
||||||
self.alias = Alias or AirbaseName.." CAP Wing"
|
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.coalitionname = string.lower(Coalition) or "blue"
|
||||||
self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED
|
self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED
|
||||||
self.wings = {}
|
self.wings = {}
|
||||||
@@ -333,10 +349,8 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
|||||||
self.FuelCriticalThreshold = 10
|
self.FuelCriticalThreshold = 10
|
||||||
self.showpatrolpointmarks = false
|
self.showpatrolpointmarks = false
|
||||||
self.EngageTargetTypes = {"Air"}
|
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.
|
-- Add FSM transitions.
|
||||||
-- From State --> Event --> To State
|
-- From State --> Event --> To State
|
||||||
self:SetStartState("Stopped")
|
self:SetStartState("Stopped")
|
||||||
@@ -350,6 +364,34 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
|||||||
|
|
||||||
self:__Start(math.random(6,12))
|
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
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -369,6 +411,28 @@ function EASYGCICAP:GetAirwing(AirbaseName)
|
|||||||
return nil
|
return nil
|
||||||
end
|
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
|
--- Get a table of all managed AirWings
|
||||||
-- @param #EASYGCICAP self
|
-- @param #EASYGCICAP self
|
||||||
-- @return #table Table of Ops.AirWing#AIRWING Airwings
|
-- @return #table Table of Ops.AirWing#AIRWING Airwings
|
||||||
@@ -548,6 +612,18 @@ function EASYGCICAP:SetDefaultMissionRange(Range)
|
|||||||
return self
|
return self
|
||||||
end
|
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)
|
--- Set default number of airframes standing by for intercept tasks (visible on the airfield)
|
||||||
-- @param #EASYGCICAP self
|
-- @param #EASYGCICAP self
|
||||||
-- @param #number Airframes defaults to 2
|
-- @param #number Airframes defaults to 2
|
||||||
@@ -611,7 +687,6 @@ function EASYGCICAP:SetCapStartTimeVariation(Start, End)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--- Set which target types CAP flights will prefer to engage, defaults to {"Air"}
|
--- Set which target types CAP flights will prefer to engage, defaults to {"Air"}
|
||||||
-- @param #EASYGCICAP self
|
-- @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"},
|
-- @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
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--- (internal) Create and add another AirWing to the manager
|
--- (Internal) Create and add another AirWing to the manager
|
||||||
-- @param #EASYGCICAP self
|
-- @param #EASYGCICAP self
|
||||||
-- @param #string Airbasename
|
-- @param #string Airbasename
|
||||||
-- @param #string Alias
|
-- @param #string Alias
|
||||||
@@ -763,8 +838,13 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.noalert5 > 0 then
|
if self.noalert5 > 0 then
|
||||||
local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
|
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:SetRequiredAssets(self.noalert5)
|
||||||
alert:SetRepeat(99)
|
alert:SetRepeat(99)
|
||||||
CAP_Wing:AddMission(alert)
|
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:AddMissionCapability({AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROLRACETRACK, AUFTRAG.Type.ALERT5})
|
||||||
--Squadron_One:SetFuelLowRefuel(true)
|
--Squadron_One:SetFuelLowRefuel(true)
|
||||||
Squadron_One:SetFuelLowThreshold(0.3)
|
Squadron_One:SetFuelLowThreshold(0.3)
|
||||||
Squadron_One:SetTurnoverTime(10,20)
|
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||||
Squadron_One:SetModex(Modex)
|
Squadron_One:SetModex(Modex)
|
||||||
Squadron_One:SetLivery(Livery)
|
Squadron_One:SetLivery(Livery)
|
||||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
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:AddMissionCapability({AUFTRAG.Type.RECON})
|
||||||
--Squadron_One:SetFuelLowRefuel(true)
|
--Squadron_One:SetFuelLowRefuel(true)
|
||||||
Squadron_One:SetFuelLowThreshold(0.3)
|
Squadron_One:SetFuelLowThreshold(0.3)
|
||||||
Squadron_One:SetTurnoverTime(10,20)
|
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||||
Squadron_One:SetModex(Modex)
|
Squadron_One:SetModex(Modex)
|
||||||
Squadron_One:SetLivery(Livery)
|
Squadron_One:SetLivery(Livery)
|
||||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
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:AddMissionCapability({AUFTRAG.Type.TANKER})
|
||||||
--Squadron_One:SetFuelLowRefuel(true)
|
--Squadron_One:SetFuelLowRefuel(true)
|
||||||
Squadron_One:SetFuelLowThreshold(0.3)
|
Squadron_One:SetFuelLowThreshold(0.3)
|
||||||
Squadron_One:SetTurnoverTime(10,20)
|
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||||
Squadron_One:SetModex(Modex)
|
Squadron_One:SetModex(Modex)
|
||||||
Squadron_One:SetLivery(Livery)
|
Squadron_One:SetLivery(Livery)
|
||||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
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:AddMissionCapability({AUFTRAG.Type.AWACS})
|
||||||
--Squadron_One:SetFuelLowRefuel(true)
|
--Squadron_One:SetFuelLowRefuel(true)
|
||||||
Squadron_One:SetFuelLowThreshold(0.3)
|
Squadron_One:SetFuelLowThreshold(0.3)
|
||||||
Squadron_One:SetTurnoverTime(10,20)
|
Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime)
|
||||||
Squadron_One:SetModex(Modex)
|
Squadron_One:SetModex(Modex)
|
||||||
Squadron_One:SetLivery(Livery)
|
Squadron_One:SetLivery(Livery)
|
||||||
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
|
||||||
@@ -1300,6 +1380,54 @@ function EASYGCICAP:AddConflictZone(Zone)
|
|||||||
return self
|
return self
|
||||||
end
|
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.
|
--- (Internal) Try to assign the intercept to a FlightGroup already in air and ready.
|
||||||
-- @param #EASYGCICAP self
|
-- @param #EASYGCICAP self
|
||||||
@@ -1459,6 +1587,8 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
|||||||
if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then
|
if coord and conflictset:Count() > 0 and conflictset:IsCoordinateInZone(coord) then
|
||||||
success = false
|
success = false
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
success = true
|
||||||
end
|
end
|
||||||
return success
|
return success
|
||||||
end,
|
end,
|
||||||
@@ -1468,6 +1598,14 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
|||||||
)
|
)
|
||||||
end
|
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)
|
table.insert(self.ListOfAuftrag,InterceptAuftrag)
|
||||||
local assigned, rest = self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize)
|
local assigned, rest = self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize)
|
||||||
if not assigned then
|
if not assigned then
|
||||||
@@ -1500,6 +1638,14 @@ function EASYGCICAP:_StartIntel()
|
|||||||
BlueIntel:SetRejectZones(self.NoGoZoneSet)
|
BlueIntel:SetRejectZones(self.NoGoZoneSet)
|
||||||
BlueIntel:SetConflictZones(self.ConflictZoneSet)
|
BlueIntel:SetConflictZones(self.ConflictZoneSet)
|
||||||
BlueIntel:SetVerbosity(0)
|
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()
|
BlueIntel:Start()
|
||||||
|
|
||||||
if self.debug then
|
if self.debug then
|
||||||
@@ -1603,7 +1749,7 @@ function EASYGCICAP:onafterStatus(From,Event,To)
|
|||||||
tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER})
|
tankermission = tankermission + _wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER})
|
||||||
assets = assets + count
|
assets = assets + count
|
||||||
instock = instock + count2
|
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
|
-- update ready groups
|
||||||
self.ReadyFlightGroups = nil
|
self.ReadyFlightGroups = nil
|
||||||
self.ReadyFlightGroups = {}
|
self.ReadyFlightGroups = {}
|
||||||
@@ -1614,7 +1760,8 @@ function EASYGCICAP:onafterStatus(From,Event,To)
|
|||||||
local name = FG:GetName()
|
local name = FG:GetName()
|
||||||
local engage = FG:IsEngaging()
|
local engage = FG:IsEngaging()
|
||||||
local hasmissiles = FG:IsOutOfMissiles() == nil and true or false
|
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)))
|
--self:T(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready)))
|
||||||
if ready then
|
if ready then
|
||||||
self.ReadyFlightGroups[name] = FG
|
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 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 #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 #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
|
-- @extends Ops.OpsGroup#OPSGROUP
|
||||||
|
|
||||||
@@ -153,8 +151,7 @@ FLIGHTGROUP = {
|
|||||||
playerWarnings = {},
|
playerWarnings = {},
|
||||||
prohibitAB = false,
|
prohibitAB = false,
|
||||||
jettisonEmptyTanks = true,
|
jettisonEmptyTanks = true,
|
||||||
jettisonWeapons = true, -- that's actually a negative option like prohibitAB
|
jettisonWeapons = true, -- that's actually a negative option like prohibitAB
|
||||||
flightplans = {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -888,17 +885,6 @@ function FLIGHTGROUP:GetFlightControl()
|
|||||||
return self.flightcontrol
|
return self.flightcontrol
|
||||||
end
|
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.
|
--- Set the homebase.
|
||||||
-- @param #FLIGHTGROUP self
|
-- @param #FLIGHTGROUP self
|
||||||
@@ -1723,10 +1709,6 @@ function FLIGHTGROUP:Status()
|
|||||||
-- TODO: Not sure why I introduced this here.
|
-- TODO: Not sure why I introduced this here.
|
||||||
self:_CheckGroupDone()
|
self:_CheckGroupDone()
|
||||||
end
|
end
|
||||||
|
|
||||||
if not (self.cargoTransport or mission) then
|
|
||||||
self:_CheckFlightPlans()
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -3817,56 +3799,6 @@ function FLIGHTGROUP:onafterFuelCritical(From, Event, To)
|
|||||||
end
|
end
|
||||||
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
|
-- 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 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 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 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 Contacts Table of detected items.
|
||||||
-- @field #table ContactsLost Table of lost detected items.
|
-- @field #table ContactsLost Table of lost detected items.
|
||||||
-- @field #table ContactsUnknown Table of new 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 #number prediction Seconds default to be used with CalcClusterFuturePosition.
|
||||||
-- @field #boolean detectStatics If `true`, detect STATIC objects. Default `false`.
|
-- @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 #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
|
-- @extends Core.Fsm#FSM
|
||||||
|
|
||||||
--- Top Secret!
|
--- Top Secret!
|
||||||
@@ -102,6 +108,9 @@ INTEL = {
|
|||||||
clusterarrows = false,
|
clusterarrows = false,
|
||||||
prediction = 300,
|
prediction = 300,
|
||||||
detectStatics = false,
|
detectStatics = false,
|
||||||
|
DetectAccoustic = false,
|
||||||
|
DetectAccousticRadius = 1000,
|
||||||
|
DetectAccousticUnitTypes = {Unit.Category.HELICOPTER},
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Detected item info.
|
--- Detected item info.
|
||||||
@@ -160,14 +169,14 @@ INTEL.Ctype={
|
|||||||
|
|
||||||
--- INTEL class version.
|
--- INTEL class version.
|
||||||
-- @field #string version
|
-- @field #string version
|
||||||
INTEL.version="0.3.6"
|
INTEL.version="0.3.10"
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- ToDo list
|
-- ToDo list
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- TODO: Add min cluster size. Only create new clusters if they have a certain group size.
|
-- 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: Add statics.
|
||||||
-- DONE: Filter detection methods.
|
-- DONE: Filter detection methods.
|
||||||
-- DONE: Accept zones.
|
-- DONE: Accept zones.
|
||||||
@@ -175,6 +184,7 @@ INTEL.version="0.3.6"
|
|||||||
-- NOGO: SetAttributeZone --> return groups of generalized attributes in a zone.
|
-- 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: 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: Combine units to groups for all, new and lost.
|
||||||
|
-- DONE: Add corridor zones.
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- Constructor
|
-- Constructor
|
||||||
@@ -266,6 +276,7 @@ function INTEL:New(DetectionSet, Coalition, Alias)
|
|||||||
self:SetForgetTime()
|
self:SetForgetTime()
|
||||||
self:SetAcceptZones()
|
self:SetAcceptZones()
|
||||||
self:SetRejectZones()
|
self:SetRejectZones()
|
||||||
|
self:SetCorridorZones()
|
||||||
self:SetConflictZones()
|
self:SetConflictZones()
|
||||||
|
|
||||||
------------------------
|
------------------------
|
||||||
@@ -398,6 +409,26 @@ function INTEL:SetAcceptZones(AcceptZoneSet)
|
|||||||
return self
|
return self
|
||||||
end
|
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.
|
--- Add an accept zone. Only contacts detected in this zone are considered.
|
||||||
-- @param #INTEL self
|
-- @param #INTEL self
|
||||||
-- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set.
|
-- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set.
|
||||||
@@ -475,7 +506,69 @@ function INTEL:RemoveConflictZone(ConflictZone)
|
|||||||
return self
|
return self
|
||||||
end
|
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.
|
-- 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.
|
-- This avoids fast oscillations between a contact being detected and undetected.
|
||||||
-- @param #INTEL self
|
-- @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)
|
self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting, self.DetectVisual, self.DetectOptical, self.DetectRadar, self.DetectIRST, self.DetectRWR, self.DetectDLINK)
|
||||||
|
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
@@ -884,6 +989,36 @@ function INTEL:UpdateIntel()
|
|||||||
table.insert(remove, unitname)
|
table.insert(remove, unitname)
|
||||||
end
|
end
|
||||||
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.
|
-- 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
|
if #self.filterCategory>0 and unit:IsInstanceOf("UNIT") then
|
||||||
@@ -1040,7 +1175,7 @@ function INTEL:_CreateContact(Positionable, RecceName)
|
|||||||
item.category=3 --static:GetCategory()
|
item.category=3 --static:GetCategory()
|
||||||
item.categoryname=static:GetCategoryName() or "Unknown"
|
item.categoryname=static:GetCategoryName() or "Unknown"
|
||||||
item.threatlevel=static:GetThreatLevel() or 0
|
item.threatlevel=static:GetThreatLevel() or 0
|
||||||
item.position=static:GetCoordinate()
|
item.position=static:GetCoord()
|
||||||
item.velocity=static:GetVelocityVec3()
|
item.velocity=static:GetVelocityVec3()
|
||||||
item.speed=0
|
item.speed=0
|
||||||
item.recce=RecceName
|
item.recce=RecceName
|
||||||
@@ -1106,7 +1241,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, DetectedStatics, RecceDetecti
|
|||||||
return self
|
return self
|
||||||
end
|
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.
|
-- 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.
|
-- If no detection method is given, the detection will use all the available methods by default.
|
||||||
-- @param #INTEL self
|
-- @param #INTEL self
|
||||||
@@ -1147,15 +1282,15 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua
|
|||||||
local DetectionAccepted = true
|
local DetectionAccepted = true
|
||||||
|
|
||||||
if self.RadarAcceptRange then
|
if self.RadarAcceptRange then
|
||||||
local reccecoord = Unit:GetCoordinate()
|
local reccecoord = Unit:GetCoord()
|
||||||
local coord = unit:GetCoordinate()
|
local coord = unit:GetCoord()
|
||||||
local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km
|
local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km
|
||||||
if dist > self.RadarAcceptRangeKilometers then DetectionAccepted = false end
|
if dist > self.RadarAcceptRangeKilometers then DetectionAccepted = false end
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.RadarBlur then
|
if self.RadarBlur then
|
||||||
local reccecoord = Unit:GetCoordinate()
|
local reccecoord = Unit:GetCoord()
|
||||||
local coord = unit:GetCoordinate()
|
local coord = unit:GetCoord()
|
||||||
local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km
|
local dist = math.floor(coord:Get2DDistance(reccecoord)/1000) -- km
|
||||||
local AGL = unit:GetAltitude(true)
|
local AGL = unit:GetAltitude(true)
|
||||||
local minheight = self.RadarBlurMinHeight or 250 -- meters
|
local minheight = self.RadarBlurMinHeight or 250 -- meters
|
||||||
@@ -1202,6 +1337,33 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua
|
|||||||
end
|
end
|
||||||
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
|
-- FSM Events
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -2144,7 +2306,7 @@ function INTEL:GetClusterCoordinate(Cluster, Update)
|
|||||||
return Cluster.coordinate
|
return Cluster.coordinate
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Check if the coorindate of the cluster changed.
|
--- Check if the coordindate of the cluster changed.
|
||||||
-- @param #INTEL self
|
-- @param #INTEL self
|
||||||
-- @param #INTEL.Cluster Cluster The cluster.
|
-- @param #INTEL.Cluster Cluster The cluster.
|
||||||
-- @param #number Threshold in meters. Default 100 m.
|
-- @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 Wrapper.Marker#MARKER marker Marker on the F10 map.
|
||||||
-- @field #string formation Ground formation. Similar to action but on/off road.
|
-- @field #string formation Ground formation. Similar to action but on/off road.
|
||||||
-- @field #number missionUID Mission UID (Auftragsnr) this waypoint belongs to.
|
-- @field #number missionUID Mission UID (Auftragsnr) this waypoint belongs to.
|
||||||
-- @field Navigation.FlightPlan#FLIGHTPLAN flightplan Flightplan this waypoint belongs to.
|
|
||||||
|
|
||||||
--- Cargo Carrier status.
|
--- Cargo Carrier status.
|
||||||
-- @type OPSGROUP.CarrierStatus
|
-- @type OPSGROUP.CarrierStatus
|
||||||
@@ -576,11 +575,6 @@ function OPSGROUP:New(group)
|
|||||||
|
|
||||||
-- Set DCS group and controller.
|
-- Set DCS group and controller.
|
||||||
self.dcsgroup=self:GetDCSGroup()
|
self.dcsgroup=self:GetDCSGroup()
|
||||||
|
|
||||||
if not self.dcsgroup then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
self.controller=self.dcsgroup:getController()
|
self.controller=self.dcsgroup:getController()
|
||||||
|
|
||||||
-- Category.
|
-- Category.
|
||||||
@@ -11417,9 +11411,9 @@ function OPSGROUP:_SimpleTaskFunction(Function, uid)
|
|||||||
return DCSTask
|
return DCSTask
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Enhanced waypoint table.
|
--- Enhance waypoint table.
|
||||||
-- @param #OPSGROUP self
|
-- @param #OPSGROUP self
|
||||||
-- @param #OPSGROUP.Waypoint waypoint Waypoint data.
|
-- @param #OPSGROUP.Waypoint Waypoint data.
|
||||||
-- @return #OPSGROUP.Waypoint Modified waypoint data.
|
-- @return #OPSGROUP.Waypoint Modified waypoint data.
|
||||||
function OPSGROUP:_CreateWaypoint(waypoint)
|
function OPSGROUP:_CreateWaypoint(waypoint)
|
||||||
|
|
||||||
@@ -12847,7 +12841,7 @@ function OPSGROUP:_UpdatePosition()
|
|||||||
self.positionLast=self.position or self:GetVec3()
|
self.positionLast=self.position or self:GetVec3()
|
||||||
self.headingLast=self.heading or self:GetHeading()
|
self.headingLast=self.heading or self:GetHeading()
|
||||||
self.orientXLast=self.orientX or self:GetOrientationX()
|
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.
|
-- Current state.
|
||||||
self.position=self:GetVec3()
|
self.position=self:GetVec3()
|
||||||
@@ -13693,24 +13687,20 @@ end
|
|||||||
-- @return Core.Point#COORDINATE The coordinate of the object.
|
-- @return Core.Point#COORDINATE The coordinate of the object.
|
||||||
function OPSGROUP:_CoordinateFromObject(Object)
|
function OPSGROUP:_CoordinateFromObject(Object)
|
||||||
|
|
||||||
env.info("FF coordfrom object")
|
|
||||||
if Object then
|
if Object then
|
||||||
if VECTOR._IsVector(Object) then
|
if Object:IsInstanceOf("COORDINATE") then
|
||||||
env.info("FF VECTOR")
|
|
||||||
return Object:GetCoordinate()
|
|
||||||
elseif Object:IsInstanceOf("COORDINATE") then
|
|
||||||
return Object
|
return Object
|
||||||
else
|
else
|
||||||
if Object:IsInstanceOf("POSITIONABLE") or Object:IsInstanceOf("ZONE_BASE") then
|
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()
|
local coord=Object:GetCoordinate()
|
||||||
return coord
|
return coord
|
||||||
else
|
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
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
self:E(self.lid.."ERROR: Object passed is nil!")
|
self:T(self.lid.."ERROR: Object passed is nil!")
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
-- ===
|
-- ===
|
||||||
-- @module Ops.PlayerTask
|
-- @module Ops.PlayerTask
|
||||||
-- @image OPS_PlayerTask.jpg
|
-- @image OPS_PlayerTask.jpg
|
||||||
-- @date Last Update Oct 2025
|
-- @date Last Update Dec 2025
|
||||||
|
|
||||||
|
|
||||||
do
|
do
|
||||||
@@ -61,6 +61,7 @@ do
|
|||||||
-- @field #number PreviousCount
|
-- @field #number PreviousCount
|
||||||
-- @field #boolean CanSmoke
|
-- @field #boolean CanSmoke
|
||||||
-- @field #boolean ShowThreatDetails
|
-- @field #boolean ShowThreatDetails
|
||||||
|
-- @field #boolean PersistMe
|
||||||
-- @extends Core.Fsm#FSM
|
-- @extends Core.Fsm#FSM
|
||||||
|
|
||||||
|
|
||||||
@@ -98,11 +99,12 @@ PLAYERTASK = {
|
|||||||
PreviousCount = 0,
|
PreviousCount = 0,
|
||||||
CanSmoke = true,
|
CanSmoke = true,
|
||||||
ShowThreatDetails = true,
|
ShowThreatDetails = true,
|
||||||
|
PersistMe = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
--- PLAYERTASK class version.
|
--- PLAYERTASK class version.
|
||||||
-- @field #string version
|
-- @field #string version
|
||||||
PLAYERTASK.version="0.1.29"
|
PLAYERTASK.version="0.1.31"
|
||||||
|
|
||||||
--- Generic task condition.
|
--- Generic task condition.
|
||||||
-- @type PLAYERTASK.Condition
|
-- @type PLAYERTASK.Condition
|
||||||
@@ -235,7 +237,7 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType)
|
|||||||
-- @param #string From From state.
|
-- @param #string From From state.
|
||||||
-- @param #string Event Event.
|
-- @param #string Event Event.
|
||||||
-- @param #string To To state.
|
-- @param #string To To state.
|
||||||
-- @param #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.
|
--- On After "Planned" event. Task has been planned.
|
||||||
-- @function [parent=#PLAYERTASK] OnAfterPilotPlanned
|
-- @function [parent=#PLAYERTASK] OnAfterPilotPlanned
|
||||||
@@ -401,8 +403,17 @@ function PLAYERTASK:CanJoinTask(Group, Client)
|
|||||||
return true
|
return true
|
||||||
end
|
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
|
--- [Internal] Add a PLAYERTASKCONTROLLER for this task
|
||||||
-- @param #PLAYERTASK self
|
-- @param #PLAYERTASK self
|
||||||
|
--
|
||||||
-- @param Ops.PlayerTask#PLAYERTASKCONTROLLER Controller
|
-- @param Ops.PlayerTask#PLAYERTASKCONTROLLER Controller
|
||||||
-- @return #PLAYERTASK self
|
-- @return #PLAYERTASK self
|
||||||
function PLAYERTASK:_SetController(Controller)
|
function PLAYERTASK:_SetController(Controller)
|
||||||
@@ -1088,7 +1099,6 @@ function PLAYERTASK:onafterStatus(From, Event, To)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--- [Internal] On after progress call
|
--- [Internal] On after progress call
|
||||||
-- @param #PLAYERTASK self
|
-- @param #PLAYERTASK self
|
||||||
-- @param #string From
|
-- @param #string From
|
||||||
@@ -1363,6 +1373,12 @@ do
|
|||||||
-- @field Core.ClientMenu#CLIENTMENU MenuNoTask
|
-- @field Core.ClientMenu#CLIENTMENU MenuNoTask
|
||||||
-- @field #boolean InformationMenu Show Radio Info Menu
|
-- @field #boolean InformationMenu Show Radio Info Menu
|
||||||
-- @field #number TaskInfoDuration How long to show the briefing info on the screen
|
-- @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
|
-- @extends Core.Fsm#FSM
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -1583,6 +1599,7 @@ do
|
|||||||
-- ELEVATION = "\nTarget Elevation: %s %s",
|
-- ELEVATION = "\nTarget Elevation: %s %s",
|
||||||
-- METER = "meter",
|
-- METER = "meter",
|
||||||
-- FEET = "feet",
|
-- FEET = "feet",
|
||||||
|
-- INTERCEPTCOURSE = "Intercept course",
|
||||||
-- },
|
-- },
|
||||||
--
|
--
|
||||||
-- e.g.
|
-- 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.
|
-- 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.
|
-- 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.
|
-- 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,
|
MenuNoTask = nil,
|
||||||
InformationMenu = false,
|
InformationMenu = false,
|
||||||
TaskInfoDuration = 30,
|
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.CTLD = "Combat Transport"
|
||||||
AUFTRAG.Type.CSAR = "Combat Rescue"
|
AUFTRAG.Type.CSAR = "Combat Rescue"
|
||||||
AUFTRAG.Type.CONQUER = "Conquer"
|
AUFTRAG.Type.CONQUER = "Conquer"
|
||||||
|
|
||||||
---
|
---
|
||||||
-- @type Scores
|
-- @type Scores
|
||||||
PLAYERTASKCONTROLLER.Scores = {
|
PLAYERTASKCONTROLLER.Scores = {
|
||||||
@@ -1759,6 +1803,24 @@ PLAYERTASKCONTROLLER.Scores = {
|
|||||||
[AUFTRAG.Type.CAP] = 100,
|
[AUFTRAG.Type.CAP] = 100,
|
||||||
[AUFTRAG.Type.CAPTUREZONE] = 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
|
-- @type SeadAttributes
|
||||||
@@ -1860,6 +1922,7 @@ PLAYERTASKCONTROLLER.Messages = {
|
|||||||
DESTROYER = "Destroyer",
|
DESTROYER = "Destroyer",
|
||||||
CARRIER = "Aircraft Carrier",
|
CARRIER = "Aircraft Carrier",
|
||||||
RADIOS = "Radios",
|
RADIOS = "Radios",
|
||||||
|
INTERCEPTCOURSE = "Intercept course",
|
||||||
},
|
},
|
||||||
DE = {
|
DE = {
|
||||||
TASKABORT = "Auftrag abgebrochen!",
|
TASKABORT = "Auftrag abgebrochen!",
|
||||||
@@ -1947,12 +2010,13 @@ PLAYERTASKCONTROLLER.Messages = {
|
|||||||
DESTROYER = "Zerstörer",
|
DESTROYER = "Zerstörer",
|
||||||
CARRIER = "Flugzeugträger",
|
CARRIER = "Flugzeugträger",
|
||||||
RADIOS = "Frequenzen",
|
RADIOS = "Frequenzen",
|
||||||
|
INTERCEPTCOURSE = "Abfangkurs",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
--- PLAYERTASK class version.
|
--- PLAYERTASK class version.
|
||||||
-- @field #string version
|
-- @field #string version
|
||||||
PLAYERTASKCONTROLLER.version="0.1.71"
|
PLAYERTASKCONTROLLER.version="0.1.73"
|
||||||
|
|
||||||
--- Create and run a new TASKCONTROLLER instance.
|
--- Create and run a new TASKCONTROLLER instance.
|
||||||
-- @param #PLAYERTASKCONTROLLER self
|
-- @param #PLAYERTASKCONTROLLER self
|
||||||
@@ -2059,7 +2123,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
|
|||||||
self:AddTransition("*", "Stop", "Stopped")
|
self:AddTransition("*", "Stop", "Stopped")
|
||||||
|
|
||||||
self:__Start(2)
|
self:__Start(2)
|
||||||
local starttime = math.random(5,10)
|
local starttime = math.random(10,15)
|
||||||
self:__Status(starttime)
|
self:__Status(starttime)
|
||||||
|
|
||||||
self:I(self.lid..self.version.." Started.")
|
self:I(self.lid..self.version.." Started.")
|
||||||
@@ -2174,6 +2238,30 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
|
|||||||
|
|
||||||
end
|
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
|
--- [User] Set or create a SCORING object for this taskcontroller
|
||||||
-- @param #PLAYERTASKCONTROLLER self
|
-- @param #PLAYERTASKCONTROLLER self
|
||||||
-- @param Functional.Scoring#SCORING Scoring (optional) the Scoring object
|
-- @param Functional.Scoring#SCORING Scoring (optional) the Scoring object
|
||||||
@@ -2861,15 +2949,6 @@ function PLAYERTASKCONTROLLER:_GetTasksPerType()
|
|||||||
end
|
end
|
||||||
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
|
return tasktypes
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -3566,9 +3645,9 @@ function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask,Silent,TaskFilter)
|
|||||||
PlayerTask:_SetController(self)
|
PlayerTask:_SetController(self)
|
||||||
PlayerTask:SetCoalition(self.Coalition)
|
PlayerTask:SetCoalition(self.Coalition)
|
||||||
self.TaskQueue:Push(PlayerTask)
|
self.TaskQueue:Push(PlayerTask)
|
||||||
if not Silent then
|
--if not Silent then
|
||||||
self:__TaskAdded(10,PlayerTask)
|
self:__TaskAdded(10,PlayerTask,Silent)
|
||||||
end
|
--end
|
||||||
else
|
else
|
||||||
self:E(self.lid.."***** NO valid PAYERTASK object sent!")
|
self:E(self.lid.."***** NO valid PAYERTASK object sent!")
|
||||||
end
|
end
|
||||||
@@ -3700,6 +3779,41 @@ function PLAYERTASKCONTROLLER:_ShowRadioInfo(Group, Client)
|
|||||||
return self
|
return self
|
||||||
end
|
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
|
--- [Internal] Flashing directional info for a client
|
||||||
-- @param #PLAYERTASKCONTROLLER self
|
-- @param #PLAYERTASKCONTROLLER self
|
||||||
-- @return #PLAYERTASKCONTROLLER self
|
-- @return #PLAYERTASKCONTROLLER self
|
||||||
@@ -3709,16 +3823,34 @@ function PLAYERTASKCONTROLLER:_FlashInfo()
|
|||||||
if _client and _client:IsAlive() then
|
if _client and _client:IsAlive() then
|
||||||
if self.TasksPerPlayer:HasUniqueID(_playername) then
|
if self.TasksPerPlayer:HasUniqueID(_playername) then
|
||||||
local task = self.TasksPerPlayer:ReadByID(_playername) -- Ops.PlayerTask#PLAYERTASK
|
local task = self.TasksPerPlayer:ReadByID(_playername) -- Ops.PlayerTask#PLAYERTASK
|
||||||
local Coordinate = task.Target:GetCoordinate()
|
local Coordinate = task.Target:GetCoordinate() -- Core.Point#COORDINATE
|
||||||
local CoordText = ""
|
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)
|
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
|
else
|
||||||
CoordText = Coordinate:ToStringA2A(_client, nil, self.ShowMagnetic)
|
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
|
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
|
end
|
||||||
end
|
end
|
||||||
@@ -3770,7 +3902,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
|
|||||||
local CoordTextLLDM = nil
|
local CoordTextLLDM = nil
|
||||||
local ShowThreatInfo = task.ShowThreatDetails
|
local ShowThreatInfo = task.ShowThreatDetails
|
||||||
local LasingDrone = self:_FindLasingDroneForTaskID(task.PlayerTaskNr)
|
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)
|
CoordText = Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic)
|
||||||
else
|
else
|
||||||
CoordText = Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic)
|
CoordText = Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic)
|
||||||
@@ -3807,8 +3939,10 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
|
|||||||
Elevation = math.floor(UTILS.MetersToFeet(Elevation))
|
Elevation = math.floor(UTILS.MetersToFeet(Elevation))
|
||||||
end
|
end
|
||||||
-- ELEVATION = "\nTarget Elevation: %s %s",
|
-- ELEVATION = "\nTarget Elevation: %s %s",
|
||||||
local elev = self.gettext:GetEntry("ELEVATION",self.locale)
|
if task.Type ~= AUFTRAG.Type.INTERCEPT then
|
||||||
text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure)
|
local elev = self.gettext:GetEntry("ELEVATION",self.locale)
|
||||||
|
text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure)
|
||||||
|
end
|
||||||
-- Prec bombing
|
-- Prec bombing
|
||||||
if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
|
if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
|
||||||
if LasingDrone and LasingDrone.playertask then
|
if LasingDrone and LasingDrone.playertask then
|
||||||
@@ -4584,6 +4718,76 @@ function PLAYERTASKCONTROLLER:RemoveConflictZone(ConflictZone)
|
|||||||
return self
|
return self
|
||||||
end
|
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.
|
--- [User] Set the top menu name to a custom string.
|
||||||
-- @param #PLAYERTASKCONTROLLER self
|
-- @param #PLAYERTASKCONTROLLER self
|
||||||
-- @param #string Name The name to use as the top menu designation.
|
-- @param #string Name The name to use as the top menu designation.
|
||||||
@@ -4765,6 +4969,128 @@ function PLAYERTASKCONTROLLER:SetSRSBroadcast(Frequency,Modulation)
|
|||||||
return self
|
return self
|
||||||
end
|
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
|
-- FSM Functions PLAYERTASKCONTROLLER
|
||||||
-- TODO: 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.PilotDead, self._EventHandler)
|
||||||
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
|
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
|
||||||
self:HandleEvent(EVENTS.UnitLost, 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
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4828,6 +5159,11 @@ function PLAYERTASKCONTROLLER:onafterStatus(From, Event, To)
|
|||||||
self:I(text)
|
self:I(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Persistence
|
||||||
|
if self.TaskPersistanceSwitch == true then
|
||||||
|
self:_SaveTasksPersisted()
|
||||||
|
end
|
||||||
|
|
||||||
if self:GetState() ~= "Stopped" then
|
if self:GetState() ~= "Stopped" then
|
||||||
self:__Status(-30)
|
self:__Status(-30)
|
||||||
end
|
end
|
||||||
@@ -4983,19 +5319,26 @@ end
|
|||||||
-- @param #string Event
|
-- @param #string Event
|
||||||
-- @param #string To
|
-- @param #string To
|
||||||
-- @param Ops.PlayerTask#PLAYERTASK Task
|
-- @param Ops.PlayerTask#PLAYERTASK Task
|
||||||
|
-- @param #boolean Silent
|
||||||
-- @return #PLAYERTASKCONTROLLER self
|
-- @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({From, Event, To})
|
||||||
self:T(self.lid.."TaskAdded")
|
self:T(self.lid.."TaskAdded")
|
||||||
local addtxt = self.gettext:GetEntry("TASKADDED",self.locale)
|
local addtxt = self.gettext:GetEntry("TASKADDED",self.locale)
|
||||||
local taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.Type))
|
local taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.Type))
|
||||||
if not self.NoScreenOutput then
|
if not Silent then
|
||||||
self:_SendMessageToClients(taskname,15)
|
if not self.NoScreenOutput then
|
||||||
--local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition)
|
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
|
end
|
||||||
if self.UseSRS then
|
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)))
|
||||||
taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.TTSType))
|
if self.TaskPersistanceSwitch == true and self.TasksPersistable[Task.Type] == true and Task.PersistMe == true then
|
||||||
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
|
self:_AddPersistenceData(Task)
|
||||||
end
|
end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ _RECOVERYTANKERID=0
|
|||||||
|
|
||||||
--- Class version.
|
--- Class version.
|
||||||
-- @field #string version
|
-- @field #string version
|
||||||
RECOVERYTANKER.version="1.0.10"
|
RECOVERYTANKER.version="1.0.11"
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- TODO list
|
-- TODO list
|
||||||
@@ -923,6 +923,22 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
|
|||||||
Spawn:InitRadioCommsOnOff(true)
|
Spawn:InitRadioCommsOnOff(true)
|
||||||
Spawn:InitRadioFrequency(self.RadioFreq)
|
Spawn:InitRadioFrequency(self.RadioFreq)
|
||||||
Spawn:InitRadioModulation(self.RadioModu)
|
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:InitModex(self.modex)
|
||||||
|
|
||||||
-- Spawn on carrier.
|
-- Spawn on carrier.
|
||||||
@@ -1190,9 +1206,9 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To)
|
|||||||
-- Task combo.
|
-- Task combo.
|
||||||
|
|
||||||
-- Be a tanker or be an AWACS.
|
-- Be a tanker or be an AWACS.
|
||||||
local taskroll = self.tanker:EnRouteTaskTanker()
|
local taskrole = self.tanker:EnRouteTaskTanker()
|
||||||
if self.awacs then
|
if self.awacs then
|
||||||
taskroll=self.tanker:EnRouteTaskAWACS()
|
taskrole=self.tanker:EnRouteTaskAWACS()
|
||||||
end
|
end
|
||||||
|
|
||||||
--local taskeplrs=self.tanker:TaskEPLRS(true, 2)
|
--local taskeplrs=self.tanker:TaskEPLRS(true, 2)
|
||||||
@@ -1201,7 +1217,7 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To)
|
|||||||
local taskroute = self.tanker:TaskRoute(wp)
|
local taskroute = self.tanker:TaskRoute(wp)
|
||||||
|
|
||||||
-- Note that the order is important here! tasktanker has to come first. Otherwise it does not work.
|
-- 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.
|
-- Set task.
|
||||||
self.tanker:SetTask(taskcombo, 1)
|
self.tanker:SetTask(taskcombo, 1)
|
||||||
@@ -1450,6 +1466,20 @@ function RECOVERYTANKER:_RefuelingStop(EventData)
|
|||||||
|
|
||||||
end
|
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.
|
--- A unit crashed or died.
|
||||||
-- @param #RECOVERYTANKER self
|
-- @param #RECOVERYTANKER self
|
||||||
-- @param Core.Event#EVENTDATA EventData Event data.
|
-- @param Core.Event#EVENTDATA EventData Event data.
|
||||||
|
|||||||
@@ -884,7 +884,7 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
|||||||
else
|
else
|
||||||
|
|
||||||
-- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine.
|
-- 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.
|
-- Set modex for spawn.
|
||||||
Spawn:InitModex(self.modex)
|
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))
|
self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!", self.helogroupname))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
end
|
||||||
elseif UsesAliveGroup==false then
|
elseif UsesAliveGroup==false then
|
||||||
|
|
||||||
-- Spawn at airbase.
|
-- Spawn at airbase.
|
||||||
self.helo=Spawn:SpawnAtAirbase(self.airbase, self.takeoff, nil, AIRBASE.TerminalType.HelicopterUsable)
|
self.helo=Spawn:SpawnAtAirbase(self.airbase, self.takeoff, nil, AIRBASE.TerminalType.HelicopterUsable)
|
||||||
@@ -948,9 +948,8 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
|||||||
delay=60
|
delay=60
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- Set of group(s) to follow Mother.
|
-- Set of group(s) to follow Mother.
|
||||||
self.followset=SET_GROUP:New()
|
self.followset=SET_GROUP:New()
|
||||||
|
|||||||
@@ -515,7 +515,8 @@ ENUMS.ReportingName =
|
|||||||
Atlas = "A400",
|
Atlas = "A400",
|
||||||
Lancer = "B1-B",
|
Lancer = "B1-B",
|
||||||
Stratofortress = "B-52H",
|
Stratofortress = "B-52H",
|
||||||
Hercules = "C-130",
|
Herc = "C-130",
|
||||||
|
Hercules = "C-130J-30",
|
||||||
Super_Hercules = "Hercules",
|
Super_Hercules = "Hercules",
|
||||||
Globemaster = "C-17",
|
Globemaster = "C-17",
|
||||||
Greyhound = "C-2A",
|
Greyhound = "C-2A",
|
||||||
@@ -528,6 +529,7 @@ ENUMS.ReportingName =
|
|||||||
Orion = "P-3C",
|
Orion = "P-3C",
|
||||||
Viking = "S-3B",
|
Viking = "S-3B",
|
||||||
Osprey = "V-22",
|
Osprey = "V-22",
|
||||||
|
Intruder = "A6E",
|
||||||
-- Bomber Rus
|
-- Bomber Rus
|
||||||
Badger = "H6-J",
|
Badger = "H6-J",
|
||||||
Bear_J = "Tu-142", -- also Tu-95
|
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.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.bombs.Mk_82 = 'weapons.bombs.Mk_82'
|
||||||
ENUMS.Storage.weapons.adapters.BRU42_LS_SUU25 = 'weapons.adapters.BRU-42_LS_(SUU-25)'
|
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
|
-- UH-60L Mod
|
||||||
ENUMS.Storage.weapons.gunmounts.UH60LGAU19 = 'weapons.gunmounts.UH-60L GAU-19'
|
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.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_LEFT = 'weapons.gunmounts.{UH60_M230_LEFT}'
|
||||||
ENUMS.Storage.weapons.gunmounts.UH60_M230_RIGHT = 'weapons.gunmounts.{UH60_M230_RIGHT}'
|
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
|
-- @type ENUMS.FARPType
|
||||||
|
|||||||
@@ -249,7 +249,23 @@ CALLSIGN={
|
|||||||
Stetson = 22,
|
Stetson = 22,
|
||||||
Wrath = 23,
|
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
|
} --#CALLSIGN
|
||||||
|
|
||||||
--- Utilities static class.
|
--- Utilities static class.
|
||||||
@@ -805,33 +821,6 @@ UTILS.kg2lbs = function( kg )
|
|||||||
return kg * 2.20462
|
return kg * 2.20462
|
||||||
end
|
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:
|
--[[acc:
|
||||||
in DM: decimal point of minutes.
|
in DM: decimal point of minutes.
|
||||||
In DMS: decimal point of seconds.
|
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}
|
return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z}
|
||||||
end
|
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
|
--- 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)
|
function UTILS.VecSubtract(a, b)
|
||||||
return UTILS.VecSubstract(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}
|
return {x=a.x-b.x, y=a.y-b.y}
|
||||||
end
|
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
|
--- 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)
|
function UTILS.Vec2Subtract(a, b)
|
||||||
return UTILS.Vec2Substract(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}
|
return {x=a.x+b.x, y=a.y+b.y}
|
||||||
end
|
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.
|
--- Calculate the angle between two 3D vectors.
|
||||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||||
-- @param DCS#Vec3 b 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)
|
return math.deg(alpha)
|
||||||
end
|
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.
|
--- Calculate "heading" of a 3D vector in the X-Z plane.
|
||||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||||
-- @return #number Heading in degrees in [0,360).
|
-- @return #number Heading in degrees in [0,360).
|
||||||
@@ -1628,20 +1599,6 @@ function UTILS.Vec2Hdg(a)
|
|||||||
return h
|
return h
|
||||||
end
|
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.
|
--- Calculate the difference between two "heading", i.e. angles in [0,360) deg.
|
||||||
-- @param #number h1 Heading one.
|
-- @param #number h1 Heading one.
|
||||||
-- @param #number h2 Heading two.
|
-- @param #number h2 Heading two.
|
||||||
@@ -1708,6 +1665,13 @@ function UTILS.Vec2Translate(a, distance, angle)
|
|||||||
return {x=TX, y=TY}
|
return {x=TX, y=TY}
|
||||||
end
|
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.
|
--- 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 DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||||
-- @param #number angle Rotation angle in degrees.
|
-- @param #number angle Rotation angle in degrees.
|
||||||
@@ -2089,6 +2053,12 @@ function UTILS.GetCallsignName(Callsign)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
for name, value in pairs(CALLSIGN.Intruder) do
|
||||||
|
if value==Callsign then
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return "Ghostrider"
|
return "Ghostrider"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2375,6 +2345,21 @@ function UTILS.IsLoadingDoorOpen( unit_name )
|
|||||||
return true
|
return true
|
||||||
end
|
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
|
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")
|
BASE:T(unit_name .. " rear doors are open")
|
||||||
return true
|
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 #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 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 #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 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 )`
|
-- @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 = {}
|
local fires = {}
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @param Core.Point#COORDINATE coord
|
||||||
local function Smokers(name,coord,effect,density)
|
local function Smokers(name,coord,effect,density)
|
||||||
local eff = math.random(8)
|
local eff = math.random(8)
|
||||||
if type(effect) == "number" then eff = effect end
|
if type(effect) == "number" then eff = effect end
|
||||||
coord:BigSmokeAndFire(eff,density,name)
|
coord:BigSmokeAndFire( eff, Density, 300, 1, name )
|
||||||
table.insert(fires,name)
|
table.insert(fires,name)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2962,7 +2958,16 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema
|
|||||||
local name = _unit:GetName()
|
local name = _unit:GetName()
|
||||||
Smokers(name,coordinate,Effect,Density)
|
Smokers(name,coordinate,Effect,Density)
|
||||||
end
|
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
|
reduced = reduced + 1
|
||||||
if reduced == anzahl then break end
|
if reduced == anzahl then break end
|
||||||
end
|
end
|
||||||
@@ -4297,25 +4302,27 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
|
|||||||
|
|
||||||
local function PopulateStorage(Name,liquids,equip,airframes)
|
local function PopulateStorage(Name,liquids,equip,airframes)
|
||||||
local newWH = STORAGE:New(Name)
|
local newWH = STORAGE:New(Name)
|
||||||
if liquids and liquids > 0 then
|
if newWH then
|
||||||
-- Storage fill-up
|
if liquids and liquids > 0 then
|
||||||
newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons
|
-- Storage fill-up
|
||||||
newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids)
|
newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons
|
||||||
newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids)
|
newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids)
|
||||||
newWH:SetLiquid(STORAGE.Liquid.MW50,liquids)
|
newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids)
|
||||||
end
|
newWH:SetLiquid(STORAGE.Liquid.MW50,liquids)
|
||||||
|
end
|
||||||
if equip and equip > 0 then
|
|
||||||
for cat,nitem in pairs(ENUMS.Storage.weapons) do
|
if equip and equip > 0 then
|
||||||
for name,item in pairs(nitem) do
|
for cat,nitem in pairs(ENUMS.Storage.weapons) do
|
||||||
newWH:SetItem(item,equip)
|
for name,item in pairs(nitem) do
|
||||||
|
newWH:SetItem(item,equip)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
if airframes and airframes > 0 then
|
||||||
if airframes and airframes > 0 then
|
for typename in pairs (CSAR.AircraftType) do
|
||||||
for typename in pairs (CSAR.AircraftType) do
|
newWH:SetItem(typename,airframes)
|
||||||
newWH:SetItem(typename,airframes)
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -4394,8 +4401,6 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
|
|||||||
}
|
}
|
||||||
-- Create BIRTH event.
|
-- Create BIRTH event.
|
||||||
world.onEvent(Event)
|
world.onEvent(Event)
|
||||||
|
|
||||||
PopulateStorage(Name.."-1",liquids,equip,airframes)
|
|
||||||
else
|
else
|
||||||
-- Spawn FARP
|
-- Spawn FARP
|
||||||
local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "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)
|
newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart)
|
||||||
local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name)
|
local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name)
|
||||||
table.insert(ReturnObjects,spawnedfarp)
|
table.insert(ReturnObjects,spawnedfarp)
|
||||||
|
|
||||||
PopulateStorage(Name,liquids,equip,airframes)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
PopulateStorage(Name,liquids,equip,airframes)
|
||||||
|
|
||||||
-- Spawn Objects
|
-- Spawn Objects
|
||||||
local FARPStaticObjectsNato = {
|
local FARPStaticObjectsNato = {
|
||||||
["FUEL"] = { TypeName = "FARP Fuel Depot", ShapeName = "GSM Rus", Category = "Fortifications"},
|
["FUEL"] = { TypeName = "FARP Fuel Depot", ShapeName = "GSM Rus", Category = "Fortifications"},
|
||||||
@@ -4612,6 +4618,18 @@ function UTILS.Vec2toVec3(vec,y)
|
|||||||
end
|
end
|
||||||
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
|
--- Get the correction needed for true north in radians
|
||||||
-- @param gPoint The map point vec2 or vec3
|
-- @param gPoint The map point vec2 or vec3
|
||||||
-- @return number correction
|
-- @return number correction
|
||||||
@@ -5349,3 +5367,241 @@ function UTILS.CreateAirbaseEnum()
|
|||||||
_savefile(string.format("%s-enums.txt", env.mission.theatre), text)
|
_savefile(string.format("%s-enums.txt", env.mission.theatre), text)
|
||||||
--env.info(text)
|
--env.info(text)
|
||||||
end
|
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 parkingByID Parking spot data table with ID as key.
|
||||||
-- @field #table parkingWhitelist List of parking spot terminal IDs considered for spawning.
|
-- @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 #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 #table runways Runways of airdromes.
|
||||||
-- @field #AIRBASE.Runway runwayLanding Runway used for landing.
|
-- @field #AIRBASE.Runway runwayLanding Runway used for landing.
|
||||||
-- @field #AIRBASE.Runway runwayTakeoff Runway used for takeoff.
|
-- @field #AIRBASE.Runway runwayTakeoff Runway used for takeoff.
|
||||||
-- @field Wrapper.Storage#STORAGE storage The DCS warehouse storage.
|
-- @field Wrapper.Storage#STORAGE storage The DCS warehouse storage.
|
||||||
-- @field #table taxiways Taxiways stored as PATHLINEs.
|
|
||||||
-- @extends Wrapper.Positionable#POSITIONABLE
|
-- @extends Wrapper.Positionable#POSITIONABLE
|
||||||
|
|
||||||
--- Wrapper class to handle the DCS Airbase objects:
|
--- Wrapper class to handle the DCS Airbase objects:
|
||||||
@@ -79,7 +79,6 @@ AIRBASE = {
|
|||||||
[Airbase.Category.SHIP] = "Ship",
|
[Airbase.Category.SHIP] = "Ship",
|
||||||
},
|
},
|
||||||
activerwyno = nil,
|
activerwyno = nil,
|
||||||
taxiways = {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Airbases of the Caucasus map
|
--- Airbases of the Caucasus map
|
||||||
@@ -719,62 +718,62 @@ AIRBASE.SouthAtlantic = {
|
|||||||
|
|
||||||
--- Airbases of the Sinai map
|
--- Airbases of the Sinai map
|
||||||
--
|
--
|
||||||
-- * `AIRBASE.SinaiMap.Abu_Rudeis` Abu Rudeis
|
-- * `AIRBASE.Sinai.Abu_Rudeis` Abu Rudeis
|
||||||
-- * `AIRBASE.SinaiMap.Abu_Suwayr` Abu Suwayr
|
-- * `AIRBASE.Sinai.Abu_Suwayr` Abu Suwayr
|
||||||
-- * `AIRBASE.SinaiMap.Al_Bahr_al_Ahmar` Al Bahr al Ahmar
|
-- * `AIRBASE.Sinai.Al_Bahr_al_Ahmar` Al Bahr al Ahmar
|
||||||
-- * `AIRBASE.SinaiMap.Al_Ismailiyah` Al Ismailiyah
|
-- * `AIRBASE.Sinai.Al_Ismailiyah` Al Ismailiyah
|
||||||
-- * `AIRBASE.SinaiMap.Al_Khatatbah` Al Khatatbah
|
-- * `AIRBASE.Sinai.Al_Khatatbah` Al Khatatbah
|
||||||
-- * `AIRBASE.SinaiMap.Al_Mansurah` Al Mansurah
|
-- * `AIRBASE.Sinai.Al_Mansurah` Al Mansurah
|
||||||
-- * `AIRBASE.SinaiMap.Al_Rahmaniyah_Air_Base` Al Rahmaniyah Air Base
|
-- * `AIRBASE.Sinai.Al_Rahmaniyah_Air_Base` Al Rahmaniyah Air Base
|
||||||
-- * `AIRBASE.SinaiMap.As_Salihiyah` As Salihiyah
|
-- * `AIRBASE.Sinai.As_Salihiyah` As Salihiyah
|
||||||
-- * `AIRBASE.SinaiMap.AzZaqaziq` AzZaqaziq
|
-- * `AIRBASE.Sinai.AzZaqaziq` AzZaqaziq
|
||||||
-- * `AIRBASE.SinaiMap.Baluza` Baluza
|
-- * `AIRBASE.Sinai.Baluza` Baluza
|
||||||
-- * `AIRBASE.SinaiMap.Ben_Gurion` Ben-Gurion
|
-- * `AIRBASE.Sinai.Ben_Gurion` Ben-Gurion
|
||||||
-- * `AIRBASE.SinaiMap.Beni_Suef` Beni Suef
|
-- * `AIRBASE.Sinai.Beni_Suef` Beni Suef
|
||||||
-- * `AIRBASE.SinaiMap.Bilbeis_Air_Base` Bilbeis Air Base
|
-- * `AIRBASE.Sinai.Bilbeis_Air_Base` Bilbeis Air Base
|
||||||
-- * `AIRBASE.SinaiMap.Bir_Hasanah` Bir Hasanah
|
-- * `AIRBASE.Sinai.Bir_Hasanah` Bir Hasanah
|
||||||
-- * `AIRBASE.SinaiMap.Birma_Air_Base` Birma Air Base
|
-- * `AIRBASE.Sinai.Birma_Air_Base` Birma Air Base
|
||||||
-- * `AIRBASE.SinaiMap.Borg_El_Arab_International_Airport` Borg El Arab International Airport
|
-- * `AIRBASE.Sinai.Borg_El_Arab_International_Airport` Borg El Arab International Airport
|
||||||
-- * `AIRBASE.SinaiMap.Cairo_International_Airport` Cairo International Airport
|
-- * `AIRBASE.Sinai.Cairo_International_Airport` Cairo International Airport
|
||||||
-- * `AIRBASE.SinaiMap.Cairo_West` Cairo West
|
-- * `AIRBASE.Sinai.Cairo_West` Cairo West
|
||||||
-- * `AIRBASE.SinaiMap.Damascus_Intl` Damascus Intl
|
-- * `AIRBASE.Sinai.Damascus_Intl` Damascus Intl
|
||||||
-- * `AIRBASE.SinaiMap.Difarsuwar_Airfield` Difarsuwar Airfield
|
-- * `AIRBASE.Sinai.Difarsuwar_Airfield` Difarsuwar Airfield
|
||||||
-- * `AIRBASE.SinaiMap.Ein_Shamer` Ein Shamer
|
-- * `AIRBASE.Sinai.Ein_Shamer` Ein Shamer
|
||||||
-- * `AIRBASE.SinaiMap.El_Arish` El Arish
|
-- * `AIRBASE.Sinai.El_Arish` El Arish
|
||||||
-- * `AIRBASE.SinaiMap.El_Gora` El Gora
|
-- * `AIRBASE.Sinai.El_Gora` El Gora
|
||||||
-- * `AIRBASE.SinaiMap.El_Minya` El Minya
|
-- * `AIRBASE.Sinai.El_Minya` El Minya
|
||||||
-- * `AIRBASE.SinaiMap.Fayed` Fayed
|
-- * `AIRBASE.Sinai.Fayed` Fayed
|
||||||
-- * `AIRBASE.SinaiMap.Gebel_El_Basur_Air_Base` Gebel El Basur Air Base
|
-- * `AIRBASE.Sinai.Gebel_El_Basur_Air_Base` Gebel El Basur Air Base
|
||||||
-- * `AIRBASE.SinaiMap.Hatzerim` Hatzerim
|
-- * `AIRBASE.Sinai.Hatzerim` Hatzerim
|
||||||
-- * `AIRBASE.SinaiMap.Hatzor` Hatzor
|
-- * `AIRBASE.Sinai.Hatzor` Hatzor
|
||||||
-- * `AIRBASE.SinaiMap.Hurghada_International_Airport` Hurghada International Airport
|
-- * `AIRBASE.Sinai.Hurghada_International_Airport` Hurghada International Airport
|
||||||
-- * `AIRBASE.SinaiMap.Inshas_Airbase` Inshas Airbase
|
-- * `AIRBASE.Sinai.Inshas_Airbase` Inshas Airbase
|
||||||
-- * `AIRBASE.SinaiMap.Jiyanklis_Air_Base` Jiyanklis Air Base
|
-- * `AIRBASE.Sinai.Jiyanklis_Air_Base` Jiyanklis Air Base
|
||||||
-- * `AIRBASE.SinaiMap.Kedem` Kedem
|
-- * `AIRBASE.Sinai.Kedem` Kedem
|
||||||
-- * `AIRBASE.SinaiMap.Khalkhalah_Air_Base` Khalkhalah Air Base
|
-- * `AIRBASE.Sinai.Khalkhalah_Air_Base` Khalkhalah Air Base
|
||||||
-- * `AIRBASE.SinaiMap.Kibrit_Air_Base` Kibrit Air Base
|
-- * `AIRBASE.Sinai.Kibrit_Air_Base` Kibrit Air Base
|
||||||
-- * `AIRBASE.SinaiMap.King_Feisal_Air_Base` King Feisal Air Base
|
-- * `AIRBASE.Sinai.King_Feisal_Air_Base` King Feisal Air Base
|
||||||
-- * `AIRBASE.SinaiMap.Kom_Awshim` Kom Awshim
|
-- * `AIRBASE.Sinai.Kom_Awshim` Kom Awshim
|
||||||
-- * `AIRBASE.SinaiMap.Megiddo` Megiddo
|
-- * `AIRBASE.Sinai.Megiddo` Megiddo
|
||||||
-- * `AIRBASE.SinaiMap.Melez` Melez
|
-- * `AIRBASE.Sinai.Melez` Melez
|
||||||
-- * `AIRBASE.SinaiMap.Mezzeh_Air_Base` Mezzeh Air Base
|
-- * `AIRBASE.Sinai.Mezzeh_Air_Base` Mezzeh Air Base
|
||||||
-- * `AIRBASE.SinaiMap.Nevatim` Nevatim
|
-- * `AIRBASE.Sinai.Nevatim` Nevatim
|
||||||
-- * `AIRBASE.SinaiMap.Ovda` Ovda
|
-- * `AIRBASE.Sinai.Ovda` Ovda
|
||||||
-- * `AIRBASE.SinaiMap.Palmachim` Palmachim
|
-- * `AIRBASE.Sinai.Palmachim` Palmachim
|
||||||
-- * `AIRBASE.SinaiMap.Quwaysina` Quwaysina
|
-- * `AIRBASE.Sinai.Quwaysina` Quwaysina
|
||||||
-- * `AIRBASE.SinaiMap.Rafic_Hariri_Intl` Rafic Hariri Intl
|
-- * `AIRBASE.Sinai.Rafic_Hariri_Intl` Rafic Hariri Intl
|
||||||
-- * `AIRBASE.SinaiMap.Ramat_David` Ramat David
|
-- * `AIRBASE.Sinai.Ramat_David` Ramat David
|
||||||
-- * `AIRBASE.SinaiMap.Ramon_Airbase` Ramon Airbase
|
-- * `AIRBASE.Sinai.Ramon_Airbase` Ramon Airbase
|
||||||
-- * `AIRBASE.SinaiMap.Ramon_International_Airport` Ramon International Airport
|
-- * `AIRBASE.Sinai.Ramon_International_Airport` Ramon International Airport
|
||||||
-- * `AIRBASE.SinaiMap.Sde_Dov` Sde Dov
|
-- * `AIRBASE.Sinai.Sde_Dov` Sde Dov
|
||||||
-- * `AIRBASE.SinaiMap.Sharm_El_Sheikh_International_Airport` Sharm El Sheikh International Airport
|
-- * `AIRBASE.Sinai.Sharm_El_Sheikh_International_Airport` Sharm El Sheikh International Airport
|
||||||
-- * `AIRBASE.SinaiMap.St_Catherine` St Catherine
|
-- * `AIRBASE.Sinai.St_Catherine` St Catherine
|
||||||
-- * `AIRBASE.SinaiMap.Taba_International_Airport` Taba International Airport
|
-- * `AIRBASE.Sinai.Taba_International_Airport` Taba International Airport
|
||||||
-- * `AIRBASE.SinaiMap.Tabuk` Tabuk
|
-- * `AIRBASE.Sinai.Tabuk` Tabuk
|
||||||
-- * `AIRBASE.SinaiMap.TabukHeliBase` TabukHeliBase
|
-- * `AIRBASE.Sinai.TabukHeliBase` TabukHeliBase
|
||||||
-- * `AIRBASE.SinaiMap.Tel_Nof` Tel Nof
|
-- * `AIRBASE.Sinai.Tel_Nof` Tel Nof
|
||||||
-- * `AIRBASE.SinaiMap.Wadi_Abu_Rish` Wadi Abu Rish
|
-- * `AIRBASE.Sinai.Wadi_Abu_Rish` Wadi Abu Rish
|
||||||
-- * `AIRBASE.SinaiMap.Wadi_al_Jandali` Wadi al Jandali
|
-- * `AIRBASE.Sinai.Wadi_al_Jandali` Wadi al Jandali
|
||||||
--
|
--
|
||||||
-- @field Sinai
|
-- @field Sinai
|
||||||
AIRBASE.Sinai = {
|
AIRBASE.Sinai = {
|
||||||
@@ -835,6 +834,66 @@ AIRBASE.Sinai = {
|
|||||||
["Wadi_Abu_Rish"] = "Wadi Abu Rish",
|
["Wadi_Abu_Rish"] = "Wadi Abu Rish",
|
||||||
["Wadi_al_Jandali"] = "Wadi al Jandali",
|
["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
|
--- Airbases of the Kola map
|
||||||
--
|
--
|
||||||
@@ -1551,36 +1610,10 @@ AIRBASE.SpotStatus = {
|
|||||||
RESERVED="Reserved",
|
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.
|
--- Runway data.
|
||||||
-- @type AIRBASE.Runway
|
-- @type AIRBASE.Runway
|
||||||
-- @field #string name Runway name.
|
-- @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 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 magheading Magnetic heading of the runway in degrees. This is what is marked on the runway.
|
||||||
-- @field #number length Length of runway in meters.
|
-- @field #number length Length of runway in meters.
|
||||||
@@ -1690,6 +1723,10 @@ end
|
|||||||
else
|
else
|
||||||
self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName))
|
self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if Nrunways>0 then
|
||||||
|
self:GetMinimumBoundingCircleFromParkingSpots()
|
||||||
|
end
|
||||||
|
|
||||||
-- Debug info.
|
-- Debug info.
|
||||||
self:T2(string.format("Registered airbase %s", tostring(self.AirbaseName)))
|
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.
|
--- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase.
|
||||||
-- @param #AIRBASE self
|
-- @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
|
-- @return #AIRBASE self
|
||||||
function AIRBASE:FindByName( AirbaseName )
|
function AIRBASE:FindByName( AirbaseName )
|
||||||
|
|
||||||
local AirbaseFound = _DATABASE:FindAirbase( 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
|
return AirbaseFound
|
||||||
end
|
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.
|
--- Find a AIRBASE in the _DATABASE by its ID.
|
||||||
-- @param #AIRBASE self
|
-- @param #AIRBASE self
|
||||||
-- @param #number id Airbase ID.
|
-- @param #number id Airbase ID.
|
||||||
@@ -2118,129 +2122,6 @@ function AIRBASE:IsShip()
|
|||||||
return self.isShip
|
return self.isShip
|
||||||
end
|
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
|
-- Parking
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -2288,7 +2169,7 @@ end
|
|||||||
function AIRBASE:GetParkingSpotsNumber(termtype)
|
function AIRBASE:GetParkingSpotsNumber(termtype)
|
||||||
|
|
||||||
-- Get free parking spots data.
|
-- Get free parking spots data.
|
||||||
local parkingdata=self:GetParkingData(false)
|
local parkingdata=self:GetParkingData(false) or {}
|
||||||
|
|
||||||
local nspots=0
|
local nspots=0
|
||||||
for _,parkingspot in pairs(parkingdata) do
|
for _,parkingspot in pairs(parkingdata) do
|
||||||
@@ -2308,7 +2189,7 @@ end
|
|||||||
function AIRBASE:GetFreeParkingSpotsNumber(termtype, allowTOAC)
|
function AIRBASE:GetFreeParkingSpotsNumber(termtype, allowTOAC)
|
||||||
|
|
||||||
-- Get free parking spots data.
|
-- Get free parking spots data.
|
||||||
local parkingdata=self:GetParkingData(true)
|
local parkingdata=self:GetParkingData(true) or {}
|
||||||
|
|
||||||
local nfree=0
|
local nfree=0
|
||||||
for _,parkingspot in pairs(parkingdata) do
|
for _,parkingspot in pairs(parkingdata) do
|
||||||
@@ -2331,7 +2212,7 @@ end
|
|||||||
function AIRBASE:GetFreeParkingSpotsCoordinates(termtype, allowTOAC)
|
function AIRBASE:GetFreeParkingSpotsCoordinates(termtype, allowTOAC)
|
||||||
|
|
||||||
-- Get free parking spots data.
|
-- Get free parking spots data.
|
||||||
local parkingdata=self:GetParkingData(true)
|
local parkingdata=self:GetParkingData(true) or {}
|
||||||
|
|
||||||
-- Put coordinates of free spots into table.
|
-- Put coordinates of free spots into table.
|
||||||
local spots={}
|
local spots={}
|
||||||
@@ -2375,13 +2256,62 @@ function AIRBASE:GetParkingSpotsCoordinates(termtype)
|
|||||||
return spots
|
return spots
|
||||||
end
|
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.
|
--- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase.
|
||||||
-- @param #AIRBASE self
|
-- @param #AIRBASE self
|
||||||
-- @return#AIRBASE self
|
-- @return#AIRBASE self
|
||||||
function AIRBASE:_InitParkingSpots()
|
function AIRBASE:_InitParkingSpots()
|
||||||
|
|
||||||
-- Get parking data of all spots (free or occupied)
|
-- Get parking data of all spots (free or occupied)
|
||||||
local parkingdata=self:GetParkingData(false)
|
local parkingdata=self:GetParkingData(false) or {}
|
||||||
|
|
||||||
-- Init table.
|
-- Init table.
|
||||||
self.parking={}
|
self.parking={}
|
||||||
@@ -2457,10 +2387,10 @@ end
|
|||||||
function AIRBASE:GetParkingSpotsTable(termtype)
|
function AIRBASE:GetParkingSpotsTable(termtype)
|
||||||
|
|
||||||
-- Get parking data of all spots (free or occupied)
|
-- 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.
|
-- 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.
|
-- Function to ckeck if any parking spot is free.
|
||||||
local function _isfree(_tocheck)
|
local function _isfree(_tocheck)
|
||||||
@@ -2509,7 +2439,7 @@ end
|
|||||||
function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
|
function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
|
||||||
|
|
||||||
-- Get parking data of all free spots.
|
-- 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.
|
-- Put coordinates of free spots into table.
|
||||||
local freespots={}
|
local freespots={}
|
||||||
@@ -2539,7 +2469,7 @@ end
|
|||||||
function AIRBASE:GetParkingSpotData(TerminalID)
|
function AIRBASE:GetParkingSpotData(TerminalID)
|
||||||
|
|
||||||
-- Get parking data.
|
-- Get parking data.
|
||||||
local parkingdata=self:GetParkingSpotsTable()
|
local parkingdata=self:GetParkingSpotsTable() or {}
|
||||||
|
|
||||||
for _,_spot in pairs(parkingdata) do
|
for _,_spot in pairs(parkingdata) do
|
||||||
local spot=_spot --#AIRBASE.ParkingSpot
|
local spot=_spot --#AIRBASE.ParkingSpot
|
||||||
@@ -2565,7 +2495,7 @@ function AIRBASE:MarkParkingSpots(termtype, mark)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Get parking data from getParking() wrapper function.
|
-- Get parking data from getParking() wrapper function.
|
||||||
local parkingdata=self:GetParkingSpotsTable(termtype)
|
local parkingdata=self:GetParkingSpotsTable(termtype) or {}
|
||||||
|
|
||||||
-- Get airbase name.
|
-- Get airbase name.
|
||||||
local airbasename=self:GetName()
|
local airbasename=self:GetName()
|
||||||
@@ -2680,7 +2610,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
|
|||||||
local markobstacles=false
|
local markobstacles=false
|
||||||
|
|
||||||
-- Loop over all known parking spots
|
-- Loop over all known parking spots
|
||||||
for _,parkingspot in pairs(parkingdata) do
|
for _,parkingspot in pairs(parkingdata or {}) do
|
||||||
|
|
||||||
-- Coordinate of the parking spot.
|
-- Coordinate of the parking spot.
|
||||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||||
|
|||||||
@@ -2439,7 +2439,7 @@ end
|
|||||||
-- @return #table The mission route defined by points.
|
-- @return #table The mission route defined by points.
|
||||||
function GROUP:GetTaskRoute()
|
function GROUP:GetTaskRoute()
|
||||||
--self:F2( self.GroupName )
|
--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 )
|
return UTILS.DeepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points )
|
||||||
else
|
else
|
||||||
return {}
|
return {}
|
||||||
@@ -2467,7 +2467,7 @@ function GROUP:CopyRoute( Begin, End, Randomize, Radius )
|
|||||||
|
|
||||||
--self:T3( { GroupName } )
|
--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 Template then
|
||||||
if not Begin then
|
if not Begin then
|
||||||
@@ -2491,7 +2491,7 @@ function GROUP:CopyRoute( Begin, End, Randomize, Radius )
|
|||||||
end
|
end
|
||||||
return Points
|
return Points
|
||||||
else
|
else
|
||||||
error( "Template not found for Group : " .. GroupName )
|
BASE:E( "Template not found for Group : " .. GroupName )
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -2944,6 +2944,16 @@ function GROUP:EnableEmission(switch)
|
|||||||
return self
|
return self
|
||||||
end
|
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.
|
--- Switch on/off invisible flag for the group.
|
||||||
-- @param #GROUP self
|
-- @param #GROUP self
|
||||||
-- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled.
|
-- @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
|
if not Name then return nil end
|
||||||
local playerList = net.get_player_list()
|
local playerList = net.get_player_list()
|
||||||
for i=1,#playerList do
|
for i=1,#playerList do
|
||||||
local playerName = net.get_name(i)
|
local playerName = net.get_name(playerList[i])
|
||||||
if playerName == Name then
|
if playerName == Name then
|
||||||
return playerList[i]
|
return playerList[i]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -729,16 +729,23 @@ end
|
|||||||
|
|
||||||
--- Returns the altitude above sea level of the POSITIONABLE.
|
--- Returns the altitude above sea level of the POSITIONABLE.
|
||||||
-- @param #POSITIONABLE self
|
-- @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 DCS#Distance The altitude of the POSITIONABLE.
|
||||||
-- @return #nil The POSITIONABLE is not existing or alive.
|
-- @return #nil The POSITIONABLE is not existing or alive.
|
||||||
function POSITIONABLE:GetAltitude()
|
function POSITIONABLE:GetAltitude(FromGround)
|
||||||
self:F2()
|
self:F2()
|
||||||
|
|
||||||
local DCSPositionable = self:GetDCSObject()
|
local DCSPositionable = self:GetDCSObject()
|
||||||
|
|
||||||
if DCSPositionable then
|
if DCSPositionable then
|
||||||
local PositionablePointVec3 = DCSPositionable:getPoint() -- DCS#Vec3
|
local altitude = 0
|
||||||
return PositionablePointVec3.y
|
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
|
end
|
||||||
|
|
||||||
BASE:E( { "Cannot GetAltitude", Positionable = self, Alive = self:IsAlive() } )
|
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
|
self.Vector = (self.Vec3 and VECTOR) and VECTOR:NewFromVec(self.Vec3) or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if SceneryObject then
|
if SceneryObject and SceneryObject.getPoint then
|
||||||
local vec3 = SceneryObject:getPoint()
|
local vec3 = SceneryObject:getPoint()
|
||||||
self.Vec3 = { x = vec3.x, y = vec3.y, z = vec3.z }
|
self.Vec3 = { x = vec3.x, y = vec3.y, z = vec3.z }
|
||||||
self.Vec2 = { x = vec3.x, y = vec3.z }
|
self.Vec2 = { x = vec3.x, y = vec3.z }
|
||||||
|
|||||||
@@ -1352,9 +1352,7 @@ function UNIT:GetThreatLevel()
|
|||||||
end
|
end
|
||||||
|
|
||||||
ThreatText = ThreatLevels[ThreatLevel + 1]
|
ThreatText = ThreatLevels[ThreatLevel + 1]
|
||||||
end
|
elseif self:IsAir() then
|
||||||
|
|
||||||
if self:IsAir() then
|
|
||||||
|
|
||||||
local ThreatLevels = {
|
local ThreatLevels = {
|
||||||
[1] = "Unarmed",
|
[1] = "Unarmed",
|
||||||
@@ -1397,9 +1395,7 @@ function UNIT:GetThreatLevel()
|
|||||||
end
|
end
|
||||||
|
|
||||||
ThreatText = ThreatLevels[ThreatLevel + 1]
|
ThreatText = ThreatLevels[ThreatLevel + 1]
|
||||||
end
|
elseif self:IsShip() then
|
||||||
|
|
||||||
if self:IsShip() then
|
|
||||||
|
|
||||||
--["Aircraft Carriers"] = {"Heavy armed ships",},
|
--["Aircraft Carriers"] = {"Heavy armed ships",},
|
||||||
--["Cruisers"] = {"Heavy armed ships",},
|
--["Cruisers"] = {"Heavy armed ships",},
|
||||||
@@ -1953,3 +1949,26 @@ end
|
|||||||
function UNIT:SetValidateAndRepositionGroundUnits(Enabled)
|
function UNIT:SetValidateAndRepositionGroundUnits(Enabled)
|
||||||
self.ValidateAndRepositionGroundUnits = Enabled
|
self.ValidateAndRepositionGroundUnits = Enabled
|
||||||
end
|
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)
|
-- myweapon:SetFuncImpact(OnImpact)
|
||||||
--
|
--
|
||||||
-- -- Start tracking.
|
-- -- Start tracking.
|
||||||
-- myweapon:Track()
|
-- myweapon:StartTrack()
|
||||||
--
|
--
|
||||||
function WEAPON:SetFuncImpact(FuncImpact, ...)
|
function WEAPON:SetFuncImpact(FuncImpact, ...)
|
||||||
self.impactFunc=FuncImpact
|
self.impactFunc=FuncImpact
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ Ops/FlightControl.lua
|
|||||||
Ops/PlayerTask.lua
|
Ops/PlayerTask.lua
|
||||||
Ops/PlayerRecce.lua
|
Ops/PlayerRecce.lua
|
||||||
Ops/EasyGCICAP.lua
|
Ops/EasyGCICAP.lua
|
||||||
|
Ops/EasyA2G.lua
|
||||||
Ops/OpsZone.lua
|
Ops/OpsZone.lua
|
||||||
Ops/ArmyGroup.lua
|
Ops/ArmyGroup.lua
|
||||||
Ops/OpsTransport.lua
|
Ops/OpsTransport.lua
|
||||||
|
|||||||
Reference in New Issue
Block a user