Compare commits

..

62 Commits

Author SHA1 Message Date
Applevangelist
8af3f89c14 Adjustments for Forrestal by Pene 2021-10-24 14:35:55 +02:00
Applevangelist
fe3079caad Added Bell-47 2021-10-22 17:04:23 +02:00
Applevangelist
61ac6b4131 Added Bell-47 2021-10-22 17:04:19 +02:00
Frank
36cb189512 Merge pull request #1612 from FlightControl-Master/FF/MasterDevel
AIRBOSS v1.2.0
2021-10-20 19:55:19 +02:00
Frank
15f9843878 AIRBOSS v1.2.0
- Added Forrestal carrier CV-59
2021-10-16 12:11:34 +02:00
Frank
67f847dd16 Update Group.lua
- Fixed SetInvisible and SetImmortal functions to acknowledge parameter false.
2021-10-12 22:16:18 +02:00
Applevangelist
8b9143d3f1 CTLD - added option to force opening of doors 2021-10-12 08:32:34 +02:00
Applevangelist
0388d47f23 CSAR - Added country options for spawned pilots 2021-10-12 08:31:55 +02:00
Applevangelist
de9b173d9b UTILS - added door check for Hercules 2021-10-07 18:14:29 +02:00
Applevangelist
2cecc526fb ZONE_CAPTURE_COALITION - fixed an issue when monitoring hits and SCENERY was delivered as hit UNIT 2021-10-05 19:10:21 +02:00
Applevangelist
968d178317 Update README.md 2021-10-02 10:18:13 +02:00
Applevangelist
3c477b872a CSAR - hovering rescued parameters added 2021-10-01 14:54:31 +02:00
Applevangelist
77e6088114 UTILS - corrected open door check MI-8 2021-10-01 14:54:16 +02:00
Applevangelist
edd6594953 CTLD: added user-friendly function to inject static cargos: CTLD:InjectStaticFromTemplate(Zone, Template, Mass) 2021-10-01 10:43:17 +02:00
Applevangelist
f8c05c99d0 RADIO - delete frequency check 2021-09-30 08:07:34 +02:00
Applevangelist
50f6d98b49 push for a new build 2021-09-29 09:25:26 +02:00
Frank
147eeb05f6 Merge pull request #1607 from FlightControl-Master/FF/MasterDevel
DATABASE
2021-09-29 09:22:41 +02:00
Frank
d8cb15a577 Update Airbase.lua 2021-09-29 09:01:47 +02:00
Frank
0daac876ea Update Airbase.lua
- Register oil rigs and gas platforms as helipads. DCS bug registers them as ship (Airbase.Category.SHIP instead of Airbase.Category.HELIPAD).
2021-09-29 09:00:53 +02:00
Frank
1832125022 Globals
- Moved _DATABASE:_RegisterAirbases() to Globals.lua
2021-09-29 08:58:46 +02:00
Applevangelist
c311c40b72 GROUP:GetAmmunition() - fix to also return bomb count (#1606)
GROUP:GetAmmunition() - fix to also return bomb count
2021-09-28 16:53:53 +02:00
Applevangelist
db516a2077 Fix "local" error 2021-09-27 15:48:45 +02:00
Applevangelist
ff8766669c Small fix for Airbase Parking Spot Finder 2021-09-26 09:51:14 +02:00
Applevangelist
06dc9a732e bugfix 2021-09-24 18:37:13 +02:00
Applevangelist
50c74d0852 Added option for slingload: enableslingload 2021-09-24 11:08:23 +02:00
Applevangelist
1c97eb6f3c SPAWNSTATIC - bugfix on canCargo, mass could be set but not transported into the template spawn 2021-09-24 11:04:54 +02:00
Applevangelist
69449430d1 CTLD - Added Statics as cargo (#1600)
CTLD - Added Statics as cargo, and the ability to load and save them (alongside your dropped buildable crates).
2021-09-22 15:54:37 +02:00
Applevangelist
663cd34aa3 CTLD - fix when using SAVE or LOAD w/o filename and path 2021-09-21 07:48:09 +02:00
Applevangelist
cfed6f5153 SET - Added SET_CLIENT:CountAlive() 2021-09-21 07:47:43 +02:00
Applevangelist
2b22d5288c CTLD - added persistence 2021-09-20 14:27:45 +02:00
Applevangelist
a64424ecc8 Positionable - Add IsSubmarine, Passenger seats for VAB Mephisto 2021-09-20 14:27:22 +02:00
Applevangelist
fd1b2ecb86 ZONE - Docu bug fix 2021-09-20 14:26:42 +02:00
Applevangelist
6cae3e62cf CTLD - small bug fix on stock removal 2021-09-12 17:37:32 +02:00
Applevangelist
05ce7e4513 CTLD - added alternative crate spawn by @mousepilot. Add menu item to list stock.Injected troops will not lead to cargo type duplication. 2021-09-11 15:20:20 +02:00
Applevangelist
136bd19f19 Bug fixing 2021-09-11 10:03:49 +02:00
Applevangelist
8873504daf CTLD: Align to Dev changes 2021-09-07 19:52:14 +02:00
Applevangelist
a844a5d697 CSAR: Align to Dev changes 2021-09-07 19:52:13 +02:00
Applevangelist
a49f4eaa21 Merge pull request #1597 from Penecruz/Airboss-V/Stol
Airboss v/stol
2021-09-06 07:12:20 +02:00
Penecruz
e6e2651f8c Bug fix to AV-8B grading WIP 2021-09-06 11:08:29 +10:00
Penecruz
b93ba13644 bug fix to V/Stol groove. 2021-09-06 08:20:37 +10:00
Frank
e4a51951b0 Merge pull request #1596 from Penecruz/Airboss-V/Stol
Airboss v/stol
2021-09-04 15:20:03 +02:00
Penecruz
ad56e39942 Docs AV-8B clarifications 2021-09-04 13:40:10 +10:00
Penecruz
ea09dc5a6e Vstol groove timing 2021-09-04 11:25:44 +10:00
Penecruz
8ecfd913a3 Av-8B specific deviation counts adj. 2021-09-04 11:25:25 +10:00
Penecruz
53367c786e AV-8B LIG and Unicorn fix 2021-09-04 11:24:47 +10:00
Applevangelist
db5797bb4e Bug fixing 2021-09-02 18:48:40 +02:00
Applevangelist
5e8fe97752 CTLD Added method to inject troops into the field. 2021-09-01 13:34:13 +02:00
Applevangelist
393fa0bfbb MANTIS - Changes from the dev branch merged 2021-08-28 14:01:37 +02:00
Applevangelist
4f51884b9d SEAD - make padding a variable (radar switch-back-on time) 2021-08-28 14:01:37 +02:00
Frank
4c5c320073 Merge pull request #1594 from Penecruz/Pene-LHA-and-LHD-edits
Pene lha and lhd edits
2021-08-28 10:52:12 +02:00
Penecruz
9098590568 Sound Pack Gabriella add 2021-08-28 14:51:30 +10:00
Penecruz
555bb7e68b Update instructions for AV-8B Harrier 2021-08-28 14:27:32 +10:00
Penecruz
c0a18957f0 AoA for harrier and JC Spot 5 timings 2021-08-28 13:31:16 +10:00
Penecruz
2cf939560e Allow for JC Spot 5 Voice over 2021-08-28 11:02:05 +10:00
Penecruz
9d3a7aae78 Add Landing Spot 5 to JC 2021-08-28 10:26:49 +10:00
Applevangelist
f6ed592f92 CSAR - remove noise 2021-08-27 18:47:13 +02:00
Applevangelist
c98757d13c CTLD - added ENGINEERING 2021-08-27 18:46:59 +02:00
Applevangelist
17378f509e SEAD - Code cleanup and enabled delayed switch off 2021-08-27 18:46:45 +02:00
Applevangelist
7f18ea0e7a UNIT/GROUP - added function to get the skill of a unit. SEAD - added functionality to calculate time-2-impact of HARMS and adjust behaviour accordingly 2021-08-27 14:56:16 +02:00
Frank
5172619cb1 Merge pull request #1593 from Penecruz/Pene-LHA-and-LHD-edits
Pene-LHA-LHD-additions
2021-08-26 08:22:11 +02:00
Penecruz
3962529698 Update Airboss.lua 2021-08-26 09:33:40 +10:00
Penecruz
6481d5d41e Update Airboss.lua 2021-08-25 17:59:33 +10:00
21 changed files with 2857 additions and 840 deletions

View File

@@ -136,7 +136,7 @@ function DATABASE:New()
self:_RegisterGroupsAndUnits() self:_RegisterGroupsAndUnits()
self:_RegisterClients() self:_RegisterClients()
self:_RegisterStatics() self:_RegisterStatics()
self:_RegisterAirbases() --self:_RegisterAirbases()
--self:_RegisterPlayers() --self:_RegisterPlayers()
self.UNITS_Position = 0 self.UNITS_Position = 0

View File

@@ -3998,7 +3998,25 @@ do -- SET_CLIENT
return self return self
end end
--- Iterate the SET_CLIENT and count alive units.
-- @param #SET_CLIENT self
-- @return #number count
function SET_CLIENT:CountAlive()
local Set = self:GetSet()
local CountU = 0
for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP
if UnitData and UnitData:IsAlive() then
CountU = CountU + 1
end
end
return CountU
end
--- ---
-- @param #SET_CLIENT self -- @param #SET_CLIENT self
-- @param Wrapper.Client#CLIENT MClient -- @param Wrapper.Client#CLIENT MClient
@@ -4746,7 +4764,7 @@ do -- SET_AIRBASE
local airbaseName, airbase=self:FindInDatabase(EventData) local airbaseName, airbase=self:FindInDatabase(EventData)
if airbase and airbase:IsShip() or airbase:IsHelipad() then if airbase and (airbase:IsShip() or airbase:IsHelipad()) then
self:RemoveAirbasesByName(airbaseName) self:RemoveAirbasesByName(airbaseName)
end end

View File

@@ -138,24 +138,24 @@ SPAWNSTATIC = {
-- @return #SPAWNSTATIC self -- @return #SPAWNSTATIC self
function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID) function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName) local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
if TemplateStatic then if TemplateStatic then
self.SpawnTemplatePrefix = SpawnTemplateName self.SpawnTemplatePrefix = SpawnTemplateName
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1]) self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
self.CountryID = SpawnCountryID or CountryID self.CountryID = SpawnCountryID or CountryID
self.CategoryID = CategoryID self.CategoryID = CategoryID
self.CoalitionID = CoalitionID self.CoalitionID = CoalitionID
self.SpawnIndex = 0 self.SpawnIndex = 0
else else
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" ) error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
end end
self:SetEventPriority( 5 ) self:SetEventPriority( 5 )
return self return self
end end
--- Creates the main object to spawn a @{Static} given a template table. --- Creates the main object to spawn a @{Static} given a template table.
@@ -422,7 +422,11 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
end end
if self.InitCargo~=nil then if self.InitCargo~=nil then
Template.isCargo=self.InitCargo Template.canCargo=self.InitCargo
end
if self.InitCargoMass~=nil then
Template.mass=self.InitCargoMass
end end
if self.InitLinkUnit then if self.InitLinkUnit then

View File

@@ -183,12 +183,12 @@ function ZONE_BASE:IsCoordinateInZone( Coordinate )
return InZone return InZone
end end
--- Returns if a PointVec2 is within the zone. --- Returns if a PointVec2 is within the zone. (Name is misleading, actually takes a #COORDINATE)
-- @param #ZONE_BASE self -- @param #ZONE_BASE self
-- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 to test. -- @param Core.Point#COORDINATE PointVec2 The coordinate to test.
-- @return #boolean true if the PointVec2 is within the zone. -- @return #boolean true if the PointVec2 is within the zone.
function ZONE_BASE:IsPointVec2InZone( PointVec2 ) function ZONE_BASE:IsPointVec2InZone( Coordinate )
local InZone = self:IsVec2InZone( PointVec2:GetVec2() ) local InZone = self:IsVec2InZone( Coordinate:GetVec2() )
return InZone return InZone
end end
@@ -498,8 +498,8 @@ end
-- --
-- @field #ZONE_RADIUS -- @field #ZONE_RADIUS
ZONE_RADIUS = { ZONE_RADIUS = {
ClassName="ZONE_RADIUS", ClassName="ZONE_RADIUS",
} }
--- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius. --- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
@@ -510,15 +510,15 @@ ZONE_RADIUS = {
function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) function ZONE_RADIUS:New( ZoneName, Vec2, Radius )
-- Inherit ZONE_BASE. -- Inherit ZONE_BASE.
local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS
self:F( { ZoneName, Vec2, Radius } ) self:F( { ZoneName, Vec2, Radius } )
self.Radius = Radius self.Radius = Radius
self.Vec2 = Vec2 self.Vec2 = Vec2
--self.Coordinate=COORDINATE:NewFromVec2(Vec2) --self.Coordinate=COORDINATE:NewFromVec2(Vec2)
return self return self
end end
--- Update zone from a 2D vector. --- Update zone from a 2D vector.
@@ -746,11 +746,11 @@ end
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @return DCS#Vec2 The location of the zone. -- @return DCS#Vec2 The location of the zone.
function ZONE_RADIUS:GetVec2() function ZONE_RADIUS:GetVec2()
self:F2( self.ZoneName ) self:F2( self.ZoneName )
self:T2( { self.Vec2 } ) self:T2( { self.Vec2 } )
return self.Vec2 return self.Vec2
end end
--- Sets the @{DCS#Vec2} of the zone. --- Sets the @{DCS#Vec2} of the zone.
@@ -1165,20 +1165,20 @@ end
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
-- @return DCS#Vec2 The random location within the zone. -- @return DCS#Vec2 The random location within the zone.
function ZONE_RADIUS:GetRandomVec2( inner, outer ) function ZONE_RADIUS:GetRandomVec2( inner, outer )
self:F( self.ZoneName, inner, outer ) self:F( self.ZoneName, inner, outer )
local Point = {} local Point = {}
local Vec2 = self:GetVec2() local Vec2 = self:GetVec2()
local _inner = inner or 0 local _inner = inner or 0
local _outer = outer or self:GetRadius() local _outer = outer or self:GetRadius()
local angle = math.random() * math.pi * 2; local angle = math.random() * math.pi * 2;
Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer); Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer);
Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer); Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer);
self:T( { Point } ) self:T( { Point } )
return Point return Point
end end
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.

View File

@@ -23,9 +23,9 @@
-- Date: July 2021 -- Date: July 2021
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **MANTIS** class, extends #Core.Base#BASE --- **MANTIS** class, extends Core.Base#BASE
-- @type MANTIS -- @type MANTIS
-- @field #string Classname -- @field #string ClassName
-- @field #string name Name of this Mantis -- @field #string name Name of this Mantis
-- @field #string SAM_Templates_Prefix Prefix to build the #SET_GROUP for SAM sites -- @field #string SAM_Templates_Prefix Prefix to build the #SET_GROUP for SAM sites
-- @field Core.Set#SET_GROUP SAM_Group The SAM #SET_GROUP -- @field Core.Set#SET_GROUP SAM_Group The SAM #SET_GROUP
@@ -195,6 +195,9 @@ MANTIS = {
TimeStamp = 0, TimeStamp = 0,
state2flag = false, state2flag = false,
SamStateTracker = {}, SamStateTracker = {},
DLink = false,
DLTimeStamp = 0,
Padding = 10,
} }
--- Advanced state enumerator --- Advanced state enumerator
@@ -219,7 +222,8 @@ do
--@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral"
--@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional)
--@param #string awacs Group name of your Awacs (optional) --@param #string awacs Group name of your Awacs (optional)
--@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional)
--@param #number Padding For #SEAD - Extra number of seconds to add to radar switch-back-on time (optional)
--@return #MANTIS self --@return #MANTIS self
--@usage Start up your MANTIS with a basic setting --@usage Start up your MANTIS with a basic setting
-- --
@@ -241,7 +245,7 @@ do
-- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` -- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")`
-- `mybluemantis:Start()` -- `mybluemantis:Start()`
-- --
function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs, EmOnOff) function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs, EmOnOff, Padding)
-- DONE: Create some user functions for these -- DONE: Create some user functions for these
-- DONE: Make HQ useful -- DONE: Make HQ useful
@@ -278,7 +282,9 @@ do
self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins
self.state2flag = false self.state2flag = false
self.SamStateTracker = {} -- table to hold alert states, so we don't trigger state changes twice in adv mode self.SamStateTracker = {} -- table to hold alert states, so we don't trigger state changes twice in adv mode
self.DLink = false
self.Padding = Padding or 10
if EmOnOff then if EmOnOff then
if EmOnOff == false then if EmOnOff == false then
self.UseEmOnOff = false self.UseEmOnOff = false
@@ -325,7 +331,7 @@ do
end end
-- @field #string version -- @field #string version
self.version="0.5.2" self.version="0.6.2"
self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) self:I(string.format("***** Starting MANTIS Version %s *****", self.version))
--- FSM Functions --- --- FSM Functions ---
@@ -593,7 +599,7 @@ do
-- E.g. `mymantis:SetAdvancedMode(true, 90)` -- E.g. `mymantis:SetAdvancedMode(true, 90)`
function MANTIS:SetAdvancedMode(onoff, ratio) function MANTIS:SetAdvancedMode(onoff, ratio)
self:T(self.lid .. "SetAdvancedMode") self:T(self.lid .. "SetAdvancedMode")
self:T({onoff, ratio}) --self:T({onoff, ratio})
local onoff = onoff or false local onoff = onoff or false
local ratio = ratio or 100 local ratio = ratio or 100
if (type(self.HQ_Template_CC) == "string") and onoff and self.dynamic then if (type(self.HQ_Template_CC) == "string") and onoff and self.dynamic then
@@ -619,6 +625,17 @@ do
return self return self
end end
--- Set using an #INTEL_DLINK object instead of #DETECTION. Requires Develop branch of Moose.lua.
-- @param #MANTIS self
-- @param Ops.Intelligence#INTEL_DLINK DLink The data link object to be used.
function MANTIS:SetUsingDLink(DLink)
self:T(self.lid .. "SetUsingDLink")
self.DLink = true
self.Detection = DLink
self.DLTimeStamp = timer.getAbsTime()
return self
end
--- [Internal] Function to check if HQ is alive --- [Internal] Function to check if HQ is alive
-- @param #MANTIS self -- @param #MANTIS self
-- @return #boolean True if HQ is alive, else false -- @return #boolean True if HQ is alive, else false
@@ -633,10 +650,10 @@ do
local hqgrp = GROUP:FindByName(hq) local hqgrp = GROUP:FindByName(hq)
if hqgrp then if hqgrp then
if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive
self:T(self.lid.." HQ is alive!") --self:T(self.lid.." HQ is alive!")
return true return true
else else
self:T(self.lid.." HQ is dead!") --self:T(self.lid.." HQ is dead!")
return false return false
end end
end end
@@ -650,7 +667,7 @@ do
function MANTIS:_CheckEWRState() function MANTIS:_CheckEWRState()
self:T(self.lid .. "CheckEWRState") self:T(self.lid .. "CheckEWRState")
local text = self.lid.." Checking EWR State" local text = self.lid.." Checking EWR State"
self:T(text) --self:T(text)
local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(text) end if self.verbose then self:I(text) end
-- start check -- start check
@@ -666,7 +683,7 @@ do
end end
end end
end end
self:T(self.lid..string.format(" No of EWR alive is %d", nalive)) --self:T(self.lid..string.format(" No of EWR alive is %d", nalive))
if nalive > 0 then if nalive > 0 then
return true return true
else else
@@ -682,10 +699,8 @@ do
-- @return #number Previous state for tracking 0, 1, or 2 -- @return #number Previous state for tracking 0, 1, or 2
function MANTIS:_CalcAdvState() function MANTIS:_CalcAdvState()
self:T(self.lid .. "CalcAdvState") self:T(self.lid .. "CalcAdvState")
local text = self.lid.." Calculating Advanced State" local m=MESSAGE:New(self.lid.." Calculating Advanced State",10,"MANTIS"):ToAllIf(self.debug)
self:T(text) if self.verbose then self:I(self.lid.." Calculating Advanced State") end
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(text) end
-- start check -- start check
local currstate = self.adv_state -- save curr state for comparison later local currstate = self.adv_state -- save curr state for comparison later
local EWR_State = self:_CheckEWRState() local EWR_State = self:_CheckEWRState()
@@ -703,10 +718,12 @@ do
local ratio = self.adv_ratio / 100 -- e.g. 80/100 = 0.8 local ratio = self.adv_ratio / 100 -- e.g. 80/100 = 0.8
ratio = ratio * self.adv_state -- e.g 0.8*2 = 1.6 ratio = ratio * self.adv_state -- e.g 0.8*2 = 1.6
local newinterval = interval + (interval * ratio) -- e.g. 30+(30*1.6) = 78 local newinterval = interval + (interval * ratio) -- e.g. 30+(30*1.6) = 78
local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval) if self.debug or self.verbose then
self:T(text) local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval)
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) --self:T(text)
if self.verbose then self:I(text) end local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(text) end
end
return newinterval, currstate return newinterval, currstate
end end
@@ -716,13 +733,13 @@ do
-- @param #boolean ewr If true, will relocate EWR objects -- @param #boolean ewr If true, will relocate EWR objects
function MANTIS:SetAutoRelocate(hq, ewr) function MANTIS:SetAutoRelocate(hq, ewr)
self:T(self.lid .. "SetAutoRelocate") self:T(self.lid .. "SetAutoRelocate")
self:T({hq, ewr}) --self:T({hq, ewr})
local hqrel = hq or false local hqrel = hq or false
local ewrel = ewr or false local ewrel = ewr or false
if hqrel or ewrel then if hqrel or ewrel then
self.autorelocate = true self.autorelocate = true
self.autorelocateunits = { HQ = hqrel, EWR = ewrel } self.autorelocateunits = { HQ = hqrel, EWR = ewrel }
self:T({self.autorelocate, self.autorelocateunits}) --self:T({self.autorelocate, self.autorelocateunits})
end end
return self return self
end end
@@ -739,7 +756,7 @@ do
local HQGroup = self.HQ_CC local HQGroup = self.HQ_CC
if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive() then --only relocate if HQ exists if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive() then --only relocate if HQ exists
local _hqgrp = self.HQ_CC local _hqgrp = self.HQ_CC
self:T(self.lid.." Relocating HQ") --self:T(self.lid.." Relocating HQ")
local text = self.lid.." Relocating HQ" local text = self.lid.." Relocating HQ"
--local m= MESSAGE:New(text,10,"MANTIS"):ToAll() --local m= MESSAGE:New(text,10,"MANTIS"):ToAll()
_hqgrp:RelocateGroundRandomInRadius(20,500,true,true) _hqgrp:RelocateGroundRandomInRadius(20,500,true,true)
@@ -752,7 +769,7 @@ do
local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP
for _,_grp in pairs (EWR_Grps) do for _,_grp in pairs (EWR_Grps) do
if _grp:IsAlive() and _grp:IsGround() then if _grp:IsAlive() and _grp:IsGround() then
self:T(self.lid.." Relocating EWR ".._grp:GetName()) --self:T(self.lid.." Relocating EWR ".._grp:GetName())
local text = self.lid.." Relocating EWR ".._grp:GetName() local text = self.lid.." Relocating EWR ".._grp:GetName()
local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(text) end if self.verbose then self:I(text) end
@@ -778,12 +795,14 @@ do
for _,_coord in pairs (set) do for _,_coord in pairs (set) do
local coord = _coord -- get current coord to check local coord = _coord -- get current coord to check
-- output for cross-check -- output for cross-check
local dectstring = coord:ToStringLLDMS()
local samstring = samcoordinate:ToStringLLDMS()
local targetdistance = samcoordinate:DistanceFromPointVec2(coord) local targetdistance = samcoordinate:DistanceFromPointVec2(coord)
local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) if self.verbose or self.debug then
local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) local dectstring = coord:ToStringLLDMS()
if self.verbose then self:I(self.lid..text) end local samstring = samcoordinate:ToStringLLDMS()
local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring)
local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug)
self:I(self.lid..text)
end
-- end output to cross-check -- end output to cross-check
if targetdistance <= radius then if targetdistance <= radius then
return true, targetdistance return true, targetdistance
@@ -888,7 +907,7 @@ do
end end
self.SAM_Table = SAM_Tbl self.SAM_Table = SAM_Tbl
-- make SAMs evasive -- make SAMs evasive
local mysead = SEAD:New( SEAD_Grps ) local mysead = SEAD:New( SEAD_Grps, self.Padding ) -- Functional.Sead#SEAD
mysead:SetEngagementRange(engagerange) mysead:SetEngagementRange(engagerange)
self.mysead = mysead self.mysead = mysead
return self return self
@@ -999,9 +1018,11 @@ do
self:__ShoradActivated(1,name, radius, ontime) self:__ShoradActivated(1,name, radius, ontime)
end end
-- debug output -- debug output
local text = string.format("SAM %s switched to alarm state RED!", name) if self.debug or self.verbose then
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local text = string.format("SAM %s switched to alarm state RED!", name)
if self.verbose then self:I(self.lid..text) end local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(self.lid..text) end
end
end --end alive end --end alive
else else
if samgroup:IsAlive() then if samgroup:IsAlive() then
@@ -1014,9 +1035,11 @@ do
self:__GreenState(1,samgroup) self:__GreenState(1,samgroup)
self.SamStateTracker[name] = "GREEN" self.SamStateTracker[name] = "GREEN"
end end
local text = string.format("SAM %s switched to alarm state GREEN!", name) if self.debug or self.verbose then
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) local text = string.format("SAM %s switched to alarm state GREEN!", name)
if self.verbose then self:I(self.lid..text) end local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(self.lid..text) end
end
end --end alive end --end alive
end --end check end --end check
end --for for loop end --for for loop
@@ -1066,6 +1089,20 @@ do
end -- end newstate vs oldstate end -- end newstate vs oldstate
return self return self
end end
--- [Internal] Check DLink state
-- @param #MANTIS self
-- @return #MANTIS self
function MANTIS:_CheckDLinkState()
self:T(self.lid .. "_CheckDLinkState")
local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK
local TS = timer.getAbsTime()
if not dlink:Is("Running") and (TS - self.DLTimeStamp > 29) then
self.DLink = false
self.Detection = self:StartDetection() -- fall back
self:I(self.lid .. "Intel DLink not running - switching back to single detection!")
end
end
--- [Internal] Function to set start state --- [Internal] Function to set start state
-- @param #MANTIS self -- @param #MANTIS self
@@ -1077,11 +1114,13 @@ do
self:T({From, Event, To}) self:T({From, Event, To})
self:T(self.lid.."Starting MANTIS") self:T(self.lid.."Starting MANTIS")
self:SetSAMStartState() self:SetSAMStartState()
self.Detection = self:StartDetection() if not self.DLink then
self.Detection = self:StartDetection()
end
if self.advAwacs then if self.advAwacs then
self.AWACS_Detection = self:StartAwacsDetection() self.AWACS_Detection = self:StartAwacsDetection()
end end
self:__Status(self.detectinterval) self:__Status(-math.random(1,10))
return self return self
end end
@@ -1120,11 +1159,16 @@ do
end end
end end
-- timer for advanced state check -- advanced state check
if self.advanced then if self.advanced then
self:_CheckAdvState() self:_CheckAdvState()
end end
-- check DLink state
if self.DLink then
self:_CheckDLinkState()
end
return self return self
end end
@@ -1136,6 +1180,13 @@ do
-- @return #MANTIS self -- @return #MANTIS self
function MANTIS:onafterStatus(From,Event,To) function MANTIS:onafterStatus(From,Event,To)
self:T({From, Event, To}) self:T({From, Event, To})
-- Display some states
if self.debug then
self:I(self.lid .. "Status Report")
for _name,_state in pairs(self.SamStateTracker) do
self:I(string.format("Site %s\tStatus %s",_name,_state))
end
end
local interval = self.detectinterval * -1 local interval = self.detectinterval * -1
self:__Status(interval) self:__Status(interval)
return self return self

View File

@@ -2831,6 +2831,7 @@ function RANGE:_CheckInZone(_unitName)
local accur=0 local accur=0
if shots>0 then if shots>0 then
accur=_result.hits/shots*100 accur=_result.hits/shots*100
if accur > 100 then accur = 100 end
end end
-- Message text. -- Message text.

View File

@@ -17,14 +17,15 @@
-- --
-- ### Authors: **FlightControl**, **applevangelist** -- ### Authors: **FlightControl**, **applevangelist**
-- --
-- Last Update: July 2021 -- Last Update: Aug 2021
-- --
-- === -- ===
-- --
-- @module Functional.Sead -- @module Functional.Sead
-- @image SEAD.JPG -- @image SEAD.JPG
--- @type SEAD ---
-- @type SEAD
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- Make SAM sites execute evasive and defensive behaviour when being fired upon.
@@ -48,7 +49,8 @@ SEAD = {
}, },
SEADGroupPrefixes = {}, SEADGroupPrefixes = {},
SuppressedGroups = {}, SuppressedGroups = {},
EngagementRange = 75 -- default 75% engagement range Feature Request #1355 EngagementRange = 75, -- default 75% engagement range Feature Request #1355
Padding = 10,
} }
--- Missile enumerators --- Missile enumerators
@@ -59,7 +61,7 @@ SEAD = {
["AGM_122"] = "AGM_122", ["AGM_122"] = "AGM_122",
["AGM_84"] = "AGM_84", ["AGM_84"] = "AGM_84",
["AGM_45"] = "AGM_45", ["AGM_45"] = "AGM_45",
["ALARN"] = "ALARM", ["ALARM"] = "ALARM",
["LD-10"] = "LD-10", ["LD-10"] = "LD-10",
["X_58"] = "X_58", ["X_58"] = "X_58",
["X_28"] = "X_28", ["X_28"] = "X_28",
@@ -68,17 +70,35 @@ SEAD = {
["Kh25"] = "Kh25", ["Kh25"] = "Kh25",
} }
--- Missile enumerators - from DCS ME and Wikipedia
-- @field HarmData
SEAD.HarmData = {
-- km and mach
["AGM_88"] = { 150, 3},
["AGM_45"] = { 12, 2},
["AGM_122"] = { 16.5, 2.3},
["AGM_84"] = { 280, 0.85},
["ALARM"] = { 45, 2},
["LD-10"] = { 60, 4},
["X_58"] = { 70, 4},
["X_28"] = { 80, 2.5},
["X_25"] = { 25, 0.76},
["X_31"] = {150, 3},
["Kh25"] = {25, 0.8},
}
--- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles.
-- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... -- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions...
-- Chances are big that the missile will miss. -- Chances are big that the missile will miss.
-- @param #SEAD self -- @param #SEAD self
-- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken. -- @param #table SEADGroupPrefixes Table of #string entries or single #string, which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken.
-- @param #number Padding (Optional) Extra number of seconds to add to radar switch-back-on time
-- @return SEAD -- @return SEAD
-- @usage -- @usage
-- -- CCCP SEAD Defenses -- -- CCCP SEAD Defenses
-- -- Defends the Russian SA installations from SEAD attacks. -- -- Defends the Russian SA installations from SEAD attacks.
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
function SEAD:New( SEADGroupPrefixes ) function SEAD:New( SEADGroupPrefixes, Padding )
local self = BASE:Inherit( self, BASE:New() ) local self = BASE:Inherit( self, BASE:New() )
self:F( SEADGroupPrefixes ) self:F( SEADGroupPrefixes )
@@ -91,8 +111,13 @@ function SEAD:New( SEADGroupPrefixes )
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
end end
local padding = Padding or 10
if padding < 10 then padding = 10 end
self.Padding = padding
self:HandleEvent( EVENTS.Shot, self.HandleEventShot ) self:HandleEvent( EVENTS.Shot, self.HandleEventShot )
self:I("*** SEAD - Started Version 0.2.9")
self:I("*** SEAD - Started Version 0.3.1")
return self return self
end end
@@ -130,54 +155,100 @@ function SEAD:SetEngagementRange(range)
return self return self
end end
--- Set the padding in seconds, which extends the radar off time calculated by SEAD
-- @param #SEAD self
-- @param #number Padding Extra number of seconds to add for the switch-on
function SEAD:SetPadding(Padding)
self:T( { Padding } )
local padding = Padding or 10
if padding < 10 then padding = 10 end
self.Padding = padding
return self
end
--- Check if a known HARM was fired --- Check if a known HARM was fired
-- @param #SEAD self -- @param #SEAD self
-- @param #string WeaponName -- @param #string WeaponName
-- @return #boolean Returns true for a match -- @return #boolean Returns true for a match
-- @return #string name Name of hit in table
function SEAD:_CheckHarms(WeaponName) function SEAD:_CheckHarms(WeaponName)
self:T( { WeaponName } ) self:T( { WeaponName } )
local hit = false local hit = false
local name = ""
for _,_name in pairs (SEAD.Harms) do for _,_name in pairs (SEAD.Harms) do
if string.find(WeaponName,_name,1) then hit = true end if string.find(WeaponName,_name,1) then
hit = true
name = _name
break
end
end end
return hit return hit, name
end end
--- (Internal) Return distance in meters between two coordinates or -1 on error.
-- @param #SEAD self
-- @param Core.Point#COORDINATE _point1 Coordinate one
-- @param Core.Point#COORDINATE _point2 Coordinate two
-- @return #number Distance in meters
function SEAD:_GetDistance(_point1, _point2)
self:T("_GetDistance")
if _point1 and _point2 then
local distance1 = _point1:Get2DDistance(_point2)
local distance2 = _point1:DistanceFromPointVec2(_point2)
--self:T({dist1=distance1, dist2=distance2})
if distance1 and type(distance1) == "number" then
return distance1
elseif distance2 and type(distance2) == "number" then
return distance2
else
self:E("*****Cannot calculate distance!")
self:E({_point1,_point2})
return -1
end
else
self:E("******Cannot calculate distance!")
self:E({_point1,_point2})
return -1
end
end
--- Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. --- Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
-- @see SEAD -- @see SEAD
-- @param #SEAD -- @param #SEAD
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function SEAD:HandleEventShot( EventData ) function SEAD:HandleEventShot( EventData )
self:T( { EventData } ) self:T( { EventData.id } )
local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT
local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE
local SEADUnit = EventData.IniDCSUnit local SEADUnit = EventData.IniDCSUnit
local SEADUnitName = EventData.IniDCSUnitName local SEADUnitName = EventData.IniDCSUnitName
local SEADWeapon = EventData.Weapon -- Identify the weapon fired local SEADWeapon = EventData.Weapon -- Identify the weapon fired
local SEADWeaponName = EventData.WeaponName -- return weapon type local SEADWeaponName = EventData.WeaponName -- return weapon type
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
self:T({ SEADWeapon }) --self:T({ SEADWeapon })
if self:_CheckHarms(SEADWeaponName) then if self:_CheckHarms(SEADWeaponName) then
self:T( '*** SEAD - Weapon Match' )
local _targetskill = "Random" local _targetskill = "Random"
local _targetMimgroupName = "none" local _targetgroupname = "none"
local _evade = math.random (1,100) -- random number for chance of evading action local _target = EventData.Weapon:getTarget() -- Identify target
local _targetMim = EventData.Weapon:getTarget() -- Identify target local _targetUnit = UNIT:Find(_target) -- Wrapper.Unit#UNIT
local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object local _targetgroup = nil -- Wrapper.Group#GROUP
if _targetUnit and _targetUnit:IsAlive() then if _targetUnit and _targetUnit:IsAlive() then
local _targetMimgroup = _targetUnit:GetGroup() _targetgroup = _targetUnit:GetGroup()
_targetMimgroupName = _targetMimgroup:GetName() -- group name _targetgroupname = _targetgroup:GetName() -- group name
--local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill local _targetUnitName = _targetUnit:GetName()
self:T( self.SEADGroupPrefixes ) _targetUnit:GetSkill()
self:T( _targetMimgroupName ) _targetskill = _targetUnit:GetSkill()
end end
-- see if we are shot at -- see if we are shot at
local SEADGroupFound = false local SEADGroupFound = false
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
self:T( SEADGroupPrefix ) self:T( SEADGroupPrefix )
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then if string.find( _targetgroupname, SEADGroupPrefix, 1, true ) then
SEADGroupFound = true SEADGroupFound = true
self:T( '*** SEAD - Group Found' ) self:T( '*** SEAD - Group Match Found' )
break break
end end
end end
@@ -186,42 +257,67 @@ function SEAD:HandleEventShot( EventData )
local Skills = { "Average", "Good", "High", "Excellent" } local Skills = { "Average", "Good", "High", "Excellent" }
_targetskill = Skills[ math.random(1,4) ] _targetskill = Skills[ math.random(1,4) ]
end end
self:T( _targetskill ) --self:T( _targetskill )
if self.TargetSkill[_targetskill] then if self.TargetSkill[_targetskill] then
local _evade = math.random (1,100) -- random number for chance of evading action
if (_evade > self.TargetSkill[_targetskill].Evade) then if (_evade > self.TargetSkill[_targetskill].Evade) then
self:T("*** SEAD - Evading")
self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) ) -- calculate distance of attacker
local _targetpos = _targetgroup:GetCoordinate()
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) local _distance = self:_GetDistance(SEADPlanePos, _targetpos)
local _targetMimcont= _targetMimgroup:getController() -- weapon speed
local hit, data = self:_CheckHarms(SEADWeaponName)
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly local wpnspeed = 666 -- ;)
local reach = 10
--tracker ID table to switch groups off and on again if hit then
local id = { local wpndata = SEAD.HarmData[data]
groupName = _targetMimgroup, reach = wpndata[1] * 1,1
ctrl = _targetMimcont local mach = wpndata[2]
} wpnspeed = math.floor(mach * 340.29)
local function SuppressionEnd(id) --switch group back on
local range = self.EngagementRange -- Feature Request #1355
self:T(string.format("*** SEAD - Engagement Range is %d", range))
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
--id.groupName:enableEmission(true)
id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355
self.SuppressedGroups[id.groupName] = nil --delete group id from table when done
end end
-- randomize switch-on time -- time to impact
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) local _tti = math.floor(_distance / wpnspeed) -- estimated impact time
local SuppressionEndTime = timer.getTime() + delay if _distance > 0 then
--create entry _distance = math.floor(_distance / 1000) -- km
if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet else
self.SuppressedGroups[id.groupName] = { _distance = 0
SuppressionEndTime = delay end
}
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti ))
--_targetMimgroup:enableEmission(false)
timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function if reach >= _distance then
self:T("*** SEAD - Shot in Reach")
local function SuppressionStart(args)
self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2]))
local grp = args[1] -- Wrapper.Group#GROUP
grp:OptionAlarmStateGreen()
grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond")
end
local function SuppressionStop(args)
self:T(string.format("*** SEAD - %s Radar On",args[2]))
local grp = args[1] -- Wrapper.Group#GROUP
grp:OptionAlarmStateRed()
grp:OptionEngageRange(self.EngagementRange)
self.SuppressedGroups[args[2]] = false
end
-- randomize switch-on time
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
if delay > _tti then delay = delay / 2 end -- speed up
if _tti > (3*delay) then delay = (_tti / 2) * 0.9 end -- shot from afar
local SuppressionStartTime = timer.getTime() + delay
local SuppressionEndTime = timer.getTime() + _tti + self.Padding
if not self.SuppressedGroups[_targetgroupname] then
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname},SuppressionStartTime)
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
self.SuppressedGroups[_targetgroupname] = true
end
end end
end end
end end

View File

@@ -113,7 +113,7 @@ do
["AGM_122"] = "AGM_122", ["AGM_122"] = "AGM_122",
["AGM_84"] = "AGM_84", ["AGM_84"] = "AGM_84",
["AGM_45"] = "AGM_45", ["AGM_45"] = "AGM_45",
["ALARN"] = "ALARM", ["ALARM"] = "ALARM",
["LD-10"] = "LD-10", ["LD-10"] = "LD-10",
["X_58"] = "X_58", ["X_58"] = "X_58",
["X_28"] = "X_28", ["X_28"] = "X_28",

View File

@@ -715,20 +715,21 @@ do -- ZONE_CAPTURE_COALITION
local UnitHit = EventData.TgtUnit local UnitHit = EventData.TgtUnit
if UnitHit.ClassName ~= "SCENERY" then
-- Check if unit is inside the capture zone and that it is of the defending coalition. -- Check if unit is inside the capture zone and that it is of the defending coalition.
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
-- Update last hit time. -- Update last hit time.
self.HitTimeLast=timer.getTime() self.HitTimeLast=timer.getTime()
-- Only trigger attacked event if not already in state "Attacked". -- Only trigger attacked event if not already in state "Attacked".
if self:GetState()~="Attacked" then if self:GetState()~="Attacked" then
self:F2("Hit ==> Attack") self:F2("Hit ==> Attack")
self:Attack() self:Attack()
end end
end
end end
end end
end end
@@ -890,12 +891,14 @@ do -- ZONE_CAPTURE_COALITION
end end
-- Status text. -- Status text.
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State) if false then
local NewState = self:GetState() local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State)
if NewState~=State then local NewState = self:GetState()
text=text..string.format(" --> %s", NewState) if NewState~=State then
text=text..string.format(" --> %s", NewState)
end
self:I(text)
end end
self:I(text)
end end

View File

@@ -18,6 +18,7 @@ _DATABASE:_RegisterCargos()
--- Register zones. --- Register zones.
_DATABASE:_RegisterZones() _DATABASE:_RegisterZones()
_DATABASE:_RegisterAirbases()
--- Check if os etc is available. --- Check if os etc is available.
BASE:I("Checking de-sanitization of os, io and lfs:") BASE:I("Checking de-sanitization of os, io and lfs:")

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,7 @@
-- @module Ops.CSAR -- @module Ops.CSAR
-- @image OPS_CSAR.jpg -- @image OPS_CSAR.jpg
-- Date: Aug 2021 -- Date: Oct 2021
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
@@ -97,6 +97,14 @@
-- self.pilotmustopendoors = false -- switch to true to enable check of open doors -- self.pilotmustopendoors = false -- switch to true to enable check of open doors
-- -- (added 0.1.9) -- -- (added 0.1.9)
-- self.suppressmessages = false -- switch off all messaging if you want to do your own -- self.suppressmessages = false -- switch off all messaging if you want to do your own
-- -- (added 0.1.11)
-- self.rescuehoverheight = 20 -- max height for a hovering rescue in meters
-- self.rescuehoverdistance = 10 -- max distance for a hovering rescue in meters
-- -- (added 0.1.12)
-- -- Country codes for spawned pilots
-- self.countryblue= country.id.USA
-- self.countryred = country.id.RUSSIA
-- self.countryneutral = country.id.UN_PEACEKEEPERS
-- --
-- ## 2.1 Experimental Features -- ## 2.1 Experimental Features
-- --
@@ -230,10 +238,11 @@ CSAR.AircraftType["Mi-8MTV2"] = 12
CSAR.AircraftType["Mi-8MT"] = 12 CSAR.AircraftType["Mi-8MT"] = 12
CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24P"] = 8
CSAR.AircraftType["Mi-24V"] = 8 CSAR.AircraftType["Mi-24V"] = 8
CSAR.AircraftType["Bell-47"] = 2
--- CSAR class version. --- CSAR class version.
-- @field #string version -- @field #string version
CSAR.version="0.1.10r3" CSAR.version="0.1.11r2"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
@@ -349,7 +358,7 @@ function CSAR:New(Coalition, Template, Alias)
self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names!
self.template = Template or "generic" -- template for downed pilot self.template = Template or "generic" -- template for downed pilot
self.mashprefix = {"MASH"} -- prefixes used to find MASHes self.mashprefix = {"MASH"} -- prefixes used to find MASHes
self.mash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also?
self.autosmoke = false -- automatically smoke location when heli is near self.autosmoke = false -- automatically smoke location when heli is near
self.autosmokedistance = 2000 -- distance for autosmoke self.autosmokedistance = 2000 -- distance for autosmoke
-- added 0.1.4 -- added 0.1.4
@@ -362,6 +371,15 @@ function CSAR:New(Coalition, Template, Alias)
self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters
self.pilotmustopendoors = false -- switch to true to enable check on open doors self.pilotmustopendoors = false -- switch to true to enable check on open doors
self.suppressmessages = false self.suppressmessages = false
-- added 0.1.11r1
self.rescuehoverheight = 20
self.rescuehoverdistance = 10
-- added 0.1.12
self.countryblue= country.id.USA
self.countryred = country.id.RUSSIA
self.countryneutral = country.id.UN_PEACEKEEPERS
-- WARNING - here\'ll be dragons -- WARNING - here\'ll be dragons
-- for this to work you need to de-sanitize your mission environment in <DCS root>\Scripts\MissionScripting.lua -- for this to work you need to de-sanitize your mission environment in <DCS root>\Scripts\MissionScripting.lua
@@ -549,6 +567,7 @@ function CSAR:_SpawnPilotInField(country,point,frequency)
for i=1,10 do for i=1,10 do
math.random(i,10000) math.random(i,10000)
end end
if point:IsSurfaceTypeWater() then point.y = 0 end
local template = self.template local template = self.template
local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,99)) local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,99))
local coalition = self.coalition local coalition = self.coalition
@@ -687,11 +706,11 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _
local _country = 0 local _country = 0
if _coalition == coalition.side.BLUE then if _coalition == coalition.side.BLUE then
_country = country.id.USA _country = self.countryblue
elseif _coalition == coalition.side.RED then elseif _coalition == coalition.side.RED then
_country = country.id.RUSSIA _country = self.countryred
else else
_country = country.id.UN_PEACEKEEPERS _country = self.countryneutral
end end
self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description, forcedesc) self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description, forcedesc)
@@ -718,7 +737,6 @@ function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessa
return self return self
end end
-- TODO: Split in functions per Event type
--- (Internal) Event handler. --- (Internal) Event handler.
-- @param #CSAR self -- @param #CSAR self
function CSAR:_EventHandler(EventData) function CSAR:_EventHandler(EventData)
@@ -790,7 +808,7 @@ function CSAR:_EventHandler(EventData)
if self:_DoubleEjection(_unitname) then if self:_DoubleEjection(_unitname) then
return return
end end
self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!", self.coalition, self.messageTime)
else else
self:T(self.lid .. " Pilot has not taken off, ignore") self:T(self.lid .. " Pilot has not taken off, ignore")
end end
@@ -878,13 +896,6 @@ function CSAR:_EventHandler(EventData)
if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then
self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true) self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true)
--[[
if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_event.IniUnitName) then
self:_DisplayMessageToSAR(_unit, "Open the door to let me out!", self.messageTime, true)
else
self:_RescuePilots(_unit)
end
--]]
else else
self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition()))
end end
@@ -904,7 +915,6 @@ end
function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage)
self:T(self.lid .. " _InitSARForPilot") self:T(self.lid .. " _InitSARForPilot")
local _leader = _downedGroup:GetUnit(1) local _leader = _downedGroup:GetUnit(1)
--local _groupName = _downedGroup:GetName()
local _groupName = _GroupName local _groupName = _GroupName
local _freqk = _freq / 1000 local _freqk = _freq / 1000
local _coordinatesText = self:_GetPositionOfWounded(_downedGroup) local _coordinatesText = self:_GetPositionOfWounded(_downedGroup)
@@ -920,7 +930,7 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage)
end end
-- trigger FSM event -- trigger FSM event
self:__PilotDown(2,_downedGroup, _freqk, _leadername, _coordinatesText) self:__PilotDown(2,_downedGroup, _freqk, _groupName, _coordinatesText)
return self return self
end end
@@ -1090,7 +1100,6 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam
local grouptable = downedgrouptable --#CSAR.DownedPilot local grouptable = downedgrouptable --#CSAR.DownedPilot
self.inTransitGroups[_heliName][_woundedGroupName] = self.inTransitGroups[_heliName][_woundedGroupName] =
{ {
-- DONE: Fix with #CSAR.DownedPilot
originalUnit = grouptable.originalUnit, originalUnit = grouptable.originalUnit,
woundedGroup = _woundedGroupName, woundedGroup = _woundedGroupName,
side = self.coalition, side = self.coalition,
@@ -1129,42 +1138,7 @@ end
-- @return #boolean outcome The outcome. -- @return #boolean outcome The outcome.
function CSAR:_IsLoadingDoorOpen( unit_name ) function CSAR:_IsLoadingDoorOpen( unit_name )
self:T(self.lid .. " _IsLoadingDoorOpen") self:T(self.lid .. " _IsLoadingDoorOpen")
--[[
local ret_val = false
local unit = Unit.getByName(unit_name)
if unit ~= nil then
local type_name = unit:getTypeName()
if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then
self:T(unit_name .. " Cargo doors are open or cargo door not present")
ret_val = true
end
if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then
self:T(unit_name .. " a side door is open")
ret_val = true
end
if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then
self:T(unit_name .. " a side door is open ")
ret_val = true
end
if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then
self:T(unit_name .. " front door(s) are open")
ret_val = true
end
if ret_val == false then
self:T(unit_name .. " all doors are closed")
end
return ret_val
end -- nil
--]]
return UTILS.IsLoadingDoorOpen(unit_name) return UTILS.IsLoadingDoorOpen(unit_name)
end end
--- (Internal) Function to check if heli is close to group. --- (Internal) Function to check if heli is close to group.
@@ -1195,14 +1169,12 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
else else
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,false,true) self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,false,true)
end end
--mark as shown for THIS heli and THIS group
self.heliCloseMessage[_lookupKeyHeli] = true self.heliCloseMessage[_lookupKeyHeli] = true
end end
-- have we landed close enough? -- have we landed close enough?
if not _heliUnit:InAir() then if not _heliUnit:InAir() then
-- if you land on them, doesnt matter if they were heading to someone else as you\'re closer, you win! :)
if self.pilotRuntoExtractPoint == true then if self.pilotRuntoExtractPoint == true then
if (_distance < self.extractDistance) then if (_distance < self.extractDistance) then
local _time = self.landedStatus[_lookupKeyHeli] local _time = self.landedStatus[_lookupKeyHeli]
@@ -1246,15 +1218,16 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
end end
if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then
-- TODO - make variable
if _distance < 8.0 then if _distance < self.rescuehoverdistance then
--check height! --check height!
local leaderheight = _woundedLeader:GetHeight() local leaderheight = _woundedLeader:GetHeight()
if leaderheight < 0 then leaderheight = 0 end if leaderheight < 0 then leaderheight = 0 end
local _height = _heliUnit:GetHeight() - leaderheight local _height = _heliUnit:GetHeight() - leaderheight
if _height <= 20.0 then -- TODO - make variable
if _height <= self.rescuehoverheight then
local _time = self.hoverStatus[_lookupKeyHeli] local _time = self.hoverStatus[_lookupKeyHeli]
@@ -1353,8 +1326,7 @@ function CSAR:_RescuePilots(_heliUnit)
-- Groups already rescued -- Groups already rescued
return return
end end
-- DONE: count saved units?
local PilotsSaved = self:_PilotsOnboard(_heliName) local PilotsSaved = self:_PilotsOnboard(_heliName)
self.inTransitGroups[_heliName] = nil self.inTransitGroups[_heliName] = nil
@@ -1450,7 +1422,6 @@ function CSAR:_DisplayActiveSAR(_unitName)
local _groupName = _value.name local _groupName = _value.name
self:T(string.format("Display Active Pilot: %s", tostring(_groupName))) self:T(string.format("Display Active Pilot: %s", tostring(_groupName)))
self:T({Table=_value}) self:T({Table=_value})
--local _woundedGroup = GROUP:FindByName(_groupName)
local _woundedGroup = _value.group local _woundedGroup = _value.group
if _woundedGroup and _value.alive then if _woundedGroup and _value.alive then
local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup)
@@ -1747,7 +1718,6 @@ function CSAR:_GetDistance(_point1, _point2)
if _point1 and _point2 then if _point1 and _point2 then
local distance1 = _point1:Get2DDistance(_point2) local distance1 = _point1:Get2DDistance(_point2)
local distance2 = _point1:DistanceFromPointVec2(_point2) local distance2 = _point1:DistanceFromPointVec2(_point2)
self:I({dist1=distance1, dist2=distance2})
if distance1 and type(distance1) == "number" then if distance1 and type(distance1) == "number" then
return distance1 return distance1
elseif distance2 and type(distance2) == "number" then elseif distance2 and type(distance2) == "number" then
@@ -1768,7 +1738,6 @@ end
-- @param #CSAR self -- @param #CSAR self
function CSAR:_GenerateVHFrequencies() function CSAR:_GenerateVHFrequencies()
self:T(self.lid .. " _GenerateVHFrequencies") self:T(self.lid .. " _GenerateVHFrequencies")
--local _skipFrequencies = self.SkipFrequencies
local FreeVHFFrequencies = {} local FreeVHFFrequencies = {}
FreeVHFFrequencies = UTILS.GenerateVHFrequencies() FreeVHFFrequencies = UTILS.GenerateVHFrequencies()
@@ -1808,7 +1777,6 @@ function CSAR:_GetClockDirection(_heli, _group)
if _heading then if _heading then
local Aspect = Angle - _heading local Aspect = Angle - _heading
if Aspect == 0 then Aspect = 360 end if Aspect == 0 then Aspect = 360 end
--clock = math.floor(Aspect / 30)
clock = math.abs(UTILS.Round((Aspect / 30),0)) clock = math.abs(UTILS.Round((Aspect / 30),0))
if clock == 0 then clock = 12 end if clock == 0 then clock = 12 end
end end
@@ -1917,6 +1885,7 @@ function CSAR:onafterStart(From, Event, To)
else else
self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart()
end end
self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -- currently only GROUP objects, maybe support STATICs also?
self:__Status(-10) self:__Status(-10)
return self return self
end end

File diff suppressed because it is too large Load Diff

View File

@@ -157,7 +157,7 @@ end
--- Set the frequency for the radio transmission. --- Set the frequency for the radio transmission.
-- If the transmitting positionable is a unit or group, this also set the command "SetFrequency" with the defined frequency and modulation. -- If the transmitting positionable is a unit or group, this also set the command "SetFrequency" with the defined frequency and modulation.
-- @param #RADIO self -- @param #RADIO self
-- @param #number Frequency Frequency in MHz. Ranges allowed for radio transmissions in DCS : 30-87.995 / 108-173.995 / 225-399.975MHz. -- @param #number Frequency Frequency in MHz.
-- @return #RADIO self -- @return #RADIO self
function RADIO:SetFrequency(Frequency) function RADIO:SetFrequency(Frequency)
self:F2(Frequency) self:F2(Frequency)
@@ -165,7 +165,7 @@ function RADIO:SetFrequency(Frequency)
if type(Frequency) == "number" then if type(Frequency) == "number" then
-- If frequency is in range -- If frequency is in range
if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then -- if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
-- Convert frequency from MHz to Hz -- Convert frequency from MHz to Hz
self.Frequency = Frequency * 1000000 self.Frequency = Frequency * 1000000
@@ -186,10 +186,10 @@ function RADIO:SetFrequency(Frequency)
end end
return self return self
end -- end
end end
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.", Frequency}) self:E({"Frequency is not a number. Frequency unchanged.", Frequency})
return self return self
end end

View File

@@ -1,13 +1,13 @@
--- This module contains derived utilities taken from the MIST framework, which are excellent tools to be reused in an OO environment. --- This module contains derived utilities taken from the MIST framework, which are excellent tools to be reused in an OO environment.
-- --
-- ### Authors: -- ### Authors:
-- --
-- * Grimes : Design & Programming of the MIST framework. -- * Grimes : Design & Programming of the MIST framework.
-- --
-- ### Contributions: -- ### Contributions:
-- --
-- * FlightControl : Rework to OO framework -- * FlightControl : Rework to OO framework.
-- --
-- @module Utils -- @module Utils
-- @image MOOSE.JPG -- @image MOOSE.JPG
@@ -18,7 +18,7 @@
-- @field White -- @field White
-- @field Orange -- @field Orange
-- @field Blue -- @field Blue
SMOKECOLOR = trigger.smokeColor -- #SMOKECOLOR SMOKECOLOR = trigger.smokeColor -- #SMOKECOLOR
--- @type FLARECOLOR --- @type FLARECOLOR
@@ -94,7 +94,7 @@ CALLSIGN={
Texaco=1, Texaco=1,
Arco=2, Arco=2,
Shell=3, Shell=3,
}, },
-- JTAC -- JTAC
JTAC={ JTAC={
Axeman=1, Axeman=1,
@@ -163,31 +163,31 @@ UTILS = {
UTILS.IsInstanceOf = function( object, className ) UTILS.IsInstanceOf = function( object, className )
-- Is className NOT a string ? -- Is className NOT a string ?
if not type( className ) == 'string' then if not type( className ) == 'string' then
-- Is className a Moose class ? -- Is className a Moose class ?
if type( className ) == 'table' and className.IsInstanceOf ~= nil then if type( className ) == 'table' and className.IsInstanceOf ~= nil then
-- Get the name of the Moose class as a string -- Get the name of the Moose class as a string
className = className.ClassName className = className.ClassName
-- className is neither a string nor a Moose class, throw an error -- className is neither a string nor a Moose class, throw an error
else else
-- I'm not sure if this should take advantage of MOOSE logging function, or throw an error for pcall -- I'm not sure if this should take advantage of MOOSE logging function, or throw an error for pcall
local err_str = 'className parameter should be a string; parameter received: '..type( className ) local err_str = 'className parameter should be a string; parameter received: '..type( className )
return false return false
-- error( err_str ) -- error( err_str )
end end
end end
-- Is the object a Moose class instance ? -- Is the object a Moose class instance ?
if type( object ) == 'table' and object.IsInstanceOf ~= nil then if type( object ) == 'table' and object.IsInstanceOf ~= nil then
-- Use the IsInstanceOf method of the BASE class -- Use the IsInstanceOf method of the BASE class
return object:IsInstanceOf( className ) return object:IsInstanceOf( className )
else else
-- If the object is not an instance of a Moose class, evaluate against lua basic data types -- If the object is not an instance of a Moose class, evaluate against lua basic data types
local basicDataTypes = { 'string', 'number', 'function', 'boolean', 'nil', 'table' } local basicDataTypes = { 'string', 'number', 'function', 'boolean', 'nil', 'table' }
for _, basicDataType in ipairs( basicDataTypes ) do for _, basicDataType in ipairs( basicDataTypes ) do
@@ -196,7 +196,7 @@ UTILS.IsInstanceOf = function( object, className )
end end
end end
end end
-- Check failed -- Check failed
return false return false
end end
@@ -208,7 +208,7 @@ end
UTILS.DeepCopy = function(object) UTILS.DeepCopy = function(object)
local lookup_table = {} local lookup_table = {}
-- Copy function. -- Copy function.
local function _copy(object) local function _copy(object)
if type(object) ~= "table" then if type(object) ~= "table" then
@@ -216,20 +216,20 @@ UTILS.DeepCopy = function(object)
elseif lookup_table[object] then elseif lookup_table[object] then
return lookup_table[object] return lookup_table[object]
end end
local new_table = {} local new_table = {}
lookup_table[object] = new_table lookup_table[object] = new_table
for index, value in pairs(object) do for index, value in pairs(object) do
new_table[_copy(index)] = _copy(value) new_table[_copy(index)] = _copy(value)
end end
return setmetatable(new_table, getmetatable(object)) return setmetatable(new_table, getmetatable(object))
end end
local objectreturn = _copy(object) local objectreturn = _copy(object)
return objectreturn return objectreturn
end end
@@ -239,19 +239,19 @@ end
UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function
lookup_table = {} lookup_table = {}
local function _Serialize( tbl ) local function _Serialize( tbl )
if type(tbl) == 'table' then --function only works for tables! if type(tbl) == 'table' then --function only works for tables!
if lookup_table[tbl] then if lookup_table[tbl] then
return lookup_table[object] return lookup_table[object]
end end
local tbl_str = {} local tbl_str = {}
lookup_table[tbl] = tbl_str lookup_table[tbl] = tbl_str
tbl_str[#tbl_str + 1] = '{' tbl_str[#tbl_str + 1] = '{'
for ind,val in pairs(tbl) do -- serialize its fields for ind,val in pairs(tbl) do -- serialize its fields
@@ -299,7 +299,7 @@ UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a s
env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind))
env.info( debug.traceback() ) env.info( debug.traceback() )
end end
end end
tbl_str[#tbl_str + 1] = '}' tbl_str[#tbl_str + 1] = '}'
return table.concat(tbl_str) return table.concat(tbl_str)
@@ -307,7 +307,7 @@ UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a s
return tostring(tbl) return tostring(tbl)
end end
end end
local objectreturn = _Serialize(tbl) local objectreturn = _Serialize(tbl)
return objectreturn return objectreturn
end end
@@ -339,18 +339,34 @@ UTILS.MetersToNM = function(meters)
return meters/1852 return meters/1852
end end
UTILS.KiloMetersToNM = function(kilometers)
return kilometers/1852*1000
end
UTILS.MetersToSM = function(meters) UTILS.MetersToSM = function(meters)
return meters/1609.34 return meters/1609.34
end end
UTILS.KiloMetersToSM = function(kilometers)
return kilometers/1609.34*1000
end
UTILS.MetersToFeet = function(meters) UTILS.MetersToFeet = function(meters)
return meters/0.3048 return meters/0.3048
end end
UTILS.KiloMetersToFeet = function(kilometers)
return kilometers/0.3048*1000
end
UTILS.NMToMeters = function(NM) UTILS.NMToMeters = function(NM)
return NM*1852 return NM*1852
end end
UTILS.NMToKiloMeters = function(NM)
return NM*1852/1000
end
UTILS.FeetToMeters = function(feet) UTILS.FeetToMeters = function(feet)
return feet*0.3048 return feet*0.3048
end end
@@ -400,7 +416,7 @@ end
-- @param #number Celcius Temperature in degrees Celsius. -- @param #number Celcius Temperature in degrees Celsius.
-- @return #number Temperature in degrees Farenheit. -- @return #number Temperature in degrees Farenheit.
UTILS.CelciusToFarenheit = function( Celcius ) UTILS.CelciusToFarenheit = function( Celcius )
return Celcius * 9/5 + 32 return Celcius * 9/5 + 32
end end
--- Convert pressure from hecto Pascal (hPa) to inches of mercury (inHg). --- Convert pressure from hecto Pascal (hPa) to inches of mercury (inHg).
@@ -415,7 +431,7 @@ end
-- @param #number altitude Altitude in feet -- @param #number altitude Altitude in feet
-- @return #number Corrected KIAS -- @return #number Corrected KIAS
UTILS.KnotsToAltKIAS = function( knots, altitude ) UTILS.KnotsToAltKIAS = function( knots, altitude )
return (knots * 0.018 * (altitude / 1000)) + knots return (knots * 0.018 * (altitude / 1000)) + knots
end end
--- Convert pressure from hecto Pascal (hPa) to millimeters of mercury (mmHg). --- Convert pressure from hecto Pascal (hPa) to millimeters of mercury (mmHg).
@@ -534,23 +550,23 @@ UTILS.tostringMGRS = function(MGRS, acc) --R2.1
-- Test if Easting/Northing have less than 4 digits. -- Test if Easting/Northing have less than 4 digits.
--MGRS.Easting=123 -- should be 00123 --MGRS.Easting=123 -- should be 00123
--MGRS.Northing=5432 -- should be 05432 --MGRS.Northing=5432 -- should be 05432
-- Truncate rather than round MGRS grid! -- Truncate rather than round MGRS grid!
local Easting=tostring(MGRS.Easting) local Easting=tostring(MGRS.Easting)
local Northing=tostring(MGRS.Northing) local Northing=tostring(MGRS.Northing)
-- Count number of missing digits. Easting/Northing should have 5 digits. However, it is passed as a number. Therefore, any leading zeros would not be displayed by lua. -- Count number of missing digits. Easting/Northing should have 5 digits. However, it is passed as a number. Therefore, any leading zeros would not be displayed by lua.
local nE=5-string.len(Easting) local nE=5-string.len(Easting)
local nN=5-string.len(Northing) local nN=5-string.len(Northing)
-- Get leading zeros (if any). -- Get leading zeros (if any).
for i=1,nE do Easting="0"..Easting end for i=1,nE do Easting="0"..Easting end
for i=1,nN do Northing="0"..Northing end for i=1,nN do Northing="0"..Northing end
-- Return MGRS string. -- Return MGRS string.
return string.format("%s %s %s %s", MGRS.UTMZone, MGRS.MGRSDigraph, string.sub(Easting, 1, acc), string.sub(Northing, 1, acc)) return string.format("%s %s %s %s", MGRS.UTMZone, MGRS.MGRSDigraph, string.sub(Easting, 1, acc), string.sub(Northing, 1, acc))
end end
end end
@@ -578,7 +594,7 @@ function UTILS.spairs( t, order )
for k in pairs(t) do keys[#keys+1] = k end for k in pairs(t) do keys[#keys+1] = k end
-- if order function given, sort by it by passing the table and keys a, b, -- if order function given, sort by it by passing the table and keys a, b,
-- otherwise just sort the keys -- otherwise just sort the keys
if order then if order then
table.sort(keys, function(a,b) return order(t, a, b) end) table.sort(keys, function(a,b) return order(t, a, b) end)
else else
@@ -604,7 +620,7 @@ function UTILS.kpairs( t, getkey, order )
for k, o in pairs(t) do keys[#keys+1] = k keyso[#keyso+1] = getkey( o ) end for k, o in pairs(t) do keys[#keys+1] = k keyso[#keyso+1] = getkey( o ) end
-- if order function given, sort by it by passing the table and keys a, b, -- if order function given, sort by it by passing the table and keys a, b,
-- otherwise just sort the keys -- otherwise just sort the keys
if order then if order then
table.sort(keys, function(a,b) return order(t, a, b) end) table.sort(keys, function(a,b) return order(t, a, b) end)
else else
@@ -624,7 +640,7 @@ 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.
function UTILS.rpairs( t ) function UTILS.rpairs( t )
-- collect the keys -- collect the keys
local keys = {} local keys = {}
for k in pairs(t) do keys[#keys+1] = k end for k in pairs(t) do keys[#keys+1] = k end
@@ -635,7 +651,7 @@ function UTILS.rpairs( t )
random[i] = keys[k] random[i] = keys[k]
table.remove( keys, k ) table.remove( keys, k )
end end
-- return the iterator function -- return the iterator function
local i = 0 local i = 0
return function() return function()
@@ -751,12 +767,12 @@ end
function UTILS.GetCharacters(str) function UTILS.GetCharacters(str)
local chars={} local chars={}
for i=1,#str do for i=1,#str do
local c=str:sub(i,i) local c=str:sub(i,i)
table.insert(chars, c) table.insert(chars, c)
end end
return chars return chars
end end
@@ -765,15 +781,15 @@ end
-- @param #boolean short (Optional) If true, use short output, i.e. (HH:)MM:SS without day. -- @param #boolean short (Optional) If true, use short output, i.e. (HH:)MM:SS without day.
-- @return #string Time in format Hours:Minutes:Seconds+Days (HH:MM:SS+D). -- @return #string Time in format Hours:Minutes:Seconds+Days (HH:MM:SS+D).
function UTILS.SecondsToClock(seconds, short) function UTILS.SecondsToClock(seconds, short)
-- Nil check. -- Nil check.
if seconds==nil then if seconds==nil then
return nil return nil
end end
-- Seconds -- Seconds
local seconds = tonumber(seconds) local seconds = tonumber(seconds)
-- Seconds of this day. -- Seconds of this day.
local _seconds=seconds%(60*60*24) local _seconds=seconds%(60*60*24)
@@ -803,10 +819,10 @@ function UTILS.SecondsOfToday()
-- Time in seconds. -- Time in seconds.
local time=timer.getAbsTime() local time=timer.getAbsTime()
-- Short format without days since mission start. -- Short format without days since mission start.
local clock=UTILS.SecondsToClock(time, true) local clock=UTILS.SecondsToClock(time, true)
-- Time is now the seconds passed since last midnight. -- Time is now the seconds passed since last midnight.
return UTILS.ClockToSeconds(clock) return UTILS.ClockToSeconds(clock)
end end
@@ -821,24 +837,24 @@ end
-- @param #string clock String of clock time. E.g., "06:12:35" or "5:1:30+1". Format is (H)H:(M)M:((S)S)(+D) H=Hours, M=Minutes, S=Seconds, D=Days. -- @param #string clock String of clock time. E.g., "06:12:35" or "5:1:30+1". Format is (H)H:(M)M:((S)S)(+D) H=Hours, M=Minutes, S=Seconds, D=Days.
-- @return #number Seconds. Corresponds to what you cet from timer.getAbsTime() function. -- @return #number Seconds. Corresponds to what you cet from timer.getAbsTime() function.
function UTILS.ClockToSeconds(clock) function UTILS.ClockToSeconds(clock)
-- Nil check. -- Nil check.
if clock==nil then if clock==nil then
return nil return nil
end end
-- Seconds init. -- Seconds init.
local seconds=0 local seconds=0
-- Split additional days. -- Split additional days.
local dsplit=UTILS.Split(clock, "+") local dsplit=UTILS.Split(clock, "+")
-- Convert days to seconds. -- Convert days to seconds.
if #dsplit>1 then if #dsplit>1 then
seconds=seconds+tonumber(dsplit[2])*60*60*24 seconds=seconds+tonumber(dsplit[2])*60*60*24
end end
-- Split hours, minutes, seconds -- Split hours, minutes, seconds
local tsplit=UTILS.Split(dsplit[1], ":") local tsplit=UTILS.Split(dsplit[1], ":")
-- Get time in seconds -- Get time in seconds
@@ -856,7 +872,7 @@ function UTILS.ClockToSeconds(clock)
end end
i=i+1 i=i+1
end end
return seconds return seconds
end end
@@ -868,12 +884,12 @@ function UTILS.DisplayMissionTime(duration)
local mission_time=Tnow-timer.getTime0() local mission_time=Tnow-timer.getTime0()
local mission_time_minutes=mission_time/60 local mission_time_minutes=mission_time/60
local mission_time_seconds=mission_time%60 local mission_time_seconds=mission_time%60
local local_time=UTILS.SecondsToClock(Tnow) local local_time=UTILS.SecondsToClock(Tnow)
local text=string.format("Time: %s - %02d:%02d", local_time, mission_time_minutes, mission_time_seconds) local text=string.format("Time: %s - %02d:%02d", local_time, mission_time_minutes, mission_time_seconds)
MESSAGE:New(text, duration):ToAll() MESSAGE:New(text, duration):ToAll()
end end
--- Replace illegal characters [<>|/?*:\\] in a string. --- Replace illegal characters [<>|/?*:\\] in a string.
-- @param #string Text Input text. -- @param #string Text Input text.
-- @param #string ReplaceBy Replace illegal characters by this character or string. Default underscore "_". -- @param #string ReplaceBy Replace illegal characters by this character or string. Default underscore "_".
-- @return #string The input text with illegal chars replaced. -- @return #string The input text with illegal chars replaced.
@@ -894,28 +910,28 @@ function UTILS.RandomGaussian(x0, sigma, xmin, xmax, imax)
-- Standard deviation. Default 10 if not given. -- Standard deviation. Default 10 if not given.
sigma=sigma or 10 sigma=sigma or 10
-- Max attempts. -- Max attempts.
imax=imax or 100 imax=imax or 100
local r local r
local gotit=false local gotit=false
local i=0 local i=0
while not gotit do while not gotit do
-- Uniform numbers in [0,1). We need two. -- Uniform numbers in [0,1). We need two.
local x1=math.random() local x1=math.random()
local x2=math.random() local x2=math.random()
-- Transform to Gaussian exp(-(x-x0)²/(2*sigma²). -- Transform to Gaussian exp(-(x-x0)°/(2*sigma°).
r = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.cos(2*math.pi * x2) + x0 r = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.cos(2*math.pi * x2) + x0
i=i+1 i=i+1
if (r>=xmin and r<=xmax) or i>imax then if (r>=xmin and r<=xmax) or i>imax then
gotit=true gotit=true
end end
end end
return r return r
end end
@@ -940,9 +956,9 @@ function UTILS.Randomize(value, fac, lower, upper)
else else
max=value+value*fac max=value+value*fac
end end
local r=math.random(min, max) local r=math.random(min, max)
return r return r
end end
@@ -961,6 +977,33 @@ function UTILS.VecNorm(a)
return math.sqrt(UTILS.VecDot(a, a)) return math.sqrt(UTILS.VecDot(a, a))
end 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.
-- @return #number Distance between the vectors.
function UTILS.VecDist2D(a, b)
local c={x=b.x-a.x, y=b.y-a.y}
local d=math.sqrt(c.x*c.x+c.y*c.y)
return d
end
--- Calculate the distance between two 3D vectors.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return #number Distance between the vectors.
function UTILS.VecDist3D(a, b)
local c={x=b.x-a.x, y=b.y-a.y, z=b.z-a.z}
local d=math.sqrt(UTILS.VecDot(c, c))
return d
end
--- Calculate the [cross product](https://en.wikipedia.org/wiki/Cross_product) of two 3D vectors. The result is a 3D vector. --- Calculate the [cross product](https://en.wikipedia.org/wiki/Cross_product) of two 3D vectors. The result is a 3D vector.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components.
@@ -969,7 +1012,7 @@ function UTILS.VecCross(a, b)
return {x=a.y*b.z - a.z*b.y, y=a.z*b.x - a.x*b.z, z=a.x*b.y - a.y*b.x} return {x=a.y*b.z - a.z*b.y, y=a.z*b.x - a.x*b.z, z=a.x*b.y - a.y*b.x}
end end
--- Calculate the difference between two 3D vectors by substracting the x,y,z components from each other. --- Calculate the difference between two 3D vectors by substracting the x,y,z components from each other.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return DCS#Vec3 Vector c=a-b with c(i)=a(i)-b(i), i=x,y,z. -- @return DCS#Vec3 Vector c=a-b with c(i)=a(i)-b(i), i=x,y,z.
@@ -977,7 +1020,7 @@ function UTILS.VecSubstract(a, b)
return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z} return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z}
end end
--- Calculate the total vector of two 3D vectors by adding the x,y,z components of each other. --- Calculate the total vector of two 3D vectors by adding the x,y,z components of each other.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return DCS#Vec3 Vector c=a+b with c(i)=a(i)+b(i), i=x,y,z. -- @return DCS#Vec3 Vector c=a+b with c(i)=a(i)+b(i), i=x,y,z.
@@ -985,14 +1028,14 @@ function UTILS.VecAdd(a, b)
return {x=a.x+b.x, y=a.y+b.y, z=a.z+b.z} return {x=a.x+b.x, y=a.y+b.y, z=a.z+b.z}
end end
--- Calculate the angle between two 3D vectors. --- Calculate the angle between two 3D vectors.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return #number Angle alpha between and b in degrees. alpha=acos(a*b)/(|a||b|), (* denotes the dot product). -- @return #number Angle alpha between and b in degrees. alpha=acos(a*b)/(|a||b|), (* denotes the dot product).
function UTILS.VecAngle(a, b) function UTILS.VecAngle(a, b)
local cosalpha=UTILS.VecDot(a,b)/(UTILS.VecNorm(a)*UTILS.VecNorm(b)) local cosalpha=UTILS.VecDot(a,b)/(UTILS.VecNorm(a)*UTILS.VecNorm(b))
local alpha=0 local alpha=0
if cosalpha>=0.9999999999 then --acos(1) is not defined. if cosalpha>=0.9999999999 then --acos(1) is not defined.
alpha=0 alpha=0
@@ -1000,8 +1043,8 @@ function UTILS.VecAngle(a, b)
alpha=math.pi alpha=math.pi
else else
alpha=math.acos(cosalpha) alpha=math.acos(cosalpha)
end end
return math.deg(alpha) return math.deg(alpha)
end end
@@ -1025,18 +1068,18 @@ function UTILS.HdgDiff(h1, h2)
-- Angle in rad. -- Angle in rad.
local alpha= math.rad(tonumber(h1)) local alpha= math.rad(tonumber(h1))
local beta = math.rad(tonumber(h2)) local beta = math.rad(tonumber(h2))
-- Runway vector. -- Runway vector.
local v1={x=math.cos(alpha), y=0, z=math.sin(alpha)} local v1={x=math.cos(alpha), y=0, z=math.sin(alpha)}
local v2={x=math.cos(beta), y=0, z=math.sin(beta)} local v2={x=math.cos(beta), y=0, z=math.sin(beta)}
local delta=UTILS.VecAngle(v1, v2) local delta=UTILS.VecAngle(v1, v2)
return math.abs(delta) return math.abs(delta)
end end
--- Translate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. --- Translate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param #number distance The distance to translate. -- @param #number distance The distance to translate.
-- @param #number angle Rotation angle in degrees. -- @param #number angle Rotation angle in degrees.
@@ -1052,21 +1095,21 @@ function UTILS.VecTranslate(a, distance, angle)
return {x=TX, y=a.y, z=TY} return {x=TX, y=a.y, z=TY}
end end
--- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. --- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param #number angle Rotation angle in degrees. -- @param #number angle Rotation angle in degrees.
-- @return DCS#Vec3 Vector rotated in the (x,z) plane. -- @return DCS#Vec3 Vector rotated in the (x,z) plane.
function UTILS.Rotate2D(a, angle) function UTILS.Rotate2D(a, angle)
local phi=math.rad(angle) local phi=math.rad(angle)
local x=a.z local x=a.z
local y=a.x local y=a.x
local Z=x*math.cos(phi)-y*math.sin(phi) local Z=x*math.cos(phi)-y*math.sin(phi)
local X=x*math.sin(phi)+y*math.cos(phi) local X=x*math.sin(phi)+y*math.cos(phi)
local Y=a.y local Y=a.y
local A={x=X, y=Y, z=Z} local A={x=X, y=Y, z=Z}
return A return A
@@ -1084,17 +1127,17 @@ function UTILS.TACANToFrequency(TACANChannel, TACANMode)
end end
if TACANMode ~= "X" and TACANMode ~= "Y" then if TACANMode ~= "X" and TACANMode ~= "Y" then
return nil -- error in arguments return nil -- error in arguments
end end
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. -- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
-- I have no idea what it does but it seems to work -- I have no idea what it does but it seems to work
local A = 1151 -- 'X', channel >= 64 local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64 local B = 64 -- channel >= 64
if TACANChannel < 64 then if TACANChannel < 64 then
B = 1 B = 1
end end
if TACANMode == 'Y' then if TACANMode == 'Y' then
A = 1025 A = 1025
if TACANChannel < 64 then if TACANChannel < 64 then
@@ -1105,7 +1148,7 @@ function UTILS.TACANToFrequency(TACANChannel, TACANMode)
A = 962 A = 962
end end
end end
return (A + TACANChannel - B) * 1000000 return (A + TACANChannel - B) * 1000000
end end
@@ -1132,13 +1175,13 @@ end
-- @param #number Time (Optional) Abs. time in seconds. Default now, i.e. the value return from timer.getAbsTime(). -- @param #number Time (Optional) Abs. time in seconds. Default now, i.e. the value return from timer.getAbsTime().
-- @return #number Day of the mission. Mission starts on day 0. -- @return #number Day of the mission. Mission starts on day 0.
function UTILS.GetMissionDay(Time) function UTILS.GetMissionDay(Time)
Time=Time or timer.getAbsTime() Time=Time or timer.getAbsTime()
local clock=UTILS.SecondsToClock(Time, false) local clock=UTILS.SecondsToClock(Time, false)
local x=tonumber(UTILS.Split(clock, "+")[2]) local x=tonumber(UTILS.Split(clock, "+")[2])
return x return x
end end
@@ -1148,11 +1191,11 @@ end
function UTILS.GetMissionDayOfYear(Time) function UTILS.GetMissionDayOfYear(Time)
local Date, Year, Month, Day=UTILS.GetDCSMissionDate() local Date, Year, Month, Day=UTILS.GetDCSMissionDate()
local d=UTILS.GetMissionDay(Time) local d=UTILS.GetMissionDay(Time)
return UTILS.GetDayOfYear(Year, Month, Day)+d return UTILS.GetDayOfYear(Year, Month, Day)+d
end end
--- Returns the current date. --- Returns the current date.
@@ -1164,20 +1207,20 @@ function UTILS.GetDate()
-- Mission start date -- Mission start date
local date, year, month, day=UTILS.GetDCSMissionDate() local date, year, month, day=UTILS.GetDCSMissionDate()
local time=timer.getAbsTime() local time=timer.getAbsTime()
local clock=UTILS.SecondsToClock(time, false) local clock=UTILS.SecondsToClock(time, false)
local x=tonumber(UTILS.Split(clock, "+")[2]) local x=tonumber(UTILS.Split(clock, "+")[2])
local day=day+x local day=day+x
end end
--- Returns the magnetic declination of the map. --- Returns the magnetic declination of the map.
-- Returned values for the current maps are: -- Returned values for the current maps are:
-- --
-- * Caucasus +6 (East), year ~ 2011 -- * Caucasus +6 (East), year ~ 2011
-- * NTTR +12 (East), year ~ 2011 -- * NTTR +12 (East), year ~ 2011
-- * Normandy -10 (West), year ~ 1944 -- * Normandy -10 (West), year ~ 1944
@@ -1191,7 +1234,7 @@ function UTILS.GetMagneticDeclination(map)
-- Map. -- Map.
map=map or UTILS.GetDCSMap() map=map or UTILS.GetDCSMap()
local declination=0 local declination=0
if map==DCSMAP.Caucasus then if map==DCSMAP.Caucasus then
declination=6 declination=6
@@ -1228,12 +1271,12 @@ function UTILS.FileExists(file)
end end
else else
return nil return nil
end end
end end
--- Checks the current memory usage collectgarbage("count"). Info is printed to the DCS log file. Time stamp is the current mission runtime. --- Checks the current memory usage collectgarbage("count"). Info is printed to the DCS log file. Time stamp is the current mission runtime.
-- @param #boolean output If true, print to DCS log file. -- @param #boolean output If true, print to DCS log file.
-- @return #number Memory usage in kByte. -- @return #number Memory usage in kByte.
function UTILS.CheckMemory(output) function UTILS.CheckMemory(output)
local time=timer.getTime() local time=timer.getTime()
local clock=UTILS.SecondsToClock(time) local clock=UTILS.SecondsToClock(time)
@@ -1263,7 +1306,7 @@ function UTILS.GetCoalitionName(Coalition)
else else
return "Unknown" return "Unknown"
end end
end end
--- Get the modulation name from its numerical value. --- Get the modulation name from its numerical value.
@@ -1282,7 +1325,7 @@ function UTILS.GetModulationName(Modulation)
else else
return "Unknown" return "Unknown"
end end
end end
--- Get the callsign name from its enumerator value --- Get the callsign name from its enumerator value
@@ -1295,7 +1338,7 @@ function UTILS.GetCallsignName(Callsign)
return name return name
end end
end end
for name, value in pairs(CALLSIGN.AWACS) do for name, value in pairs(CALLSIGN.AWACS) do
if value==Callsign then if value==Callsign then
return name return name
@@ -1307,7 +1350,7 @@ function UTILS.GetCallsignName(Callsign)
return name return name
end end
end end
for name, value in pairs(CALLSIGN.Tanker) do for name, value in pairs(CALLSIGN.Tanker) do
if value==Callsign then if value==Callsign then
return name return name
@@ -1336,7 +1379,7 @@ function UTILS.GMTToLocalTimeDifference()
elseif theatre==DCSMAP.Syria then elseif theatre==DCSMAP.Syria then
return 3 -- Damascus is UTC+3 hours return 3 -- Damascus is UTC+3 hours
elseif theatre==DCSMAP.MarianaIslands then elseif theatre==DCSMAP.MarianaIslands then
return 10 -- Guam is UTC+10 hours. return 10 -- Guam is UTC+10 hours.
else else
BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre)))
return 0 return 0
@@ -1353,11 +1396,11 @@ end
function UTILS.GetDayOfYear(Year, Month, Day) function UTILS.GetDayOfYear(Year, Month, Day)
local floor = math.floor local floor = math.floor
local n1 = floor(275 * Month / 9) local n1 = floor(275 * Month / 9)
local n2 = floor((Month + 9) / 12) local n2 = floor((Month + 9) / 12)
local n3 = (1 + floor((Year - 4 * floor(Year / 4) + 2) / 3)) local n3 = (1 + floor((Year - 4 * floor(Year / 4) + 2) / 3))
return n1 - (n2 * n3) + Day - 30 return n1 - (n2 * n3) + Day - 30
end end
@@ -1370,14 +1413,14 @@ end
-- @return #number Sun rise/set in seconds of the day. -- @return #number Sun rise/set in seconds of the day.
function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal) function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal)
-- Defaults -- Defaults
local zenith=90.83 local zenith=90.83
local latitude=Latitude local latitude=Latitude
local longitude=Longitude local longitude=Longitude
local rising=Rising local rising=Rising
local n=DayOfYear local n=DayOfYear
Tlocal=Tlocal or 0 Tlocal=Tlocal or 0
-- Short cuts. -- Short cuts.
local rad = math.rad local rad = math.rad
@@ -1404,47 +1447,47 @@ function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal)
return val return val
end end
end end
-- Convert the longitude to hour value and calculate an approximate time -- Convert the longitude to hour value and calculate an approximate time
local lng_hour = longitude / 15 local lng_hour = longitude / 15
local t local t
if rising then -- Rising time is desired if rising then -- Rising time is desired
t = n + ((6 - lng_hour) / 24) t = n + ((6 - lng_hour) / 24)
else -- Setting time is desired else -- Setting time is desired
t = n + ((18 - lng_hour) / 24) t = n + ((18 - lng_hour) / 24)
end end
-- Calculate the Sun's mean anomaly -- Calculate the Sun's mean anomaly
local M = (0.9856 * t) - 3.289 local M = (0.9856 * t) - 3.289
-- Calculate the Sun's true longitude -- Calculate the Sun's true longitude
local L = fit_into_range(M + (1.916 * sin(M)) + (0.020 * sin(2 * M)) + 282.634, 0, 360) local L = fit_into_range(M + (1.916 * sin(M)) + (0.020 * sin(2 * M)) + 282.634, 0, 360)
-- Calculate the Sun's right ascension -- Calculate the Sun's right ascension
local RA = fit_into_range(atan(0.91764 * tan(L)), 0, 360) local RA = fit_into_range(atan(0.91764 * tan(L)), 0, 360)
-- Right ascension value needs to be in the same quadrant as L -- Right ascension value needs to be in the same quadrant as L
local Lquadrant = floor(L / 90) * 90 local Lquadrant = floor(L / 90) * 90
local RAquadrant = floor(RA / 90) * 90 local RAquadrant = floor(RA / 90) * 90
RA = RA + Lquadrant - RAquadrant RA = RA + Lquadrant - RAquadrant
-- Right ascension value needs to be converted into hours -- Right ascension value needs to be converted into hours
RA = RA / 15 RA = RA / 15
-- Calculate the Sun's declination -- Calculate the Sun's declination
local sinDec = 0.39782 * sin(L) local sinDec = 0.39782 * sin(L)
local cosDec = cos(asin(sinDec)) local cosDec = cos(asin(sinDec))
-- Calculate the Sun's local hour angle -- Calculate the Sun's local hour angle
local cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude)) local cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude))
if rising and cosH > 1 then if rising and cosH > 1 then
return "N/R" -- The sun never rises on this location on the specified date return "N/R" -- The sun never rises on this location on the specified date
elseif cosH < -1 then elseif cosH < -1 then
return "N/S" -- The sun never sets on this location on the specified date return "N/S" -- The sun never sets on this location on the specified date
end end
-- Finish calculating H and convert into hours -- Finish calculating H and convert into hours
local H local H
if rising then if rising then
@@ -1453,13 +1496,13 @@ function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal)
H = acos(cosH) H = acos(cosH)
end end
H = H / 15 H = H / 15
-- Calculate local mean time of rising/setting -- Calculate local mean time of rising/setting
local T = H + RA - (0.06571 * t) - 6.622 local T = H + RA - (0.06571 * t) - 6.622
-- Adjust back to UTC -- Adjust back to UTC
local UT = fit_into_range(T - lng_hour +Tlocal, 0, 24) local UT = fit_into_range(T - lng_hour +Tlocal, 0, 24)
return floor(UT)*60*60+frac(UT)*60*60--+Tlocal*60*60 return floor(UT)*60*60+frac(UT)*60*60--+Tlocal*60*60
end end
@@ -1534,17 +1577,17 @@ function UTILS.IsLoadingDoorOpen( unit_name )
local unit = Unit.getByName(unit_name) local unit = Unit.getByName(unit_name)
if unit ~= nil then if unit ~= nil then
local type_name = unit:getTypeName() local type_name = unit:getTypeName()
if type_name == "Mi-8MT" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then if type_name == "Mi-8MT" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) < 0 then
BASE:T(unit_name .. " Cargo doors are open or cargo door not present") BASE:T(unit_name .. " Cargo doors are open or cargo door not present")
ret_val = true ret_val = true
end end
if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then
BASE:T(unit_name .. " a side door is open") BASE:T(unit_name .. " a side door is open")
ret_val = true ret_val = true
end end
if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then
BASE:T(unit_name .. " a side door is open ") BASE:T(unit_name .. " a side door is open ")
ret_val = true ret_val = true
@@ -1555,13 +1598,33 @@ function UTILS.IsLoadingDoorOpen( unit_name )
ret_val = true ret_val = true
end end
if string.find(type_name, "Hercules") and unit:getDrawArgumentValue(1215) == 1 and unit:getDrawArgumentValue(1216) == 1 then
BASE:T(unit_name .. " rear doors are open")
ret_val = true
end
if string.find(type_name, "Hercules") and (unit:getDrawArgumentValue(1220) == 1 or unit:getDrawArgumentValue(1221) == 1) then
BASE:T(unit_name .. " para doors are open")
ret_val = true
end
if string.find(type_name, "Hercules") and unit:getDrawArgumentValue(1217) == 1 then
BASE:T(unit_name .. " side door is open")
ret_val = true
end
if string.find(type_name, "Bell-47") then -- bell aint got no doors so always ready to load injured soldiers
BASE:T(unit_name .. " door is open")
ret_val = true
end
if ret_val == false then if ret_val == false then
BASE:T(unit_name .. " all doors are closed") BASE:T(unit_name .. " all doors are closed")
end end
return ret_val return ret_val
end -- nil end -- nil
return nil return nil
end end
@@ -1588,10 +1651,10 @@ function UTILS.GenerateVHFrequencies()
local _skipFrequencies = { local _skipFrequencies = {
214,274,291.5,295,297.5, 214,274,291.5,295,297.5,
300.5,304,307,309.5,311,312,312.5,316, 300.5,304,307,309.5,311,312,312.5,316,
320,324,328,329,330,336,337, 320,324,328,329,330,332,336,337,
342,343,348,351,352,353,358, 342,343,348,351,352,353,358,
363,365,368,372.5,374, 363,365,368,372.5,374,
380,381,384,389,395,396, 380,381,384,385,389,395,396,
414,420,430,432,435,440,450,455,462,470,485, 414,420,430,432,435,440,450,455,462,470,485,
507,515,520,525,528,540,550,560,570,577,580, 507,515,520,525,528,540,550,560,570,577,580,
602,625,641,662,670,680,682,690, 602,625,641,662,670,680,682,690,
@@ -1600,13 +1663,13 @@ function UTILS.GenerateVHFrequencies()
905,907,920,935,942,950,995, 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
} }
local FreeVHFFrequencies = {} local FreeVHFFrequencies = {}
-- first range -- first range
local _start = 200000 local _start = 200000
while _start < 400000 do while _start < 400000 do
-- skip existing NDB frequencies# -- skip existing NDB frequencies#
local _found = false local _found = false
for _, value in pairs(_skipFrequencies) do for _, value in pairs(_skipFrequencies) do
@@ -1620,7 +1683,7 @@ function UTILS.GenerateVHFrequencies()
end end
_start = _start + 10000 _start = _start + 10000
end end
-- second range -- second range
_start = 400000 _start = 400000
while _start < 850000 do while _start < 850000 do
@@ -1637,7 +1700,7 @@ function UTILS.GenerateVHFrequencies()
end end
_start = _start + 10000 _start = _start + 10000
end end
-- third range -- third range
_start = 850000 _start = 850000
while _start <= 999000 do -- adjusted for Gazelle while _start <= 999000 do -- adjusted for Gazelle
@@ -1677,7 +1740,7 @@ end
-- @return #table Laser Codes. -- @return #table Laser Codes.
function UTILS.GenerateLaserCodes() function UTILS.GenerateLaserCodes()
local jtacGeneratedLaserCodes = {} local jtacGeneratedLaserCodes = {}
-- helper function -- helper function
local function ContainsDigit(_number, _numberToFind) local function ContainsDigit(_number, _numberToFind)
local _thisNumber = _number local _thisNumber = _number
@@ -1691,14 +1754,14 @@ function UTILS.GenerateLaserCodes()
end end
return false return false
end end
-- generate list of laser codes -- generate list of laser codes
local _code = 1111 local _code = 1111
local _count = 1 local _count = 1
while _code < 1777 and _count < 30 do while _code < 1777 and _count < 30 do
while true do while true do
_code = _code + 1 _code = _code + 1
if not self:_ContainsDigit(_code, 8) if not ContainsDigit(_code, 8)
and not ContainsDigit(_code, 9) and not ContainsDigit(_code, 9)
and not ContainsDigit(_code, 0) then and not ContainsDigit(_code, 0) then
table.insert(jtacGeneratedLaserCodes, _code) table.insert(jtacGeneratedLaserCodes, _code)

View File

@@ -17,6 +17,7 @@
-- @field #table CategoryName Names of airbase categories. -- @field #table CategoryName Names of airbase categories.
-- @field #string AirbaseName Name of the airbase. -- @field #string AirbaseName Name of the airbase.
-- @field #number AirbaseID Airbase ID. -- @field #number AirbaseID Airbase ID.
-- @field Core.Zone#ZONE AirbaseZone Circular zone around the airbase with a radius of 2500 meters. For ships this is a ZONE_UNIT object.
-- @field #number category Airbase category. -- @field #number category Airbase category.
-- @field #table descriptors DCS descriptors. -- @field #table descriptors DCS descriptors.
-- @field #boolean isAirdrome Airbase is an airdrome. -- @field #boolean isAirdrome Airbase is an airdrome.
@@ -526,19 +527,19 @@ function AIRBASE:Register(AirbaseName)
-- Inherit everything from positionable. -- Inherit everything from positionable.
local self=BASE:Inherit(self, POSITIONABLE:New(AirbaseName)) --#AIRBASE local self=BASE:Inherit(self, POSITIONABLE:New(AirbaseName)) --#AIRBASE
-- Set airbase name. -- Set airbase name.
self.AirbaseName=AirbaseName self.AirbaseName=AirbaseName
-- Set airbase ID. -- Set airbase ID.
self.AirbaseID=self:GetID(true) self.AirbaseID=self:GetID(true)
-- Get descriptors. -- Get descriptors.
self.descriptors=self:GetDesc() self.descriptors=self:GetDesc()
-- Category. -- Category.
self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME
-- Set category. -- Set category.
if self.category==Airbase.Category.AIRDROME then if self.category==Airbase.Category.AIRDROME then
self.isAirdrome=true self.isAirdrome=true
@@ -546,20 +547,33 @@ function AIRBASE:Register(AirbaseName)
self.isHelipad=true self.isHelipad=true
elseif self.category==Airbase.Category.SHIP then elseif self.category==Airbase.Category.SHIP then
self.isShip=true self.isShip=true
-- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects()
if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then
self.isHelipad=true
self.isShip=false
self.category=Airbase.Category.HELIPAD
_DATABASE:AddStatic(AirbaseName)
end
else else
self:E("ERROR: Unknown airbase category!") self:E("ERROR: Unknown airbase category!")
end end
self:_InitParkingSpots() self:_InitParkingSpots()
local vec2=self:GetVec2() local vec2=self:GetVec2()
-- Init coordinate. -- Init coordinate.
self:GetCoordinate() self:GetCoordinate()
if vec2 then if vec2 then
-- TODO: For ships we need a moving zone. if self.isShip then
self.AirbaseZone=ZONE_RADIUS:New( AirbaseName, vec2, 2500 ) local unit=UNIT:FindByName(AirbaseName)
if unit then
self.AirbaseZone=ZONE_UNIT:New(AirbaseName, unit, 2500)
end
else
self.AirbaseZone=ZONE_RADIUS:New(AirbaseName, vec2, 2500)
end
else else
self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName)) self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName))
end end
@@ -693,7 +707,7 @@ function AIRBASE:GetID(unique)
local airbaseID=tonumber(DCSAirbase:getID()) local airbaseID=tonumber(DCSAirbase:getID())
local airbaseCategory=self:GetAirbaseCategory() local airbaseCategory=self:GetAirbaseCategory()
if AirbaseName==self.AirbaseName then if AirbaseName==self.AirbaseName then
if airbaseCategory==Airbase.Category.SHIP or airbaseCategory==Airbase.Category.HELIPAD then if airbaseCategory==Airbase.Category.SHIP or airbaseCategory==Airbase.Category.HELIPAD then
-- Ships get a negative sign as their unit number might be the same as the ID of another airbase. -- Ships get a negative sign as their unit number might be the same as the ID of another airbase.
@@ -932,16 +946,16 @@ function AIRBASE:_InitParkingSpots()
-- Init table. -- Init table.
self.parking={} self.parking={}
self.parkingByID={} self.parkingByID={}
self.NparkingTotal=0 self.NparkingTotal=0
self.NparkingTerminal={} self.NparkingTerminal={}
for _,terminalType in pairs(AIRBASE.TerminalType) do for _,terminalType in pairs(AIRBASE.TerminalType) do
self.NparkingTerminal[terminalType]=0 self.NparkingTerminal[terminalType]=0
end end
-- Put coordinates of parking spots into table. -- Put coordinates of parking spots into table.
for _,spot in pairs(parkingdata) do for _,spot in pairs(parkingdata) do
-- New parking spot. -- New parking spot.
local park={} --#AIRBASE.ParkingSpot local park={} --#AIRBASE.ParkingSpot
park.Vec3=spot.vTerminalPos park.Vec3=spot.vTerminalPos
@@ -952,15 +966,15 @@ function AIRBASE:_InitParkingSpots()
park.TerminalID0=spot.Term_Index_0 park.TerminalID0=spot.Term_Index_0
park.TerminalType=spot.Term_Type park.TerminalType=spot.Term_Type
park.TOAC=spot.TO_AC park.TOAC=spot.TO_AC
self.NparkingTotal=self.NparkingTotal+1 self.NparkingTotal=self.NparkingTotal+1
for _,terminalType in pairs(AIRBASE.TerminalType) do for _,terminalType in pairs(AIRBASE.TerminalType) do
if self._CheckTerminalType(terminalType, park.TerminalType) then if self._CheckTerminalType(terminalType, park.TerminalType) then
self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1 self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1
end end
end end
self.parkingByID[park.TerminalID]=park self.parkingByID[park.TerminalID]=park
table.insert(self.parking, park) table.insert(self.parking, park)
end end
@@ -984,7 +998,7 @@ function AIRBASE:GetParkingSpotsTable(termtype)
-- Get parking data of all spots (free or occupied) -- Get parking data of all spots (free or occupied)
local parkingdata=self:GetParkingData(false) local parkingdata=self:GetParkingData(false)
-- Get parking data of all free spots. -- Get parking data of all free spots.
local parkingfree=self:GetParkingData(true) local parkingfree=self:GetParkingData(true)
@@ -1001,17 +1015,26 @@ function AIRBASE:GetParkingSpotsTable(termtype)
-- Put coordinates of parking spots into table. -- Put coordinates of parking spots into table.
local spots={} local spots={}
for _,_spot in pairs(parkingdata) do for _,_spot in pairs(parkingdata) do
if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then
local spot=self:_GetParkingSpotByID(_spot.Term_Index) local spot=self:_GetParkingSpotByID(_spot.Term_Index)
spot.Free=_isfree(_spot) -- updated if spot then
spot.TOAC=_spot.TO_AC -- updated
spot.Free=_isfree(_spot) -- updated
table.insert(spots, spot) spot.TOAC=_spot.TO_AC -- updated
table.insert(spots, spot)
else
self:E(string.format("ERROR: Parking spot %s is nil!", tostring(_spot.Term_Index)))
end
end end
end end
return spots return spots
@@ -1032,14 +1055,14 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
for _,_spot in pairs(parkingfree) do for _,_spot in pairs(parkingfree) do
if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) and _spot.Term_Index>0 then if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) and _spot.Term_Index>0 then
if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then
local spot=self:_GetParkingSpotByID(_spot.Term_Index) local spot=self:_GetParkingSpotByID(_spot.Term_Index)
spot.Free=true -- updated spot.Free=true -- updated
spot.TOAC=_spot.TO_AC -- updated spot.TOAC=_spot.TO_AC -- updated
table.insert(freespots, spot) table.insert(freespots, spot)
end end
end end
end end
@@ -1084,7 +1107,7 @@ function AIRBASE:MarkParkingSpots(termtype, mark)
-- Get airbase name. -- Get airbase name.
local airbasename=self:GetName() local airbasename=self:GetName()
self:E(string.format("Parking spots at %s for termial type %s:", airbasename, tostring(termtype))) self:E(string.format("Parking spots at %s for terminal type %s:", airbasename, tostring(termtype)))
for _,_spot in pairs(parkingdata) do for _,_spot in pairs(parkingdata) do
@@ -1118,7 +1141,7 @@ end
-- @param #table parkingdata (Optional) Parking spots data table. If not given it is automatically derived from the GetParkingSpotsTable() function. -- @param #table parkingdata (Optional) Parking spots data table. If not given it is automatically derived from the GetParkingSpotsTable() function.
-- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID. -- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID.
function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots, parkingdata) function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots, parkingdata)
-- Init default -- Init default
scanradius=scanradius or 50 scanradius=scanradius or 50
if scanunits==nil then if scanunits==nil then
@@ -1161,14 +1184,25 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype) parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype)
-- Get the aircraft size, i.e. it's longest side of x,z. -- Get the aircraft size, i.e. it's longest side of x,z.
local aircraft=group:GetUnit(1) local aircraft = nil -- fix local problem below
local _aircraftsize, ax,ay,az=aircraft:GetObjectSize() local _aircraftsize, ax,ay,az
if group and group.ClassName == "GROUP" then
aircraft=group:GetUnit(1)
_aircraftsize, ax,ay,az=aircraft:GetObjectSize()
else
-- SU27 dimensions
_aircraftsize = 23
ax = 23 -- length
ay = 7 -- height
az = 17 -- width
end
-- Number of spots we are looking for. Note that, e.g. grouping can require a number different from the group size! -- Number of spots we are looking for. Note that, e.g. grouping can require a number different from the group size!
local _nspots=nspots or group:GetSize() local _nspots=nspots or group:GetSize()
-- Debug info. -- Debug info.
self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at termial type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype))) self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype)))
-- Table of valid spots. -- Table of valid spots.
local validspots={} local validspots={}
@@ -1291,6 +1325,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
-- Retrun spots we found, even if there were not enough. -- Retrun spots we found, even if there were not enough.
return validspots return validspots
end end
--- Check black and white lists. --- Check black and white lists.
@@ -1392,7 +1427,7 @@ function AIRBASE:GetRunwayData(magvar, mark)
-- Get spawn points on runway. These can be used to determine the runway heading. -- Get spawn points on runway. These can be used to determine the runway heading.
local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway)
-- Debug: For finding the numbers of the spawn points belonging to each runway. -- Debug: For finding the numbers of the spawn points belonging to each runway.
if false then if false then
for i,_coord in pairs(runwaycoords) do for i,_coord in pairs(runwaycoords) do
@@ -1411,7 +1446,7 @@ function AIRBASE:GetRunwayData(magvar, mark)
-- Airbase name. -- Airbase name.
local name=self:GetName() local name=self:GetName()
-- Exceptions -- Exceptions
if name==AIRBASE.Nevada.Jean_Airport or if name==AIRBASE.Nevada.Jean_Airport or
@@ -1424,36 +1459,36 @@ function AIRBASE:GetRunwayData(magvar, mark)
-- 1-->4, 2-->3, 3-->2, 4-->1 -- 1-->4, 2-->3, 3-->2, 4-->1
exception=1 exception=1
elseif UTILS.GetDCSMap()==DCSMAP.Syria and N>=2 and elseif UTILS.GetDCSMap()==DCSMAP.Syria and N>=2 and
name~=AIRBASE.Syria.Minakh and name~=AIRBASE.Syria.Minakh and
name~=AIRBASE.Syria.Damascus and name~=AIRBASE.Syria.Damascus and
name~=AIRBASE.Syria.Khalkhalah and name~=AIRBASE.Syria.Khalkhalah and
name~=AIRBASE.Syria.Marj_Ruhayyil and name~=AIRBASE.Syria.Marj_Ruhayyil and
name~=AIRBASE.Syria.Beirut_Rafic_Hariri then name~=AIRBASE.Syria.Beirut_Rafic_Hariri then
-- 1-->3, 2-->4, 3-->1, 4-->2 -- 1-->3, 2-->4, 3-->1, 4-->2
exception=2 exception=2
end end
--- Function returning the index of the runway coordinate belonding to the given index i. --- Function returning the index of the runway coordinate belonding to the given index i.
local function f(i) local function f(i)
local j local j
if exception==1 then if exception==1 then
j=N-(i-1) -- 1-->4, 2-->3 j=N-(i-1) -- 1-->4, 2-->3
elseif exception==2 then elseif exception==2 then
if i<=N2 then if i<=N2 then
j=i+N2 -- 1-->3, 2-->4 j=i+N2 -- 1-->3, 2-->4
else else
j=i-N2 -- 3-->1, 4-->3 j=i-N2 -- 3-->1, 4-->3
end end
else else
if i%2==0 then if i%2==0 then
@@ -1461,9 +1496,9 @@ function AIRBASE:GetRunwayData(magvar, mark)
else else
j=i+1 -- odd 1-->2, 3-->4 j=i+1 -- odd 1-->2, 3-->4
end end
end end
-- Special case where there is no obvious order. -- Special case where there is no obvious order.
if name==AIRBASE.Syria.Beirut_Rafic_Hariri then if name==AIRBASE.Syria.Beirut_Rafic_Hariri then
if i==1 then if i==1 then
@@ -1496,7 +1531,7 @@ function AIRBASE:GetRunwayData(magvar, mark)
j=2 j=2
end end
end end
return j return j
end end
@@ -1505,7 +1540,7 @@ function AIRBASE:GetRunwayData(magvar, mark)
-- Get the other spawn point coordinate. -- Get the other spawn point coordinate.
local j=f(i) local j=f(i)
-- Debug info. -- Debug info.
--env.info(string.format("Runway i=%s j=%s (N=%d #runwaycoord=%d)", tostring(i), tostring(j), N, #runwaycoords)) --env.info(string.format("Runway i=%s j=%s (N=%d #runwaycoord=%d)", tostring(i), tostring(j), N, #runwaycoords))

View File

@@ -3779,8 +3779,9 @@ end
-- @param #number radius Radius of the relocation zone, default 500 -- @param #number radius Radius of the relocation zone, default 500
-- @param #boolean onroad If true, route on road (less problems with AI way finding), default true -- @param #boolean onroad If true, route on road (less problems with AI way finding), default true
-- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false -- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false
-- @param #string formation Formation string as in the mission editor, e.g. "Vee", "Diamond", "Line abreast", etc. Defaults to "Off Road"
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut) function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut, formation)
self:F2( { self.ControllableName } ) self:F2( { self.ControllableName } )
local _coord = self:GetCoordinate() local _coord = self:GetCoordinate()
@@ -3791,14 +3792,14 @@ function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortc
local _grptsk = {} local _grptsk = {}
local _candoroad = false local _candoroad = false
local _shortcut = shortcut or false local _shortcut = shortcut or false
local _formation = formation or "Off Road"
-- create a DCS Task an push it on the group -- create a DCS Task an push it on the group
-- TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,Shortcut,FromCoordinate,WaypointFunction,WaypointFunctionArguments)
if onroad then if onroad then
_grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,"Off Road",_shortcut) _grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,_formation,_shortcut)
self:Route(_grptsk,5) self:Route(_grptsk,5)
else else
self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,"Off Road") self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,_formation)
end end
return self return self

View File

@@ -1137,7 +1137,7 @@ end
-- @return #number Number of shells left. -- @return #number Number of shells left.
-- @return #number Number of rockets left. -- @return #number Number of rockets left.
-- @return #number Number of bombs left. -- @return #number Number of bombs left.
-- @return #number Number of missiles left. -- @return #number Number of missiles left.
function GROUP:GetAmmunition() function GROUP:GetAmmunition()
self:F( self.ControllableName ) self:F( self.ControllableName )
@@ -1147,6 +1147,7 @@ function GROUP:GetAmmunition()
local Nshells=0 local Nshells=0
local Nrockets=0 local Nrockets=0
local Nmissiles=0 local Nmissiles=0
local Nbombs=0
if DCSControllable then if DCSControllable then
@@ -1155,18 +1156,19 @@ function GROUP:GetAmmunition()
local Unit = UnitData -- Wrapper.Unit#UNIT local Unit = UnitData -- Wrapper.Unit#UNIT
-- Get ammo of the unit -- Get ammo of the unit
local ntot, nshells, nrockets, nmissiles = Unit:GetAmmunition() local ntot, nshells, nrockets, nbombs, nmissiles = Unit:GetAmmunition()
Ntot=Ntot+ntot Ntot=Ntot+ntot
Nshells=Nshells+nshells Nshells=Nshells+nshells
Nrockets=Nrockets+nrockets Nrockets=Nrockets+nrockets
Nmissiles=Nmissiles+nmissiles Nmissiles=Nmissiles+nmissiles
Nbombs=Nbombs+nbombs
end end
end end
return Ntot, Nshells, Nrockets, Nmissiles return Ntot, Nshells, Nrockets, Nbombs, Nmissiles
end end
@@ -2576,8 +2578,10 @@ end
-- @return #GROUP self -- @return #GROUP self
function GROUP:SetCommandInvisible(switch) function GROUP:SetCommandInvisible(switch)
self:F2( self.GroupName ) self:F2( self.GroupName )
local switch = switch or false if switch==nil then
local SetInvisible = {id = 'SetInvisible', params = {value = true}} switch=false
end
local SetInvisible = {id = 'SetInvisible', params = {value = switch}}
self:SetCommand(SetInvisible) self:SetCommand(SetInvisible)
return self return self
end end
@@ -2588,12 +2592,25 @@ end
-- @return #GROUP self -- @return #GROUP self
function GROUP:SetCommandImmortal(switch) function GROUP:SetCommandImmortal(switch)
self:F2( self.GroupName ) self:F2( self.GroupName )
local switch = switch or false if switch==nil then
local SetInvisible = {id = 'SetImmortal', params = {value = true}} switch=false
self:SetCommand(SetInvisible) end
local SetImmortal = {id = 'SetImmortal', params = {value = switch}}
self:SetCommand(SetImmortal)
return self return self
end end
--- Get skill from Group. Effectively gets the skill from Unit 1 as the group holds no skill value.
-- @param #GROUP self
-- @return #string Skill String of skill name.
function GROUP:GetSkill()
self:F2( self.GroupName )
local unit = self:GetUnit(1)
local name = unit:GetName()
local skill = _DATABASE.Templates.Units[name].Template.skill or "Random"
return skill
end
--do -- Smoke --do -- Smoke
-- --
----- Signal a flare at the position of the GROUP. ----- Signal a flare at the position of the GROUP.

View File

@@ -684,6 +684,27 @@ function POSITIONABLE:IsShip()
end end
--- Returns if the unit is a submarine.
-- @param #POSITIONABLE self
-- @return #boolean Submarines attributes result.
function POSITIONABLE:IsSubmarine()
self:F2()
local DCSUnit = self:GetDCSObject()
if DCSUnit then
local UnitDescriptor = DCSUnit:getDesc()
if UnitDescriptor.attributes["Submarines"] == true then
return true
else
return false
end
end
return nil
end
--- Returns true if the POSITIONABLE is in the air. --- Returns true if the POSITIONABLE is in the air.
-- Polymorphic, is overridden in GROUP and UNIT. -- Polymorphic, is overridden in GROUP and UNIT.
-- @param Wrapper.Positionable#POSITIONABLE self -- @param Wrapper.Positionable#POSITIONABLE self
@@ -1513,6 +1534,7 @@ do -- Cargo
["Ural-4320 APA-5D"] = 10, ["Ural-4320 APA-5D"] = 10,
["Ural-4320T"] = 14, ["Ural-4320T"] = 14,
["ZBD04A"] = 7, -- new by kappa ["ZBD04A"] = 7, -- new by kappa
["VAB_Mephisto"] = 8, -- new by Apple
} }
local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 95 local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 95

View File

@@ -1426,3 +1426,13 @@ function UNIT:EnableEmission(switch)
return self return self
end end
--- Get skill from Unit.
-- @param #UNIT self
-- @return #string Skill String of skill name.
function UNIT:GetSkill()
self:F2( self.UnitName )
local name = self.UnitName
local skill = _DATABASE.Templates.Units[name].Template.skill or "Random"
return skill
end

View File

@@ -74,6 +74,6 @@ MOOSE has a living (chat and video) community of users, beta testers and contrib
Kind regards, Kind regards,
FlightControl (FC) The Moose Team