mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63b3807cf6 | ||
|
|
f3b6b27521 | ||
|
|
ff656182e8 | ||
|
|
a85ef6e769 | ||
|
|
0bb8c56ea9 | ||
|
|
5f108aec23 | ||
|
|
bf6683f993 | ||
|
|
b7a5e8dd85 | ||
|
|
57294aeaf5 | ||
|
|
20bfb8b08f | ||
|
|
5585a8992c | ||
|
|
d92a20050c | ||
|
|
c9a91d0683 | ||
|
|
ae6716ac01 | ||
|
|
34285b26ae | ||
|
|
5341e5faee | ||
|
|
a6224b484e | ||
|
|
e95c1ad3aa | ||
|
|
9cfed56847 | ||
|
|
6c1abddb1e | ||
|
|
7dc239f506 | ||
|
|
3e8413c6b7 | ||
|
|
ff86bfb91d | ||
|
|
66494b7b5a | ||
|
|
973127aa8c | ||
|
|
83866c3dd3 | ||
|
|
4797abc287 | ||
|
|
50f73f1be2 | ||
|
|
2ebbc8f466 | ||
|
|
1a96b30a45 | ||
|
|
3b1b57a33e | ||
|
|
f85c0320ec | ||
|
|
bae7edb914 | ||
|
|
35322399e9 | ||
|
|
5d4c0f3c87 | ||
|
|
92c5c32e8c | ||
|
|
c8780b2d5f | ||
|
|
f11dbfa367 | ||
|
|
54fb9deb3e | ||
|
|
d3c34ef04d | ||
|
|
3d5470eb22 | ||
|
|
2a3d69d0d5 | ||
|
|
9862c32378 | ||
|
|
9f02232589 | ||
|
|
ffc86bf046 | ||
|
|
e4c8ca34ed | ||
|
|
f02b7036a9 | ||
|
|
6adef47340 | ||
|
|
5b044cc036 | ||
|
|
03ba7524b2 | ||
|
|
772921c6b8 | ||
|
|
d4d305d53b | ||
|
|
c58918e002 | ||
|
|
dec7854476 | ||
|
|
8ca102f584 | ||
|
|
4748c66d9c | ||
|
|
bf486b9f44 | ||
|
|
6ff433ed7a | ||
|
|
f9aa392c6d | ||
|
|
819912cfd3 | ||
|
|
50ee1ae922 | ||
|
|
c9ccc28251 | ||
|
|
2157f8e8cd |
@@ -88,6 +88,7 @@ DATABASE = {
|
||||
WAREHOUSES = {},
|
||||
FLIGHTGROUPS = {},
|
||||
FLIGHTCONTROLS = {},
|
||||
PATHLINES = {},
|
||||
}
|
||||
|
||||
local _DATABASECoalition =
|
||||
@@ -244,7 +245,7 @@ function DATABASE:FindAirbase( AirbaseName )
|
||||
end
|
||||
|
||||
|
||||
do -- Zones
|
||||
do -- Zones and Pathlines
|
||||
|
||||
--- Finds a @{Core.Zone} based on the zone name.
|
||||
-- @param #DATABASE self
|
||||
@@ -267,7 +268,6 @@ do -- Zones
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Deletes a @{Core.Zone} from the DATABASE based on the zone name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string ZoneName The name of the zone.
|
||||
@@ -277,6 +277,39 @@ do -- Zones
|
||||
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.
|
||||
-- @param #DATABASE self
|
||||
-- @return #DATABASE self
|
||||
@@ -370,60 +403,148 @@ do -- Zones
|
||||
-- Add zone to DB.
|
||||
self:AddZone( ZoneName, Zone_Polygon )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Drawings as zones
|
||||
if env.mission.drawings and env.mission.drawings.layers then
|
||||
|
||||
|
||||
-- Loop over layers.
|
||||
for layerID, layerData in pairs(env.mission.drawings.layers or {}) do
|
||||
|
||||
|
||||
-- Loop over objects in layers.
|
||||
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)
|
||||
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.
|
||||
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.
|
||||
local vec2={x=objectData.mapX, y=objectData.mapY}
|
||||
|
||||
-- Copy points array.
|
||||
|
||||
-- Debug stuff.
|
||||
--local vec3={x=objectData.mapX, y=0, z=objectData.mapY}
|
||||
--local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY")
|
||||
--trigger.action.markToAll(id, "mapXY", vec3)
|
||||
|
||||
-- Copy points array.
|
||||
local points=UTILS.DeepCopy(objectData.points)
|
||||
|
||||
|
||||
-- Translate points.
|
||||
for i,_point in pairs(points) do
|
||||
local point=_point --DCS#Vec2
|
||||
local point=_point --DCS#Vec2
|
||||
points[i]=UTILS.Vec2Add(point, vec2)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Remove last point.
|
||||
table.remove(points, #points)
|
||||
|
||||
|
||||
-- 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.
|
||||
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)
|
||||
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
-- 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 -- zone
|
||||
|
||||
do -- Zone_Goal
|
||||
@@ -1040,7 +1161,7 @@ function DATABASE:_RegisterClients()
|
||||
for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do
|
||||
self:I(string.format("Register Client: %s", tostring(ClientName)))
|
||||
local client=self:AddClient( ClientName )
|
||||
client.SpawnCoord=COORDINATE:New(ClientTemplate.x, ClientTemplate.alt, ClientTemplate.y)
|
||||
client.SpawnCoord=COORDINATE:New(ClientTemplate.x, ClientTemplate.alt, ClientTemplate.y)
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -1090,7 +1211,7 @@ end
|
||||
function DATABASE:_RegisterAirbase(airbase)
|
||||
|
||||
if airbase then
|
||||
|
||||
|
||||
-- Get the airbase name.
|
||||
local DCSAirbaseName = airbase:getName()
|
||||
|
||||
@@ -1719,7 +1840,7 @@ function DATABASE:_RegisterTemplates()
|
||||
|
||||
if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group
|
||||
|
||||
self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
|
||||
self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
|
||||
|
||||
else
|
||||
|
||||
|
||||
@@ -305,8 +305,8 @@ EVENTS = {
|
||||
-- @field Wrapper.Airbase#AIRBASE Place The MOOSE airbase object.
|
||||
-- @field #string PlaceName The name of the airbase.
|
||||
--
|
||||
-- @field #table weapon The weapon used during the event.
|
||||
-- @field #table Weapon
|
||||
-- @field DCS#Weapon weapon The weapon used during the event.
|
||||
-- @field DCS#Weapon Weapon The weapon used during the event.
|
||||
-- @field #string WeaponName Name of the weapon.
|
||||
-- @field DCS#Unit WeaponTgtDCSUnit Target DCS unit of the weapon.
|
||||
--
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
-- ### Author: **Applevangelist**
|
||||
--
|
||||
-- Date: 5 May 2021
|
||||
-- Last Update: Sep 2022
|
||||
-- Last Update: Feb 2023
|
||||
--
|
||||
-- ===
|
||||
---
|
||||
@@ -50,7 +50,7 @@ MARKEROPS_BASE = {
|
||||
ClassName = "MARKEROPS",
|
||||
Tag = "mytag",
|
||||
Keywords = {},
|
||||
version = "0.1.0",
|
||||
version = "0.1.1",
|
||||
debug = false,
|
||||
Casesensitive = true,
|
||||
}
|
||||
@@ -124,7 +124,8 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @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.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
|
||||
-- @param #MARKEROPS_BASE self
|
||||
@@ -172,7 +173,7 @@ function MARKEROPS_BASE:OnEventMark(Event)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkChanged(Eventtext,matchtable,coord)
|
||||
self:MarkChanged(Eventtext,matchtable,coord,Event.idx)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
|
||||
|
||||
370
Moose Development/Moose/Core/Pathline.lua
Normal file
370
Moose Development/Moose/Core/Pathline.lua
Normal 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
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1054,28 +1054,55 @@ do -- COORDINATE
|
||||
return heading
|
||||
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.
|
||||
-- @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.
|
||||
-- @return Direction the wind is blowing from in degrees.
|
||||
-- @return Wind strength in m/s.
|
||||
function COORDINATE:GetWind(height)
|
||||
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.
|
||||
local point={x=self.x, y=math.max(height or self.y, landheight), z=self.z}
|
||||
-- get wind velocity vector
|
||||
local wind = atmosphere.getWind(point)
|
||||
local direction = math.deg(math.atan2(wind.z, wind.x))
|
||||
if direction < 0 then
|
||||
direction = 360 + direction
|
||||
end
|
||||
-- Convert to direction to from direction
|
||||
-- @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.
|
||||
-- @param #boolean turbulence If `true`, include turbulence. If `false` or `nil`, wind without turbulence.
|
||||
-- @return #number Direction the wind is blowing from in degrees.
|
||||
-- @return #number Wind strength in m/s.
|
||||
function COORDINATE:GetWind(height, turbulence)
|
||||
|
||||
-- Get wind velocity vector
|
||||
local wind = self:GetWindVec3(height, turbulence)
|
||||
|
||||
-- Calculate the direction of the vector.
|
||||
local direction=UTILS.VecHdg(wind)
|
||||
|
||||
-- Invert "to" direction to "from" direction.
|
||||
if direction > 180 then
|
||||
direction = direction-180
|
||||
else
|
||||
direction = direction+180
|
||||
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
|
||||
end
|
||||
|
||||
@@ -1133,12 +1160,15 @@ do -- COORDINATE
|
||||
|
||||
--- Return the 3D distance in meters between the target COORDINATE and the COORDINATE.
|
||||
-- @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.
|
||||
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()
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -903,7 +903,7 @@ end
|
||||
|
||||
do -- SET_GROUP
|
||||
|
||||
--- @type SET_GROUP
|
||||
--- @type SET_GROUP #SET_GROUP
|
||||
-- @extends Core.Set#SET_BASE
|
||||
|
||||
--- 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
|
||||
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.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param Core.Point#COORDINATE Coordinate Reference Coordinate from which the closest group is determined.
|
||||
@@ -1952,6 +1934,23 @@ do -- SET_GROUP
|
||||
return gmin, dmin
|
||||
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
|
||||
|
||||
do -- SET_UNIT
|
||||
@@ -2114,10 +2113,12 @@ do -- SET_UNIT
|
||||
self:F2( Unit:GetName() )
|
||||
|
||||
self:Add( Unit:GetName(), Unit )
|
||||
|
||||
-- Set the default cargo bay limit each time a new unit is added to the set.
|
||||
Unit:SetCargoBayWeightLimit()
|
||||
|
||||
|
||||
if Unit:IsInstanceOf("UNIT") then
|
||||
-- Set the default cargo bay limit each time a new unit is added to the set.
|
||||
Unit:SetCargoBayWeightLimit()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -3794,6 +3795,40 @@ do -- SET_STATIC
|
||||
return TypeReport:Text( Delimiter )
|
||||
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
|
||||
|
||||
do -- SET_CLIENT
|
||||
|
||||
@@ -772,10 +772,8 @@ end
|
||||
-- @usage
|
||||
--
|
||||
-- -- 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).
|
||||
-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
|
||||
-- -- 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 )
|
||||
-- -- UNIT positions of this group will be randomized around the base unit #1 in a circle of 50 to 500 meters.
|
||||
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeUnits( true, 500, 50 )
|
||||
--
|
||||
function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )
|
||||
self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } )
|
||||
@@ -791,6 +789,46 @@ function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )
|
||||
return self
|
||||
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 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.
|
||||
@@ -1146,7 +1184,8 @@ do -- Delay methods
|
||||
return self
|
||||
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
|
||||
-- @return #SPAWN The SPAWN object
|
||||
function SPAWN:InitDelayOn()
|
||||
@@ -1342,7 +1381,38 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
|
||||
SpawnTemplate.units[UnitID].psi = -SpawnTemplate.units[UnitID].heading
|
||||
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.
|
||||
if self.SpawnInitLivery then
|
||||
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 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.
|
||||
-- @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
|
||||
-- @usage
|
||||
-- -- NATO helicopters engaging in the battle field.
|
||||
@@ -1453,17 +1525,20 @@ end
|
||||
-- -- 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.
|
||||
-- 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 } )
|
||||
|
||||
|
||||
local SpawnTime = SpawnTime or 60
|
||||
local SpawnTimeVariation = SpawnTimeVariation or 0.5
|
||||
|
||||
if SpawnTime ~= nil and SpawnTimeVariation ~= nil then
|
||||
local InitialDelay = 0
|
||||
if self.DelayOnOff == true then
|
||||
if WithDelay or self.DelayOnOff == true then
|
||||
InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation )
|
||||
end
|
||||
self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, InitialDelay, SpawnTime, SpawnTimeVariation )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
-- SPOT implements the DCS Spot class functionality, but adds additional luxury to be able to:
|
||||
--
|
||||
-- * 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.
|
||||
-- * Provide a @{Wrapper.Unit} as a target, instead of a point.
|
||||
-- * Implement a status machine, LaseOn, LaseOff.
|
||||
@@ -13,18 +13,8 @@
|
||||
-- ===
|
||||
--
|
||||
-- # Demo Missions
|
||||
--
|
||||
-- ### [SPOT Demo Missions source code]()
|
||||
--
|
||||
-- ### [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]()
|
||||
-- ### [Demo Missions on GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -50,14 +40,14 @@ do
|
||||
--- Implements the target spotting or marking functionality, but adds additional luxury to be able to:
|
||||
--
|
||||
-- * 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.
|
||||
-- * Provide a @{Wrapper.Unit} as a target, instead of a point.
|
||||
-- * Implement a status machine, LaseOn, LaseOff.
|
||||
--
|
||||
-- ## 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
|
||||
--
|
||||
@@ -217,6 +207,8 @@ do
|
||||
|
||||
|
||||
self.Recce = Recce
|
||||
|
||||
self.RecceName = self.Recce:GetName()
|
||||
|
||||
self.LaseScheduler = SCHEDULER:New( self )
|
||||
|
||||
@@ -243,6 +235,9 @@ do
|
||||
end
|
||||
|
||||
self.Target = Target
|
||||
|
||||
self.TargetName = Target:GetName()
|
||||
|
||||
self.LaserCode = LaserCode
|
||||
|
||||
self.Lasing = true
|
||||
@@ -302,12 +297,18 @@ do
|
||||
function SPOT:OnEventDead(EventData)
|
||||
self:F( { Dead = EventData.IniDCSUnitName, Target = self.Target } )
|
||||
if self.Target then
|
||||
if EventData.IniDCSUnitName == self.Target:GetName() then
|
||||
self:F( {"Target dead ", self.Target:GetName() } )
|
||||
if EventData.IniDCSUnitName == self.TargetName then
|
||||
self:F( {"Target dead ", self.TargetName } )
|
||||
self:Destroyed()
|
||||
self:LaseOff()
|
||||
end
|
||||
end
|
||||
if self.Recce then
|
||||
if EventData.IniDCSUnitName == self.RecceName then
|
||||
self:F( {"Recce dead ", self.RecceName } )
|
||||
self:LaseOff()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #SPOT self
|
||||
@@ -382,4 +383,4 @@ do
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -555,7 +555,7 @@ function ZONE_BASE:GetZoneMaybe()
|
||||
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 #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.
|
||||
@@ -569,7 +569,7 @@ function ZONE_BASE:GetProperty(PropertyName)
|
||||
return self.Properties[PropertyName]
|
||||
end
|
||||
|
||||
-- Returns the zone Properties table.
|
||||
--- Returns the zone Properties table.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return #table The Key:Value table of TriggerZone properties of the zone.
|
||||
function ZONE_BASE:GetAllProperties()
|
||||
@@ -927,7 +927,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
||||
local ZoneCoord = self:GetCoordinate()
|
||||
local ZoneRadius = self:GetRadius()
|
||||
|
||||
self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
|
||||
--self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
|
||||
|
||||
local SphereSearch = {
|
||||
id = world.VolumeType.SPHERE,
|
||||
@@ -2084,13 +2084,10 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get the smallest circular zone encompassing all points points of the polygon zone.
|
||||
--- Get the smallest radius 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)
|
||||
-- @return #number Radius of the zone in meters.
|
||||
function ZONE_POLYGON_BASE:GetRadius()
|
||||
|
||||
local center=self:GetVec2()
|
||||
|
||||
@@ -2106,6 +2103,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)
|
||||
|
||||
@@ -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.
|
||||
-- @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
|
||||
function ZONE_ELASTIC:AddSetGroup(GroupSet)
|
||||
|
||||
|
||||
@@ -193,21 +193,29 @@ do -- land
|
||||
|
||||
--- [Type of surface enumerator](https://wiki.hoggitworld.com/view/DCS_singleton_land)
|
||||
-- @type land.SurfaceType
|
||||
-- @field LAND
|
||||
-- @field SHALLOW_WATER
|
||||
-- @field WATER
|
||||
-- @field ROAD
|
||||
-- @field RUNWAY
|
||||
-- @field LAND Land=1
|
||||
-- @field SHALLOW_WATER Shallow water=2
|
||||
-- @field WATER Water=3
|
||||
-- @field ROAD Road=4
|
||||
-- @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
|
||||
-- @param #Vec2 point point on the ground.
|
||||
-- @return #Distance
|
||||
-- @param #Vec2 point Point on the ground.
|
||||
-- @return #number Height in meters.
|
||||
|
||||
--- Returns the surface height and depth of a point. Useful for checking if the path is deep enough to support a given ship.
|
||||
-- Both values are positive. When checked over water at sea level the first value is always zero.
|
||||
-- When checked over water at altitude, for example the reservoir of the Inguri Dam, the first value is the corresponding altitude the water level is at.
|
||||
-- @function [parent=#land] getSurfaceHeightWithSeabed
|
||||
-- @param #Vec2 point Position where to check.
|
||||
-- @return #number Height in meters.
|
||||
-- @return #number Depth in meters.
|
||||
|
||||
--- returns surface type at the given point.
|
||||
--- Returns surface type at the given point.
|
||||
-- @function [parent=#land] getSurfaceType
|
||||
-- @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)
|
||||
land = {} --#land
|
||||
@@ -427,8 +435,8 @@ do -- Types
|
||||
--- Vec3 type is a 3D-vector.
|
||||
-- DCS world has 3-dimensional coordinate system. DCS ground is an infinite plain.
|
||||
-- @type Vec3
|
||||
-- @field #Distance x is directed to the north
|
||||
-- @field #Distance z is directed to the east
|
||||
-- @field #Distance x is directed to the North
|
||||
-- @field #Distance z is directed to the East
|
||||
-- @field #Distance y is directed up
|
||||
|
||||
--- 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.
|
||||
-- @type Weapon.Category
|
||||
-- @field SHELL
|
||||
-- @field MISSILE
|
||||
-- @field ROCKET
|
||||
-- @field BOMB
|
||||
-- @field #number SHELL Shell.
|
||||
-- @field #number MISSILE Missile
|
||||
-- @field #number ROCKET Rocket.
|
||||
-- @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).
|
||||
@@ -1001,8 +1010,8 @@ do -- Unit
|
||||
|
||||
--- Enum that stores aircraft refueling system types.
|
||||
-- @type Unit.RefuelingSystem
|
||||
-- @field BOOM_AND_RECEPTACLE
|
||||
-- @field PROBE_AND_DROGUE
|
||||
-- @field BOOM_AND_RECEPTACLE Tanker with a boom.
|
||||
-- @field PROBE_AND_DROGUE Tanker with a probe.
|
||||
|
||||
--- Enum that stores sensor types.
|
||||
-- @type Unit.SensorType
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
-- @field #number coalition The coalition of the arty group.
|
||||
-- @field #boolean respawnafterdeath Respawn arty group after all units are dead.
|
||||
-- @field #number respawndelay Respawn delay in seconds.
|
||||
-- @field #number dtTrack Time interval in seconds for weapon tracking.
|
||||
-- @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
|
||||
@@ -693,7 +694,7 @@ ARTY.db={
|
||||
|
||||
--- Arty script version.
|
||||
-- @field #string version
|
||||
ARTY.version="1.2.0"
|
||||
ARTY.version="1.3.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -801,6 +802,9 @@ function ARTY:New(group, alias)
|
||||
else
|
||||
self.ismobile=false
|
||||
end
|
||||
|
||||
-- Set track time interval.
|
||||
self.dtTrack=0.2
|
||||
|
||||
-- Set speed to 0.7 of maximum.
|
||||
self.Speed=self.SpeedMax * 0.7
|
||||
@@ -1497,6 +1501,15 @@ function ARTY:SetStatusInterval(interval)
|
||||
return self
|
||||
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.
|
||||
-- @param #ARTY self
|
||||
-- @param #number waittime Time in seconds. Default 300 seconds.
|
||||
@@ -2129,6 +2142,95 @@ end
|
||||
-- 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.
|
||||
-- @param #ARTY self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
@@ -2162,128 +2264,32 @@ function ARTY:OnEventShot(EventData)
|
||||
self:T(self.lid..text)
|
||||
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.
|
||||
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 _tracksmoke = self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke>0
|
||||
|
||||
|
||||
if _tracknuke or _trackillu or _tracksmoke then
|
||||
|
||||
self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.", self.groupname))
|
||||
|
||||
local _peter={}
|
||||
_peter.weapon=EventData.weapon
|
||||
_peter.target=UTILS.DeepCopy(self.currentTarget)
|
||||
|
||||
timer.scheduleFunction(_TrackWeapon, _peter, timer.getTime() + 2.0)
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.", self.groupname))
|
||||
|
||||
-- Create a weapon object.
|
||||
local weapon=WEAPON:New(EventData.weapon)
|
||||
|
||||
-- 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
|
||||
|
||||
-- Get current ammo.
|
||||
@@ -3931,9 +3937,10 @@ function ARTY:GetAmmo(display)
|
||||
return nammo, nshells, nrockets, nmissiles
|
||||
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.
|
||||
local text=string.format("ARTY group %s - unit %s:\n", self.groupname, unit:GetName())
|
||||
|
||||
@@ -474,7 +474,7 @@ do -- DETECTION_BASE
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param #table DetectedItem The DetectedItem.
|
||||
-- @param #DetectedItem DetectedItem The DetectedItem data structure.
|
||||
|
||||
self:AddTransition( "*", "Stop", "Stopped" )
|
||||
|
||||
@@ -2478,14 +2478,14 @@ do -- DETECTION_AREAS
|
||||
--- DETECTION_AREAS constructor.
|
||||
-- @param #DETECTION_AREAS self
|
||||
-- @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
|
||||
function DETECTION_AREAS:New( DetectionSetGroup, DetectionZoneRange )
|
||||
|
||||
-- Inherits from DETECTION_BASE
|
||||
local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) )
|
||||
|
||||
self.DetectionZoneRange = DetectionZoneRange
|
||||
self.DetectionZoneRange = DetectionZoneRange or 5000
|
||||
|
||||
self._SmokeDetectedUnits = false
|
||||
self._FlareDetectedUnits = false
|
||||
@@ -2988,4 +2988,3 @@ do -- DETECTION_AREAS
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
--- FOX class.
|
||||
-- @type FOX
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity level.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table menuadded Table of groups the menu was added for.
|
||||
@@ -124,6 +125,7 @@
|
||||
-- @field #FOX
|
||||
FOX = {
|
||||
ClassName = "FOX",
|
||||
verbose = 0,
|
||||
Debug = false,
|
||||
lid = nil,
|
||||
menuadded = {},
|
||||
@@ -168,7 +170,7 @@ FOX = {
|
||||
|
||||
--- Missile data table.
|
||||
-- @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 #string missileType Type 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 targetOrig Name of the "original" target, i.e. the one right after launched.
|
||||
-- @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.
|
||||
-- @field #table MenuF10 Root menu table on group level.
|
||||
@@ -196,7 +200,7 @@ FOX.MenuF10Root=nil
|
||||
|
||||
--- FOX class version.
|
||||
-- @field #string version
|
||||
FOX.version="0.6.1"
|
||||
FOX.version="0.8.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@@ -500,6 +504,7 @@ function FOX:SetDisableF10Menu()
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Enable F10 menu for all players.
|
||||
-- @param #FOX self
|
||||
-- @return #FOX self
|
||||
@@ -510,6 +515,15 @@ function FOX:SetEnableF10Menu()
|
||||
return self
|
||||
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.
|
||||
-- @param #FOX self
|
||||
-- @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)
|
||||
|
||||
-- Status.
|
||||
self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate))
|
||||
if self.verbose>=1 then
|
||||
self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate))
|
||||
end
|
||||
|
||||
-- Check missile status.
|
||||
self:_CheckMissileStatus()
|
||||
@@ -713,7 +729,9 @@ function FOX:_CheckMissileStatus()
|
||||
if #self.missiles==0 then
|
||||
text=text.." none"
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
if self.verbose>=2 then
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-- Remove inactive missiles.
|
||||
for i=#self.missiles,1,-1 do
|
||||
@@ -743,7 +761,7 @@ function FOX:_IsProtected(targetunit)
|
||||
if targetgroup then
|
||||
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
|
||||
|
||||
if group then
|
||||
@@ -762,6 +780,277 @@ function FOX:_IsProtected(targetunit)
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- Function called from weapon tracking.
|
||||
-- @param Wrapper.Weapon#WEAPON weapon Weapon object.
|
||||
-- @param #FOX self FOX object.
|
||||
-- @param #FOX.MissileData missile Fired missile
|
||||
function FOX._FuncTrack(weapon, self, missile)
|
||||
|
||||
-- Missile coordinate.
|
||||
local missileCoord= missile.missileCoord:UpdateFromVec3(weapon.vec3) --COORDINATE:NewFromVec3(_lastBombPos)
|
||||
|
||||
-- Missile velocity in m/s.
|
||||
local missileVelocity=weapon:GetSpeed() --UTILS.VecNorm(_ordnance:getVelocity())
|
||||
|
||||
-- Update missile target if necessary.
|
||||
self:GetMissileTarget(missile)
|
||||
|
||||
-- Target unit of the missile.
|
||||
local target=nil --Wrapper.Unit#UNIT
|
||||
|
||||
if missile.targetUnit then
|
||||
|
||||
-----------------------------------
|
||||
-- Missile has a specific target --
|
||||
-----------------------------------
|
||||
|
||||
if missile.targetPlayer then
|
||||
-- Target is a player.
|
||||
if missile.targetPlayer.destroy==true then
|
||||
target=missile.targetUnit
|
||||
end
|
||||
else
|
||||
-- Check if unit is protected.
|
||||
if self:_IsProtected(missile.targetUnit) then
|
||||
target=missile.targetUnit
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
------------------------------------
|
||||
-- Missile has NO specific target --
|
||||
------------------------------------
|
||||
|
||||
-- TODO: This might cause a problem with wingman. Even if the shooter itself is excluded from the check, it's wingmen are not.
|
||||
-- That would trigger the distance check right after missile launch if things to wrong.
|
||||
--
|
||||
-- Possible solutions:
|
||||
-- * Time check: enable this check after X seconds after missile was fired. What is X?
|
||||
-- * Coalition check. But would not work in training situations where blue on blue is valid!
|
||||
-- * At least enable it for surface-to-air missiles.
|
||||
|
||||
local function _GetTarget(_unit)
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
|
||||
-- Player position.
|
||||
local playerCoord=unit:GetCoordinate()
|
||||
|
||||
-- Distance.
|
||||
local dist=missileCoord:Get3DDistance(playerCoord)
|
||||
|
||||
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
|
||||
if dist<=self.explosiondist then
|
||||
return unit
|
||||
end
|
||||
end
|
||||
|
||||
-- Distance to closest player.
|
||||
local mindist=nil
|
||||
|
||||
-- Loop over players.
|
||||
for _,_player in pairs(self.players) do
|
||||
local player=_player --#FOX.PlayerData
|
||||
|
||||
-- Check that player was not the one who launched the missile.
|
||||
if player.unitname~=missile.shooterName then
|
||||
|
||||
-- Player position.
|
||||
local playerCoord=player.unit:GetCoordinate()
|
||||
|
||||
-- Distance.
|
||||
local dist=missileCoord:Get3DDistance(playerCoord)
|
||||
|
||||
-- Distance from shooter to player.
|
||||
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
|
||||
|
||||
-- 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
|
||||
mindist=dist
|
||||
target=player.unit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.protectedset then
|
||||
|
||||
-- Distance to closest protected unit.
|
||||
mindist=nil
|
||||
|
||||
for _,_group in pairs(self.protectedset:GetSet()) do
|
||||
local group=_group --Wrapper.Group#GROUP
|
||||
for _,_unit in pairs(group:GetUnits()) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
|
||||
if unit and unit:IsAlive() then
|
||||
|
||||
-- Check that player was not the one who launched the missile.
|
||||
if unit:GetName()~=missile.shooterName then
|
||||
|
||||
-- Player position.
|
||||
local playerVec3=unit:GetVec3()
|
||||
|
||||
-- Distance.
|
||||
local dist=missileCoord:Get3DDistance(playerVec3)
|
||||
|
||||
-- Distance from shooter to player.
|
||||
local Dshooter2player=missile.shotCoord:Get3DDistance(playerVec3)
|
||||
|
||||
-- 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
|
||||
mindist=dist
|
||||
target=unit
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if target then
|
||||
self:T(self.lid..string.format("Missile %s with NO explicit target got closest unit to missile as target %s. Dist=%s m", missile.missileType, target:GetName(), tostring(mindist)))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Check if missile has a valid target.
|
||||
if target then
|
||||
|
||||
-- Target coordinate.
|
||||
local targetVec3=target:GetVec3() --target:GetCoordinate()
|
||||
|
||||
-- Distance from missile to target.
|
||||
local distance=missileCoord:Get3DDistance(targetVec3)
|
||||
|
||||
-- Distance missile to shooter.
|
||||
local distShooter=nil
|
||||
if missile.shooterUnit and missile.shooterUnit:IsAlive() then
|
||||
distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetVec3())
|
||||
end
|
||||
|
||||
|
||||
-- Debug output.
|
||||
if self.Debug then
|
||||
local bearing=missileCoord:HeadingTo(targetVec3)
|
||||
local eta=distance/missileVelocity
|
||||
-- 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))
|
||||
end
|
||||
|
||||
-- Distroy missile if it's getting too close.
|
||||
local destroymissile=distance<=self.explosiondist
|
||||
|
||||
-- Check BIG missiles.
|
||||
if self.explosiondist2 and distance<=self.explosiondist2 and not destroymissile then
|
||||
destroymissile=missile.explosive>=self.bigmissilemass
|
||||
end
|
||||
|
||||
-- If missile is 150 m from target ==> destroy missile if in safe zone.
|
||||
if destroymissile and self:_CheckCoordSafe(targetVec3) then
|
||||
|
||||
-- Destroy missile.
|
||||
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))
|
||||
weapon:Destroy()
|
||||
|
||||
-- Missile is not active any more.
|
||||
missile.active=false
|
||||
|
||||
-- Debug smoke.
|
||||
if self.Debug then
|
||||
missileCoord:SmokeRed()
|
||||
end
|
||||
|
||||
-- Create event.
|
||||
self:MissileDestroyed(missile)
|
||||
|
||||
-- Little explosion for the visual effect.
|
||||
if self.explosionpower>0 and distance>50 and (distShooter==nil or (distShooter and distShooter>50)) then
|
||||
missileCoord:Explosion(self.explosionpower)
|
||||
end
|
||||
|
||||
-- Target was a player.
|
||||
if missile.targetPlayer then
|
||||
|
||||
-- Message to target.
|
||||
local text=string.format("Destroying missile. %s", self:_DeadText())
|
||||
MESSAGE:New(text, 10):ToGroup(target:GetGroup())
|
||||
|
||||
-- Increase dead counter.
|
||||
missile.targetPlayer.dead=missile.targetPlayer.dead+1
|
||||
end
|
||||
|
||||
-- We could disable the tracking here but then the impact function would not be called.
|
||||
--weapon.tracking=false
|
||||
|
||||
else
|
||||
|
||||
-- Time step.
|
||||
local dt=1.0
|
||||
if distance>50000 then
|
||||
-- > 50 km
|
||||
dt=self.dt50 --=5.0
|
||||
elseif distance>10000 then
|
||||
-- 10-50 km
|
||||
dt=self.dt10 --=1.0
|
||||
elseif distance>5000 then
|
||||
-- 5-10 km
|
||||
dt=self.dt05 --0.5
|
||||
elseif distance>1000 then
|
||||
-- 1-5 km
|
||||
dt=self.dt01 --0.1
|
||||
else
|
||||
-- < 1 km
|
||||
dt=self.dt00 --0.01
|
||||
end
|
||||
|
||||
-- Set time step.
|
||||
weapon:SetTimeStepTrack(dt)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- 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))
|
||||
weapon:SetTimeStepTrack(0.1)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Callback function on impact or destroy otherwise.
|
||||
-- @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 missile.targetPlayer then
|
||||
|
||||
-- Get human player.
|
||||
local player=missile.targetPlayer
|
||||
|
||||
-- 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
|
||||
local text=string.format("Missile defeated. Well done, %s!", player.name)
|
||||
MESSAGE:New(text, 10):ToClient(player.client)
|
||||
|
||||
-- Increase defeated counter.
|
||||
player.defeated=player.defeated+1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Missile is not active any more.
|
||||
missile.active=false
|
||||
|
||||
--Terminate the timer.
|
||||
self:T(FOX.lid..string.format("Terminating missile track timer."))
|
||||
weapon.tracking=false
|
||||
|
||||
end
|
||||
|
||||
--- Missle launch event.
|
||||
-- @param #FOX self
|
||||
-- @param #string From From state.
|
||||
@@ -818,304 +1107,19 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Init missile position.
|
||||
local _lastBombPos = {x=0,y=0,z=0}
|
||||
-- Set callback function for tracking.
|
||||
missile.Weapon:SetFuncTrack(FOX._FuncTrack, self, missile)
|
||||
|
||||
-- Missile coordinate.
|
||||
local missileCoord = nil --Core.Point#COORDINATE
|
||||
-- Set callback function for impact.
|
||||
missile.Weapon:SetFuncImpact(FOX._FuncImpact, self, missile)
|
||||
|
||||
-- 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.
|
||||
local missileVelocity=UTILS.VecNorm(_ordnance:getVelocity())
|
||||
|
||||
-- Update missile target if necessary.
|
||||
self:GetMissileTarget(missile)
|
||||
|
||||
if missile.targetUnit then
|
||||
|
||||
-----------------------------------
|
||||
-- Missile has a specific target --
|
||||
-----------------------------------
|
||||
|
||||
if missile.targetPlayer then
|
||||
-- Target is a player.
|
||||
if missile.targetPlayer.destroy==true then
|
||||
target=missile.targetUnit
|
||||
end
|
||||
else
|
||||
-- Check if unit is protected.
|
||||
if self:_IsProtected(missile.targetUnit) then
|
||||
target=missile.targetUnit
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
------------------------------------
|
||||
-- Missile has NO specific target --
|
||||
------------------------------------
|
||||
|
||||
-- TODO: This might cause a problem with wingman. Even if the shooter itself is excluded from the check, it's wingmen are not.
|
||||
-- That would trigger the distance check right after missile launch if things to wrong.
|
||||
--
|
||||
-- Possible solutions:
|
||||
-- * Time check: enable this check after X seconds after missile was fired. What is X?
|
||||
-- * Coalition check. But would not work in training situations where blue on blue is valid!
|
||||
-- * At least enable it for surface-to-air missiles.
|
||||
|
||||
local function _GetTarget(_unit)
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
|
||||
-- Player position.
|
||||
local playerCoord=unit:GetCoordinate()
|
||||
|
||||
-- Distance.
|
||||
local dist=missileCoord:Get3DDistance(playerCoord)
|
||||
|
||||
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
|
||||
if dist<=self.explosiondist then
|
||||
return unit
|
||||
end
|
||||
end
|
||||
|
||||
-- Distance to closest player.
|
||||
local mindist=nil
|
||||
|
||||
-- Loop over players.
|
||||
for _,_player in pairs(self.players) do
|
||||
local player=_player --#FOX.PlayerData
|
||||
|
||||
-- Check that player was not the one who launched the missile.
|
||||
if player.unitname~=missile.shooterName then
|
||||
|
||||
-- Player position.
|
||||
local playerCoord=player.unit:GetCoordinate()
|
||||
|
||||
-- Distance.
|
||||
local dist=missileCoord:Get3DDistance(playerCoord)
|
||||
|
||||
-- Distance from shooter to player.
|
||||
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
|
||||
|
||||
-- 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
|
||||
mindist=dist
|
||||
target=player.unit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.protectedset then
|
||||
|
||||
-- Distance to closest protected unit.
|
||||
mindist=nil
|
||||
|
||||
for _,_group in pairs(self.protectedset:GetSet()) do
|
||||
local group=_group --Wrapper.Group#GROUP
|
||||
for _,_unit in pairs(group:GetUnits()) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
|
||||
if unit and unit:IsAlive() then
|
||||
|
||||
-- Check that player was not the one who launched the missile.
|
||||
if unit:GetName()~=missile.shooterName then
|
||||
|
||||
-- Player position.
|
||||
local playerCoord=unit:GetCoordinate()
|
||||
|
||||
-- Distance.
|
||||
local dist=missileCoord:Get3DDistance(playerCoord)
|
||||
|
||||
-- Distance from shooter to player.
|
||||
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
|
||||
|
||||
-- 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
|
||||
mindist=dist
|
||||
target=unit
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if target then
|
||||
self:T(self.lid..string.format("Missile %s with NO explicit target got closest unit to missile as target %s. Dist=%s m", missile.missileType, target:GetName(), tostring(mindist)))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Check if missile has a valid target.
|
||||
if target then
|
||||
|
||||
-- Target coordinate.
|
||||
local targetCoord=target:GetCoordinate()
|
||||
|
||||
-- Distance from missile to target.
|
||||
local distance=missileCoord:Get3DDistance(targetCoord)
|
||||
|
||||
-- Distance missile to shooter.
|
||||
local distShooter=nil
|
||||
if missile.shooterUnit and missile.shooterUnit:IsAlive() then
|
||||
distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetCoordinate())
|
||||
end
|
||||
|
||||
|
||||
-- Debug output.
|
||||
if self.Debug then
|
||||
local bearing=targetCoord:HeadingTo(missileCoord)
|
||||
local eta=distance/missileVelocity
|
||||
|
||||
-- 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))
|
||||
end
|
||||
|
||||
-- Distroy missile if it's getting too close.
|
||||
local destroymissile=distance<=self.explosiondist
|
||||
|
||||
-- Check BIG missiles.
|
||||
if self.explosiondist2 and distance<=self.explosiondist2 and not destroymissile then
|
||||
destroymissile=missile.explosive>=self.bigmissilemass
|
||||
end
|
||||
|
||||
-- If missile is 150 m from target ==> destroy missile if in safe zone.
|
||||
if destroymissile and self:_CheckCoordSafe(targetCoord) then
|
||||
|
||||
-- Destroy missile.
|
||||
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))
|
||||
_ordnance:destroy()
|
||||
|
||||
-- Missile is not active any more.
|
||||
missile.active=false
|
||||
|
||||
-- Debug smoke.
|
||||
if self.Debug then
|
||||
missileCoord:SmokeRed()
|
||||
targetCoord:SmokeGreen()
|
||||
end
|
||||
|
||||
-- Create event.
|
||||
self:MissileDestroyed(missile)
|
||||
|
||||
-- Little explosion for the visual effect.
|
||||
if self.explosionpower>0 and distance>50 and (distShooter==nil or (distShooter and distShooter>50)) then
|
||||
missileCoord:Explosion(self.explosionpower)
|
||||
end
|
||||
|
||||
-- Target was a player.
|
||||
if missile.targetPlayer then
|
||||
|
||||
-- Message to target.
|
||||
local text=string.format("Destroying missile. %s", self:_DeadText())
|
||||
MESSAGE:New(text, 10):ToGroup(target:GetGroup())
|
||||
|
||||
-- Increase dead counter.
|
||||
missile.targetPlayer.dead=missile.targetPlayer.dead+1
|
||||
end
|
||||
|
||||
-- Terminate timer.
|
||||
return nil
|
||||
|
||||
else
|
||||
|
||||
-- Time step.
|
||||
local dt=1.0
|
||||
if distance>50000 then
|
||||
-- > 50 km
|
||||
dt=self.dt50 --=5.0
|
||||
elseif distance>10000 then
|
||||
-- 10-50 km
|
||||
dt=self.dt10 --=1.0
|
||||
elseif distance>5000 then
|
||||
-- 5-10 km
|
||||
dt=self.dt05 --0.5
|
||||
elseif distance>1000 then
|
||||
-- 1-5 km
|
||||
dt=self.dt01 --0.1
|
||||
else
|
||||
-- < 1 km
|
||||
dt=self.dt00 --0.01
|
||||
end
|
||||
|
||||
-- Check again in dt seconds.
|
||||
return timer.getTime()+dt
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Destroy missile.
|
||||
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
|
||||
|
||||
-- No target ==> terminate timer.
|
||||
--return nil
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-------------------------------------
|
||||
-- Missile does not exist any more --
|
||||
-------------------------------------
|
||||
|
||||
if target then
|
||||
|
||||
-- Get human player.
|
||||
local player=self:_GetPlayerFromUnit(target)
|
||||
|
||||
-- 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
|
||||
local text=string.format("Missile defeated. Well done, %s!", player.name)
|
||||
MESSAGE:New(text, 10):ToClient(player.client)
|
||||
|
||||
-- Increase defeated counter.
|
||||
player.defeated=player.defeated+1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Missile is not active any more.
|
||||
missile.active=false
|
||||
|
||||
--Terminate the timer.
|
||||
self:T(FOX.lid..string.format("Terminating missile track timer."))
|
||||
return nil
|
||||
|
||||
end -- _status check
|
||||
|
||||
end -- end function trackBomb
|
||||
|
||||
-- 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."))
|
||||
timer.scheduleFunction(trackMissile, missile.weapon, timer.getTime()+0.0001)
|
||||
--timer.scheduleFunction(trackMissile, missile.weapon, timer.getTime()+0.0001)
|
||||
missile.Weapon:StartTrack(0.0001)
|
||||
|
||||
end
|
||||
|
||||
@@ -1246,30 +1250,29 @@ end
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function FOX:OnEventShot(EventData)
|
||||
self:T2({eventshot=EventData})
|
||||
|
||||
-- Nil checks.
|
||||
if EventData.Weapon==nil or EventData.IniDCSUnit==nil or EventData.weapon==nil then
|
||||
return
|
||||
end
|
||||
|
||||
if EventData.Weapon==nil then
|
||||
return
|
||||
end
|
||||
if EventData.IniDCSUnit==nil then
|
||||
return
|
||||
end
|
||||
-- Create a weapon object.
|
||||
local weapon=WEAPON:New(EventData.weapon)
|
||||
|
||||
-- Weapon data.
|
||||
local _weapon = EventData.WeaponName
|
||||
local _weapon = weapon:GetTypeName()
|
||||
local _target = EventData.Weapon:getTarget()
|
||||
local _targetName = "unknown"
|
||||
local _targetUnit = nil --Wrapper.Unit#UNIT
|
||||
|
||||
-- Weapon descriptor.
|
||||
local desc=EventData.Weapon:getDesc()
|
||||
local desc=weapon.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
|
||||
local missilecategory=desc.missileCategory
|
||||
|
||||
-- Missile range.
|
||||
local missilerange=nil
|
||||
if missilecategory then
|
||||
missilerange=desc.rangeMaxAltMax
|
||||
@@ -1279,8 +1282,8 @@ function FOX:OnEventShot(EventData)
|
||||
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 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 categ = %s", tostring(weaponcategory)))
|
||||
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(weapon:GetCategory())))
|
||||
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)))
|
||||
|
||||
@@ -1292,7 +1295,7 @@ function FOX:OnEventShot(EventData)
|
||||
end
|
||||
|
||||
-- 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
|
||||
if _track then
|
||||
@@ -1301,6 +1304,7 @@ function FOX:OnEventShot(EventData)
|
||||
|
||||
missile.active=true
|
||||
missile.weapon=EventData.weapon
|
||||
missile.Weapon=weapon
|
||||
missile.missileType=_weapon
|
||||
missile.missileRange=missilerange
|
||||
missile.missileName=EventData.weapon:getName()
|
||||
@@ -1313,6 +1317,7 @@ function FOX:OnEventShot(EventData)
|
||||
missile.fuseDist=desc.fuseDist
|
||||
missile.explosive=desc.warhead.explosiveMass or desc.warhead.shapedExplosiveMass
|
||||
missile.targetOrig=missile.targetName
|
||||
missile.missileCoord=COORDINATE:New(0,0,0)
|
||||
|
||||
-- Set missile target name, unit and player.
|
||||
self:GetMissileTarget(missile)
|
||||
@@ -1631,7 +1636,7 @@ end
|
||||
|
||||
--- Check if a coordinate lies within a safe training zone.
|
||||
-- @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.
|
||||
function FOX:_CheckCoordSafe(coord)
|
||||
|
||||
@@ -1643,7 +1648,9 @@ function FOX:_CheckCoordSafe(coord)
|
||||
-- Loop over all zones.
|
||||
for _,_zone in pairs(self.safezones) do
|
||||
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
|
||||
return true
|
||||
end
|
||||
@@ -1654,7 +1661,7 @@ end
|
||||
|
||||
--- Check if a coordinate lies within a launch zone.
|
||||
-- @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.
|
||||
function FOX:_CheckCoordLaunch(coord)
|
||||
|
||||
@@ -1666,7 +1673,9 @@ function FOX:_CheckCoordLaunch(coord)
|
||||
-- Loop over all zones.
|
||||
for _,_zone in pairs(self.launchzones) do
|
||||
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
|
||||
return true
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -873,8 +873,10 @@ end
|
||||
function SCORING:OnEventBirth( Event )
|
||||
|
||||
if Event.IniUnit then
|
||||
Event.IniUnit.ThreatLevel, Event.IniUnit.ThreatType = Event.IniUnit:GetThreatLevel()
|
||||
if Event.IniObjectCategory == 1 then
|
||||
local PlayerName = Event.IniUnit:GetPlayerName()
|
||||
Event.IniUnit.BirthTime = timer.getTime()
|
||||
if PlayerName then
|
||||
self:_AddPlayerFromUnit( Event.IniUnit )
|
||||
self:SetScoringMenu( Event.IniGroup )
|
||||
@@ -1005,7 +1007,18 @@ function SCORING:_EventOnHit( Event )
|
||||
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
|
||||
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
|
||||
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()
|
||||
-- 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.
|
||||
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 == TargetCoalition then
|
||||
Player.Penalty = Player.Penalty + 10
|
||||
PlayerHit.Penalty = PlayerHit.Penalty + 10
|
||||
local Penalty = 10
|
||||
Player.Penalty = Player.Penalty + Penalty
|
||||
PlayerHit.Penalty = PlayerHit.Penalty + Penalty
|
||||
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1
|
||||
|
||||
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. " ..
|
||||
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
|
||||
"Penalty: -" .. Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
else
|
||||
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 )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
end
|
||||
self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
else
|
||||
Player.Score = Player.Score + 1
|
||||
PlayerHit.Score = PlayerHit.Score + 1
|
||||
-- Hitting a target multiple times before destoying it should not result in a higger score
|
||||
-- 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
|
||||
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. " ..
|
||||
@@ -1104,7 +1120,18 @@ function SCORING:_EventOnHit( Event )
|
||||
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
|
||||
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
|
||||
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()
|
||||
-- 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.
|
||||
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 == TargetCoalition then
|
||||
-- TODO: Penalty according scale
|
||||
Player.Penalty = Player.Penalty + 10 --* self.ScaleDestroyPenalty
|
||||
PlayerHit.Penalty = PlayerHit.Penalty + 10 --* self.ScaleDestroyPenalty
|
||||
local Penalty = 10
|
||||
Player.Penalty = Player.Penalty + Penalty --* self.ScaleDestroyPenalty
|
||||
PlayerHit.Penalty = PlayerHit.Penalty + Penalty --* self.ScaleDestroyPenalty
|
||||
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty
|
||||
|
||||
MESSAGE
|
||||
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
|
||||
TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
|
||||
"Penalty: -" .. PlayerHit.Penalty .. " = " .. Player.Score - Player.Penalty,
|
||||
"Penalty: -" .. Penalty .. " = " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Update
|
||||
)
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
: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 )
|
||||
else
|
||||
Player.Score = Player.Score + 1
|
||||
PlayerHit.Score = PlayerHit.Score + 1
|
||||
-- Hitting a target multiple times before destoying it should not result in a higger score
|
||||
-- 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
|
||||
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 )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
@@ -1211,7 +1241,7 @@ function SCORING:_EventOnDeadOrCrash( Event )
|
||||
local Destroyed = false
|
||||
|
||||
-- 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 TargetThreatType = Player.Hit[TargetCategory][TargetUnitName].ThreatType
|
||||
@@ -1240,13 +1270,13 @@ function SCORING:_EventOnDeadOrCrash( Event )
|
||||
|
||||
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
|
||||
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 )
|
||||
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
|
||||
else
|
||||
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 )
|
||||
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
|
||||
@@ -1268,13 +1298,13 @@ function SCORING:_EventOnDeadOrCrash( Event )
|
||||
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1
|
||||
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
|
||||
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 )
|
||||
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
|
||||
else
|
||||
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 )
|
||||
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
|
||||
|
||||
@@ -715,7 +715,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
|
||||
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.
|
||||
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ __Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.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/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/Scenery.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/CargoUnit.lua' )
|
||||
|
||||
@@ -10180,6 +10180,28 @@ function AIRBOSS:_GetSternCoord()
|
||||
return self.sterncoord
|
||||
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.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param Core.Point#COORDINATE Lcoord Landing position.
|
||||
|
||||
@@ -2750,12 +2750,12 @@ function CSAR:onafterLoad(From, Event, To, path, filename)
|
||||
vec3.z = tonumber(dataset[4])
|
||||
local point = COORDINATE:NewFromVec3(vec3)
|
||||
|
||||
local coalition = dataset[5]
|
||||
local country = dataset[6]
|
||||
local coalition = tonumber(dataset[5])
|
||||
local country = tonumber(dataset[6])
|
||||
local description = dataset[7]
|
||||
local typeName = dataset[8]
|
||||
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)
|
||||
end
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
-- @module Ops.CTLD
|
||||
-- @image OPS_CTLD.jpg
|
||||
|
||||
-- Last Update Jan 2023
|
||||
-- Last Update Mar 2023
|
||||
|
||||
do
|
||||
|
||||
@@ -288,7 +288,8 @@ CTLD_ENGINEERING = {
|
||||
end
|
||||
|
||||
|
||||
do
|
||||
do
|
||||
|
||||
------------------------------------------------------
|
||||
--- **CTLD_CARGO** class, extends Core.Base#BASE
|
||||
-- @type CTLD_CARGO
|
||||
@@ -494,7 +495,7 @@ CTLD_CARGO = {
|
||||
|
||||
--- Add Stock.
|
||||
-- @param #CTLD_CARGO self
|
||||
-- @param #number Number to add, one if nil.
|
||||
-- @param #number Number to add, none if nil.
|
||||
-- @return #CTLD_CARGO self
|
||||
function CTLD_CARGO:AddStock(Number)
|
||||
if self.Stock then -- Stock nil?
|
||||
@@ -506,7 +507,7 @@ CTLD_CARGO = {
|
||||
|
||||
--- Remove Stock.
|
||||
-- @param #CTLD_CARGO self
|
||||
-- @param #number Number to reduce, one if nil.
|
||||
-- @param #number Number to reduce, none if nil.
|
||||
-- @return #CTLD_CARGO self
|
||||
function CTLD_CARGO:RemoveStock(Number)
|
||||
if self.Stock then -- Stock nil?
|
||||
@@ -517,6 +518,15 @@ CTLD_CARGO = {
|
||||
return self
|
||||
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
|
||||
-- @param #CTLD_CARGO self
|
||||
-- @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.CrateDistance = 35 -- List and Load crates in this radius only.
|
||||
-- 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.minimumHoverHeight = 4 -- Hover min this low to load.
|
||||
-- my_ctld.forcehoverload = true -- Crates (not: troops) can **only** be loaded while hovering.
|
||||
@@ -932,9 +943,9 @@ do
|
||||
-- ...Checking template for ART 2S9 NONA Skid [19030lb] (SAU 2-C9) ... MISSING)
|
||||
-- ...Checking template for EWR SBORKA Air [21624lb] (Dog Ear radar) ... MISSING)
|
||||
-- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK)
|
||||
--
|
||||
--
|
||||
-- Expected template names are the ones in the rounded brackets.
|
||||
--
|
||||
--
|
||||
-- ### 5.2.1 Hints
|
||||
--
|
||||
-- The script works on the EVENTS.Shot trigger, which is used by the mod when you **drop cargo from the Hercules while flying**. Unloading on the ground does
|
||||
@@ -951,6 +962,18 @@ do
|
||||
--
|
||||
-- ["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
|
||||
--
|
||||
-- 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.
|
||||
-- @field #string version
|
||||
CTLD.version="1.0.29"
|
||||
CTLD.version="1.0.32"
|
||||
|
||||
--- Instantiate a new CTLD.
|
||||
-- @param #CTLD self
|
||||
@@ -1321,7 +1344,8 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
||||
self.forcehoverload = true
|
||||
self.hoverautoloading = true
|
||||
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.movetroopstowpzone = true
|
||||
self.movetroopsdistance = 5000
|
||||
@@ -1776,6 +1800,22 @@ function CTLD:_FindTroopsCargoObject(Name)
|
||||
return nil
|
||||
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!
|
||||
-- @param #CTLD self
|
||||
-- @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
|
||||
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.
|
||||
-- @param #CTLD self
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
@@ -2199,7 +2317,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop)
|
||||
end
|
||||
-- loop crates needed
|
||||
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
|
||||
cratedistance = (i-1)*2.5 + capabilities.length
|
||||
if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end
|
||||
@@ -2299,10 +2417,10 @@ function CTLD:InjectStatics(Zone, Cargo, RandomCoord)
|
||||
--local number = 1
|
||||
local cratesneeded = cargotype:GetCratesNeeded() --#number
|
||||
local cratetemplate = "Container"-- #string
|
||||
local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000))
|
||||
local cratename = cargotype:GetName()
|
||||
local cgotype = cargotype:GetType()
|
||||
local cgomass = cargotype:GetMass()
|
||||
local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000))
|
||||
local isstatic = false
|
||||
if cgotype == CTLD_CARGO.Enum.STATIC then
|
||||
cratetemplate = cargotype:GetTemplates()
|
||||
@@ -2515,7 +2633,7 @@ function CTLD:_LoadCratesNearby(Group, Unit)
|
||||
crateind = _crate:GetID()
|
||||
end
|
||||
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()
|
||||
end
|
||||
end
|
||||
@@ -4384,6 +4502,7 @@ end
|
||||
_troop:AddStock(number)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- User - function to add stock of a certain crates type
|
||||
@@ -4401,6 +4520,115 @@ end
|
||||
_troop:AddStock(number)
|
||||
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
|
||||
|
||||
--- User - function to remove stock of a certain troops type
|
||||
@@ -4418,6 +4646,7 @@ end
|
||||
_troop:RemoveStock(number)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- User - function to remove stock of a certain crates type
|
||||
@@ -4438,6 +4667,24 @@ end
|
||||
return self
|
||||
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
|
||||
-- @param #CTLD self
|
||||
-- @return #CTLD self
|
||||
@@ -5273,7 +5520,7 @@ CTLD_HERCULES = {
|
||||
ClassName = "CTLD_HERCULES",
|
||||
lid = "",
|
||||
Name = "",
|
||||
Version = "0.0.2",
|
||||
Version = "0.0.3",
|
||||
}
|
||||
|
||||
--- Define cargo types.
|
||||
@@ -5569,6 +5816,34 @@ function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Positio
|
||||
return self
|
||||
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
|
||||
-- @param #CTLD_HERCULES self
|
||||
-- @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 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, 5)
|
||||
--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, 10)
|
||||
else
|
||||
self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country)
|
||||
@@ -5618,8 +5893,12 @@ function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direct
|
||||
if ParatrooperGroupSpawn == true then
|
||||
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0)
|
||||
else
|
||||
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)
|
||||
if self.CTLD.dropAsCargoCrate then
|
||||
self:Cargo_SpawnDroppedAsCargo(Cargo_Type_name, Cargo_Content_position)
|
||||
else
|
||||
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)
|
||||
end
|
||||
end
|
||||
else
|
||||
self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country)
|
||||
|
||||
@@ -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.
|
||||
-- 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.
|
||||
--
|
||||
|
||||
@@ -489,6 +489,7 @@ ENUMS.ReportingName =
|
||||
--Mosquito = "A-20",
|
||||
Skyhawk = "A-4E",
|
||||
Viggen = "AJS37",
|
||||
Harrier_B = "AV8BNA",
|
||||
Harrier = "AV-8B",
|
||||
Spirit = "B-2",
|
||||
Aviojet = "C-101",
|
||||
|
||||
@@ -44,6 +44,7 @@ SOCKET = {
|
||||
}
|
||||
|
||||
--- Data type. This is the keyword the socket listener uses.
|
||||
-- @type SOCKET.DataType
|
||||
-- @field #string TEXT Plain text.
|
||||
-- @field #string BOMBRESULT Range bombing.
|
||||
-- @field #string STRAFERESULT Range strafeing result.
|
||||
|
||||
@@ -489,6 +489,31 @@ UTILS.hPa2inHg = function( hPa )
|
||||
return hPa * 0.0295299830714
|
||||
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.
|
||||
-- @param #number knots Speed in knots.
|
||||
-- @param #number altitude Altitude in feet
|
||||
@@ -642,7 +667,10 @@ function UTILS.Round( num, idp )
|
||||
return math.floor( num * mult + 0.5 ) / mult
|
||||
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 )
|
||||
local f, err = loadstring( s )
|
||||
if f then
|
||||
@@ -652,7 +680,15 @@ function UTILS.DoString( s )
|
||||
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 )
|
||||
-- collect the keys
|
||||
local keys = {}
|
||||
@@ -677,7 +713,16 @@ function UTILS.spairs( t, order )
|
||||
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 )
|
||||
-- collect the keys
|
||||
local keys = {}
|
||||
@@ -702,7 +747,14 @@ function UTILS.kpairs( t, getkey, order )
|
||||
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 )
|
||||
-- collect the keys
|
||||
|
||||
@@ -1197,6 +1249,20 @@ function UTILS.HdgDiff(h1, h2)
|
||||
return math.abs(delta)
|
||||
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.
|
||||
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
|
||||
@@ -2001,9 +2067,9 @@ function UTILS.GenerateUHFrequencies()
|
||||
local _start = 220000000
|
||||
|
||||
while _start < 399000000 do
|
||||
if _start ~= 243000000 then
|
||||
table.insert(FreeUHFFrequencies, _start)
|
||||
end
|
||||
if _start ~= 243000000 then
|
||||
table.insert(FreeUHFFrequencies, _start)
|
||||
end
|
||||
_start = _start + 500000
|
||||
end
|
||||
|
||||
@@ -2807,3 +2873,45 @@ function UTILS.IsAnyInTable(Table, Objects, Key)
|
||||
|
||||
return false
|
||||
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
|
||||
|
||||
@@ -540,8 +540,12 @@ AIRBASE.SouthAtlantic={
|
||||
["Rio_Chico"] = "Rio Chico",
|
||||
["Franco_Bianco"] = "Franco Bianco",
|
||||
["Goose_Green"] = "Goose Green",
|
||||
["Hipico"] = "Hipico",
|
||||
["Hipico_Flying_Club"] = "Hipico Flying Club",
|
||||
["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".
|
||||
|
||||
@@ -62,15 +62,15 @@
|
||||
--
|
||||
-- @field #CLIENT
|
||||
CLIENT = {
|
||||
ClassName = "CLIENT",
|
||||
ClientName = nil,
|
||||
ClientAlive = false,
|
||||
ClientTransport = false,
|
||||
ClientBriefingShown = false,
|
||||
_Menus = {},
|
||||
_Tasks = {},
|
||||
Messages = {},
|
||||
Players = {},
|
||||
ClassName = "CLIENT",
|
||||
ClientName = nil,
|
||||
ClientAlive = false,
|
||||
ClientTransport = false,
|
||||
ClientBriefingShown = false,
|
||||
_Menus = {},
|
||||
_Tasks = {},
|
||||
Messages = {},
|
||||
Players = {},
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,22 @@ function CLIENT:Find(DCSUnit, Error)
|
||||
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.
|
||||
-- As an optional parameter, a briefing text can be given also.
|
||||
@@ -105,13 +121,13 @@ end
|
||||
-- @return #CLIENT
|
||||
-- @usage
|
||||
-- -- Create new Clients.
|
||||
-- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' )
|
||||
-- Mission:AddGoal( DeploySA6TroopsGoal )
|
||||
-- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' )
|
||||
-- Mission:AddGoal( DeploySA6TroopsGoal )
|
||||
--
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() )
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() )
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() )
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() )
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() )
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() )
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() )
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() )
|
||||
function CLIENT:FindByName( ClientName, ClientBriefing, Error )
|
||||
|
||||
-- Client
|
||||
@@ -124,7 +140,7 @@ function CLIENT:FindByName( ClientName, ClientBriefing, Error )
|
||||
|
||||
ClientFound.MessageSwitch = true
|
||||
|
||||
return ClientFound
|
||||
return ClientFound
|
||||
end
|
||||
|
||||
if not Error then
|
||||
@@ -262,8 +278,8 @@ end
|
||||
-- @param #CLIENT self
|
||||
-- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client.
|
||||
function CLIENT:Reset( ClientName )
|
||||
self:F()
|
||||
self._Menus = {}
|
||||
self:F()
|
||||
self._Menus = {}
|
||||
end
|
||||
|
||||
-- Is Functions
|
||||
@@ -347,85 +363,85 @@ function CLIENT:GetDCSGroup()
|
||||
self:F3()
|
||||
|
||||
-- local ClientData = Group.getByName( self.ClientName )
|
||||
-- if ClientData and ClientData:isExist() then
|
||||
-- self:T( self.ClientName .. " : group found!" )
|
||||
-- return ClientData
|
||||
-- else
|
||||
-- return nil
|
||||
-- end
|
||||
-- if ClientData and ClientData:isExist() then
|
||||
-- self:T( self.ClientName .. " : group found!" )
|
||||
-- return ClientData
|
||||
-- else
|
||||
-- return nil
|
||||
-- end
|
||||
|
||||
local ClientUnit = Unit.getByName( self.ClientName )
|
||||
|
||||
local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) }
|
||||
|
||||
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
|
||||
self:T3( { "CoalitionData:", CoalitionData } )
|
||||
for UnitId, UnitData in pairs( CoalitionData ) do
|
||||
self:T3( { "UnitData:", UnitData } )
|
||||
if UnitData and UnitData:isExist() then
|
||||
local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) }
|
||||
|
||||
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
|
||||
self:T3( { "CoalitionData:", CoalitionData } )
|
||||
for UnitId, UnitData in pairs( CoalitionData ) do
|
||||
self:T3( { "UnitData:", UnitData } )
|
||||
if UnitData and UnitData:isExist() then
|
||||
|
||||
--self:F(self.ClientName)
|
||||
if ClientUnit then
|
||||
|
||||
local ClientGroup = ClientUnit:getGroup()
|
||||
|
||||
if ClientGroup then
|
||||
self:T3( "ClientGroup = " .. self.ClientName )
|
||||
|
||||
if ClientGroup:isExist() and UnitData:getGroup():isExist() then
|
||||
|
||||
if ClientGroup:getID() == UnitData:getGroup():getID() then
|
||||
self:T3( "Normal logic" )
|
||||
self:T3( self.ClientName .. " : group found!" )
|
||||
local ClientGroup = ClientUnit:getGroup()
|
||||
|
||||
if ClientGroup then
|
||||
self:T3( "ClientGroup = " .. self.ClientName )
|
||||
|
||||
if ClientGroup:isExist() and UnitData:getGroup():isExist() then
|
||||
|
||||
if ClientGroup:getID() == UnitData:getGroup():getID() then
|
||||
self:T3( "Normal logic" )
|
||||
self:T3( self.ClientName .. " : group found!" )
|
||||
self.ClientGroupID = ClientGroup:getID()
|
||||
self.ClientGroupName = ClientGroup:getName()
|
||||
return ClientGroup
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Now we need to resolve the bugs in DCS 1.5 ...
|
||||
-- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil)
|
||||
self:T3( "Bug 1.5 logic" )
|
||||
|
||||
local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate
|
||||
|
||||
self.ClientGroupID = ClientGroupTemplate.groupId
|
||||
|
||||
self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName
|
||||
|
||||
self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" )
|
||||
return ClientGroup
|
||||
|
||||
end
|
||||
-- else
|
||||
-- error( "Client " .. self.ClientName .. " not found!" )
|
||||
end
|
||||
else
|
||||
--self:F( { "Client not found!", self.ClientName } )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- For non player clients
|
||||
if ClientUnit then
|
||||
local ClientGroup = ClientUnit:getGroup()
|
||||
if ClientGroup then
|
||||
self:T3( "ClientGroup = " .. self.ClientName )
|
||||
if ClientGroup:isExist() then
|
||||
self:T3( "Normal logic" )
|
||||
self:T3( self.ClientName .. " : group found!" )
|
||||
return ClientGroup
|
||||
end
|
||||
end
|
||||
self.ClientGroupName = ClientGroup:getName()
|
||||
return ClientGroup
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Now we need to resolve the bugs in DCS 1.5 ...
|
||||
-- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil)
|
||||
self:T3( "Bug 1.5 logic" )
|
||||
|
||||
local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate
|
||||
|
||||
self.ClientGroupID = ClientGroupTemplate.groupId
|
||||
|
||||
self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName
|
||||
|
||||
self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" )
|
||||
return ClientGroup
|
||||
|
||||
end
|
||||
-- else
|
||||
-- error( "Client " .. self.ClientName .. " not found!" )
|
||||
end
|
||||
else
|
||||
--self:F( { "Client not found!", self.ClientName } )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Nothing could be found :(
|
||||
self.ClientGroupID = nil
|
||||
self.ClientGroupName = nil
|
||||
|
||||
return nil
|
||||
|
||||
-- For non player clients
|
||||
if ClientUnit then
|
||||
local ClientGroup = ClientUnit:getGroup()
|
||||
if ClientGroup then
|
||||
self:T3( "ClientGroup = " .. self.ClientName )
|
||||
if ClientGroup:isExist() then
|
||||
self:T3( "Normal logic" )
|
||||
self:T3( self.ClientName .. " : group found!" )
|
||||
return ClientGroup
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Nothing could be found :(
|
||||
self.ClientGroupID = nil
|
||||
self.ClientGroupName = nil
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
@@ -437,7 +453,7 @@ function CLIENT:GetClientGroupID()
|
||||
-- This updates the ID.
|
||||
self:GetDCSGroup()
|
||||
|
||||
return self.ClientGroupID
|
||||
return self.ClientGroupID
|
||||
end
|
||||
|
||||
|
||||
@@ -449,7 +465,7 @@ function CLIENT:GetClientGroupName()
|
||||
-- This updates the group name.
|
||||
self:GetDCSGroup()
|
||||
|
||||
return self.ClientGroupName
|
||||
return self.ClientGroupName
|
||||
end
|
||||
|
||||
--- Returns the UNIT of the CLIENT.
|
||||
@@ -458,23 +474,23 @@ end
|
||||
function CLIENT:GetClientGroupUnit()
|
||||
self:F2()
|
||||
|
||||
local ClientDCSUnit = Unit.getByName( self.ClientName )
|
||||
local ClientDCSUnit = Unit.getByName( self.ClientName )
|
||||
|
||||
self:T( self.ClientDCSUnit )
|
||||
|
||||
if ClientDCSUnit and ClientDCSUnit:isExist() then
|
||||
local ClientUnit=_DATABASE:FindUnit( self.ClientName )
|
||||
return ClientUnit
|
||||
end
|
||||
|
||||
return nil
|
||||
if ClientDCSUnit and ClientDCSUnit:isExist() then
|
||||
local ClientUnit=_DATABASE:FindUnit( self.ClientName )
|
||||
return ClientUnit
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the DCSUnit of the CLIENT.
|
||||
-- @param #CLIENT self
|
||||
-- @return DCS#Unit
|
||||
function CLIENT:GetClientGroupDCSUnit()
|
||||
self:F2()
|
||||
self:F2()
|
||||
|
||||
local ClientDCSUnit = Unit.getByName( self.ClientName )
|
||||
|
||||
@@ -489,29 +505,29 @@ end
|
||||
-- @param #CLIENT self
|
||||
-- @return #boolean true is a transport.
|
||||
function CLIENT:IsTransport()
|
||||
self:F()
|
||||
return self.ClientTransport
|
||||
self:F()
|
||||
return self.ClientTransport
|
||||
end
|
||||
|
||||
--- Shows the @{AI.AI_Cargo#CARGO} contained within the CLIENT to the player as a message.
|
||||
-- The @{AI.AI_Cargo#CARGO} is shown using the @{Core.Message#MESSAGE} distribution system.
|
||||
-- @param #CLIENT self
|
||||
function CLIENT:ShowCargo()
|
||||
self:F()
|
||||
self:F()
|
||||
|
||||
local CargoMsg = ""
|
||||
local CargoMsg = ""
|
||||
|
||||
for CargoName, Cargo in pairs( CARGOS ) do
|
||||
if self == Cargo:IsLoadedInClient() then
|
||||
CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n"
|
||||
end
|
||||
end
|
||||
for CargoName, Cargo in pairs( CARGOS ) do
|
||||
if self == Cargo:IsLoadedInClient() then
|
||||
CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n"
|
||||
end
|
||||
end
|
||||
|
||||
if CargoMsg == "" then
|
||||
CargoMsg = "empty"
|
||||
end
|
||||
if CargoMsg == "" then
|
||||
CargoMsg = "empty"
|
||||
end
|
||||
|
||||
self:Message( CargoMsg, 15, "Co-Pilot: Cargo Status", 30 )
|
||||
self:Message( CargoMsg, 15, "Co-Pilot: Cargo Status", 30 )
|
||||
|
||||
end
|
||||
|
||||
@@ -526,39 +542,39 @@ end
|
||||
-- @param #number MessageInterval is the interval in seconds between the display of the @{Core.Message#MESSAGE} when the CLIENT is in the air.
|
||||
-- @param #string MessageID is the identifier of the message when displayed with intervals.
|
||||
function CLIENT:Message( Message, MessageDuration, MessageCategory, MessageInterval, MessageID )
|
||||
self:F( { Message, MessageDuration, MessageCategory, MessageInterval } )
|
||||
self:F( { Message, MessageDuration, MessageCategory, MessageInterval } )
|
||||
|
||||
if self.MessageSwitch == true then
|
||||
if MessageCategory == nil then
|
||||
MessageCategory = "Messages"
|
||||
end
|
||||
if MessageID ~= nil then
|
||||
if self.Messages[MessageID] == nil then
|
||||
self.Messages[MessageID] = {}
|
||||
self.Messages[MessageID].MessageId = MessageID
|
||||
self.Messages[MessageID].MessageTime = timer.getTime()
|
||||
self.Messages[MessageID].MessageDuration = MessageDuration
|
||||
if MessageInterval == nil then
|
||||
self.Messages[MessageID].MessageInterval = 600
|
||||
else
|
||||
self.Messages[MessageID].MessageInterval = MessageInterval
|
||||
end
|
||||
MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self )
|
||||
else
|
||||
if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then
|
||||
if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + 10 then
|
||||
MESSAGE:New( Message, MessageDuration , MessageCategory):ToClient( self )
|
||||
self.Messages[MessageID].MessageTime = timer.getTime()
|
||||
end
|
||||
else
|
||||
if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + self.Messages[MessageID].MessageInterval then
|
||||
MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self )
|
||||
self.Messages[MessageID].MessageTime = timer.getTime()
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if self.MessageSwitch == true then
|
||||
if MessageCategory == nil then
|
||||
MessageCategory = "Messages"
|
||||
end
|
||||
if MessageID ~= nil then
|
||||
if self.Messages[MessageID] == nil then
|
||||
self.Messages[MessageID] = {}
|
||||
self.Messages[MessageID].MessageId = MessageID
|
||||
self.Messages[MessageID].MessageTime = timer.getTime()
|
||||
self.Messages[MessageID].MessageDuration = MessageDuration
|
||||
if MessageInterval == nil then
|
||||
self.Messages[MessageID].MessageInterval = 600
|
||||
else
|
||||
self.Messages[MessageID].MessageInterval = MessageInterval
|
||||
end
|
||||
MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self )
|
||||
else
|
||||
if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then
|
||||
if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + 10 then
|
||||
MESSAGE:New( Message, MessageDuration , MessageCategory):ToClient( self )
|
||||
self.Messages[MessageID].MessageTime = timer.getTime()
|
||||
end
|
||||
else
|
||||
if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + self.Messages[MessageID].MessageInterval then
|
||||
MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self )
|
||||
self.Messages[MessageID].MessageTime = timer.getTime()
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1653,7 +1653,7 @@ function GROUP:GetMinHeight()
|
||||
return nil
|
||||
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.
|
||||
-- @param #GROUP self
|
||||
-- @return #number Maximum height found.
|
||||
@@ -1668,7 +1668,7 @@ function GROUP:GetMaxHeight()
|
||||
for Index, UnitData in pairs( DCSGroup:getUnits() ) do
|
||||
local UnitData = UnitData -- DCS#Unit
|
||||
|
||||
local UnitHeight = UnitData:getPoint()
|
||||
local UnitHeight = UnitData:getPoint().p.y -- Height -- found by @Heavydrinker
|
||||
|
||||
if UnitHeight > GroupHeightMax then
|
||||
GroupHeightMax = UnitHeight
|
||||
@@ -2785,11 +2785,16 @@ end
|
||||
-- @param #GROUP self
|
||||
-- @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 #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.
|
||||
-- @return #string Callsign
|
||||
-- @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,{
|
||||
-- Devil = 'Bengal',
|
||||
-- Snake = 'Winder',
|
||||
@@ -2797,12 +2802,12 @@ end
|
||||
-- Enfield = 'Victory',
|
||||
-- Uzi = 'Evil Eye'
|
||||
-- })
|
||||
--
|
||||
-- results in this outcome if the group has Callsign "Enfield 9 1" on the 1st #UNIT of the group:
|
||||
--
|
||||
-- 'Victory 9'
|
||||
--
|
||||
--
|
||||
-- -- then GetCustomCallsign will return
|
||||
-- -- Enfield-1 for Slot 1
|
||||
-- -- Apollo for Slot 2 or Apollo 403 if Keepnumber is set
|
||||
-- -- Apollo for Slot 3
|
||||
-- -- Bengal-4 for Slot 4
|
||||
|
||||
function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
|
||||
--self:I("GetCustomCallSign")
|
||||
|
||||
@@ -2810,14 +2815,18 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
|
||||
if self:IsAlive() then
|
||||
local IsPlayer = self:IsPlayer()
|
||||
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)
|
||||
local groupname = self:GetName()
|
||||
local callnumber = string.match(shortcallsign, "(%d+)$" ) or "91" -- 91
|
||||
local callnumbermajor = string.char(string.byte(callnumber,1)) -- 9
|
||||
local callnumberminor = string.char(string.byte(callnumber,2)) -- 1
|
||||
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
|
||||
if Keepnumber then
|
||||
shortcallsign = string.match(groupname,"#(.+)") or "Ghost 111" -- Ghostrider 219
|
||||
@@ -2830,32 +2839,28 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
|
||||
shortcallsign = string.match(self:GetPlayerName(),"|%s*([%a]+)") or string.match(self:GetPlayerName(),"|%s*([%d]+)") or "Ghost" -- Ghostrider
|
||||
personalized = true
|
||||
end
|
||||
|
||||
if (not personalized) and CallsignTranslations and CallsignTranslations[callsignroot] then
|
||||
callsignroot = CallsignTranslations[callsignroot]
|
||||
|
||||
if personalized then
|
||||
-- player personalized callsign
|
||||
-- remove trailing/leading spaces
|
||||
shortcallsign=string.gsub(shortcallsign,"^%s*","")
|
||||
shortcallsign=string.gsub(shortcallsign,"%s*$","")
|
||||
if Keepnumber then
|
||||
return shortcallsign -- Ghostrider 219
|
||||
elseif ShortCallsign then
|
||||
callsign = shortcallsign.." "..callnumbermajor -- Ghostrider 9
|
||||
else
|
||||
callsign = shortcallsign.." "..callnumbermajor.." "..callnumberminor -- Ghostrider 9 1
|
||||
end
|
||||
return callsign
|
||||
end
|
||||
|
||||
if personalized then
|
||||
-- player personalized callsign
|
||||
-- remove trailing/leading spaces
|
||||
shortcallsign=string.gsub(shortcallsign,"^%s*","")
|
||||
shortcallsign=string.gsub(shortcallsign,"%s*$","")
|
||||
if Keepnumber then
|
||||
return shortcallsign -- Ghostrider 219
|
||||
elseif ShortCallsign then
|
||||
callsign = shortcallsign.." "..callnumbermajor -- Ghostrider 9
|
||||
|
||||
-- AI or not personalized
|
||||
if ShortCallsign then
|
||||
callsign = callsignroot.." "..callnumbermajor -- Uzi/Victory 9
|
||||
else
|
||||
callsign = shortcallsign.." "..callnumbermajor.." "..callnumberminor -- Ghostrider 9 1
|
||||
callsign = callsignroot.." "..callnumbermajor.." "..callnumberminor -- Uzi/Victory 9 1
|
||||
end
|
||||
return callsign
|
||||
end
|
||||
|
||||
-- AI or not personalized
|
||||
if ShortCallsign then
|
||||
callsign = callsignroot.." "..callnumbermajor -- Uzi/Victory 9
|
||||
else
|
||||
callsign = callsignroot.." "..callnumbermajor.." "..callnumberminor -- Uzi/Victory 9 1
|
||||
end
|
||||
|
||||
--self:I("Generated Callsign = " .. callsign)
|
||||
end
|
||||
|
||||
833
Moose Development/Moose/Wrapper/Net.lua
Normal file
833
Moose Development/Moose/Wrapper/Net.lua
Normal 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
|
||||
@@ -703,11 +703,11 @@ function POSITIONABLE:IsSubmarine()
|
||||
|
||||
if DCSUnit then
|
||||
local UnitDescriptor = DCSUnit:getDesc()
|
||||
if UnitDescriptor.attributes["Submarines"] == true then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
if UnitDescriptor.attributes["Submarines"] == true then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
self:E( { "Cannot check IsSubmarine", Positionable = self, Alive = self:IsAlive() } )
|
||||
@@ -845,6 +845,78 @@ function POSITIONABLE:GetVelocityKNOTS()
|
||||
return UTILS.MpsToKnots( self:GetVelocityMPS() )
|
||||
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.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @return #number Angle of attack in degrees.
|
||||
@@ -1696,6 +1768,7 @@ do -- Cargo
|
||||
["tt_DSHK"] = 6,
|
||||
["HL_KORD"] = 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.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ### Contributions: **Applevangelist**
|
||||
-- ### Contributions: **Applevangelist**, **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -12,12 +12,12 @@
|
||||
-- @image Wrapper_Scenery.JPG
|
||||
|
||||
|
||||
|
||||
--- @type SCENERY
|
||||
-- @field #string ClassName
|
||||
-- @field #string SceneryName
|
||||
-- @field #DCS.Object SceneryObject
|
||||
-- @field #number Life0
|
||||
--- SCENERY Class
|
||||
-- @type SCENERY
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string SceneryName Name of the scenery object.
|
||||
-- @field DCS#Object SceneryObject DCS scenery object.
|
||||
-- @field #number Life0 Initial life points.
|
||||
-- @extends Wrapper.Positionable#POSITIONABLE
|
||||
|
||||
|
||||
@@ -37,12 +37,16 @@ SCENERY = {
|
||||
--- Register scenery object as POSITIONABLE.
|
||||
--@param #SCENERY self
|
||||
--@param #string SceneryName Scenery name.
|
||||
--@param #DCS.Object SceneryObject DCS scenery object.
|
||||
--@param DCS#Object SceneryObject DCS scenery object.
|
||||
--@return #SCENERY Scenery object.
|
||||
function SCENERY:Register( SceneryName, SceneryObject )
|
||||
|
||||
local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) )
|
||||
|
||||
self.SceneryName = SceneryName
|
||||
|
||||
self.SceneryObject = SceneryObject
|
||||
|
||||
if self.SceneryObject then
|
||||
self.Life0 = self.SceneryObject:getLife()
|
||||
else
|
||||
@@ -53,7 +57,7 @@ end
|
||||
|
||||
--- Obtain DCS Object from the SCENERY Object.
|
||||
--@param #SCENERY self
|
||||
--@return #DCS.Object DCS scenery object.
|
||||
--@return DCS#Object DCS scenery object.
|
||||
function SCENERY:GetDCSObject()
|
||||
return self.SceneryObject
|
||||
end
|
||||
@@ -69,7 +73,7 @@ function SCENERY:GetLife()
|
||||
return life
|
||||
end
|
||||
|
||||
--- Get current initial life points from the SCENERY Object.
|
||||
--- Get initial life points of the SCENERY Object.
|
||||
--@param #SCENERY self
|
||||
--@return #number life
|
||||
function SCENERY:GetLife0()
|
||||
@@ -90,7 +94,7 @@ function SCENERY:IsDead()
|
||||
return self:GetLife() < 1 and true or false
|
||||
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
|
||||
--@return #number Threat level 0.
|
||||
--@return #string "Scenery".
|
||||
|
||||
@@ -89,9 +89,9 @@
|
||||
--
|
||||
-- @field #UNIT
|
||||
UNIT = {
|
||||
ClassName="UNIT",
|
||||
UnitName=nil,
|
||||
GroupName=nil,
|
||||
ClassName="UNIT",
|
||||
UnitName=nil,
|
||||
GroupName=nil,
|
||||
}
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ end
|
||||
|
||||
--- Get the DCS unit object.
|
||||
-- @param #UNIT self
|
||||
-- @return DCS#Unit
|
||||
-- @return DCS#Unit The DCS unit object.
|
||||
function UNIT:GetDCSObject()
|
||||
|
||||
local DCSUnit = Unit.getByName( self.UnitName )
|
||||
@@ -325,14 +325,19 @@ function UNIT:IsAlive()
|
||||
local DCSUnit = self:GetDCSObject() -- DCS#Unit
|
||||
|
||||
if DCSUnit then
|
||||
local UnitIsAlive = DCSUnit:isExist() and DCSUnit:isActive()
|
||||
local UnitIsAlive = DCSUnit:isExist() and DCSUnit:isActive() -- and DCSUnit:getLife() > 1
|
||||
return UnitIsAlive
|
||||
end
|
||||
|
||||
return nil
|
||||
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.
|
||||
-- @param #UNIT self
|
||||
@@ -626,7 +631,7 @@ function UNIT:IsFuelSupply()
|
||||
return false
|
||||
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
|
||||
-- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist.
|
||||
function UNIT:GetGroup()
|
||||
@@ -915,7 +920,7 @@ function UNIT:GetLife()
|
||||
|
||||
local DCSUnit = self:GetDCSObject()
|
||||
|
||||
if DCSUnit then
|
||||
if DCSUnit and DCSUnit:isExist() then
|
||||
local UnitLife = DCSUnit:getLife()
|
||||
return UnitLife
|
||||
end
|
||||
@@ -967,6 +972,24 @@ function UNIT:GetDamageRelative()
|
||||
return 1
|
||||
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
|
||||
--
|
||||
-- * Unit.Category.AIRPLANE
|
||||
|
||||
839
Moose Development/Moose/Wrapper/Weapon.lua
Normal file
839
Moose Development/Moose/Wrapper/Weapon.lua
Normal 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
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -34,6 +34,7 @@ Core/MarkerOps_Base.lua
|
||||
Core/Astar.lua
|
||||
Core/Condition.lua
|
||||
Core/TextAndSound.lua
|
||||
Core/Pathline.lua
|
||||
|
||||
Wrapper/Object.lua
|
||||
Wrapper/Identifiable.lua
|
||||
@@ -46,6 +47,8 @@ Wrapper/Static.lua
|
||||
Wrapper/Airbase.lua
|
||||
Wrapper/Scenery.lua
|
||||
Wrapper/Marker.lua
|
||||
Wrapper/Weapon.lua
|
||||
Wrapper/Net.lua
|
||||
|
||||
Cargo/Cargo.lua
|
||||
Cargo/CargoUnit.lua
|
||||
|
||||
Reference in New Issue
Block a user