Compare commits

...

63 Commits

Author SHA1 Message Date
Applevangelist
63b3807cf6 Added Init Methods to set Unit Positions on Spawn 2023-03-30 12:22:04 +02:00
Applevangelist
f3b6b27521 Small fix for SET_UNIT if used in capture zone coalition with a polygon zone 2023-03-30 09:24:08 +02:00
Applevangelist
ff656182e8 SET_STATIC - Added GetClosestStatic() 2023-03-28 11:03:00 +02:00
Applevangelist
a85ef6e769 Merge remote-tracking branch 'origin/master' 2023-03-23 09:21:46 +01:00
Applevangelist
0bb8c56ea9 Typo fix 2023-03-23 09:21:42 +01:00
Applevangelist
5f108aec23 Typo fix 2023-03-23 09:21:20 +01:00
Applevangelist
bf6683f993 Merge remote-tracking branch 'origin/master' 2023-03-23 08:47:07 +01:00
Applevangelist
b7a5e8dd85 Add'l check in UNIT:GetLife() 2023-03-23 08:47:04 +01:00
Applevangelist
57294aeaf5 Add'l check in UNIT:GetLife() 2023-03-23 08:45:54 +01:00
Applevangelist
20bfb8b08f #AIRBASE
* Added/changed South Atlantic Map:
  ["Hipico_Flying_Club"] = "Hipico Flying Club",
  ["Aeropuerto_de_Gobernador_Gregores"] = "Aeropuerto de Gobernador Gregores",
  ["Aerodromo_O_Higgins"] = "Aerodromo O'Higgins",
  ["Cullen_Airport"] = "Cullen Airport",
  ["Gull_Point"] = "Gull Point",
2023-03-16 08:44:16 +01:00
UglySkyfire
5585a8992c Drop crates from herc as CTLD_Cargo (#1929)
Adds option to drop crates from a herc via parachute drop as CTLD_Cargo that needs proper unpacking - can be picked up be a helo.
2023-03-15 20:12:30 +01:00
Applevangelist
d92a20050c #CTLD
* Added/completed add/get/set functions for stock
2023-03-10 10:09:08 +01:00
Applevangelist
c9a91d0683 #GROUP
* Fix for GetMaxHeight()
2023-03-09 08:49:07 +01:00
Applevangelist
ae6716ac01 #GROUP
* Callsign translation refactor
2023-03-05 11:03:26 +01:00
Applevangelist
34285b26ae #SPAWN
* clarified docu for SpawnScheduled()
* Added parameter to SpawnScheduled() for a delayed spawn
2023-02-28 08:00:06 +01:00
Thomas
5341e5faee Update Enums.lua 2023-02-26 19:03:33 +01:00
Applevangelist
a6224b484e dcs 2023-02-23 10:33:11 +01:00
Applevangelist
e95c1ad3aa #Fixes from dev 2023-02-23 10:30:59 +01:00
Applevangelist
9cfed56847 #CTLD logic correction for loading 2023-02-21 11:38:40 +01:00
Applevangelist
6c1abddb1e #NET added a couple of functions 2023-02-19 17:16:00 +01:00
Applevangelist
7dc239f506 Added CLIENT:FindByPlayerName(Name)
NET - slot blocker comments in log removed
2023-02-19 12:31:32 +01:00
Applevangelist
3e8413c6b7 #NET - further ado 2023-02-18 15:03:53 +01:00
Applevangelist
ff86bfb91d #NET Event blocker fix 2023-02-17 16:41:38 +01:00
Applevangelist
66494b7b5a NET 2023-02-17 16:23:02 +01:00
Applevangelist
973127aa8c #NET Fixes 2023-02-17 15:42:06 +01:00
Applevangelist
83866c3dd3 #NET Fixes 2023-02-17 15:02:11 +01:00
Applevangelist
4797abc287 #NET Bugfix 2023-02-17 14:32:48 +01:00
Applevangelist
50f73f1be2 #NET extended block to UCID 2023-02-17 13:22:10 +01:00
Applevangelist
2ebbc8f466 #NET - added docu 2023-02-17 08:54:15 +01:00
Frank
1a96b30a45 Merge pull request #1922 from FlightControl-Master/FF/MasterDevel
Airspeed
2023-02-16 17:28:13 +01:00
Frank
3b1b57a33e Update Positionable.lua
- Added GetGroundSpeed
2023-02-16 17:20:53 +01:00
Frank
f85c0320ec Airspeed
**POSITIONABLE**
- Added function `GetAirspeedIndicated` to return IAS
- Added function `GetAirspeedTrue` to return TAS

**UTILS**
- Added function `UTILS.IasToTas` to convert IAS to TAS
- Added function `TasToIas` to convert TAS to IAS.

**POINT**
- Added function `COORDINATE:GetWindVec3`
2023-02-16 17:09:12 +01:00
Applevangelist
bae7edb914 #NET
* Added event management for clients and block/unblock functions
2023-02-16 11:41:45 +01:00
Applevangelist
35322399e9 #SPOT - minor fixes 2023-02-15 12:23:31 +01:00
Applevangelist
5d4c0f3c87 Bugfix 2023-02-15 10:31:52 +01:00
Frank
92c5c32e8c Merge pull request #1921 from FlightControl-Master/FF/MasterDevel
Update Range.lua
2023-02-12 13:03:08 +01:00
Frank
c8780b2d5f Update Range.lua
- Coordinate of bomb target is now always updated (not only for moving targets)
- If the target is not alive any more, the last known position is used for its coordinate
2023-02-12 13:00:19 +01:00
Frank
f11dbfa367 Merge branch 'FF/MasterDevel' 2023-02-12 11:49:13 +01:00
Frank
54fb9deb3e Polygon and line drawings 2023-02-12 11:38:46 +01:00
Thomas
d3c34ef04d Update MarkerOps_Base.lua (#1919) 2023-02-11 22:11:21 +01:00
Applevangelist
3d5470eb22 #NET
* Initial release
2023-02-10 11:00:14 +01:00
Applevangelist
2a3d69d0d5 #DETECTION
* Docu fixes
2023-02-09 16:11:45 +01:00
Frank
9862c32378 Update settings.json 2023-02-08 16:30:44 +01:00
Frank
9f02232589 Update settings.json 2023-02-08 16:29:25 +01:00
Frank
ffc86bf046 Unwanted changes 2023-02-08 16:28:04 +01:00
Frank
e4c8ca34ed Update Pathline.lua 2023-02-08 16:11:47 +01:00
Frank
f02b7036a9 PATHLINE 2023-02-08 12:37:18 +01:00
Frank
6adef47340 PATHLINE
Added new class PATHLINE
2023-02-07 22:47:55 +01:00
Thomas
5b044cc036 Update CSAR.lua (#1917) 2023-02-07 07:28:00 +01:00
Mr.Alien
03ba7524b2 Comment out code for scoring hits (#1911) 2023-02-06 08:15:45 +01:00
Frank
772921c6b8 WEAPON
- Added new class WEAPON
2023-02-04 23:31:55 +01:00
Mr.Alien
d4d305d53b Fix confusing hit and kill messages with incorrect math (#1913) 2023-02-04 23:02:36 +01:00
Mr.Alien
c58918e002 Add missing part of code in previous commit (#1910) 2023-02-04 22:36:16 +01:00
Mr.Alien
dec7854476 Fix issue with getting thread level after a kill (#1908) 2023-02-04 22:01:55 +01:00
Mr.Alien
8ca102f584 Scoring credits a kill to player who hit the same unit before it spawned (#1905) 2023-02-04 21:46:34 +01:00
Applevangelist
4748c66d9c #CTLD
* Added method `CTLD:PreloadCrates(Unit,Cratesname,NumberOfCrates)`
2023-02-04 17:21:16 +01:00
Frank
bf486b9f44 Merge pull request #1901 from MrAlien753/patch-1
fix nil error message in dcs.log
2023-02-03 22:34:01 +01:00
MrAlien753
6ff433ed7a fix nil error message in dcs.log
When instant killing a target in a capture zone nil error message is printed in the dcs.log. This fixes this issue.
2023-02-03 22:14:14 +01:00
Applevangelist
f9aa392c6d #CTLD_HERCULES
* Fixed an issue spawning 90 paratroopers instead of 30
2023-02-02 09:35:53 +01:00
Frank
819912cfd3 Database
- Polygon drawings are adde as polygon zones to DB
2023-01-28 15:39:42 +01:00
Frank
50ee1ae922 Merge branch 'master' into FF/MasterDevel 2023-01-28 11:09:08 +01:00
Frank
c9ccc28251 Merge branch 'master' into FF/MasterDevel 2022-11-19 11:44:46 +01:00
Frank
2157f8e8cd Airboss
Take animation for determining the wire
2022-09-26 23:34:00 +02:00
33 changed files with 4053 additions and 1197 deletions

View File

@@ -88,6 +88,7 @@ DATABASE = {
WAREHOUSES = {}, WAREHOUSES = {},
FLIGHTGROUPS = {}, FLIGHTGROUPS = {},
FLIGHTCONTROLS = {}, FLIGHTCONTROLS = {},
PATHLINES = {},
} }
local _DATABASECoalition = local _DATABASECoalition =
@@ -244,7 +245,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
@@ -267,7 +268,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.
@@ -277,6 +277,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
@@ -370,6 +403,7 @@ 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
@@ -382,14 +416,23 @@ do -- Zones
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}
-- 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. -- Copy points array.
local points=UTILS.DeepCopy(objectData.points) local points=UTILS.DeepCopy(objectData.points)
@@ -403,7 +446,7 @@ do -- Zones
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)
@@ -417,13 +460,91 @@ do -- Zones
-- Add zone. -- Add zone.
self:AddZone(ZoneName, Zone) self:AddZone(ZoneName, Zone)
-- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice)
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 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
end -- zone end -- zone
do -- Zone_Goal do -- Zone_Goal

View File

@@ -305,8 +305,8 @@ EVENTS = {
-- @field Wrapper.Airbase#AIRBASE Place The MOOSE airbase object. -- @field Wrapper.Airbase#AIRBASE Place The MOOSE airbase object.
-- @field #string PlaceName The name of the airbase. -- @field #string PlaceName The name of the airbase.
-- --
-- @field #table weapon The weapon used during the event. -- @field DCS#Weapon weapon The weapon used during the event.
-- @field #table Weapon -- @field DCS#Weapon Weapon The weapon used during the event.
-- @field #string WeaponName Name of the weapon. -- @field #string WeaponName Name of the weapon.
-- @field DCS#Unit WeaponTgtDCSUnit Target DCS unit of the weapon. -- @field DCS#Unit WeaponTgtDCSUnit Target DCS unit of the weapon.
-- --

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,6 +124,7 @@ 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
@@ -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

@@ -1054,28 +1054,55 @@ do -- COORDINATE
return heading return heading
end end
--- Returns the 3D wind direction vector. Note that vector points into the direction the wind in blowing to.
-- @param #COORDINATE self
-- @param #number height (Optional) parameter specifying the height ASL in meters. The minimum height will be always be the land height since the wind is zero below the ground.
-- @param #boolean turbulence (Optional) If `true`, include turbulence.
-- @return DCS#Vec3 Wind 3D vector. Components in m/s.
function COORDINATE:GetWindVec3(height, turbulence)
-- We at 0.1 meters to be sure to be above ground since wind is zero below ground level.
local landheight=self:GetLandHeight()+0.1
local point={x=self.x, y=math.max(height or self.y, landheight), z=self.z}
-- Get wind velocity vector.
local wind = nil --DCS#Vec3
if turbulence then
wind = atmosphere.getWindWithTurbulence(point)
else
wind = atmosphere.getWind(point)
end
return wind
end
--- Returns the wind direction (from) and strength. --- Returns the wind direction (from) and strength.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param height (Optional) parameter specifying the height ASL. The minimum height will be always be the land height since the wind is zero below the ground. -- @param #number height (Optional) parameter specifying the height ASL. The minimum height will be always be the land height since the wind is zero below the ground.
-- @return Direction the wind is blowing from in degrees. -- @param #boolean turbulence If `true`, include turbulence. If `false` or `nil`, wind without turbulence.
-- @return Wind strength in m/s. -- @return #number Direction the wind is blowing from in degrees.
function COORDINATE:GetWind(height) -- @return #number Wind strength in m/s.
local landheight=self:GetLandHeight()+0.1 -- we at 0.1 meters to be sure to be above ground since wind is zero below ground level. function COORDINATE:GetWind(height, turbulence)
local point={x=self.x, y=math.max(height or self.y, landheight), z=self.z}
-- get wind velocity vector -- Get wind velocity vector
local wind = atmosphere.getWind(point) local wind = self:GetWindVec3(height, turbulence)
local direction = math.deg(math.atan2(wind.z, wind.x))
if direction < 0 then -- Calculate the direction of the vector.
direction = 360 + direction local direction=UTILS.VecHdg(wind)
end
-- Convert to direction to from direction -- Invert "to" direction to "from" direction.
if direction > 180 then if direction > 180 then
direction = direction-180 direction = direction-180
else else
direction = direction+180 direction = direction+180
end end
local strength=math.sqrt((wind.x)^2+(wind.z)^2)
-- Return wind direction and strength km/h. -- Wind strength in m/s.
local strength=UTILS.VecNorm(wind) -- math.sqrt((wind.x)^2+(wind.z)^2)
-- Return wind direction and strength.
return direction, strength return direction, strength
end end
@@ -1133,12 +1160,15 @@ do -- COORDINATE
--- Return the 3D distance in meters between the target COORDINATE and the COORDINATE. --- Return the 3D distance in meters between the target COORDINATE and the COORDINATE.
-- @param #COORDINATE self -- @param #COORDINATE self
-- @param #COORDINATE TargetCoordinate The target COORDINATE. -- @param #COORDINATE TargetCoordinate The target COORDINATE. Can also be a DCS#Vec3.
-- @return DCS#Distance Distance The distance in meters. -- @return DCS#Distance Distance The distance in meters.
function COORDINATE:Get3DDistance( TargetCoordinate ) function COORDINATE:Get3DDistance( TargetCoordinate )
local TargetVec3 = TargetCoordinate:GetVec3() --local TargetVec3 = TargetCoordinate:GetVec3()
local TargetVec3 = {x=TargetCoordinate.x, y=TargetCoordinate.y, z=TargetCoordinate.z}
local SourceVec3 = self:GetVec3() local SourceVec3 = self:GetVec3()
return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 --local dist=( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5
local dist=UTILS.VecDist3D(TargetVec3, SourceVec3)
return dist
end end

View File

@@ -903,7 +903,7 @@ end
do -- SET_GROUP do -- SET_GROUP
--- @type SET_GROUP --- @type SET_GROUP #SET_GROUP
-- @extends Core.Set#SET_BASE -- @extends Core.Set#SET_BASE
--- Mission designers can use the @{Core.Set#SET_GROUP} class to build sets of groups belonging to certain: --- Mission designers can use the @{Core.Set#SET_GROUP} class to build sets of groups belonging to certain:
@@ -1900,24 +1900,6 @@ do -- SET_GROUP
return MGroupInclude return MGroupInclude
end end
--- Iterate the SET_GROUP and set for each unit the default cargo bay weight limit.
-- Because within a group, the type of carriers can differ, each cargo bay weight limit is set on @{Wrapper.Unit} level.
-- @param #SET_GROUP self
-- @usage
-- -- Set the default cargo bay weight limits of the carrier units.
-- local MySetGroup = SET_GROUP:New()
-- MySetGroup:SetCargoBayWeightLimit()
function SET_GROUP:SetCargoBayWeightLimit()
local Set = self:GetSet()
for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP
for UnitName, UnitData in pairs( GroupData:GetUnits() ) do
-- local UnitData = UnitData -- Wrapper.Unit#UNIT
UnitData:SetCargoBayWeightLimit()
end
end
end
--- 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. --- 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 #SET_GROUP self
-- @param Core.Point#COORDINATE Coordinate Reference Coordinate from which the closest group is determined. -- @param Core.Point#COORDINATE Coordinate Reference Coordinate from which the closest group is determined.
@@ -1952,6 +1934,23 @@ do -- SET_GROUP
return gmin, dmin return gmin, dmin
end end
--- Iterate the SET_GROUP and set for each unit the default cargo bay weight limit.
-- Because within a group, the type of carriers can differ, each cargo bay weight limit is set on @{Wrapper.Unit} level.
-- @param #SET_GROUP self
-- @usage
-- -- Set the default cargo bay weight limits of the carrier units.
-- local MySetGroup = SET_GROUP:New()
-- MySetGroup:SetCargoBayWeightLimit()
function SET_GROUP:SetCargoBayWeightLimit()
local Set = self:GetSet()
for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP
for UnitName, UnitData in pairs( GroupData:GetUnits() ) do
-- local UnitData = UnitData -- Wrapper.Unit#UNIT
UnitData:SetCargoBayWeightLimit()
end
end
end
end end
do -- SET_UNIT do -- SET_UNIT
@@ -2115,8 +2114,10 @@ do -- SET_UNIT
self:Add( Unit:GetName(), Unit ) self:Add( Unit:GetName(), Unit )
if Unit:IsInstanceOf("UNIT") then
-- Set the default cargo bay limit each time a new unit is added to the set. -- Set the default cargo bay limit each time a new unit is added to the set.
Unit:SetCargoBayWeightLimit() Unit:SetCargoBayWeightLimit()
end
return self return self
end end
@@ -3794,6 +3795,40 @@ do -- SET_STATIC
return TypeReport:Text( Delimiter ) return TypeReport:Text( Delimiter )
end end
--- Get the closest static of the set with respect to a given reference coordinate. Optionally, only statics of given coalitions are considered in the search.
-- @param #SET_STATIC self
-- @param Core.Point#COORDINATE Coordinate Reference Coordinate from which the closest static is determined.
-- @return Wrapper.Static#STATIC The closest static (if any).
-- @return #number Distance in meters to the closest static.
function SET_STATIC:GetClosestStatic(Coordinate, Coalitions)
local Set = self:GetSet()
local dmin=math.huge
local gmin=nil
for GroupID, GroupData in pairs( Set ) do -- For each STATIC in SET_STATIC
local group=GroupData --Wrapper.Static#STATIC
if group and group:IsAlive() and (Coalitions==nil or UTILS.IsAnyInTable(Coalitions, group:GetCoalition())) then
local coord=group:GetCoord()
-- Distance between ref. coordinate and group coordinate.
local d=UTILS.VecDist3D(Coordinate, coord)
if d<dmin then
dmin=d
gmin=group
end
end
end
return gmin, dmin
end
end end
do -- SET_CLIENT do -- SET_CLIENT

View File

@@ -772,10 +772,8 @@ end
-- @usage -- @usage
-- --
-- -- NATO helicopters engaging in the battle field. -- -- NATO helicopters engaging in the battle field.
-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). -- -- UNIT positions of this group will be randomized around the base unit #1 in a circle of 50 to 500 meters.
-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeUnits( true, 500, 50 )
-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 )
-- --
function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )
self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } )
@@ -791,6 +789,46 @@ function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )
return self return self
end end
--- Spawn the UNITs of this group with individual relative positions to unit #1 and individual headings.
-- @param #SPAWN self
-- @param #table Positions Table of positions, needs to one entry per unit in the group(!). The table contains one table each for each unit, with x,y, and optionally z
-- relative positions, and optionally an individual heading.
-- @return #SPAWN
-- @usage
--
-- -- NATO helicopter group of three units engaging in the battle field.
-- local Positions = { [1] = {x = 0, y = 0, heading = 0}, [2] = {x = 50, y = 50, heading = 90}, [3] = {x = -50, y = 50, heading = 180} }
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitSetUnitRelativePositions(Positions)
--
function SPAWN:InitSetUnitRelativePositions(Positions)
self:F({self.SpawnTemplatePrefix, Positions})
self.SpawnUnitsWithRelativePositions = true
self.UnitsRelativePositions = Positions
return self
end
--- Spawn the UNITs of this group with individual absolute positions and individual headings.
-- @param #SPAWN self
-- @param #table Positions Table of positions, needs to one entry per unit in the group(!). The table contains one table each for each unit, with x,y, and optionally z
-- absolute positions, and optionally an individual heading.
-- @return #SPAWN
-- @usage
--
-- -- NATO helicopter group of three units engaging in the battle field.
-- local Positions = { [1] = {x = 0, y = 0, heading = 0}, [2] = {x = 50, y = 50, heading = 90}, [3] = {x = -50, y = 50, heading = 180} }
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitSetUnitAbsolutePositions(Positions)
--
function SPAWN:InitSetUnitAbsolutePositions(Positions)
self:F({self.SpawnTemplatePrefix, Positions})
self.SpawnUnitsWithAbsolutePositions = true
self.UnitsAbsolutePositions = Positions
return self
end
--- This method is rather complicated to understand. But I'll try to explain. --- This method is rather complicated to understand. But I'll try to explain.
-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor,
-- but they will all follow the same Template route and have the same prefix name. -- but they will all follow the same Template route and have the same prefix name.
@@ -1146,7 +1184,8 @@ do -- Delay methods
return self return self
end end
--- Turns the Delay On for the @{Wrapper.Group} when spawning. --- Turns the Delay On for the @{Wrapper.Group} when spawning with @{SpawnScheduled}(). In effect then the 1st group will only be spawned
-- after the number of seconds given in SpawnScheduled as arguments, and not immediately.
-- @param #SPAWN self -- @param #SPAWN self
-- @return #SPAWN The SPAWN object -- @return #SPAWN The SPAWN object
function SPAWN:InitDelayOn() function SPAWN:InitDelayOn()
@@ -1343,6 +1382,37 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
end end
end end
-- Individual relative unit positions + heading
if self.SpawnUnitsWithRelativePositions and self.UnitsRelativePositions then
local BaseX = SpawnTemplate.units[1].x or 0
local BaseY = SpawnTemplate.units[1].y or 0
local BaseZ = SpawnTemplate.units[1].z or 0
for UnitID = 1, #SpawnTemplate.units do
if self.UnitsRelativePositions[UnitID].heading then
SpawnTemplate.units[UnitID].heading = math.rad(self.UnitsRelativePositions[UnitID].heading or 0)
end
SpawnTemplate.units[UnitID].x = BaseX + (self.UnitsRelativePositions[UnitID].x or 0)
SpawnTemplate.units[UnitID].y = BaseY + (self.UnitsRelativePositions[UnitID].y or 0)
if self.UnitsRelativePositions[UnitID].z then
SpawnTemplate.units[UnitID].z = BaseZ + (self.UnitsRelativePositions[UnitID].z or 0)
end
end
end
-- Individual asbolute unit positions + heading
if self.SpawnUnitsWithAbsolutePositions and self.UnitsAbsolutePositions then
for UnitID = 1, #SpawnTemplate.units do
if self.UnitsAbsolutePositions[UnitID].heading then
SpawnTemplate.units[UnitID].heading = math.rad(self.UnitsAbsolutePositions[UnitID].heading or 0)
end
SpawnTemplate.units[UnitID].x = self.UnitsAbsolutePositions[UnitID].x or 0
SpawnTemplate.units[UnitID].y = self.UnitsAbsolutePositions[UnitID].y or 0
if self.UnitsAbsolutePositions[UnitID].z then
SpawnTemplate.units[UnitID].z = self.UnitsAbsolutePositions[UnitID].z or 0
end
end
end
-- Set livery. -- Set livery.
if self.SpawnInitLivery then if self.SpawnInitLivery then
for UnitID = 1, #SpawnTemplate.units do for UnitID = 1, #SpawnTemplate.units do
@@ -1443,6 +1513,8 @@ end
-- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups. -- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups.
-- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn. -- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn.
-- The variation is a number between 0 and 1, representing the % of variation to be applied on the time interval. -- The variation is a number between 0 and 1, representing the % of variation to be applied on the time interval.
-- @param #boolen WithDelay Do not spawn the **first** group immediately, but delay the spawn as per the calculation below.
-- Effectively the same as @{InitDelayOn}().
-- @return #SPAWN self -- @return #SPAWN self
-- @usage -- @usage
-- -- NATO helicopters engaging in the battle field. -- -- NATO helicopters engaging in the battle field.
@@ -1453,12 +1525,15 @@ end
-- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750 -- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750
-- -- Between these two values, a random amount of seconds will be chosen for each new spawn of the helicopters. -- -- Between these two values, a random amount of seconds will be chosen for each new spawn of the helicopters.
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):SpawnScheduled( 600, 0.5 ) -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):SpawnScheduled( 600, 0.5 )
function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation ) function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation, WithDelay )
self:F( { SpawnTime, SpawnTimeVariation } ) self:F( { SpawnTime, SpawnTimeVariation } )
local SpawnTime = SpawnTime or 60
local SpawnTimeVariation = SpawnTimeVariation or 0.5
if SpawnTime ~= nil and SpawnTimeVariation ~= nil then if SpawnTime ~= nil and SpawnTimeVariation ~= nil then
local InitialDelay = 0 local InitialDelay = 0
if self.DelayOnOff == true then if WithDelay or self.DelayOnOff == true then
InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation ) InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation )
end end
self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, InitialDelay, SpawnTime, SpawnTimeVariation ) self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, InitialDelay, SpawnTime, SpawnTimeVariation )

View File

@@ -5,7 +5,7 @@
-- SPOT implements the DCS Spot class functionality, but adds additional luxury to be able to: -- SPOT implements the DCS Spot class functionality, but adds additional luxury to be able to:
-- --
-- * Spot for a defined duration. -- * Spot for a defined duration.
-- * Updates of laer spot position every 0.2 seconds for moving targets. -- * Updates of laser spot position every 0.2 seconds for moving targets.
-- * Wiggle the spot at the target. -- * Wiggle the spot at the target.
-- * Provide a @{Wrapper.Unit} as a target, instead of a point. -- * Provide a @{Wrapper.Unit} as a target, instead of a point.
-- * Implement a status machine, LaseOn, LaseOff. -- * Implement a status machine, LaseOn, LaseOff.
@@ -14,17 +14,7 @@
-- --
-- # Demo Missions -- # Demo Missions
-- --
-- ### [SPOT Demo Missions source code]() -- ### [Demo Missions on GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS)
--
-- ### [SPOT Demo Missions, only for beta testers]()
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ===
--
-- # YouTube Channel
--
-- ### [SPOT YouTube Channel]()
-- --
-- === -- ===
-- --
@@ -50,14 +40,14 @@ do
--- Implements the target spotting or marking functionality, but adds additional luxury to be able to: --- Implements the target spotting or marking functionality, but adds additional luxury to be able to:
-- --
-- * Mark targets for a defined duration. -- * Mark targets for a defined duration.
-- * Updates of laer spot position every 0.2 seconds for moving targets. -- * Updates of laser spot position every 0.25 seconds for moving targets.
-- * Wiggle the spot at the target. -- * Wiggle the spot at the target.
-- * Provide a @{Wrapper.Unit} as a target, instead of a point. -- * Provide a @{Wrapper.Unit} as a target, instead of a point.
-- * Implement a status machine, LaseOn, LaseOff. -- * Implement a status machine, LaseOn, LaseOff.
-- --
-- ## 1. SPOT constructor -- ## 1. SPOT constructor
-- --
-- * @{#SPOT.New}(..\Presentations\SPOT\Dia2.JPG): Creates a new SPOT object. -- * @{#SPOT.New}(): Creates a new SPOT object.
-- --
-- ## 2. SPOT is a FSM -- ## 2. SPOT is a FSM
-- --
@@ -218,6 +208,8 @@ do
self.Recce = Recce self.Recce = Recce
self.RecceName = self.Recce:GetName()
self.LaseScheduler = SCHEDULER:New( self ) self.LaseScheduler = SCHEDULER:New( self )
self:SetEventPriority( 5 ) self:SetEventPriority( 5 )
@@ -243,6 +235,9 @@ do
end end
self.Target = Target self.Target = Target
self.TargetName = Target:GetName()
self.LaserCode = LaserCode self.LaserCode = LaserCode
self.Lasing = true self.Lasing = true
@@ -302,12 +297,18 @@ do
function SPOT:OnEventDead(EventData) function SPOT:OnEventDead(EventData)
self:F( { Dead = EventData.IniDCSUnitName, Target = self.Target } ) self:F( { Dead = EventData.IniDCSUnitName, Target = self.Target } )
if self.Target then if self.Target then
if EventData.IniDCSUnitName == self.Target:GetName() then if EventData.IniDCSUnitName == self.TargetName then
self:F( {"Target dead ", self.Target:GetName() } ) self:F( {"Target dead ", self.TargetName } )
self:Destroyed() self:Destroyed()
self:LaseOff() self:LaseOff()
end end
end end
if self.Recce then
if EventData.IniDCSUnitName == self.RecceName then
self:F( {"Recce dead ", self.RecceName } )
self:LaseOff()
end
end
end end
--- @param #SPOT self --- @param #SPOT self

View File

@@ -555,7 +555,7 @@ function ZONE_BASE:GetZoneMaybe()
end end
end end
-- Returns the Value of the zone with the given PropertyName, or nil if no matching property exists. --- Returns the Value of the zone with the given PropertyName, or nil if no matching property exists.
-- @param #ZONE_BASE self -- @param #ZONE_BASE self
-- @param #string PropertyName The name of a the TriggerZone Property to be retrieved. -- @param #string PropertyName The name of a the TriggerZone Property to be retrieved.
-- @return #string The Value of the TriggerZone Property with the given PropertyName, or nil if absent. -- @return #string The Value of the TriggerZone Property with the given PropertyName, or nil if absent.
@@ -569,7 +569,7 @@ function ZONE_BASE:GetProperty(PropertyName)
return self.Properties[PropertyName] return self.Properties[PropertyName]
end end
-- Returns the zone Properties table. --- Returns the zone Properties table.
-- @param #ZONE_BASE self -- @param #ZONE_BASE self
-- @return #table The Key:Value table of TriggerZone properties of the zone. -- @return #table The Key:Value table of TriggerZone properties of the zone.
function ZONE_BASE:GetAllProperties() function ZONE_BASE:GetAllProperties()
@@ -927,7 +927,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
local ZoneCoord = self:GetCoordinate() local ZoneCoord = self:GetCoordinate()
local ZoneRadius = self:GetRadius() local ZoneRadius = self:GetRadius()
self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) --self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
local SphereSearch = { local SphereSearch = {
id = world.VolumeType.SPHERE, id = world.VolumeType.SPHERE,
@@ -2084,13 +2084,10 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph
return self return self
end end
--- Get the smallest radius encompassing all points of the polygon zone.
--- Get the smallest circular zone encompassing all points points of the polygon zone.
-- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE self
-- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone. -- @return #number Radius of the zone in meters.
-- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered. function ZONE_POLYGON_BASE:GetRadius()
-- @return #ZONE_RADIUS The circular zone.
function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName, DoNotRegisterZone)
local center=self:GetVec2() local center=self:GetVec2()
@@ -2107,6 +2104,20 @@ function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName, DoNotRegisterZone)
end end
return radius
end
--- Get the smallest circular zone encompassing all points of the polygon zone.
-- @param #ZONE_POLYGON_BASE self
-- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone.
-- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered.
-- @return #ZONE_RADIUS The circular zone.
function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName, DoNotRegisterZone)
local center=self:GetVec2()
local radius=self:GetRadius()
local zone=ZONE_RADIUS:New(ZoneName or self.ZoneName, center, radius, DoNotRegisterZone) local zone=ZONE_RADIUS:New(ZoneName or self.ZoneName, center, radius, DoNotRegisterZone)
return zone return zone
@@ -2913,7 +2924,7 @@ do -- ZONE_ELASTIC
--- Add a set of groups. Positions of the group will be considered as polygon vertices when contructing the convex hull. --- Add a set of groups. Positions of the group will be considered as polygon vertices when contructing the convex hull.
-- @param #ZONE_ELASTIC self -- @param #ZONE_ELASTIC self
-- @param Core.Set#SET_GROUP SetGroup Set of groups. -- @param Core.Set#SET_GROUP GroupSet Set of groups.
-- @return #ZONE_ELASTIC self -- @return #ZONE_ELASTIC self
function ZONE_ELASTIC:AddSetGroup(GroupSet) function ZONE_ELASTIC:AddSetGroup(GroupSet)

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 surface type at the given point. --- 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.
-- @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
@@ -427,8 +435,8 @@ do -- Types
--- Vec3 type is a 3D-vector. --- Vec3 type is a 3D-vector.
-- DCS world has 3-dimensional coordinate system. DCS ground is an infinite plain. -- DCS world has 3-dimensional coordinate system. DCS ground is an infinite plain.
-- @type Vec3 -- @type Vec3
-- @field #Distance x is directed to the north -- @field #Distance x is directed to the North
-- @field #Distance z is directed to the east -- @field #Distance z is directed to the East
-- @field #Distance y is directed up -- @field #Distance y is directed up
--- Vec2 is a 2D-vector for the ground plane as a reference plane. --- Vec2 is a 2D-vector for the ground plane as a reference plane.
@@ -679,10 +687,11 @@ do -- Weapon
--- Weapon.Category enum that stores weapon categories. --- Weapon.Category enum that stores weapon categories.
-- @type Weapon.Category -- @type Weapon.Category
-- @field SHELL -- @field #number SHELL Shell.
-- @field MISSILE -- @field #number MISSILE Missile
-- @field ROCKET -- @field #number ROCKET Rocket.
-- @field BOMB -- @field #number BOMB Bomb.
-- @field #number TORPEDO Torpedo.
--- Weapon.GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB). --- Weapon.GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB).
@@ -1001,8 +1010,8 @@ do -- Unit
--- Enum that stores aircraft refueling system types. --- Enum that stores aircraft refueling system types.
-- @type Unit.RefuelingSystem -- @type Unit.RefuelingSystem
-- @field BOOM_AND_RECEPTACLE -- @field BOOM_AND_RECEPTACLE Tanker with a boom.
-- @field PROBE_AND_DROGUE -- @field PROBE_AND_DROGUE Tanker with a probe.
--- Enum that stores sensor types. --- Enum that stores sensor types.
-- @type Unit.SensorType -- @type Unit.SensorType

View File

@@ -103,6 +103,7 @@
-- @field #number coalition The coalition of the arty group. -- @field #number coalition The coalition of the arty group.
-- @field #boolean respawnafterdeath Respawn arty group after all units are dead. -- @field #boolean respawnafterdeath Respawn arty group after all units are dead.
-- @field #number respawndelay Respawn delay in seconds. -- @field #number respawndelay Respawn delay in seconds.
-- @field #number dtTrack Time interval in seconds for weapon tracking.
-- @extends Core.Fsm#FSM_CONTROLLABLE -- @extends Core.Fsm#FSM_CONTROLLABLE
--- Enables mission designers easily to assign targets for artillery units. Since the implementation is based on a Finite State Model (FSM), the mission designer can --- Enables mission designers easily to assign targets for artillery units. Since the implementation is based on a Finite State Model (FSM), the mission designer can
@@ -693,7 +694,7 @@ ARTY.db={
--- Arty script version. --- Arty script version.
-- @field #string version -- @field #string version
ARTY.version="1.2.0" ARTY.version="1.3.0"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -802,6 +803,9 @@ function ARTY:New(group, alias)
self.ismobile=false self.ismobile=false
end end
-- Set track time interval.
self.dtTrack=0.2
-- Set speed to 0.7 of maximum. -- Set speed to 0.7 of maximum.
self.Speed=self.SpeedMax * 0.7 self.Speed=self.SpeedMax * 0.7
@@ -1497,6 +1501,15 @@ function ARTY:SetStatusInterval(interval)
return self return self
end end
--- Set time interval for weapon tracking.
-- @param #ARTY self
-- @param #number interval Time interval in seconds. Default 0.2 seconds.
-- @return self
function ARTY:SetTrackInterval(interval)
self.dtTrack=interval or 0.2
return self
end
--- Set time how it is waited a unit the first shot event happens. If no shot is fired after this time, the task to fire is aborted and the target removed. --- Set time how it is waited a unit the first shot event happens. If no shot is fired after this time, the task to fire is aborted and the target removed.
-- @param #ARTY self -- @param #ARTY self
-- @param #number waittime Time in seconds. Default 300 seconds. -- @param #number waittime Time in seconds. Default 300 seconds.
@@ -2129,6 +2142,95 @@ end
-- Event Handling -- Event Handling
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Function called during tracking of weapon.
-- @param Wrapper.Weapon#WEAPON weapon Weapon object.
-- @param #ARTY self ARTY object.
-- @param #ARTY.Target target Target of the weapon.
function ARTY._FuncTrack(weapon, self, target)
-- Coordinate and distance to target.
local _coord=weapon.coordinate
local _dist=_coord:Get2DDistance(target.coord)
local _destroyweapon=false
-- Debug
self:T3(self.lid..string.format("ARTY %s weapon to target dist = %d m", self.groupname,_dist))
if target.weapontype==ARTY.WeaponType.IlluminationShells then
-- Check if within distace.
if _dist<target.radius then
-- Get random coordinate within certain radius of the target.
local _cr=target.coord:GetRandomCoordinateInRadius(target.radius)
-- Get random altitude over target.
local _alt=_cr:GetLandHeight()+math.random(self.illuMinalt, self.illuMaxalt)
-- Adjust explosion height of coordinate.
local _ci=COORDINATE:New(_cr.x,_alt,_cr.z)
-- Create illumination flare.
_ci:IlluminationBomb(self.illuPower)
-- Destroy actual shell.
_destroyweapon=true
end
elseif target.weapontype==ARTY.WeaponType.SmokeShells then
if _dist<target.radius then
-- Get random coordinate within a certain radius.
local _cr=_coord:GetRandomCoordinateInRadius(_data.target.radius)
-- Fire smoke at this coordinate.
_cr:Smoke(self.smokeColor)
-- Destroy actual shell.
_destroyweapon=true
end
end
if _destroyweapon then
self:T2(self.lid..string.format("ARTY %s destroying shell, stopping timer.", self.groupname))
-- Destroy weapon and stop timer.
weapon:Destroy()
-- No more tracking.
weapon.tracking=false
end
end
--- Function called after impact of weapon.
-- @param Wrapper.Weapon#WEAPON weapon Weapon object.
-- @param #ARTY self ARTY object.
-- @param #ARTY.Target target Target of the weapon.
function ARTY._FuncImpact(weapon, self, target)
-- Debug info.
self:I(self.lid..string.format("ARTY %s weapon NOT ALIVE any more.", self.groupname))
-- Get impact coordinate.
local _impactcoord=weapon:GetImpactCoordinate()
-- Create a "nuclear" explosion and blast at the impact point.
if target.weapontype==ARTY.WeaponType.TacticalNukes then
self:T(self.lid..string.format("ARTY %s triggering nuclear explosion in one second.", self.groupname))
--SCHEDULER:New(nil, ARTY._NuclearBlast, {self,_impactcoord}, 1.0)
self:ScheduleOnce(1.0, ARTY._NuclearBlast, self, _impactcoord)
end
end
--- Eventhandler for shot event. --- Eventhandler for shot event.
-- @param #ARTY self -- @param #ARTY self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
@@ -2162,128 +2264,32 @@ function ARTY:OnEventShot(EventData)
self:T(self.lid..text) self:T(self.lid..text)
MESSAGE:New(text, 5):Clear():ToAllIf(self.report or self.Debug) MESSAGE:New(text, 5):Clear():ToAllIf(self.report or self.Debug)
-- Last known position of the weapon fired.
local _lastpos={x=0, y=0, z=0}
--- Track the position of the weapon if it is supposed to model a tac nuke, illumination or smoke shell.
-- @param #table _weapon
local function _TrackWeapon(_data)
-- When the pcall status returns false the weapon has hit.
local _weaponalive,_currpos = pcall(
function()
return _data.weapon:getPoint()
end)
-- Debug
self:T3(self.lid..string.format("ARTY %s: Weapon still in air: %s", self.groupname, tostring(_weaponalive)))
-- Destroy weapon before impact.
local _destroyweapon=false
if _weaponalive then
-- Update last position.
_lastpos={x=_currpos.x, y=_currpos.y, z=_currpos.z}
-- Coordinate and distance to target.
local _coord=COORDINATE:NewFromVec3(_lastpos)
local _dist=_coord:Get2DDistance(_data.target.coord)
-- Debug
self:T3(self.lid..string.format("ARTY %s weapon to target dist = %d m", self.groupname,_dist))
if _data.target.weapontype==ARTY.WeaponType.IlluminationShells then
-- Check if within distace.
if _dist<_data.target.radius then
-- Get random coordinate within certain radius of the target.
local _cr=_data.target.coord:GetRandomCoordinateInRadius(_data.target.radius)
-- Get random altitude over target.
local _alt=_cr:GetLandHeight()+math.random(self.illuMinalt, self.illuMaxalt)
-- Adjust explosion height of coordinate.
local _ci=COORDINATE:New(_cr.x,_alt,_cr.z)
-- Create illumination flare.
_ci:IlluminationBomb(self.illuPower)
-- Destroy actual shell.
_destroyweapon=true
end
elseif _data.target.weapontype==ARTY.WeaponType.SmokeShells then
if _dist<_data.target.radius then
-- Get random coordinate within a certain radius.
local _cr=_coord:GetRandomCoordinateInRadius(_data.target.radius)
-- Fire smoke at this coordinate.
_cr:Smoke(self.smokeColor)
-- Destroy actual shell.
_destroyweapon=true
end
end
if _destroyweapon then
self:T2(self.lid..string.format("ARTY %s destroying shell, stopping timer.", self.groupname))
-- Destroy weapon and stop timer.
_data.weapon:destroy()
return nil
else
-- TODO: Make dt input parameter.
local dt=0.02
self:T3(self.lid..string.format("ARTY %s tracking weapon again in %.3f seconds", self.groupname, dt))
-- Check again in 0.05 seconds.
return timer.getTime() + dt
end
else
-- Get impact coordinate.
local _impactcoord=COORDINATE:NewFromVec3(_lastpos)
self:I(self.lid..string.format("ARTY %s weapon NOT ALIVE any more.", self.groupname))
-- Create a "nuclear" explosion and blast at the impact point.
if _data.target.weapontype==ARTY.WeaponType.TacticalNukes then
self:T(self.lid..string.format("ARTY %s triggering nuclear explosion in one second.", self.groupname))
SCHEDULER:New(nil, ARTY._NuclearBlast, {self,_impactcoord}, 1.0)
end
-- Stop timer.
return nil
end
end
-- Start track the shell if we want to model a tactical nuke. -- Start track the shell if we want to model a tactical nuke.
local _tracknuke = self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes>0 local _tracknuke = self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes>0
local _trackillu = self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu>0 local _trackillu = self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu>0
local _tracksmoke = self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke>0 local _tracksmoke = self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke>0
if _tracknuke or _trackillu or _tracksmoke then if _tracknuke or _trackillu or _tracksmoke then
-- Debug info.
self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.", self.groupname)) self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.", self.groupname))
local _peter={} -- Create a weapon object.
_peter.weapon=EventData.weapon local weapon=WEAPON:New(EventData.weapon)
_peter.target=UTILS.DeepCopy(self.currentTarget)
timer.scheduleFunction(_TrackWeapon, _peter, timer.getTime() + 2.0) -- Set time step for tracking.
weapon:SetTimeStepTrack(self.dtTrack)
-- Copy target. We need a copy because it might already be overwritten with the next target during flight of weapon.
local target=UTILS.DeepCopy(self.currentTarget)
-- Set callback functions.
weapon:SetFuncTrack(ARTY._FuncTrack, self, target)
weapon:SetFuncImpact(ARTY._FuncImpact, self, target)
-- Start tracking in 2 sec (arty ammo should fly a bit).
weapon:StartTrack(2)
end end
-- Get current ammo. -- Get current ammo.
@@ -3931,9 +3937,10 @@ function ARTY:GetAmmo(display)
return nammo, nshells, nrockets, nmissiles return nammo, nshells, nrockets, nmissiles
end end
for _,unit in pairs(units) do for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
if unit and unit:IsAlive() then if unit then
-- Output. -- Output.
local text=string.format("ARTY group %s - unit %s:\n", self.groupname, unit:GetName()) local text=string.format("ARTY group %s - unit %s:\n", self.groupname, unit: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

@@ -26,6 +26,7 @@
--- FOX class. --- FOX class.
-- @type FOX -- @type FOX
-- @field #string ClassName Name of the class. -- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity level.
-- @field #boolean Debug Debug mode. Messages to all about status. -- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file. -- @field #string lid Class id string for output to DCS log file.
-- @field #table menuadded Table of groups the menu was added for. -- @field #table menuadded Table of groups the menu was added for.
@@ -124,6 +125,7 @@
-- @field #FOX -- @field #FOX
FOX = { FOX = {
ClassName = "FOX", ClassName = "FOX",
verbose = 0,
Debug = false, Debug = false,
lid = nil, lid = nil,
menuadded = {}, menuadded = {},
@@ -168,7 +170,7 @@ FOX = {
--- Missile data table. --- Missile data table.
-- @type FOX.MissileData -- @type FOX.MissileData
-- @field Wrapper.Unit#UNIT weapon Missile weapon unit. -- @field DCS#Weapon weapon Missile weapon object.
-- @field #boolean active If true the missile is active. -- @field #boolean active If true the missile is active.
-- @field #string missileType Type of missile. -- @field #string missileType Type of missile.
-- @field #string missileName Name of missile. -- @field #string missileName Name of missile.
@@ -185,6 +187,8 @@ FOX = {
-- @field #string targetName Name of the target unit or "unknown". -- @field #string targetName Name of the target unit or "unknown".
-- @field #string targetOrig Name of the "original" target, i.e. the one right after launched. -- @field #string targetOrig Name of the "original" target, i.e. the one right after launched.
-- @field #FOX.PlayerData targetPlayer Player that was targeted or nil. -- @field #FOX.PlayerData targetPlayer Player that was targeted or nil.
-- @field Core.Point#COORDINATE missileCoord Missile coordinate during tracking.
-- @field Wrapper.Weapon#WEAPON Weapon Weapon object.
--- Main radio menu on group level. --- Main radio menu on group level.
-- @field #table MenuF10 Root menu table on group level. -- @field #table MenuF10 Root menu table on group level.
@@ -196,7 +200,7 @@ FOX.MenuF10Root=nil
--- FOX class version. --- FOX class version.
-- @field #string version -- @field #string version
FOX.version="0.6.1" FOX.version="0.8.0"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
@@ -500,6 +504,7 @@ function FOX:SetDisableF10Menu()
return self return self
end end
--- Enable F10 menu for all players. --- Enable F10 menu for all players.
-- @param #FOX self -- @param #FOX self
-- @return #FOX self -- @return #FOX self
@@ -510,6 +515,15 @@ function FOX:SetEnableF10Menu()
return self return self
end end
--- Set verbosity level.
-- @param #FOX self
-- @param #number VerbosityLevel Level of output (higher=more). Default 0.
-- @return #FOX self
function FOX:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
--- Set default player setting for missile destruction. --- Set default player setting for missile destruction.
-- @param #FOX self -- @param #FOX self
-- @param #boolean switch If true missiles are destroyed. If false/nil missiles are not destroyed. -- @param #boolean switch If true missiles are destroyed. If false/nil missiles are not destroyed.
@@ -605,7 +619,9 @@ function FOX:onafterStatus(From, Event, To)
local clock=UTILS.SecondsToClock(time) local clock=UTILS.SecondsToClock(time)
-- Status. -- Status.
if self.verbose>=1 then
self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate)) self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate))
end
-- Check missile status. -- Check missile status.
self:_CheckMissileStatus() self:_CheckMissileStatus()
@@ -713,7 +729,9 @@ function FOX:_CheckMissileStatus()
if #self.missiles==0 then if #self.missiles==0 then
text=text.." none" text=text.." none"
end end
if self.verbose>=2 then
self:I(self.lid..text) self:I(self.lid..text)
end
-- Remove inactive missiles. -- Remove inactive missiles.
for i=#self.missiles,1,-1 do for i=#self.missiles,1,-1 do
@@ -743,7 +761,7 @@ function FOX:_IsProtected(targetunit)
if targetgroup then if targetgroup then
local targetname=targetgroup:GetName() local targetname=targetgroup:GetName()
for _,_group in pairs(self.protectedset:GetSetObjects()) do for _,_group in pairs(self.protectedset:GetSet()) do
local group=_group --Wrapper.Group#GROUP local group=_group --Wrapper.Group#GROUP
if group then if group then
@@ -762,101 +780,25 @@ function FOX:_IsProtected(targetunit)
return false return false
end end
--- Missle launch event.
-- @param #FOX self --- Function called from weapon tracking.
-- @param #string From From state. -- @param Wrapper.Weapon#WEAPON weapon Weapon object.
-- @param #string Event Event. -- @param #FOX self FOX object.
-- @param #string To To state.
-- @param #FOX.MissileData missile Fired missile -- @param #FOX.MissileData missile Fired missile
function FOX:onafterMissileLaunch(From, Event, To, missile) function FOX._FuncTrack(weapon, self, missile)
-- Tracking info and init of last bomb position.
local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s", missile.missileType, missile.missileName, tostring(missile.targetName), missile.shooterName)
self:I(FOX.lid..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
-- Loop over players.
for _,_player in pairs(self.players) do
local player=_player --#FOX.PlayerData
-- Player position.
local playerUnit=player.unit
-- Check that player is alive and of the opposite coalition.
if playerUnit and playerUnit:IsAlive() and player.coalition~=missile.shooterCoalition then
-- Player missile distance.
local distance=playerUnit:GetCoordinate():Get3DDistance(missile.shotCoord)
-- Player bearing to missile.
local bearing=playerUnit:GetCoordinate():HeadingTo(missile.shotCoord)
-- Alert that missile has been launched.
if player.launchalert then
-- Alert directly targeted players or players that are within missile max range.
if (missile.targetPlayer and player.unitname==missile.targetPlayer.unitname) or (distance<missile.missileRange) then
-- Inform player.
local text=string.format("Missile launch detected! Distance %.1f NM, bearing %03d°.", UTILS.MetersToNM(distance), bearing)
-- Say notching headings.
self:ScheduleOnce(5, FOX._SayNotchingHeadings, self, player, missile.weapon)
--TODO: ALERT or INFO depending on whether this is a direct target.
--TODO: lauchalertall option.
MESSAGE:New(text, 5, "ALERT"):ToClient(player.client)
end
end
-- Mark coordinate.
if player.marklaunch then
local text=string.format("Missile launch coordinates:\n%s\n%s", missile.shotCoord:ToStringLLDMS(), missile.shotCoord:ToStringBULLS(player.coalition))
missile.shotCoord:MarkToGroup(text, player.group)
end
end
end
-- Init missile position.
local _lastBombPos = {x=0,y=0,z=0}
-- Missile coordinate. -- Missile coordinate.
local missileCoord = nil --Core.Point#COORDINATE local missileCoord= missile.missileCoord:UpdateFromVec3(weapon.vec3) --COORDINATE:NewFromVec3(_lastBombPos)
-- Target unit of the missile.
local target=nil --Wrapper.Unit#UNIT
--- Function monitoring the position of a bomb until impact.
local function trackMissile(_ordnance)
-- When the pcall returns a failure the weapon has hit.
local _status,_bombPos = pcall(
function()
return _ordnance:getPoint()
end)
-- Check if status is not nil. If so, we have a valid point.
if _status then
----------------------------------------------
-- Still in the air. Remember this position --
----------------------------------------------
-- Missile position.
_lastBombPos = {x=_bombPos.x, y=_bombPos.y, z=_bombPos.z}
-- Missile coordinate.
missileCoord=COORDINATE:NewFromVec3(_lastBombPos)
-- Missile velocity in m/s. -- Missile velocity in m/s.
local missileVelocity=UTILS.VecNorm(_ordnance:getVelocity()) local missileVelocity=weapon:GetSpeed() --UTILS.VecNorm(_ordnance:getVelocity())
-- Update missile target if necessary. -- Update missile target if necessary.
self:GetMissileTarget(missile) self:GetMissileTarget(missile)
-- Target unit of the missile.
local target=nil --Wrapper.Unit#UNIT
if missile.targetUnit then if missile.targetUnit then
----------------------------------- -----------------------------------
@@ -947,13 +889,13 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
if unit:GetName()~=missile.shooterName then if unit:GetName()~=missile.shooterName then
-- Player position. -- Player position.
local playerCoord=unit:GetCoordinate() local playerVec3=unit:GetVec3()
-- Distance. -- Distance.
local dist=missileCoord:Get3DDistance(playerCoord) local dist=missileCoord:Get3DDistance(playerVec3)
-- Distance from shooter to player. -- Distance from shooter to player.
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord) local Dshooter2player=missile.shotCoord:Get3DDistance(playerVec3)
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin. -- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if (mindist==nil or dist<mindist) and (Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist) then if (mindist==nil or dist<mindist) and (Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist) then
@@ -977,23 +919,22 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
if target then if target then
-- Target coordinate. -- Target coordinate.
local targetCoord=target:GetCoordinate() local targetVec3=target:GetVec3() --target:GetCoordinate()
-- Distance from missile to target. -- Distance from missile to target.
local distance=missileCoord:Get3DDistance(targetCoord) local distance=missileCoord:Get3DDistance(targetVec3)
-- Distance missile to shooter. -- Distance missile to shooter.
local distShooter=nil local distShooter=nil
if missile.shooterUnit and missile.shooterUnit:IsAlive() then if missile.shooterUnit and missile.shooterUnit:IsAlive() then
distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetCoordinate()) distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetVec3())
end end
-- Debug output. -- Debug output.
if self.Debug then if self.Debug then
local bearing=targetCoord:HeadingTo(missileCoord) local bearing=missileCoord:HeadingTo(targetVec3)
local eta=distance/missileVelocity local eta=distance/missileVelocity
-- Debug distance check. -- Debug distance check.
self:I(self.lid..string.format("Missile %s Target %s: Distance = %.1f m, v=%.1f m/s, bearing=%03d°, ETA=%.1f sec", missile.missileType, target:GetName(), distance, missileVelocity, bearing, eta)) self:I(self.lid..string.format("Missile %s Target %s: Distance = %.1f m, v=%.1f m/s, bearing=%03d°, ETA=%.1f sec", missile.missileType, target:GetName(), distance, missileVelocity, bearing, eta))
end end
@@ -1007,12 +948,12 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
end end
-- If missile is 150 m from target ==> destroy missile if in safe zone. -- If missile is 150 m from target ==> destroy missile if in safe zone.
if destroymissile and self:_CheckCoordSafe(targetCoord) then if destroymissile and self:_CheckCoordSafe(targetVec3) then
-- Destroy missile. -- Destroy missile.
self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m", self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m",
missile.missileType, missile.missileName, missile.shooterName, target:GetName(), tostring(missile.targetPlayer~=nil), distance)) missile.missileType, missile.missileName, missile.shooterName, target:GetName(), tostring(missile.targetPlayer~=nil), distance))
_ordnance:destroy() weapon:Destroy()
-- Missile is not active any more. -- Missile is not active any more.
missile.active=false missile.active=false
@@ -1020,7 +961,6 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
-- Debug smoke. -- Debug smoke.
if self.Debug then if self.Debug then
missileCoord:SmokeRed() missileCoord:SmokeRed()
targetCoord:SmokeGreen()
end end
-- Create event. -- Create event.
@@ -1042,8 +982,8 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
missile.targetPlayer.dead=missile.targetPlayer.dead+1 missile.targetPlayer.dead=missile.targetPlayer.dead+1
end end
-- Terminate timer. -- We could disable the tracking here but then the impact function would not be called.
return nil --weapon.tracking=false
else else
@@ -1066,30 +1006,30 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
dt=self.dt00 --0.01 dt=self.dt00 --0.01
end end
-- Check again in dt seconds. -- Set time step.
return timer.getTime()+dt weapon:SetTimeStepTrack(dt)
end end
else else
-- Destroy missile. -- No current target.
self:T(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.", missile.missileType, missile.missileName, missile.shooterName)) self:T(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.", missile.missileType, missile.missileName, missile.shooterName))
return timer.getTime()+0.1 weapon:SetTimeStepTrack(0.1)
-- No target ==> terminate timer.
--return nil
end end
else end
------------------------------------- --- Callback function on impact or destroy otherwise.
-- Missile does not exist any more -- -- @param Wrapper.Weapon#WEAPON weapon Weapon object.
------------------------------------- -- @param #FOX self FOX object.
-- @param #FOX.MissileData missile Fired missile.
function FOX._FuncImpact(weapon, self, missile)
if target then if missile.targetPlayer then
-- Get human player. -- Get human player.
local player=self:_GetPlayerFromUnit(target) local player=missile.targetPlayer
-- Check for player and distance < 10 km. -- Check for player and distance < 10 km.
if player and player.unit:IsAlive() then -- and missileCoord and player.unit:GetCoordinate():Get3DDistance(missileCoord)<10*1000 then if player and player.unit:IsAlive() then -- and missileCoord and player.unit:GetCoordinate():Get3DDistance(missileCoord)<10*1000 then
@@ -1107,15 +1047,79 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
--Terminate the timer. --Terminate the timer.
self:T(FOX.lid..string.format("Terminating missile track timer.")) self:T(FOX.lid..string.format("Terminating missile track timer."))
return nil weapon.tracking=false
end -- _status check end
--- Missle launch event.
-- @param #FOX self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #FOX.MissileData missile Fired missile
function FOX:onafterMissileLaunch(From, Event, To, missile)
-- Tracking info and init of last bomb position.
local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s", missile.missileType, missile.missileName, tostring(missile.targetName), missile.shooterName)
self:I(FOX.lid..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
-- Loop over players.
for _,_player in pairs(self.players) do
local player=_player --#FOX.PlayerData
-- Player position.
local playerUnit=player.unit
-- Check that player is alive and of the opposite coalition.
if playerUnit and playerUnit:IsAlive() and player.coalition~=missile.shooterCoalition then
-- Player missile distance.
local distance=playerUnit:GetCoordinate():Get3DDistance(missile.shotCoord)
-- Player bearing to missile.
local bearing=playerUnit:GetCoordinate():HeadingTo(missile.shotCoord)
-- Alert that missile has been launched.
if player.launchalert then
-- Alert directly targeted players or players that are within missile max range.
if (missile.targetPlayer and player.unitname==missile.targetPlayer.unitname) or (distance<missile.missileRange) then
-- Inform player.
local text=string.format("Missile launch detected! Distance %.1f NM, bearing %03d°.", UTILS.MetersToNM(distance), bearing)
-- Say notching headings.
self:ScheduleOnce(5, FOX._SayNotchingHeadings, self, player, missile.weapon)
--TODO: ALERT or INFO depending on whether this is a direct target.
--TODO: lauchalertall option.
MESSAGE:New(text, 5, "ALERT"):ToClient(player.client)
end
end
-- Mark coordinate.
if player.marklaunch then
local text=string.format("Missile launch coordinates:\n%s\n%s", missile.shotCoord:ToStringLLDMS(), missile.shotCoord:ToStringBULLS(player.coalition))
missile.shotCoord:MarkToGroup(text, player.group)
end
end
end
-- Set callback function for tracking.
missile.Weapon:SetFuncTrack(FOX._FuncTrack, self, missile)
-- Set callback function for impact.
missile.Weapon:SetFuncImpact(FOX._FuncImpact, self, missile)
end -- end function trackBomb
-- Weapon is not yet "alife" just yet. Start timer with a little delay. -- Weapon is not yet "alife" just yet. Start timer with a little delay.
self:T(FOX.lid..string.format("Tracking of missile starts in 0.0001 seconds.")) self:T(FOX.lid..string.format("Tracking of missile starts in 0.0001 seconds."))
timer.scheduleFunction(trackMissile, missile.weapon, timer.getTime()+0.0001) --timer.scheduleFunction(trackMissile, missile.weapon, timer.getTime()+0.0001)
missile.Weapon:StartTrack(0.0001)
end end
@@ -1247,29 +1251,28 @@ end
function FOX:OnEventShot(EventData) function FOX:OnEventShot(EventData)
self:T2({eventshot=EventData}) self:T2({eventshot=EventData})
if EventData.Weapon==nil then -- Nil checks.
return if EventData.Weapon==nil or EventData.IniDCSUnit==nil or EventData.weapon==nil then
end
if EventData.IniDCSUnit==nil then
return return
end end
-- Create a weapon object.
local weapon=WEAPON:New(EventData.weapon)
-- Weapon data. -- Weapon data.
local _weapon = EventData.WeaponName local _weapon = weapon:GetTypeName()
local _target = EventData.Weapon:getTarget() local _target = EventData.Weapon:getTarget()
local _targetName = "unknown" local _targetName = "unknown"
local _targetUnit = nil --Wrapper.Unit#UNIT local _targetUnit = nil --Wrapper.Unit#UNIT
-- Weapon descriptor. -- Weapon descriptor.
local desc=EventData.Weapon:getDesc() local desc=weapon.desc
self:T2({desc=desc}) self:T2({desc=desc})
-- Weapon category: 0=Shell, 1=Missile, 2=Rocket, 3=BOMB
local weaponcategory=desc.category
-- Missile category: 1=AAM, 2=SAM, 6=OTHER -- Missile category: 1=AAM, 2=SAM, 6=OTHER
local missilecategory=desc.missileCategory local missilecategory=desc.missileCategory
-- Missile range.
local missilerange=nil local missilerange=nil
if missilecategory then if missilecategory then
missilerange=desc.rangeMaxAltMax missilerange=desc.rangeMaxAltMax
@@ -1279,8 +1282,8 @@ function FOX:OnEventShot(EventData)
self:T2(FOX.lid.."EVENT SHOT: FOX") self:T2(FOX.lid.."EVENT SHOT: FOX")
self:T2(FOX.lid..string.format("EVENT SHOT: Ini unit = %s", tostring(EventData.IniUnitName))) self:T2(FOX.lid..string.format("EVENT SHOT: Ini unit = %s", tostring(EventData.IniUnitName)))
self:T2(FOX.lid..string.format("EVENT SHOT: Ini group = %s", tostring(EventData.IniGroupName))) self:T2(FOX.lid..string.format("EVENT SHOT: Ini group = %s", tostring(EventData.IniGroupName)))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon type = %s", tostring(_weapon))) self:T2(FOX.lid..string.format("EVENT SHOT: Weapon type = %s", tostring(weapon:GetTypeName())))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon categ = %s", tostring(weaponcategory))) self:T2(FOX.lid..string.format("EVENT SHOT: Weapon categ = %s", tostring(weapon:GetCategory())))
self:T2(FOX.lid..string.format("EVENT SHOT: Missil categ = %s", tostring(missilecategory))) self:T2(FOX.lid..string.format("EVENT SHOT: Missil categ = %s", tostring(missilecategory)))
self:T2(FOX.lid..string.format("EVENT SHOT: Missil range = %s", tostring(missilerange))) self:T2(FOX.lid..string.format("EVENT SHOT: Missil range = %s", tostring(missilerange)))
@@ -1292,7 +1295,7 @@ function FOX:OnEventShot(EventData)
end end
-- Track missiles of type AAM=1, SAM=2 or OTHER=6 -- Track missiles of type AAM=1, SAM=2 or OTHER=6
local _track = weaponcategory==1 and missilecategory and (missilecategory==1 or missilecategory==2 or missilecategory==6) local _track = weapon:IsMissile() and missilecategory and (missilecategory==1 or missilecategory==2 or missilecategory==6)
-- Only track missiles -- Only track missiles
if _track then if _track then
@@ -1301,6 +1304,7 @@ function FOX:OnEventShot(EventData)
missile.active=true missile.active=true
missile.weapon=EventData.weapon missile.weapon=EventData.weapon
missile.Weapon=weapon
missile.missileType=_weapon missile.missileType=_weapon
missile.missileRange=missilerange missile.missileRange=missilerange
missile.missileName=EventData.weapon:getName() missile.missileName=EventData.weapon:getName()
@@ -1313,6 +1317,7 @@ function FOX:OnEventShot(EventData)
missile.fuseDist=desc.fuseDist missile.fuseDist=desc.fuseDist
missile.explosive=desc.warhead.explosiveMass or desc.warhead.shapedExplosiveMass missile.explosive=desc.warhead.explosiveMass or desc.warhead.shapedExplosiveMass
missile.targetOrig=missile.targetName missile.targetOrig=missile.targetName
missile.missileCoord=COORDINATE:New(0,0,0)
-- Set missile target name, unit and player. -- Set missile target name, unit and player.
self:GetMissileTarget(missile) self:GetMissileTarget(missile)
@@ -1631,7 +1636,7 @@ end
--- Check if a coordinate lies within a safe training zone. --- Check if a coordinate lies within a safe training zone.
-- @param #FOX self -- @param #FOX self
-- @param Core.Point#COORDINATE coord Coordinate to check. -- @param Core.Point#COORDINATE coord Coordinate to check. Can also be a DCS#Vec3.
-- @return #boolean True if safe. -- @return #boolean True if safe.
function FOX:_CheckCoordSafe(coord) function FOX:_CheckCoordSafe(coord)
@@ -1643,7 +1648,9 @@ function FOX:_CheckCoordSafe(coord)
-- Loop over all zones. -- Loop over all zones.
for _,_zone in pairs(self.safezones) do for _,_zone in pairs(self.safezones) do
local zone=_zone --Core.Zone#ZONE local zone=_zone --Core.Zone#ZONE
local inzone=zone:IsCoordinateInZone(coord) local Vec2={x=coord.x, y=coord.z}
local inzone=zone:IsVec2InZone(Vec2)
--local inzone=zone:IsCoordinateInZone(coord)
if inzone then if inzone then
return true return true
end end
@@ -1654,7 +1661,7 @@ end
--- Check if a coordinate lies within a launch zone. --- Check if a coordinate lies within a launch zone.
-- @param #FOX self -- @param #FOX self
-- @param Core.Point#COORDINATE coord Coordinate to check. -- @param Core.Point#COORDINATE coord Coordinate to check. Can also be a DCS#Vec2.
-- @return #boolean True if in launch zone. -- @return #boolean True if in launch zone.
function FOX:_CheckCoordLaunch(coord) function FOX:_CheckCoordLaunch(coord)
@@ -1666,7 +1673,9 @@ function FOX:_CheckCoordLaunch(coord)
-- Loop over all zones. -- Loop over all zones.
for _,_zone in pairs(self.launchzones) do for _,_zone in pairs(self.launchzones) do
local zone=_zone --Core.Zone#ZONE local zone=_zone --Core.Zone#ZONE
local inzone=zone:IsCoordinateInZone(coord) local Vec2={x=coord.x, y=coord.z}
local inzone=zone:IsVec2InZone(Vec2)
--local inzone=zone:IsCoordinateInZone(coord)
if inzone then if inzone then
return true return true
end end

View File

@@ -56,7 +56,7 @@
-- @field #string ClassName Name of the Class. -- @field #string ClassName Name of the Class.
-- @field #boolean Debug If true, debug info is sent as messages on the screen. -- @field #boolean Debug If true, debug info is sent as messages on the screen.
-- @field #boolean verbose Verbosity level. Higher means more output to DCS log file. -- @field #boolean verbose Verbosity level. Higher means more output to DCS log file.
-- @field #string id String id of range for output in DCS log. -- @field #string lid String id of range for output in DCS log.
-- @field #string rangename Name of the range. -- @field #string rangename Name of the range.
-- @field Core.Point#COORDINATE location Coordinate of the range location. -- @field Core.Point#COORDINATE location Coordinate of the range location.
-- @field #number rangeradius Radius of range defining its total size for e.g. smoking bomb impact points and sending radio messages. Default 5 km. -- @field #number rangeradius Radius of range defining its total size for e.g. smoking bomb impact points and sending radio messages. Default 5 km.
@@ -86,7 +86,6 @@
-- @field #number illuminationmaxalt Maximum altitude in meters AGL at which illumination bombs are fired. Default is 1000 m. -- @field #number illuminationmaxalt Maximum altitude in meters AGL at which illumination bombs are fired. Default is 1000 m.
-- @field #number scorebombdistance Distance from closest target up to which bomb hits are counted. Default 1000 m. -- @field #number scorebombdistance Distance from closest target up to which bomb hits are counted. Default 1000 m.
-- @field #number TdelaySmoke Time delay in seconds between impact of bomb and starting the smoke. Default 3 seconds. -- @field #number TdelaySmoke Time delay in seconds between impact of bomb and starting the smoke. Default 3 seconds.
-- @field #boolean eventmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler. Default true.
-- @field #boolean trackbombs If true (default), all bomb types are tracked and impact point to closest bombing target is evaluated. -- @field #boolean trackbombs If true (default), all bomb types are tracked and impact point to closest bombing target is evaluated.
-- @field #boolean trackrockets If true (default), all rocket types are tracked and impact point to closest bombing target is evaluated. -- @field #boolean trackrockets If true (default), all rocket types are tracked and impact point to closest bombing target is evaluated.
-- @field #boolean trackmissiles If true (default), all missile types are tracked and impact point to closest bombing target is evaluated. -- @field #boolean trackmissiles If true (default), all missile types are tracked and impact point to closest bombing target is evaluated.
@@ -102,10 +101,10 @@
-- @field #boolean targetsheet If true, players can save their target sheets. Rangeboss will not work if targetsheets do not save. -- @field #boolean targetsheet If true, players can save their target sheets. Rangeboss will not work if targetsheets do not save.
-- @field #string targetpath Path where to save the target sheets. -- @field #string targetpath Path where to save the target sheets.
-- @field #string targetprefix File prefix for target sheet files. -- @field #string targetprefix File prefix for target sheet files.
-- @field Sound.SRS#MSRS controlmsrs -- @field Sound.SRS#MSRS controlmsrs SRS wrapper for range controller.
-- @field Sound.SRS#MSRSQUEUE controlsrsQ -- @field Sound.SRS#MSRSQUEUE controlsrsQ SRS queue for range controller.
-- @field Sound.SRS#MSRS instructmsrs -- @field Sound.SRS#MSRS instructmsrs SRS wrapper for range instructor.
-- @field Sound.SRS#MSRSQUEUE instructsrsQ -- @field Sound.SRS#MSRSQUEUE instructsrsQ SRS queue for range instructor.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven --- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven
@@ -232,8 +231,8 @@
-- --
-- ## Voice output via SRS -- ## Voice output via SRS
-- --
-- Alternatively, the voice output can be fully done via SRS, **no sound file additions needed**. Set up SRS with @{#RANGE.SetSRS}(). Range control and instructor frequencies and voices can then be -- Alternatively, the voice output can be fully done via SRS, **no sound file additions needed**. Set up SRS with @{#RANGE.SetSRS}().
-- set via @{#RANGE.SetSRSRangeControl}() and @{#RANGE.SetSRSRangeInstructor}() -- Range control and instructor frequencies and voices can then be set via @{#RANGE.SetSRSRangeControl}() and @{#RANGE.SetSRSRangeInstructor}().
-- --
-- # Persistence -- # Persistence
-- --
@@ -343,7 +342,6 @@ RANGE = {
illuminationmaxalt = 1000, illuminationmaxalt = 1000,
scorebombdistance = 1000, scorebombdistance = 1000,
TdelaySmoke = 3.0, TdelaySmoke = 3.0,
eventmoose = true,
trackbombs = true, trackbombs = true,
trackrockets = true, trackrockets = true,
trackmissiles = true, trackmissiles = true,
@@ -360,7 +358,18 @@ RANGE = {
} }
--- Default range parameters. --- Default range parameters.
-- @list Defaults -- @type RANGE.Defaults
-- @param #number goodhitrange Radius for good hits in meters.
-- @param #number strafemaxalt Max altitude in meters for players to enter a strafing pit.
-- @param #number dtBombtrack Timer interval in seconds.
-- @param #number Tmsg Message display time in seconds.
-- @param #number ndisplayresults Number of results to display.
-- @param #number rangeradius Radius of range in meters.
-- @param #number TdelaySmoke Time delay in seconds before smoke is triggered.
-- @param #number boxlength Length of strafe pit box in meters.
-- @param #number boxwidth Width of strafe pit box in meters.
-- @param #number goodpass Number of hits for a good strafing pit pass.
-- @param #number foulline Distance of foul line in meters.
RANGE.Defaults = { RANGE.Defaults = {
goodhitrange = 25, goodhitrange = 25,
strafemaxalt = 914, strafemaxalt = 914,
@@ -377,13 +386,15 @@ RANGE.Defaults = {
--- Target type, i.e. unit, static, or coordinate. --- Target type, i.e. unit, static, or coordinate.
-- @type RANGE.TargetType -- @type RANGE.TargetType
-- @field #string UNIT Target is a unit. -- @field #string UNIT Target is a unitobject.
-- @field #string STATIC Target is a static. -- @field #string STATIC Target is a static object.
-- @field #string COORD Target is a coordinate. -- @field #string COORD Target is a coordinate.
-- @field #string SCENERY Target is a scenery object.
RANGE.TargetType = { RANGE.TargetType = {
UNIT = "Unit", UNIT = "Unit",
STATIC = "Static", STATIC = "Static",
COORD = "Coordinate" COORD = "Coordinate",
SCENERY = "Scenery"
} }
--- Player settings. --- Player settings.
@@ -395,9 +406,11 @@ RANGE.TargetType = {
-- @field #boolean messages Display info messages. -- @field #boolean messages Display info messages.
-- @field Wrapper.Client#CLIENT client Client object of player. -- @field Wrapper.Client#CLIENT client Client object of player.
-- @field #string unitname Name of player aircraft unit. -- @field #string unitname Name of player aircraft unit.
-- @field Wrapper.Unit#UNIT unit Player unit.
-- @field #string playername Name of player. -- @field #string playername Name of player.
-- @field #string airframe Aircraft type name. -- @field #string airframe Aircraft type name.
-- @field #boolean inzone If true, player is inside the range zone. -- @field #boolean inzone If true, player is inside the range zone.
-- @field #boolean targeton Target on.
--- Bomb target data. --- Bomb target data.
-- @type RANGE.BombTarget -- @type RANGE.BombTarget
@@ -578,13 +591,14 @@ RANGE.MenuF10Root = nil
--- Range script version. --- Range script version.
-- @field #string version -- @field #string version
RANGE.version = "2.5.1" RANGE.version = "2.7.0"
-- TODO list: -- TODO list:
-- TODO: Verbosity level for messages. -- TODO: Verbosity level for messages.
-- TODO: Add option for default settings such as smoke off. -- TODO: Add option for default settings such as smoke off.
-- TODO: Add custom weapons, which can be specified by the user. -- TODO: Add custom weapons, which can be specified by the user.
-- TODO: Check if units are still alive. -- TODO: Check if units are still alive.
-- DONE: Scenery as targets.
-- DONE: Add statics for strafe pits. -- DONE: Add statics for strafe pits.
-- DONE: Add missiles. -- DONE: Add missiles.
-- DONE: Convert env.info() to self:T() -- DONE: Convert env.info() to self:T()
@@ -610,11 +624,11 @@ function RANGE:New( RangeName )
self.rangename = RangeName or "Practice Range" self.rangename = RangeName or "Practice Range"
-- Log id. -- Log id.
self.id = string.format( "RANGE %s | ", self.rangename ) self.lid = string.format( "RANGE %s | ", self.rangename )
-- Debug info. -- Debug info.
local text = string.format( "Script version %s - creating new RANGE object %s.", RANGE.version, self.rangename ) local text = string.format( "Script version %s - creating new RANGE object %s.", RANGE.version, self.rangename )
self:I( self.id .. text ) self:I( self.lid .. text )
-- Defaults -- Defaults
self:SetDefaultPlayerSmokeBomb() self:SetDefaultPlayerSmokeBomb()
@@ -802,7 +816,7 @@ function RANGE:onafterStart()
if self.location == nil then if self.location == nil then
local text = string.format( "ERROR! No range location found. Number of strafe targets = %d. Number of bomb targets = %d.", self.nstrafetargets, self.nbombtargets ) local text = string.format( "ERROR! No range location found. Number of strafe targets = %d. Number of bomb targets = %d.", self.nstrafetargets, self.nbombtargets )
self:E( self.id .. text ) self:E( self.lid .. text )
return return
end end
@@ -813,31 +827,20 @@ function RANGE:onafterStart()
-- Starting range. -- Starting range.
local text = string.format( "Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets ) local text = string.format( "Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets )
self:I( self.id .. text ) self:I( self.lid .. text )
-- Event handling. -- Event handling.
if self.eventmoose then
-- Events are handled my MOOSE.
self:T( self.id .. "Events are handled by MOOSE." )
self:HandleEvent( EVENTS.Birth ) self:HandleEvent( EVENTS.Birth )
self:HandleEvent( EVENTS.Hit ) self:HandleEvent( EVENTS.Hit )
self:HandleEvent( EVENTS.Shot ) self:HandleEvent( EVENTS.Shot )
else
-- Events are handled directly by DCS.
self:T( self.id .. "Events are handled directly by DCS." )
world.addEventHandler( self )
end
-- Make bomb target move randomly within the range zone. -- Make bomb target move randomly within the range zone.
for _, _target in pairs( self.bombingTargets ) do for _, _target in pairs( self.bombingTargets ) do
local target=_target --#RANGE.BombTarget
-- Check if it is a static object. -- Check if unit and can move.
-- local _static=self:_CheckStatic(_target.target:GetName()) if target.move and target.type==RANGE.TargetType.UNIT and target.speed > 1 then
local _static = _target.type == RANGE.TargetType.STATIC target.target:PatrolZones( { self.rangezone }, target.speed * 0.75, ENUMS.Formation.Vehicle.OffRoad )
if _target.move and _static == false and _target.speed > 1 then
local unit = _target.target -- Wrapper.Unit#UNIT
_target.target:PatrolZones( { self.rangezone }, _target.speed * 0.75, "Off road" )
end end
end end
@@ -1064,7 +1067,7 @@ end
--- Set smoke color for marking bomb targets. By default bomb targets are marked by red smoke. --- Set smoke color for marking bomb targets. By default bomb targets are marked by red smoke.
-- @param #RANGE self -- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Red. -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default `SMOKECOLOR.Red`.
-- @return #RANGE self -- @return #RANGE self
function RANGE:SetBombTargetSmokeColor( colorid ) function RANGE:SetBombTargetSmokeColor( colorid )
self.BombSmokeColor = colorid or SMOKECOLOR.Red self.BombSmokeColor = colorid or SMOKECOLOR.Red
@@ -1082,7 +1085,7 @@ end
--- Set smoke color for marking strafe targets. By default strafe targets are marked by green smoke. --- Set smoke color for marking strafe targets. By default strafe targets are marked by green smoke.
-- @param #RANGE self -- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.Green. -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default `SMOKECOLOR.Green`.
-- @return #RANGE self -- @return #RANGE self
function RANGE:SetStrafeTargetSmokeColor( colorid ) function RANGE:SetStrafeTargetSmokeColor( colorid )
self.StrafeSmokeColor = colorid or SMOKECOLOR.Green self.StrafeSmokeColor = colorid or SMOKECOLOR.Green
@@ -1091,7 +1094,7 @@ end
--- Set smoke color for marking strafe pit approach boxes. By default strafe pit boxes are marked by white smoke. --- Set smoke color for marking strafe pit approach boxes. By default strafe pit boxes are marked by white smoke.
-- @param #RANGE self -- @param #RANGE self
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default SMOKECOLOR.White. -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default `SMOKECOLOR.White`.
-- @return #RANGE self -- @return #RANGE self
function RANGE:SetStrafePitSmokeColor( colorid ) function RANGE:SetStrafePitSmokeColor( colorid )
self.StrafePitSmokeColor = colorid or SMOKECOLOR.White self.StrafePitSmokeColor = colorid or SMOKECOLOR.White
@@ -1191,13 +1194,14 @@ end
-- @param #RANGE self -- @param #RANGE self
-- @param #string PathToSRS Path to SRS directory. -- @param #string PathToSRS Path to SRS directory.
-- @param #number Port SRS port. Default 5002. -- @param #number Port SRS port. Default 5002.
-- @param #number Coalition Coalition side, e.g. coalition.side.BLUE or coalition.side.RED -- @param #number Coalition Coalition side, e.g. `coalition.side.BLUE` or `coalition.side.RED`. Default `coalition.side.BLUE`.
-- @param #number Frequency Frequency to use, defaults to 256 (same as rangecontrol) -- @param #number Frequency Frequency to use. Default is 256 MHz for range control and 305 MHz for instructor. If given, both control and instructor get this frequency.
-- @param #number Modulation Modulation to use, defaults to radio.modulation.AM -- @param #number Modulation Modulation to use, defaults to radio.modulation.AM
-- @param #number Volume Volume, between 0.0 and 1.0. Defaults to 1.0 -- @param #number Volume Volume, between 0.0 and 1.0. Defaults to 1.0
-- @param #string PathToGoogleKey Path to Google TTS credentials. -- @param #string PathToGoogleKey Path to Google TTS credentials.
-- @return #RANGE self -- @return #RANGE self
function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, PathToGoogleKey) function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, PathToGoogleKey)
if PathToSRS then if PathToSRS then
self.useSRS=true self.useSRS=true
@@ -1215,7 +1219,7 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT") self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
if PathToGoogleKey then if PathToGoogleKey then
self.instructmsrs:SetGoogle(PathToGoogleKey) self.controlmsrs:SetGoogle(PathToGoogleKey)
self.instructmsrs:SetGoogle(PathToGoogleKey) self.instructmsrs:SetGoogle(PathToGoogleKey)
end end
@@ -1304,7 +1308,7 @@ end
-- @return #RANGE self -- @return #RANGE self
function RANGE:SetSoundfilesPath( path ) function RANGE:SetSoundfilesPath( path )
self.soundpath = tostring( path or "Range Soundfiles/" ) self.soundpath = tostring( path or "Range Soundfiles/" )
self:I( self.id .. string.format( "Setting sound files path to %s", self.soundpath ) ) self:I( self.lid .. string.format( "Setting sound files path to %s", self.soundpath ) )
return self return self
end end
@@ -1342,20 +1346,20 @@ function RANGE:AddStrafePit( targetnames, boxlength, boxwidth, heading, inverseh
if _isstatic == true then if _isstatic == true then
-- Add static object. -- Add static object.
self:T( self.id .. string.format( "Adding STATIC object %s as strafe target #%d.", _name, _i ) ) self:T( self.lid .. string.format( "Adding STATIC object %s as strafe target #%d.", _name, _i ) )
unit = STATIC:FindByName( _name, false ) unit = STATIC:FindByName( _name, false )
elseif _isstatic == false then elseif _isstatic == false then
-- Add unit object. -- Add unit object.
self:T( self.id .. string.format( "Adding UNIT object %s as strafe target #%d.", _name, _i ) ) self:T( self.lid .. string.format( "Adding UNIT object %s as strafe target #%d.", _name, _i ) )
unit = UNIT:FindByName( _name ) unit = UNIT:FindByName( _name )
else else
-- Neither unit nor static object with this name could be found. -- Neither unit nor static object with this name could be found.
local text = string.format( "ERROR! Could not find ANY strafe target object with name %s.", _name ) local text = string.format( "ERROR! Could not find ANY strafe target object with name %s.", _name )
self:E( self.id .. text ) self:E( self.lid .. text )
end end
@@ -1374,7 +1378,7 @@ function RANGE:AddStrafePit( targetnames, boxlength, boxwidth, heading, inverseh
-- Check if at least one target could be found. -- Check if at least one target could be found.
if ntargets == 0 then if ntargets == 0 then
local text = string.format( "ERROR! No strafe target could be found when calling RANGE:AddStrafePit() for range %s", self.rangename ) local text = string.format( "ERROR! No strafe target could be found when calling RANGE:AddStrafePit() for range %s", self.rangename )
self:E( self.id .. text ) self:E( self.lid .. text )
return return
end end
@@ -1443,7 +1447,7 @@ function RANGE:AddStrafePit( targetnames, boxlength, boxwidth, heading, inverseh
-- Debug info -- Debug info
local text = string.format( "Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f", _name, ntargets, heading, l, w, goodpass, foulline ) local text = string.format( "Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f", _name, ntargets, heading, l, w, goodpass, foulline )
self:T( self.id .. text ) self:T( self.lid .. text )
return self return self
end end
@@ -1513,14 +1517,14 @@ function RANGE:AddBombingTargets( targetnames, goodhitrange, randommove )
if _isstatic == true then if _isstatic == true then
local _static = STATIC:FindByName( name ) local _static = STATIC:FindByName( name )
self:T2( self.id .. string.format( "Adding static bombing target %s with hit range %d.", name, goodhitrange, false ) ) self:T2( self.lid .. string.format( "Adding static bombing target %s with hit range %d.", name, goodhitrange, false ) )
self:AddBombingTargetUnit( _static, goodhitrange ) self:AddBombingTargetUnit( _static, goodhitrange )
elseif _isstatic == false then elseif _isstatic == false then
local _unit = UNIT:FindByName( name ) local _unit = UNIT:FindByName( name )
self:T2( self.id .. string.format( "Adding unit bombing target %s with hit range %d.", name, goodhitrange, randommove ) ) self:T2( self.lid .. string.format( "Adding unit bombing target %s with hit range %d.", name, goodhitrange, randommove ) )
self:AddBombingTargetUnit( _unit, goodhitrange, randommove ) self:AddBombingTargetUnit( _unit, goodhitrange, randommove )
else else
self:E( self.id .. string.format( "ERROR! Could not find bombing target %s.", name ) ) self:E( self.lid .. string.format( "ERROR! Could not find bombing target %s.", name ) )
end end
end end
@@ -1530,7 +1534,7 @@ end
--- Add a unit or static object as bombing target. --- Add a unit or static object as bombing target.
-- @param #RANGE self -- @param #RANGE self
-- @param Wrapper.Positionable#POSITIONABLE unit Positionable (unit or static) of the strafe target. -- @param Wrapper.Positionable#POSITIONABLE unit Positionable (unit or static) of the bombing target.
-- @param #number goodhitrange Max distance from unit which is considered as a good hit. -- @param #number goodhitrange Max distance from unit which is considered as a good hit.
-- @param #boolean randommove If true, unit will move randomly within the range. Default is false. -- @param #boolean randommove If true, unit will move randomly within the range. Default is false.
-- @return #RANGE self -- @return #RANGE self
@@ -1553,11 +1557,11 @@ function RANGE:AddBombingTargetUnit( unit, goodhitrange, randommove )
-- Debug or error output. -- Debug or error output.
if _isstatic == true then if _isstatic == true then
self:I( self.id .. string.format( "Adding STATIC bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) ) self:I( self.lid .. string.format( "Adding STATIC bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) )
elseif _isstatic == false then elseif _isstatic == false then
self:I( self.id .. string.format( "Adding UNIT bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) ) self:I( self.lid .. string.format( "Adding UNIT bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) )
else else
self:E( self.id .. string.format( "ERROR! No bombing target with name %s could be found. Carefully check all UNIT and STATIC names defined in the mission editor!", name ) ) self:E( self.lid .. string.format( "ERROR! No bombing target with name %s could be found. Carefully check all UNIT and STATIC names defined in the mission editor!", name ) )
end end
-- Get max speed of unit in km/h. -- Get max speed of unit in km/h.
@@ -1608,6 +1612,42 @@ function RANGE:AddBombingTargetCoordinate( coord, name, goodhitrange )
return self return self
end end
--- Add a scenery object as bombing target.
-- @param #RANGE self
-- @param Wrapper.Scenery#SCENERY scenery Scenary object.
-- @param #number goodhitrange Max distance from unit which is considered as a good hit.
-- @return #RANGE self
function RANGE:AddBombingTargetScenery( scenery, goodhitrange)
-- Get name of positionable.
local name = scenery:GetName()
-- Default range is 25 m.
goodhitrange = goodhitrange or RANGE.Defaults.goodhitrange
-- Debug or error output.
if name then
self:I( self.lid .. string.format( "Adding SCENERY bombing target %s with good hit range %d", name, goodhitrange) )
else
self:E( self.lid .. string.format( "ERROR! No bombing target with name %s could be found!", name ) )
end
local target = {} -- #RANGE.BombTarget
target.name = name
target.target = scenery
target.goodhitrange = goodhitrange
target.move = false
target.speed = 0
target.coordinate = scenery:GetCoordinate()
target.type = RANGE.TargetType.SCENERY
-- Insert target to table.
table.insert( self.bombingTargets, target )
return self
end
--- Add all units of a group as bombing targets. --- Add all units of a group as bombing targets.
-- @param #RANGE self -- @param #RANGE self
-- @param Wrapper.Group#GROUP group Group of bombing targets. -- @param Wrapper.Group#GROUP group Group of bombing targets.
@@ -1650,7 +1690,7 @@ function RANGE:GetFoullineDistance( namepit, namefoulline )
elseif _staticpit == false then elseif _staticpit == false then
pit = UNIT:FindByName( namepit ) pit = UNIT:FindByName( namepit )
else else
self:E( self.id .. string.format( "ERROR! Pit object %s could not be found in GetFoullineDistance function. Check the name in the ME.", namepit ) ) self:E( self.lid .. string.format( "ERROR! Pit object %s could not be found in GetFoullineDistance function. Check the name in the ME.", namepit ) )
end end
-- Get the unit or static foul line object. -- Get the unit or static foul line object.
@@ -1660,7 +1700,7 @@ function RANGE:GetFoullineDistance( namepit, namefoulline )
elseif _staticfoul == false then elseif _staticfoul == false then
foul = UNIT:FindByName( namefoulline ) foul = UNIT:FindByName( namefoulline )
else else
self:E( self.id .. string.format( "ERROR! Foul line object %s could not be found in GetFoullineDistance function. Check the name in the ME.", namefoulline ) ) self:E( self.lid .. string.format( "ERROR! Foul line object %s could not be found in GetFoullineDistance function. Check the name in the ME.", namefoulline ) )
end end
-- Get the distance between the two objects. -- Get the distance between the two objects.
@@ -1668,10 +1708,10 @@ function RANGE:GetFoullineDistance( namepit, namefoulline )
if pit ~= nil and foul ~= nil then if pit ~= nil and foul ~= nil then
fouldist = pit:GetCoordinate():Get2DDistance( foul:GetCoordinate() ) fouldist = pit:GetCoordinate():Get2DDistance( foul:GetCoordinate() )
else else
self:E( self.id .. string.format( "ERROR! Foul line distance could not be determined. Check pit object name %s and foul line object name %s in the ME.", namepit, namefoulline ) ) self:E( self.lid .. string.format( "ERROR! Foul line distance could not be determined. Check pit object name %s and foul line object name %s in the ME.", namepit, namefoulline ) )
end end
self:T( self.id .. string.format( "Foul line distance = %.1f m.", fouldist ) ) self:T( self.lid .. string.format( "Foul line distance = %.1f m.", fouldist ) )
return fouldist return fouldist
end end
@@ -1679,73 +1719,6 @@ end
-- Event Handling -- Event Handling
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- General event handler.
-- @param #RANGE self
-- @param #table Event DCS event table.
function RANGE:onEvent( Event )
self:F3( Event )
if Event == nil or Event.initiator == nil then
self:T3( "Skipping onEvent. Event or Event.initiator unknown." )
return true
end
if Unit.getByName( Event.initiator:getName() ) == nil then
self:T3( "Skipping onEvent. Initiator unit name unknown." )
return true
end
local DCSiniunit = Event.initiator
local DCStgtunit = Event.target
local DCSweapon = Event.weapon
local EventData = {}
local _playerunit = nil
local _playername = nil
if Event.initiator then
EventData.IniUnitName = Event.initiator:getName()
EventData.IniDCSGroup = Event.initiator:getGroup()
EventData.IniGroupName = Event.initiator:getGroup():getName()
-- Get player unit and name. This returns nil,nil if the event was not fired by a player unit. And these are the only events we are interested in.
_playerunit, _playername = self:_GetPlayerUnitAndName( EventData.IniUnitName )
end
if Event.target then
EventData.TgtUnitName = Event.target:getName()
EventData.TgtUnit = UNIT:FindByName( EventData.TgtUnitName )
end
if Event.weapon then
EventData.Weapon = Event.weapon
EventData.weapon = Event.weapon
EventData.WeaponTypeName = Event.weapon:getTypeName()
end
-- Event info.
self:T3( self.id .. string.format( "EVENT: Event in onEvent with ID = %s", tostring( Event.id ) ) )
self:T3( self.id .. string.format( "EVENT: Ini unit = %s", tostring( EventData.IniUnitName ) ) )
self:T3( self.id .. string.format( "EVENT: Ini group = %s", tostring( EventData.IniGroupName ) ) )
self:T3( self.id .. string.format( "EVENT: Ini player = %s", tostring( _playername ) ) )
self:T3( self.id .. string.format( "EVENT: Tgt unit = %s", tostring( EventData.TgtUnitName ) ) )
self:T3( self.id .. string.format( "EVENT: Wpn type = %s", tostring( EventData.WeaponTypeName ) ) )
-- Call event Birth function.
if Event.id == world.event.S_EVENT_BIRTH and _playername then
self:OnEventBirth( EventData )
end
-- Call event Shot function.
if Event.id == world.event.S_EVENT_SHOT and _playername and Event.weapon then
self:OnEventShot( EventData )
end
-- Call event Hit function.
if Event.id == world.event.S_EVENT_HIT and _playername and DCStgtunit then
self:OnEventHit( EventData )
end
end
--- Range event handler for event birth. --- Range event handler for event birth.
-- @param #RANGE self -- @param #RANGE self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
@@ -1755,9 +1728,9 @@ function RANGE:OnEventBirth( EventData )
local _unitName = EventData.IniUnitName local _unitName = EventData.IniUnitName
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
self:T3( self.id .. "BIRTH: unit = " .. tostring( EventData.IniUnitName ) ) self:T3( self.lid .. "BIRTH: unit = " .. tostring( EventData.IniUnitName ) )
self:T3( self.id .. "BIRTH: group = " .. tostring( EventData.IniGroupName ) ) self:T3( self.lid .. "BIRTH: group = " .. tostring( EventData.IniGroupName ) )
self:T3( self.id .. "BIRTH: player = " .. tostring( _playername ) ) self:T3( self.lid .. "BIRTH: player = " .. tostring( _playername ) )
if _unit and _playername then if _unit and _playername then
@@ -1768,7 +1741,7 @@ function RANGE:OnEventBirth( EventData )
-- Debug output. -- Debug output.
local text = string.format( "Player %s, callsign %s entered unit %s (UID %d) of group %s (GID %d)", _playername, _callsign, _unitName, _uid, _group:GetName(), _gid ) local text = string.format( "Player %s, callsign %s entered unit %s (UID %d) of group %s (GID %d)", _playername, _callsign, _unitName, _uid, _group:GetName(), _gid )
self:T( self.id .. text ) self:T( self.lid .. text )
-- Reset current strafe status. -- Reset current strafe status.
self.strafeStatus[_uid] = nil self.strafeStatus[_uid] = nil
@@ -1786,6 +1759,7 @@ function RANGE:OnEventBirth( EventData )
self.PlayerSettings[_playername].messages = true self.PlayerSettings[_playername].messages = true
self.PlayerSettings[_playername].client = CLIENT:FindByName( _unitName, nil, true ) self.PlayerSettings[_playername].client = CLIENT:FindByName( _unitName, nil, true )
self.PlayerSettings[_playername].unitname = _unitName self.PlayerSettings[_playername].unitname = _unitName
self.PlayerSettings[_playername].unit = _unit
self.PlayerSettings[_playername].playername = _playername self.PlayerSettings[_playername].playername = _playername
self.PlayerSettings[_playername].airframe = EventData.IniUnit:GetTypeName() self.PlayerSettings[_playername].airframe = EventData.IniUnit:GetTypeName()
self.PlayerSettings[_playername].inzone = false self.PlayerSettings[_playername].inzone = false
@@ -1806,9 +1780,9 @@ function RANGE:OnEventHit( EventData )
self:F( { eventhit = EventData } ) self:F( { eventhit = EventData } )
-- Debug info. -- Debug info.
self:T3( self.id .. "HIT: Ini unit = " .. tostring( EventData.IniUnitName ) ) self:T3( self.lid .. "HIT: Ini unit = " .. tostring( EventData.IniUnitName ) )
self:T3( self.id .. "HIT: Ini group = " .. tostring( EventData.IniGroupName ) ) self:T3( self.lid .. "HIT: Ini group = " .. tostring( EventData.IniGroupName ) )
self:T3( self.id .. "HIT: Tgt target = " .. tostring( EventData.TgtUnitName ) ) self:T3( self.lid .. "HIT: Tgt target = " .. tostring( EventData.TgtUnitName ) )
-- Player info -- Player info
local _unitName = EventData.IniUnitName local _unitName = EventData.IniUnitName
@@ -1861,7 +1835,7 @@ function RANGE:OnEventHit( EventData )
self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2) self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2)
end end
self:_DisplayMessageToGroup( _unit, text ) self:_DisplayMessageToGroup( _unit, text )
self:T2( self.id .. text ) self:T2( self.lid .. text )
_currentTarget.pastfoulline = true _currentTarget.pastfoulline = true
end end
end end
@@ -1894,115 +1868,14 @@ function RANGE:OnEventHit( EventData )
end end
end end
--- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun). --- Function called on impact of a tracked weapon.
-- @param #RANGE self -- @param Wrapper.Weapon#WEAPON weapon The weapon object.
-- @param #table weapon Weapon -- @param #RANGE self RANGE object.
function RANGE:_TrackWeapon(weapon) -- @param #RANGE.PlayerData playerData Player data table.
-- @param #number attackHdg Attack heading.
end -- @param #number attackAlt Attack altitude.
-- @param #number attackVel Attack velocity.
--- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun). function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackVel)
-- @param #RANGE self
-- @param Core.Event#EVENTDATA EventData
function RANGE:OnEventShot( EventData )
self:F( { eventshot = EventData } )
-- Nil checks.
if EventData.Weapon == nil then
return
end
if EventData.IniDCSUnit == nil then
return
end
if EventData.IniPlayerName == nil then
return
end
-- Weapon data.
local _weapon = EventData.Weapon:getTypeName() -- should be the same as Event.WeaponTypeName
local _weaponStrArray = UTILS.Split( _weapon, "%." )
local _weaponName = _weaponStrArray[#_weaponStrArray]
-- Weapon descriptor.
local desc = EventData.Weapon:getDesc()
-- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X)
local weaponcategory = desc.category
-- Debug info.
self:T( self.id .. "EVENT SHOT: Range " .. self.rangename )
self:T( self.id .. "EVENT SHOT: Ini unit = " .. EventData.IniUnitName )
self:T( self.id .. "EVENT SHOT: Ini group = " .. EventData.IniGroupName )
self:T( self.id .. "EVENT SHOT: Weapon type = " .. _weapon )
self:T( self.id .. "EVENT SHOT: Weapon name = " .. _weaponName )
self:T( self.id .. "EVENT SHOT: Weapon cate = " .. weaponcategory )
-- Tracking conditions for bombs, rockets and missiles.
local _bombs = weaponcategory == Weapon.Category.BOMB -- string.match(_weapon, "weapons.bombs")
local _rockets = weaponcategory == Weapon.Category.ROCKET -- string.match(_weapon, "weapons.nurs")
local _missiles = weaponcategory == Weapon.Category.MISSILE -- string.match(_weapon, "weapons.missiles") or _viggen
-- Check if any condition applies here.
local _track = (_bombs and self.trackbombs) or (_rockets and self.trackrockets) or (_missiles and self.trackmissiles)
-- Get unit name.
local _unitName = EventData.IniUnitName
-- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
-- Attack parameters.
local attackHdg=_unit:GetHeading()
local attackAlt=_unit:GetHeight()
local attackVel=_unit:GetVelocityKNOTS()
-- Set this to larger value than the threshold.
local dPR = self.BombtrackThreshold * 2
-- Distance player to range.
if _unit and _playername then
dPR = _unit:GetCoordinate():Get2DDistance( self.location )
self:T( self.id .. string.format( "Range %s, player %s, player-range distance = %d km.", self.rangename, _playername, dPR / 1000 ) )
end
-- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons.
if _track and dPR <= self.BombtrackThreshold and _unit and _playername then
-- Player data.
local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData
-- Tracking info and init of last bomb position.
self:T( self.id .. string.format( "RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName() ) )
-- Init bomb position.
local _lastBombPos = { x = 0, y = 0, z = 0 } -- DCS#Vec3
-- Function monitoring the position of a bomb until impact.
local function trackBomb( _ordnance )
-- When the pcall returns a failure the weapon has hit.
local _status, _bombPos = pcall( function()
return _ordnance:getPoint()
end )
self:T2( self.id .. string.format( "Range %s: Bomb still in air: %s", self.rangename, tostring( _status ) ) )
if _status then
----------------------------
-- Weapon is still in air --
----------------------------
-- Remember this position.
_lastBombPos = { x = _bombPos.x, y = _bombPos.y, z = _bombPos.z }
-- Check again in ~0.005 seconds ==> 200 checks per second.
return timer.getTime() + self.dtBombtrack
else
-----------------------------
-- Bomb did hit the ground --
-----------------------------
-- Get closet target to last position. -- Get closet target to last position.
local _closetTarget = nil -- #RANGE.BombTarget local _closetTarget = nil -- #RANGE.BombTarget
@@ -2011,18 +1884,18 @@ function RANGE:OnEventShot( EventData )
local _hitquality = "POOR" local _hitquality = "POOR"
-- Get callsign. -- Get callsign.
local _callsign = self:_myname( _unitName ) local _callsign = self:_myname( playerData.unitname )
local _playername=playerData.playername
local _unit=playerData.unit
-- Coordinate of impact point. -- Coordinate of impact point.
local impactcoord = COORDINATE:NewFromVec3( _lastBombPos ) local impactcoord = weapon:GetImpactCoordinate()
-- Check if impact happened in range zone. -- Check if impact happened in range zone.
local insidezone = self.rangezone:IsCoordinateInZone( impactcoord ) local insidezone = self.rangezone:IsCoordinateInZone( impactcoord )
-- Impact point of bomb.
if self.Debug then
impactcoord:MarkToAll( "Bomb impact point" )
end
-- Smoke impact point of bomb. -- Smoke impact point of bomb.
if playerData.smokebombimpact and insidezone then if playerData.smokebombimpact and insidezone then
@@ -2081,7 +1954,7 @@ function RANGE:OnEventShot( EventData )
result.name = _closetTarget.name or "unknown" result.name = _closetTarget.name or "unknown"
result.distance = _distance result.distance = _distance
result.radial = _closeCoord:HeadingTo( impactcoord ) result.radial = _closeCoord:HeadingTo( impactcoord )
result.weapon = _weaponName or "unknown" result.weapon = weapon:GetTypeName() or "unknown"
result.quality = _hitquality result.quality = _hitquality
result.player = playerData.playername result.player = playerData.playername
result.time = timer.getAbsTime() result.time = timer.getAbsTime()
@@ -2096,6 +1969,7 @@ function RANGE:OnEventShot( EventData )
result.attackHdg = attackHdg result.attackHdg = attackHdg
result.attackVel = attackVel result.attackVel = attackVel
result.attackAlt = attackAlt result.attackAlt = attackAlt
result.date=os and os.date() or "n/a"
-- Add to table. -- Add to table.
table.insert( _results, result ) table.insert( _results, result )
@@ -2124,22 +1998,65 @@ function RANGE:OnEventShot( EventData )
end end
else else
self:T( self.id .. "Weapon impacted outside range zone." ) self:T( self.lid .. "Weapon impacted outside range zone." )
end end
-- Terminate the timer end
self:T( self.id .. string.format( "Range %s, player %s: Terminating bomb track timer.", self.rangename, _playername ) )
return nil
end -- _status check --- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun).
-- @param #RANGE self
-- @param Core.Event#EVENTDATA EventData
function RANGE:OnEventShot( EventData )
self:F( { eventshot = EventData } )
end -- end function trackBomb -- Nil checks.
if EventData.Weapon == nil or EventData.IniDCSUnit == nil or EventData.IniPlayerName == nil then
return
end
-- Weapon is not yet "alife" just yet. Start timer in one second. -- Create weapon object.
self:T( self.id .. string.format( "Range %s, player %s: Tracking of weapon starts in 0.1 seconds.", self.rangename, _playername ) ) local weapon=WEAPON:New(EventData.weapon)
timer.scheduleFunction( trackBomb, EventData.weapon, timer.getTime() + 0.1 )
end -- if _track (string.match) and player-range distance < threshold. -- Check if any condition applies here.
local _track = (weapon:IsBomb() and self.trackbombs) or (weapon:IsRocket() and self.trackrockets) or (weapon:IsMissile() and self.trackmissiles)
-- Get unit name.
local _unitName = EventData.IniUnitName
-- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
-- Distance Player-to-Range. Set this to larger value than the threshold.
local dPR = self.BombtrackThreshold * 2
-- Distance player to range.
if _unit and _playername then
dPR = _unit:GetCoordinate():Get2DDistance( self.location )
self:T( self.lid .. string.format( "Range %s, player %s, player-range distance = %d km.", self.rangename, _playername, dPR / 1000 ) )
end
-- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons.
if _track and dPR <= self.BombtrackThreshold and _unit and _playername then
-- Player data.
local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData
-- Attack parameters.
local attackHdg=_unit:GetHeading()
local attackAlt=_unit:GetHeight()
local attackVel=_unit:GetVelocityKNOTS()
-- Tracking info and init of last bomb position.
self:T( self.lid .. string.format( "RANGE %s: Tracking %s - %s.", self.rangename, weapon:GetTypeName(), weapon:GetName()))
-- Set callback function on impact.
weapon:SetFuncImpact(RANGE._OnImpact, self, playerData, attackHdg, attackAlt, attackVel)
-- Weapon is not yet "alife" just yet. Start timer in 0.1 seconds.
self:T( self.lid .. string.format( "Range %s, player %s: Tracking of weapon starts in 0.1 seconds.", self.rangename, _playername ) )
weapon:StartTrack(0.1)
end
end end
@@ -2183,7 +2100,7 @@ function RANGE:onafterStatus( From, Event, To )
end end
-- Check range status. -- Check range status.
self:I( self.id .. text ) self:I( self.lid .. text )
end end
@@ -2205,22 +2122,31 @@ function RANGE:onafterEnterRange( From, Event, To, player )
if self.instructor and self.rangecontrol then if self.instructor and self.rangecontrol then
if self.useSRS then if self.useSRS then
local text = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f MHz", self.rangecontrolfreq) local text = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f MHz", self.rangecontrolfreq)
local ttstext = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f mega hertz.", self.rangecontrolfreq) local ttstext = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f mega hertz.", self.rangecontrolfreq)
local group = player.client:GetGroup() local group = player.client:GetGroup()
self.instructsrsQ:NewTransmission(ttstext, nil, self.instructmsrs, nil, 1, {group}, text, 10) self.instructsrsQ:NewTransmission(ttstext, nil, self.instructmsrs, nil, 1, {group}, text, 10)
else else
-- Range control radio frequency split. -- Range control radio frequency split.
local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." ) local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." )
-- Radio message that player entered the range -- Radio message that player entered the range
-- You entered the bombing range. For hit assessment, contact the range controller at xy MHz -- You entered the bombing range. For hit assessment, contact the range controller at xy MHz
self.instructor:NewTransmission( RANGE.Sound.IREnterRange.filename, RANGE.Sound.IREnterRange.duration, self.soundpath ) self.instructor:NewTransmission( RANGE.Sound.IREnterRange.filename, RANGE.Sound.IREnterRange.duration, self.soundpath )
self.instructor:Number2Transmission( RF[1] ) self.instructor:Number2Transmission( RF[1] )
if tonumber( RF[2] ) > 0 then if tonumber( RF[2] ) > 0 then
self.instructor:NewTransmission( RANGE.Sound.IRDecimal.filename, RANGE.Sound.IRDecimal.duration, self.soundpath ) self.instructor:NewTransmission( RANGE.Sound.IRDecimal.filename, RANGE.Sound.IRDecimal.duration, self.soundpath )
self.instructor:Number2Transmission( RF[2] ) self.instructor:Number2Transmission( RF[2] )
end end
self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath ) self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath )
end end
end end
@@ -2238,9 +2164,24 @@ function RANGE:onafterExitRange( From, Event, To, player )
if self.instructor then if self.instructor then
-- You left the bombing range zone. Have a nice day! -- You left the bombing range zone. Have a nice day!
if self.useSRS then if self.useSRS then
local text = "You left the bombing range zone. Have a nice day!"
local group = player.client:GetGroup() local text = "You left the bombing range zone. "
self.instructsrsQ:NewTransmission(text,nil,self.instructmsrs,nil,1,{group},text,10)
local r=math.random(2)
if r==1 then
text=text.."Have a nice day!"
elseif r==2 then
text=text.."Take care and bye bye!"
elseif r==3 then
text=text.."Talk to you soon!"
elseif r==4 then
text=text.."See you in two weeks!"
elseif r==5 then
text=text.."!"
end
self.instructsrsQ:NewTransmission(text, nil, self.instructmsrs, nil, 1, {player.client:GetGroup()}, text, 10)
else else
self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath ) self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath )
end end
@@ -2304,7 +2245,7 @@ function RANGE:onafterImpact( From, Event, To, result, player )
-- Send message. -- Send message.
self:_DisplayMessageToGroup( unit, text, nil, true ) self:_DisplayMessageToGroup( unit, text, nil, true )
self:T( self.id .. text ) self:T( self.lid .. text )
end end
-- Save results. -- Save results.
@@ -2343,7 +2284,7 @@ function RANGE:onbeforeSave( From, Event, To )
if io and lfs then if io and lfs then
return true return true
else else
self:E( self.id .. string.format( "WARNING: io and/or lfs not desanitized. Cannot save player results." ) ) self:E( self.lid .. string.format( "WARNING: io and/or lfs not desanitized. Cannot save player results." ) )
return false return false
end end
end end
@@ -2360,9 +2301,9 @@ function RANGE:onafterSave( From, Event, To )
if f then if f then
f:write( data ) f:write( data )
f:close() f:close()
self:I( self.id .. string.format( "Saving player results to file %s", tostring( filename ) ) ) self:I( self.lid .. string.format( "Saving player results to file %s", tostring( filename ) ) )
else else
self:E( self.id .. string.format( "ERROR: Could not save results to file %s", tostring( filename ) ) ) self:E( self.lid .. string.format( "ERROR: Could not save results to file %s", tostring( filename ) ) )
end end
end end
@@ -2388,10 +2329,7 @@ function RANGE:onafterSave( From, Event, To )
local quality = result.quality local quality = result.quality
local time = UTILS.SecondsToClock(result.time, true) local time = UTILS.SecondsToClock(result.time, true)
local airframe = result.airframe local airframe = result.airframe
local date = "n/a" local date = result.date or "n/a"
if os then
date = os.date()
end
scores = scores .. string.format( "\n%s,%d,%s,%.2f,%03d,%s,%s,%s,%s,%s", playername, i, target, distance, radial, quality, weapon, airframe, time, date ) scores = scores .. string.format( "\n%s,%d,%s,%.2f,%03d,%s,%s,%s,%s,%s", playername, i, target, distance, radial, quality, weapon, airframe, time, date )
end end
end end
@@ -2408,7 +2346,7 @@ function RANGE:onbeforeLoad( From, Event, To )
if io and lfs then if io and lfs then
return true return true
else else
self:E( self.id .. string.format( "WARNING: io and/or lfs not desanitized. Cannot load player results." ) ) self:E( self.lid .. string.format( "WARNING: io and/or lfs not desanitized. Cannot load player results." ) )
return false return false
end end
end end
@@ -2424,12 +2362,12 @@ function RANGE:onafterLoad( From, Event, To )
local function _loadfile( filename ) local function _loadfile( filename )
local f = io.open( filename, "rb" ) local f = io.open( filename, "rb" )
if f then if f then
-- self:I(self.id..string.format("Loading player results from file %s", tostring(filename))) -- self:I(self.lid..string.format("Loading player results from file %s", tostring(filename)))
local data = f:read( "*all" ) local data = f:read( "*all" )
f:close() f:close()
return data return data
else else
self:E( self.id .. string.format( "WARNING: Could not load player results from file %s. File might not exist just yet.", tostring( filename ) ) ) self:E( self.lid .. string.format( "WARNING: Could not load player results from file %s. File might not exist just yet.", tostring( filename ) ) )
return nil return nil
end end
end end
@@ -2442,7 +2380,7 @@ function RANGE:onafterLoad( From, Event, To )
-- Info message. -- Info message.
local text = string.format( "Loading player bomb results from file %s", filename ) local text = string.format( "Loading player bomb results from file %s", filename )
self:I( self.id .. text ) self:I( self.lid .. text )
-- Load asset data from file. -- Load asset data from file.
local data = _loadfile( filename ) local data = _loadfile( filename )
@@ -2909,7 +2847,7 @@ function RANGE:_DisplayRangeInfo( _unitname )
self:_DisplayMessageToGroup( unit, text, nil, true, true, _multiplayer ) self:_DisplayMessageToGroup( unit, text, nil, true, true, _multiplayer )
-- Debug output. -- Debug output.
self:T2( self.id .. text ) self:T2( self.lid .. text )
end end
end end
end end
@@ -3056,9 +2994,9 @@ function RANGE:_DisplayRangeWeather( _unitname )
self:_DisplayMessageToGroup( unit, text, nil, true, true, _multiplayer ) self:_DisplayMessageToGroup( unit, text, nil, true, true, _multiplayer )
-- Debug output. -- Debug output.
self:T2( self.id .. text ) self:T2( self.lid .. text )
else else
self:T( self.id .. string.format( "ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname ) ) self:T( self.lid .. string.format( "ERROR! Could not find player unit in RangeInfo! Name = %s", _unitname ) )
end end
end end
@@ -3453,10 +3391,10 @@ function RANGE:_AddF10Commands( _unitName )
local _StrPits = MENU_GROUP_COMMAND:New( group, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName ):Refresh() local _StrPits = MENU_GROUP_COMMAND:New( group, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName ):Refresh()
end end
else else
self:E( self.id .. "Could not find group or group ID in AddF10Menu() function. Unit name: " .. _unitName or "N/A") self:E( self.lid .. "Could not find group or group ID in AddF10Menu() function. Unit name: " .. _unitName or "N/A")
end end
else else
self:E( self.id .. "Player unit does not exist in AddF10Menu() function. Unit name: " .. _unitName or "N/A") self:E( self.lid .. "Player unit does not exist in AddF10Menu() function. Unit name: " .. _unitName or "N/A")
end end
end end
@@ -3475,14 +3413,15 @@ function RANGE:_GetBombTargetCoordinate( target )
if target.type == RANGE.TargetType.UNIT then if target.type == RANGE.TargetType.UNIT then
if not target.move then -- Check if alive
-- Target should not move.
coord = target.coordinate
else
-- Moving target. Check if alive and get current position
if target.target and target.target:IsAlive() then if target.target and target.target:IsAlive() then
-- Get current position.
coord = target.target:GetCoordinate() coord = target.target:GetCoordinate()
end -- Save as last known position in case target dies.
target.coordinate=coord
else
-- Use stored position.
coord = target.coordinate
end end
elseif target.type == RANGE.TargetType.STATIC then elseif target.type == RANGE.TargetType.STATIC then
@@ -3495,8 +3434,13 @@ function RANGE:_GetBombTargetCoordinate( target )
-- Coordinates dont move. -- Coordinates dont move.
coord = target.coordinate coord = target.coordinate
elseif target.type == RANGE.TargetType.SCENERY then
-- Coordinates dont move.
coord = target.coordinate
else else
self:E( self.id .. "ERROR: Unknown target type." ) self:E( self.lid .. "ERROR: Unknown target type." )
end end
return coord return coord
@@ -3524,7 +3468,7 @@ function RANGE:_GetAmmo( unitname )
if ammotable ~= nil then if ammotable ~= nil then
local weapons = #ammotable local weapons = #ammotable
self:T2( self.id .. string.format( "Number of weapons %d.", weapons ) ) self:T2( self.lid .. string.format( "Number of weapons %d.", weapons ) )
for w = 1, weapons do for w = 1, weapons do
@@ -3538,10 +3482,10 @@ function RANGE:_GetAmmo( unitname )
ammo = ammo + Nammo ammo = ammo + Nammo
local text = string.format( "Player %s has %d rounds ammo of type %s", playername, Nammo, Tammo ) local text = string.format( "Player %s has %d rounds ammo of type %s", playername, Nammo, Tammo )
self:T( self.id .. text ) self:T( self.lid .. text )
else else
local text = string.format( "Player %s has %d ammo of type %s", playername, Nammo, Tammo ) local text = string.format( "Player %s has %d ammo of type %s", playername, Nammo, Tammo )
self:T( self.id .. text ) self:T( self.lid .. text )
end end
end end
end end
@@ -4005,20 +3949,20 @@ function RANGE:_CheckStatic( name )
-- If static is not yet in MOOSE DB, we add it. Can happen for cargo statics! -- If static is not yet in MOOSE DB, we add it. Can happen for cargo statics!
if not _MOOSEstatic then if not _MOOSEstatic then
self:T( self.id .. string.format( "Adding DCS static to MOOSE database. Name = %s.", name ) ) self:T( self.lid .. string.format( "Adding DCS static to MOOSE database. Name = %s.", name ) )
_DATABASE:AddStatic( name ) _DATABASE:AddStatic( name )
end end
return true return true
else else
self:T3( self.id .. string.format( "No static object with name %s exists.", name ) ) self:T3( self.lid .. string.format( "No static object with name %s exists.", name ) )
end end
-- Check if a unit has this name. -- Check if a unit has this name.
if UNIT:FindByName( name ) then if UNIT:FindByName( name ) then
return false return false
else else
self:T3( self.id .. string.format( "No unit object with name %s exists.", name ) ) self:T3( self.lid .. string.format( "No unit object with name %s exists.", name ) )
end end
-- If not unit or static exist, we return nil. -- If not unit or static exist, we return nil.

View File

@@ -873,8 +873,10 @@ end
function SCORING:OnEventBirth( Event ) function SCORING:OnEventBirth( Event )
if Event.IniUnit then if Event.IniUnit then
Event.IniUnit.ThreatLevel, Event.IniUnit.ThreatType = Event.IniUnit:GetThreatLevel()
if Event.IniObjectCategory == 1 then if Event.IniObjectCategory == 1 then
local PlayerName = Event.IniUnit:GetPlayerName() local PlayerName = Event.IniUnit:GetPlayerName()
Event.IniUnit.BirthTime = timer.getTime()
if PlayerName then if PlayerName then
self:_AddPlayerFromUnit( Event.IniUnit ) self:_AddPlayerFromUnit( Event.IniUnit )
self:SetScoringMenu( Event.IniGroup ) self:SetScoringMenu( Event.IniGroup )
@@ -1005,7 +1007,18 @@ function SCORING:_EventOnHit( Event )
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth
if PlayerHit.UNIT.ThreatType == nil then
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
-- if this fails for some reason, set a good default value
if PlayerHit.ThreatType == nil then
PlayerHit.ThreatLevel = 1
PlayerHit.ThreatType = "Unknown"
end
else
PlayerHit.ThreatLevel = PlayerHit.UNIT.ThreatLevel
PlayerHit.ThreatType = PlayerHit.UNIT.ThreatType
end
-- Only grant hit scores if there was more than one second between the last hit. -- Only grant hit scores if there was more than one second between the last hit.
if timer.getTime() - PlayerHit.TimeStamp > 1 then if timer.getTime() - PlayerHit.TimeStamp > 1 then
@@ -1021,27 +1034,30 @@ function SCORING:_EventOnHit( Event )
if InitCoalition then -- A coalition object was hit. if InitCoalition then -- A coalition object was hit.
if InitCoalition == TargetCoalition then if InitCoalition == TargetCoalition then
Player.Penalty = Player.Penalty + 10 local Penalty = 10
PlayerHit.Penalty = PlayerHit.Penalty + 10 Player.Penalty = Player.Penalty + Penalty
PlayerHit.Penalty = PlayerHit.Penalty + Penalty
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1
if TargetPlayerName ~= nil then -- It is a player hitting another player ... if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, "Penalty: -" .. Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update ) MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
else else
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, "Penalty: -" .. Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update ) MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
end end
self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else else
Player.Score = Player.Score + 1 -- Hitting a target multiple times before destoying it should not result in a higger score
PlayerHit.Score = PlayerHit.Score + 1 -- Multiple hits is typically a results of bombs/missles missing their target but still inflict some spash damage
-- Player.Score = Player.Score + 1
-- PlayerHit.Score = PlayerHit.Score + 1
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1 PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
if TargetPlayerName ~= nil then -- It is a player hitting another player ... if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
@@ -1104,7 +1120,18 @@ function SCORING:_EventOnHit( Event )
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth
if PlayerHit.UNIT.ThreatType == nil then
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
-- if this fails for some reason, set a good default value
if PlayerHit.ThreatType == nil then
PlayerHit.ThreatLevel = 1
PlayerHit.ThreatType = "Unknown"
end
else
PlayerHit.ThreatLevel = PlayerHit.UNIT.ThreatLevel
PlayerHit.ThreatType = PlayerHit.UNIT.ThreatType
end
-- Only grant hit scores if there was more than one second between the last hit. -- Only grant hit scores if there was more than one second between the last hit.
if timer.getTime() - PlayerHit.TimeStamp > 1 then if timer.getTime() - PlayerHit.TimeStamp > 1 then
@@ -1115,25 +1142,28 @@ function SCORING:_EventOnHit( Event )
if InitCoalition then -- A coalition object was hit, probably a static. if InitCoalition then -- A coalition object was hit, probably a static.
if InitCoalition == TargetCoalition then if InitCoalition == TargetCoalition then
-- TODO: Penalty according scale -- TODO: Penalty according scale
Player.Penalty = Player.Penalty + 10 --* self.ScaleDestroyPenalty local Penalty = 10
PlayerHit.Penalty = PlayerHit.Penalty + 10 --* self.ScaleDestroyPenalty Player.Penalty = Player.Penalty + Penalty --* self.ScaleDestroyPenalty
PlayerHit.Penalty = PlayerHit.Penalty + Penalty --* self.ScaleDestroyPenalty
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty
MESSAGE MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " .. :NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Penalty: -" .. PlayerHit.Penalty .. " = " .. Player.Score - Player.Penalty, "Penalty: -" .. Penalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Update MESSAGE.Type.Update
) )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) :ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
self:ScoreCSV( Event.WeaponPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) self:ScoreCSV( Event.WeaponPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else else
Player.Score = Player.Score + 1 -- Hitting a target multiple times before destoying it should not result in a higger score
PlayerHit.Score = PlayerHit.Score + 1 -- Multiple hits is typically a results of bombs/missles missing their target but still inflict some spash damage
-- Player.Score = Player.Score + 1
-- PlayerHit.Score = PlayerHit.Score + 1
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1 PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Score: +" .. PlayerHit.Score .. " = " .. Player.Score - Player.Penalty, "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update ) MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) :ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -1211,7 +1241,7 @@ function SCORING:_EventOnDeadOrCrash( Event )
local Destroyed = false local Destroyed = false
-- What is the player destroying? -- What is the player destroying?
if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 then -- Was there a hit for this unit for this player before registered??? if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 and (TargetUnit.BirthTime == nil or Player.Hit[TargetCategory][TargetUnitName].TimeStamp > TargetUnit.BirthTime) then -- Was there a hit for this unit for this player before registered???
local TargetThreatLevel = Player.Hit[TargetCategory][TargetUnitName].ThreatLevel local TargetThreatLevel = Player.Hit[TargetCategory][TargetUnitName].ThreatLevel
local TargetThreatType = Player.Hit[TargetCategory][TargetUnitName].ThreatType local TargetThreatType = Player.Hit[TargetCategory][TargetUnitName].ThreatType
@@ -1240,13 +1270,13 @@ function SCORING:_EventOnDeadOrCrash( Event )
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty, "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else else
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty, "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
@@ -1268,13 +1298,13 @@ function SCORING:_EventOnDeadOrCrash( Event )
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1 TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty, "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else else
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty, "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )

View File

@@ -715,7 +715,7 @@ do -- ZONE_CAPTURE_COALITION
local UnitHit = EventData.TgtUnit local UnitHit = EventData.TgtUnit
if UnitHit.ClassName ~= "SCENERY" then if UnitHit and UnitHit.ClassName ~= "SCENERY" then
-- Check if unit is inside the capture zone and that it is of the defending coalition. -- Check if unit is inside the capture zone and that it is of the defending coalition.
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then

View File

@@ -33,6 +33,7 @@ __Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
__Moose.Include( 'Scripts/Moose/Core/Spot.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' ) __Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/TextAndSound.lua' ) __Moose.Include( 'Scripts/Moose/Core/TextAndSound.lua' )
__Moose.Include( 'Scripts/Moose/Core/Pathline.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
@@ -45,6 +46,8 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Marker.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Marker.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

@@ -2750,12 +2750,12 @@ function CSAR:onafterLoad(From, Event, To, path, filename)
vec3.z = tonumber(dataset[4]) vec3.z = tonumber(dataset[4])
local point = COORDINATE:NewFromVec3(vec3) local point = COORDINATE:NewFromVec3(vec3)
local coalition = dataset[5] local coalition = tonumber(dataset[5])
local country = dataset[6] local country = tonumber(dataset[6])
local description = dataset[7] local description = dataset[7]
local typeName = dataset[8] local typeName = dataset[8]
local unitName = dataset[9] local unitName = dataset[9]
local freq = dataset[10] local freq = tonumber(dataset[10])
self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, nil, description, nil) self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, nil, description, nil)
end end

View File

@@ -22,7 +22,7 @@
-- @module Ops.CTLD -- @module Ops.CTLD
-- @image OPS_CTLD.jpg -- @image OPS_CTLD.jpg
-- Last Update Jan 2023 -- Last Update Mar 2023
do do
@@ -289,6 +289,7 @@ end
do do
------------------------------------------------------ ------------------------------------------------------
--- **CTLD_CARGO** class, extends Core.Base#BASE --- **CTLD_CARGO** class, extends Core.Base#BASE
-- @type CTLD_CARGO -- @type CTLD_CARGO
@@ -494,7 +495,7 @@ CTLD_CARGO = {
--- Add Stock. --- Add Stock.
-- @param #CTLD_CARGO self -- @param #CTLD_CARGO self
-- @param #number Number to add, one if nil. -- @param #number Number to add, none if nil.
-- @return #CTLD_CARGO self -- @return #CTLD_CARGO self
function CTLD_CARGO:AddStock(Number) function CTLD_CARGO:AddStock(Number)
if self.Stock then -- Stock nil? if self.Stock then -- Stock nil?
@@ -506,7 +507,7 @@ CTLD_CARGO = {
--- Remove Stock. --- Remove Stock.
-- @param #CTLD_CARGO self -- @param #CTLD_CARGO self
-- @param #number Number to reduce, one if nil. -- @param #number Number to reduce, none if nil.
-- @return #CTLD_CARGO self -- @return #CTLD_CARGO self
function CTLD_CARGO:RemoveStock(Number) function CTLD_CARGO:RemoveStock(Number)
if self.Stock then -- Stock nil? if self.Stock then -- Stock nil?
@@ -517,6 +518,15 @@ CTLD_CARGO = {
return self return self
end end
--- Set Stock.
-- @param #CTLD_CARGO self
-- @param #number Number to set, nil means unlimited.
-- @return #CTLD_CARGO self
function CTLD_CARGO:SetStock(Number)
self.Stock = Number
return self
end
--- Query crate type for REPAIR --- Query crate type for REPAIR
-- @param #CTLD_CARGO self -- @param #CTLD_CARGO self
-- @param #boolean -- @param #boolean
@@ -690,6 +700,7 @@ do
-- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. -- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD.
-- my_ctld.CrateDistance = 35 -- List and Load crates in this radius only. -- my_ctld.CrateDistance = 35 -- List and Load crates in this radius only.
-- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. -- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere.
-- my_ctld.dropAsCargoCrate = false -- Parachuted herc cargo is not unpacked automatically but placed as crate to be unpacked. Needs a cargo with the same name defined like the cargo that was dropped.
-- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. -- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load.
-- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. -- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load.
-- my_ctld.forcehoverload = true -- Crates (not: troops) can **only** be loaded while hovering. -- my_ctld.forcehoverload = true -- Crates (not: troops) can **only** be loaded while hovering.
@@ -951,6 +962,18 @@ do
-- --
-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers -- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers
-- --
-- ### 5.3 Don't automatically unpack dropped cargo but drop as CTLD_CARGO
--
-- Cargo can be defined to be automatically dropped as crates.
-- my_ctld.dropAsCargoCrate = true -- default is false
--
-- The idea is, to have those crate behave like brought in with a helo. So any unpack restictions apply.
-- To enable those cargo drops, the cargo types must be added manually in the CTLD configuration. So when the above defined template for "Vulcan" should be used
-- as CTLD_Cargo, the following line has to be added. NoCrates, PerCrateMass, Stock, SubCategory can be configured freely.
-- my_ctld:AddCratesCargo("Vulcan", {"Vulcan"}, CTLD_CARGO.Enum.VEHICLE, 6, 2000, nil, "SAM/AAA")
--
-- So if the Vulcan in the example now needs six crates to complete, you have to bring two Hercs with three Vulcan crates each and drop them very close together...
--
-- ## 6. Save and load back units - persistance -- ## 6. Save and load back units - persistance
-- --
-- You can save and later load back units dropped or build to make your mission persistent. -- You can save and later load back units dropped or build to make your mission persistent.
@@ -1196,7 +1219,7 @@ CTLD.UnitTypes = {
--- CTLD class version. --- CTLD class version.
-- @field #string version -- @field #string version
CTLD.version="1.0.29" CTLD.version="1.0.32"
--- Instantiate a new CTLD. --- Instantiate a new CTLD.
-- @param #CTLD self -- @param #CTLD self
@@ -1321,6 +1344,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
self.forcehoverload = true self.forcehoverload = true
self.hoverautoloading = true self.hoverautoloading = true
self.dropcratesanywhere = false -- #1570 self.dropcratesanywhere = false -- #1570
self.dropAsCargoCrate = false -- Parachuted herc cargo is not unpacked automatically but placed as crate to be unpacked
self.smokedistance = 2000 self.smokedistance = 2000
self.movetroopstowpzone = true self.movetroopstowpzone = true
@@ -1776,6 +1800,22 @@ function CTLD:_FindTroopsCargoObject(Name)
return nil return nil
end end
--- (Internal) Find a crates CTLD_CARGO object in stock
-- @param #CTLD self
-- @param #string Name of the object
-- @return #CTLD_CARGO Cargo object, nil if it cannot be found
function CTLD:_FindCratesCargoObject(Name)
self:T(self.lid .. " _FindCratesCargoObject")
local cargo = nil
for _,_cargo in pairs(self.Cargo_Crates)do
local cargo = _cargo -- #CTLD_CARGO
if cargo.Name == Name then
return cargo
end
end
return nil
end
--- (User) Pre-load troops into a helo, e.g. for airstart. Unit **must** be alive in-game, i.e. player has taken the slot! --- (User) Pre-load troops into a helo, e.g. for airstart. Unit **must** be alive in-game, i.e. player has taken the slot!
-- @param #CTLD self -- @param #CTLD self
-- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object -- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object
@@ -1801,6 +1841,84 @@ function CTLD:PreloadTroops(Unit,Troopname)
return self return self
end end
--- (Internal) Pre-load crates into a helo. Do not use standalone!
-- @param #CTLD self
-- @param Wrapper.Group#GROUP Group The group to load into, can be handed as Wrapper.Client#CLIENT object
-- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object
-- @param #CTLD_CARGO Cargo The Cargo crate object to load
-- @param #number NumberOfCrates (Optional) Number of crates to be loaded. Default - all necessary to build this object. Might overload the helo!
-- @return #CTLD self
function CTLD:_PreloadCrates(Group, Unit, Cargo, NumberOfCrates)
-- load crate into heli
local group = Group -- Wrapper.Group#GROUP
local unit = Unit -- Wrapper.Unit#UNIT
local unitname = unit:GetName()
-- see if this heli can load crates
local unittype = unit:GetTypeName()
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities
local cancrates = capabilities.crates -- #boolean
local cratelimit = capabilities.cratelimit -- #number
if not cancrates then
self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group)
return self
else
-- have we loaded stuff already?
local numberonboard = 0
local massonboard = 0
local loaded = {}
if self.Loaded_Cargo[unitname] then
loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo
numberonboard = loaded.Cratesloaded or 0
massonboard = self:_GetUnitCargoMass(Unit)
else
loaded = {} -- #CTLD.LoadedCargo
loaded.Troopsloaded = 0
loaded.Cratesloaded = 0
loaded.Cargo = {}
end
local crate = Cargo -- #CTLD_CARGO
local numbercrates = NumberOfCrates or crate:GetCratesNeeded()
for i=1,numbercrates do
loaded.Cratesloaded = loaded.Cratesloaded + 1
crate:SetHasMoved(true)
crate:SetWasDropped(false)
table.insert(loaded.Cargo, crate)
crate.Positionable = nil
self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group)
--self:__CratesPickedUp(1, Group, Unit, crate)
self.Loaded_Cargo[unitname] = loaded
self:_UpdateUnitCargoMass(Unit)
end
end
return self
end
--- (User) Pre-load crates into a helo, e.g. for airstart. Unit **must** be alive in-game, i.e. player has taken the slot!
-- @param #CTLD self
-- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object
-- @param #string Cratesname The name of the cargo to be loaded. Must be created prior in the CTLD setup!
-- @param #number NumberOfCrates (Optional) Number of crates to be loaded. Default - all necessary to build this object. Might overload the helo!
-- @return #CTLD self
-- @usage
-- local client = UNIT:FindByName("Helo-1-1")
-- if client and client:IsAlive() then
-- myctld:PreloadCrates(client,"Humvee")
-- end
function CTLD:PreloadCrates(Unit,Cratesname,NumberOfCrates)
self:T(self.lid .. " PreloadCrates")
local name = Cratesname or "Unknown"
if Unit and Unit:IsAlive() then
local cargo = self:_FindCratesCargoObject(name)
local group = Unit:GetGroup()
if cargo then
self:_PreloadCrates(group,Unit,cargo,NumberOfCrates)
else
self:E(self.lid.." Crates preload - Cargo Object "..name.." not found!")
end
end
return self
end
--- (Internal) Function to load troops into a heli. --- (Internal) Function to load troops into a heli.
-- @param #CTLD self -- @param #CTLD self
-- @param Wrapper.Group#GROUP Group -- @param Wrapper.Group#GROUP Group
@@ -2199,7 +2317,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop)
end end
-- loop crates needed -- loop crates needed
for i=1,number do for i=1,number do
local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000))
if not self.placeCratesAhead then if not self.placeCratesAhead then
cratedistance = (i-1)*2.5 + capabilities.length cratedistance = (i-1)*2.5 + capabilities.length
if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end
@@ -2299,10 +2417,10 @@ function CTLD:InjectStatics(Zone, Cargo, RandomCoord)
--local number = 1 --local number = 1
local cratesneeded = cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number
local cratetemplate = "Container"-- #string local cratetemplate = "Container"-- #string
local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000))
local cratename = cargotype:GetName() local cratename = cargotype:GetName()
local cgotype = cargotype:GetType() local cgotype = cargotype:GetType()
local cgomass = cargotype:GetMass() local cgomass = cargotype:GetMass()
local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000))
local isstatic = false local isstatic = false
if cgotype == CTLD_CARGO.Enum.STATIC then if cgotype == CTLD_CARGO.Enum.STATIC then
cratetemplate = cargotype:GetTemplates() cratetemplate = cargotype:GetTemplates()
@@ -2515,7 +2633,7 @@ function CTLD:_LoadCratesNearby(Group, Unit)
crateind = _crate:GetID() crateind = _crate:GetID()
end end
else else
if not _crate:HasMoved() and _crate:WasDropped() and _crate:GetID() > crateind then if not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then
crateind = _crate:GetID() crateind = _crate:GetID()
end end
end end
@@ -4384,6 +4502,7 @@ end
_troop:AddStock(number) _troop:AddStock(number)
end end
end end
return self
end end
--- User - function to add stock of a certain crates type --- User - function to add stock of a certain crates type
@@ -4401,6 +4520,115 @@ end
_troop:AddStock(number) _troop:AddStock(number)
end end
end end
return self
end
--- User - function to add stock of a certain crates type
-- @param #CTLD self
-- @param #string Name Name as defined in the generic cargo.
-- @param #number Number Number of units/groups to add.
-- @return #CTLD self
function CTLD:AddStockStatics(Name, Number)
local name = Name or "none"
local number = Number or 1
-- find right generic type
local gentroops = self.Cargo_Statics
for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO
if _troop.Name == name then
_troop:AddStock(number)
end
end
return self
end
--- User - function to set the stock of a certain crates type
-- @param #CTLD self
-- @param #string Name Name as defined in the generic cargo.
-- @param #number Number Number of units/groups to be available. Nil equals unlimited
-- @return #CTLD self
function CTLD:SetStockCrates(Name, Number)
local name = Name or "none"
local number = Number
-- find right generic type
local gentroops = self.Cargo_Crates
for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO
if _troop.Name == name then
_troop:SetStock(number)
end
end
return self
end
--- User - function to set the stock of a certain troops type
-- @param #CTLD self
-- @param #string Name Name as defined in the generic cargo.
-- @param #number Number Number of units/groups to be available. Nil equals unlimited
-- @return #CTLD self
function CTLD:SetStockTroops(Name, Number)
local name = Name or "none"
local number = Number
-- find right generic type
local gentroops = self.Cargo_Troops
for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO
if _troop.Name == name then
_troop:SetStock(number)
end
end
return self
end
--- User - function to set the stock of a certain statics type
-- @param #CTLD self
-- @param #string Name Name as defined in the generic cargo.
-- @param #number Number Number of units/groups to be available. Nil equals unlimited
-- @return #CTLD self
function CTLD:SetStockStatics(Name, Number)
local name = Name or "none"
local number = Number
-- find right generic type
local gentroops = self.Cargo_Statics
for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO
if _troop.Name == name then
_troop:SetStock(number)
end
end
return self
end
--- User - function to get a table of crates in stock
-- @param #CTLD self
-- @return #table Table Table of Stock, indexed by cargo type name
function CTLD:GetStockCrates()
local Stock = {}
local gentroops = self.Cargo_Crates
for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO
table.insert(Stock,_troop.Name,_troop.Stock or -1)
end
return Stock
end
--- User - function to get a table of troops in stock
-- @param #CTLD self
-- @return #table Table Table of Stock, indexed by cargo type name
function CTLD:GetStockTroops()
local Stock = {}
local gentroops = self.Cargo_Troops
for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO
table.insert(Stock,_troop.Name,_troop.Stock or -1)
end
return Stock
end
--- User - function to get a table of statics cargo in stock
-- @param #CTLD self
-- @return #table Table Table of Stock, indexed by cargo type name
function CTLD:GetStockStatics()
local Stock = {}
local gentroops = self.Cargo_Statics
for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO
table.insert(Stock,_troop.Name,_troop.Stock or -1)
end
return Stock
end end
--- User - function to remove stock of a certain troops type --- User - function to remove stock of a certain troops type
@@ -4418,6 +4646,7 @@ end
_troop:RemoveStock(number) _troop:RemoveStock(number)
end end
end end
return self
end end
--- User - function to remove stock of a certain crates type --- User - function to remove stock of a certain crates type
@@ -4438,6 +4667,24 @@ end
return self return self
end end
--- User - function to remove stock of a certain statics type
-- @param #CTLD self
-- @param #string Name Name as defined in the generic cargo.
-- @param #number Number Number of units/groups to add.
-- @return #CTLD self
function CTLD:RemoveStockStatics(Name, Number)
local name = Name or "none"
local number = Number or 1
-- find right generic type
local gentroops = self.Cargo_Statics
for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO
if _troop.Name == name then
_troop:RemoveStock(number)
end
end
return self
end
--- (Internal) Check on engineering teams --- (Internal) Check on engineering teams
-- @param #CTLD self -- @param #CTLD self
-- @return #CTLD self -- @return #CTLD self
@@ -5273,7 +5520,7 @@ CTLD_HERCULES = {
ClassName = "CTLD_HERCULES", ClassName = "CTLD_HERCULES",
lid = "", lid = "",
Name = "", Name = "",
Version = "0.0.2", Version = "0.0.3",
} }
--- Define cargo types. --- Define cargo types.
@@ -5569,6 +5816,34 @@ function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Positio
return self return self
end end
--- [Internal] Function to spawn cargo by type at position
-- @param #CTLD_HERCULES self
-- @param #string Cargo_Type_name
-- @param Core.Point#POINT_VEC3 Cargo_Drop_Position
-- @return #CTLD_HERCULES self
function CTLD_HERCULES:Cargo_SpawnDroppedAsCargo(_name, _pos)
local theCargo = self.CTLD:_FindCratesCargoObject(_name)
if theCargo then
self.CTLD.CrateCounter = self.CTLD.CrateCounter + 1
self.CTLD.CargoCounter = self.CTLD.CargoCounter + 1
local basetype = self.CTLD.basetype or "container_cargo"
local theStatic = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry)
:InitCargoMass(theCargo.PerCrateMass)
:InitCargo(self.CTLD.enableslingload)
:InitCoordinate(_pos)
:Spawn(270,_name .. "-Container-".. math.random(1,100000))
self.CTLD.Spawned_Crates[self.CTLD.CrateCounter] = theStatic
local newCargo = CTLD_CARGO:New(self.CTLD.CargoCounter, theCargo.Name, theCargo.Templates, theCargo.CargoType, true, false, theCargo.CratesNeeded, self.CTLD.Spawned_Crates[self.CTLD.CrateCounter], true, theCargo.PerCrateMass, nil, theCargo.Subcategory)
table.insert(self.CTLD.Spawned_Cargo, newCargo)
newCargo:SetWasDropped(true)
newCargo:SetHasMoved(true)
end
return self
end
--- [Internal] Spawn cargo objects --- [Internal] Spawn cargo objects
-- @param #CTLD_HERCULES self -- @param #CTLD_HERCULES self
-- @param Wrapper.Group#GROUP Cargo_Drop_initiator -- @param Wrapper.Group#GROUP Cargo_Drop_initiator
@@ -5591,8 +5866,8 @@ function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direct
if offload_cargo == true or ParatrooperGroupSpawn == true then if offload_cargo == true or ParatrooperGroupSpawn == true then
if ParatrooperGroupSpawn == true then if ParatrooperGroupSpawn == true then
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) --self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0)
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 5) --self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 5)
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 10) self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 10)
else else
self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country)
@@ -5617,10 +5892,14 @@ function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direct
if Container_Enclosed == true then if Container_Enclosed == true then
if ParatrooperGroupSpawn == true then if ParatrooperGroupSpawn == true then
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0)
else
if self.CTLD.dropAsCargoCrate then
self:Cargo_SpawnDroppedAsCargo(Cargo_Type_name, Cargo_Content_position)
else else
self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country)
self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, false, Cargo_Country) self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, false, Cargo_Country)
end end
end
else else
self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country)
end end

View File

@@ -198,7 +198,7 @@
-- The first parameter *callsignname* defines the name (1=Texaco, 2=Arco, 3=Shell). The second (optional) parameter specifies the first number and has to be between 1-9. -- The first parameter *callsignname* defines the name (1=Texaco, 2=Arco, 3=Shell). The second (optional) parameter specifies the first number and has to be between 1-9.
-- Also see [DCS_enum_callsigns](https://wiki.hoggitworld.com/view/DCS_enum_callsigns) and [DCS_command_setCallsign](https://wiki.hoggitworld.com/view/DCS_command_setCallsign). -- Also see [DCS_enum_callsigns](https://wiki.hoggitworld.com/view/DCS_enum_callsigns) and [DCS_command_setCallsign](https://wiki.hoggitworld.com/view/DCS_command_setCallsign).
-- --
-- TexacoStennis:SetCAllsign(CALLSIGN.Tanker.Arco) -- TexacoStennis:SetCallsign(CALLSIGN.Tanker.Arco)
-- --
-- For convenience, MOOSE has a CALLSIGN enumerator introduced. -- For convenience, MOOSE has a CALLSIGN enumerator introduced.
-- --

View File

@@ -489,6 +489,7 @@ ENUMS.ReportingName =
--Mosquito = "A-20", --Mosquito = "A-20",
Skyhawk = "A-4E", Skyhawk = "A-4E",
Viggen = "AJS37", Viggen = "AJS37",
Harrier_B = "AV8BNA",
Harrier = "AV-8B", Harrier = "AV-8B",
Spirit = "B-2", Spirit = "B-2",
Aviojet = "C-101", Aviojet = "C-101",

View File

@@ -44,6 +44,7 @@ SOCKET = {
} }
--- Data type. This is the keyword the socket listener uses. --- Data type. This is the keyword the socket listener uses.
-- @type SOCKET.DataType
-- @field #string TEXT Plain text. -- @field #string TEXT Plain text.
-- @field #string BOMBRESULT Range bombing. -- @field #string BOMBRESULT Range bombing.
-- @field #string STRAFERESULT Range strafeing result. -- @field #string STRAFERESULT Range strafeing result.

View File

@@ -489,6 +489,31 @@ UTILS.hPa2inHg = function( hPa )
return hPa * 0.0295299830714 return hPa * 0.0295299830714
end end
--- Convert indicated airspeed (IAS) to true airspeed (TAS) for a given altitude above main sea level.
-- The conversion is based on the approximation that TAS is ~2% higher than IAS with every 1000 ft altitude above sea level.
-- @param #number ias Indicated air speed in any unit (m/s, km/h, knots, ...)
-- @param #number altitude Altitude above main sea level in meters.
-- @param #number oatcorr (Optional) Outside air temperature correction factor. Default 0.017.
-- @return #number True airspeed in the same unit the IAS has been given.
UTILS.IasToTas = function( ias, altitude, oatcorr )
oatcorr=oatcorr or 0.017
local tas=ias + (ias * oatcorr * UTILS.MetersToFeet(altitude) / 1000)
return tas
end
--- Convert true airspeed (TAS) to indicated airspeed (IAS) for a given altitude above main sea level.
-- The conversion is based on the approximation that TAS is ~2% higher than IAS with every 1000 ft altitude above sea level.
-- @param #number tas True air speed in any unit (m/s, km/h, knots, ...)
-- @param #number altitude Altitude above main sea level in meters.
-- @param #number oatcorr (Optional) Outside air temperature correction factor. Default 0.017.
-- @return #number Indicated airspeed in the same unit the TAS has been given.
UTILS.TasToIas = function( tas, altitude, oatcorr )
oatcorr=oatcorr or 0.017
local ias=tas/(1+oatcorr*UTILS.MetersToFeet(altitude)/1000)
return ias
end
--- Convert knots to altitude corrected KIAS, e.g. for tankers. --- Convert knots to altitude corrected KIAS, e.g. for tankers.
-- @param #number knots Speed in knots. -- @param #number knots Speed in knots.
-- @param #number altitude Altitude in feet -- @param #number altitude Altitude in feet
@@ -642,7 +667,10 @@ function UTILS.Round( num, idp )
return math.floor( num * mult + 0.5 ) / mult return math.floor( num * mult + 0.5 ) / mult
end end
-- porting in Slmod's dostring --- Porting in Slmod's dostring - execute a string as LUA code with error handling.
-- @param #string s The code as string to be executed
-- @return #boolean success If true, code was successfully executed, else false
-- @return #string Outcome Code outcome if successful or error string if not successful
function UTILS.DoString( s ) function UTILS.DoString( s )
local f, err = loadstring( s ) local f, err = loadstring( s )
if f then if f then
@@ -652,7 +680,15 @@ function UTILS.DoString( s )
end end
end end
-- Here is a customized version of pairs, which I called spairs because it iterates over the table in a sorted order. --- Here is a customized version of pairs, which I called spairs because it iterates over the table in a sorted order.
-- @param #table t The table
-- @param #string order (Optional) The sorting function
-- @return #string key The index key
-- @return #string value The value at the indexed key
-- @usage
-- for key,value in UTILS.spairs(mytable) do
-- -- your code here
-- end
function UTILS.spairs( t, order ) function UTILS.spairs( t, order )
-- collect the keys -- collect the keys
local keys = {} local keys = {}
@@ -677,7 +713,16 @@ function UTILS.spairs( t, order )
end end
-- Here is a customized version of pairs, which I called kpairs because it iterates over the table in a sorted order, based on a function that will determine the keys as reference first. --- Here is a customized version of pairs, which I called kpairs because it iterates over the table in a sorted order, based on a function that will determine the keys as reference first.
-- @param #table t The table
-- @param #string getkey The function to determine the keys for sorting
-- @param #string order (Optional) The sorting function itself
-- @return #string key The index key
-- @return #string value The value at the indexed key
-- @usage
-- for key,value in UTILS.kpairs(mytable, getkeyfunc) do
-- -- your code here
-- end
function UTILS.kpairs( t, getkey, order ) function UTILS.kpairs( t, getkey, order )
-- collect the keys -- collect the keys
local keys = {} local keys = {}
@@ -702,7 +747,14 @@ function UTILS.kpairs( t, getkey, order )
end end
end end
-- Here is a customized version of pairs, which I called rpairs because it iterates over the table in a random order. --- Here is a customized version of pairs, which I called rpairs because it iterates over the table in a random order.
-- @param #table t The table
-- @return #string key The index key
-- @return #string value The value at the indexed key
-- @usage
-- for key,value in UTILS.rpairs(mytable) do
-- -- your code here
-- end
function UTILS.rpairs( t ) function UTILS.rpairs( t )
-- collect the keys -- collect the keys
@@ -1197,6 +1249,20 @@ function UTILS.HdgDiff(h1, h2)
return math.abs(delta) return math.abs(delta)
end end
--- Returns the heading from one vec3 to another vec3.
-- @param DCS#Vec3 a From vec3.
-- @param DCS#Vec3 b To vec3.
-- @return #number Heading in degrees.
function UTILS.HdgTo(a, b)
local dz=b.z-a.z
local dx=b.x-a.x
local heading=math.deg(math.atan2(dz, dx))
if heading < 0 then
heading = 360 + heading
end
return heading
end
--- Translate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. --- Translate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 a Vector in 3D with x, y, z components.
@@ -2807,3 +2873,45 @@ function UTILS.IsAnyInTable(Table, Objects, Key)
return false return false
end end
--- Helper function to plot a racetrack on the F10 Map - curtesy of Buur.
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Altitude Altitude in feet
-- @param #number Speed Speed in knots
-- @param #number Heading Heading in degrees
-- @param #number Leg Leg in NM
-- @param #number Coalition Coalition side, e.g. coaltion.side.RED or coaltion.side.BLUE
-- @param #table Color Color of the line in RGB, e.g. {1,0,0} for red
-- @param #number Alpha Transparency factor, between 0.1 and 1
-- @param #number LineType Line type to be used, 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
function UTILS.PlotRacetrack(Coordinate, Altitude, Speed, Heading, Leg, Coalition, Color, Alpha, LineType, ReadOnly)
local fix_coordinate = Coordinate
local altitude = Altitude
local speed = Speed or 350
local heading = Heading or 270
local leg_distance = Leg or 10
local coalition = Coalition or -1
local color = Color or {1,0,0}
local alpha = Alpha or 1
local lineType = LineType or 1
speed = UTILS.IasToTas(speed, UTILS.FeetToMeters(altitude), oatcorr)
local turn_radius = 0.0211 * speed -3.01
local point_two = fix_coordinate:Translate(UTILS.NMToMeters(leg_distance), heading, true, false)
local point_three = point_two:Translate(UTILS.NMToMeters(turn_radius)*2, heading - 90, true, false)
local point_four = fix_coordinate:Translate(UTILS.NMToMeters(turn_radius)*2, heading - 90, true, false)
local circle_center_fix_four = point_two:Translate(UTILS.NMToMeters(turn_radius), heading - 90, true, false)
local circle_center_two_three = fix_coordinate:Translate(UTILS.NMToMeters(turn_radius), heading - 90, true, false)
fix_coordinate:LineToAll(point_two, coalition, color, alpha, lineType)
point_four:LineToAll(point_three, coalition, color, alpha, lineType)
circle_center_fix_four:CircleToAll(UTILS.NMToMeters(turn_radius), coalition, color, alpha, nil, 0, lineType)--, ReadOnly, Text)
circle_center_two_three:CircleToAll(UTILS.NMToMeters(turn_radius), coalition, color, alpha, nil, 0, lineType)--, ReadOnly, Text)
end

View File

@@ -540,8 +540,12 @@ AIRBASE.SouthAtlantic={
["Rio_Chico"] = "Rio Chico", ["Rio_Chico"] = "Rio Chico",
["Franco_Bianco"] = "Franco Bianco", ["Franco_Bianco"] = "Franco Bianco",
["Goose_Green"] = "Goose Green", ["Goose_Green"] = "Goose Green",
["Hipico"] = "Hipico", ["Hipico_Flying_Club"] = "Hipico Flying Club",
["CaletaTortel"] = "CaletaTortel", ["CaletaTortel"] = "CaletaTortel",
["Aeropuerto_de_Gobernador_Gregores"] = "Aeropuerto de Gobernador Gregores",
["Aerodromo_O_Higgins"] = "Aerodromo O'Higgins",
["Cullen_Airport"] = "Cullen Airport",
["Gull_Point"] = "Gull Point",
} }
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".

View File

@@ -95,6 +95,22 @@ function CLIENT:Find(DCSUnit, Error)
end end
end end
--- Finds a CLIENT from the _DATABASE using the relevant player name.
-- @param #CLIENT self
-- @param #string Name Name of the player
-- @return #CLIENT or nil if not found
function CLIENT:FindByPlayerName(Name)
local foundclient = nil
_DATABASE:ForEachClient(
function(client)
if client:GetPlayerName() == Name then
foundclient = client
end
end
)
return foundclient
end
--- Finds a CLIENT from the _DATABASE using the relevant Client Unit Name. --- Finds a CLIENT from the _DATABASE using the relevant Client Unit Name.
-- As an optional parameter, a briefing text can be given also. -- As an optional parameter, a briefing text can be given also.

View File

@@ -1653,7 +1653,7 @@ function GROUP:GetMinHeight()
return nil return nil
end end
--- Returns the current maximum height of the group. --- Returns the current maximum height of the group, i.e. the highest unit height of that group.
-- Each unit within the group gets evaluated, and the maximum height (= the unit which is the highest elevated) is returned. -- Each unit within the group gets evaluated, and the maximum height (= the unit which is the highest elevated) is returned.
-- @param #GROUP self -- @param #GROUP self
-- @return #number Maximum height found. -- @return #number Maximum height found.
@@ -1668,7 +1668,7 @@ function GROUP:GetMaxHeight()
for Index, UnitData in pairs( DCSGroup:getUnits() ) do for Index, UnitData in pairs( DCSGroup:getUnits() ) do
local UnitData = UnitData -- DCS#Unit local UnitData = UnitData -- DCS#Unit
local UnitHeight = UnitData:getPoint() local UnitHeight = UnitData:getPoint().p.y -- Height -- found by @Heavydrinker
if UnitHeight > GroupHeightMax then if UnitHeight > GroupHeightMax then
GroupHeightMax = UnitHeight GroupHeightMax = UnitHeight
@@ -2785,11 +2785,16 @@ end
-- @param #GROUP self -- @param #GROUP self
-- @param #boolean ShortCallsign Return a shortened customized callsign, i.e. "Ghostrider 9" and not "Ghostrider 9 1" -- @param #boolean ShortCallsign Return a shortened customized callsign, i.e. "Ghostrider 9" and not "Ghostrider 9 1"
-- @param #boolean Keepnumber (Player only) Return customized callsign, incl optional numbers at the end, e.g. "Aerial 1-1#Ghostrider 109" results in "Ghostrider 109", if you want to e.g. use historical US Navy Callsigns -- @param #boolean Keepnumber (Player only) Return customized callsign, incl optional numbers at the end, e.g. "Aerial 1-1#Ghostrider 109" results in "Ghostrider 109", if you want to e.g. use historical US Navy Callsigns
-- @param #table CallsignTranslations Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized -- @param #table CallsignTranslations Table to translate between DCS standard callsigns and bespoke ones. Overrides personal/parsed callsigns if set
-- callsigns from playername or group name. -- callsigns from playername or group name.
-- @return #string Callsign -- @return #string Callsign
-- @usage -- @usage
-- -- Set Custom CAP Flight Callsigns for use with TTS -- -- suppose there are three groups with one (client) unit each:
-- -- Slot 1 -- with mission editor callsign Enfield-1
-- -- Slot 2 # Apollo 403 -- with mission editor callsign Enfield-2
-- -- Slot 3 | Apollo -- with mission editor callsign Enfield-3
-- -- Slot 4 | Apollo -- with mission editor callsign Devil-4
-- -- and suppose these Custom CAP Flight Callsigns for use with TTS are set
-- mygroup:GetCustomCallSign(true,false,{ -- mygroup:GetCustomCallSign(true,false,{
-- Devil = 'Bengal', -- Devil = 'Bengal',
-- Snake = 'Winder', -- Snake = 'Winder',
@@ -2797,12 +2802,12 @@ end
-- Enfield = 'Victory', -- Enfield = 'Victory',
-- Uzi = 'Evil Eye' -- Uzi = 'Evil Eye'
-- }) -- })
-- -- -- then GetCustomCallsign will return
-- results in this outcome if the group has Callsign "Enfield 9 1" on the 1st #UNIT of the group: -- -- Enfield-1 for Slot 1
-- -- -- Apollo for Slot 2 or Apollo 403 if Keepnumber is set
-- 'Victory 9' -- -- Apollo for Slot 3
-- -- -- Bengal-4 for Slot 4
--
function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
--self:I("GetCustomCallSign") --self:I("GetCustomCallSign")
@@ -2810,14 +2815,18 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
if self:IsAlive() then if self:IsAlive() then
local IsPlayer = self:IsPlayer() local IsPlayer = self:IsPlayer()
local shortcallsign = self:GetCallsign() or "unknown91" -- e.g.Uzi91, but we want Uzi 9 1 local shortcallsign = self:GetCallsign() or "unknown91" -- e.g.Uzi91, but we want Uzi 9 1
local callsignroot = string.match(shortcallsign, '(%a+)') -- Uzi local callsignroot = string.match(shortcallsign, '(%a+)') or "Ghost" -- Uzi
--self:I("CallSign = " .. callsignroot) --self:I("CallSign = " .. callsignroot)
local groupname = self:GetName() local groupname = self:GetName()
local callnumber = string.match(shortcallsign, "(%d+)$" ) or "91" -- 91 local callnumber = string.match(shortcallsign, "(%d+)$" ) or "91" -- 91
local callnumbermajor = string.char(string.byte(callnumber,1)) -- 9 local callnumbermajor = string.char(string.byte(callnumber,1)) -- 9
local callnumberminor = string.char(string.byte(callnumber,2)) -- 1 local callnumberminor = string.char(string.byte(callnumber,2)) -- 1
local personalized = false local personalized = false
if IsPlayer and string.find(groupname,"#") then
-- prioritize bespoke callsigns over parsing, prefer parsing over default callsigns
if CallsignTranslations and CallsignTranslations[callsignroot] then
callsignroot = CallsignTranslations[callsignroot]
elseif IsPlayer and string.find(groupname,"#") then
-- personalized flight name in group naming -- personalized flight name in group naming
if Keepnumber then if Keepnumber then
shortcallsign = string.match(groupname,"#(.+)") or "Ghost 111" -- Ghostrider 219 shortcallsign = string.match(groupname,"#(.+)") or "Ghost 111" -- Ghostrider 219
@@ -2831,10 +2840,6 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
personalized = true personalized = true
end end
if (not personalized) and CallsignTranslations and CallsignTranslations[callsignroot] then
callsignroot = CallsignTranslations[callsignroot]
end
if personalized then if personalized then
-- player personalized callsign -- player personalized callsign
-- remove trailing/leading spaces -- remove trailing/leading spaces

View File

@@ -0,0 +1,833 @@
--- **Wrapper** - DCS net functions.
--
-- Encapsules **multiplayer server** environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net)
--
-- ===
--
-- ### Author: **applevangelist**
-- # Last Update Feb 2023
--
-- ===
--
-- @module Wrapper.Net
-- @image Utils_Profiler.jpg
do
--- The NET class
-- @type NET
-- @field #string ClassName
-- @field #string Version
-- @field #string lid
-- @field #number BlockTime
-- @field #table BlockedPilots
-- @field #table KnownPilots
-- @field #string BlockMessage
-- @field #string UnblockMessage
-- @field #table BlockedUCIDs
-- @field #table BlockedSlots
-- @field #table BlockedSides
-- @extends Core.Fsm#FSM
---
-- @type NET.PlayerData
-- @field #string name
-- @field #string ucid
-- @field #number id
-- @field #number side
-- @field #number slot
--- Encapsules multiplayer environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net)
-- with some added FSM functions and options to block/unblock players in MP environments.
--
-- @field #NET
NET = {
ClassName = "NET",
Version = "0.1.0",
BlockTime = 600,
BlockedPilots = {},
BlockedUCIDs = {},
BlockedSides = {},
BlockedSlots = {},
KnownPilots = {},
BlockMessage = nil,
UnblockMessage = nil,
lid = nil,
}
--- Instantiate a new NET object.
-- @param #NET self
-- @return #NET self
function NET:New()
-- Inherit base.
local self = BASE:Inherit(self, FSM:New()) -- #NET
self.BlockTime = 600
self.BlockedPilots = {}
self.KnownPilots = {}
self:SetBlockMessage()
self:SetUnblockMessage()
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Run", "Running") -- Start FSM.
self:AddTransition("*", "PlayerJoined", "*")
self:AddTransition("*", "PlayerLeft", "*")
self:AddTransition("*", "PlayerDied", "*")
self:AddTransition("*", "PlayerEjected", "*")
self:AddTransition("*", "PlayerBlocked", "*")
self:AddTransition("*", "PlayerUnblocked", "*")
self:AddTransition("*", "Status", "*")
self:AddTransition("*", "Stop", "Stopped")
self.lid = string.format("NET %s | ",self.Version)
--- FSM Function OnAfterPlayerJoined.
-- @function [parent=#NET] OnAfterPlayerJoined
-- @param #NET self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Unit#UNIT Client Unit Object.
-- @param #string Name Name of joining Pilot.
-- @return #NET self
--- FSM Function OnAfterPlayerLeft.
-- @function [parent=#NET] OnAfterPlayerLeft
-- @param #NET self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Unit#UNIT Client Unit Object, might be nil.
-- @param #string Name Name of leaving Pilot.
-- @return #NET self
--- FSM Function OnAfterPlayerEjected.
-- @function [parent=#NET] OnAfterPlayerEjected
-- @param #NET self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Unit#UNIT Client Unit Object, might be nil.
-- @param #string Name Name of leaving Pilot.
-- @return #NET self
--- FSM Function OnAfterPlayerDied.
-- @function [parent=#NET] OnAfterPlayerDied
-- @param #NET self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Unit#UNIT Client Unit Object, might be nil.
-- @param #string Name Name of dead Pilot.
-- @return #NET self
--- FSM Function OnAfterPlayerBlocked.
-- @function [parent=#NET] OnAfterPlayerBlocked
-- @param #NET self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Client#CLIENT Client Client Object, might be nil.
-- @param #string Name Name of blocked Pilot.
-- @param #number Seconds Blocked for this number of seconds
-- @return #NET self
--- FSM Function OnAfterPlayerUnblocked.
-- @function [parent=#NET] OnAfterPlayerUnblocked
-- @param #NET self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Client#CLIENT Client Client Object, might be nil.
-- @param #string Name Name of unblocked Pilot.
-- @return #NET self
self:Run()
return self
end
--- [Internal] Check any blockers
-- @param #NET self
-- @param #string UCID
-- @param #string Name
-- @param #number PlayerID
-- @param #number PlayerSide
-- @param #string PlayerSlot
-- @return #boolean IsBlocked
function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot)
local blocked = false
local TNow = timer.getTime()
-- UCID
if UCID and self.BlockedUCIDs[UCID] and TNow < self.BlockedUCIDs[UCID] then
return true
end
-- ID/Name
if PlayerID and not Name then
Name = self:GetPlayerIDByName(Name)
end
-- Name
if Name and self.BlockedPilots[Name] and TNow < self.BlockedPilots[Name] then
return true
end
-- Side
if PlayerSide and self.BlockedSides[PlayerSide] and TNow < self.BlockedSides[PlayerSide] then
return true
end
-- Slot
if PlayerSlot and self.BlockedSlots[PlayerSlot] and TNow < self.BlockedSlots[PlayerSlot] then
return true
end
return blocked
end
--- [Internal] Event Handler
-- @param #NET self
-- @param Core.Event#EVENTDATA EventData
-- @return #NET self
function NET:_EventHandler(EventData)
self:T(self.lid .. " _EventHandler")
self:T2({Event = EventData.id})
local data = EventData -- Core.Event#EVENTDATA EventData
if data.id and data.IniUnit and (data.IniPlayerName or data.IniUnit:GetPlayerName()) then
-- Get Player Data
local name = data.IniPlayerName and data.IniPlayerName or data.IniUnit:GetPlayerName()
local ucid = self:GetPlayerUCID(nil,name)
local PlayerID = self:GetPlayerIDByName(name) or "none"
local PlayerSide, PlayerSlot = self:GetSlot(data.IniUnit)
local TNow = timer.getTime()
self:T(self.lid.."Event for: "..name.." | UCID: "..ucid)
-- Joining
if data.id == EVENTS.PlayerEnterUnit or data.id == EVENTS.PlayerEnterAircraft then
self:T(self.lid.."Pilot Joining: "..name.." | UCID: "..ucid)
-- Check for blockages
local blocked = self:IsAnyBlocked(ucid,name,PlayerID,PlayerSide,PlayerSlot)
if blocked and PlayerID and tonumber(PlayerID) ~= 1 then
-- block pilot
local outcome = net.force_player_slot(tonumber(PlayerID), 0, '' )
else
self.KnownPilots[name] = {
name = name,
ucid = ucid,
id = PlayerID,
side = PlayerSide,
slot = PlayerSlot,
}
self:__PlayerJoined(1,data.IniUnit,name)
return self
end
end
-- Leaving
if data.id == EVENTS.PlayerLeaveUnit and self.KnownPilots[name] then
self:T(self.lid.."Pilot Leaving: "..name.." | UCID: "..ucid)
self:__PlayerLeft(1,data.IniUnit,name)
self.KnownPilots[name] = false
return self
end
-- Ejected
if data.id == EVENTS.Ejection and self.KnownPilots[name] then
self:T(self.lid.."Pilot Ejecting: "..name.." | UCID: "..ucid)
self:__PlayerEjected(1,data.IniUnit,name)
self.KnownPilots[name] = false
return self
end
-- Dead, Crash, Suicide
if (data.id == EVENTS.PilotDead or data.id == EVENTS.SelfKillPilot or data.id == EVENTS.Crash) and self.KnownPilots[name] then
self:T(self.lid.."Pilot Dead: "..name.." | UCID: "..ucid)
self:__PlayerDied(1,data.IniUnit,name)
self.KnownPilots[name] = false
return self
end
end
return self
end
--- Block a player.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client CLIENT object.
-- @param #string PlayerName (optional) Name of the player.
-- @param #number Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @param #string Message (optional) Message to be sent via chat.
-- @return #NET self
function NET:BlockPlayer(Client,PlayerName,Seconds,Message)
self:T({PlayerName,Seconds,Message})
local name = PlayerName
if Client and (not PlayerName) then
name = Client:GetPlayerName()
elseif PlayerName then
name = PlayerName
else
self:F(self.lid.."Block: No Client or PlayerName given or nothing found!")
return self
end
local ucid = self:GetPlayerUCID(Client,name)
local addon = Seconds or self.BlockTime
self.BlockedPilots[name] = timer.getTime()+addon
self.BlockedUCIDs[ucid] = timer.getTime()+addon
local message = Message or self.BlockMessage
if name then
self:SendChatToPlayer(message,name)
else
self:SendChat(name..": "..message)
end
self:__PlayerBlocked(1,Client,name,Seconds)
local PlayerID = self:GetPlayerIDByName(name)
if PlayerID and tonumber(PlayerID) ~= 1 then
local outcome = net.force_player_slot(tonumber(PlayerID), 0, '' )
end
return self
end
--- Block a SET_CLIENT of players
-- @param #NET self
-- @param Core.Set#SET_CLIENT PlayerSet The SET to block.
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @param #string Message (optional) Message to be sent via chat.
-- @return #NET self
function NET:BlockPlayerSet(PlayerSet,Seconds,Message)
self:T({PlayerSet.Set,Seconds,Message})
local addon = Seconds or self.BlockTime
local message = Message or self.BlockMessage
for _,_client in pairs(PlayerSet.Set) do
local name = _client:GetPlayerName()
self:BlockPlayer(_client,name,addon,message)
end
return self
end
--- Unblock a SET_CLIENT of players
-- @param #NET self
-- @param Core.Set#SET_CLIENT PlayerSet The SET to unblock.
-- @param #string Message (optional) Message to be sent via chat.
-- @return #NET self
function NET:UnblockPlayerSet(PlayerSet,Message)
self:T({PlayerSet.Set,Seconds,Message})
local message = Message or self.UnblockMessage
for _,_client in pairs(PlayerSet.Set) do
local name = _client:GetPlayerName()
self:UnblockPlayer(_client,name,message)
end
return self
end
--- Block a specific UCID of a player, does NOT automatically kick the player with the UCID if already joined.
-- @param #NET self
-- @param #string ucid
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @return #NET self
function NET:BlockUCID(ucid,Seconds)
self:T({ucid,Seconds})
local addon = Seconds or self.BlockTime
self.BlockedUCIDs[ucid] = timer.getTime()+addon
return self
end
--- Unblock a specific UCID of a player
-- @param #NET self
-- @param #string ucid
-- @return #NET self
function NET:UnblockUCID(ucid)
self:T({ucid})
self.BlockedUCIDs[ucid] = nil
return self
end
--- Block a specific coalition side, does NOT automatically kick all players of that side or kick out joined players
-- @param #NET self
-- @param #number side The side to block - 1 : Red, 2 : Blue
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @return #NET self
function NET:BlockSide(Side,Seconds)
self:T({Side,Seconds})
local addon = Seconds or self.BlockTime
if Side == 1 or Side == 2 then
self.BlockedSides[Side] = timer.getTime()+addon
end
return self
end
--- Unblock a specific coalition side. Does NOT unblock specifically blocked playernames or UCIDs.
-- @param #number side The side to block - 1 : Red, 2 : Blue
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @return #NET self
function NET:UnblockSide(Side,Seconds)
self:T({Side,Seconds})
local addon = Seconds or self.BlockTime
if Side == 1 or Side == 2 then
self.BlockedSides[Side] = nil
end
return self
end
--- Block a specific player slot, does NOT automatically kick a player in that slot or kick out joined players
-- @param #NET self
-- @param #string slot The slot to block
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @return #NET self
function NET:BlockSlot(Slot,Seconds)
self:T({Slot,Seconds})
local addon = Seconds or self.BlockTime
self.BlockedSlots[Slot] = timer.getTime()+addon
return self
end
--- Unblock a specific slot.
-- @param #string slot The slot to block
-- @return #NET self
function NET:UnblockSlot(Slot)
self:T({Slot})
self.BlockedSlots[Slot] = nil
return self
end
--- Unblock a player.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client CLIENT object
-- @param #string PlayerName (optional) Name of the player.
-- @param #string Message (optional) Message to be sent via chat.
-- @return #NET self
function NET:UnblockPlayer(Client,PlayerName,Message)
local name = PlayerName
if Client then
name = Client:GetPlayerName()
elseif PlayerName then
name = PlayerName
else
self:F(self.lid.."Unblock: No PlayerName given or not found!")
return self
end
local ucid = self:GetPlayerUCID(Client,name)
self.BlockedPilots[name] = nil
self.BlockedUCIDs[ucid] = nil
local message = Message or self.UnblockMessage
if name then
self:SendChatToPlayer(message,name)
else
self:SendChat(name..": "..message)
end
self:__PlayerUnblocked(1,Client,name)
return self
end
--- Set block chat message.
-- @param #NET self
-- @param #string Text The message
-- @return #NET self
function NET:SetBlockMessage(Text)
self.BlockMessage = Text or "You are blocked from joining. Wait time is: "..self.BlockTime.." seconds!"
return self
end
--- Set block time in seconds.
-- @param #NET self
-- @param #number Seconds Numnber of seconds this block will last. Defaults to 600.
-- @return #NET self
function NET:SetBlockTime(Seconds)
self.BlockTime = Seconds or 600
return self
end
--- Set unblock chat message.
-- @param #NET self
-- @param #string Text The message
-- @return #NET self
function NET:SetUnblockMessage(Text)
self.UnblockMessage = Text or "You are unblocked now and can join again."
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)
if not Name then return nil end
local playerList = self:GetPlayerList()
self:T({playerList})
for i=1,#playerList do
local playerName = net.get_name(i)
self:T({playerName})
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)
if Client then
local name = Client:GetPlayerName()
local id = self:GetPlayerIDByName(name)
return id
else
return nil
end
end
--- Send chat message to a specific player using the CLIENT object.
-- @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:SendChatToClient(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
--- Send chat message to a specific player using the player name
-- @param #NET self
-- @param #string Message The text message
-- @param #string ToPlayer Player receiving the message
-- @param #string FromPlayer(Optional) Player sending the message
-- @return #NET self
function NET:SendChatToPlayer(Message, ToPlayer, FromPlayer)
local PlayerId = self:GetPlayerIDByName(ToPlayer)
local FromId = self:GetPlayerIDByName(FromPlayer)
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
--- Get player UCID from player CLIENT object or player name. Provide either one.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client object to be used.
-- @param #string Name Player name to be used.
-- @return #boolean success
function NET:GetPlayerUCID(Client,Name)
local PlayerID = nil
if Client then
PlayerID = self:GetPlayerIDFromClient(Client)
elseif Name then
PlayerID = self:GetPlayerIDByName(Name)
else
self:E(self.lid.."Neither client nor name provided!")
end
local ucid = net.get_player_info(tonumber(PlayerID), 'ucid')
return ucid
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. Effectively the same as 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 and tonumber(PlayerID) ~= 1 then
return net.force_player_slot(tonumber(PlayerID), SideID, SlotID or '' )
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
--- Get some data of pilots who have currently joined
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client Provide either the client object whose data to find **or**
-- @param #string Name The player name whose data to find
-- @return #table Table of #NET.PlayerData or nil if not found
function NET:GetKnownPilotData(Client,Name)
local name = Name
if Client and not Name then
name = Client:GetPlayerName()
end
if name then
return self.KnownPilots[name]
else
return nil
end
end
--- Status - housekeeping
-- @param #NET self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #NET self
function NET:onafterStatus(From,Event,To)
self:T({From,Event,To})
local function HouseHold(tavolo)
local TNow = timer.getTime()
for _,entry in pairs (tavolo) do
if entry >= TNow then entry = nil end
end
end
HouseHold(self.BlockedPilots)
HouseHold(self.BlockedSides)
HouseHold(self.BlockedSlots)
HouseHold(self.BlockedUCIDs)
if self:Is("Running") then
self:__Status(-60)
end
return self
end
--- Stop the event functions
-- @param #NET self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #NET self
function NET:onafterRun(From,Event,To)
self:T({From,Event,To})
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler)
self:HandleEvent(EVENTS.PilotDead,self._EventHandler)
self:HandleEvent(EVENTS.Ejection,self._EventHandler)
self:HandleEvent(EVENTS.Crash,self._EventHandler)
self:HandleEvent(EVENTS.SelfKillPilot,self._EventHandler)
self:__Status(-30)
end
--- Stop the event functions
-- @param #NET self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #NET self
function NET:onafterStop(From,Event,To)
self:T({From,Event,To})
self:UnHandleEvent(EVENTS.PlayerEnterUnit)
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
self:UnHandleEvent(EVENTS.PlayerLeaveUnit)
self:UnHandleEvent(EVENTS.PilotDead)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.SelfKillPilot)
return self
end
-------------------------------------------------------------------------------
-- End of NET
-------------------------------------------------------------------------------
end

View File

@@ -845,6 +845,78 @@ function POSITIONABLE:GetVelocityKNOTS()
return UTILS.MpsToKnots( self:GetVelocityMPS() ) return UTILS.MpsToKnots( self:GetVelocityMPS() )
end end
--- Returns the true airspeed (TAS). This is calculated from the current velocity minus wind in 3D.
-- @param #POSITIONABLE self
-- @return #number TAS in m/s. Returns 0 if the POSITIONABLE does not exist.
function POSITIONABLE:GetAirspeedTrue()
-- TAS
local tas=0
-- Get current coordinate.
local coord=self:GetCoord()
if coord then
-- Altitude in meters.
local alt=coord.y
-- Wind velocity vector.
local wvec3=coord:GetWindVec3(alt, false)
-- Velocity vector.
local vvec3=self:GetVelocityVec3()
--GS=TAS+WIND ==> TAS=GS-WIND
local tasvec3=UTILS.VecSubstract(vvec3, wvec3)
-- True airspeed in m/s
tas=UTILS.VecNorm(tasvec3)
end
return tas
end
--- Returns the indicated airspeed (IAS).
-- The IAS is calculated from the TAS under the approximation that TAS increases by ~2% with every 1000 feet altitude ASL.
-- @param #POSITIONABLE self
-- @param #number oatcorr (Optional) Outside air temperature (OAT) correction factor. Default 0.017 (=1.7%).
-- @return #number IAS in m/s. Returns 0 if the POSITIONABLE does not exist.
function POSITIONABLE:GetAirspeedIndicated(oatcorr)
-- Get true airspeed.
local tas=self:GetAirspeedTrue()
-- Get altitude.
local altitude=self:GetAltitude()
-- Convert TAS to IAS.
local ias=UTILS.TasToIas(tas, altitude, oatcorr)
return ias
end
--- Returns the horizonal speed relative to eath's surface. The vertical component of the velocity vector is projected out (set to zero).
-- @param #POSITIONABLE self
-- @return #number Ground speed in m/s. Returns 0 if the POSITIONABLE does not exist.
function POSITIONABLE:GetGroundSpeed()
local gs=0
local vel=self:GetVelocityVec3()
if vel then
local vec2={x=vel.x, y=vel.z}
gs=UTILS.Vec2Norm(vel)
end
return gs
end
--- Returns the Angle of Attack of a POSITIONABLE. --- Returns the Angle of Attack of a POSITIONABLE.
-- @param #POSITIONABLE self -- @param #POSITIONABLE self
-- @return #number Angle of attack in degrees. -- @return #number Angle of attack in degrees.
@@ -1696,6 +1768,7 @@ do -- Cargo
["tt_DSHK"] = 6, ["tt_DSHK"] = 6,
["HL_KORD"] = 6, ["HL_KORD"] = 6,
["HL_DSHK"] = 6, ["HL_DSHK"] = 6,
["CCKW_353"] = 16, --GMC CCKW 2½-ton 6×6 truck, estimating 16 soldiers
} }
-- Assuming that each passenger weighs 95 kg on average. -- Assuming that each passenger weighs 95 kg on average.

View File

@@ -4,7 +4,7 @@
-- --
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- --
-- ### Contributions: **Applevangelist** -- ### Contributions: **Applevangelist**, **funkyfranky**
-- --
-- === -- ===
-- --
@@ -12,12 +12,12 @@
-- @image Wrapper_Scenery.JPG -- @image Wrapper_Scenery.JPG
--- SCENERY Class
--- @type SCENERY -- @type SCENERY
-- @field #string ClassName -- @field #string ClassName Name of the class.
-- @field #string SceneryName -- @field #string SceneryName Name of the scenery object.
-- @field #DCS.Object SceneryObject -- @field DCS#Object SceneryObject DCS scenery object.
-- @field #number Life0 -- @field #number Life0 Initial life points.
-- @extends Wrapper.Positionable#POSITIONABLE -- @extends Wrapper.Positionable#POSITIONABLE
@@ -37,12 +37,16 @@ SCENERY = {
--- Register scenery object as POSITIONABLE. --- Register scenery object as POSITIONABLE.
--@param #SCENERY self --@param #SCENERY self
--@param #string SceneryName Scenery name. --@param #string SceneryName Scenery name.
--@param #DCS.Object SceneryObject DCS scenery object. --@param DCS#Object SceneryObject DCS scenery object.
--@return #SCENERY Scenery object. --@return #SCENERY Scenery object.
function SCENERY:Register( SceneryName, SceneryObject ) function SCENERY:Register( SceneryName, SceneryObject )
local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) ) local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) )
self.SceneryName = SceneryName self.SceneryName = SceneryName
self.SceneryObject = SceneryObject self.SceneryObject = SceneryObject
if self.SceneryObject then if self.SceneryObject then
self.Life0 = self.SceneryObject:getLife() self.Life0 = self.SceneryObject:getLife()
else else
@@ -53,7 +57,7 @@ end
--- Obtain DCS Object from the SCENERY Object. --- Obtain DCS Object from the SCENERY Object.
--@param #SCENERY self --@param #SCENERY self
--@return #DCS.Object DCS scenery object. --@return DCS#Object DCS scenery object.
function SCENERY:GetDCSObject() function SCENERY:GetDCSObject()
return self.SceneryObject return self.SceneryObject
end end
@@ -69,7 +73,7 @@ function SCENERY:GetLife()
return life return life
end end
--- Get current initial life points from the SCENERY Object. --- Get initial life points of the SCENERY Object.
--@param #SCENERY self --@param #SCENERY self
--@return #number life --@return #number life
function SCENERY:GetLife0() function SCENERY:GetLife0()
@@ -90,7 +94,7 @@ function SCENERY:IsDead()
return self:GetLife() < 1 and true or false return self:GetLife() < 1 and true or false
end end
--- Get the threat level of a SCENERY object. Always 0. --- Get the threat level of a SCENERY object. Always 0 as scenery does not pose a threat to anyone.
--@param #SCENERY self --@param #SCENERY self
--@return #number Threat level 0. --@return #number Threat level 0.
--@return #string "Scenery". --@return #string "Scenery".

View File

@@ -167,7 +167,7 @@ end
--- Get the DCS unit object. --- Get the DCS unit object.
-- @param #UNIT self -- @param #UNIT self
-- @return DCS#Unit -- @return DCS#Unit The DCS unit object.
function UNIT:GetDCSObject() function UNIT:GetDCSObject()
local DCSUnit = Unit.getByName( self.UnitName ) local DCSUnit = Unit.getByName( self.UnitName )
@@ -325,14 +325,19 @@ function UNIT:IsAlive()
local DCSUnit = self:GetDCSObject() -- DCS#Unit local DCSUnit = self:GetDCSObject() -- DCS#Unit
if DCSUnit then if DCSUnit then
local UnitIsAlive = DCSUnit:isExist() and DCSUnit:isActive() local UnitIsAlive = DCSUnit:isExist() and DCSUnit:isActive() -- and DCSUnit:getLife() > 1
return UnitIsAlive return UnitIsAlive
end end
return nil return nil
end end
--- Returns if the Unit is dead.
-- @param #UNIT self
-- @return #boolean `true` if Unit is dead, else false or nil if the unit does not exist
function UNIT:IsDead()
return not self:IsAlive()
end
--- Returns the Unit's callsign - the localized string. --- Returns the Unit's callsign - the localized string.
-- @param #UNIT self -- @param #UNIT self
@@ -626,7 +631,7 @@ function UNIT:IsFuelSupply()
return false return false
end end
--- Returns the unit's group if it exist and nil otherwise. --- Returns the unit's group if it exists and nil otherwise.
-- @param Wrapper.Unit#UNIT self -- @param Wrapper.Unit#UNIT self
-- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist. -- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist.
function UNIT:GetGroup() function UNIT:GetGroup()
@@ -915,7 +920,7 @@ function UNIT:GetLife()
local DCSUnit = self:GetDCSObject() local DCSUnit = self:GetDCSObject()
if DCSUnit then if DCSUnit and DCSUnit:isExist() then
local UnitLife = DCSUnit:getLife() local UnitLife = DCSUnit:getLife()
return UnitLife return UnitLife
end end
@@ -967,6 +972,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

@@ -0,0 +1,839 @@
--- **Wrapper** - Weapon functions.
--
-- ## Main Features:
--
-- * Convenient access to DCS API functions
-- * Track weapon and get impact position
-- * Get launcher and target of weapon
-- * Define callback function when weapon impacts
-- * Define callback function when tracking weapon
-- * Mark impact points on F10 map
-- * Put coloured smoke on impact points
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Wrapper%20-%20Weapon).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Wrapper.Weapon
-- @image Wrapper_Weapon.png
--- WEAPON class.
-- @type WEAPON
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity level.
-- @field #string lid Class id string for output to DCS log file.
-- @field DCS#Weapon weapon The DCS weapon object.
-- @field #string name Name of the weapon object.
-- @field #string typeName Type name of the weapon.
-- @field #number category Weapon category 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB, 4=TORPEDO (Weapon.Category.X).
-- @field #number categoryMissile Missile category 0=AAM, 1=SAM, 2=BM, 3=ANTI_SHIP, 4=CRUISE, 5=OTHER (Weapon.MissileCategory.X).
-- @field #number coalition Coalition ID.
-- @field #number country Country ID.
-- @field DCS#Desc desc Descriptor table.
-- @field DCS#Unit launcher Launcher DCS unit.
-- @field Wrapper.Unit#UNIT launcherUnit Launcher Unit.
-- @field #string launcherName Name of launcher unit.
-- @field #number dtTrack Time step in seconds for tracking scheduler.
-- @field #function impactFunc Callback function for weapon impact.
-- @field #table impactArg Optional arguments for the impact callback function.
-- @field #function trackFunc Callback function when weapon is tracked and alive.
-- @field #table trackArg Optional arguments for the track callback function.
-- @field DCS#Vec3 vec3 Last known 3D position vector of the tracked weapon.
-- @field DCS#Position3 pos3 Last known 3D position and direction vector of the tracked weapon.
-- @field Core.Point#COORDINATE coordinate Coordinate object of the weapon. Can be used in other classes.
-- @field DCS#Vec3 impactVec3 Impact 3D vector.
-- @field Core.Point#COORDINATE impactCoord Impact coordinate.
-- @field #number trackScheduleID Tracking scheduler ID. Can be used to remove/destroy the scheduler function.
-- @field #boolean tracking If `true`, scheduler will keep tracking. Otherwise, function will return nil and stop tracking.
-- @field #boolean impactMark If `true`, the impact point is marked on the F10 map. Requires tracking to be started.
-- @field #boolean impactSmoke If `true`, the impact point is marked by smoke. Requires tracking to be started.
-- @field #number impactSmokeColor Colour of impact point smoke.
-- @field #boolean impactDestroy If `true`, destroy weapon before impact. Requires tracking to be started and sufficiently small time step.
-- @field #number impactDestroyDist Distance in meters to the estimated impact point. If smaller, then weapon is destroyed.
-- @field #number distIP Distance in meters for the intercept point estimation.
-- @field Wrapper.Unit#UNIT target Last known target.
-- @extends Wrapper.Positionable#POSITIONABLE
--- *In the long run, the sharpest weapon of all is a kind and gentle spirit.* -- Anne Frank
--
-- ===
--
-- # The WEAPON Concept
--
-- The WEAPON class offers an easy-to-use wrapper interface to all DCS API functions.
--
-- Probably, the most striking highlight is that the position of the weapon can be tracked and its impact position can be determined, which is not
-- possible with the native DCS scripting engine functions.
--
-- **Note** that this wrapper class is different from most others as weapon objects cannot be found with a DCS API function like `getByName()`.
-- They can only be found in DCS events like the "Shot" event, where the weapon object is contained in the event data.
--
-- # Tracking
--
-- The status of the weapon can be tracked with the @{#WEAPON.StartTrack} function. This function will try to determin the position of the weapon in (normally) relatively
-- small time steps. The time step can be set via the @{#WEAPON.SetTimeStepTrack} function and is by default set to 0.01 seconds.
--
-- Once the position cannot be retrieved any more, the weapon has impacted (or was destroyed otherwise) and the last known position is safed as the impact point.
-- The impact point can be accessed with the @{#WEAPON.GetImpactVec3} or @{#WEAPON.GetImpactCoordinate} functions.
--
-- ## Impact Point Marking
--
-- You can mark the impact point on the F10 map with @{#WEAPON.SetMarkImpact}.
--
-- You can also trigger coloured smoke at the impact point via @{#WEAPON.SetSmokeImpact}.
--
-- ## Callback functions
--
-- It is possible to define functions that are called during the tracking of the weapon and upon impact, which help you to customize further actions.
--
-- ### Callback on Impact
--
-- The function called on impact can be set with @{#WEAPON.SetFuncImpact}
--
-- ### Callback when Tracking
--
-- The function called each time the weapon status is tracked can be set with @{#WEAPON.SetFuncTrack}
--
-- # Target
--
-- If the weapon has a specific target, you can get it with the @{#WEAPON.GetTarget} function. Note that the object, which is returned can vary. Normally, it is a UNIT
-- but it could also be a STATIC object.
--
-- Also note that the weapon does not always have a target, it can loose a target and re-aquire it and the target might change to another unit.
--
-- You can get the target name with the @{#WEAPON.GetTargetName} function.
--
-- The distance to the target is returned by the @{#WEAPON.GetTargetDistance} function.
--
-- # Category
--
-- The category (bomb, rocket, missile, shell, torpedo) of the weapon can be retrieved with the @{#WEAPON.GetCategory} function.
--
-- You can check if the weapon is a
--
-- * bomb with @{#WEAPON.IsBomb}
-- * rocket with @{#WEAPON.IsRocket}
-- * missile with @{#WEAPON.IsMissile}
-- * shell with @{#WEAPON.IsShell}
-- * torpedo with @{#WEAPON.IsTorpedo}
--
-- # Parameters
--
-- You can get various parameters of the weapon, *e.g.*
--
-- * position: @{#WEAPON.GetVec3}, @{#WEAPON.GetVec2 }, @{#WEAPON.GetCoordinate}
-- * speed: @{#WEAPON.GetSpeed}
-- * coalition: @{#WEAPON.GetCoalition}
-- * country: @{#WEAPON.GetCountry}
--
-- # Dependencies
--
-- This class is used (at least) in the MOOSE classes:
--
-- * RANGE (to determine the impact points of bombs and missiles)
-- * ARTY (to destroy and replace shells with smoke or illumination)
-- * FOX (to destroy the missile before it hits the target)
--
-- @field #WEAPON
WEAPON = {
ClassName = "WEAPON",
verbose = 0,
}
--- WEAPON class version.
-- @field #string version
WEAPON.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-- TODO: Destroy before impact.
-- TODO: Monitor target.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new WEAPON object from the DCS weapon object.
-- @param #WEAPON self
-- @param DCS#Weapon WeaponObject The DCS weapon object.
-- @return #WEAPON self
function WEAPON:New(WeaponObject)
-- Nil check on object.
if WeaponObject==nil then
env.error("ERROR: Weapon object does NOT exist")
return nil
end
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, POSITIONABLE:New("Weapon")) -- #WEAPON
-- Set DCS weapon object.
self.weapon=WeaponObject
-- Descriptors containing a lot of info.
self.desc=WeaponObject:getDesc()
-- This gives the object category which is always Object.Category.WEAPON!
--self.category=WeaponObject:getCategory()
-- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X)
self.category = self.desc.category
if self:IsMissile() and self.desc.missileCategory then
self.categoryMissile=self.desc.missileCategory
end
-- Get type name.
self.typeName=WeaponObject:getTypeName() or "Unknown Type"
-- Get name of object. Usually a number like "1234567".
self.name=WeaponObject:getName()
-- Get coaliton of weapon.
self.coalition=WeaponObject:getCoalition()
-- Get country of weapon.
self.country=WeaponObject:getCountry()
-- Get DCS unit of the launcher.
self.launcher=WeaponObject:getLauncher()
-- Get launcher of weapon.
self.launcherName="Unknown Launcher"
if self.launcher then
self.launcherName=self.launcher:getName()
self.launcherUnit=UNIT:Find(self.launcher)
end
-- Init the coordinate of the weapon from that of the launcher.
self.coordinate=COORDINATE:NewFromVec3(self.launcher:getPoint())
-- Set log ID.
self.lid=string.format("[%s] %s | ", self.typeName, self.name)
-- Set default parameters
self:SetTimeStepTrack()
self:SetDistanceInterceptPoint()
-- Debug info.
local text=string.format("Weapon v%s\nName=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d, Launcher=%s",
self.version, self.name, self.typeName, self.category, self.coalition, self.country, self.launcherName)
self:T(self.lid..text)
-- Descriptors.
self:T2(self.desc)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User API Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set verbosity level.
-- @param #WEAPON self
-- @param #number VerbosityLevel Level of output (higher=more). Default 0.
-- @return #WEAPON self
function WEAPON:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
--- Set track position time step.
-- @param #WEAPON self
-- @param #number TimeStep Time step in seconds when the position is updated. Default 0.01 sec ==> 100 evaluations per second.
-- @return #WEAPON self
function WEAPON:SetTimeStepTrack(TimeStep)
self.dtTrack=TimeStep or 0.01
return self
end
--- Set distance of intercept point for estimated impact point.
-- If the weapon cannot be tracked any more, the intercept point from its last known position and direction is used to get
-- a better approximation of the impact point. Can be useful when using longer time steps in the tracking and still achieve
-- a good result on the impact point.
-- It uses the DCS function [getIP](https://wiki.hoggitworld.com/view/DCS_func_getIP).
-- @param #WEAPON self
-- @param #number Distance Distance in meters. Default is 50 m. Set to 0 to deactivate.
-- @return #WEAPON self
function WEAPON:SetDistanceInterceptPoint(Distance)
self.distIP=Distance or 50
return self
end
--- Mark impact point on the F10 map. This requires that the tracking has been started.
-- @param #WEAPON self
-- @param #boolean Switch If `true` or nil, impact is marked.
-- @return #WEAPON self
function WEAPON:SetMarkImpact(Switch)
if Switch==false then
self.impactMark=false
else
self.impactMark=true
end
return self
end
--- Put smoke on impact point. This requires that the tracking has been started.
-- @param #WEAPON self
-- @param #boolean Switch If `true` or nil, impact is smoked.
-- @param #number SmokeColor Color of smoke. Default is `SMOKECOLOR.Red`.
-- @return #WEAPON self
function WEAPON:SetSmokeImpact(Switch, SmokeColor)
if Switch==false then
self.impactSmoke=false
else
self.impactSmoke=true
end
self.impactSmokeColor=SmokeColor or SMOKECOLOR.Red
return self
end
--- Set callback function when weapon is tracked and still alive. The first argument will be the WEAPON object.
-- Note that this can be called many times per second. So be careful for performance reasons.
-- @param #WEAPON self
-- @param #function FuncTrack Function called during tracking.
-- @param ... Optional function arguments.
-- @return #WEAPON self
function WEAPON:SetFuncTrack(FuncTrack, ...)
self.trackFunc=FuncTrack
self.trackArg=arg or {}
return self
end
--- Set callback function when weapon impacted or was destroyed otherwise, *i.e.* cannot be tracked any more.
-- @param #WEAPON self
-- @param #function FuncImpact Function called once the weapon impacted.
-- @param ... Optional function arguments.
-- @return #WEAPON self
--
-- @usage
-- -- Function called on impact.
-- local function OnImpact(Weapon)
-- Weapon:GetImpactCoordinate():MarkToAll("Impact Coordinate of weapon")
-- end
--
-- -- Set which function to call.
-- myweapon:SetFuncImpact(OnImpact)
--
-- -- Start tracking.
-- myweapon:Track()
--
function WEAPON:SetFuncImpact(FuncImpact, ...)
self.impactFunc=FuncImpact
self.impactArg=arg or {}
return self
end
--- Get the unit that launched the weapon.
-- @param #WEAPON self
-- @return Wrapper.Unit#UNIT Laucher unit.
function WEAPON:GetLauncher()
return self.launcherUnit
end
--- Get the target, which the weapon is guiding to.
-- @param #WEAPON self
-- @return Wrapper.Object#OBJECT The target object, which can be a UNIT or STATIC object.
function WEAPON:GetTarget()
local target=nil --Wrapper.Object#OBJECT
if self.weapon then
-- Get the DCS target object, which can be a Unit, Weapon, Static, Scenery, Airbase.
local object=self.weapon:getTarget()
if object then
-- Get object category.
local category=object:getCategory()
--Target name
local name=object:getName()
-- Debug info.
self:T(self.lid..string.format("Got Target Object %s, category=%d", object:getName(), category))
if category==Object.Category.UNIT then
target=UNIT:FindByName(name)
elseif category==Object.Category.STATIC then
target=STATIC:FindByName(name, false)
elseif category==Object.Category.SCENERY then
self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!"))
else
self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category))
end
end
end
return target
end
--- Get the distance to the current target the weapon is guiding to.
-- @param #WEAPON self
-- @param #function ConversionFunction (Optional) Conversion function from meters to desired unit, *e.g.* `UTILS.MpsToKmph`.
-- @return #number Distance from weapon to target in meters.
function WEAPON:GetTargetDistance(ConversionFunction)
-- Get the target of the weapon.
local target=self:GetTarget() --Wrapper.Unit#UNIT
local distance=nil
if target then
-- Current position of target.
local tv3=target:GetVec3()
-- Current position of weapon.
local wv3=self:GetVec3()
if tv3 and wv3 then
distance=UTILS.VecDist3D(tv3, wv3)
if ConversionFunction then
distance=ConversionFunction(distance)
end
end
end
return distance
end
--- Get name the current target the weapon is guiding to.
-- @param #WEAPON self
-- @return #string Name of the target or "None" if no target.
function WEAPON:GetTargetName()
-- Get the target of the weapon.
local target=self:GetTarget() --Wrapper.Unit#UNIT
local name="None"
if target then
name=target:GetName()
end
return name
end
--- Get velocity vector of weapon.
-- @param #WEAPON self
-- @return DCS#Vec3 Velocity vector with x, y and z components in meters/second.
function WEAPON:GetVelocityVec3()
local Vvec3=nil
if self.weapon then
Vvec3=self.weapon:getVelocity()
end
return Vvec3
end
--- Get speed of weapon.
-- @param #WEAPON self
-- @param #function ConversionFunction (Optional) Conversion function from m/s to desired unit, *e.g.* `UTILS.MpsToKmph`.
-- @return #number Speed in meters per second.
function WEAPON:GetSpeed(ConversionFunction)
local speed=nil
if self.weapon then
local v=self:GetVelocityVec3()
speed=UTILS.VecNorm(v)
if ConversionFunction then
speed=ConversionFunction(speed)
end
end
return speed
end
--- Get the current 3D position vector.
-- @param #WEAPON self
-- @return DCS#Vec3 Current position vector in 3D.
function WEAPON:GetVec3()
local vec3=nil
if self.weapon then
vec3=self.weapon:getPoint()
end
return vec3
end
--- Get the current 2D position vector.
-- @param #WEAPON self
-- @return DCS#Vec2 Current position vector in 2D.
function WEAPON:GetVec2()
local vec3=self:GetVec3()
if vec3 then
local vec2={x=vec3.x, y=vec3.z}
return vec2
end
return nil
end
--- Get type name.
-- @param #WEAPON self
-- @return #string The type name.
function WEAPON:GetTypeName()
return self.typeName
end
--- Get coalition.
-- @param #WEAPON self
-- @return #number Coalition ID.
function WEAPON:GetCoalition()
return self.coalition
end
--- Get country.
-- @param #WEAPON self
-- @return #number Country ID.
function WEAPON:GetCountry()
return self.country
end
--- Get DCS object.
-- @param #WEAPON self
-- @return DCS#Weapon The weapon object.
function WEAPON:GetDCSObject()
-- This polymorphic function is used in Wrapper.Identifiable#IDENTIFIABLE
return self.weapon
end
--- Get the impact position vector. Note that this might not exist if the weapon has not impacted yet!
-- @param #WEAPON self
-- @return DCS#Vec3 Impact position vector (if any).
function WEAPON:GetImpactVec3()
return self.impactVec3
end
--- Get the impact coordinate. Note that this might not exist if the weapon has not impacted yet!
-- @param #WEAPON self
-- @return Core.Point#COORDINATE Impact coordinate (if any).
function WEAPON:GetImpactCoordinate()
return self.impactCoord
end
--- Check if weapon is in the air. Obviously not really useful for torpedos. Well, then again, this is DCS...
-- @param #WEAPON self
-- @return #boolean If `true`, weapon is in the air and `false` if not. Returns `nil` if weapon object itself is `nil`.
function WEAPON:InAir()
local inAir=nil
if self.weapon then
inAir=self.weapon:inAir()
end
return inAir
end
--- Check if weapon object (still) exists.
-- @param #WEAPON self
-- @return #boolean If `true`, the weapon object still exists and `false` otherwise. Returns `nil` if weapon object itself is `nil`.
function WEAPON:IsExist()
local isExist=nil
if self.weapon then
isExist=self.weapon:isExist()
end
return isExist
end
--- Check if weapon is a bomb.
-- @param #WEAPON self
-- @return #boolean If `true`, is a bomb.
function WEAPON:IsBomb()
return self.category==Weapon.Category.BOMB
end
--- Check if weapon is a missile.
-- @param #WEAPON self
-- @return #boolean If `true`, is a missile.
function WEAPON:IsMissile()
return self.category==Weapon.Category.MISSILE
end
--- Check if weapon is a rocket.
-- @param #WEAPON self
-- @return #boolean If `true`, is a missile.
function WEAPON:IsRocket()
return self.category==Weapon.Category.ROCKET
end
--- Check if weapon is a shell.
-- @param #WEAPON self
-- @return #boolean If `true`, is a shell.
function WEAPON:IsShell()
return self.category==Weapon.Category.SHELL
end
--- Check if weapon is a torpedo.
-- @param #WEAPON self
-- @return #boolean If `true`, is a torpedo.
function WEAPON:IsTorpedo()
return self.category==Weapon.Category.TORPEDO
end
--- Destroy the weapon object.
-- @param #WEAPON self
-- @param #number Delay Delay before destroy in seconds.
-- @return #WEAPON self
function WEAPON:Destroy(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, WEAPON.Destroy, self, 0)
else
if self.weapon then
self:T(self.lid.."Destroying Weapon NOW!")
self:StopTrack()
self.weapon:destroy()
end
end
return self
end
--- Start tracking the weapon until it impacts or is destroyed otherwise.
-- The position of the weapon is monitored in small time steps. Once the position cannot be determined anymore, the monitoring is stopped and the last known position is
-- the (approximate) impact point. Of course, the smaller the time step, the better the position can be determined. However, this can hit the performance as many
-- calculations per second need to be carried out.
-- @param #WEAPON self
-- @param #number Delay Delay in seconds before the tracking starts. Default 0.001 sec.
-- @return #WEAPON self
function WEAPON:StartTrack(Delay)
-- Set delay before start.
Delay=math.max(Delay or 0.001, 0.001)
-- Debug info.
self:T(self.lid..string.format("Start tracking weapon in %.4f sec", Delay))
-- Weapon is not yet "alife" just yet. Start timer in 0.001 seconds.
self.trackScheduleID=timer.scheduleFunction(WEAPON._TrackWeapon, self, timer.getTime() + Delay)
return self
end
--- Stop tracking the weapon by removing the scheduler function.
-- @param #WEAPON self
-- @param #number Delay (Optional) Delay in seconds before the tracking is stopped.
-- @return #WEAPON self
function WEAPON:StopTrack(Delay)
if Delay and Delay>0 then
-- Delayed call.
self:ScheduleOnce(Delay, WEAPON.StopTrack, self, 0)
else
if self.trackScheduleID then
timer.removeFunction(self.trackScheduleID)
end
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Track weapon until impact.
-- @param #WEAPON self
-- @param DCS#Time time Time in seconds.
-- @return #number Time when called next or nil if not called again.
function WEAPON:_TrackWeapon(time)
-- Debug info.
if self.verbose>=20 then
self:I(self.lid..string.format("Tracking at T=%.5f", time))
end
-- Protected call to get the weapon position. If the position cannot be determined any more, the weapon has impacted and status is nil.
local status, pos3= pcall(
function()
local point=self.weapon:getPosition()
return point
end
)
if status then
-------------------------------
-- Weapon is still in exists --
-------------------------------
-- Update last known position.
self.pos3 = pos3
-- Update last known vec3.
self.vec3 = UTILS.DeepCopy(self.pos3.p)
-- Update coordinate.
self.coordinate:UpdateFromVec3(self.vec3)
-- Keep on tracking by returning the next time below.
self.tracking=true
-- Callback function.
if self.trackFunc then
self.trackFunc(self, unpack(self.trackArg))
end
-- Verbose output.
if self.verbose>=5 then
-- Get vec2 of current position.
local vec2={x=self.vec3.x, y=self.vec3.z}
-- Land hight.
local height=land.getHeight(vec2)
-- Current height above ground level.
local agl=self.vec3.y-height
-- Estimated IP (if any)
local ip=self:_GetIP(self.distIP)
-- Distance between positon and estimated impact.
local d=0
if ip then
d=UTILS.VecDist3D(self.vec3, ip)
end
-- Output.
self:I(self.lid..string.format("T=%.3f: Height=%.3f m AGL=%.3f m, dIP=%.3f", time, height, agl, d))
end
else
---------------------------
-- Weapon does NOT exist --
---------------------------
-- Get intercept point from position (p) and direction (x) in 50 meters.
local ip = self:_GetIP(self.distIP)
if self.verbose>=10 and ip then
-- Output.
self:I(self.lid.."Got intercept point!")
-- Coordinate of the impact point.
local coord=COORDINATE:NewFromVec3(ip)
-- Mark coordinate.
coord:MarkToAll("Intercept point")
coord:SmokeBlue()
-- Distance to last known pos.
local d=UTILS.VecDist3D(ip, self.vec3)
-- Output.
self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters", d))
end
-- Safe impact vec3.
self.impactVec3=ip or self.vec3
-- Safe impact coordinate.
self.impactCoord=COORDINATE:NewFromVec3(self.vec3)
-- Mark impact point on F10 map.
if self.impactMark then
self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s", self.name, self.typeName, self.launcherName))
end
-- Smoke on impact point.
if self.impactSmoke then
self.impactCoord:Smoke(self.impactSmokeColor)
end
-- Call callback function.
if self.impactFunc then
self.impactFunc(self, unpack(self.impactArg or {}))
end
-- Stop tracking by returning nil below.
self.tracking=false
end
-- Return next time the function is called or nil to stop the scheduler.
if self.tracking then
if self.dtTrack and self.dtTrack>0.001 then
return time+self.dtTrack
else
return nil
end
end
return nil
end
--- Compute estimated intercept/impact point (IP) based on last known position and direction.
-- @param #WEAPON self
-- @param #number Distance Distance in meters. Default 50 m.
-- @return DCS#Vec3 Estimated intercept/impact point. Can also return `nil`, if no IP can be determined.
function WEAPON:_GetIP(Distance)
Distance=Distance or 50
local ip=nil --DCS#Vec3
if Distance>0 and self.pos3 then
-- Get intercept point from position (p) and direction (x) in 20 meters.
ip = land.getIP(self.pos3.p, self.pos3.x, Distance or 20) --DCS#Vec3
end
return ip
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -34,6 +34,7 @@ Core/MarkerOps_Base.lua
Core/Astar.lua Core/Astar.lua
Core/Condition.lua Core/Condition.lua
Core/TextAndSound.lua Core/TextAndSound.lua
Core/Pathline.lua
Wrapper/Object.lua Wrapper/Object.lua
Wrapper/Identifiable.lua Wrapper/Identifiable.lua
@@ -46,6 +47,8 @@ Wrapper/Static.lua
Wrapper/Airbase.lua Wrapper/Airbase.lua
Wrapper/Scenery.lua Wrapper/Scenery.lua
Wrapper/Marker.lua Wrapper/Marker.lua
Wrapper/Weapon.lua
Wrapper/Net.lua
Cargo/Cargo.lua Cargo/Cargo.lua
Cargo/CargoUnit.lua Cargo/CargoUnit.lua