Merge branch 'develop' into FF/Ops

This commit is contained in:
Frank
2023-02-12 11:52:13 +01:00
12 changed files with 1133 additions and 76 deletions

View File

@@ -89,6 +89,7 @@ DATABASE = {
FLIGHTGROUPS = {}, FLIGHTGROUPS = {},
FLIGHTCONTROLS = {}, FLIGHTCONTROLS = {},
OPSZONES = {}, OPSZONES = {},
PATHLINES = {},
} }
local _DATABASECoalition = local _DATABASECoalition =
@@ -245,7 +246,7 @@ function DATABASE:FindAirbase( AirbaseName )
end end
do -- Zones do -- Zones and Pathlines
--- Finds a @{Core.Zone} based on the zone name. --- Finds a @{Core.Zone} based on the zone name.
-- @param #DATABASE self -- @param #DATABASE self
@@ -268,7 +269,6 @@ do -- Zones
end end
end end
--- Deletes a @{Core.Zone} from the DATABASE based on the zone name. --- Deletes a @{Core.Zone} from the DATABASE based on the zone name.
-- @param #DATABASE self -- @param #DATABASE self
-- @param #string ZoneName The name of the zone. -- @param #string ZoneName The name of the zone.
@@ -278,6 +278,39 @@ do -- Zones
end end
--- Adds a @{Core.Pathline} based on its name in the DATABASE.
-- @param #DATABASE self
-- @param #string PathlineName The name of the pathline
-- @param Core.Pathline#PATHLINE Pathline The pathline.
function DATABASE:AddPathline( PathlineName, Pathline )
if not self.PATHLINES[PathlineName] then
self.PATHLINES[PathlineName]=Pathline
end
end
--- Finds a @{Core.Pathline} by its name.
-- @param #DATABASE self
-- @param #string PathlineName The name of the Pathline.
-- @return Core.Pathline#PATHLINE The found PATHLINE.
function DATABASE:FindPathline( PathlineName )
local pathline = self.PATHLINES[PathlineName]
return pathline
end
--- Deletes a @{Core.Pathline} from the DATABASE based on its name.
-- @param #DATABASE self
-- @param #string PathlineName The name of the PATHLINE.
function DATABASE:DeletePathline( PathlineName )
self.PATHLINES[PathlineName]=nil
return self
end
--- Private method that registers new ZONE_BASE derived objects within the DATABASE Object. --- Private method that registers new ZONE_BASE derived objects within the DATABASE Object.
-- @param #DATABASE self -- @param #DATABASE self
-- @return #DATABASE self -- @return #DATABASE self
@@ -371,60 +404,148 @@ do -- Zones
-- Add zone to DB. -- Add zone to DB.
self:AddZone( ZoneName, Zone_Polygon ) self:AddZone( ZoneName, Zone_Polygon )
end end
end end
-- Drawings as zones -- Drawings as zones
if env.mission.drawings and env.mission.drawings.layers then if env.mission.drawings and env.mission.drawings.layers then
-- Loop over layers. -- Loop over layers.
for layerID, layerData in pairs(env.mission.drawings.layers or {}) do for layerID, layerData in pairs(env.mission.drawings.layers or {}) do
-- Loop over objects in layers. -- Loop over objects in layers.
for objectID, objectData in pairs(layerData.objects or {}) do for objectID, objectData in pairs(layerData.objects or {}) do
-- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice) -- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice)
if objectData.polygonMode=="free" and objectData.points and #objectData.points>=4 then if objectData.polygonMode and (objectData.polygonMode=="free") and objectData.points and #objectData.points>=4 then
---
-- Drawing: Polygon free
---
-- Name of the zone. -- Name of the zone.
local ZoneName=objectData.name or "Unknown Drawing Zone" local ZoneName=objectData.name or "Unknown free Polygon Drawing"
-- Reference point. All other points need to be translated by this. -- Reference point. All other points need to be translated by this.
local vec2={x=objectData.mapX, y=objectData.mapY} local vec2={x=objectData.mapX, y=objectData.mapY}
-- Copy points array. -- Debug stuff.
--local vec3={x=objectData.mapX, y=0, z=objectData.mapY}
--local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY")
--trigger.action.markToAll(id, "mapXY", vec3)
-- Copy points array.
local points=UTILS.DeepCopy(objectData.points) local points=UTILS.DeepCopy(objectData.points)
-- Translate points. -- Translate points.
for i,_point in pairs(points) do for i,_point in pairs(points) do
local point=_point --DCS#Vec2 local point=_point --DCS#Vec2
points[i]=UTILS.Vec2Add(point, vec2) points[i]=UTILS.Vec2Add(point, vec2)
end end
-- Remove last point. -- Remove last point.
table.remove(points, #points) table.remove(points, #points)
-- Debug output -- Debug output
self:I(string.format("Register ZONE: %s (Polygon drawing with %d verticies)", ZoneName, #points)) self:I(string.format("Register ZONE: %s (Polygon (free) drawing with %d vertices)", ZoneName, #points))
-- Create new polygon zone. -- Create new polygon zone.
local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points) local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points)
-- Set color. -- Set color.
Zone:SetColor({1, 0, 0}, 0.15) Zone:SetColor({1, 0, 0}, 0.15)
-- Store in DB. -- Store in DB.
self.ZONENAMES[ZoneName] = ZoneName self.ZONENAMES[ZoneName] = ZoneName
-- Add zone. -- Add zone.
self:AddZone(ZoneName, Zone) self:AddZone(ZoneName, Zone)
end -- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice)
end elseif objectData.polygonMode and objectData.polygonMode=="rect" then
---
-- Drawing: Polygon rect
---
-- Name of the zone.
local ZoneName=objectData.name or "Unknown rect Polygon Drawing"
-- Reference point (center of the rectangle).
local vec2={x=objectData.mapX, y=objectData.mapY}
-- For a rectangular polygon drawing, we have the width (y) and height (x).
local w=objectData.width
local h=objectData.height
-- Create points from center using with and height (width for y and height for x is a bit confusing, but this is how ED implemented it).
local points={}
points[1]={x=vec2.x-h/2, y=vec2.y+w/2} --Upper left
points[2]={x=vec2.x+h/2, y=vec2.y+w/2} --Upper right
points[3]={x=vec2.x+h/2, y=vec2.y-w/2} --Lower right
points[4]={x=vec2.x-h/2, y=vec2.y-w/2} --Lower left
--local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY")
-- Debug output
self:I(string.format("Register ZONE: %s (Polygon (rect) drawing with %d vertices)", ZoneName, #points))
-- Create new polygon zone.
local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points)
-- Set color.
Zone:SetColor({1, 0, 0}, 0.15)
-- Store in DB.
self.ZONENAMES[ZoneName] = ZoneName
-- Add zone.
self:AddZone(ZoneName, Zone)
elseif objectData.lineMode and (objectData.lineMode=="segments" or objectData.lineMode=="segment" or objectData.lineMode=="free") and objectData.points and #objectData.points>=2 then
---
-- Drawing: Line (segments, segment or free)
---
-- Name of the zone.
local Name=objectData.name or "Unknown Line Drawing"
-- Reference point. All other points need to be translated by this.
local vec2={x=objectData.mapX, y=objectData.mapY}
-- Copy points array.
local points=UTILS.DeepCopy(objectData.points)
-- Translate points.
for i,_point in pairs(points) do
local point=_point --DCS#Vec2
points[i]=UTILS.Vec2Add(point, vec2)
end
-- Debug output
self:I(string.format("Register PATHLINE: %s (Line drawing with %d points)", Name, #points))
-- Create new polygon zone.
local Pathline=PATHLINE:NewFromVec2Array(Name, points)
-- Set color.
--Zone:SetColor({1, 0, 0}, 0.15)
-- Add zone.
self:AddPathline(Name,Pathline)
end
end
end end
end end
end end
end -- zone end -- zone
do -- Zone_Goal do -- Zone_Goal
@@ -470,7 +591,7 @@ do -- OpsZone
function DATABASE:FindOpsZone( ZoneName ) function DATABASE:FindOpsZone( ZoneName )
local ZoneFound = self.OPSZONES[ZoneName] local ZoneFound = self.OPSZONES[ZoneName]
return ZoneFound return ZoneFound
end end
@@ -478,15 +599,15 @@ do -- OpsZone
-- @param #DATABASE self -- @param #DATABASE self
-- @param Ops.OpsZone#OPSZONE OpsZone The zone. -- @param Ops.OpsZone#OPSZONE OpsZone The zone.
function DATABASE:AddOpsZone( OpsZone ) function DATABASE:AddOpsZone( OpsZone )
if OpsZone then if OpsZone then
local ZoneName=OpsZone:GetName() local ZoneName=OpsZone:GetName()
if not self.OPSZONES[ZoneName] then if not self.OPSZONES[ZoneName] then
self.OPSZONES[ZoneName] = OpsZone self.OPSZONES[ZoneName] = OpsZone
end end
end end
end end
@@ -1081,7 +1202,7 @@ function DATABASE:_RegisterClients()
for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do
self:I(string.format("Register Client: %s", tostring(ClientName))) self:I(string.format("Register Client: %s", tostring(ClientName)))
local client=self:AddClient( ClientName ) local client=self:AddClient( ClientName )
client.SpawnCoord=COORDINATE:New(ClientTemplate.x, ClientTemplate.alt, ClientTemplate.y) client.SpawnCoord=COORDINATE:New(ClientTemplate.x, ClientTemplate.alt, ClientTemplate.y)
end end
return self return self
@@ -1131,7 +1252,7 @@ end
function DATABASE:_RegisterAirbase(airbase) function DATABASE:_RegisterAirbase(airbase)
if airbase then if airbase then
-- Get the airbase name. -- Get the airbase name.
local DCSAirbaseName = airbase:getName() local DCSAirbaseName = airbase:getName()
@@ -1760,7 +1881,7 @@ function DATABASE:_RegisterTemplates()
if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group
self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID) self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
else else

View File

@@ -17,7 +17,7 @@
-- ### Author: **Applevangelist** -- ### Author: **Applevangelist**
-- --
-- Date: 5 May 2021 -- Date: 5 May 2021
-- Last Update: Sep 2022 -- Last Update: Feb 2023
-- --
-- === -- ===
--- ---
@@ -50,7 +50,7 @@ MARKEROPS_BASE = {
ClassName = "MARKEROPS", ClassName = "MARKEROPS",
Tag = "mytag", Tag = "mytag",
Keywords = {}, Keywords = {},
version = "0.1.0", version = "0.1.1",
debug = false, debug = false,
Casesensitive = true, Casesensitive = true,
} }
@@ -124,7 +124,8 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
-- @param #string Text The text on the marker -- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text -- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker. -- @param Core.Point#COORDINATE Coord Coordinate of the marker.
-- @param #number idx DCS Marker ID
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map. --- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted -- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
-- @param #MARKEROPS_BASE self -- @param #MARKEROPS_BASE self
@@ -172,7 +173,7 @@ function MARKEROPS_BASE:OnEventMark(Event)
if Eventtext~=nil then if Eventtext~=nil then
if self:_MatchTag(Eventtext) then if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext) local matchtable = self:_MatchKeywords(Eventtext)
self:MarkChanged(Eventtext,matchtable,coord) self:MarkChanged(Eventtext,matchtable,coord,Event.idx)
end end
end end
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then elseif Event.id==world.event.S_EVENT_MARK_REMOVED then

View File

@@ -0,0 +1,370 @@
--- **Core** - Path from A to B.
--
-- **Main Features:**
--
-- * Path from A to B
-- * Arbitrary number of points
-- * Automatically from lines drawtool
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Core.Pathline
-- @image CORE_Pathline.png
--- PATHLINE class.
-- @type PATHLINE
-- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string name Name of the path line.
-- @field #table points List of 3D points defining the path.
-- @extends Core.Base#BASE
--- *The shortest distance between two points is a straight line.* -- Archimedes
--
-- ===
--
-- # The PATHLINE Concept
--
-- List of points defining a path from A to B. The pathline can consist of multiple points. Each point holds the information of its position, the surface type, the land height
-- and the water depth (if over sea).
--
-- Line drawings created in the mission editor are automatically registered as pathlines and stored in the MOOSE database.
-- They can be accessed with the @{#PATHLINE.FindByName) function.
--
-- # Constructor
--
-- The @{PATHLINE.New) function creates a new PATHLINE object. This does not hold any points. Points can be added with the @{#PATHLINE.AddPointFromVec2} and @{#PATHLINE.AddPointFromVec3}
--
-- For a given table of 2D or 3D positions, a new PATHLINE object can be created with the @{#PATHLINE.NewFromVec2Array} or @{#PATHLINE.NewFromVec3Array}, respectively.
--
-- # Line Drawings
--
-- The most convenient way to create a pathline is the draw panel feature in the DCS mission editor. You can select "Line" and then "Segments", "Segment" or "Free" to draw your lines.
-- These line drawings are then automatically added to the MOOSE database as PATHLINE objects and can be retrieved with the @{#PATHLINE.FindByName) function, where the name is the one
-- you specify in the draw panel.
--
-- # Mark on F10 map
--
-- The ponints of the PATHLINE can be marked on the F10 map with the @{#PATHLINE.MarkPoints}(`true`) function. The mark points contain information of the surface type, land height and
-- water depth.
--
-- To remove the marks, use @{#PATHLINE.MarkPoints}(`false`).
--
-- @field #PATHLINE
PATHLINE = {
ClassName = "PATHLINE",
lid = nil,
points = {},
}
--- Point of line.
-- @type PATHLINE.Point
-- @field DCS#Vec3 vec3 3D position.
-- @field DCS#Vec2 vec2 2D position.
-- @field #number surfaceType Surface type.
-- @field #number landHeight Land height in meters.
-- @field #number depth Water depth in meters.
-- @field #number markerID Marker ID.
--- PATHLINE class version.
-- @field #string version
PATHLINE.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new PATHLINE object. Points need to be added later.
-- @param #PATHLINE self
-- @param #string Name Name of the path.
-- @return #PATHLINE self
function PATHLINE:New(Name)
-- Inherit everything from INTEL class.
local self=BASE:Inherit(self, BASE:New()) --#PATHLINE
self.name=Name or "Unknown Path"
self.lid=string.format("PATHLINE %s | ", Name)
return self
end
--- Create a new PATHLINE object from a given list of 2D points.
-- @param #PATHLINE self
-- @param #string Name Name of the pathline.
-- @param #table Vec2Array List of DCS#Vec2 points.
-- @return #PATHLINE self
function PATHLINE:NewFromVec2Array(Name, Vec2Array)
local self=PATHLINE:New(Name)
for i=1,#Vec2Array do
self:AddPointFromVec2(Vec2Array[i])
end
return self
end
--- Create a new PATHLINE object from a given list of 3D points.
-- @param #PATHLINE self
-- @param #string Name Name of the pathline.
-- @param #table Vec3Array List of DCS#Vec3 points.
-- @return #PATHLINE self
function PATHLINE:NewFromVec3Array(Name, Vec3Array)
local self=PATHLINE:New(Name)
for i=1,#Vec3Array do
self:AddPointFromVec3(Vec3Array[i])
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Find a pathline in the database.
-- @param #PATHLINE self
-- @param #string Name The name of the pathline.
-- @return #PATHLINE self
function PATHLINE:FindByName(Name)
local pathline = _DATABASE:FindPathline(Name)
return pathline
end
--- Add a point to the path from a given 2D position. The third dimension is determined from the land height.
-- @param #PATHLINE self
-- @param DCS#Vec2 Vec2 The 2D vector (x,y) to add.
-- @return #PATHLINE self
function PATHLINE:AddPointFromVec2(Vec2)
if Vec2 then
local point=self:_CreatePoint(Vec2)
table.insert(self.points, point)
end
return self
end
--- Add a point to the path from a given 3D position.
-- @param #PATHLINE self
-- @param DCS#Vec3 Vec3 The 3D vector (x,y) to add.
-- @return #PATHLINE self
function PATHLINE:AddPointFromVec3(Vec3)
if Vec3 then
local point=self:_CreatePoint(Vec3)
table.insert(self.points, point)
end
return self
end
--- Get name of pathline.
-- @param #PATHLINE self
-- @return #string Name of the pathline.
function PATHLINE:GetName()
return self.name
end
--- Get number of points.
-- @param #PATHLINE self
-- @return #number Number of points.
function PATHLINE:GetNumberOfPoints()
local N=#self.points
return N
end
--- Get points of pathline. Not that points are tables, that contain more information as just the 2D or 3D position but also the surface type etc.
-- @param #PATHLINE self
-- @return <#PATHLINE.Point> List of points.
function PATHLINE:GetPoints()
return self.points
end
--- Get 3D points of pathline.
-- @param #PATHLINE self
-- @return <DCS#Vec3> List of DCS#Vec3 points.
function PATHLINE:GetPoints3D()
local vecs={}
for _,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
table.insert(vecs, point.vec3)
end
return vecs
end
--- Get 2D points of pathline.
-- @param #PATHLINE self
-- @return <DCS#Vec2> List of DCS#Vec2 points.
function PATHLINE:GetPoints2D()
local vecs={}
for _,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
table.insert(vecs, point.vec2)
end
return vecs
end
--- Get COORDINATES of pathline. Note that COORDINATE objects are created when calling this function. That does involve deep copy calls and can have an impact on performance if done too often.
-- @param #PATHLINE self
-- @return <Core.Point#COORDINATE> List of COORDINATES points.
function PATHLINE:GetCoordinats()
local vecs={}
for _,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
local coord=COORDINATE:NewFromVec3(point.vec3)
end
return vecs
end
--- Get the n-th point of the pathline.
-- @param #PATHLINE self
-- @param #number n The index of the point. Default is the first point.
-- @return #PATHLINE.Point Point.
function PATHLINE:GetPointFromIndex(n)
local N=self:GetNumberOfPoints()
n=n or 1
local point=nil --#PATHLINE.Point
if n>=1 and n<=N then
point=self.point[n]
else
self:E(self.lid..string.format("ERROR: No point in pathline for N=%s", tostring(n)))
end
return point
end
--- Get the 3D position of the n-th point.
-- @param #PATHLINE self
-- @param #number n The n-th point.
-- @return DCS#VEC3 Position in 3D.
function PATHLINE:GetPoint3DFromIndex(n)
local point=self:GetPointFromIndex(n)
if point then
return point.vec3
end
return nil
end
--- Get the 2D position of the n-th point.
-- @param #PATHLINE self
-- @param #number n The n-th point.
-- @return DCS#VEC2 Position in 3D.
function PATHLINE:GetPoint2DFromIndex(n)
local point=self:GetPointFromIndex(n)
if point then
return point.vec2
end
return nil
end
--- Mark points on F10 map.
-- @param #PATHLINE self
-- @param #boolean Switch If `true` or nil, set marks. If `false`, remove marks.
-- @return <DCS#Vec3> List of DCS#Vec3 points.
function PATHLINE:MarkPoints(Switch)
for i,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
if Switch==false then
if point.markerID then
UTILS.RemoveMark(point.markerID, Delay)
end
else
if point.markerID then
UTILS.RemoveMark(point.markerID)
end
point.markerID=UTILS.GetMarkID()
local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m\nDepth=%.1f m", self.name, i, point.surfaceType, point.landHeight, point.depth)
trigger.action.markToAll(point.markerID, text, point.vec3, "")
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get 3D points of pathline.
-- @param #PATHLINE self
-- @param DCS#Vec3 Vec Position vector. Can also be a DCS#Vec2 in which case the altitude at landheight is taken.
-- @return #PATHLINE.Point
function PATHLINE:_CreatePoint(Vec)
local point={} --#PATHLINE.Point
if Vec.z then
-- Given vec is 3D
point.vec3=UTILS.DeepCopy(Vec)
point.vec2={x=Vec.x, y=Vec.z}
else
-- Given vec is 2D
point.vec2=UTILS.DeepCopy(Vec)
point.vec3={x=Vec.x, y=land.getHeight(Vec), z=Vec.y}
end
-- Get surface type.
point.surfaceType=land.getSurfaceType(point.vec2)
-- Get land height and depth.
point.landHeight, point.depth=land.getSurfaceHeightWithSeabed(point.vec2)
point.markerID=nil
return point
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -193,21 +193,29 @@ do -- land
--- [Type of surface enumerator](https://wiki.hoggitworld.com/view/DCS_singleton_land) --- [Type of surface enumerator](https://wiki.hoggitworld.com/view/DCS_singleton_land)
-- @type land.SurfaceType -- @type land.SurfaceType
-- @field LAND -- @field LAND Land=1
-- @field SHALLOW_WATER -- @field SHALLOW_WATER Shallow water=2
-- @field WATER -- @field WATER Water=3
-- @field ROAD -- @field ROAD Road=4
-- @field RUNWAY -- @field RUNWAY Runway=5
--- Returns altitude MSL of the point. --- Returns the distance from sea level (y-axis) of a given vec2 point.
-- @function [parent=#land] getHeight -- @function [parent=#land] getHeight
-- @param #Vec2 point point on the ground. -- @param #Vec2 point Point on the ground.
-- @return #Distance -- @return #number Height in meters.
--- Returns the surface height and depth of a point. Useful for checking if the path is deep enough to support a given ship.
-- Both values are positive. When checked over water at sea level the first value is always zero.
-- When checked over water at altitude, for example the reservoir of the Inguri Dam, the first value is the corresponding altitude the water level is at.
-- @function [parent=#land] getSurfaceHeightWithSeabed
-- @param #Vec2 point Position where to check.
-- @return #number Height in meters.
-- @return #number Depth in meters.
--- returns surface type at the given point. --- Returns surface type at the given point.
-- @function [parent=#land] getSurfaceType -- @function [parent=#land] getSurfaceType
-- @param #Vec2 point Point on the land. -- @param #Vec2 point Point on the land.
-- @return #land.SurfaceType -- @return #number Enumerator value from `land.SurfaceType` (LAND=1, SHALLOW_WATER=2, WATER=3, ROAD=4, RUNWAY=5)
--- [DCS Singleton land](https://wiki.hoggitworld.com/view/DCS_singleton_land) --- [DCS Singleton land](https://wiki.hoggitworld.com/view/DCS_singleton_land)
land = {} --#land land = {} --#land

View File

@@ -11,6 +11,7 @@
-- * Dedicated MASH zone -- * Dedicated MASH zone
-- * Some FSM functions to include in your mission scripts -- * Some FSM functions to include in your mission scripts
-- * Limit number of available helos -- * Limit number of available helos
-- * SRS voice output via TTS or soundfiles
-- --
-- === -- ===
-- --
@@ -20,8 +21,8 @@
-- --
-- === -- ===
-- --
-- ### Author: **applevangelist** -- ### Author: **Applevangelist**
-- Last Update April 2022 -- Last Update February 2022
-- --
-- === -- ===
-- @module Functional.AICSAR -- @module Functional.AICSAR
@@ -87,10 +88,11 @@
-- my_aicsar.rescuezoneradius -- landing zone around downed pilot. Defaults to 200m -- my_aicsar.rescuezoneradius -- landing zone around downed pilot. Defaults to 200m
-- my_aicsar.autoonoff -- stop operations when human helicopter pilots are around. Defaults to true. -- my_aicsar.autoonoff -- stop operations when human helicopter pilots are around. Defaults to true.
-- my_aicsar.verbose -- text messages to own coalition about ongoing operations. Defaults to true. -- my_aicsar.verbose -- text messages to own coalition about ongoing operations. Defaults to true.
-- my_aicsarlimithelos -- limit available number of helos going on mission (defaults to true) -- my_aicsar.limithelos -- limit available number of helos going on mission (defaults to true)
-- my_aicsar.helonumber -- number of helos available (default: 3) -- my_aicsar.helonumber -- number of helos available (default: 3)
-- my_aicsar.verbose -- boolean, set to `true`for message output on-screen
-- --
-- ## Radio options -- ## Radio output options
-- --
-- Radio messages, soundfile names and (for SRS) lengths are defined in three enumerators, so you can customize, localize messages and soundfiles to your liking: -- Radio messages, soundfile names and (for SRS) lengths are defined in three enumerators, so you can customize, localize messages and soundfiles to your liking:
-- --
@@ -100,7 +102,7 @@
-- EN = { -- EN = {
-- INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!", -- INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
-- INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!", -- INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
-- PILOTDOWN = "Pilot down at ", -- note that this will be appended with the position -- PILOTDOWN = "Mayday, mayday, mayday! Pilot down at ", -- note that this will be appended with the position in MGRS
-- PILOTKIA = "Pilot KIA!", -- PILOTKIA = "Pilot KIA!",
-- HELODOWN = "CSAR Helo Down!", -- HELODOWN = "CSAR Helo Down!",
-- PILOTRESCUED = "Pilot rescued!", -- PILOTRESCUED = "Pilot rescued!",
@@ -136,8 +138,31 @@
-- }, -- },
-- } -- }
-- --
-- ## Radio output via SRS and Text-To-Speech (TTS)
--
-- Radio output can be done via SRS and Text-To-Speech. No extra sound files required!
-- [Initially, Have a look at the guide on setting up SRS TTS for Moose](https://github.com/FlightControl-Master/MOOSE_GUIDES/blob/master/documents/Moose%20TTS%20Setup%20Guide.pdf).
-- The text from the `AICSAR.Messages` table above is converted on the fly to an .ogg-file, which is then played back via SRS on the selected frequency and mdulation.
-- Hint - the small black window popping up shortly is visible in Single-Player only.
--
-- To set up AICSAR for SRS TTS output, add e.g. the following to your script:
--
-- -- setup for google TTS, radio 243 AM, SRS server port 5002 with a google standard-quality voice (google cloud account required)
-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\google.json")
--
-- -- alternatively for MS Desktop TTS (voices need to be installed locally first!)
-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female")
--
-- -- define a different voice for the downed pilot(s)
-- my_aicsar:SetPilotTTSVoice(MSRS.Voices.Google.Standard.en_AU_Standard_D,"en-AU","male")
--
-- -- define another voice for the operator
-- my_aicsar:SetOperatorTTSVoice(MSRS.Voices.Google.Standard.en_GB_Standard_A,"en-GB","female")
--
-- ## Radio output via preproduced soundfiles
--
-- The easiest way to add a soundfile to your mission is to use the "Sound to..." trigger in the mission editor. This will effectively -- The easiest way to add a soundfile to your mission is to use the "Sound to..." trigger in the mission editor. This will effectively
-- save your sound file inside of the .miz mission file. -- save your sound file inside of the .miz mission file. [Example soundfiles are located on github](https://github.com/FlightControl-Master/MOOSE_SOUND/tree/master/AICSAR)
-- --
-- To customize or localize your texts and sounds, you can take e.g. the following approach to add a German language version: -- To customize or localize your texts and sounds, you can take e.g. the following approach to add a German language version:
-- --
@@ -147,7 +172,7 @@
-- --
-- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so: -- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so:
-- --
-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,5002) -- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,nil,5002)
-- --
-- or -- or
-- --
@@ -161,7 +186,7 @@
-- @field #AICSAR -- @field #AICSAR
AICSAR = { AICSAR = {
ClassName = "AICSAR", ClassName = "AICSAR",
version = "0.0.8", version = "0.1.9",
lid = "", lid = "",
coalition = coalition.side.BLUE, coalition = coalition.side.BLUE,
template = "", template = "",
@@ -173,7 +198,7 @@ AICSAR = {
pilotqueue = {}, pilotqueue = {},
pilotindex = 0, pilotindex = 0,
helos = {}, helos = {},
verbose = true, verbose = false,
rescuezoneradius = 200, rescuezoneradius = 200,
rescued = {}, rescued = {},
autoonoff = true, autoonoff = true,
@@ -194,6 +219,13 @@ AICSAR = {
helonumber = 3, helonumber = 3,
gettext = nil, gettext = nil,
locale ="en", -- default text language locale ="en", -- default text language
SRSTTSRadio = false,
SRSGoogle = false,
SRSQ = nil,
SRSPilot = nil,
SRSPilotVoice = false,
SRSOperator = nil,
SRSOperatorVoice = false,
} }
-- TODO Messages -- TODO Messages
@@ -203,7 +235,7 @@ AICSAR.Messages = {
EN = { EN = {
INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!", INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!", INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
PILOTDOWN = "Pilot down at ", PILOTDOWN = "Mayday, mayday, mayday! Pilot down at ",
PILOTKIA = "Pilot KIA!", PILOTKIA = "Pilot KIA!",
HELODOWN = "CSAR Helo Down!", HELODOWN = "CSAR Helo Down!",
PILOTRESCUED = "Pilot rescued!", PILOTRESCUED = "Pilot rescued!",
@@ -212,7 +244,7 @@ AICSAR.Messages = {
DE = { DE = {
INITIALOK = "Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind!\nEin Hubschrauber sammelt Sie auf!", INITIALOK = "Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind!\nEin Hubschrauber sammelt Sie auf!",
INITIALNOTOK = "Verstehe, Pilot. Sie sind zu weit weg von uns.\nViel Glück!", INITIALNOTOK = "Verstehe, Pilot. Sie sind zu weit weg von uns.\nViel Glück!",
PILOTDOWN = "Pilot abgestürzt: ", PILOTDOWN = "Mayday, mayday, mayday! Pilot abgestürzt: ",
PILOTKIA = "Pilot gefallen!", PILOTKIA = "Pilot gefallen!",
HELODOWN = "CSAR Hubschrauber verloren!", HELODOWN = "CSAR Hubschrauber verloren!",
PILOTRESCUED = "Pilot gerettet!", PILOTRESCUED = "Pilot gerettet!",
@@ -309,6 +341,9 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
-- Radio -- Radio
self.SRS = nil self.SRS = nil
self.SRSRadio = false self.SRSRadio = false
self.SRSTTSRadio = false
self.SRSGoogle = false
self.SRSQ = nil
self.SRSFrequency = 243 self.SRSFrequency = 243
self.SRSPath = "\\" self.SRSPath = "\\"
self.SRSModulation = radio.modulation.AM self.SRSModulation = radio.modulation.AM
@@ -343,6 +378,7 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
self:AddTransition("*", "Status", "*") -- CSAR status update. self:AddTransition("*", "Status", "*") -- CSAR status update.
self:AddTransition("*", "PilotDown", "*") -- Pilot down self:AddTransition("*", "PilotDown", "*") -- Pilot down
self:AddTransition("*", "PilotPickedUp", "*") -- Pilot in helo self:AddTransition("*", "PilotPickedUp", "*") -- Pilot in helo
self:AddTransition("*", "PilotUnloaded", "*") -- Pilot Unloaded from helo
self:AddTransition("*", "PilotRescued", "*") -- Pilot Rescued self:AddTransition("*", "PilotRescued", "*") -- Pilot Rescued
self:AddTransition("*", "PilotKIA", "*") -- Pilot dead self:AddTransition("*", "PilotKIA", "*") -- Pilot dead
self:AddTransition("*", "HeloDown", "*") -- Helo dead self:AddTransition("*", "HeloDown", "*") -- Helo dead
@@ -404,6 +440,15 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
--- On after "PilotUnloaded" event.
-- @function [parent=#AICSAR] OnAfterPilotUnloaded
-- @param #AICSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
-- @param Ops.OpsGroup#OPSGROUP OpsGroup
--- On after "PilotKIA" event. --- On after "PilotKIA" event.
-- @function [parent=#AICSAR] OnAfterPilotKIA -- @function [parent=#AICSAR] OnAfterPilotKIA
-- @param #AICSAR self -- @param #AICSAR self
@@ -453,7 +498,7 @@ function AICSAR:InitLocalization()
return self return self
end end
--- [User] Switch sound output on and use SRS --- [User] Switch sound output on and use SRS output for sound files.
-- @param #AICSAR self -- @param #AICSAR self
-- @param #boolean OnOff Switch on (true) or off (false). -- @param #boolean OnOff Switch on (true) or off (false).
-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" -- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone"
@@ -464,10 +509,12 @@ end
-- @return #AICSAR self -- @return #AICSAR self
function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port) function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port)
self:T(self.lid .. "SetSRSRadio") self:T(self.lid .. "SetSRSRadio")
self:T(self.lid .. "SetSRSRadio to "..tostring(OnOff))
self.SRSRadio = OnOff and true self.SRSRadio = OnOff and true
self.SRSTTSRadio = false
self.SRSFrequency = Frequency or 243 self.SRSFrequency = Frequency or 243
self.SRSPath = Path or "c:\\" self.SRSPath = Path or "c:\\"
self.SRS:SetLabel("ACSR")
self.SRS:SetCoalition(self.coalition)
self.SRSModulation = Modulation or radio.modulation.AM self.SRSModulation = Modulation or radio.modulation.AM
local soundpath = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT" -- defaults to "l10n/DEFAULT/", i.e. add messages by "Sound to..." in the ME local soundpath = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT" -- defaults to "l10n/DEFAULT/", i.e. add messages by "Sound to..." in the ME
self.SRSSoundPath = SoundPath or soundpath self.SRSSoundPath = SoundPath or soundpath
@@ -479,6 +526,88 @@ function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port)
return self return self
end end
--- [User] Switch sound output on and use SRS-TTS output. The voice will be used across all outputs, unless you define an extra voice for downed pilots and/or the operator.
-- See `AICSAR:SetPilotTTSVoice()` and `AICSAR:SetOperatorTTSVoice()`
-- @param #AICSAR self
-- @param #boolean OnOff Switch on (true) or off (false).
-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone"
-- @param #number Frequency (Optional) Defaults to 243 (guard)
-- @param #number Modulation (Optional) Radio modulation. Defaults to radio.modulation.AM
-- @param #number Port (Optional) Port of the SRS, defaults to 5002.
-- @param #string Voice (Optional) The voice to be used.
-- @param #string Culture (Optional) The culture to be used, defaults to "en-GB"
-- @param #string Gender (Optional) The gender to be used, defaults to "male"
-- @param #string GoogleCredentials (Optional) Path to google credentials
-- @return #AICSAR self
function AICSAR:SetSRSTTSRadio(OnOff,Path,Frequency,Modulation,Port,Voice,Culture,Gender,GoogleCredentials)
self:T(self.lid .. "SetSRSTTSRadio")
self.SRSTTSRadio = OnOff and true
self.SRSRadio = false
self.SRSFrequency = Frequency or 243
self.SRSPath = Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.SRSModulation = Modulation or radio.modulation.AM
self.SRSPort = Port or 5002
if OnOff then
self.SRS = MSRS:New(Path,Frequency,Modulation,1)
self.SRS:SetPort(self.SRSPort)
self.SRS:SetCoalition(self.coalition)
self.SRS:SetLabel("ACSR")
self.SRS:SetVoice(Voice)
self.SRS:SetCulture(Culture)
self.SRS:SetGender(Gender)
if GoogleCredentials then
self.SRS:SetGoogle(GoogleCredentials)
self.SRSGoogle = true
end
self.SRSQ = MSRSQUEUE:New(self.alias)
end
return self
end
--- [User] Set SRS TTS Voice of downed pilot. `AICSAR:SetSRSTTSRadio()` needs to be set first!
-- @param #AICSAR self
-- @param #string Voice The voice to be used, e.g. `MSRS.Voices.Google.Standard.en_US_Standard_J` for Google or `MSRS.Voices.Microsoft.David` for Microsoft.
-- Specific voices override culture and gender!
-- @param #string Culture (Optional) The culture to be used, defaults to "en-US"
-- @param #string Gender (Optional) The gender to be used, defaults to "male"
-- @return #AICSAR self
function AICSAR:SetPilotTTSVoice(Voice,Culture,Gender)
self:T(self.lid .. "SetPilotTTSVoice")
self.SRSPilotVoice = true
self.SRSPilot = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation,1)
self.SRSPilot:SetCoalition(self.coalition)
self.SRSPilot:SetVoice(Voice)
self.SRSPilot:SetCulture(Culture or "en-US")
self.SRSPilot:SetGender(Gender or "male")
self.SRSPilot:SetLabel("PILOT")
if self.SRS.google then
self.SRSPilot:SetGoogle(self.SRS.google)
end
return self
end
--- [User] Set SRS TTS Voice of the rescue operator. `AICSAR:SetSRSTTSRadio()` needs to be set first!
-- @param #AICSAR self
-- @param #string Voice The voice to be used, e.g. `MSRS.Voices.Google.Standard.en_US_Standard_J` for Google or `MSRS.Voices.Microsoft.David` for Microsoft.
-- Specific voices override culture and gender!
-- @param #string Culture (Optional) The culture to be used, defaults to "en-GB"
-- @param #string Gender (Optional) The gender to be used, defaults to "female"
-- @return #AICSAR self
function AICSAR:SetOperatorTTSVoice(Voice,Culture,Gender)
self:T(self.lid .. "SetOperatorTTSVoice")
self.SRSOperatorVoice = true
self.SRSOperator = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation,1)
self.SRSOperator:SetCoalition(self.coalition)
self.SRSOperator:SetVoice(Voice)
self.SRSOperator:SetCulture(Culture or "en-GB")
self.SRSOperator:SetGender(Gender or "female")
self.SRSPilot:SetLabel("RESCUE")
if self.SRS.google then
self.SRSOperator:SetGoogle(self.SRS.google)
end
return self
end
--- [User] Switch sound output on and use normale (DCS) radio --- [User] Switch sound output on and use normale (DCS) radio
-- @param #AICSAR self -- @param #AICSAR self
-- @param #boolean OnOff Switch on (true) or off (false). -- @param #boolean OnOff Switch on (true) or off (false).
@@ -545,13 +674,25 @@ function AICSAR:OnEventLandingAfterEjection(EventData)
-- Mayday Message -- Mayday Message
local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale) local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale)
local text = "" local text = ""
local setting = {}
setting.MGRS_Accuracy = self.MGRS_Accuracy
local location = _LandingPos:ToStringMGRS(setting)
local msgtxt = Text..location.."!"
location = string.gsub(location,"MGRS ","")
location = string.gsub(location,"%s+","")
location = string.gsub(location,"([%a%d])","%1;") -- "0 5 1 "
location = string.gsub(location,"0","zero")
location = string.gsub(location,"9","niner")
location = "MGRS;"..location
if self.SRSGoogle then
location = string.format("<say-as interpret-as='characters'>%s</say-as>",location)
end
text = Text .. location .. "!"
local ttstext = Text .. location .. "! Repeat! "..location
if _coalition == self.coalition then if _coalition == self.coalition then
if self.verbose then if self.verbose then
local setting = {} MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition)
setting.MGRS_Accuracy = self.MGRS_Accuracy -- MESSAGE:New(msgtxt,15,"AICSAR"):ToLog()
local location = _LandingPos:ToStringMGRS(setting)
text = Text .. location .. "!"
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end end
if self.SRSRadio then if self.SRSRadio then
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
@@ -559,6 +700,12 @@ function AICSAR:OnEventLandingAfterEjection(EventData)
self.SRS:PlaySoundFile(sound,2) self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text) self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
if self.SRSPilotVoice then
self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1)
else
self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1)
end
end end
end end
@@ -634,6 +781,10 @@ function AICSAR:_InitMission(Pilot,Index)
self:__HeloDown(2,Helo,Index) self:__HeloDown(2,Helo,Index)
end end
local function AICHeloUnloaded(Helo,OpsGroup)
self:__PilotUnloaded(2,Helo,OpsGroup)
end
function helo:OnAfterLoadingDone(From,Event,To) function helo:OnAfterLoadingDone(From,Event,To)
AICPickedUp(helo,helo:GetCargoGroups(),Index) AICPickedUp(helo,helo:GetCargoGroups(),Index)
end end
@@ -642,6 +793,10 @@ function AICSAR:_InitMission(Pilot,Index)
AICHeloDead(helo,Index) AICHeloDead(helo,Index)
end end
function helo:OnAfterUnloaded(From,Event,To,OpsGroupCargo)
AICHeloUnloaded(helo,OpsGroupCargo)
end
self.helos[Index] = helo self.helos[Index] = helo
return self return self
@@ -652,7 +807,7 @@ end
-- @param Wrapper.Group#GROUP Pilot The pilot to be rescued. -- @param Wrapper.Group#GROUP Pilot The pilot to be rescued.
-- @return #boolean outcome -- @return #boolean outcome
function AICSAR:_CheckInMashZone(Pilot) function AICSAR:_CheckInMashZone(Pilot)
self:T(self.lid .. "_CheckQueue") self:T(self.lid .. "_CheckInMashZone")
if Pilot:IsInZone(self.farpzone) then if Pilot:IsInZone(self.farpzone) then
return true return true
else else
@@ -696,8 +851,9 @@ end
--- [Internal] Check pilot queue for next mission --- [Internal] Check pilot queue for next mission
-- @param #AICSAR self -- @param #AICSAR self
-- @param Ops.OpsGroup#OPSGROUP OpsGroup
-- @return #AICSAR self -- @return #AICSAR self
function AICSAR:_CheckQueue() function AICSAR:_CheckQueue(OpsGroup)
self:T(self.lid .. "_CheckQueue") self:T(self.lid .. "_CheckQueue")
for _index, _pilot in pairs(self.pilotqueue) do for _index, _pilot in pairs(self.pilotqueue) do
local classname = _pilot.ClassName and _pilot.ClassName or "NONE" local classname = _pilot.ClassName and _pilot.ClassName or "NONE"
@@ -709,8 +865,12 @@ function AICSAR:_CheckQueue()
local flightgroup = self.helos[_index] -- Ops.FlightGroup#FLIGHTGROUP local flightgroup = self.helos[_index] -- Ops.FlightGroup#FLIGHTGROUP
-- rescued? -- rescued?
if self:_CheckInMashZone(_pilot) then if self:_CheckInMashZone(_pilot) then
self:T("Pilot" .. _pilot.GroupName .. " rescued!") self:T("Pilot" .. _pilot.GroupName .. " rescued!")
_pilot:Destroy(false) if OpsGroup then
OpsGroup:Despawn(10)
else
_pilot:Destroy(true,10)
end
self.pilotqueue[_index] = nil self.pilotqueue[_index] = nil
self.rescued[_index] = true self.rescued[_index] = true
self:__PilotRescued(2) self:__PilotRescued(2)
@@ -767,7 +927,7 @@ end
-- @return #AICSAR self -- @return #AICSAR self
function AICSAR:onafterStatus(From, Event, To) function AICSAR:onafterStatus(From, Event, To)
self:T({From, Event, To}) self:T({From, Event, To})
self:_CheckQueue() --self:_CheckQueue()
self:_CheckHelos() self:_CheckHelos()
self:__Status(30) self:__Status(30)
return self return self
@@ -814,6 +974,12 @@ function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach)
self.SRS:PlaySoundFile(sound,2) self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text) self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
if self.SRSOperatorVoice then
self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1)
else
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
end
end end
else else
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALNOTOK",self.locale) local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALNOTOK",self.locale)
@@ -828,8 +994,15 @@ function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach)
self.SRS:PlaySoundFile(sound,2) self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text) self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
if self.SRSOperatorVoice then
self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1)
else
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
end
end end
end end
self:_CheckQueue()
return self return self
end end
@@ -851,7 +1024,9 @@ function AICSAR:onafterPilotKIA(From, Event, To)
self.SRS:PlaySoundFile(sound,2) self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text) self:DCSRadioBroadcast(Soundfile,Soundlength,text)
end elseif self.SRSTTSRadio then
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
end
return self return self
end end
@@ -875,6 +1050,12 @@ function AICSAR:onafterHeloDown(From, Event, To, Helo, Index)
self.SRS:PlaySoundFile(sound,2) self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text) self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
if self.SRSOperatorVoice then
self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1)
else
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
end
end end
local findex = 0 local findex = 0
local fhname = Helo:GetName() local fhname = Helo:GetName()
@@ -927,10 +1108,26 @@ function AICSAR:onafterPilotRescued(From, Event, To)
self.SRS:PlaySoundFile(sound,2) self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text) self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
end end
return self return self
end end
--- [Internal] onafterPilotUnloaded
-- @param #AICSAR self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
-- @param Ops.OpsGroup#OPSGROUP OpsGroup
-- @return #AICSAR self
function AICSAR:onafterPilotUnloaded(From, Event, To, Helo, OpsGroup)
self:T({From, Event, To})
self:_CheckQueue(OpsGroup)
return self
end
--- [Internal] onafterPilotPickedUp --- [Internal] onafterPilotPickedUp
-- @param #AICSAR self -- @param #AICSAR self
-- @param #string From -- @param #string From
@@ -952,6 +1149,8 @@ function AICSAR:onafterPilotPickedUp(From, Event, To, Helo, CargoTable, Index)
self.SRS:PlaySoundFile(sound,2) self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text) self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
end end
local findex = 0 local findex = 0
local fhname = Helo:GetName() local fhname = Helo:GetName()

View File

@@ -474,7 +474,7 @@ do -- DETECTION_BASE
-- @param #string From The From State string. -- @param #string From The From State string.
-- @param #string Event The Event string. -- @param #string Event The Event string.
-- @param #string To The To State string. -- @param #string To The To State string.
-- @param #table DetectedItem The DetectedItem. -- @param #DetectedItem DetectedItem The DetectedItem data structure.
self:AddTransition( "*", "Stop", "Stopped" ) self:AddTransition( "*", "Stop", "Stopped" )
@@ -2478,14 +2478,14 @@ do -- DETECTION_AREAS
--- DETECTION_AREAS constructor. --- DETECTION_AREAS constructor.
-- @param #DETECTION_AREAS self -- @param #DETECTION_AREAS self
-- @param Core.Set#SET_GROUP DetectionSetGroup The @{Core.Set} of GROUPs in the Forward Air Controller role. -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Core.Set} of GROUPs in the Forward Air Controller role.
-- @param DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. -- @param #number DetectionZoneRange The range in meters within which targets are grouped upon the first detected target. Default 5000m.
-- @return #DETECTION_AREAS -- @return #DETECTION_AREAS
function DETECTION_AREAS:New( DetectionSetGroup, DetectionZoneRange ) function DETECTION_AREAS:New( DetectionSetGroup, DetectionZoneRange )
-- Inherits from DETECTION_BASE -- Inherits from DETECTION_BASE
local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) )
self.DetectionZoneRange = DetectionZoneRange self.DetectionZoneRange = DetectionZoneRange or 5000
self._SmokeDetectedUnits = false self._SmokeDetectedUnits = false
self._FlareDetectedUnits = false self._FlareDetectedUnits = false
@@ -2988,4 +2988,3 @@ do -- DETECTION_AREAS
end end
end end

View File

@@ -34,6 +34,7 @@ __Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' )
__Moose.Include( 'Scripts/Moose/Core/Velocity.lua' ) __Moose.Include( 'Scripts/Moose/Core/Velocity.lua' )
__Moose.Include( 'Scripts/Moose/Core/Zone_Detection.lua' ) __Moose.Include( 'Scripts/Moose/Core/Zone_Detection.lua' )
__Moose.Include( 'Scripts/Moose/Core/Zone.lua' ) __Moose.Include( 'Scripts/Moose/Core/Zone.lua' )
__Moose.Include( 'Scripts/Moose/Core/Pathline.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Client.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Client.lua' )
@@ -47,6 +48,7 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Unit.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Unit.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Weapon.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Weapon.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Net.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' )

View File

@@ -10180,6 +10180,28 @@ function AIRBOSS:_GetSternCoord()
return self.sterncoord return self.sterncoord
end end
--- Get wire from draw argument.
-- @param #AIRBOSS self
-- @param Core.Point#COORDINATE Lcoord Landing position.
-- @return #number Trapped wire (1-4) or 99 if no wire was trapped.
function AIRBOSS:_GetWireFromDrawArg()
local wireArgs={}
wireArgs[1]=141
wireArgs[2]=142
wireArgs[3]=143
wireArgs[4]=144
for wire,drawArg in pairs(wireArgs) do
local value=self.carrier:GetDrawArgumentValue(drawArg)
if math.abs(value)>0.001 then
return wire
end
end
return 99
end
--- Get wire from landing position. --- Get wire from landing position.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param Core.Point#COORDINATE Lcoord Landing position. -- @param Core.Point#COORDINATE Lcoord Landing position.

View File

@@ -96,7 +96,7 @@ PLAYERTASK = {
--- PLAYERTASK class version. --- PLAYERTASK class version.
-- @field #string version -- @field #string version
PLAYERTASK.version="0.1.12" PLAYERTASK.version="0.1.14"
--- Generic task condition. --- Generic task condition.
-- @type PLAYERTASK.Condition -- @type PLAYERTASK.Condition
@@ -3526,6 +3526,23 @@ function PLAYERTASKCONTROLLER:AddAgent(Recce)
return self return self
end end
--- [User] Add agent SET_GROUP to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
-- @param #PLAYERTASKCONTROLLER self
-- @param Core.Set#SET_GROUP RecceSet SET_GROUP of agents.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:AddAgentSet(RecceSet)
self:T(self.lid.."AddAgentSet")
if self.Intel then
local Set = RecceSet:GetAliveSet()
for _,_Recce in pairs(Set) do
self.Intel:AddAgent(_Recce)
end
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
--- [User] Set up detection of STATIC objects. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. --- [User] Set up detection of STATIC objects. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this.
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
-- @param #boolean OnOff Set to `true`for on and `false`for off. -- @param #boolean OnOff Set to `true`for on and `false`for off.

View File

@@ -0,0 +1,298 @@
--- **Wrapper** - DCS net functions.
--
-- Encapsules **multiplayer** environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net)
--
-- ===
--
-- ### Author: **applevangelist**
--
-- ===
--
-- @module Wrapper.Net
-- @image Utils_Profiler.jpg
do
--- The NET class
-- @type NET
-- @field #string ClassName
-- @field #string Version
-- @extends Core.Base#BASE
--- Encapsules multiplayer environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net)
--
-- @field #NET
NET = {
ClassName = "NET",
Version = "0.0.2"
}
--- Instantiate a new NET object.
-- @param #NET self
-- @return #NET self
function NET:New()
-- Inherit base.
local self = BASE:Inherit(self, BASE:New()) -- #NET
return self
end
--- Send chat message.
-- @param #NET self
-- @param #string Message Message to send
-- @param #boolean ToAll (Optional)
-- @return #NET self
function NET:SendChat(Message,ToAll)
if Message then
net.send_chat(Message, ToAll)
end
return self
end
--- Find the PlayerID by name
-- @param #NET self
-- @param #string Name The player name whose ID to find
-- @return #number PlayerID or nil
function NET:GetPlayerIdByName(Name)
local playerList = self:GetPlayerList()
for i=1,#playerList do
local playerName = net.get_name(i)
if playerName == Name then
return playerList[i]
end
end
return nil
end
--- Find the PlayerID from a CLIENT object.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @return #number PlayerID or nil
function NET:GetPlayerIDFromClient(Client)
local name = Client:GetPlayerName()
local id = self:GetPlayerIdByName(name)
return id
end
--- Send chat message to a specific player.
-- @param #NET self
-- @param #string Message The text message
-- @param Wrapper.Client#CLIENT ToClient Client receiving the message
-- @param Wrapper.Client#CLIENT FromClient (Optional) Client sending the message
-- @return #NET self
function NET:SendChatToPlayer(Message, ToClient, FromClient)
local PlayerId = self:GetPlayerIDFromClient(ToClient)
local FromId = self:GetPlayerIDFromClient(FromClient)
if Message and PlayerId and FromId then
net.send_chat_to(Message, tonumber(PlayerId) , tonumber(FromId))
elseif Message and PlayerId then
net.send_chat_to(Message, tonumber(PlayerId))
end
return self
end
--- Load a specific mission.
-- @param #NET self
-- @param #string Path and Mission
-- @return #boolean success
-- @usage
-- mynet:LoadMission(lfs.writeDir() .. 'Missions\\' .. 'MyTotallyAwesomeMission.miz')
function NET:LoadMission(Path)
local outcome = false
if Path then
outcome = net.load_mission(Path)
end
return outcome
end
--- Load next mission. Returns false if at the end of list.
-- @param #NET self
-- @return #boolean success
function NET:LoadNextMission()
local outcome = false
outcome = net.load_next_mission()
return outcome
end
--- Return a table of players currently connected to the server.
-- @param #NET self
-- @return #table PlayerList
function NET:GetPlayerList()
local plist = nil
plist = net.get_player_list()
return plist
end
--- Returns the playerID of the local player. Always returns 1 for server.
-- @param #NET self
-- @return #number ID
function NET:GetMyPlayerID()
return net.get_my_player_id()
end
--- Returns the playerID of the server. Currently always returns 1.
-- @param #NET self
-- @return #number ID
function NET:GetServerID()
return net.get_server_id()
end
--- Return a table of attributes for a given client. If optional attribute is present, only that value is returned.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client.
-- @param #string Attribute (Optional) The attribute to obtain. List see below.
-- @return #table PlayerInfo or nil if it cannot be found
-- @usage
-- Table holds these attributes:
--
-- 'id' : playerID
-- 'name' : player name
-- 'side' : 0 - spectators, 1 - red, 2 - blue
-- 'slot' : slotID of the player or
-- 'ping' : ping of the player in ms
-- 'ipaddr': IP address of the player, SERVER ONLY
-- 'ucid' : Unique Client Identifier, SERVER ONLY
--
function NET:GetPlayerInfo(Client,Attribute)
local PlayerID = self:GetPlayerIDFromClient(Client)
if PlayerID then
return net.get_player_info(tonumber(PlayerID), Attribute)
else
return nil
end
end
--- Kicks a player from the server. Can display a message to the user.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @param #string Message (Optional) The message to send.
-- @return #boolean success
function NET:Kick(Client,Message)
local PlayerID = self:GetPlayerIDFromClient(Client)
if PlayerID and tonumber(PlayerID) ~= 1 then
return net.kick(tonumber(PlayerID), Message)
else
return false
end
end
--- Return a statistic for a given client.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @param #number StatisticID The statistic to obtain
-- @return #number Statistic or nil
-- @usage
-- StatisticIDs are:
--
-- net.PS_PING (0) - ping (in ms)
-- net.PS_CRASH (1) - number of crashes
-- net.PS_CAR (2) - number of destroyed vehicles
-- net.PS_PLANE (3) - ... planes/helicopters
-- net.PS_SHIP (4) - ... ships
-- net.PS_SCORE (5) - total score
-- net.PS_LAND (6) - number of landings
-- net.PS_EJECT (7) - of ejects
--
-- mynet:GetPlayerStatistic(Client,7) -- return number of ejects
function NET:GetPlayerStatistic(Client,StatisticID)
local PlayerID = self:GetPlayerIDFromClient(Client)
local stats = StatisticID or 0
if stats > 7 or stats < 0 then stats = 0 end
if PlayerID then
return net.get_stat(tonumber(PlayerID),stats)
else
return nil
end
end
--- Return the name of a given client. Same a CLIENT:GetPlayerName().
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @return #string Name or nil if not obtainable
function NET:GetName(Client)
local PlayerID = self:GetPlayerIDFromClient(Client)
if PlayerID then
return net.get_name(tonumber(PlayerID))
else
return nil
end
end
--- Returns the SideId and SlotId of a given client.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @return #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue
-- @return #number SlotID
function NET:GetSlot(Client)
local PlayerID = self:GetPlayerIDFromClient(Client)
if PlayerID then
local side,slot = net.get_slot(tonumber(PlayerID))
return side,slot
else
return nil,nil
end
end
--- Force the slot for a specific client.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @param #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue
-- @param #number SlotID Slot number
-- @return #boolean Success
function NET:ForceSlot(Client,SideID,SlotID)
local PlayerID = self:GetPlayerIDFromClient(Client)
if PlayerID then
return net.force_player_slot(tonumber(PlayerID), SideID, SlotID )
else
return false
end
end
--- Force a client back to spectators.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @return #boolean Succes
function NET:ReturnToSpectators(Client)
local outcome = self:ForceSlot(Client,0)
return outcome
end
--- Converts a lua value to a JSON string.
-- @param #string Lua Anything lua
-- @return #table Json
function NET.Lua2Json(Lua)
return net.lua2json(Lua)
end
--- Converts a JSON string to a lua value.
-- @param #string Json Anything JSON
-- @return #table Lua
function NET.Lua2Json(Json)
return net.json2lua(Json)
end
--- Executes a lua string in a given lua environment in the game.
-- @param #NET self
-- @param #string State The state in which to execute - see below.
-- @param #string DoString The lua string to be executed.
-- @return #string Output
-- @usage
-- States are:
-- 'config': the state in which $INSTALL_DIR/Config/main.cfg is executed, as well as $WRITE_DIR/Config/autoexec.cfg - used for configuration settings
-- 'mission': holds current mission
-- 'export': runs $WRITE_DIR/Scripts/Export.lua and the relevant export API
function NET:DoStringIn(State,DoString)
return net.dostring_in(State,DoString)
end
--- Write an "INFO" entry to the DCS log file, with the message Message.
-- @param #NET self
-- @param #string Message The message to be logged.
-- @return #NET self
function NET:Log(Message)
net.log(Message)
return self
end
-------------------------------------------------------------------------------
-- End of NET
-------------------------------------------------------------------------------
end

View File

@@ -967,6 +967,24 @@ function UNIT:GetDamageRelative()
return 1 return 1
end end
--- Returns the current value for an animation argument on the external model of the given object.
-- Each model animation has an id tied to with different values representing different states of the model.
-- Animation arguments can be figured out by opening the respective 3d model in the modelviewer.
-- @param #UNIT self
-- @param #number AnimationArgument Number corresponding to the animated part of the unit.
-- @return #number Value of the animation argument [-1, 1]. If draw argument value is invalid for the unit in question a value of 0 will be returned.
function UNIT:GetDrawArgumentValue(AnimationArgument)
local DCSUnit = self:GetDCSObject()
if DCSUnit then
local value = DCSUnit:getDrawArgumentValue(AnimationArgument or 0)
return value
end
return 0
end
--- Returns the category of the #UNIT from descriptor. Returns one of --- Returns the category of the #UNIT from descriptor. Returns one of
-- --
-- * Unit.Category.AIRPLANE -- * Unit.Category.AIRPLANE

View File

@@ -31,6 +31,7 @@ Core/Goal.lua
Core/Spot.lua Core/Spot.lua
Core/TextAndSound.lua Core/TextAndSound.lua
Core/Condition.lua Core/Condition.lua
Core/Pathline.lua
Wrapper/Object.lua Wrapper/Object.lua
Wrapper/Identifiable.lua Wrapper/Identifiable.lua
@@ -44,6 +45,7 @@ Wrapper/Airbase.lua
Wrapper/Scenery.lua Wrapper/Scenery.lua
Wrapper/Marker.lua Wrapper/Marker.lua
Wrapper/Weapon.lua Wrapper/Weapon.lua
Wrapper/Net.lua
Cargo/Cargo.lua Cargo/Cargo.lua
Cargo/CargoUnit.lua Cargo/CargoUnit.lua