Merge branch 'develop' into FF/Ops

This commit is contained in:
Frank 2023-12-03 21:09:17 +01:00
commit cfca4fdc46
16 changed files with 875 additions and 172 deletions

View File

@ -1348,7 +1348,8 @@ function EVENT:onEvent( Event )
Event.Weapon = Event.weapon
Event.WeaponName = Event.Weapon:getTypeName()
Event.WeaponUNIT = CLIENT:Find( Event.Weapon, '', true ) -- Sometimes, the weapon is a player unit!
Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName()
Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName()
--Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName()
Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition()
Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category
Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName()

View File

@ -8,22 +8,6 @@
--
-- ===
--
-- # Demo Missions
--
-- ### [POINT_VEC Demo Missions source code]()
--
-- ### [POINT_VEC Demo Missions, only for beta testers]()
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ===
--
-- # YouTube Channel
--
-- ### [POINT_VEC YouTube Channel]()
--
-- ===
--
-- ### Authors:
--
-- * FlightControl (Design & Programming)
@ -920,7 +904,7 @@ do -- COORDINATE
end
--- Return an angle in radians from the COORDINATE using a direction vector in Vec3 format.
--- Return an angle in radians from the COORDINATE using a **direction vector in Vec3 format**.
-- @param #COORDINATE self
-- @param DCS#Vec3 DirectionVec3 The direction vector in Vec3 format.
-- @return #number DirectionRadians The angle in radians.
@ -933,10 +917,12 @@ do -- COORDINATE
return DirectionRadians
end
--- Return an angle in degrees from the COORDINATE using a direction vector in Vec3 format.
--- Return an angle in degrees from the COORDINATE using a **direction vector in Vec3 format**.
-- @param #COORDINATE self
-- @param DCS#Vec3 DirectionVec3 The direction vector in Vec3 format.
-- @return #number DirectionRadians The angle in degrees.
-- @usage
-- local directionAngle = currentCoordinate:GetAngleDegrees(currentCoordinate:GetDirectionVec3(sourceCoordinate:GetVec3()))
function COORDINATE:GetAngleDegrees( DirectionVec3 )
local AngleRadians = self:GetAngleRadians( DirectionVec3 )
local Angle = UTILS.ToDegree( AngleRadians )
@ -3021,6 +3007,16 @@ do -- COORDINATE
return BRAANATO
end
--- Return the BULLSEYE as COORDINATE Object
-- @param #number Coalition Coalition of the bulls eye to return, e.g. coalition.side.BLUE
-- @return #COORDINATE self
-- @usage
-- -- note the dot (.) here,not using the colon (:)
-- local redbulls = COORDINATE.GetBullseyeCoordinate(coalition.side.RED)
function COORDINATE.GetBullseyeCoordinate(Coalition)
return COORDINATE:NewFromVec3( coalition.getMainRefPoint( Coalition ) )
end
--- Return a BULLS string out of the BULLS of the coalition to the COORDINATE.
-- @param #COORDINATE self
-- @param DCS#coalition.side Coalition The coalition.

View File

@ -1065,6 +1065,13 @@ do
self:FilterActive( false )
return self
--- Filter the set once
-- @function [parent=#SET_GROUP] FilterOnce
-- @param #SET_GROUP self
-- @return #SET_GROUP self
end
--- Get a *new* set that only contains alive groups.
@ -1976,6 +1983,7 @@ do
--- Get the closest group of the set with respect to a given reference coordinate. Optionally, only groups of given coalitions are considered in the search.
-- @param #SET_GROUP self
-- @param Core.Point#COORDINATE Coordinate Reference Coordinate from which the closest group is determined.
-- @param #table Coalitions (Optional) Table of coalition #number entries to filter for.
-- @return Wrapper.Group#GROUP The closest group (if any).
-- @return #number Distance in meters to the closest group.
function SET_GROUP:GetClosestGroup(Coordinate, Coalitions)

View File

@ -3324,7 +3324,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
-- UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].callsign,1)
end
else
-- Ruskis
-- Russkis
for UnitID = 1, #SpawnTemplate.units do
SpawnTemplate.units[UnitID].callsign = math.random(1,999)
end
@ -3402,8 +3402,25 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
-- UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].datalinks,1)
end
end
-- Link16 team members
for UnitID = 1, #SpawnTemplate.units do
if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.network then
local team = {}
local isF16 = string.find(SpawnTemplate.units[UnitID].type,"F-16",1,true) and true or false
for ID = 1, #SpawnTemplate.units do
local member = {}
member.missionUnitId = ID
if isF16 then
member.TDOA = true
end
table.insert(team,member)
end
SpawnTemplate.units[UnitID].datalinks.Link16.network.teamMembers = team
end
end
self:T3( { "Template:", SpawnTemplate } )
--UTILS.PrintTableToLog(SpawnTemplate,1)
return SpawnTemplate
end

View File

@ -42,11 +42,12 @@
-- * @{#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Wrapper.Unit#UNIT} with a radius.
-- * @{#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius.
-- * @{#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon.
-- * @{#ZONE_OVAL}: The ZONE_OVAL class isdefined by a center point, major axis, minor axis, and angle.
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions: **Applevangelist**, **FunkyFranky**
-- ### Contributions: **Applevangelist**, **FunkyFranky**, **coconutcockpit**
--
-- ===
--
@ -480,7 +481,13 @@ function ZONE_BASE:UndrawZone(Delay)
self:ScheduleOnce(Delay, ZONE_BASE.UndrawZone, self)
else
if self.DrawID then
UTILS.RemoveMark(self.DrawID)
if type(self.DrawID) ~= "table" then
UTILS.RemoveMark(self.DrawID)
else -- DrawID is a table with a collections of mark ids, as used in ZONE_POLYGON
for _, mark_id in pairs(self.DrawID) do
UTILS.RemoveMark(mark_id)
end
end
end
end
return self
@ -1994,6 +2001,323 @@ function ZONE_GROUP:GetRandomPointVec2( inner, outer )
end
--- ZONE_OVAL created from a center point, major axis, minor axis, and angle.
-- Ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Oval.lua
-- @type ZONE_OVAL
-- @extends Core.Zone#ZONE_BASE
--- ## ZONE_OVAL class, extends @{#ZONE_BASE}
--
-- The ZONE_OVAL class is defined by a center point, major axis, minor axis, and angle.
-- This class implements the inherited functions from @{#ZONE_BASE} taking into account the own zone format and properties.
--
-- @field #ZONE_OVAL
ZONE_OVAL = {
ClassName = "OVAL",
ZoneName="",
MajorAxis = nil,
MinorAxis = nil,
Angle = 0,
DrawPoly = nil -- let's just use a ZONE_POLYGON to draw the ZONE_OVAL on the map
}
--- Creates a new ZONE_OVAL from a center point, major axis, minor axis, and angle.
--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Oval.lua
-- @param #table vec2 The center point of the oval
-- @param #number major_axis The major axis of the oval
-- @param #number minor_axis The minor axis of the oval
-- @param #number angle The angle of the oval
-- @return #ZONE_OVAL The new oval
function ZONE_OVAL:New(name, vec2, major_axis, minor_axis, angle)
self = BASE:Inherit(self, ZONE_BASE:New())
self.ZoneName = name
self.CenterVec2 = vec2
self.MajorAxis = major_axis
self.MinorAxis = minor_axis
self.Angle = angle or 0
_DATABASE:AddZone(name, self)
return self
end
--- Constructor to create a ZONE_OVAL instance, taking the name of a drawing made with the draw tool in the Mission Editor.
--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Oval.lua
-- @param #ZONE_OVAL self
-- @param #string DrawingName The name of the drawing in the Mission Editor
-- @return #ZONE_OVAL self
function ZONE_OVAL:NewFromDrawing(DrawingName)
self = BASE:Inherit(self, ZONE_BASE:New(DrawingName))
for _, layer in pairs(env.mission.drawings.layers) do
for _, object in pairs(layer["objects"]) do
if string.find(object["name"], DrawingName, 1, true) then
if object["polygonMode"] == "oval" then
self.CenterVec2 = { x = object["mapX"], y = object["mapY"] }
self.MajorAxis = object["r1"]
self.MinorAxis = object["r2"]
self.Angle = object["angle"]
end
end
end
end
_DATABASE:AddZone(DrawingName, self)
return self
end
--- Gets the major axis of the oval.
-- @param #ZONE_OVAL self
-- @return #number The major axis of the oval
function ZONE_OVAL:GetMajorAxis()
return self.MajorAxis
end
--- Gets the minor axis of the oval.
-- @param #ZONE_OVAL self
-- @return #number The minor axis of the oval
function ZONE_OVAL:GetMinorAxis()
return self.MinorAxis
end
--- Gets the angle of the oval.
-- @param #ZONE_OVAL self
-- @return #number The angle of the oval
function ZONE_OVAL:GetAngle()
return self.Angle
end
--- Returns a the center point of the oval
-- @param #ZONE_OVAL self
-- @return #table The center Vec2
function ZONE_OVAL:GetVec2()
return self.CenterVec2
end
--- Checks if a point is contained within the oval.
-- @param #ZONE_OVAL self
-- @param #table point The point to check
-- @return #bool True if the point is contained, false otherwise
function ZONE_OVAL:IsVec2InZone(vec2)
local cos, sin = math.cos, math.sin
local dx = vec2.x - self.CenterVec2.x
local dy = vec2.y - self.CenterVec2.y
local rx = dx * cos(self.Angle) + dy * sin(self.Angle)
local ry = -dx * sin(self.Angle) + dy * cos(self.Angle)
return rx * rx / (self.MajorAxis * self.MajorAxis) + ry * ry / (self.MinorAxis * self.MinorAxis) <= 1
end
--- Calculates the bounding box of the oval. The bounding box is the smallest rectangle that contains the oval.
-- @param #ZONE_OVAL self
-- @return #table The bounding box of the oval
function ZONE_OVAL:GetBoundingSquare()
local min_x = self.CenterVec2.x - self.MajorAxis
local min_y = self.CenterVec2.y - self.MinorAxis
local max_x = self.CenterVec2.x + self.MajorAxis
local max_y = self.CenterVec2.y + self.MinorAxis
return {
{x=min_x, y=min_x}, {x=max_x, y=min_y}, {x=max_x, y=max_y}, {x=min_x, y=max_y}
}
end
--- Find points on the edge of the oval
-- @param #ZONE_OVAL self
-- @param #number num_points How many points should be found. More = smoother shape
-- @return #table Points on he edge
function ZONE_OVAL:PointsOnEdge(num_points)
num_points = num_points or 40
local points = {}
local dtheta = 2 * math.pi / num_points
for i = 0, num_points - 1 do
local theta = i * dtheta
local x = self.CenterVec2.x + self.MajorAxis * math.cos(theta) * math.cos(self.Angle) - self.MinorAxis * math.sin(theta) * math.sin(self.Angle)
local y = self.CenterVec2.y + self.MajorAxis * math.cos(theta) * math.sin(self.Angle) + self.MinorAxis * math.sin(theta) * math.cos(self.Angle)
table.insert(points, {x = x, y = y})
end
return points
end
--- Returns a random Vec2 within the oval.
-- @param #ZONE_OVAL self
-- @return #table The random Vec2
function ZONE_OVAL:GetRandomVec2()
local theta = math.rad(self.Angle)
local random_point = math.sqrt(math.random()) --> uniformly
--local random_point = math.random() --> more clumped around center
local phi = math.random() * 2 * math.pi
local x_c = random_point * math.cos(phi)
local y_c = random_point * math.sin(phi)
local x_e = x_c * self.MajorAxis
local y_e = y_c * self.MinorAxis
local rx = (x_e * math.cos(theta) - y_e * math.sin(theta)) + self.CenterVec2.x
local ry = (x_e * math.sin(theta) + y_e * math.cos(theta)) + self.CenterVec2.y
return {x=rx, y=ry}
end
--- Define a random @{Core.Point#POINT_VEC2} within the zone.
-- @param #ZONE_OVAL self
-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates.
function ZONE_OVAL:GetRandomPointVec2()
return POINT_VEC2:NewFromVec2(self:GetRandomVec2())
end
--- Define a random @{Core.Point#POINT_VEC2} within the zone.
-- @param #ZONE_OVAL self
-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates.
function ZONE_OVAL:GetRandomPointVec3()
return POINT_VEC2:NewFromVec3(self:GetRandomVec2())
end
--- Draw the zone on the F10 map.
--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Oval.lua
-- @param #ZONE_OVAL self
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red.
-- @param #number Alpha Transparency [0,1]. Default 1.
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. -- doesn't seem to work
-- @param #number FillAlpha Transparency [0,1]. Default 0.15. -- doesn't seem to work
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false.
-- @return #ZONE_OVAL self
function ZONE_OVAL:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType)
Coalition = Coalition or self:GetDrawCoalition()
-- Set draw coalition.
self:SetDrawCoalition(Coalition)
Color = Color or self:GetColorRGB()
Alpha = Alpha or 1
-- Set color.
self:SetColor(Color, Alpha)
FillColor = FillColor or self:GetFillColorRGB()
if not FillColor then
UTILS.DeepCopy(Color)
end
FillAlpha = FillAlpha or self:GetFillColorAlpha()
if not FillAlpha then
FillAlpha = 0.15
end
LineType = LineType or 1
-- Set fill color -----------> has fill color worked in recent versions of DCS?
-- doing something like
--
-- trigger.action.markupToAll(7, -1, 501, p.Coords[1]:GetVec3(), p.Coords[2]:GetVec3(),p.Coords[3]:GetVec3(),p.Coords[4]:GetVec3(),{1,0,0, 1}, {1,0,0, 1}, 4, false, Text or "")
--
-- doesn't seem to fill in the shape for an n-sided polygon
self:SetFillColor(FillColor, FillAlpha)
self.DrawPoly = ZONE_POLYGON:NewFromPointsArray(self.ZoneName, self:PointsOnEdge(80))
self.DrawPoly:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType)
end
--- Remove drawing from F10 map
-- @param #ZONE_OVAL self
function ZONE_OVAL:UndrawZone()
if self.DrawPoly then
self.DrawPoly:UndrawZone()
end
end
--- Ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Triangle.lua
--- This triangle "zone" is not really to be used on its own, it only serves as building blocks for
--- ZONE_POLYGON to accurately find a point inside a polygon; as well as getting the correct surface area of
--- a polygon.
-- @type _ZONE_TRIANGLE
-- @extends #BASE
_ZONE_TRIANGLE = {
ClassName="ZONE_TRIANGLE",
Points={},
Coords={},
CenterVec2={x=0, y=0},
SurfaceArea=0,
DrawIDs={}
}
function _ZONE_TRIANGLE:New(p1, p2, p3)
local self = BASE:Inherit(self, ZONE_BASE:New())
self.Points = {p1, p2, p3}
local center_x = (p1.x + p2.x + p3.x) / 3
local center_y = (p1.y + p2.y + p3.y) / 3
self.CenterVec2 = {x=center_x, y=center_y}
for _, pt in pairs({p1, p2, p3}) do
table.add(self.Coords, COORDINATE:NewFromVec2(pt))
end
self.SurfaceArea = math.abs((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) * 0.5
return self
end
--- Checks if a point is contained within the triangle.
-- @param #table pt The point to check
-- @param #table points (optional) The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it
-- @return #bool True if the point is contained, false otherwise
function _ZONE_TRIANGLE:ContainsPoint(pt, points)
points = points or self.Points
local function sign(p1, p2, p3)
return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y)
end
local d1 = sign(pt, self.Points[1], self.Points[2])
local d2 = sign(pt, self.Points[2], self.Points[3])
local d3 = sign(pt, self.Points[3], self.Points[1])
local has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0)
local has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0)
return not (has_neg and has_pos)
end
--- Returns a random Vec2 within the triangle.
-- @param #table points The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it
-- @return #table The random Vec2
function _ZONE_TRIANGLE:GetRandomVec2(points)
points = points or self.Points
local pt = {math.random(), math.random()}
table.sort(pt)
local s = pt[1]
local t = pt[2] - pt[1]
local u = 1 - pt[2]
return {x = s * points[1].x + t * points[2].x + u * points[3].x,
y = s * points[1].y + t * points[2].y + u * points[3].y}
end
--- Draw the triangle
function _ZONE_TRIANGLE:Draw(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
Coalition=Coalition or -1
Color=Color or {1, 0, 0 }
Alpha=Alpha or 1
FillColor=FillColor or Color
if not FillColor then UTILS.DeepCopy(Color) end
FillAlpha=FillAlpha or Alpha
if not FillAlpha then FillAlpha=1 end
for i=1, #self.Coords do
local c1 = self.Coords[i]
local c2 = self.Coords[i % #self.Coords + 1]
table.add(self.DrawIDs, c1:LineToAll(c2, Coalition, Color, Alpha, LineType, ReadOnly))
end
return self.DrawIDs
end
---
-- @type ZONE_POLYGON_BASE
-- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}.
@ -2021,7 +2345,10 @@ end
-- @field #ZONE_POLYGON_BASE
ZONE_POLYGON_BASE = {
ClassName="ZONE_POLYGON_BASE",
}
_Triangles={}, -- _ZONE_TRIANGLES
SurfaceArea=0,
DrawID={} -- making a table out of the MarkID so its easier to draw an n-sided polygon, see ZONE_POLYGON_BASE:Draw()
}
--- A 2D points array.
-- @type ZONE_POLYGON_BASE.ListVec2
@ -2055,9 +2382,101 @@ function ZONE_POLYGON_BASE:New( ZoneName, PointsArray )
end
-- triangulate the polygon so we can work with it
self._Triangles = self:_Triangulate()
-- set the polygon's surface area
self.SurfaceArea = self:_CalculateSurfaceArea()
return self
end
--- Triangulates the polygon.
--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Polygon.lua
-- @return #table The #_TRIANGLE list that make up
function ZONE_POLYGON_BASE:_Triangulate()
local points = self._.Polygon
local triangles = {}
local function get_orientation(shape_points)
local sum = 0
for i = 1, #shape_points do
local j = i % #shape_points + 1
sum = sum + (shape_points[j].x - shape_points[i].x) * (shape_points[j].y + shape_points[i].y)
end
return sum >= 0 and "clockwise" or "counter-clockwise" -- sum >= 0, return "clockwise", else return "counter-clockwise"
end
local function ensure_clockwise(shape_points)
local orientation = get_orientation(shape_points)
if orientation == "counter-clockwise" then
-- Reverse the order of shape_points so they're clockwise
local reversed = {}
for i = #shape_points, 1, -1 do
table.insert(reversed, shape_points[i])
end
return reversed
end
return shape_points
end
local function is_clockwise(p1, p2, p3)
local cross_product = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)
return cross_product < 0
end
local function divide_recursively(shape_points)
if #shape_points == 3 then
table.insert(triangles, _ZONE_TRIANGLE:New(shape_points[1], shape_points[2], shape_points[3]))
elseif #shape_points > 3 then -- find an ear -> a triangle with no other points inside it
for i, p1 in ipairs(shape_points) do
local p2 = shape_points[(i % #shape_points) + 1]
local p3 = shape_points[(i + 1) % #shape_points + 1]
local triangle = _ZONE_TRIANGLE:New(p1, p2, p3)
local is_ear = true
if not is_clockwise(p1, p2, p3) then
is_ear = false
else
for _, point in ipairs(shape_points) do
if point ~= p1 and point ~= p2 and point ~= p3 and triangle:ContainsPoint(point) then
is_ear = false
break
end
end
end
if is_ear then
-- Check if any point in the original polygon is inside the ear triangle
local is_valid_triangle = true
for _, point in ipairs(points) do
if point ~= p1 and point ~= p2 and point ~= p3 and triangle:ContainsPoint(point) then
is_valid_triangle = false
break
end
end
if is_valid_triangle then
table.insert(triangles, triangle)
local remaining_points = {}
for j, point in ipairs(shape_points) do
if point ~= p2 then
table.insert(remaining_points, point)
end
end
divide_recursively(remaining_points)
break
end
else
end
end
end
end
points = ensure_clockwise(points)
divide_recursively(points)
return triangles
end
--- Update polygon points with an array of @{DCS#Vec2}.
-- @param #ZONE_POLYGON_BASE self
-- @param #ZONE_POLYGON_BASE.ListVec2 Vec2Array An array of @{DCS#Vec2}, forming a polygon.
@ -2072,6 +2491,10 @@ function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array)
self._.Polygon[i].y=Vec2Array[i].y
end
-- triangulate the polygon so we can work with it
self._Triangles = self:_Triangulate()
-- set the polygon's surface area
self.SurfaceArea = self:_CalculateSurfaceArea()
return self
end
@ -2089,9 +2512,24 @@ function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array)
self._.Polygon[i].y=Vec3Array[i].z
end
-- triangulate the polygon so we can work with it
self._Triangles = self:_Triangulate()
-- set the polygon's surface area
self.SurfaceArea = self:_CalculateSurfaceArea()
return self
end
--- Calculates the surface area of the polygon. The surface area is the sum of the areas of the triangles that make up the polygon.
--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Polygon.lua
-- @return #number The surface area of the polygon
function ZONE_POLYGON_BASE:_CalculateSurfaceArea()
local area = 0
for _, triangle in pairs(self._Triangles) do
area = area + triangle.SurfaceArea
end
return area
end
--- Returns the center location of the polygon.
-- @param #ZONE_POLYGON_BASE self
-- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location.
@ -2233,63 +2671,77 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound )
return self
end
--- Draw the zone on the F10 map. **NOTE** Currently, only polygons **up to ten points** are supported!
--- Draw the zone on the F10 map. Infinite number of points supported
--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Polygon.lua
-- @param #ZONE_POLYGON_BASE self
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red.
-- @param #number Alpha Transparency [0,1]. Default 1.
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value.
-- @param #number FillAlpha Transparency [0,1]. Default 0.15.
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. -- doesn't seem to work
-- @param #number FillAlpha Transparency [0,1]. Default 0.15. -- doesn't seem to work
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false.
-- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, IncludeTriangles)
if self._.Polygon and #self._.Polygon >= 3 then
Coalition = Coalition or self:GetDrawCoalition()
if self._.Polygon and #self._.Polygon>=3 then
-- Set draw coalition.
self:SetDrawCoalition(Coalition)
local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1])
Color = Color or self:GetColorRGB()
Alpha = Alpha or 1
Coalition=Coalition or self:GetDrawCoalition()
-- Set color.
self:SetColor(Color, Alpha)
-- Set draw coalition.
self:SetDrawCoalition(Coalition)
FillColor = FillColor or self:GetFillColorRGB()
if not FillColor then
UTILS.DeepCopy(Color)
end
FillAlpha = FillAlpha or self:GetFillColorAlpha()
if not FillAlpha then
FillAlpha = 0.15
end
Color=Color or self:GetColorRGB()
Alpha=Alpha or 1
-- Set fill color -----------> has fill color worked in recent versions of DCS?
-- doing something like
--
-- trigger.action.markupToAll(7, -1, 501, p.Coords[1]:GetVec3(), p.Coords[2]:GetVec3(),p.Coords[3]:GetVec3(),p.Coords[4]:GetVec3(),{1,0,0, 1}, {1,0,0, 1}, 4, false, Text or "")
--
-- doesn't seem to fill in the shape for an n-sided polygon
self:SetFillColor(FillColor, FillAlpha)
-- Set color.
self:SetColor(Color, Alpha)
FillColor=FillColor or self:GetFillColorRGB()
if not FillColor then UTILS.DeepCopy(Color) end
FillAlpha=FillAlpha or self:GetFillColorAlpha()
if not FillAlpha then FillAlpha=0.15 end
-- Set fill color.
self:SetFillColor(FillColor, FillAlpha)
if #self._.Polygon==4 then
local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2])
local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3])
local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4])
self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
else
local Coordinates=self:GetVerticiesCoordinates()
table.remove(Coordinates, 1)
self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
IncludeTriangles = IncludeTriangles or false
-- just draw the triangles, we get the outline for free
if IncludeTriangles then
for _, triangle in pairs(self._Triangles) do
local draw_ids = triangle:Draw()
table.combine(self.DrawID, draw_ids)
end
-- draw outline only
else
local coords = self:GetVerticiesCoordinates()
for i = 1, #coords do
local c1 = coords[i]
local c2 = coords[i % #coords + 1]
table.add(self.DrawID, c1:LineToAll(c2, Coalition, Color, Alpha, LineType, ReadOnly))
end
end
end
end
return self
return self
end
--- Get the surface area of this polygon
-- @param #ZONE_POLYGON_BASE self
-- @return #number Surface area
function ZONE_POLYGON_BASE:GetSurfaceArea()
return self.SurfaceArea
end
--- Get the smallest radius encompassing all points of the polygon zone.
-- @param #ZONE_POLYGON_BASE self
-- @return #number Radius of the zone in meters.
@ -2488,31 +2940,25 @@ function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 )
end
--- Define a random @{DCS#Vec2} within the zone.
--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Polygon.lua
-- @param #ZONE_POLYGON_BASE self
-- @return DCS#Vec2 The Vec2 coordinate.
function ZONE_POLYGON_BASE:GetRandomVec2()
-- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way...
-- Get the bounding square.
local BS = self:GetBoundingSquare()
local Nmax=1000 ; local n=0
while n<Nmax do
-- Random point in the bounding square.
local Vec2={x=math.random(BS.x1, BS.x2), y=math.random(BS.y1, BS.y2)}
-- Check if this is in the polygon.
if self:IsVec2InZone(Vec2) then
return Vec2
-- make sure we assign weights to the triangles based on their surface area, otherwise
-- we'll be more likely to generate random points in smaller triangles
local weights = {}
for _, triangle in pairs(self._Triangles) do
weights[triangle] = triangle.SurfaceArea / self.SurfaceArea
end
n=n+1
end
self:E("Could not find a random point in the polygon zone!")
return nil
local random_weight = math.random()
local accumulated_weight = 0
for triangle, weight in pairs(weights) do
accumulated_weight = accumulated_weight + weight
if accumulated_weight >= random_weight then
return triangle:GetRandomVec2()
end
end
end
--- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone.
@ -2649,7 +3095,8 @@ end
-- @extends #ZONE_POLYGON_BASE
--- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon.
--- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon, OR by drawings made with the Draw tool
--- in the Mission Editor
-- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties.
--
-- ## Declare a ZONE_POLYGON directly in the DCS mission editor!
@ -2672,6 +3119,13 @@ end
-- then SetZone would contain the ZONE_POLYGON object `DefenseZone` as part of the zone collection,
-- without much scripting overhead!
--
-- This class now also supports drawings made with the Draw tool in the Mission Editor. Any drawing made with Line > Segments > Closed, Polygon > Rect or Polygon > Free can be
-- made into a ZONE_POLYGON.
--
-- This class has been updated to use a accurate way of generating random points inside the polygon without having to use trial and error guesses.
-- You can also get the surface area of the polygon now, handy if you want measure which coalition has the largest captured area, for example.
-- @field #ZONE_POLYGON
ZONE_POLYGON = {
ClassName="ZONE_POLYGON",
@ -2732,6 +3186,49 @@ function ZONE_POLYGON:NewFromGroupName( GroupName )
return self
end
--- Constructor to create a ZONE_POLYGON instance, taking the name of a drawing made with the draw tool in the Mission Editor.
-- @param #ZONE_POLYGON self
-- @param #string DrawingName The name of the drawing in the Mission Editor
-- @return #ZONE_POLYGON self
function ZONE_POLYGON:NewFromDrawing(DrawingName)
local points = {}
for _, layer in pairs(env.mission.drawings.layers) do
for _, object in pairs(layer["objects"]) do
if object["name"] == DrawingName then
if (object["primitiveType"] == "Line" and object["closed"] == true) or (object["polygonMode"] == "free") then
-- points for the drawings are saved in local space, so add the object's map x and y coordinates to get
-- world space points we can use
for _, point in UTILS.spairs(object["points"]) do
local p = {x = object["mapX"] + point["x"],
y = object["mapY"] + point["y"] }
table.add(points, p)
end
elseif object["polygonMode"] == "rect" then
-- the points for a rect are saved as local coordinates with an angle. To get the world space points from this
-- we need to rotate the points around the center of the rects by an angle. UTILS.RotatePointAroundPivot was
-- committed in an earlier commit
local angle = object["angle"]
local half_width = object["width"] / 2
local half_height = object["height"] / 2
local center = { x = object["mapX"], y = object["mapY"] }
local p1 = UTILS.RotatePointAroundPivot({ x = center.x - half_height, y = center.y + half_width }, center, angle)
local p2 = UTILS.RotatePointAroundPivot({ x = center.x + half_height, y = center.y + half_width }, center, angle)
local p3 = UTILS.RotatePointAroundPivot({ x = center.x + half_height, y = center.y - half_width }, center, angle)
local p4 = UTILS.RotatePointAroundPivot({ x = center.x - half_height, y = center.y - half_width }, center, angle)
points = {p1, p2, p3, p4}
else
-- something else that might be added in the future
end
end
end
end
local self = BASE:Inherit(self, ZONE_POLYGON_BASE:New(DrawingName, points))
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
--- Find a polygon zone in the _DATABASE using the name of the polygon zone.
-- @param #ZONE_POLYGON self

View File

@ -1234,7 +1234,7 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
return self
end
--- (SRS) Set range control frequency and voice.
--- (SRS) Set range control frequency and voice. Use `RANGE:SetSRS()` once first before using this function.
-- @param #RANGE self
-- @param #number frequency Frequency in MHz. Default 256 MHz.
-- @param #number modulation Modulation, defaults to radio.modulation.AM.
@ -1244,6 +1244,10 @@ end
-- @param #string relayunitname Name of the unit used for transmission location.
-- @return #RANGE self
function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender, relayunitname )
if not self.instructmsrs then
self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeControl!")
return self
end
self.rangecontrolfreq = frequency or 256
self.controlmsrs:SetFrequencies(self.rangecontrolfreq)
self.controlmsrs:SetModulations(modulation or radio.modulation.AM)
@ -1259,7 +1263,7 @@ function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender
return self
end
--- (SRS) Set range instructor frequency and voice.
--- (SRS) Set range instructor frequency and voice. Use `RANGE:SetSRS()` once first before using this function.
-- @param #RANGE self
-- @param #number frequency Frequency in MHz. Default 305 MHz.
-- @param #number modulation Modulation, defaults to radio.modulation.AM.
@ -1269,6 +1273,10 @@ end
-- @param #string relayunitname Name of the unit used for transmission location.
-- @return #RANGE self
function RANGE:SetSRSRangeInstructor( frequency, modulation, voice, culture, gender, relayunitname )
if not self.instructmsrs then
self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeInstructor!")
return self
end
self.instructorfreq = frequency or 305
self.instructmsrs:SetFrequencies(self.instructorfreq)
self.instructmsrs:SetModulations(modulation or radio.modulation.AM)

View File

@ -7405,6 +7405,8 @@ function WAREHOUSE:_CheckRequestNow(request)
-- Check if at least one (cargo) asset is available.
if _nassets>0 then
local asset=_assets[1] --#WAREHOUSE.Assetitem
-- Get the attibute of the requested asset.
_assetattribute=_assets[1].attribute
_assetcategory=_assets[1].category
@ -7415,6 +7417,19 @@ function WAREHOUSE:_CheckRequestNow(request)
if self.airbase and self.airbase:GetCoalition()==self:GetCoalition() then
-- Check if DCS warehouse of airbase has enough assets
if self.airbase.storage then
local nS=self.airbase.storage:GetAmount(asset.unittype)
local nA=asset.nunits*request.nasset -- Number of units requested
if nS<nA then
local text=string.format("Warehouse %s: Request denied! DCS Warehouse has only %d assets of type %s ==> NOT enough to spawn the requested %d asset units (%d groups)",
self.alias, nS, asset.unittype, nA, request.nasset)
self:_InfoMessage(text, 5)
return false
end
end
if self:IsRunwayOperational() or _assetairstart then
if _assetairstart then
@ -7531,6 +7546,9 @@ function WAREHOUSE:_CheckRequestNow(request)
return false
end
elseif _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
end
end

View File

@ -316,6 +316,12 @@
--
-- This uses a male voice with US accent. It requires SRS to be installed in the `D:\DCS\_SRS\` directory. Note that backslashes need to be escaped or simply use slashes (as in linux).
--
-- ### SRS can use multiple frequencies:
--
-- atis=ATIS:New("Batumi", {305,103.85}, {radio.modulation.AM,radio.modulation.FM})
-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US")
-- atis:Start()
--
-- ### SRS Localization
--
-- You can localize the SRS output, all you need is to provide a table of translations and set the `locale` of your instance. You need to provide the translations in your script **before you instantiate your ATIS**.
@ -884,13 +890,14 @@ _ATIS = {}
--- ATIS class version.
-- @field #string version
ATIS.version = "0.10.3"
ATIS.version = "0.10.4"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Correct fog for elevation.
-- DONE: Option to add multiple frequencies for SRS
-- DONE: Zulu time --> Zulu in output.
-- DONE: Fix for AB not having a runway - Helopost like Naqoura
-- DONE: Add new Normandy airfields.
@ -899,7 +906,7 @@ ATIS.version = "0.10.3"
-- DONE: Visibility reported twice over SRS
-- DONE: Add text report for output.
-- DONE: Add stop FMS functions.
-- NOGO: Use local time. Not realisitc!
-- NOGO: Use local time. Not realistic!
-- DONE: Dew point. Approx. done.
-- DONE: Metric units.
-- DONE: Set UTC correction.
@ -915,8 +922,8 @@ ATIS.version = "0.10.3"
--- Create a new ATIS class object for a specific airbase.
-- @param #ATIS self
-- @param #string AirbaseName Name of the airbase.
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz.
-- @param #number Modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators.
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. When using **SRS** this can be passed as a table of multiple frequencies.
-- @param #number Modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. When using **SRS** this can be passed as a table of multiple modulations.
-- @return #ATIS self
function ATIS:New(AirbaseName, Frequency, Modulation)
@ -1594,8 +1601,16 @@ function ATIS:onafterStart( From, Event, To )
end
-- Info.
self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) )
if type(self.frequency) == "table" then
local frequency = table.concat(self.frequency,"/")
local modulation = self.modulation
if type(self.modulation) == "table" then
modulation = table.concat(self.modulation,"/")
end
self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %s MHz Modulation=%s", ATIS.version, self.airbasename, frequency, modulation ) )
else
self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) )
end
-- Start radio queue.
if not self.useSRS then
self.radioqueue = RADIOQUEUE:New( self.frequency, self.modulation, string.format( "ATIS %s", self.airbasename ) )
@ -1653,7 +1668,17 @@ function ATIS:onafterStatus( From, Event, To )
end
-- Info text.
local text = string.format( "State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName( self.modulation ) )
local text = ""
if type(self.frequency) == "table" then
local frequency = table.concat(self.frequency,"/")
local modulation = self.modulation
if type(self.modulation) == "table" then
modulation = table.concat(self.modulation,"/")
end
text = string.format( "State %s: Freq=%s MHz %s", fsmstate, frequency, modulation )
else
text = string.format( "State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName( self.modulation ) )
end
if self.useSRS then
text = text .. string.format( ", SRS path=%s (%s), gender=%s, culture=%s, voice=%s", tostring( self.msrs.path ), tostring( self.msrs.port ), tostring( self.msrs.gender ), tostring( self.msrs.culture ), tostring( self.msrs.voice ) )
else
@ -2919,8 +2944,17 @@ function ATIS:UpdateMarker( information, runact, wind, altimeter, temperature )
if self.markerid then
self.airbase:GetCoordinate():RemoveMark( self.markerid )
end
local text = string.format( "ATIS on %.3f %s, %s:\n", self.frequency, UTILS.GetModulationName( self.modulation ), tostring( information ) )
local text = ""
if type(self.frequency) == "table" then
local frequency = table.concat(self.frequency,"/")
local modulation = self.modulation
if type(modulation) == "table" then
modulation = table.concat(self.modulation,"/")
end
text = string.format( "ATIS on %s %s, %s:\n", tostring(frequency), tostring(modulation), tostring( information ) )
else
text = string.format( "ATIS on %.3f %s, %s:\n", self.frequency, UTILS.GetModulationName( self.modulation ), tostring( information ) )
end
text = text .. string.format( "%s\n", tostring( runact ) )
text = text .. string.format( "%s\n", tostring( wind ) )
text = text .. string.format( "%s\n", tostring( altimeter ) )

View File

@ -2811,7 +2811,11 @@ function FLIGHTCONTROL:_PlayerInfoATIS(groupname)
-- Radio message.
self:TransmissionPilot(rtext, flight)
self:TransmissionTower(srstxt,flight,10)
if self.atis then
self:TransmissionTower(srstxt,flight,10)
else
self:TransmissionTower(text,flight,10)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname)))

View File

@ -2863,8 +2863,11 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime)
end
else
-- Check if not parking (could be on ALERT5 and just spawned (current mission=nil)
if not self:IsParking() then
self:T(self.lid..string.format("Passed Final WP but Tasks=%d or Missions=%d left in the queue. Wait!", nTasks, nMissions))
self:__Wait(-1)
end
end
else
self:T(self.lid..string.format("Passed Final WP but still have current Task (#%s) or Mission (#%s) left to do", tostring(self.taskcurrent), tostring(self.currentmission)))

View File

@ -1483,11 +1483,11 @@ function OPSZONE:_GetZoneColor()
local color={0,0,0}
if self.ownerCurrent==coalition.side.NEUTRAL then
color={1, 1, 1}
color=self.ZoneOwnerNeutral or {1, 1, 1}
elseif self.ownerCurrent==coalition.side.BLUE then
color={0, 0, 1}
color=self.ZoneOwnerBlue or {0, 0, 1}
elseif self.ownerCurrent==coalition.side.RED then
color={1, 0, 0}
color=self.ZoneOwnerRed or {1, 0, 0}
else
end
@ -1495,6 +1495,21 @@ function OPSZONE:_GetZoneColor()
return color
end
--- Set custom RGB color of zone depending on current owner.
-- @param #OPSZONE self
-- @param #table Neutral Color is a table of RGB values 0..1 for Red, Green, and Blue respectively, e.g. {1,0,0} for red.
-- @param #table Blue Color is a table of RGB values 0..1 for Red, Green, and Blue respectively, e.g. {0,1,0} for green.
-- @param #table Red Color is a table of RGB values 0..1 for Red, Green, and Blue respectively, e.g. {0,0,1} for blue.
-- @return #OPSZONE self
function OPSZONE:SetZoneColor(Neutral, Blue, Red)
self.ZoneOwnerNeutral = Neutral or {1, 1, 1}
self.ZoneOwnerBlue = Blue or {0, 0, 1}
self.ZoneOwnerRed = Red or {1, 0, 0}
return self
end
--- Update marker on the F10 map.
-- @param #OPSZONE self
function OPSZONE:_UpdateMarker()

View File

@ -104,7 +104,7 @@ PLAYERRECCE = {
ClassName = "PLAYERRECCE",
verbose = true,
lid = nil,
version = "0.0.21",
version = "0.0.22",
ViewZone = {},
ViewZoneVisual = {},
ViewZoneLaser = {},
@ -469,8 +469,10 @@ function PLAYERRECCE:_GetClockDirection(unit, target)
local _playerPosition = unit:GetCoordinate() -- get position of helicopter
local _targetpostions = target:GetCoordinate() -- get position of downed pilot
local _heading = unit:GetHeading() -- heading
--self:I("Heading = ".._heading)
local DirectionVec3 = _playerPosition:GetDirectionVec3( _targetpostions )
local Angle = _playerPosition:GetAngleDegrees( DirectionVec3 )
--self:I("Angle = "..Angle)
local clock = 12
local hours = 0
if _heading and Angle then
@ -478,10 +480,13 @@ function PLAYERRECCE:_GetClockDirection(unit, target)
--if angle == 0 then angle = 360 end
clock = _heading-Angle
hours = (clock/30)*-1
--self:I("hours = "..hours)
clock = 12+hours
clock = UTILS.Round(clock,0)
if clock > 12 then clock = clock-12 end
if clock == 0 then clock = 12 end
end
--self:I("Clock ="..clock)
return clock
end
@ -709,8 +714,8 @@ function PLAYERRECCE:_GetViewZone(unit, vheading, minview, maxview, angle, camon
local heading2 = (vheading-90)%360
self:T({heading1,heading2})
local startpos = startp:Translate(minview,vheading)
local pos1 = startpos:Translate(10,heading1)
local pos2 = startpos:Translate(10,heading2)
local pos1 = startpos:Translate(12.5,heading1)
local pos2 = startpos:Translate(12.5,heading2)
local pos3 = pos1:Translate(maxview,vheading)
local pos4 = pos2:Translate(maxview,vheading)
local array = {}
@ -912,32 +917,41 @@ function PLAYERRECCE:_LaseTarget(client,targetset)
else
laser = self.LaserSpots[playername]
end
-- old target
if self.LaserTarget[playername] then
-- still looking at target?
local target=self.LaserTarget[playername] -- Ops.Target#TARGET
local oldtarget = target:GetObject() --or laser.Target
--self:I("Targetstate: "..target:GetState())
--self:I("Laser State: "..tostring(laser:IsLasing()))
if not oldtarget or targetset:IsNotInSet(oldtarget) or target:IsDead() or target:IsDestroyed() then
self:T("Targetstate: "..target:GetState())
self:T("Laser State: "..tostring(laser:IsLasing()))
if (not oldtarget) or targetset:IsNotInSet(oldtarget) or target:IsDead() or target:IsDestroyed() then
-- lost LOS or dead
laser:LaseOff()
if target:IsDead() or target:IsDestroyed() or target:GetLife() < 2 then
self:__Shack(-1,client,oldtarget)
self.LaserTarget[playername] = nil
--self.LaserTarget[playername] = nil
else
self:__TargetLOSLost(-1,client,oldtarget)
self.LaserTarget[playername] = nil
--self.LaserTarget[playername] = nil
end
end
if oldtarget and (not laser:IsLasing()) then
--self:I("Switching laser back on ..")
self.LaserTarget[playername] = nil
oldtarget = nil
self.LaserSpots[playername] = nil
elseif oldtarget and laser and (not laser:IsLasing()) then
--laser:LaseOff()
self:T("Switching laser back on ..")
local lasercode = self.UnitLaserCodes[playername] or laser.LaserCode or 1688
local lasingtime = self.lasingtime or 60
--local targettype = target:GetTypeName()
laser:LaseOn(oldtarget,lasercode,lasingtime)
--self:__TargetLasing(-1,client,oldtarget,lasercode,lasingtime)
else
-- we should not be here...
self:T("Target alive and laser is on!")
--self.LaserSpots[playername] = nil
end
elseif not laser:IsLasing() and target then
-- new target
elseif (not laser:IsLasing()) and target then
local relativecam = self.LaserRelativePos[client:GetTypeName()]
laser:SetRelativeStartPosition(relativecam)
local lasercode = self.UnitLaserCodes[playername] or laser.LaserCode or 1688
@ -945,7 +959,7 @@ function PLAYERRECCE:_LaseTarget(client,targetset)
--local targettype = target:GetTypeName()
laser:LaseOn(target,lasercode,lasingtime)
self.LaserTarget[playername] = TARGET:New(target)
self.LaserTarget[playername].TStatus = 9
--self.LaserTarget[playername].TStatus = 9
self:__TargetLasing(-1,client,target,lasercode,lasingtime)
end
return self
@ -1027,6 +1041,13 @@ function PLAYERRECCE:_SwitchLasing(client,group,playername)
MESSAGE:New("Lasing is now ON",10,self.Name or "FACA"):ToClient(client)
else
self.AutoLase[playername] = false
if self.LaserSpots[playername] then
local laser = self.LaserSpots[playername] -- Core.Spot#SPOT
if laser:IsLasing() then
laser:LaseOff()
end
self.LaserSpots[playername] = nil
end
MESSAGE:New("Lasing is now OFF",10,self.Name or "FACA"):ToClient(client)
end
if self.ClientMenus[playername] then
@ -1681,7 +1702,7 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername)
local text2tts = string.format("All stations, FACA %s on station at %s!",callsign, coordtext)
text2tts = self:_GetTextForSpeech(text2tts)
if self.debug then
self:I(text2.."\n"..text2tts)
self:T(text2.."\n"..text2tts)
end
if self.UseSRS then
local grp = Client:GetGroup()
@ -1720,7 +1741,7 @@ function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername)
local texttts = string.format("All stations, FACA %s leaving station at %s, good bye!",callsign, coordtext)
texttts = self:_GetTextForSpeech(texttts)
if self.debug then
self:I(text.."\n"..texttts)
self:T(text.."\n"..texttts)
end
local text1 = "Going home!"
if self.UseSRS then

View File

@ -239,6 +239,13 @@ AIRBASE.Nevada = {
-- * AIRBASE.Normandy.Broglie
-- * AIRBASE.Normandy.Bernay_Saint_Martin
-- * AIRBASE.Normandy.Saint_Andre_de_lEure
-- * AIRBASE.Normandy.Biggin_Hill
-- * AIRBASE.Normandy.Manston
-- * AIRBASE.Normandy.Detling
-- * AIRBASE.Normandy.Lympne
-- * AIRBASE.Normandy.Abbeville_Drucat
-- * AIRBASE.Normandy.Merville_Calonne
-- * AIRBASE.Normandy.Saint_Omer_Wizernes
--
-- @field Normandy
AIRBASE.Normandy = {
@ -312,6 +319,13 @@ AIRBASE.Normandy = {
["Broglie"] = "Broglie",
["Bernay_Saint_Martin"] = "Bernay Saint Martin",
["Saint_Andre_de_lEure"] = "Saint-Andre-de-lEure",
["Biggin_Hill"] = "Biggin Hill",
["Manston"] = "Manston",
["Detling"] = "Detling",
["Lympne"] = "Lympne",
["Abbeville_Drucat"] = "Abbeville Drucat",
["Merville_Calonne"] = "Merville Calonne",
["Saint_Omer_Wizernes"] = "Saint-Omer Wizernes",
}
--- Airbases of the Persion Gulf Map:

View File

@ -3974,7 +3974,7 @@ function CONTROLLABLE:OptionAAAttackRange( range )
local Controller = self:_GetController()
if Controller then
if self:IsAir() then
self:SetOption( AI.Option.Air.val.MISSILE_ATTACK, range )
self:SetOption( AI.Option.Air.id.MISSILE_ATTACK, range )
end
end
return self

View File

@ -2924,7 +2924,7 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
return callsign
end
---
--- Set a GROUP to act as recovery tanker
-- @param #GROUP self
-- @param Wrapper.Group#GROUP CarrierGroup.
-- @param #number Speed Speed in knots.
@ -2950,3 +2950,37 @@ function GROUP:SetAsRecoveryTanker(CarrierGroup,Speed,ToKIAS,Altitude,Delay,Last
return self
end
--- Get a list of Link16 S/TN data from a GROUP. Can (as of Nov 2023) be obtained from F-18, F-16, F-15E (not the user flyable one) and A-10C-II groups.
-- @param #GROUP self
-- @return #table Table of data entries, indexed by unit name, each entry is a table containing STN, VCL (voice call label), VCN (voice call number), and Lead (#boolean, if true it's the flight lead)
-- @return #string Report Formatted report of all data
function GROUP:GetGroupSTN()
local tSTN = {} -- table
local units = self:GetUnits()
local gname = self:GetName()
gname = string.gsub(gname,"(#%d+)$","")
local report = REPORT:New()
report:Add("Link16 S/TN Report")
report:Add("Group: "..gname)
report:Add("==================")
for _,_unit in pairs(units) do
local unit = _unit -- Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
local STN, VCL, VCN, Lead = unit:GetSTN()
local name = unit:GetName()
tSTN[name] = {
STN=STN,
VCL=VCL,
VCN=VCN,
Lead=Lead,
}
local lead = Lead == true and "(*)" or ""
report:Add(string.format("| %s%s %s %s",tostring(VCL),tostring(VCN),tostring(STN),lead))
end
end
report:Add("==================")
local text = report:Text()
return tSTN,text
end

View File

@ -1659,3 +1659,36 @@ function UNIT:GetSkill()
local skill = _DATABASE.Templates.Units[name].Template.skill or "Random"
return skill
end
--- Get Link16 STN or SADL TN and other datalink info from Unit, if any.
-- @param #UNIT self
-- @return #string STN STN or TN Octal as string, or nil if not set/capable.
-- @return #string VCL Voice Callsign Label or nil if not set/capable.
-- @return #string VCN Voice Callsign Number or nil if not set/capable.
-- @return #string Lead If true, unit is Flight Lead, else false or nil.
function UNIT:GetSTN()
self:F2(self.UnitName)
local STN = nil -- STN/TN
local VCL = nil -- VoiceCallsignLabel
local VCN = nil -- VoiceCallsignNumber
local FGL = false -- FlightGroupLeader
local template = self:GetTemplate()
if template.AddPropAircraft then
if template.AddPropAircraft.STN_L16 then
STN = template.AddPropAircraft.STN_L16
elseif template.AddPropAircraft.SADL_TN then
STN = template.AddPropAircraft.SADL_TN
end
VCN = template.AddPropAircraft.VoiceCallsignNumber
VCL = template.AddPropAircraft.VoiceCallsignLabel
end
if template.datalinks and template.datalinks.Link16 and template.datalinks.Link16.settings then
FGL = template.datalinks.Link16.settings.flightLead
end
-- A10CII
if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then
FGL = template.datalinks.SADL.settings.flightLead
end
return STN, VCL, VCN, FGL
end