mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Compare commits
106 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 | ||
|
|
48721859fa | ||
|
|
ee59d999f5 | ||
|
|
2f4f98cfe0 | ||
|
|
ac1f202c19 | ||
|
|
3f97ba3bd7 | ||
|
|
819912cfd3 | ||
|
|
50ee1ae922 | ||
|
|
c0442fca68 | ||
|
|
c20927b6d7 | ||
|
|
0d5b6d4c3e | ||
|
|
bdc08a530d | ||
|
|
6ab1632b4b | ||
|
|
cec865b9fb | ||
|
|
d763e924a9 | ||
|
|
57a30621e1 | ||
|
|
f344084791 | ||
|
|
be68314c3a | ||
|
|
53cff8229b | ||
|
|
1d52e27668 | ||
|
|
6ec867196c | ||
|
|
80798f278c | ||
|
|
4e61bbb92e | ||
|
|
fabab9bfbb | ||
|
|
4ae0089e4f | ||
|
|
d82bce79c9 | ||
|
|
55fcaf1c05 | ||
|
|
e83df502ed | ||
|
|
6501e89fa2 | ||
|
|
91801d441f | ||
|
|
dd2a4ee7ff | ||
|
|
8cedd88ce2 | ||
|
|
c3fde2b698 | ||
|
|
59e8aed445 | ||
|
|
e47b8f377c | ||
|
|
793c0d988e | ||
|
|
b0eef34146 | ||
|
|
3fbfb8b528 | ||
|
|
cdd240abb7 | ||
|
|
5d802f0e16 | ||
|
|
8661d07e1e | ||
|
|
41eec658e0 | ||
|
|
9facf07955 | ||
|
|
cd4844d26c | ||
|
|
9619b11c58 | ||
|
|
535060153a | ||
|
|
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,10 +403,147 @@ do -- Zones
|
||||
-- Add zone to DB.
|
||||
self:AddZone( ZoneName, Zone_Polygon )
|
||||
end
|
||||
|
||||
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 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 free Polygon Drawing"
|
||||
|
||||
-- Reference point. All other points need to be translated by this.
|
||||
local vec2={x=objectData.mapX, y=objectData.mapY}
|
||||
|
||||
-- Debug stuff.
|
||||
--local vec3={x=objectData.mapX, y=0, z=objectData.mapY}
|
||||
--local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY")
|
||||
--trigger.action.markToAll(id, "mapXY", vec3)
|
||||
|
||||
-- Copy points array.
|
||||
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
|
||||
|
||||
-- Remove last point.
|
||||
table.remove(points, #points)
|
||||
|
||||
-- Debug output
|
||||
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)
|
||||
|
||||
-- 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
|
||||
|
||||
@@ -991,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
|
||||
@@ -1041,7 +1211,7 @@ end
|
||||
function DATABASE:_RegisterAirbase(airbase)
|
||||
|
||||
if airbase then
|
||||
|
||||
|
||||
-- Get the airbase name.
|
||||
local DCSAirbaseName = airbase:getName()
|
||||
|
||||
@@ -1670,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.
|
||||
--
|
||||
@@ -1224,7 +1224,7 @@ function EVENT:onEvent( Event )
|
||||
if Event.TgtObjectCategory == Object.Category.STATIC then
|
||||
-- get base data
|
||||
Event.TgtDCSUnit = Event.target
|
||||
if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object
|
||||
if Event.target:isExist() and Event.id ~= 33 and not Event.TgtObjectCategory == Object.Category.COORDINATE then -- leave out ejected seat object
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false )
|
||||
@@ -1280,9 +1280,11 @@ function EVENT:onEvent( Event )
|
||||
--local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :(
|
||||
-- However, this is not a big thing, as the aircraft the pilot ejected from is usually long crashed before the ejected pilot touches the ground.
|
||||
--Event.Place=UNIT:Find(Event.place)
|
||||
else
|
||||
Event.Place=AIRBASE:Find(Event.place)
|
||||
Event.PlaceName=Event.Place:GetName()
|
||||
else
|
||||
if Event.place:isExist() and Event.place:getCategory() ~= Object.Category.SCENERY then
|
||||
Event.Place=AIRBASE:Find(Event.place)
|
||||
Event.PlaceName=Event.Place:GetName()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -5,13 +5,19 @@
|
||||
-- * Create an easy way to tap into markers added to the F10 map by users.
|
||||
-- * Recognize own tag and list of keywords.
|
||||
-- * Matched keywords are handed down to functions.
|
||||
-- ##Listen for your tag
|
||||
-- myMarker = MARKEROPS_BASE:New("tag", {}, false)
|
||||
-- function myMarker:OnAfterMarkChanged(From, Event, To, Text, Keywords, Coord, idx)
|
||||
--
|
||||
-- end
|
||||
-- Make sure to use the "MarkChanged" event as "MarkAdded" comes in right after the user places a blank marker and your callback will never be called.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **Applevangelist**
|
||||
--
|
||||
-- Date: 5 May 2021
|
||||
-- Last Update: Sep 2022
|
||||
-- Last Update: Feb 2023
|
||||
--
|
||||
-- ===
|
||||
---
|
||||
@@ -44,7 +50,7 @@ MARKEROPS_BASE = {
|
||||
ClassName = "MARKEROPS",
|
||||
Tag = "mytag",
|
||||
Keywords = {},
|
||||
version = "0.1.0",
|
||||
version = "0.1.1",
|
||||
debug = false,
|
||||
Casesensitive = true,
|
||||
}
|
||||
@@ -118,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
|
||||
@@ -166,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
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -406,6 +406,42 @@ do -- COORDINATE
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns the magnetic declination at the given coordinate.
|
||||
-- NOTE that this needs `require` to be available so you need to desanitize the `MissionScripting.lua` file in your DCS/Scrips folder.
|
||||
-- If `require` is not available, a constant value for the whole map.
|
||||
-- @param #COORDINATE self
|
||||
-- @param #number Month (Optional) The month at which the declination is calculated. Default is the mission month.
|
||||
-- @param #number Year (Optional) The year at which the declination is calculated. Default is the mission year.
|
||||
-- @return #number Magnetic declination in degrees.
|
||||
function COORDINATE:GetMagneticDeclination(Month, Year)
|
||||
|
||||
local decl=UTILS.GetMagneticDeclination()
|
||||
|
||||
if require then
|
||||
|
||||
local magvar = require('magvar')
|
||||
|
||||
if magvar then
|
||||
|
||||
local date, year, month, day=UTILS.GetDCSMissionDate()
|
||||
|
||||
magvar.init(Month or month, Year or year)
|
||||
|
||||
local lat, lon=self:GetLLDDM()
|
||||
|
||||
decl=magvar.get_mag_decl(lat, lon)
|
||||
|
||||
if decl then
|
||||
decl=math.deg(decl)
|
||||
end
|
||||
|
||||
end
|
||||
else
|
||||
self:T("The require package is not available. Using constant value for magnetic declination")
|
||||
end
|
||||
|
||||
return decl
|
||||
end
|
||||
|
||||
--- Returns the coordinate from the latitude and longitude given in decimal degrees.
|
||||
-- @param #COORDINATE self
|
||||
@@ -504,7 +540,7 @@ do -- COORDINATE
|
||||
local gotscenery=false
|
||||
|
||||
local function EvaluateZone(ZoneObject)
|
||||
|
||||
BASE:T({ZoneObject})
|
||||
if ZoneObject then
|
||||
|
||||
-- Get category of scanned object.
|
||||
@@ -1018,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
|
||||
|
||||
@@ -1097,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
|
||||
|
||||
|
||||
@@ -1285,7 +1351,15 @@ do -- COORDINATE
|
||||
self.y=alt
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set altitude to be at land height (i.e. on the ground!)
|
||||
-- @param #COORDINATE self
|
||||
function COORDINATE:SetAtLandheight()
|
||||
local alt=self:GetLandHeight()
|
||||
self.y=alt
|
||||
return self
|
||||
end
|
||||
|
||||
--- Build an air type route point.
|
||||
-- @param #COORDINATE self
|
||||
-- @param #COORDINATE.WaypointAltType AltType The altitude type.
|
||||
@@ -1911,7 +1985,6 @@ do -- COORDINATE
|
||||
-- @param #COORDINATE self
|
||||
-- @param #string name (Optional) Name of the fire to stop it, if not using the same COORDINATE object.
|
||||
function COORDINATE:StopBigSmokeAndFire( name )
|
||||
self:F2( { name = name } )
|
||||
name = name or self.firename
|
||||
trigger.action.effectSmokeStop( name )
|
||||
end
|
||||
|
||||
@@ -53,6 +53,7 @@ do -- SET_BASE
|
||||
-- @field #table Index Table of indices.
|
||||
-- @field #table List Unused table.
|
||||
-- @field Core.Scheduler#SCHEDULER CallScheduler
|
||||
-- @field #SET_BASE.Filters Filter Filters
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects.
|
||||
@@ -83,6 +84,11 @@ do -- SET_BASE
|
||||
YieldInterval = nil,
|
||||
}
|
||||
|
||||
--- Filters
|
||||
-- @type SET_BASE.Filters
|
||||
-- @field #table Coalition Coalitions
|
||||
-- @field #table Prefix Prefixes.
|
||||
|
||||
--- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names.
|
||||
-- @param #SET_BASE self
|
||||
-- @return #SET_BASE
|
||||
@@ -135,11 +141,12 @@ do -- SET_BASE
|
||||
|
||||
--- Clear the Objects in the Set.
|
||||
-- @param #SET_BASE self
|
||||
-- @param #boolean TriggerEvent If `true`, an event remove is triggered for each group that is removed from the set.
|
||||
-- @return #SET_BASE self
|
||||
function SET_BASE:Clear()
|
||||
function SET_BASE:Clear(TriggerEvent)
|
||||
|
||||
for Name, Object in pairs( self.Set ) do
|
||||
self:Remove( Name )
|
||||
self:Remove( Name, not TriggerEvent )
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -166,7 +173,7 @@ do -- SET_BASE
|
||||
|
||||
--- Gets a list of the Names of the Objects in the Set.
|
||||
-- @param #SET_BASE self
|
||||
-- @return #SET_BASE self
|
||||
-- @return #table Table of names.
|
||||
function SET_BASE:GetSetNames() -- R2.3
|
||||
self:F2()
|
||||
|
||||
@@ -181,7 +188,7 @@ do -- SET_BASE
|
||||
|
||||
--- Returns a table of the Objects in the Set.
|
||||
-- @param #SET_BASE self
|
||||
-- @return #SET_BASE self
|
||||
-- @return #table Table of objects.
|
||||
function SET_BASE:GetSetObjects() -- R2.3
|
||||
self:F2()
|
||||
|
||||
@@ -197,16 +204,21 @@ do -- SET_BASE
|
||||
--- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name.
|
||||
-- @param #SET_BASE self
|
||||
-- @param #string ObjectName
|
||||
-- @param NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event.
|
||||
-- @param #boolean NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event.
|
||||
function SET_BASE:Remove( ObjectName, NoTriggerEvent )
|
||||
self:F2( { ObjectName = ObjectName } )
|
||||
|
||||
local TriggerEvent = true
|
||||
if NoTriggerEvent then TriggerEvent = false end
|
||||
if NoTriggerEvent then
|
||||
TriggerEvent = false
|
||||
else
|
||||
TriggerEvent = true
|
||||
end
|
||||
|
||||
local Object = self.Set[ObjectName]
|
||||
|
||||
if Object then
|
||||
|
||||
for Index, Key in ipairs( self.Index ) do
|
||||
if Key == ObjectName then
|
||||
table.remove( self.Index, Index )
|
||||
@@ -214,6 +226,7 @@ do -- SET_BASE
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- When NoTriggerEvent is true, then no Removed event will be triggered.
|
||||
if TriggerEvent then
|
||||
self:Removed( ObjectName, Object )
|
||||
@@ -311,7 +324,6 @@ do -- SET_BASE
|
||||
-- @param #SET_BASE self
|
||||
-- @param Core.Set#SET_BASE SetB Set other set, called *B*.
|
||||
-- @return Core.Set#SET_BASE A set of objects that is included in set *A* **and** in set *B*.
|
||||
|
||||
function SET_BASE:GetSetIntersection(SetB)
|
||||
|
||||
local intersection=SET_BASE:New()
|
||||
@@ -461,16 +473,32 @@ do -- SET_BASE
|
||||
-- @param #SET_BASE self
|
||||
-- @return #SET_BASE self
|
||||
function SET_BASE:FilterOnce()
|
||||
|
||||
--self:Clear()
|
||||
|
||||
for ObjectName, Object in pairs( self.Database ) do
|
||||
|
||||
if self:IsIncludeObject( Object ) then
|
||||
self:Add( ObjectName, Object )
|
||||
else
|
||||
self:Remove(ObjectName, true)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Clear all filters. You still need to apply :FilterOnce()
|
||||
-- @param #SET_BASE self
|
||||
-- @return #SET_BASE self
|
||||
function SET_BASE:FilterClear()
|
||||
|
||||
for key,value in pairs(self.Filter) do
|
||||
self.Filter[key]={}
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Starts the filtering for the defined collection.
|
||||
-- @param #SET_BASE self
|
||||
@@ -817,7 +845,7 @@ do -- SET_BASE
|
||||
--- Decides whether an object is in the SET
|
||||
-- @param #SET_BASE self
|
||||
-- @param #table Object
|
||||
-- @return #SET_BASE self
|
||||
-- @return #boolean `true` if object is in set and `false` otherwise.
|
||||
function SET_BASE:IsInSet( Object )
|
||||
self:F3( Object )
|
||||
local outcome = false
|
||||
@@ -875,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:
|
||||
@@ -1021,9 +1049,9 @@ do -- SET_GROUP
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the Set.
|
||||
--- Get a *new* set that only contains alive groups.
|
||||
-- @param #SET_GROUP self
|
||||
-- @return #table Table of objects
|
||||
-- @return #SET_GROUP Set of alive groups.
|
||||
function SET_GROUP:GetAliveSet()
|
||||
self:F2()
|
||||
|
||||
@@ -1169,11 +1197,14 @@ do -- SET_GROUP
|
||||
--- Builds a set of groups in zones.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_GROUP self
|
||||
function SET_GROUP:FilterZones( Zones )
|
||||
if not self.Filter.Zones then
|
||||
function SET_GROUP:FilterZones( Zones, Clear )
|
||||
|
||||
if Clear or not self.Filter.Zones then
|
||||
self.Filter.Zones = {}
|
||||
end
|
||||
|
||||
local zones = {}
|
||||
if Zones.ClassName and Zones.ClassName == "SET_ZONE" then
|
||||
zones = Zones.Set
|
||||
@@ -1183,34 +1214,12 @@ do -- SET_GROUP
|
||||
else
|
||||
zones = Zones
|
||||
end
|
||||
|
||||
for _, Zone in pairs( zones ) do
|
||||
local zonename = Zone:GetName()
|
||||
self.Filter.Zones[zonename] = Zone
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of groups in zones.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE
|
||||
-- @return #SET_GROUP self
|
||||
function SET_GROUP:FilterZones( Zones )
|
||||
if not self.Filter.Zones then
|
||||
self.Filter.Zones = {}
|
||||
end
|
||||
local zones = {}
|
||||
if Zones.ClassName and Zones.ClassName == "SET_ZONE" then
|
||||
zones = Zones.Set
|
||||
elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then
|
||||
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
|
||||
return self
|
||||
else
|
||||
zones = Zones
|
||||
end
|
||||
for _,Zone in pairs( zones ) do
|
||||
local zonename = Zone:GetName()
|
||||
self.Filter.Zones[zonename] = Zone
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1218,17 +1227,21 @@ do -- SET_GROUP
|
||||
-- Possible current coalitions are red, blue and neutral.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral".
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_GROUP self
|
||||
function SET_GROUP:FilterCoalitions( Coalitions )
|
||||
if not self.Filter.Coalitions then
|
||||
function SET_GROUP:FilterCoalitions( Coalitions, Clear )
|
||||
|
||||
if Clear or (not self.Filter.Coalitions) then
|
||||
self.Filter.Coalitions = {}
|
||||
end
|
||||
if type( Coalitions ) ~= "table" then
|
||||
Coalitions = { Coalitions }
|
||||
end
|
||||
|
||||
-- Ensure table.
|
||||
Coalitions = UTILS.EnsureTable(Coalitions, false)
|
||||
|
||||
for CoalitionID, Coalition in pairs( Coalitions ) do
|
||||
self.Filter.Coalitions[Coalition] = Coalition
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1236,17 +1249,22 @@ do -- SET_GROUP
|
||||
-- Possible current categories are plane, helicopter, ground, ship.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship".
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_GROUP self
|
||||
function SET_GROUP:FilterCategories( Categories )
|
||||
if not self.Filter.Categories then
|
||||
function SET_GROUP:FilterCategories( Categories, Clear )
|
||||
|
||||
if Clear or not self.Filter.Categories then
|
||||
self.Filter.Categories = {}
|
||||
end
|
||||
|
||||
if type( Categories ) ~= "table" then
|
||||
Categories = { Categories }
|
||||
end
|
||||
|
||||
for CategoryID, Category in pairs( Categories ) do
|
||||
self.Filter.Categories[Category] = Category
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1882,6 +1900,40 @@ do -- SET_GROUP
|
||||
return MGroupInclude
|
||||
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.
|
||||
-- @return Wrapper.Group#GROUP The closest group (if any).
|
||||
-- @return #number Distance in meters to the closest group.
|
||||
function SET_GROUP:GetClosestGroup(Coordinate, Coalitions)
|
||||
|
||||
local Set = self:GetSet()
|
||||
|
||||
local dmin=math.huge
|
||||
local gmin=nil
|
||||
|
||||
for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP
|
||||
local group=GroupData --Wrapper.Group#GROUP
|
||||
|
||||
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
|
||||
|
||||
--- 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
|
||||
@@ -2061,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
|
||||
|
||||
@@ -2120,9 +2174,11 @@ do -- SET_UNIT
|
||||
if type( Coalitions ) ~= "table" then
|
||||
Coalitions = { Coalitions }
|
||||
end
|
||||
|
||||
for CoalitionID, Coalition in pairs( Coalitions ) do
|
||||
self.Filter.Coalitions[Coalition] = Coalition
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -3739,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
|
||||
@@ -4030,7 +4120,7 @@ do -- SET_CLIENT
|
||||
--- Builds a set of clients in zones.
|
||||
-- @param #SET_CLIENT self
|
||||
-- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE
|
||||
-- @return #SET_TABLE self
|
||||
-- @return #SET_CLIENT self
|
||||
function SET_CLIENT:FilterZones( Zones )
|
||||
if not self.Filter.Zones then
|
||||
self.Filter.Zones = {}
|
||||
@@ -5840,8 +5930,7 @@ do -- SET_ZONE
|
||||
-- If zones overlap, the first zone that validates the test is returned.
|
||||
-- @param #SET_ZONE self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate to be searched.
|
||||
-- @return Core.Zone#ZONE_BASE The zone that validates the coordinate location.
|
||||
-- @return #nil No zone has been found.
|
||||
-- @return Core.Zone#ZONE_BASE The zone (if any) that validates the coordinate location.
|
||||
function SET_ZONE:IsCoordinateInZone( Coordinate )
|
||||
|
||||
for _, Zone in pairs( self:GetSet() ) do
|
||||
@@ -5853,6 +5942,27 @@ do -- SET_ZONE
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get the closest zone to a given coordinate.
|
||||
-- @param #SET_ZONE self
|
||||
-- @param Core.Point#COORDINATE Coordinate The reference coordinate from which the closest zone is determined.
|
||||
-- @return Core.Zone#ZONE_BASE The closest zone (if any).
|
||||
-- @return #number Distance to ref coordinate in meters.
|
||||
function SET_ZONE:GetClosestZone( Coordinate )
|
||||
|
||||
local dmin=math.huge
|
||||
local zmin=nil
|
||||
for _, Zone in pairs( self:GetSet() ) do
|
||||
local Zone = Zone -- Core.Zone#ZONE_BASE
|
||||
local d=Zone:Get2DDistance(Coordinate)
|
||||
if d<dmin then
|
||||
dmin=d
|
||||
zmin=Zone
|
||||
end
|
||||
end
|
||||
|
||||
return zmin, dmin
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -6167,6 +6277,461 @@ do -- SET_ZONE_GOAL
|
||||
|
||||
end
|
||||
|
||||
do -- SET_OPSZONE
|
||||
|
||||
--- @type SET_OPSZONE
|
||||
-- @extends Core.Set#SET_BASE
|
||||
|
||||
--- Mission designers can use the @{Core.Set#SET_OPSZONE} class to build sets of zones of various types.
|
||||
--
|
||||
-- ## SET_OPSZONE constructor
|
||||
--
|
||||
-- Create a new SET_OPSZONE object with the @{#SET_OPSZONE.New} method:
|
||||
--
|
||||
-- * @{#SET_OPSZONE.New}: Creates a new SET_OPSZONE object.
|
||||
--
|
||||
-- ## Add or Remove ZONEs from SET_OPSZONE
|
||||
--
|
||||
-- ZONEs can be added and removed using the @{Core.Set#SET_OPSZONE.AddZonesByName} and @{Core.Set#SET_OPSZONE.RemoveZonesByName} respectively.
|
||||
-- These methods take a single ZONE name or an array of ZONE names to be added or removed from SET_OPSZONE.
|
||||
--
|
||||
-- ## SET_OPSZONE filter criteria
|
||||
--
|
||||
-- You can set filter criteria to build the collection of zones in SET_OPSZONE.
|
||||
-- Filter criteria are defined by:
|
||||
--
|
||||
-- * @{#SET_OPSZONE.FilterPrefixes}: Builds the SET_OPSZONE with the zones having a certain text pattern in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*.
|
||||
--
|
||||
-- Once the filter criteria have been set for the SET_OPSZONE, you can start filtering using:
|
||||
--
|
||||
-- * @{#SET_OPSZONE.FilterStart}: Starts the filtering of the zones within the SET_OPSZONE.
|
||||
--
|
||||
-- ## SET_OPSZONE iterators
|
||||
--
|
||||
-- Once the filters have been defined and the SET_OPSZONE has been built, you can iterate the SET_OPSZONE with the available iterator methods.
|
||||
-- The iterator methods will walk the SET_OPSZONE set, and call for each airbase within the set a function that you provide.
|
||||
-- The following iterator methods are currently available within the SET_OPSZONE:
|
||||
--
|
||||
-- * @{#SET_OPSZONE.ForEachZone}: Calls a function for each zone it finds within the SET_OPSZONE.
|
||||
--
|
||||
-- ===
|
||||
-- @field #SET_OPSZONE SET_OPSZONE
|
||||
SET_OPSZONE = {
|
||||
ClassName = "SET_OPSZONE",
|
||||
Zones = {},
|
||||
Filter = {
|
||||
Prefixes = nil,
|
||||
Coalitions = nil,
|
||||
},
|
||||
FilterMeta = {
|
||||
Coalitions = {
|
||||
red = coalition.side.RED,
|
||||
blue = coalition.side.BLUE,
|
||||
neutral = coalition.side.NEUTRAL,
|
||||
},
|
||||
}, --FilterMeta
|
||||
}
|
||||
|
||||
--- Creates a new SET_OPSZONE object, building a set of zones.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:New()
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.OPSZONES ) )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an OPSZONE to set.
|
||||
-- @param Core.Set#SET_OPSZONE self
|
||||
-- @param Ops.OpsZone#OPSZONE Zone The OPSZONE object.
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:AddZone( Zone )
|
||||
|
||||
self:Add( Zone:GetName(), Zone )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove ZONEs from SET_OPSZONE.
|
||||
-- @param Core.Set#SET_OPSZONE self
|
||||
-- @param #table RemoveZoneNames A single name or an array of OPSZONE names.
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:RemoveZonesByName( RemoveZoneNames )
|
||||
|
||||
local RemoveZoneNamesArray = (type( RemoveZoneNames ) == "table") and RemoveZoneNames or { RemoveZoneNames }
|
||||
|
||||
--UTILS.EnsureTable(Object,ReturnNil)
|
||||
|
||||
for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do
|
||||
self:Remove( RemoveZoneName )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Finds a Zone based on its name.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param #string ZoneName
|
||||
-- @return Ops.OpsZone#OPSZONE The found Zone.
|
||||
function SET_OPSZONE:FindZone( ZoneName )
|
||||
|
||||
local ZoneFound = self.Set[ZoneName]
|
||||
|
||||
return ZoneFound
|
||||
end
|
||||
|
||||
--- Get a random zone from the set.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return Ops.OpsZone#OPSZONE The random Zone.
|
||||
function SET_OPSZONE:GetRandomZone()
|
||||
|
||||
if self:Count() ~= 0 then
|
||||
|
||||
local Index = self.Index
|
||||
local ZoneFound = nil -- Core.Zone#ZONE_BASE
|
||||
|
||||
-- Loop until a zone has been found.
|
||||
-- The :GetZoneMaybe() call will evaluate the probability for the zone to be selected.
|
||||
-- If the zone is not selected, then nil is returned by :GetZoneMaybe() and the loop continues!
|
||||
while not ZoneFound do
|
||||
local ZoneRandom = math.random( 1, #Index )
|
||||
ZoneFound = self.Set[Index[ZoneRandom]]:GetZoneMaybe()
|
||||
end
|
||||
|
||||
return ZoneFound
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Set a zone probability.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param #string ZoneName The name of the zone.
|
||||
-- @param #number Probability The probability in percent.
|
||||
function SET_OPSZONE:SetZoneProbability( ZoneName, Probability )
|
||||
local Zone = self:FindZone( ZoneName )
|
||||
Zone:SetZoneProbability( Probability )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of OPSZONEs that contain the given string in their name.
|
||||
-- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param #string Prefixes The string pattern(s) that needs to be contained in the zone name. Can also be passed as a `#table` of strings.
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:FilterPrefixes( Prefixes )
|
||||
|
||||
if not self.Filter.Prefixes then
|
||||
self.Filter.Prefixes = {}
|
||||
end
|
||||
|
||||
Prefixes=UTILS.EnsureTable(Prefixes, false)
|
||||
|
||||
for PrefixID, Prefix in pairs( Prefixes ) do
|
||||
self.Filter.Prefixes[Prefix] = Prefix
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of groups of coalitions. Possible current coalitions are red, blue and neutral.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`.
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:FilterCoalitions(Coalitions)
|
||||
|
||||
-- Create an empty set.
|
||||
if not self.Filter.Coalitions then
|
||||
self.Filter.Coalitions={}
|
||||
end
|
||||
|
||||
-- Ensure we got a table.
|
||||
Coalitions=UTILS.EnsureTable(Coalitions, false)
|
||||
|
||||
-- Set filter.
|
||||
for CoalitionID, Coalition in pairs( Coalitions ) do
|
||||
self.Filter.Coalitions[Coalition] = Coalition
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Filters for the defined collection.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:FilterOnce()
|
||||
|
||||
for ObjectName, Object in pairs( self.Database ) do
|
||||
|
||||
-- First remove the object (without creating an event).
|
||||
self:Remove(ObjectName, true)
|
||||
|
||||
if self:IsIncludeObject( Object ) then
|
||||
self:Add( ObjectName, Object )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Clear all filters. You still need to apply `FilterOnce()` to have an effect on the set.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:FilterClear()
|
||||
|
||||
local parent=self:GetParent(self, SET_OPSZONE) --#SET_BASE
|
||||
|
||||
parent:FilterClear()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Starts the filtering.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:FilterStart()
|
||||
|
||||
if _DATABASE then
|
||||
|
||||
-- We initialize the first set.
|
||||
for ObjectName, Object in pairs( self.Database ) do
|
||||
if self:IsIncludeObject( Object ) then
|
||||
self:Add( ObjectName, Object )
|
||||
else
|
||||
self:RemoveZonesByName( ObjectName )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:HandleEvent( EVENTS.NewZoneGoal )
|
||||
self:HandleEvent( EVENTS.DeleteZoneGoal )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stops the filtering for the defined collection.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:FilterStop()
|
||||
|
||||
self:UnHandleEvent( EVENTS.NewZoneGoal )
|
||||
self:UnHandleEvent( EVENTS.DeleteZoneGoal )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Handles the Database to check on an event (birth) that the Object was added in the Database.
|
||||
-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
-- @return #string The name of the AIRBASE
|
||||
-- @return #table The AIRBASE
|
||||
function SET_OPSZONE:AddInDatabase( Event )
|
||||
self:F3( { Event } )
|
||||
|
||||
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
|
||||
end
|
||||
|
||||
--- Handles the Database to check on any event that Object exists in the Database.
|
||||
-- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa!
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
-- @return #string The name of the AIRBASE
|
||||
-- @return #table The AIRBASE
|
||||
function SET_OPSZONE:FindInDatabase( Event )
|
||||
self:F3( { Event } )
|
||||
|
||||
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
|
||||
end
|
||||
|
||||
--- Iterate the SET_OPSZONE and call an iterator function for each ZONE, providing the ZONE and optional parameters.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_OPSZONE. The function needs to accept a AIRBASE parameter.
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:ForEachZone( IteratorFunction, ... )
|
||||
self:F2( arg )
|
||||
|
||||
self:ForEach( IteratorFunction, arg, self:GetSet() )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Private function that checks if an object is contained in the set or filtered.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Ops.OpsZone#OPSZONE MZone The OPSZONE object.
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:IsIncludeObject( MZone )
|
||||
self:F2( MZone )
|
||||
|
||||
local MZoneInclude = true
|
||||
|
||||
if MZone then
|
||||
|
||||
local MZoneName = MZone:GetName()
|
||||
|
||||
if self.Filter.Prefixes then
|
||||
|
||||
local MZonePrefix = false
|
||||
|
||||
-- Loop over prefixes.
|
||||
for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do
|
||||
|
||||
-- Prifix
|
||||
self:T3( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } )
|
||||
|
||||
if string.find(MZoneName, ZonePrefix, 1) then
|
||||
MZonePrefix = true
|
||||
break --Break the loop as we found the prefix.
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
self:T( { "Evaluated Prefix", MZonePrefix } )
|
||||
|
||||
MZoneInclude = MZoneInclude and MZonePrefix
|
||||
end
|
||||
|
||||
-- Filter coalitions.
|
||||
if self.Filter.Coalitions then
|
||||
|
||||
local MGroupCoalition = false
|
||||
|
||||
local coalition=MZone:GetOwner()
|
||||
|
||||
for _, CoalitionName in pairs( self.Filter.Coalitions ) do
|
||||
|
||||
if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName]==coalition then
|
||||
MGroupCoalition = true
|
||||
break -- Break the loop the coalition is contains.
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
MZoneInclude = MZoneInclude and MGroupCoalition
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
self:T2( MZoneInclude )
|
||||
return MZoneInclude
|
||||
end
|
||||
|
||||
--- Handles the OnEventNewZone event for the Set.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SET_OPSZONE:OnEventNewZoneGoal( EventData )
|
||||
|
||||
-- Debug info.
|
||||
self:T( { "New Zone Capture Coalition", EventData } )
|
||||
self:T( { "Zone Capture Coalition", EventData.ZoneGoal } )
|
||||
|
||||
if EventData.ZoneGoal then
|
||||
if EventData.ZoneGoal and self:IsIncludeObject( EventData.ZoneGoal ) then
|
||||
self:T( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } )
|
||||
self:Add( EventData.ZoneGoal.ZoneName, EventData.ZoneGoal )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Handles the OnDead or OnCrash event for alive units set.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SET_OPSZONE:OnEventDeleteZoneGoal( EventData ) -- R2.1
|
||||
self:F3( { EventData } )
|
||||
|
||||
if EventData.ZoneGoal then
|
||||
local Zone = _DATABASE:FindZone( EventData.ZoneGoal.ZoneName )
|
||||
if Zone and Zone.ZoneName then
|
||||
|
||||
-- When cargo was deleted, it may probably be because of an S_EVENT_DEAD.
|
||||
-- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call.
|
||||
-- And this is a problem because it will remove all entries from the SET_OPSZONEs.
|
||||
-- To prevent this from happening, the Zone object has a flag NoDestroy.
|
||||
-- When true, the SET_OPSZONE won't Remove the Zone object from the set.
|
||||
-- This flag is switched off after the event handlers have been called in the EVENT class.
|
||||
self:F( { ZoneNoDestroy = Zone.NoDestroy } )
|
||||
if Zone.NoDestroy then
|
||||
else
|
||||
self:Remove( Zone.ZoneName )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Start all opszones of the set.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:Start()
|
||||
|
||||
for _,_Zone in pairs( self:GetSet() ) do
|
||||
local Zone = _Zone --Ops.OpsZone#OPSZONE
|
||||
if Zone:IsStopped() then
|
||||
Zone:Start()
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Validate if a coordinate is in one of the zones in the set.
|
||||
-- Returns the ZONE object where the coordiante is located.
|
||||
-- If zones overlap, the first zone that validates the test is returned.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate to be searched.
|
||||
-- @return Core.Zone#ZONE_BASE The zone that validates the coordinate location.
|
||||
-- @return #nil No zone has been found.
|
||||
function SET_OPSZONE:IsCoordinateInZone( Coordinate )
|
||||
|
||||
for _,_Zone in pairs( self:GetSet() ) do
|
||||
local Zone = _Zone --Ops.OpsZone#OPSZONE
|
||||
if Zone:GetZone():IsCoordinateInZone( Coordinate ) then
|
||||
return Zone
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Get the closest OPSZONE from a given reference coordinate. Only started zones are considered.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Core.Point#COORDINATE Coordinate The reference coordinate from which the closest zone is determined.
|
||||
-- @param #table Coalitions Only consider the given coalition(s), *e.g.* `{coaliton.side.RED}` to find the closest red zone.
|
||||
-- @return Ops.OpsZone#OPSZONE The closest OPSZONE (if any).
|
||||
-- @return #number Distance to ref coordinate in meters.
|
||||
function SET_OPSZONE:GetClosestZone( Coordinate, Coalitions )
|
||||
|
||||
Coalitions=UTILS.EnsureTable(Coalitions, true)
|
||||
|
||||
local dmin=math.huge --#number
|
||||
local zmin=nil --Ops.OpsZone#OPSZONE
|
||||
|
||||
for _,_opszone in pairs(self:GetSet()) do
|
||||
local opszone=_opszone --Ops.OpsZone#OPSZONE
|
||||
|
||||
local coal=opszone:GetOwner()
|
||||
|
||||
if opszone:IsStarted() and (Coalitions==nil or (Coalitions and UTILS.IsInTable(Coalitions, coal))) then
|
||||
|
||||
-- Get 2D distance.
|
||||
local d=opszone:GetZone():Get2DDistance(Coordinate)
|
||||
|
||||
if d<dmin then
|
||||
dmin=d
|
||||
zmin=opszone
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
return zmin, dmin
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- SET_OPSGROUP
|
||||
@@ -6289,7 +6854,7 @@ do -- SET_OPSGROUP
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the Set.
|
||||
--- Gets a **new** set that only contains alive groups.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:GetAliveSet()
|
||||
@@ -6454,11 +7019,12 @@ do -- SET_OPSGROUP
|
||||
-- Possible current coalitions are red, blue and neutral.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`.
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCoalitions(Coalitions)
|
||||
function SET_OPSGROUP:FilterCoalitions(Coalitions, Clear)
|
||||
|
||||
-- Create an empty set.
|
||||
if not self.Filter.Coalitions then
|
||||
if Clear or not self.Filter.Coalitions then
|
||||
self.Filter.Coalitions={}
|
||||
end
|
||||
|
||||
@@ -6487,10 +7053,11 @@ do -- SET_OPSGROUP
|
||||
--
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship" or combinations as a table, for example `{"plane", "helicopter"}`.
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCategories( Categories )
|
||||
function SET_OPSGROUP:FilterCategories( Categories, Clear )
|
||||
|
||||
if not self.Filter.Categories then
|
||||
if Clear or not self.Filter.Categories then
|
||||
self.Filter.Categories={}
|
||||
end
|
||||
|
||||
@@ -6548,11 +7115,12 @@ do -- SET_OPSGROUP
|
||||
--- Builds a set of groups of defined countries.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string Countries Can take those country strings known within DCS world.
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCountries(Countries)
|
||||
function SET_OPSGROUP:FilterCountries(Countries, Clear)
|
||||
|
||||
-- Create empty table if necessary.
|
||||
if not self.Filter.Countries then
|
||||
if Clear or not self.Filter.Countries then
|
||||
self.Filter.Countries = {}
|
||||
end
|
||||
|
||||
@@ -6574,11 +7142,12 @@ do -- SET_OPSGROUP
|
||||
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings.
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterPrefixes(Prefixes)
|
||||
function SET_OPSGROUP:FilterPrefixes(Prefixes, Clear)
|
||||
|
||||
-- Create emtpy table if necessary.
|
||||
if not self.Filter.GroupPrefixes then
|
||||
if Clear or not self.Filter.GroupPrefixes then
|
||||
self.Filter.GroupPrefixes={}
|
||||
end
|
||||
|
||||
|
||||
@@ -389,7 +389,7 @@ end
|
||||
-- @param #SPAWN self
|
||||
-- @param #table SpawnTemplate is the Template of the Group. This must be a valid Group Template structure!
|
||||
-- @param #string SpawnTemplatePrefix is the name of the Group that will be given at each spawn.
|
||||
-- @param #string SpawnAliasPrefix (optional) is the name that will be given to the Group at runtime.
|
||||
-- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime.
|
||||
-- @return #SPAWN
|
||||
-- @usage
|
||||
-- -- Create a new SPAWN object based on a Group Template defined from scratch.
|
||||
@@ -403,7 +403,7 @@ function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPr
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } )
|
||||
if SpawnAliasPrefix == nil or SpawnAliasPrefix == "" then
|
||||
BASE:I( "ERROR: in function NewFromTemplate, required paramter SpawnAliasPrefix is not set" )
|
||||
BASE:I( "ERROR: in function NewFromTemplate, required parameter SpawnAliasPrefix is not set" )
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -155,7 +155,7 @@ function TIMER:New(Function, ...)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new TIMER object.
|
||||
--- Start TIMER object.
|
||||
-- @param #TIMER self
|
||||
-- @param #number Tstart Relative start time in seconds.
|
||||
-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once.
|
||||
@@ -192,6 +192,20 @@ function TIMER:Start(Tstart, dT, Duration)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Start TIMER object if a condition is met. Useful for e.g. debugging.
|
||||
-- @param #TIMER self
|
||||
-- @param #boolean Condition Must be true for the TIMER to start
|
||||
-- @param #number Tstart Relative start time in seconds.
|
||||
-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once.
|
||||
-- @param #number Duration Time in seconds for how long the timer is running. If not specified `nil`, the timer runs forever or until stopped manually by the `TIMER:Stop()` function.
|
||||
-- @return #TIMER self
|
||||
function TIMER:StartIf(Condition,Tstart, dT, Duration)
|
||||
if Condition then
|
||||
self:Start(Tstart, dT, Duration)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stop the timer by removing the timer function.
|
||||
-- @param #TIMER self
|
||||
-- @param #number Delay (Optional) Delay in seconds, before the timer is stopped.
|
||||
|
||||
@@ -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,
|
||||
@@ -1387,7 +1387,7 @@ end
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m.
|
||||
-- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone.
|
||||
-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type!
|
||||
-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 100 times to find the right type!
|
||||
-- @return Core.Point#COORDINATE The random coordinate.
|
||||
function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes)
|
||||
|
||||
@@ -2084,6 +2084,63 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the smallest radius encompassing all points of the polygon zone.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return #number Radius of the zone in meters.
|
||||
function ZONE_POLYGON_BASE:GetRadius()
|
||||
|
||||
local center=self:GetVec2()
|
||||
|
||||
local radius=0
|
||||
|
||||
for _,_vec2 in pairs(self._.Polygon) do
|
||||
local vec2=_vec2 --DCS#Vec2
|
||||
|
||||
local r=UTILS.VecDist2D(center, vec2)
|
||||
|
||||
if r>radius then
|
||||
radius=r
|
||||
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)
|
||||
|
||||
return zone
|
||||
end
|
||||
|
||||
|
||||
--- Get the smallest rectangular zone encompassing all points 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_POLYGON The rectangular zone.
|
||||
function ZONE_POLYGON_BASE:GetZoneQuad(ZoneName, DoNotRegisterZone)
|
||||
|
||||
local vec1, vec3=self:GetBoundingVec2()
|
||||
|
||||
local vec2={x=vec1.x, y=vec3.y}
|
||||
local vec4={x=vec3.x, y=vec1.y}
|
||||
|
||||
local zone=ZONE_POLYGON_BASE:New(ZoneName or self.ZoneName, {vec1, vec2, vec3, vec4})
|
||||
|
||||
return zone
|
||||
end
|
||||
|
||||
--- Smokes the zone boundaries in a color.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color.
|
||||
@@ -2286,6 +2343,32 @@ function ZONE_POLYGON_BASE:GetBoundingSquare()
|
||||
return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 }
|
||||
end
|
||||
|
||||
--- Get the bounding 2D vectors of the polygon.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return DCS#Vec2 Coordinates of western-southern-lower vertex of the box.
|
||||
-- @return DCS#Vec2 Coordinates of eastern-northern-upper vertex of the box.
|
||||
function ZONE_POLYGON_BASE:GetBoundingVec2()
|
||||
|
||||
local x1 = self._.Polygon[1].x
|
||||
local y1 = self._.Polygon[1].y
|
||||
local x2 = self._.Polygon[1].x
|
||||
local y2 = self._.Polygon[1].y
|
||||
|
||||
for i = 2, #self._.Polygon do
|
||||
self:T2( { self._.Polygon[i], x1, y1, x2, y2 } )
|
||||
x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1
|
||||
x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2
|
||||
y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1
|
||||
y2 = ( y2 < self._.Polygon[i].y ) and self._.Polygon[i].y or y2
|
||||
|
||||
end
|
||||
|
||||
local vec1={x=x1, y=y1}
|
||||
local vec2={x=x2, y=y2}
|
||||
|
||||
return vec1, vec2
|
||||
end
|
||||
|
||||
--- Draw a frontier on the F10 map with small filled circles.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All.
|
||||
@@ -2841,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
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
-- @field #number talt Interval in seconds between reporting altitude until touchdown. Default 3 sec.
|
||||
-- @field #boolean chatty Display some messages on events like take-off and touchdown.
|
||||
-- @field #boolean eventsmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler.
|
||||
-- @field #boolean reportplayername If true, use playername not callsign on callouts
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Adds some rudimentary ATC functionality via the radio menu.
|
||||
@@ -88,6 +89,7 @@ PSEUDOATC={
|
||||
talt=3,
|
||||
chatty=true,
|
||||
eventsmoose=true,
|
||||
reportplayername = false,
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -98,7 +100,7 @@ PSEUDOATC.id="PseudoATC | "
|
||||
|
||||
--- PSEUDOATC version.
|
||||
-- @field #number version
|
||||
PSEUDOATC.version="0.9.2"
|
||||
PSEUDOATC.version="0.9.5"
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -183,6 +185,13 @@ function PSEUDOATC:SetMessageDuration(duration)
|
||||
self.mdur=duration or 30
|
||||
end
|
||||
|
||||
--- Use player name, not call sign, in callouts
|
||||
-- @param #PSEUDOATC self
|
||||
function PSEUDOATC:SetReportPlayername()
|
||||
self.reportplayername = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set time interval after which the F10 radio menu is refreshed.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #number interval Interval in seconds. Default is every 120 sec.
|
||||
@@ -441,14 +450,18 @@ function PSEUDOATC:PlayerLanded(unit, place)
|
||||
local group=unit:GetGroup()
|
||||
local GID=group:GetID()
|
||||
local UID=unit:GetDCSObject():getID()
|
||||
local PlayerName=self.group[GID].player[UID].playername
|
||||
local UnitName=self.group[GID].player[UID].unitname
|
||||
local GroupName=self.group[GID].player[UID].groupname
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("Player %s in unit %s of group %s (id=%d) landed at %s.", PlayerName, UnitName, GroupName, GID, place)
|
||||
self:T(PSEUDOATC.id..text)
|
||||
MESSAGE:New(text, 30):ToAllIf(self.Debug)
|
||||
--local PlayerName=self.group[GID].player[UID].playername
|
||||
--local UnitName=self.group[GID].player[UID].unitname
|
||||
--local GroupName=self.group[GID].player[UID].groupname
|
||||
local PlayerName = unit:GetPlayerName() or "Ghost"
|
||||
local UnitName = unit:GetName() or "Ghostplane"
|
||||
local GroupName = group:GetName() or "Ghostgroup"
|
||||
if self.Debug then
|
||||
-- Debug message.
|
||||
local text=string.format("Player %s in unit %s of group %s landed at %s.", PlayerName, UnitName, GroupName, place)
|
||||
self:T(PSEUDOATC.id..text)
|
||||
MESSAGE:New(text, 30):ToAllIf(self.Debug)
|
||||
end
|
||||
|
||||
-- Stop altitude reporting timer if its activated.
|
||||
self:AltitudeTimerStop(GID,UID)
|
||||
@@ -470,21 +483,28 @@ function PSEUDOATC:PlayerTakeOff(unit, place)
|
||||
|
||||
-- Gather some information.
|
||||
local group=unit:GetGroup()
|
||||
local GID=group:GetID()
|
||||
local UID=unit:GetDCSObject():getID()
|
||||
local PlayerName=self.group[GID].player[UID].playername
|
||||
local CallSign=self.group[GID].player[UID].callsign
|
||||
local UnitName=self.group[GID].player[UID].unitname
|
||||
local GroupName=self.group[GID].player[UID].groupname
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("Player %s in unit %s of group %s (id=%d) took off at %s.", PlayerName, UnitName, GroupName, GID, place)
|
||||
self:T(PSEUDOATC.id..text)
|
||||
MESSAGE:New(text, 30):ToAllIf(self.Debug)
|
||||
|
||||
--local GID=group:GetID()
|
||||
--local UID=unit:GetDCSObject():getID()
|
||||
--local PlayerName=self.group[GID].player[UID].playername
|
||||
--local CallSign=self.group[GID].player[UID].callsign
|
||||
--local UnitName=self.group[GID].player[UID].unitname
|
||||
--local GroupName=self.group[GID].player[UID].groupname
|
||||
local PlayerName = unit:GetPlayerName() or "Ghost"
|
||||
local UnitName = unit:GetName() or "Ghostplane"
|
||||
local GroupName = group:GetName() or "Ghostgroup"
|
||||
local CallSign = unit:GetCallsign() or "Ghost11"
|
||||
if self.Debug then
|
||||
-- Debug message.
|
||||
local text=string.format("Player %s in unit %s of group %s took off at %s.", PlayerName, UnitName, GroupName, place)
|
||||
self:T(PSEUDOATC.id..text)
|
||||
MESSAGE:New(text, 30):ToAllIf(self.Debug)
|
||||
end
|
||||
-- Bye-Bye message.
|
||||
if place and self.chatty then
|
||||
local text=string.format("%s, %s, you are airborne. Have a safe trip!", place, CallSign)
|
||||
if self.reportplayername then
|
||||
text=string.format("%s, %s, you are airborne. Have a safe trip!", place, PlayerName)
|
||||
end
|
||||
MESSAGE:New(text, self.mdur):ToGroup(group)
|
||||
end
|
||||
|
||||
@@ -501,7 +521,7 @@ function PSEUDOATC:PlayerLeft(unit)
|
||||
local GID=group:GetID()
|
||||
local UID=unit:GetDCSObject():getID()
|
||||
|
||||
if self.group[GID].player[UID] then
|
||||
if self.group[GID] and self.group[GID].player and self.group[GID].player[UID] then
|
||||
local PlayerName=self.group[GID].player[UID].playername
|
||||
local CallSign=self.group[GID].player[UID].callsign
|
||||
local UnitName=self.group[GID].player[UID].unitname
|
||||
@@ -687,7 +707,9 @@ function PSEUDOATC:MenuWaypoints(GID, UID)
|
||||
-- Position of Waypoint
|
||||
local pos=COORDINATE:New(wp.x, wp.alt, wp.y)
|
||||
local name=string.format("Waypoint %d", i-1)
|
||||
|
||||
if wp.name and wp.name ~= "" then
|
||||
name = string.format("Waypoint %s",wp.name)
|
||||
end
|
||||
-- "F10/PseudoATC/Waypoints/Waypoint X"
|
||||
local submenu=missionCommands.addSubMenuForGroup(GID, name, self.group[GID].player[UID].menu_waypoints)
|
||||
|
||||
@@ -844,7 +866,8 @@ function PSEUDOATC:ReportHeight(GID, UID, dt, _clear)
|
||||
local position=unit:GetCoordinate()
|
||||
local height=get_AGL(position)
|
||||
local callsign=unit:GetCallsign()
|
||||
|
||||
local PlayerName=self.group[GID].player[UID].playername
|
||||
|
||||
-- Settings.
|
||||
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS
|
||||
|
||||
@@ -856,7 +879,9 @@ function PSEUDOATC:ReportHeight(GID, UID, dt, _clear)
|
||||
|
||||
-- Message text.
|
||||
local _text=string.format("%s, your altitude is %s AGL.", callsign, Hs)
|
||||
|
||||
if self.reportplayername then
|
||||
_text=string.format("%s, your altitude is %s AGL.", PlayerName, Hs)
|
||||
end
|
||||
-- Append flight level.
|
||||
if _clear==false then
|
||||
_text=_text..string.format(" FL%03d.", position.y/30.48)
|
||||
|
||||
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() )
|
||||
|
||||
@@ -302,8 +302,8 @@
|
||||
--
|
||||
-- Initial Spawn states is as follows:
|
||||
-- GROUND: ROE, "Return Fire" Alarm, "Green"
|
||||
-- AIR: ROE, "Return Fire" Reaction to Threat, "Passive Defense"
|
||||
-- NAVAL ROE, "Return Fire" Alarm,"N/A"
|
||||
-- AIR: ROE, "Return Fire" Reaction to Threat, "Passive Defense"
|
||||
-- NAVAL ROE, "Return Fire" Alarm,"N/A"
|
||||
--
|
||||
-- A request can be added by the @{#WAREHOUSE.AddRequest}(*warehouse*, *AssetDescriptor*, *AssetDescriptorValue*, *nAsset*, *TransportType*, *nTransport*, *Prio*, *Assignment*) function.
|
||||
-- The parameters are
|
||||
@@ -2647,6 +2647,13 @@ function WAREHOUSE:SetWarehouseZone(zone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the warehouse zone.
|
||||
-- @param #WAREHOUSE self
|
||||
-- @return Core.Zone#ZONE The warehouse zone.
|
||||
function WAREHOUSE:GetWarehouseZone()
|
||||
return self.zone
|
||||
end
|
||||
|
||||
--- Set auto defence on. When the warehouse is under attack, all ground assets are spawned automatically and will defend the warehouse zone.
|
||||
-- @param #WAREHOUSE self
|
||||
-- @return #WAREHOUSE self
|
||||
@@ -5810,6 +5817,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request)
|
||||
-- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning.
|
||||
local Parking={}
|
||||
if Request.cargocategory==Group.Category.AIRPLANE or Request.cargocategory==Group.Category.HELICOPTER then
|
||||
--TODO: Check for airstart. Should be a request property.
|
||||
Parking=self:_FindParkingForAssets(self.airbase, cargoassets) or {}
|
||||
end
|
||||
|
||||
@@ -6069,7 +6077,9 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
|
||||
end
|
||||
|
||||
if self.Debug then
|
||||
coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.", unit.name, terminal))
|
||||
local text=string.format("Spawnplace unit %s terminal %d.", unit.name, terminal)
|
||||
coord:MarkToAll(text)
|
||||
env.info(text)
|
||||
end
|
||||
|
||||
unit.x=coord.x
|
||||
@@ -7374,6 +7384,7 @@ function WAREHOUSE:_CheckRequestNow(request)
|
||||
local _transports
|
||||
local _assetattribute
|
||||
local _assetcategory
|
||||
local _assetairstart=false
|
||||
|
||||
-- Check if at least one (cargo) asset is available.
|
||||
if _nassets>0 then
|
||||
@@ -7381,21 +7392,28 @@ function WAREHOUSE:_CheckRequestNow(request)
|
||||
-- Get the attibute of the requested asset.
|
||||
_assetattribute=_assets[1].attribute
|
||||
_assetcategory=_assets[1].category
|
||||
_assetairstart=_assets[1].takeoffType and _assets[1].takeoffType==COORDINATE.WaypointType.TurningPoint or false
|
||||
|
||||
-- Check available parking for air asset units.
|
||||
if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
|
||||
|
||||
if self.airbase and self.airbase:GetCoalition()==self:GetCoalition() then
|
||||
|
||||
if self:IsRunwayOperational() then
|
||||
if self:IsRunwayOperational() or _assetairstart then
|
||||
|
||||
local Parking=self:_FindParkingForAssets(self.airbase,_assets)
|
||||
|
||||
--if Parking==nil and not (self.category==Airbase.Category.HELIPAD) then
|
||||
if Parking==nil then
|
||||
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.", self.alias)
|
||||
self:_InfoMessage(text, 5)
|
||||
return false
|
||||
if _assetairstart then
|
||||
-- Airstart no need to check parking
|
||||
else
|
||||
|
||||
-- Check parking.
|
||||
local Parking=self:_FindParkingForAssets(self.airbase,_assets)
|
||||
|
||||
-- No parking?
|
||||
if Parking==nil then
|
||||
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.", self.alias)
|
||||
self:_InfoMessage(text, 5)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
@@ -7969,93 +7987,123 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
|
||||
-- Loop over all assets that need a parking psot.
|
||||
for _,asset in pairs(assets) do
|
||||
local _asset=asset --#WAREHOUSE.Assetitem
|
||||
|
||||
-- Get terminal type of this asset
|
||||
local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute, self:GetAirbaseCategory())
|
||||
|
||||
-- Asset specific parking.
|
||||
parking[_asset.uid]={}
|
||||
|
||||
-- Loop over all units - each one needs a spot.
|
||||
for i=1,_asset.nunits do
|
||||
|
||||
-- Asset name
|
||||
local assetname=_asset.spawngroupname.."-"..tostring(i)
|
||||
|
||||
-- Loop over all parking spots.
|
||||
local gotit=false
|
||||
for _,_parkingspot in pairs(parkingdata) do
|
||||
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
|
||||
-- Check correct terminal type for asset. We don't want helos in shelters etc.
|
||||
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingValid(parkingspot) and self:_CheckParkingAsset(parkingspot, asset) and airbase:_CheckParkingLists(parkingspot.TerminalID) then
|
||||
|
||||
-- Coordinate of the parking spot.
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
local _termid=parkingspot.TerminalID
|
||||
local free=true
|
||||
local problem=nil
|
||||
|
||||
-- Loop over all obstacles.
|
||||
for _,obstacle in pairs(obstacles) do
|
||||
|
||||
-- Check if aircraft overlaps with any obstacle.
|
||||
local dist=_spot:Get2DDistance(obstacle.coord)
|
||||
local safe=_overlap(_asset.size, obstacle.size, dist)
|
||||
|
||||
-- Spot is blocked.
|
||||
if not safe then
|
||||
self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", assetname, _asset.uid, _termid, dist))
|
||||
free=false
|
||||
problem=obstacle
|
||||
problem.dist=dist
|
||||
break
|
||||
else
|
||||
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", assetname, _asset.uid, _termid, dist))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Check if spot is free
|
||||
if free then
|
||||
|
||||
-- Add parkingspot for this asset unit.
|
||||
table.insert(parking[_asset.uid], parkingspot)
|
||||
|
||||
-- Debug
|
||||
self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!", _termid, assetname, _asset.uid))
|
||||
|
||||
-- Add the unit as obstacle so that this spot will not be available for the next unit.
|
||||
table.insert(obstacles, {coord=_spot, size=_asset.size, name=assetname, type="asset"})
|
||||
|
||||
gotit=true
|
||||
break
|
||||
if not _asset.spawned then
|
||||
|
||||
-- Get terminal type of this asset
|
||||
local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute, self:GetAirbaseCategory())
|
||||
|
||||
-- Asset specific parking.
|
||||
parking[_asset.uid]={}
|
||||
|
||||
-- Loop over all units - each one needs a spot.
|
||||
for i=1,_asset.nunits do
|
||||
|
||||
-- Asset name
|
||||
local assetname=_asset.spawngroupname.."-"..tostring(i)
|
||||
|
||||
-- Loop over all parking spots.
|
||||
local gotit=false
|
||||
for _,_parkingspot in pairs(parkingdata) do
|
||||
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
|
||||
-- Parking valid?
|
||||
local valid=true
|
||||
|
||||
if asset.parkingIDs then
|
||||
-- If asset has assigned parking spots, we take these no matter what.
|
||||
valid=self:_CheckParkingAsset(parkingspot, asset)
|
||||
else
|
||||
|
||||
-- Debug output for occupied spots.
|
||||
if self.Debug then
|
||||
local coord=problem.coord --Core.Point#COORDINATE
|
||||
local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist)
|
||||
self:I(self.lid..text)
|
||||
coord:MarkToAll(string.format(text))
|
||||
else
|
||||
self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid))
|
||||
end
|
||||
|
||||
|
||||
-- Valid terminal type depending on attribute.
|
||||
local validTerminal=AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype)
|
||||
|
||||
-- Valid parking list.
|
||||
local validParking=self:_CheckParkingValid(parkingspot)
|
||||
|
||||
-- Black and white list.
|
||||
local validBWlist=airbase:_CheckParkingLists(parkingspot.TerminalID)
|
||||
|
||||
-- Debug info.
|
||||
--env.info(string.format("FF validTerminal = %s", tostring(validTerminal)))
|
||||
--env.info(string.format("FF validParking = %s", tostring(validParking)))
|
||||
--env.info(string.format("FF validBWlist = %s", tostring(validBWlist)))
|
||||
|
||||
-- Check if all are true
|
||||
valid=validTerminal and validParking and validBWlist
|
||||
end
|
||||
|
||||
else
|
||||
self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported", parkingspot.TerminalID, parkingspot.TerminalType))
|
||||
end -- check terminal type
|
||||
end -- loop over parking spots
|
||||
|
||||
-- No parking spot for at least one asset :(
|
||||
if not gotit then
|
||||
self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]", assetname, _asset.uid))
|
||||
return nil
|
||||
end
|
||||
end -- loop over asset units
|
||||
|
||||
|
||||
-- Check correct terminal type for asset. We don't want helos in shelters etc.
|
||||
if valid then
|
||||
|
||||
-- Coordinate of the parking spot.
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
local _termid=parkingspot.TerminalID
|
||||
local free=true
|
||||
local problem=nil
|
||||
|
||||
-- Loop over all obstacles.
|
||||
for _,obstacle in pairs(obstacles) do
|
||||
|
||||
-- Check if aircraft overlaps with any obstacle.
|
||||
local dist=_spot:Get2DDistance(obstacle.coord)
|
||||
local safe=_overlap(_asset.size, obstacle.size, dist)
|
||||
|
||||
-- Spot is blocked.
|
||||
if not safe then
|
||||
self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", assetname, _asset.uid, _termid, dist))
|
||||
free=false
|
||||
problem=obstacle
|
||||
problem.dist=dist
|
||||
break
|
||||
else
|
||||
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", assetname, _asset.uid, _termid, dist))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Check if spot is free
|
||||
if free then
|
||||
|
||||
-- Add parkingspot for this asset unit.
|
||||
table.insert(parking[_asset.uid], parkingspot)
|
||||
|
||||
-- Debug
|
||||
self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!", _termid, assetname, _asset.uid))
|
||||
|
||||
-- Add the unit as obstacle so that this spot will not be available for the next unit.
|
||||
table.insert(obstacles, {coord=_spot, size=_asset.size, name=assetname, type="asset"})
|
||||
|
||||
gotit=true
|
||||
break
|
||||
|
||||
else
|
||||
|
||||
-- Debug output for occupied spots.
|
||||
if self.Debug then
|
||||
local coord=problem.coord --Core.Point#COORDINATE
|
||||
local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist)
|
||||
self:I(self.lid..text)
|
||||
coord:MarkToAll(string.format(text))
|
||||
else
|
||||
self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported", parkingspot.TerminalID, parkingspot.TerminalType))
|
||||
end -- check terminal type
|
||||
end -- loop over parking spots
|
||||
|
||||
-- No parking spot for at least one asset :(
|
||||
if not gotit then
|
||||
self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]", assetname, _asset.uid))
|
||||
return nil
|
||||
end
|
||||
end -- loop over asset units
|
||||
end -- Asset spawned check
|
||||
end -- loop over asset groups
|
||||
|
||||
return parking
|
||||
|
||||
@@ -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' )
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
-- @field #boolean ReportmBar Report mBar/hpa even if not metric, i.e. for Mirage flights
|
||||
-- @field #boolean TransmitOnlyWithPlayers For SRS - If true, only transmit if there are alive Players.
|
||||
-- @field #string SRSText Text of the complete SRS message (if done at least once, else nil)
|
||||
-- @field #boolean ATISforFARPs Will be set to true if the base given is a FARP/Helipad
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
|
||||
@@ -309,6 +310,19 @@
|
||||
-- atis:Start()
|
||||
--
|
||||
-- This uses a male voice with US accent. It requires SRS to be installed in the `D:\DCS\_SRS\` directory. Not that backslashes need to be escaped or simply use slashes (as in linux).
|
||||
--
|
||||
-- ## FARPS
|
||||
--
|
||||
-- ATIS is working with FARPS, but this requires the usage of SRS. The airbase name for the `New()-method` is the UNIT name of the FARP:
|
||||
--
|
||||
-- atis = ATIS:New("FARP Gold",119,radio.modulation.AM)
|
||||
-- atis:SetMetricUnits()
|
||||
-- atis:SetTransmitOnlyWithPlayers(true)
|
||||
-- atis:SetReportmBar(true)
|
||||
-- atis:SetTowerFrequencies(127.50)
|
||||
-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US",nil,5002)
|
||||
-- atis:SetAdditionalInformation("Welcome to the Jungle!")
|
||||
-- atis:__Start(3)
|
||||
--
|
||||
-- @field #ATIS
|
||||
ATIS = {
|
||||
@@ -351,6 +365,7 @@ ATIS = {
|
||||
relHumidity = nil,
|
||||
ReportmBar = false,
|
||||
TransmitOnlyWithPlayers = false,
|
||||
ATISforFARPs = false,
|
||||
}
|
||||
|
||||
--- NATO alphabet.
|
||||
@@ -593,7 +608,7 @@ _ATIS = {}
|
||||
|
||||
--- ATIS class version.
|
||||
-- @field #string version
|
||||
ATIS.version = "0.9.12"
|
||||
ATIS.version = "0.9.14"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -619,7 +634,7 @@ ATIS.version = "0.9.12"
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new ATIS class object for a specific aircraft carrier unit.
|
||||
--- Create a new ATIS class object for a specific airbase.
|
||||
-- @param #ATIS self
|
||||
-- @param #string AirbaseName Name of the airbase.
|
||||
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz.
|
||||
@@ -1049,7 +1064,7 @@ end
|
||||
--
|
||||
-- * 186° on the Caucaus map
|
||||
-- * 192° on the Nevada map
|
||||
-- * 170° on the Normany map
|
||||
-- * 170° on the Normandy map
|
||||
-- * 182° on the Persian Gulf map
|
||||
--
|
||||
-- Likewise, to convert *true* into *magnetic* heading, one has to substract easterly and add westerly variation.
|
||||
@@ -1257,11 +1272,18 @@ end
|
||||
function ATIS:onafterStart( From, Event, To )
|
||||
|
||||
-- Check that this is an airdrome.
|
||||
if self.airbase:GetAirbaseCategory() ~= Airbase.Category.AIRDROME then
|
||||
self:E( self.lid .. string.format( "ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT FARPS or SHIPS.", self.airbasename ) )
|
||||
if self.airbase:GetAirbaseCategory() == Airbase.Category.SHIP then
|
||||
self:E( self.lid .. string.format( "ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT SHIPS.", self.airbasename ) )
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
-- Check that if is a Helipad.
|
||||
if self.airbase:GetAirbaseCategory() == Airbase.Category.HELIPAD then
|
||||
self:E( self.lid .. string.format( "EXPERIMENTAL: Starting ATIS for Helipad %s! SRS must be ON", self.airbasename ) )
|
||||
self.ATISforFARPs = true
|
||||
self.useSRS = true
|
||||
end
|
||||
|
||||
-- Info.
|
||||
self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) )
|
||||
|
||||
@@ -1473,10 +1495,19 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
--------------
|
||||
--- Runway ---
|
||||
--------------
|
||||
|
||||
local runwayLanding, rwyLandingLeft=self:GetActiveRunway()
|
||||
local runwayTakeoff, rwyTakeoffLeft=self:GetActiveRunway(true)
|
||||
|
||||
|
||||
|
||||
local runwayLanding, rwyLandingLeft
|
||||
local runwayTakeoff, rwyTakeoffLeft
|
||||
|
||||
if self.airbase:GetAirbaseCategory() == Airbase.Category.HELIPAD then
|
||||
runwayLanding, rwyLandingLeft="PAD 01",false
|
||||
runwayTakeoff, rwyTakeoffLeft="PAD 02",false
|
||||
else
|
||||
runwayLanding, rwyLandingLeft=self:GetActiveRunway()
|
||||
runwayTakeoff, rwyTakeoffLeft=self:GetActiveRunway(true)
|
||||
end
|
||||
|
||||
------------
|
||||
--- Time ---
|
||||
------------
|
||||
@@ -1790,7 +1821,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
|
||||
-- Airbase name
|
||||
subtitle = string.format( "%s", self.airbasename )
|
||||
if self.airbasename:find( "AFB" ) == nil and self.airbasename:find( "Airport" ) == nil and self.airbasename:find( "Airstrip" ) == nil and self.airbasename:find( "airfield" ) == nil and self.airbasename:find( "AB" ) == nil then
|
||||
if (not self.ATISforFARPs) and self.airbasename:find( "AFB" ) == nil and self.airbasename:find( "Airport" ) == nil and self.airbasename:find( "Airstrip" ) == nil and self.airbasename:find( "airfield" ) == nil and self.airbasename:find( "AB" ) == nil then
|
||||
subtitle = subtitle .. " Airport"
|
||||
end
|
||||
if not self.useSRS then
|
||||
@@ -1865,8 +1896,6 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
end
|
||||
end
|
||||
alltext = alltext .. ";\n" .. subtitle
|
||||
--self:I("Line 1811")
|
||||
--self:I(alltext)
|
||||
|
||||
-- Visibility
|
||||
if self.metric then
|
||||
@@ -1884,8 +1913,6 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
end
|
||||
end
|
||||
alltext = alltext .. ";\n" .. subtitle
|
||||
--self:I("Line 1830")
|
||||
--self:I(alltext)
|
||||
|
||||
subtitle = ""
|
||||
-- Weather phenomena
|
||||
@@ -1987,10 +2014,8 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
end
|
||||
end
|
||||
end
|
||||
--self:I("Line 1932")
|
||||
|
||||
alltext = alltext .. ";\n" .. subtitle
|
||||
--self:I(alltext)
|
||||
subtitle = ""
|
||||
-- Temperature
|
||||
if self.TDegF then
|
||||
@@ -2019,9 +2044,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 )
|
||||
end
|
||||
end
|
||||
--self:I("Line 1962")
|
||||
alltext = alltext .. ";\n" .. subtitle
|
||||
--self:I(alltext)
|
||||
|
||||
-- Dew point
|
||||
if self.TDegF then
|
||||
@@ -2050,8 +2073,6 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 )
|
||||
end
|
||||
end
|
||||
--self:I("Line 1992")
|
||||
--self:I(alltext)
|
||||
alltext = alltext .. ";\n" .. subtitle
|
||||
|
||||
-- Altimeter QNH/QFE.
|
||||
@@ -2117,69 +2138,68 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
end
|
||||
end
|
||||
end
|
||||
--self:I("Line 2049")
|
||||
--self:I(alltext)
|
||||
alltext = alltext .. ";\n" .. subtitle
|
||||
|
||||
-- Active runway.
|
||||
local subtitle=string.format("Active runway %s", runwayLanding)
|
||||
if rwyLandingLeft==true then
|
||||
subtitle=subtitle.." Left"
|
||||
elseif rwyLandingLeft==false then
|
||||
subtitle=subtitle.." Right"
|
||||
end
|
||||
local _RUNACT = subtitle
|
||||
if not self.useSRS then
|
||||
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(runwayLanding)
|
||||
if not self.ATISforFARPs then
|
||||
-- Active runway.
|
||||
local subtitle=string.format("Active runway %s", runwayLanding)
|
||||
if rwyLandingLeft==true then
|
||||
self:Transmission(ATIS.Sound.Left, 0.2)
|
||||
subtitle=subtitle.." Left"
|
||||
elseif rwyLandingLeft==false then
|
||||
self:Transmission(ATIS.Sound.Right, 0.2)
|
||||
subtitle=subtitle.." Right"
|
||||
end
|
||||
end
|
||||
alltext = alltext .. ";\n" .. subtitle
|
||||
|
||||
-- Runway length.
|
||||
if self.rwylength then
|
||||
|
||||
local runact = self.airbase:GetActiveRunway( self.runwaym2t )
|
||||
local length = runact.length
|
||||
if not self.metric then
|
||||
length = UTILS.MetersToFeet( length )
|
||||
end
|
||||
|
||||
-- Length in thousands and hundrets of ft/meters.
|
||||
local L1000, L0100 = self:_GetThousandsAndHundreds( length )
|
||||
|
||||
-- Subtitle.
|
||||
local subtitle = string.format( "Runway length %d", length )
|
||||
if self.metric then
|
||||
subtitle = subtitle .. " meters"
|
||||
else
|
||||
subtitle = subtitle .. " feet"
|
||||
end
|
||||
|
||||
-- Transmit.
|
||||
local _RUNACT = subtitle
|
||||
if not self.useSRS then
|
||||
self:Transmission( ATIS.Sound.RunwayLength, 1.0, subtitle )
|
||||
if tonumber( L1000 ) > 0 then
|
||||
self.radioqueue:Number2Transmission( L1000 )
|
||||
self:Transmission( ATIS.Sound.Thousand, 0.1 )
|
||||
end
|
||||
if tonumber( L0100 ) > 0 then
|
||||
self.radioqueue:Number2Transmission( L0100 )
|
||||
self:Transmission( ATIS.Sound.Hundred, 0.1 )
|
||||
end
|
||||
if self.metric then
|
||||
self:Transmission( ATIS.Sound.Meters, 0.1 )
|
||||
else
|
||||
self:Transmission( ATIS.Sound.Feet, 0.1 )
|
||||
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
|
||||
self.radioqueue:Number2Transmission(runwayLanding)
|
||||
if rwyLandingLeft==true then
|
||||
self:Transmission(ATIS.Sound.Left, 0.2)
|
||||
elseif rwyLandingLeft==false then
|
||||
self:Transmission(ATIS.Sound.Right, 0.2)
|
||||
end
|
||||
end
|
||||
alltext = alltext .. ";\n" .. subtitle
|
||||
|
||||
-- Runway length.
|
||||
if self.rwylength then
|
||||
|
||||
local runact = self.airbase:GetActiveRunway( self.runwaym2t )
|
||||
local length = runact.length
|
||||
if not self.metric then
|
||||
length = UTILS.MetersToFeet( length )
|
||||
end
|
||||
|
||||
-- Length in thousands and hundrets of ft/meters.
|
||||
local L1000, L0100 = self:_GetThousandsAndHundreds( length )
|
||||
|
||||
-- Subtitle.
|
||||
local subtitle = string.format( "Runway length %d", length )
|
||||
if self.metric then
|
||||
subtitle = subtitle .. " meters"
|
||||
else
|
||||
subtitle = subtitle .. " feet"
|
||||
end
|
||||
|
||||
-- Transmit.
|
||||
if not self.useSRS then
|
||||
self:Transmission( ATIS.Sound.RunwayLength, 1.0, subtitle )
|
||||
if tonumber( L1000 ) > 0 then
|
||||
self.radioqueue:Number2Transmission( L1000 )
|
||||
self:Transmission( ATIS.Sound.Thousand, 0.1 )
|
||||
end
|
||||
if tonumber( L0100 ) > 0 then
|
||||
self.radioqueue:Number2Transmission( L0100 )
|
||||
self:Transmission( ATIS.Sound.Hundred, 0.1 )
|
||||
end
|
||||
if self.metric then
|
||||
self:Transmission( ATIS.Sound.Meters, 0.1 )
|
||||
else
|
||||
self:Transmission( ATIS.Sound.Feet, 0.1 )
|
||||
end
|
||||
end
|
||||
alltext = alltext .. ";\n" .. subtitle
|
||||
end
|
||||
end
|
||||
|
||||
-- Airfield elevation
|
||||
if self.elevation then
|
||||
|
||||
@@ -2246,9 +2266,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
end
|
||||
|
||||
-- ILS
|
||||
--self:I({ils=self.ils})
|
||||
local ils=self:GetNavPoint(self.ils, runwayLanding, rwyLandingLeft)
|
||||
--self:I({ils=ils,runwayLanding=runwayLanding, rwyLandingLeft=rwyLandingLeft})
|
||||
if ils then
|
||||
subtitle = string.format( "ILS frequency %.2f MHz", ils.frequency )
|
||||
if not self.useSRS then
|
||||
@@ -2263,7 +2281,6 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
self:Transmission( ATIS.Sound.MegaHertz, 0.2 )
|
||||
end
|
||||
alltext = alltext .. ";\n" .. subtitle
|
||||
--self:I(alltext)
|
||||
end
|
||||
|
||||
-- Outer NDB
|
||||
@@ -2399,6 +2416,8 @@ function ATIS:onafterReport( From, Event, To, Text )
|
||||
local text = string.gsub( text, "mmHg", "millimeters of Mercury" )
|
||||
local text = string.gsub( text, "hPa", "hectopascals" )
|
||||
local text = string.gsub( text, "m/s", "meters per second" )
|
||||
local text = string.gsub( text, "TACAN", "tackan" )
|
||||
local text = string.gsub( text, "FARP", "farp" )
|
||||
|
||||
-- Replace ";" by "."
|
||||
local text = string.gsub( text, ";", " . " )
|
||||
|
||||
@@ -32,20 +32,21 @@
|
||||
-- * [USS George Washington](https://en.wikipedia.org/wiki/USS_George_Washington_(CVN-73\)) (CVN-73) [Super Carrier Module]
|
||||
-- * [USS Harry S. Truman](https://en.wikipedia.org/wiki/USS_Harry_S._Truman) (CVN-75) [Super Carrier Module]
|
||||
-- * [USS Forrestal](https://en.wikipedia.org/wiki/USS_Forrestal_(CV-59\)) (CV-59) [Heatblur Carrier Module]
|
||||
-- * [HMS Hermes](https://en.wikipedia.org/wiki/HMS_Hermes_(R12\)) (R12) [**WIP**]
|
||||
-- * [HMS Invincible](https://en.wikipedia.org/wiki/HMS_Invincible_(R05\)) (R05) [**WIP**]
|
||||
-- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1\)) (LHA-1) [**WIP**]
|
||||
-- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6\)) (LHA-6) [**WIP**]
|
||||
-- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61) [**WIP**]
|
||||
-- * [HMAS Canberra](https://en.wikipedia.org/wiki/HMAS_Canberra_(L02\)) (L02) [**WIP**]
|
||||
-- * [HMS Hermes](https://en.wikipedia.org/wiki/HMS_Hermes_(R12\)) (R12)
|
||||
-- * [HMS Invincible](https://en.wikipedia.org/wiki/HMS_Invincible_(R05\)) (R05)
|
||||
-- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1\)) (LHA-1)
|
||||
-- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6\)) (LHA-6)
|
||||
-- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61)
|
||||
-- * [HMAS Canberra](https://en.wikipedia.org/wiki/HMAS_Canberra_(L02\)) (L02)
|
||||
--
|
||||
-- **Supported Aircraft:**
|
||||
--
|
||||
-- * [F/A-18C Hornet Lot 20](https://forums.eagle.ru/forumdisplay.php?f=557) (Player & AI)
|
||||
-- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
|
||||
-- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI)
|
||||
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**]
|
||||
-- * [T-45C Goshawk](https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**]
|
||||
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI)
|
||||
-- * [T-45C Goshawk](https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO mod) (Player & AI)
|
||||
-- * [FE/A-18E/F/G Superhornet](https://forum.dcs.world/topic/316971-cjs-super-hornet-community-mod-v20-official-thread/) (CJS mod) (Player & AI)
|
||||
-- * F/A-18C Hornet (AI)
|
||||
-- * F-14A Tomcat (AI)
|
||||
-- * E-2D Hawkeye (AI)
|
||||
@@ -1278,7 +1279,10 @@ AIRBOSS = {
|
||||
-- @field #string S3BTANKER Lockheed S-3B Viking tanker.
|
||||
-- @field #string E2D Grumman E-2D Hawkeye AWACS.
|
||||
-- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod.
|
||||
-- @field #string T45C T-45C by VNAO
|
||||
-- @field #string T45C T-45C by VNAO.
|
||||
-- @field #string RHINOE F/A-18E Superhornet (mod).
|
||||
-- @field #string RHINOF F/A-18F Superhornet (mod).
|
||||
-- @field #string GROWLER FEA-18G Superhornet (mod).
|
||||
AIRBOSS.AircraftCarrier={
|
||||
AV8B="AV8BNA",
|
||||
HORNET="FA-18C_hornet",
|
||||
@@ -1292,6 +1296,9 @@ AIRBOSS.AircraftCarrier={
|
||||
S3BTANKER="S-3B Tanker",
|
||||
E2D="E-2C",
|
||||
C2A="C2A_Greyhound",
|
||||
RHINOE="FA-18E",
|
||||
RHINOF="FA-18F",
|
||||
GROWLER="EA-18G",
|
||||
}
|
||||
|
||||
--- Carrier types.
|
||||
@@ -1302,7 +1309,7 @@ AIRBOSS.AircraftCarrier={
|
||||
-- @field #string STENNIS USS John C. Stennis (CVN-74)
|
||||
-- @field #string TRUMAN USS Harry S. Truman (CVN-75) [Super Carrier Module]
|
||||
-- @field #string FORRESTAL USS Forrestal (CV-59) [Heatblur Carrier Module]
|
||||
-- @field #string VINSON USS Carl Vinson (CVN-70) [Obsolete]
|
||||
-- @field #string VINSON USS Carl Vinson (CVN-70) [Deprecated!]
|
||||
-- @field #string HERMES HMS Hermes (R12) [V/STOL Carrier]
|
||||
-- @field #string INVINCIBLE HMS Invincible (R05) [V/STOL Carrier]
|
||||
-- @field #string TARAWA USS Tarawa (LHA-1) [V/STOL Carrier]
|
||||
@@ -5177,7 +5184,10 @@ end
|
||||
function AIRBOSS:_GetAircraftAoA( playerData )
|
||||
|
||||
-- Get AC type.
|
||||
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
|
||||
local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C
|
||||
local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC
|
||||
local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B
|
||||
@@ -5340,7 +5350,10 @@ function AIRBOSS:_GetAircraftParameters( playerData, step )
|
||||
step = step or playerData.step
|
||||
|
||||
-- Get AC type.
|
||||
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
|
||||
local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC
|
||||
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
|
||||
local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B
|
||||
@@ -6251,6 +6264,9 @@ function AIRBOSS:_RefuelAI( flight )
|
||||
actype==AIRBOSS.AircraftCarrier.F14B or
|
||||
actype==AIRBOSS.AircraftCarrier.F14A_AI or
|
||||
actype==AIRBOSS.AircraftCarrier.HORNET or
|
||||
actype==AIRBOSS.AircraftCarrier.RHINOE or
|
||||
actype==AIRBOSS.AircraftCarrier.RHINOF or
|
||||
actype==AIRBOSS.AircraftCarrier.GROWLER or
|
||||
actype==AIRBOSS.AircraftCarrier.FA18C or
|
||||
actype==AIRBOSS.AircraftCarrier.S3B or
|
||||
actype==AIRBOSS.AircraftCarrier.S3BTANKER then
|
||||
@@ -6348,7 +6364,11 @@ function AIRBOSS:_LandAI( flight )
|
||||
-- Aircraft speed when flying the pattern.
|
||||
local Speed = UTILS.KnotsToKmph( 200 )
|
||||
|
||||
if flight.actype == AIRBOSS.AircraftCarrier.HORNET or flight.actype == AIRBOSS.AircraftCarrier.FA18C then
|
||||
if flight.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or flight.actype == AIRBOSS.AircraftCarrier.FA18C
|
||||
or flight.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or flight.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or flight.actype == AIRBOSS.AircraftCarrier.GROWLER then
|
||||
Speed = UTILS.KnotsToKmph( 200 )
|
||||
elseif flight.actype == AIRBOSS.AircraftCarrier.E2D then
|
||||
Speed = UTILS.KnotsToKmph( 150 )
|
||||
@@ -9172,7 +9192,13 @@ function AIRBOSS:_DirtyUp( playerData )
|
||||
self:_PlayerHint( playerData )
|
||||
|
||||
-- Radio call "Say/Fly needles". Delayed by 10/15 seconds.
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET or playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.F14A
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.F14B
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
|
||||
then
|
||||
local callsay = self:_NewRadioCall( self.MarshalCall.SAYNEEDLES, nil, nil, 5, playerData.onboard )
|
||||
local callfly = self:_NewRadioCall( self.MarshalCall.FLYNEEDLES, nil, nil, 5, playerData.onboard )
|
||||
self:RadioTransmission( self.MarshalRadio, callsay, false, 55, nil, true )
|
||||
@@ -10154,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.
|
||||
@@ -10263,7 +10311,10 @@ function AIRBOSS:_Trapped( playerData )
|
||||
|
||||
-- Get current wire (estimate). This now based on the position where the player comes to a standstill which should reflect the trapped wire better.
|
||||
local dcorr = 100
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER then
|
||||
dcorr = 100
|
||||
elseif playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
-- TODO: Check Tomcat.
|
||||
@@ -12463,7 +12514,10 @@ function AIRBOSS:_PlayerHint( playerData, delay, soundoff )
|
||||
if playerData.step == AIRBOSS.PatternStep.BULLSEYE then
|
||||
-- Hint follow the needles.
|
||||
if playerData.difficulty == AIRBOSS.Difficulty.EASY then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER then
|
||||
hint = hint .. string.format( "\nIntercept glideslope and follow the needles." )
|
||||
else
|
||||
hint = hint .. string.format( "\nIntercept glideslope." )
|
||||
@@ -13957,6 +14011,10 @@ function AIRBOSS:_GetACNickname( actype )
|
||||
nickname = "Tomcat"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.FA18C or actype == AIRBOSS.AircraftCarrier.HORNET then
|
||||
nickname = "Hornet"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.RHINOE or actype == AIRBOSS.AircraftCarrier.RHINOF then
|
||||
nickname = "Rhino"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.GROWLER then
|
||||
nickname = "Growler"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.S3B or actype == AIRBOSS.AircraftCarrier.S3BTANKER then
|
||||
nickname = "Viking"
|
||||
end
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing)
|
||||
-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), The Chosen One (Persistence)
|
||||
-- @module Ops.CSAR
|
||||
-- @image OPS_CSAR.jpg
|
||||
|
||||
-- Date: November 2022
|
||||
-- Date: January 2023
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
|
||||
@@ -197,6 +197,26 @@
|
||||
--
|
||||
-- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat
|
||||
-- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE)
|
||||
--
|
||||
-- ## 6. Save and load downed pilots - Persistance
|
||||
--
|
||||
-- You can save and later load back downed pilots to make your mission persistent.
|
||||
-- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts.
|
||||
-- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you.
|
||||
--
|
||||
-- Use the following options to manage your saves:
|
||||
--
|
||||
-- mycsar.enableLoadSave = true -- allow auto-saving and loading of files
|
||||
-- mycsar.saveinterval = 600 -- save every 10 minutes
|
||||
-- mycsar.filename = "missionsave.csv" -- example filename
|
||||
-- mycsar.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path
|
||||
--
|
||||
-- Then use an initial load at the beginning of your mission:
|
||||
--
|
||||
-- mycsar:__Load(10)
|
||||
--
|
||||
-- **Caveat:**
|
||||
-- Dropped troop noMessage and forcedesc parameters aren't saved.
|
||||
--
|
||||
-- @field #CSAR
|
||||
CSAR = {
|
||||
@@ -272,7 +292,7 @@ CSAR.AircraftType["Bronco-OV-10A"] = 2
|
||||
|
||||
--- CSAR class version.
|
||||
-- @field #string version
|
||||
CSAR.version="1.0.16"
|
||||
CSAR.version="1.0.17"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@@ -296,6 +316,8 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #CSAR
|
||||
|
||||
BASE:T({Coalition, Prefixes, Alias})
|
||||
|
||||
--set Coalition
|
||||
if Coalition and type(Coalition)=="string" then
|
||||
if Coalition=="blue" then
|
||||
@@ -346,6 +368,8 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
self:AddTransition("*", "Returning", "*") -- CSAR able to return to base.
|
||||
self:AddTransition("*", "Rescued", "*") -- Pilot at MASH.
|
||||
self:AddTransition("*", "KIA", "*") -- Pilot killed in action.
|
||||
self:AddTransition("*", "Load", "*") -- CSAR load event.
|
||||
self:AddTransition("*", "Save", "*") -- CSAR save event.
|
||||
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
||||
|
||||
-- tables, mainly for tracking actions
|
||||
@@ -442,6 +466,14 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
self.SRSVolume = 1.0 -- volume 0.0 to 1.0
|
||||
self.SRSGender = "male" -- male or female
|
||||
|
||||
local AliaS = string.gsub(self.alias," ","_")
|
||||
self.filename = string.format("CSAR_%s_Persist.csv",AliaS)
|
||||
|
||||
-- load and save downed pilots
|
||||
self.enableLoadSave = false
|
||||
self.filepath = nil
|
||||
self.saveinterval = 600
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
@@ -471,6 +503,24 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
-- @function [parent=#CSAR] __Status
|
||||
-- @param #CSAR self
|
||||
-- @param #number delay Delay in seconds.
|
||||
--
|
||||
-- --- Triggers the FSM event "Load".
|
||||
-- @function [parent=#CSAR] Load
|
||||
-- @param #CSAR self
|
||||
|
||||
--- Triggers the FSM event "Load" after a delay.
|
||||
-- @function [parent=#CSAR] __Load
|
||||
-- @param #CSAR self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Save".
|
||||
-- @function [parent=#CSAR] Load
|
||||
-- @param #CSAR self
|
||||
|
||||
--- Triggers the FSM event "Save" after a delay.
|
||||
-- @function [parent=#CSAR] __Save
|
||||
-- @param #CSAR self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- On After "PilotDown" event. Downed Pilot detected.
|
||||
-- @function [parent=#CSAR] OnAfterPilotDown
|
||||
@@ -538,6 +588,24 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
-- @param #string To To state.
|
||||
-- @param #string Pilotname Name of the pilot KIA.
|
||||
|
||||
--- FSM Function OnAfterLoad.
|
||||
-- @function [parent=#CSAR] OnAfterLoad
|
||||
-- @param #CSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
|
||||
-- @param #string filename (Optional) File name for loading. Default is "CSAR_<alias>_Persist.csv".
|
||||
|
||||
--- FSM Function OnAfterSave.
|
||||
-- @function [parent=#CSAR] OnAfterSave
|
||||
-- @param #CSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
|
||||
-- @param #string filename (Optional) File name for saving. Default is "CSAR_<alias>_Persist.csv".
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -850,7 +918,7 @@ end
|
||||
|
||||
--- (Internal) Function to add a CSAR object into the scene at a Point coordinate (VEC_2). For mission designers wanting to add e.g. casualties to the scene, that don't use beacons.
|
||||
-- @param #CSAR self
|
||||
-- @param #string _Point a POINT_VEC2.
|
||||
-- @param Core.Point#COORDINATE _Point
|
||||
-- @param #number _coalition Coalition.
|
||||
-- @param #string _description (optional) Description.
|
||||
-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR.
|
||||
@@ -883,7 +951,7 @@ end
|
||||
|
||||
--- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene.
|
||||
-- @param #CSAR self
|
||||
-- @param #string Point a POINT_VEC2.
|
||||
-- @param Core.Point#COORDINATE Point
|
||||
-- @param #number Coalition Coalition.
|
||||
-- @param #string Description (optional) Description.
|
||||
-- @param #boolean addBeacon (optional) yes or no.
|
||||
@@ -893,8 +961,8 @@ end
|
||||
-- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names.
|
||||
-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so:
|
||||
--
|
||||
-- -- Create casualty "CASEVAC" at Point #POINT_VEC2 for the blue coalition.
|
||||
-- my_csar:SpawnCASEVAC( POINT_VEC2, coalition.side.BLUE )
|
||||
-- -- Create casualty "CASEVAC" at coordinate Core.Point#COORDINATE for the blue coalition.
|
||||
-- my_csar:SpawnCASEVAC( coordinate, coalition.side.BLUE )
|
||||
function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc)
|
||||
self:_SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc)
|
||||
return self
|
||||
@@ -2108,9 +2176,10 @@ function CSAR:_AddBeaconToGroup(_group, _freq)
|
||||
local _radioUnit = _group:GetUnit(1)
|
||||
if _radioUnit then
|
||||
local Frequency = _freq -- Freq in Hertz
|
||||
local name = _radioUnit:GetName()
|
||||
local Sound = "l10n/DEFAULT/"..self.radioSound
|
||||
local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0}
|
||||
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000) -- Beacon in MP only runs for exactly 30secs straight
|
||||
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,name..math.random(1,10000)) -- Beacon in MP only runs for exactly 30secs straight
|
||||
end
|
||||
end
|
||||
return self
|
||||
@@ -2218,7 +2287,16 @@ function CSAR:onafterStart(From, Event, To)
|
||||
self.msrs:SetLabel("CSAR")
|
||||
self.SRSQueue = MSRSQUEUE:New("CSAR")
|
||||
end
|
||||
|
||||
self:__Status(-10)
|
||||
|
||||
if self.enableLoadSave then
|
||||
local interval = self.saveinterval
|
||||
local filename = self.filename
|
||||
local filepath = self.filepath
|
||||
self:__Save(interval,filepath,filename)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -2451,6 +2529,240 @@ function CSAR:onbeforeLanded(From, Event, To, HeliName, Airbase)
|
||||
self:T({From, Event, To, HeliName, Airbase})
|
||||
return self
|
||||
end
|
||||
|
||||
--- On before "Save" event. Checks if io and lfs are available.
|
||||
-- @param #CSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
|
||||
-- @param #string filename (Optional) File name for saving. Default is "CSAR_<alias>_Persist.csv".
|
||||
function CSAR:onbeforeSave(From, Event, To, path, filename)
|
||||
self:T({From, Event, To, path, filename})
|
||||
if not self.enableLoadSave then
|
||||
return self
|
||||
end
|
||||
-- Thanks to @FunkyFranky
|
||||
-- Check io module is available.
|
||||
if not io then
|
||||
self:E(self.lid.."ERROR: io not desanitized. Can't save current state.")
|
||||
return false
|
||||
end
|
||||
|
||||
-- Check default path.
|
||||
if path==nil and not lfs then
|
||||
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- On after "Save" event. Player data is saved to file.
|
||||
-- @param #CSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory or your "Saved Games" folder if lfs was desanitized.
|
||||
-- @param #string filename (Optional) File name for saving. Default is Default is "CSAR_<alias>_Persist.csv".
|
||||
function CSAR:onafterSave(From, Event, To, path, filename)
|
||||
self:T({From, Event, To, path, filename})
|
||||
-- Thanks to @FunkyFranky
|
||||
if not self.enableLoadSave then
|
||||
return self
|
||||
end
|
||||
--- Function that saves data to file
|
||||
local function _savefile(filename, data)
|
||||
local f = assert(io.open(filename, "wb"))
|
||||
f:write(data)
|
||||
f:close()
|
||||
end
|
||||
|
||||
-- Set path or default.
|
||||
if lfs then
|
||||
path=self.filepath or lfs.writedir()
|
||||
end
|
||||
|
||||
-- Set file name.
|
||||
filename=filename or self.filename
|
||||
|
||||
-- Set path.
|
||||
if path~=nil then
|
||||
filename=path.."\\"..filename
|
||||
end
|
||||
|
||||
local pilots = self.downedPilots
|
||||
|
||||
--local data = "LoadedData = {\n"
|
||||
local data = "playerName,x,y,z,coalition,country,description,typeName,unitName,freq\n"
|
||||
local n = 0
|
||||
for _,_grp in pairs(pilots) do
|
||||
local DownedPilot = _grp -- Wrapper.Group#GROUP
|
||||
if DownedPilot and DownedPilot.alive then
|
||||
-- get downed pilot data for saving
|
||||
local playerName = DownedPilot.player
|
||||
local group = DownedPilot.group
|
||||
local coalition = group:GetCoalition()
|
||||
local country = group:GetCountry()
|
||||
local description = DownedPilot.desc
|
||||
local typeName = DownedPilot.typename
|
||||
local freq = DownedPilot.frequency
|
||||
local location = group:GetVec3()
|
||||
local unitName = DownedPilot.originalUnit
|
||||
local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq)
|
||||
|
||||
self:I(self.lid.."Saving to CSAR File: " .. txt)
|
||||
|
||||
data = data .. txt
|
||||
end
|
||||
end
|
||||
|
||||
_savefile(filename, data)
|
||||
|
||||
-- AutoSave
|
||||
if self.enableLoadSave then
|
||||
local interval = self.saveinterval
|
||||
local filename = self.filename
|
||||
local filepath = self.filepath
|
||||
self:__Save(interval,filepath,filename)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- On before "Load" event. Checks if io and lfs and the file are available.
|
||||
-- @param #CSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
|
||||
-- @param #string filename (Optional) File name for loading. Default is "CSAR_<alias>_Persist.csv".
|
||||
function CSAR:onbeforeLoad(From, Event, To, path, filename)
|
||||
self:T({From, Event, To, path, filename})
|
||||
if not self.enableLoadSave then
|
||||
return self
|
||||
end
|
||||
--- Function that check if a file exists.
|
||||
local function _fileexists(name)
|
||||
local f=io.open(name,"r")
|
||||
if f~=nil then
|
||||
io.close(f)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Set file name and path
|
||||
filename=filename or self.filename
|
||||
path = path or self.filepath
|
||||
|
||||
-- Check io module is available.
|
||||
if not io then
|
||||
self:E(self.lid.."WARNING: io not desanitized. Cannot load file.")
|
||||
return false
|
||||
end
|
||||
|
||||
-- Check default path.
|
||||
if path==nil and not lfs then
|
||||
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
|
||||
end
|
||||
|
||||
-- Set path or default.
|
||||
if lfs then
|
||||
path=path or lfs.writedir()
|
||||
end
|
||||
|
||||
-- Set path.
|
||||
if path~=nil then
|
||||
filename=path.."\\"..filename
|
||||
end
|
||||
|
||||
-- Check if file exists.
|
||||
local exists=_fileexists(filename)
|
||||
|
||||
if exists then
|
||||
return true
|
||||
else
|
||||
self:E(self.lid..string.format("WARNING: State file %s might not exist.", filename))
|
||||
return false
|
||||
--return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after "Load" event. Loads dropped units from file.
|
||||
-- @param #CSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
|
||||
-- @param #string filename (Optional) File name for loading. Default is "CSAR_<alias>_Persist.csv".
|
||||
function CSAR:onafterLoad(From, Event, To, path, filename)
|
||||
self:T({From, Event, To, path, filename})
|
||||
if not self.enableLoadSave then
|
||||
return self
|
||||
end
|
||||
--- Function that loads data from a file.
|
||||
local function _loadfile(filename)
|
||||
local f=assert(io.open(filename, "rb"))
|
||||
local data=f:read("*all")
|
||||
f:close()
|
||||
return data
|
||||
end
|
||||
|
||||
-- Set file name and path
|
||||
filename=filename or self.filename
|
||||
path = path or self.filepath
|
||||
|
||||
-- Set path or default.
|
||||
if lfs then
|
||||
path=path or lfs.writedir()
|
||||
end
|
||||
|
||||
-- Set path.
|
||||
if path~=nil then
|
||||
filename=path.."\\"..filename
|
||||
end
|
||||
|
||||
-- Info message.
|
||||
local text=string.format("Loading CSAR state from file %s", filename)
|
||||
MESSAGE:New(text,10):ToAllIf(self.Debug)
|
||||
self:I(self.lid..text)
|
||||
|
||||
local file=assert(io.open(filename, "rb"))
|
||||
|
||||
local loadeddata = {}
|
||||
for line in file:lines() do
|
||||
loadeddata[#loadeddata+1] = line
|
||||
end
|
||||
file:close()
|
||||
|
||||
-- remove header
|
||||
table.remove(loadeddata, 1)
|
||||
|
||||
for _id,_entry in pairs (loadeddata) do
|
||||
local dataset = UTILS.Split(_entry,",")
|
||||
-- 1=playerName,2=x,3=y,4=z,5=coalition,6=country,7=description,8=typeName,9=unitName,10=freq\n
|
||||
local playerName = dataset[1]
|
||||
|
||||
local vec3 = {}
|
||||
vec3.x = tonumber(dataset[2])
|
||||
vec3.y = tonumber(dataset[3])
|
||||
vec3.z = tonumber(dataset[4])
|
||||
local point = COORDINATE:NewFromVec3(vec3)
|
||||
|
||||
local coalition = tonumber(dataset[5])
|
||||
local country = tonumber(dataset[6])
|
||||
local description = dataset[7]
|
||||
local typeName = dataset[8]
|
||||
local unitName = dataset[9]
|
||||
local freq = tonumber(dataset[10])
|
||||
|
||||
self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, nil, description, nil)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- End Ops.CSAR
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
-- @module Ops.CTLD
|
||||
-- @image OPS_CTLD.jpg
|
||||
|
||||
-- Last Update December 2022
|
||||
-- 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.
|
||||
@@ -712,7 +723,10 @@ do
|
||||
-- my_ctld.usesubcats = false -- use sub-category names for crates, adds an extra menu layer in "Get Crates", useful if you have > 10 crate types.
|
||||
-- my_ctld.placeCratesAhead = false -- place crates straight ahead of the helicopter, in a random way. If true, crates are more neatly sorted.
|
||||
-- my_ctld.nobuildinloadzones = true -- forbid players to build stuff in LOAD zones if set to `true`
|
||||
--
|
||||
-- my_ctld.movecratesbeforebuild = true -- crates must be moved once before they can be build. Set to false for direct builds.
|
||||
-- my_ctld.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} -- surfaces for loading back objects.
|
||||
-- my_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you.
|
||||
--
|
||||
-- ## 2.1 User functions
|
||||
--
|
||||
-- ### 2.1.1 Adjust or add chopper unit-type capabilities
|
||||
@@ -730,6 +744,7 @@ do
|
||||
-- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400},
|
||||
-- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15, cargoweightlimit = 700},
|
||||
-- ["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000},
|
||||
-- ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000},
|
||||
-- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0},
|
||||
-- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700},
|
||||
-- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700},
|
||||
@@ -928,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
|
||||
@@ -947,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.
|
||||
@@ -967,7 +994,110 @@ do
|
||||
--
|
||||
-- **Caveat:**
|
||||
-- If you use units build by multiple templates, they will effectively double on loading. Dropped crates are not saved. Current stock is not saved.
|
||||
--
|
||||
-- ## 7. Complex example - Build a complete FARP from a CTLD crate drop
|
||||
--
|
||||
-- Prerequisites - you need to add a cargo of type FOB to your CTLD instance, for simplification reasons we call it FOB:
|
||||
--
|
||||
-- my_ctld:AddCratesCargo("FARP",{"FOB"},CTLD_CARGO.Enum.FOB,2)
|
||||
--
|
||||
-- Also, you need to have **all statics with the fitting names** as per the script in your mission already, as we're going to copy them, and a template
|
||||
-- for FARP vehicles, so -- services are goin to work (e.g. for the blue side: an unarmed humvee, two trucks and a fuel truck. Optionally add a fire fighter).
|
||||
--
|
||||
-- The following code will build a FARP at the coordinate the FOB was dropped and built:
|
||||
--
|
||||
-- -- FARP Radio. First one has 130AM, next 131 and for forth
|
||||
-- local FARPFreq = 130
|
||||
-- local FARPName = 1 -- numbers 1..10
|
||||
--
|
||||
-- local FARPClearnames = {
|
||||
-- [1]="London",
|
||||
-- [2]="Dallas",
|
||||
-- [3]="Paris",
|
||||
-- [4]="Moscow",
|
||||
-- [5]="Berlin",
|
||||
-- [6]="Rome",
|
||||
-- [7]="Madrid",
|
||||
-- [8]="Warsaw",
|
||||
-- [9]="Dublin",
|
||||
-- [10]="Perth",
|
||||
-- }
|
||||
--
|
||||
-- function BuildAFARP(Coordinate)
|
||||
-- local coord = Coordinate -- Core.Point#COORDINATE
|
||||
--
|
||||
-- local FarpName = ((FARPName-1)%10)+1
|
||||
-- local FName = FARPClearnames[FarpName]
|
||||
--
|
||||
-- FARPFreq = FARPFreq + 1
|
||||
-- FARPName = FARPName + 1
|
||||
--
|
||||
-- -- Create a SPAWNSTATIC object from a template static FARP object.
|
||||
-- local SpawnStaticFarp=SPAWNSTATIC:NewFromStatic("Static Invisible FARP-1", country.id.USA)
|
||||
--
|
||||
-- -- Spawning FARPs is special in DCS. Therefore, we need to specify that this is a FARP. We also set the callsign and the frequency.
|
||||
-- SpawnStaticFarp:InitFARP(FARPName, FARPFreq, 0)
|
||||
-- SpawnStaticFarp:InitDead(false)
|
||||
--
|
||||
-- -- Spawn FARP
|
||||
-- local ZoneSpawn = ZONE_RADIUS:New("FARP "..FName,Coordinate:GetVec2(),160,false)
|
||||
-- local Heading = 0
|
||||
-- local FarpBerlin=SpawnStaticFarp:SpawnFromZone(ZoneSpawn, Heading, "FARP "..FName)
|
||||
--
|
||||
-- -- ATC and services - put them 125m from the center of the zone towards North
|
||||
-- local FarpVehicles = SPAWN:NewWithAlias("FARP Vehicles Template","FARP "..FName.." Technicals")
|
||||
-- FarpVehicles:InitHeading(180)
|
||||
-- local FarpVCoord = coord:Translate(125,0)
|
||||
-- FarpVehicles:SpawnFromCoordinate(FarpVCoord)
|
||||
--
|
||||
-- -- We will put the rest of the statics in a nice circle around the center
|
||||
-- local base = 330
|
||||
-- local delta = 30
|
||||
--
|
||||
-- local windsock = SPAWNSTATIC:NewFromStatic("Static Windsock-1",country.id.USA)
|
||||
-- local sockcoord = coord:Translate(125,base)
|
||||
-- windsock:SpawnFromCoordinate(sockcoord,Heading,"Windsock "..FName)
|
||||
-- base=base-delta
|
||||
--
|
||||
-- local fueldepot = SPAWNSTATIC:NewFromStatic("Static FARP Fuel Depot-1",country.id.USA)
|
||||
-- local fuelcoord = coord:Translate(125,base)
|
||||
-- fueldepot:SpawnFromCoordinate(fuelcoord,Heading,"Fueldepot "..FName)
|
||||
-- base=base-delta
|
||||
--
|
||||
-- local ammodepot = SPAWNSTATIC:NewFromStatic("Static FARP Ammo Storage-2-1",country.id.USA)
|
||||
-- local ammocoord = coord:Translate(125,base)
|
||||
-- ammodepot:SpawnFromCoordinate(ammocoord,Heading,"Ammodepot "..FName)
|
||||
-- base=base-delta
|
||||
--
|
||||
-- local CommandPost = SPAWNSTATIC:NewFromStatic("Static FARP Command Post-1",country.id.USA)
|
||||
-- local CommandCoord = coord:Translate(125,base)
|
||||
-- CommandPost:SpawnFromCoordinate(CommandCoord,Heading,"Command Post "..FName)
|
||||
-- base=base-delta
|
||||
--
|
||||
-- local Tent1 = SPAWNSTATIC:NewFromStatic("Static FARP Tent-11",country.id.USA)
|
||||
-- local Tent1Coord = coord:Translate(125,base)
|
||||
-- Tent1:SpawnFromCoordinate(Tent1Coord,Heading,"Command Tent "..FName)
|
||||
-- base=base-delta
|
||||
--
|
||||
-- local Tent2 = SPAWNSTATIC:NewFromStatic("Static FARP Tent-11",country.id.USA)
|
||||
-- local Tent2Coord = coord:Translate(125,base)
|
||||
-- Tent2:SpawnFromCoordinate(Tent2Coord,Heading,"Command Tent2 "..FName)
|
||||
--
|
||||
-- -- add a loadzone to CTLD
|
||||
-- my_ctld:AddCTLDZone("FARP "..FName,CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true)
|
||||
-- local m = MESSAGE:New(string.format("FARP %s in operation!",FName),15,"CTLD"):ToBlue()
|
||||
-- end
|
||||
--
|
||||
-- function my_ctld:OnAfterCratesBuild(From,Event,To,Group,Unit,Vehicle)
|
||||
-- local name = Vehicle:GetName()
|
||||
-- if string.match(name,"FOB",1,true) then
|
||||
-- local Coord = Vehicle:GetCoordinate()
|
||||
-- Vehicle:Destroy(false)
|
||||
-- BuildAFARP(Coord)
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
--
|
||||
-- @field #CTLD
|
||||
CTLD = {
|
||||
ClassName = "CTLD",
|
||||
@@ -1075,8 +1205,9 @@ CTLD.UnitTypes = {
|
||||
["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400},
|
||||
["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15, cargoweightlimit = 700},
|
||||
["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000},
|
||||
["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000},
|
||||
["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000},
|
||||
["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0},
|
||||
["Ka-50_3"] = {type="Ka-50_3", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0},
|
||||
["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700},
|
||||
["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700},
|
||||
["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers.
|
||||
@@ -1088,7 +1219,7 @@ CTLD.UnitTypes = {
|
||||
|
||||
--- CTLD class version.
|
||||
-- @field #string version
|
||||
CTLD.version="1.0.23"
|
||||
CTLD.version="1.0.32"
|
||||
|
||||
--- Instantiate a new CTLD.
|
||||
-- @param #CTLD self
|
||||
@@ -1171,6 +1302,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
||||
|
||||
-- radio beacons
|
||||
self.RadioSound = "beacon.ogg"
|
||||
self.RadioPath = "l10n/DEFAULT/"
|
||||
|
||||
-- zones stuff
|
||||
self.pickupZones = {}
|
||||
@@ -1199,6 +1331,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
||||
self.Engineers = 0 -- #number use as counter
|
||||
self.EngineersInField = {} -- #table holds #CTLD_ENGINEERING objects
|
||||
self.EngineerSearch = 2000 -- #number search distance for crates to build or repair
|
||||
self.nobuildmenu = false -- enfore engineer build only?
|
||||
|
||||
-- setup
|
||||
self.CrateDistance = 35 -- list/load crates in this radius
|
||||
@@ -1211,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
|
||||
@@ -1255,6 +1389,8 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
||||
|
||||
-- disallow building in loadzones
|
||||
self.nobuildinloadzones = true
|
||||
self.movecratesbeforebuild = true
|
||||
self.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER}
|
||||
|
||||
local AliaS = string.gsub(self.alias," ","_")
|
||||
self.filename = string.format("CTLD_%s_Persist.csv",AliaS)
|
||||
@@ -1664,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
|
||||
@@ -1689,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
|
||||
@@ -2087,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
|
||||
@@ -2187,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()
|
||||
@@ -2403,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
|
||||
@@ -2899,7 +3129,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering)
|
||||
-- get dropped crates
|
||||
for _,_crate in pairs(crates) do
|
||||
local Crate = _crate -- #CTLD_CARGO
|
||||
if Crate:WasDropped() and not Crate:IsRepair() and not Crate:IsStatic() then
|
||||
if (Crate:WasDropped() or not self.movecratesbeforebuild) and not Crate:IsRepair() and not Crate:IsStatic() then
|
||||
-- we can build these - maybe
|
||||
local name = Crate:GetName()
|
||||
local required = Crate:GetCratesNeeded()
|
||||
@@ -2944,7 +3174,12 @@ function CTLD:_BuildCrates(Group, Unit,Engineering)
|
||||
local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok)
|
||||
report:Add(text)
|
||||
end -- end list buildables
|
||||
if not foundbuilds then report:Add(" --- None Found ---") end
|
||||
if not foundbuilds then
|
||||
report:Add(" --- None found! ---")
|
||||
if self.movecratesbeforebuild then
|
||||
report:Add("*** Crates need to be moved before building!")
|
||||
end
|
||||
end
|
||||
report:Add("------------------------------------------------------------")
|
||||
local text = report:Text()
|
||||
if not Engineering then
|
||||
@@ -3209,6 +3444,12 @@ function CTLD:_RefreshF10Menus()
|
||||
self.subcats[entry.Subcategory] = entry.Subcategory
|
||||
end
|
||||
end
|
||||
for _id,_cargo in pairs(self.Cargo_Statics) do
|
||||
local entry = _cargo -- #CTLD_CARGO
|
||||
if not self.subcats[entry.Subcategory] then
|
||||
self.subcats[entry.Subcategory] = entry.Subcategory
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- build unit menus
|
||||
@@ -3272,6 +3513,13 @@ function CTLD:_RefreshF10Menus()
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry)
|
||||
end
|
||||
for _,_entry in pairs(self.Cargo_Statics) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
local subcat = entry.Subcategory
|
||||
menucount = menucount + 1
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry)
|
||||
end
|
||||
else
|
||||
for _,_entry in pairs(self.Cargo_Crates) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
@@ -3279,17 +3527,21 @@ function CTLD:_RefreshF10Menus()
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry)
|
||||
end
|
||||
end
|
||||
for _,_entry in pairs(self.Cargo_Statics) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
menucount = menucount + 1
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry)
|
||||
for _,_entry in pairs(self.Cargo_Statics) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
menucount = menucount + 1
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry)
|
||||
end
|
||||
end
|
||||
listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit)
|
||||
local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit)
|
||||
local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit)
|
||||
local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh()
|
||||
if not self.nobuildmenu then
|
||||
local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit)
|
||||
local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh()
|
||||
else
|
||||
unloadmenu:Refresh()
|
||||
end
|
||||
end
|
||||
if self:IsHercules(_unit) then
|
||||
local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh()
|
||||
@@ -3374,13 +3626,14 @@ end
|
||||
-- @param #string Name Unique name of this type of cargo as set in the mission editor (note: UNIT name!), e.g. "Ammunition-1".
|
||||
-- @param #number Mass Mass in kg of each static in kg, e.g. 100.
|
||||
-- @param #number Stock Number of groups in stock. Nil for unlimited.
|
||||
function CTLD:AddStaticsCargo(Name,Mass,Stock)
|
||||
-- @param #string SubCategory Name of sub-category (optional).
|
||||
function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory)
|
||||
self:T(self.lid .. " AddStaticsCargo")
|
||||
self.CargoCounter = self.CargoCounter + 1
|
||||
local type = CTLD_CARGO.Enum.STATIC
|
||||
local template = STATIC:FindByName(Name,true):GetTypeName()
|
||||
-- Crates are not directly loadable
|
||||
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock)
|
||||
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory)
|
||||
table.insert(self.Cargo_Statics,cargo)
|
||||
return self
|
||||
end
|
||||
@@ -3753,32 +4006,53 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped)
|
||||
end
|
||||
end
|
||||
local Sound = Sound or "beacon.ogg"
|
||||
if IsDropped and Zone then
|
||||
if Zone then
|
||||
if IsDropped then
|
||||
local ZoneCoord = Zone
|
||||
local ZoneVec3 = ZoneCoord:GetVec3(1)
|
||||
local Frequency = string.format("%09d",Mhz * 1000000) -- Freq in Hertz
|
||||
--local Frequency = Mhz*1000000
|
||||
local Sound = "l10n/DEFAULT/"..Sound
|
||||
--local name = string.format("%s-%f-%s",Zone:GetName(),Mhz,tostring(Modulation))
|
||||
--trigger.action.stopRadioTransmission(name)
|
||||
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, tonumber(Frequency), 1000) -- Beacon in MP only runs for 30secs straight
|
||||
--local status = string.format("***** Beacon added Freq %s Mod %s", Frequency, UTILS.GetModulationName(Modulation))
|
||||
--MESSAGE:New(status,10,"Debug"):ToLogIf(self.debug)
|
||||
elseif Zone then
|
||||
local ZoneCoord = Zone:GetCoordinate(1)
|
||||
local ZoneVec3 = ZoneCoord:GetVec3()
|
||||
local Frequency = string.format("%09d",Mhz * 1000000) -- Freq in Hertz
|
||||
--local Frequency = Mhz*1000000
|
||||
local Sound = "l10n/DEFAULT/"..Sound
|
||||
--local name = string.format("%s-%f-%s",Zone:GetName(),Mhz,tostring(Modulation))
|
||||
--trigger.action.stopRadioTransmission(name)
|
||||
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, tonumber(Frequency), 1000) -- Beacon in MP only runs for 30secs straight
|
||||
--local status = string.format("***** Beacon added Freq %s Mod %s", Frequency, UTILS.GetModulationName(Modulation))
|
||||
--MESSAGE:New(status,10,"Debug"):ToLogIf(self.debug)
|
||||
local ZoneVec3 = ZoneCoord:GetVec3() or {x=0,y=0,z=0}
|
||||
local Frequency = Mhz * 1000000 -- Freq in Hertz
|
||||
local Sound = self.RadioPath..Sound
|
||||
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000, Name..math.random(1,10000)) -- Beacon in MP only runs for 30secs straight
|
||||
self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = %d %d %d | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation))
|
||||
else
|
||||
local ZoneCoord = Zone:GetCoordinate()
|
||||
local ZoneVec3 = ZoneCoord:GetVec3() or {x=0,y=0,z=0}
|
||||
local Frequency = Mhz * 1000000 -- Freq in Hert
|
||||
local Sound = self.RadioPath..Sound
|
||||
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000, Name..math.random(1,10000)) -- Beacon in MP only runs for 30secs straightt
|
||||
self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = {x=%d, y=%d, z=%d} | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation))
|
||||
end
|
||||
else
|
||||
self:E(self.lid.."***** _AddRadioBeacon: Zone does not exist: "..Name)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set folder path where the CTLD sound files are located **within you mission (miz) file**.
|
||||
-- The default path is "l10n/DEFAULT/" but sound files simply copied there will be removed by DCS the next time you save the mission.
|
||||
-- However, if you create a new folder inside the miz file, which contains the sounds, it will not be deleted and can be used.
|
||||
-- @param #CTLD self
|
||||
-- @param #string FolderPath The path to the sound files, e.g. "CTLD_Soundfiles/".
|
||||
-- @return #CTLD self
|
||||
function CTLD:SetSoundfilesFolder( FolderPath )
|
||||
self:T(self.lid .. " SetSoundfilesFolder")
|
||||
-- Check that it ends with /
|
||||
if FolderPath then
|
||||
local lastchar = string.sub( FolderPath, -1 )
|
||||
if lastchar ~= "/" then
|
||||
FolderPath = FolderPath .. "/"
|
||||
end
|
||||
end
|
||||
|
||||
-- Folderpath.
|
||||
self.RadioPath = FolderPath
|
||||
|
||||
-- Info message.
|
||||
self:I( self.lid .. string.format( "Setting sound files folder to: %s", self.RadioPath ) )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function to refresh radio beacons
|
||||
-- @param #CTLD self
|
||||
function CTLD:_RefreshRadioBeacons()
|
||||
@@ -3801,11 +4075,7 @@ function CTLD:_RefreshRadioBeacons()
|
||||
local Name = czone.name
|
||||
local FM = FMbeacon.frequency -- MHz
|
||||
local VHF = VHFbeacon.frequency -- KHz
|
||||
local UHF = UHFbeacon.frequency -- MHz
|
||||
-- local co = coroutine.create(self._AddRadioBeacon)
|
||||
--coroutine.resume(co, self, Name,Sound,FM,CTLD.RadioModulation.FM, IsShip, IsDropped)
|
||||
--coroutine.resume(co, self, Name,Sound,VHF,CTLD.RadioModulation.FM, IsShip, IsDropped)
|
||||
--coroutine.resume(co, self, Name,Sound,UHF,CTLD.RadioModulation.AM, IsShip, IsDropped)
|
||||
local UHF = UHFbeacon.frequency -- MHz
|
||||
self:_AddRadioBeacon(Name,Sound,FM, CTLD.RadioModulation.FM, IsShip, IsDropped)
|
||||
self:_AddRadioBeacon(Name,Sound,VHF,CTLD.RadioModulation.FM, IsShip, IsDropped)
|
||||
self:_AddRadioBeacon(Name,Sound,UHF,CTLD.RadioModulation.AM, IsShip, IsDropped)
|
||||
@@ -4232,6 +4502,7 @@ end
|
||||
_troop:AddStock(number)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- User - function to add stock of a certain crates type
|
||||
@@ -4249,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
|
||||
@@ -4266,6 +4646,7 @@ end
|
||||
_troop:RemoveStock(number)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- User - function to remove stock of a certain crates type
|
||||
@@ -4286,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
|
||||
@@ -4320,6 +4719,8 @@ end
|
||||
-- @param #CTLD self
|
||||
-- @param Core.Zone#ZONE Zone The zone where to drop the troops.
|
||||
-- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn.
|
||||
-- @param #table Surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type!
|
||||
-- @param #boolean PreciseLocation (Optional) Don't try to get a random position in the zone but use the dead center. Caution not to stack up stuff on another!
|
||||
-- @return #CTLD self
|
||||
-- @usage Use this function to pre-populate the field with Troops or Engineers at a random coordinate in a zone:
|
||||
-- -- create a matching #CTLD_CARGO type
|
||||
@@ -4327,8 +4728,8 @@ end
|
||||
-- -- get a #ZONE object
|
||||
-- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE
|
||||
-- -- and go:
|
||||
-- my_ctld:InjectTroops(dropzone,InjectTroopsType)
|
||||
function CTLD:InjectTroops(Zone,Cargo)
|
||||
-- my_ctld:InjectTroops(dropzone,InjectTroopsType,{land.SurfaceType.LAND})
|
||||
function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation)
|
||||
self:T(self.lid.." InjectTroops")
|
||||
local cargo = Cargo -- #CTLD_CARGO
|
||||
|
||||
@@ -4360,8 +4761,10 @@ end
|
||||
local temptable = cargo:GetTemplates() or {}
|
||||
local factor = 1.5
|
||||
local zone = Zone
|
||||
|
||||
local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2()
|
||||
local randomcoord = zone:GetRandomCoordinate(10,30*factor,Surfacetypes):GetVec2()
|
||||
if PreciseLocation then
|
||||
randomcoord = zone:GetCoordinate():GetVec2()
|
||||
end
|
||||
for _,_template in pairs(temptable) do
|
||||
self.TroopCounter = self.TroopCounter + 1
|
||||
local alias = string.format("%s-%d", _template, math.random(1,100000))
|
||||
@@ -5063,7 +5466,7 @@ end
|
||||
self:InjectVehicles(dropzone,injectvehicle)
|
||||
elseif cargotype == CTLD_CARGO.Enum.TROOPS or cargotype == CTLD_CARGO.Enum.ENGINEERS then
|
||||
local injecttroops = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass)
|
||||
self:InjectTroops(dropzone,injecttroops)
|
||||
self:InjectTroops(dropzone,injecttroops,self.surfacetypes)
|
||||
end
|
||||
elseif (type(groupname) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then
|
||||
local cargotemplates = dataset[6]
|
||||
@@ -5117,7 +5520,7 @@ CTLD_HERCULES = {
|
||||
ClassName = "CTLD_HERCULES",
|
||||
lid = "",
|
||||
Name = "",
|
||||
Version = "0.0.2",
|
||||
Version = "0.0.3",
|
||||
}
|
||||
|
||||
--- Define cargo types.
|
||||
@@ -5413,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
|
||||
@@ -5435,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)
|
||||
@@ -5462,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
|
||||
|
||||
@@ -1061,8 +1113,8 @@ function UTILS.Vec2Norm(a)
|
||||
end
|
||||
|
||||
--- Calculate the distance between two 2D vectors.
|
||||
-- @param DCS#Vec2 a Vector in 3D with x, y components.
|
||||
-- @param DCS#Vec2 b Vector in 3D with x, y components.
|
||||
-- @param DCS#Vec2 a Vector in 2D with x, y components.
|
||||
-- @param DCS#Vec2 b Vector in 2D with x, y components.
|
||||
-- @return #number Distance between the vectors.
|
||||
function UTILS.VecDist2D(a, b)
|
||||
|
||||
@@ -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.
|
||||
@@ -1446,6 +1512,30 @@ function UTILS.GetCoalitionName(Coalition)
|
||||
|
||||
end
|
||||
|
||||
--- Get the enemy coalition for a given coalition.
|
||||
-- @param #number Coalition The coalition ID.
|
||||
-- @param #boolean Neutral Include neutral as enemy.
|
||||
-- @return #table Enemy coalition table.
|
||||
function UTILS.GetCoalitionEnemy(Coalition, Neutral)
|
||||
|
||||
local Coalitions={}
|
||||
if Coalition then
|
||||
if Coalition==coalition.side.RED then
|
||||
Coalitions={coalition.side.BLUE}
|
||||
elseif Coalition==coalition.side.BLUE then
|
||||
Coalitions={coalition.side.RED}
|
||||
elseif Coalition==coalition.side.NEUTRAL then
|
||||
Coalitions={coalition.side.RED, coalition.side.BLUE}
|
||||
end
|
||||
end
|
||||
|
||||
if Neutral then
|
||||
table.insert(Coalitions, coalition.side.NEUTRAL)
|
||||
end
|
||||
|
||||
return Coalitions
|
||||
end
|
||||
|
||||
--- Get the modulation name from its numerical value.
|
||||
-- @param #number Modulation The modulation enumerator number. Can be either 0 or 1.
|
||||
-- @return #string The modulation name, i.e. "AM"=0 or "FM"=1. Anything else will return "Unknown".
|
||||
@@ -1909,7 +1999,7 @@ function UTILS.GenerateVHFrequencies()
|
||||
705,720,722,730,735,740,745,750,770,795,
|
||||
822,830,862,866,
|
||||
905,907,920,935,942,950,995,
|
||||
1000,1025,1030,1050,1065,1116,1175,1182,1210
|
||||
1000,1025,1030,1050,1065,1116,1175,1182,1210,1215
|
||||
}
|
||||
|
||||
local FreeVHFFrequencies = {}
|
||||
@@ -1977,7 +2067,9 @@ function UTILS.GenerateUHFrequencies()
|
||||
local _start = 220000000
|
||||
|
||||
while _start < 399000000 do
|
||||
table.insert(FreeUHFFrequencies, _start)
|
||||
if _start ~= 243000000 then
|
||||
table.insert(FreeUHFFrequencies, _start)
|
||||
end
|
||||
_start = _start + 500000
|
||||
end
|
||||
|
||||
@@ -2177,10 +2269,29 @@ function UTILS.CheckFileExists(Path,Filename)
|
||||
end
|
||||
end
|
||||
|
||||
--- Function to obtain a table of typenames from the group given with the number of units of the same type in the group.
|
||||
-- @param Wrapper.Group#GROUP Group The group to list
|
||||
-- @return #table Table of typnames and typename counts, e.g. `{["KAMAZ Truck"]=3,["ATZ-5"]=1}`
|
||||
function UTILS.GetCountPerTypeName(Group)
|
||||
local units = Group:GetUnits()
|
||||
local TypeNameTable = {}
|
||||
for _,_unt in pairs (units) do
|
||||
local unit = _unt -- Wrapper.Unit#UNIT
|
||||
local typen = unit:GetTypeName()
|
||||
if not TypeNameTable[typen] then
|
||||
TypeNameTable[typen] = 1
|
||||
else
|
||||
TypeNameTable[typen] = TypeNameTable[typen] + 1
|
||||
end
|
||||
end
|
||||
return TypeNameTable
|
||||
end
|
||||
|
||||
--- Function to save the state of a list of groups found by name
|
||||
-- @param #table List Table of strings with groupnames
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @param #boolean Structured Append the data with a list of typenames in the group plus their count.
|
||||
-- @return #boolean outcome True if saving is successful, else false.
|
||||
-- @usage
|
||||
-- We will go through the list and find the corresponding group and save the current group size (0 when dead).
|
||||
@@ -2188,7 +2299,7 @@ end
|
||||
-- Position is still saved for your usage.
|
||||
-- The idea is to reduce the number of units when reloading the data again to restart the saved mission.
|
||||
-- The data will be a simple comma separated list of groupname and size, with one header line.
|
||||
function UTILS.SaveStationaryListOfGroups(List,Path,Filename)
|
||||
function UTILS.SaveStationaryListOfGroups(List,Path,Filename,Structured)
|
||||
local filename = Filename or "StateListofGroups"
|
||||
local data = "--Save Stationary List of Groups: "..Filename .."\n"
|
||||
for _,_group in pairs (List) do
|
||||
@@ -2196,7 +2307,16 @@ function UTILS.SaveStationaryListOfGroups(List,Path,Filename)
|
||||
if group and group:IsAlive() then
|
||||
local units = group:CountAliveUnits()
|
||||
local position = group:GetVec3()
|
||||
data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z)
|
||||
if Structured then
|
||||
local structure = UTILS.GetCountPerTypeName(group)
|
||||
local strucdata = ""
|
||||
for typen,anzahl in pairs (structure) do
|
||||
strucdata = strucdata .. typen .. "=="..anzahl..";"
|
||||
end
|
||||
data = string.format("%s%s,%d,%d,%d,%d,%s\n",data,_group,units,position.x,position.y,position.z,strucdata)
|
||||
else
|
||||
data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z)
|
||||
end
|
||||
else
|
||||
data = string.format("%s%s,0,0,0,0\n",data,_group)
|
||||
end
|
||||
@@ -2210,6 +2330,7 @@ end
|
||||
-- @param Core.Set#SET_BASE Set of objects to save
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @param #boolean Structured Append the data with a list of typenames in the group plus their count.
|
||||
-- @return #boolean outcome True if saving is successful, else false.
|
||||
-- @usage
|
||||
-- We will go through the set and find the corresponding group and save the current group size and current position.
|
||||
@@ -2219,7 +2340,7 @@ end
|
||||
-- **Note** Do NOT use dashes or hashes in group template names (-,#)!
|
||||
-- The data will be a simple comma separated list of groupname and size, with one header line.
|
||||
-- The current task/waypoint/etc cannot be restored.
|
||||
function UTILS.SaveSetOfGroups(Set,Path,Filename)
|
||||
function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
|
||||
local filename = Filename or "SetOfGroups"
|
||||
local data = "--Save SET of groups: "..Filename .."\n"
|
||||
local List = Set:GetSetObjects()
|
||||
@@ -2233,7 +2354,16 @@ function UTILS.SaveSetOfGroups(Set,Path,Filename)
|
||||
end
|
||||
local units = group:CountAliveUnits()
|
||||
local position = group:GetVec3()
|
||||
data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z)
|
||||
if Structured then
|
||||
local structure = UTILS.GetCountPerTypeName(group)
|
||||
local strucdata = ""
|
||||
for typen,anzahl in pairs (structure) do
|
||||
strucdata = strucdata .. typen .. "=="..anzahl..";"
|
||||
end
|
||||
data = string.format("%s%s,%s,%d,%d,%d,%d,%s\n",data,name,template,units,position.x,position.y,position.z,strucdata)
|
||||
else
|
||||
data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- save the data
|
||||
@@ -2297,8 +2427,41 @@ end
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @param #boolean Reduce If false, existing loaded groups will not be reduced to fit the saved number.
|
||||
-- @param #boolean Structured (Optional, needs Reduce = true) If true, and the data has been saved as structure before, remove the correct unit types as per the saved list.
|
||||
-- @param #boolean Cinematic (Optional, needs Structured = true) If true, place a fire/smoke effect on the dead static position.
|
||||
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
|
||||
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
|
||||
-- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read.
|
||||
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce)
|
||||
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
|
||||
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density)
|
||||
|
||||
local fires = {}
|
||||
|
||||
local function Smokers(name,coord,effect,density)
|
||||
local eff = math.random(8)
|
||||
if type(effect) == "number" then eff = effect end
|
||||
coord:BigSmokeAndFire(eff,density,name)
|
||||
table.insert(fires,name)
|
||||
end
|
||||
|
||||
local function Cruncher(group,typename,anzahl)
|
||||
local units = group:GetUnits()
|
||||
local reduced = 0
|
||||
for _,_unit in pairs (units) do
|
||||
local typo = _unit:GetTypeName()
|
||||
if typename == typo then
|
||||
if Cinematic then
|
||||
local coordinate = _unit:GetCoordinate()
|
||||
local name = _unit:GetName()
|
||||
Smokers(name,coordinate,Effect,Density)
|
||||
end
|
||||
_unit:Destroy(false)
|
||||
reduced = reduced + 1
|
||||
if reduced == anzahl then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local reduce = true
|
||||
if Reduce == false then reduce = false end
|
||||
local filename = Filename or "StateListofGroups"
|
||||
@@ -2315,18 +2478,48 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce)
|
||||
local posx = tonumber(dataset[3])
|
||||
local posy = tonumber(dataset[4])
|
||||
local posz = tonumber(dataset[5])
|
||||
local structure = dataset[6]
|
||||
--BASE:I({structure})
|
||||
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
|
||||
local data = { groupname=groupname, size=size, coordinate=coordinate, group=GROUP:FindByName(groupname) }
|
||||
if reduce then
|
||||
local actualgroup = GROUP:FindByName(groupname)
|
||||
if actualgroup and actualgroup:IsAlive() and actualgroup:CountAliveUnits() > size then
|
||||
local reduction = actualgroup:CountAliveUnits() - size
|
||||
BASE:I("Reducing groupsize by ".. reduction .. " units!")
|
||||
-- reduce existing group
|
||||
local units = actualgroup:GetUnits()
|
||||
local units2 = UTILS.ShuffleTable(units) -- randomize table
|
||||
for i=1,reduction do
|
||||
units2[i]:Destroy(false)
|
||||
if Structured and structure then
|
||||
--BASE:I("Reducing group structure!")
|
||||
local loadedstructure = {}
|
||||
local strcset = UTILS.Split(structure,";")
|
||||
for _,_data in pairs(strcset) do
|
||||
local datasplit = UTILS.Split(_data,"==")
|
||||
loadedstructure[datasplit[1]] = tonumber(datasplit[2])
|
||||
end
|
||||
--BASE:I({loadedstructure})
|
||||
local originalstructure = UTILS.GetCountPerTypeName(actualgroup)
|
||||
--BASE:I({originalstructure})
|
||||
for _name,_number in pairs(originalstructure) do
|
||||
local loadednumber = 0
|
||||
if loadedstructure[_name] then
|
||||
loadednumber = loadedstructure[_name]
|
||||
end
|
||||
local reduce = false
|
||||
if loadednumber < _number then reduce = true end
|
||||
|
||||
--BASE:I(string.format("Looking at: %s | Original number: %d | Loaded number: %d | Reduce: %s",_name,_number,loadednumber,tostring(reduce)))
|
||||
|
||||
if reduce then
|
||||
Cruncher(actualgroup,_name,_number-loadednumber)
|
||||
end
|
||||
|
||||
end
|
||||
else
|
||||
local reduction = actualgroup:CountAliveUnits() - size
|
||||
--BASE:I("Reducing groupsize by ".. reduction .. " units!")
|
||||
-- reduce existing group
|
||||
local units = actualgroup:GetUnits()
|
||||
local units2 = UTILS.ShuffleTable(units) -- randomize table
|
||||
for i=1,reduction do
|
||||
units2[i]:Destroy(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2335,22 +2528,121 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
return datatable
|
||||
return datatable,fires
|
||||
end
|
||||
|
||||
--- Load back a SET of groups from file.
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @param #boolean Spawn If set to false, do not re-spawn the groups loaded in location and reduce to size.
|
||||
-- @param #boolean Structured (Optional, needs Spawn=true)If true, and the data has been saved as structure before, remove the correct unit types as per the saved list.
|
||||
-- @param #boolean Cinematic (Optional, needs Structured=true) If true, place a fire/smoke effect on the dead static position.
|
||||
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
|
||||
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
|
||||
-- @return Core.Set#SET_GROUP Set of GROUP objects.
|
||||
-- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate, template=template }`
|
||||
function UTILS.LoadSetOfGroups(Path,Filename,Spawn)
|
||||
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
|
||||
function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density)
|
||||
|
||||
local fires = {}
|
||||
local usedtemplates = {}
|
||||
local spawn = true
|
||||
if Spawn == false then spawn = false end
|
||||
BASE:I("Spawn = "..tostring(spawn))
|
||||
local filename = Filename or "SetOfGroups"
|
||||
local setdata = SET_GROUP:New()
|
||||
local datatable = {}
|
||||
|
||||
local function Smokers(name,coord,effect,density)
|
||||
local eff = math.random(8)
|
||||
if type(effect) == "number" then eff = effect end
|
||||
coord:BigSmokeAndFire(eff,density,name)
|
||||
table.insert(fires,name)
|
||||
end
|
||||
|
||||
local function Cruncher(group,typename,anzahl)
|
||||
local units = group:GetUnits()
|
||||
local reduced = 0
|
||||
for _,_unit in pairs (units) do
|
||||
local typo = _unit:GetTypeName()
|
||||
if typename == typo then
|
||||
if Cinematic then
|
||||
local coordinate = _unit:GetCoordinate()
|
||||
local name = _unit:GetName()
|
||||
Smokers(name,coordinate,Effect,Density)
|
||||
end
|
||||
_unit:Destroy(false)
|
||||
reduced = reduced + 1
|
||||
if reduced == anzahl then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function PostSpawn(args)
|
||||
local spwndgrp = args[1]
|
||||
local size = args[2]
|
||||
local structure = args[3]
|
||||
|
||||
setdata:AddObject(spwndgrp)
|
||||
local actualsize = spwndgrp:CountAliveUnits()
|
||||
if actualsize > size then
|
||||
if Structured and structure then
|
||||
|
||||
local loadedstructure = {}
|
||||
local strcset = UTILS.Split(structure,";")
|
||||
for _,_data in pairs(strcset) do
|
||||
local datasplit = UTILS.Split(_data,"==")
|
||||
loadedstructure[datasplit[1]] = tonumber(datasplit[2])
|
||||
end
|
||||
|
||||
local originalstructure = UTILS.GetCountPerTypeName(spwndgrp)
|
||||
|
||||
for _name,_number in pairs(originalstructure) do
|
||||
local loadednumber = 0
|
||||
if loadedstructure[_name] then
|
||||
loadednumber = loadedstructure[_name]
|
||||
end
|
||||
local reduce = false
|
||||
if loadednumber < _number then reduce = true end
|
||||
|
||||
if reduce then
|
||||
Cruncher(spwndgrp,_name,_number-loadednumber)
|
||||
end
|
||||
|
||||
end
|
||||
else
|
||||
local reduction = actualsize-size
|
||||
-- reduce existing group
|
||||
local units = spwndgrp:GetUnits()
|
||||
local units2 = UTILS.ShuffleTable(units) -- randomize table
|
||||
for i=1,reduction do
|
||||
units2[i]:Destroy(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function MultiUse(Data)
|
||||
local template = Data.template
|
||||
if template and usedtemplates[template] and usedtemplates[template].used and usedtemplates[template].used > 1 then
|
||||
-- multispawn
|
||||
if not usedtemplates[template].done then
|
||||
local spwnd = 0
|
||||
local spawngrp = SPAWN:New(template)
|
||||
spawngrp:InitLimit(0,usedtemplates[template].used)
|
||||
for _,_entry in pairs(usedtemplates[template].data) do
|
||||
spwnd = spwnd + 1
|
||||
local sgrp=spawngrp:SpawnFromCoordinate(_entry.coordinate,spwnd)
|
||||
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
|
||||
end
|
||||
usedtemplates[template].done = true
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--BASE:I("Spawn = "..tostring(spawn))
|
||||
if UTILS.CheckFileExists(Path,filename) then
|
||||
local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename)
|
||||
-- remove header
|
||||
@@ -2364,36 +2656,37 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn)
|
||||
local posx = tonumber(dataset[4])
|
||||
local posy = tonumber(dataset[5])
|
||||
local posz = tonumber(dataset[6])
|
||||
local structure = dataset[7]
|
||||
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
|
||||
local group=nil
|
||||
local data = { groupname=groupname, size=size, coordinate=coordinate, template=template }
|
||||
table.insert(datatable,data)
|
||||
if spawn then
|
||||
local group = SPAWN:New(template)
|
||||
:InitDelayOff()
|
||||
:OnSpawnGroup(
|
||||
function(spwndgrp)
|
||||
setdata:AddObject(spwndgrp)
|
||||
local actualsize = spwndgrp:CountAliveUnits()
|
||||
if actualsize > size then
|
||||
local reduction = actualsize-size
|
||||
-- reduce existing group
|
||||
local units = spwndgrp:GetUnits()
|
||||
local units2 = UTILS.ShuffleTable(units) -- randomize table
|
||||
for i=1,reduction do
|
||||
units2[i]:Destroy(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
:SpawnFromCoordinate(coordinate)
|
||||
if size > 0 then
|
||||
local data = { groupname=groupname, size=size, coordinate=coordinate, template=template, structure=structure }
|
||||
table.insert(datatable,data)
|
||||
if usedtemplates[template] then
|
||||
usedtemplates[template].used = usedtemplates[template].used + 1
|
||||
table.insert(usedtemplates[template].data,data)
|
||||
else
|
||||
usedtemplates[template] = {
|
||||
data = {},
|
||||
used = 1,
|
||||
done = false,
|
||||
}
|
||||
table.insert(usedtemplates[template].data,data)
|
||||
end
|
||||
end
|
||||
end
|
||||
for _id,_entry in pairs (datatable) do
|
||||
if spawn and not MultiUse(_entry) and _entry.size > 0 then
|
||||
local group = SPAWN:New(_entry.template)
|
||||
local sgrp=group:SpawnFromCoordinate(_entry.coordinate)
|
||||
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
if spawn then
|
||||
return setdata
|
||||
return setdata,fires
|
||||
else
|
||||
return datatable
|
||||
end
|
||||
@@ -2412,12 +2705,7 @@ function UTILS.LoadSetOfStatics(Path,Filename)
|
||||
table.remove(loadeddata, 1)
|
||||
for _id,_entry in pairs (loadeddata) do
|
||||
local dataset = UTILS.Split(_entry,",")
|
||||
-- staticname,position.x,position.y,position.z
|
||||
local staticname = dataset[1]
|
||||
--local posx = tonumber(dataset[2])
|
||||
--local posy = tonumber(dataset[3])
|
||||
--local posz = tonumber(dataset[4])
|
||||
--local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
|
||||
local StaticObject = STATIC:FindByName(staticname,false)
|
||||
if StaticObject then
|
||||
datatable:AddObject(StaticObject)
|
||||
@@ -2433,9 +2721,15 @@ end
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @param #boolean Reduce If false, do not destroy the units with size=0.
|
||||
-- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object.
|
||||
-- @param #boolean Dead (Optional, needs Reduce = true) If Dead is true, re-spawn the dead object as dead and do not just delete it.
|
||||
-- @param #boolean Cinematic (Optional, needs Dead = true) If true, place a fire/smoke effect on the dead static position.
|
||||
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
|
||||
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
|
||||
-- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object. Dead objects will have coordinate points `{x=0,y=0,z=0}`
|
||||
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
|
||||
-- Returns nil when file cannot be read.
|
||||
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce)
|
||||
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density)
|
||||
local fires = {}
|
||||
local reduce = true
|
||||
if Reduce == false then reduce = false end
|
||||
local filename = Filename or "StateListofStatics"
|
||||
@@ -2458,14 +2752,31 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce)
|
||||
if size==0 and reduce then
|
||||
local static = STATIC:FindByName(staticname,false)
|
||||
if static then
|
||||
static:Destroy(false)
|
||||
if Dead then
|
||||
local deadobject = SPAWNSTATIC:NewFromStatic(staticname,static:GetCountry())
|
||||
deadobject:InitDead(true)
|
||||
local heading = static:GetHeading()
|
||||
local coord = static:GetCoordinate()
|
||||
static:Destroy(false)
|
||||
deadobject:SpawnFromCoordinate(coord,heading,staticname)
|
||||
if Cinematic then
|
||||
local effect = math.random(8)
|
||||
if type(Effect) == "number" then
|
||||
effect = Effect
|
||||
end
|
||||
coord:BigSmokeAndFire(effect,Density,staticname)
|
||||
table.insert(fires,staticname)
|
||||
end
|
||||
else
|
||||
static:Destroy(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
return datatable
|
||||
return datatable,fires
|
||||
end
|
||||
|
||||
--- Heading Degrees (0-360) to Cardinal
|
||||
@@ -2490,7 +2801,7 @@ end
|
||||
-- @return #string Formatted BRAA NATO call
|
||||
function UTILS.ToStringBRAANATO(FromGrp,ToGrp)
|
||||
local BRAANATO = "Merged."
|
||||
local GroupNumber = FromGrp:GetSize()
|
||||
local GroupNumber = ToGrp:GetSize()
|
||||
local GroupWords = "Singleton"
|
||||
if GroupNumber == 2 then GroupWords = "Two-Ship"
|
||||
elseif GroupNumber >= 3 then GroupWords = "Heavy"
|
||||
@@ -2514,3 +2825,93 @@ function UTILS.ToStringBRAANATO(FromGrp,ToGrp)
|
||||
end
|
||||
return BRAANATO
|
||||
end
|
||||
|
||||
--- Check if an object is contained in a table.
|
||||
-- @param #table Table The table.
|
||||
-- @param #table Object The object to check.
|
||||
-- @param #string Key (Optional) Key to check. By default, the object itself is checked.
|
||||
-- @return #booolen Returns `true` if object is in table.
|
||||
function UTILS.IsInTable(Table, Object, Key)
|
||||
|
||||
for key, object in pairs(Table) do
|
||||
if Key then
|
||||
if Object[Key]==object[Key] then
|
||||
return true
|
||||
end
|
||||
else
|
||||
if object==Object then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if any object of multiple given objects is contained in a table.
|
||||
-- @param #table Table The table.
|
||||
-- @param #table Objects The objects to check.
|
||||
-- @param #string Key (Optional) Key to check.
|
||||
-- @return #booolen Returns `true` if object is in table.
|
||||
function UTILS.IsAnyInTable(Table, Objects, Key)
|
||||
|
||||
for _,Object in pairs(UTILS.EnsureTable(Objects)) do
|
||||
|
||||
for key, object in pairs(Table) do
|
||||
if Key then
|
||||
if Object[Key]==object[Key] then
|
||||
return true
|
||||
end
|
||||
else
|
||||
if object==Object then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
@@ -246,7 +246,7 @@ AIRBASE.Normandy = {
|
||||
--
|
||||
-- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport
|
||||
-- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport
|
||||
-- * AIRBASE.PersianGulf.Al-Bateen_Airport
|
||||
-- * AIRBASE.PersianGulf.Al_Bateen_Airport
|
||||
-- * AIRBASE.PersianGulf.Al_Ain_International_Airport
|
||||
-- * AIRBASE.PersianGulf.Al_Dhafra_AB
|
||||
-- * AIRBASE.PersianGulf.Al_Maktoum_Intl
|
||||
@@ -265,7 +265,7 @@ AIRBASE.Normandy = {
|
||||
-- * AIRBASE.PersianGulf.Lavan_Island_Airport
|
||||
-- * AIRBASE.PersianGulf.Liwa_Airbase
|
||||
-- * AIRBASE.PersianGulf.Qeshm_Island
|
||||
-- * AIRBASE.PersianGulf.Ras_Al_Khaimah_International_Airport
|
||||
-- * AIRBASE.PersianGulf.Ras_Al_Khaimah
|
||||
-- * AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport
|
||||
-- * AIRBASE.PersianGulf.Sharjah_Intl
|
||||
-- * AIRBASE.PersianGulf.Shiraz_International_Airport
|
||||
@@ -510,7 +510,11 @@ AIRBASE.MarianaIslands = {
|
||||
-- * AIRBASE.SouthAtlantic.Porvenir_Airfield
|
||||
-- * AIRBASE.SouthAtlantic.Almirante_Schroeders
|
||||
-- * AIRBASE.SouthAtlantic.Rio_Turbio
|
||||
-- * AIRBASE.SouthAtlantic.Rio_Chico_Airfield
|
||||
-- * AIRBASE.SouthAtlantic.Rio_Chico
|
||||
-- * AIRBASE.SouthAtlantic.Franco_Bianco
|
||||
-- * AIRBASE.SouthAtlantic.Goose_Green
|
||||
-- * AIRBASE.SouthAtlantic.Hipico
|
||||
-- * AIRBASE.SouthAtlantic.CaletaTortel
|
||||
--
|
||||
--@field MarianaIslands
|
||||
AIRBASE.SouthAtlantic={
|
||||
@@ -534,6 +538,14 @@ AIRBASE.SouthAtlantic={
|
||||
["Almirante_Schroeders"]="Almirante Schroeders",
|
||||
["Rio_Turbio"]="Rio Turbio",
|
||||
["Rio_Chico"] = "Rio Chico",
|
||||
["Franco_Bianco"] = "Franco Bianco",
|
||||
["Goose_Green"] = "Goose Green",
|
||||
["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
|
||||
|
||||
@@ -67,7 +67,6 @@
|
||||
-- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point.
|
||||
-- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point.
|
||||
-- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone.
|
||||
-- * @{#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the controllable to an airbase.
|
||||
--
|
||||
-- ## 2.2) EnRoute assignment
|
||||
--
|
||||
@@ -669,12 +668,61 @@ function CONTROLLABLE:CommandActivateBeacon( Type, System, Frequency, UnitID, Ch
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activate ACLS system of the CONTROLLABLE. The controllable should be an aircraft carrier! Also needs Link4 to work.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number UnitID (Optional) The DCS UNIT ID of the unit the ACLS system is attached to. Defaults to the UNIT itself.
|
||||
-- @param #string Name (Optional) Name of the ACLS Beacon
|
||||
-- @param #number Delay (Optional) Delay in seconds before the ICLS is activated.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:CommandActivateACLS( UnitID, Name, Delay )
|
||||
|
||||
-- Command to activate ACLS system.
|
||||
local CommandActivateACLS= {
|
||||
id = 'ActivateACLS',
|
||||
params = {
|
||||
unitId = UnitID or self:GetID(),
|
||||
name = Name or "ACL",
|
||||
}
|
||||
}
|
||||
|
||||
self:T({CommandActivateACLS})
|
||||
|
||||
if Delay and Delay > 0 then
|
||||
SCHEDULER:New( nil, self.CommandActivateACLS, { self, UnitID, Name }, Delay )
|
||||
else
|
||||
self:SetCommand( CommandActivateACLS )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Deactivate ACLS system of the CONTROLLABLE. The controllable should be an aircraft carrier!
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:CommandDeactivateACLS( Delay )
|
||||
|
||||
-- Command to activate ACLS system.
|
||||
local CommandDeactivateACLS= {
|
||||
id = 'DeactivateACLS',
|
||||
params = { }
|
||||
}
|
||||
|
||||
if Delay and Delay > 0 then
|
||||
SCHEDULER:New( nil, self.CommandDeactivateACLS, { self }, Delay )
|
||||
else
|
||||
self:SetCommand( CommandDeactivateACLS )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activate ICLS system of the CONTROLLABLE. The controllable should be an aircraft carrier!
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number Channel ICLS channel.
|
||||
-- @param #number UnitID The DCS UNIT ID of the unit the ICLS system is attached to. Useful if more units are in one group.
|
||||
-- @param #string Callsign Morse code identification callsign.
|
||||
-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated.
|
||||
-- @param #number Delay (Optional) Delay in seconds before the ICLS is activated.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:CommandActivateICLS( Channel, UnitID, Callsign, Delay )
|
||||
|
||||
@@ -684,13 +732,13 @@ function CONTROLLABLE:CommandActivateICLS( Channel, UnitID, Callsign, Delay )
|
||||
params = {
|
||||
["type"] = BEACON.Type.ICLS,
|
||||
["channel"] = Channel,
|
||||
["unitId"] = UnitID,
|
||||
["unitId"] = UnitID or self:GetID(),
|
||||
["callsign"] = Callsign,
|
||||
},
|
||||
}
|
||||
|
||||
if Delay and Delay > 0 then
|
||||
SCHEDULER:New( nil, self.CommandActivateICLS, { self }, Delay )
|
||||
SCHEDULER:New( nil, self.CommandActivateICLS, { self, Channel, UnitID, Callsign }, Delay )
|
||||
else
|
||||
self:SetCommand( CommandActivateICLS )
|
||||
end
|
||||
@@ -700,53 +748,29 @@ end
|
||||
|
||||
--- Activate LINK4 system of the CONTROLLABLE. The controllable should be an aircraft carrier!
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number Frequency Link4 Frequency in MHz, e.g. 336
|
||||
-- @param #number UnitID The DCS UNIT ID of the unit the LINK4 system is attached to. Useful if more units are in one group.
|
||||
-- @param #string Callsign Morse code identification callsign.
|
||||
-- @param #number Delay (Optional) Delay in seconds before the LINK4 is deactivated.
|
||||
-- @param #number Frequency Link4 Frequency in MHz, e.g. 336 (defaults to 336 MHz)
|
||||
-- @param #number UnitID (Optional) The DCS UNIT ID of the unit the LINK4 system is attached to. Defaults to the UNIT itself.
|
||||
-- @param #string Callsign (Optional) Morse code identification callsign.
|
||||
-- @param #number Delay (Optional) Delay in seconds before the LINK4 is activated.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay)
|
||||
|
||||
|
||||
local freq = Frequency or 336
|
||||
|
||||
-- Command to activate Link4 system.
|
||||
local CommandActivateLink4= {
|
||||
id = "ActivateLink4",
|
||||
params= {
|
||||
["frequency "] = Frequency*1000,
|
||||
["unitId"] = UnitID,
|
||||
["name"] = Callsign,
|
||||
["frequency "] = freq*1000000,
|
||||
["unitId"] = UnitID or self:GetID(),
|
||||
["name"] = Callsign or "LNK",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
self:T({CommandActivateLink4})
|
||||
|
||||
if Delay and Delay>0 then
|
||||
SCHEDULER:New(nil, self.CommandActivateLink4, {self}, Delay)
|
||||
else
|
||||
self:SetCommand(CommandActivateLink4)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activate LINK4 system of the CONTROLLABLE. The controllable should be an aircraft carrier!
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number Frequency Link4 Frequency in MHz, e.g. 336
|
||||
-- @param #number UnitID The DCS UNIT ID of the unit the LINK4 system is attached to. Useful if more units are in one group.
|
||||
-- @param #string Callsign Morse code identification callsign.
|
||||
-- @param #number Delay (Optional) Delay in seconds before the LINK4 is deactivated.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay)
|
||||
|
||||
-- Command to activate Link4 system.
|
||||
local CommandActivateLink4= {
|
||||
id = "ActivateLink4",
|
||||
params= {
|
||||
["frequency "] = Frequency*1000,
|
||||
["unitId"] = UnitID,
|
||||
["name"] = Callsign,
|
||||
}
|
||||
}
|
||||
|
||||
if Delay and Delay>0 then
|
||||
SCHEDULER:New(nil, self.CommandActivateLink4, {self}, Delay)
|
||||
SCHEDULER:New(nil, self.CommandActivateLink4, {self, Frequency, UnitID, Callsign}, Delay)
|
||||
else
|
||||
self:SetCommand(CommandActivateLink4)
|
||||
end
|
||||
@@ -810,24 +834,6 @@ function CONTROLLABLE:CommandDeactivateICLS( Delay )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Deactivate the active Link4 of the CONTROLLABLE.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number Delay (Optional) Delay in seconds before the Link4 is deactivated.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:CommandDeactivateLink4(Delay)
|
||||
|
||||
-- Command to deactivate
|
||||
local CommandDeactivateLink4={id='DeactivateLink4', params={}}
|
||||
|
||||
if Delay and Delay>0 then
|
||||
SCHEDULER:New(nil, self.CommandDeactivateLink4, {self}, Delay)
|
||||
else
|
||||
self:SetCommand(CommandDeactivateLink4)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set callsign of the CONTROLLABLE. See [DCS command setCallsign](https://wiki.hoggitworld.com/view/DCS_command_setCallsign)
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param DCS#CALLSIGN CallName Number corresponding the the callsign identifier you wish this group to be called.
|
||||
@@ -1416,7 +1422,7 @@ end
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param Core.Zone#ZONE Zone The zone where to land.
|
||||
-- @param #number Duration The duration in seconds to stay on the ground.
|
||||
-- @return #CONTROLLABLE self
|
||||
-- @return DCS#Task The DCS task structure.
|
||||
function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint )
|
||||
|
||||
-- Get landing point
|
||||
@@ -1665,6 +1671,26 @@ function CONTROLLABLE:EnRouteTaskAntiShip(TargetTypes, Priority)
|
||||
return DCSTask
|
||||
end
|
||||
|
||||
--- (AIR) Enroute SEAD task.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param DCS#AttributeNameArray TargetTypes Array of target categories allowed to engage. Default `{"Air Defence"}`.
|
||||
-- @param #number Priority (Optional) All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. Default 0.
|
||||
-- @return DCS#Task The DCS task structure.
|
||||
function CONTROLLABLE:EnRouteTaskSEAD(TargetTypes, Priority)
|
||||
|
||||
local DCSTask = {
|
||||
id = 'EngageTargets',
|
||||
key = "SEAD",
|
||||
--auto = false,
|
||||
--enabled = true,
|
||||
params = {
|
||||
targetTypes = TargetTypes or {"Air Defence"},
|
||||
priority = Priority or 0
|
||||
}
|
||||
}
|
||||
|
||||
return DCSTask
|
||||
end
|
||||
|
||||
--- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets.
|
||||
-- @param #CONTROLLABLE self
|
||||
@@ -3954,4 +3980,4 @@ function CONTROLLABLE:SetAltitude(Altitude, Keep, AltType)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -624,10 +624,9 @@ function GROUP:GetRange()
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Returns a list of @{Wrapper.Unit} objects of the @{Wrapper.Group}.
|
||||
-- @param #GROUP self
|
||||
-- @return #list<Wrapper.Unit#UNIT> The list of @{Wrapper.Unit} objects of the @{Wrapper.Group}.
|
||||
-- @return #table of Wrapper.Unit#UNIT objects, indexed by number.
|
||||
function GROUP:GetUnits()
|
||||
self:F2( { self.GroupName } )
|
||||
local DCSGroup = self:GetDCSObject()
|
||||
@@ -645,7 +644,6 @@ function GROUP:GetUnits()
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Returns a list of @{Wrapper.Unit} objects of the @{Wrapper.Group} that are occupied by a player.
|
||||
-- @param #GROUP self
|
||||
-- @return #list<Wrapper.Unit#UNIT> The list of player occupied @{Wrapper.Unit} objects of the @{Wrapper.Group}.
|
||||
@@ -676,41 +674,38 @@ function GROUP:IsPlayer()
|
||||
return self:GetUnit(1):IsPlayer()
|
||||
end
|
||||
|
||||
--- Returns the UNIT wrapper class with number UnitNumber.
|
||||
-- If the underlying DCS Unit does not exist, the method will return nil. .
|
||||
--- Returns the UNIT wrapper object with number UnitNumber. If it doesn't exist, tries to return the next available unit.
|
||||
-- If no underlying DCS Units exist, the method will return nil.
|
||||
-- @param #GROUP self
|
||||
-- @param #number UnitNumber The number of the UNIT wrapper class to be returned.
|
||||
-- @return Wrapper.Unit#UNIT The UNIT wrapper class.
|
||||
-- @return Wrapper.Unit#UNIT The UNIT object or nil
|
||||
function GROUP:GetUnit( UnitNumber )
|
||||
|
||||
local DCSGroup = self:GetDCSObject()
|
||||
|
||||
if DCSGroup then
|
||||
|
||||
if DCSGroup then
|
||||
local UnitFound = nil
|
||||
-- 2.7.1 dead event bug, return the first alive unit instead
|
||||
local units = DCSGroup:getUnits() or {}
|
||||
|
||||
for _,_unit in pairs(units) do
|
||||
|
||||
local UnitFound = UNIT:Find(_unit)
|
||||
|
||||
-- Maybe fixed with 2.8?
|
||||
local units = DCSGroup:getUnits() or {}
|
||||
if units[UnitNumber] then
|
||||
local UnitFound = UNIT:Find(units[UnitNumber])
|
||||
if UnitFound then
|
||||
|
||||
return UnitFound
|
||||
|
||||
end
|
||||
else
|
||||
for _,_unit in pairs(units) do
|
||||
local UnitFound = UNIT:Find(_unit)
|
||||
if UnitFound then
|
||||
return UnitFound
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return nil
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Returns the DCS Unit with number UnitNumber.
|
||||
-- If the underlying DCS Unit does not exist, the method will return nil. .
|
||||
-- If the underlying DCS Unit does not exist, the method will return try to find the next unit. Returns nil if no units are found.
|
||||
-- @param #GROUP self
|
||||
-- @param #number UnitNumber The number of the DCS Unit to be returned.
|
||||
-- @return DCS#Unit The DCS Unit.
|
||||
@@ -723,8 +718,7 @@ function GROUP:GetDCSUnit( UnitNumber )
|
||||
if DCSGroup.getUnit and DCSGroup:getUnit( UnitNumber ) then
|
||||
return DCSGroup:getUnit( UnitNumber )
|
||||
else
|
||||
|
||||
local UnitFound = nil
|
||||
|
||||
-- 2.7.1 dead event bug, return the first alive unit instead
|
||||
local units = DCSGroup:getUnits() or {}
|
||||
|
||||
@@ -803,7 +797,20 @@ function GROUP:GetFirstUnitAlive()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get the first unit of the group. Might be nil!
|
||||
-- @param #GROUP self
|
||||
-- @return Wrapper.Unit#UNIT First unit or nil if it does not exist.
|
||||
function GROUP:GetFirstUnit()
|
||||
self:F3({self.GroupName})
|
||||
local DCSGroup = self:GetDCSObject()
|
||||
|
||||
if DCSGroup then
|
||||
local units=self:GetUnits()
|
||||
return units[1]
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the average velocity Vec3 vector.
|
||||
-- @param Wrapper.Group#GROUP self
|
||||
@@ -1646,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.
|
||||
@@ -1661,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
|
||||
@@ -2778,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',
|
||||
@@ -2790,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")
|
||||
|
||||
@@ -2803,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
|
||||
@@ -2823,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
|
||||
|
||||
@@ -58,18 +58,16 @@
|
||||
-- If the maker should be visible to a specific coalition, you can use the :ToCoalition() function.
|
||||
--
|
||||
-- mymarker = MARKER:New( Coordinate , "I am Batumi Airfield" ):ToCoalition( coalition.side.BLUE )
|
||||
--
|
||||
-- ### To Blue Coalition
|
||||
--
|
||||
-- ### To Red Coalition
|
||||
--
|
||||
--
|
||||
-- This would show the marker only to the Blue coalition.
|
||||
--
|
||||
-- ## For a Group
|
||||
--
|
||||
-- mymarker = MARKER:New( Coordinate , "Target Location" ):ToGroup( tankGroup )
|
||||
--
|
||||
-- # Removing a Marker
|
||||
--
|
||||
-- mymarker:Remove(60)
|
||||
-- This removes the marker after 60 seconds
|
||||
--
|
||||
-- # Updating a Marker
|
||||
--
|
||||
@@ -175,8 +173,6 @@ function MARKER:New( Coordinate, Text )
|
||||
-- Inherit everything from FSM class.
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #MARKER
|
||||
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #MARKER
|
||||
|
||||
self.coordinate=UTILS.DeepCopy(Coordinate)
|
||||
|
||||
self.text = Text
|
||||
@@ -307,7 +303,7 @@ end
|
||||
-- User API Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Marker is readonly. Text cannot be changed and marker cannot be removed.
|
||||
--- Marker is readonly. Text cannot be changed and marker cannot be removed. The will not update the marker in the game, Call MARKER:Refresh to update state.
|
||||
-- @param #MARKER self
|
||||
-- @return #MARKER self
|
||||
function MARKER:ReadOnly()
|
||||
@@ -317,7 +313,7 @@ function MARKER:ReadOnly()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Marker is readonly. Text cannot be changed and marker cannot be removed.
|
||||
--- Marker is read and write. Text cannot be changed and marker cannot be removed. The will not update the marker in the game, Call MARKER:Refresh to update state.
|
||||
-- @param #MARKER self
|
||||
-- @return #MARKER self
|
||||
function MARKER:ReadWrite()
|
||||
|
||||
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".
|
||||
@@ -225,3 +229,10 @@ function SCENERY:FindAllByZoneName( ZoneName )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- SCENERY objects cannot be destroyed via the API (at the punishment of game crash).
|
||||
--@param #SCENERY self
|
||||
--@return #SCENERY self
|
||||
function SCENERY:Destroy()
|
||||
return self
|
||||
end
|
||||
@@ -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 )
|
||||
@@ -229,7 +229,7 @@ function UNIT:ReSpawnAt( Coordinate, Heading )
|
||||
SpawnGroupTemplate.y = Coordinate.z
|
||||
|
||||
self:F( #SpawnGroupTemplate.units )
|
||||
for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do
|
||||
for UnitID, UnitData in pairs( SpawnGroup:GetUnits() or {} ) do
|
||||
local GroupUnit = UnitData -- #UNIT
|
||||
self:F( GroupUnit:GetName() )
|
||||
if GroupUnit:IsAlive() then
|
||||
@@ -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