Merge branch 'develop' into FF/Ops

This commit is contained in:
Frank 2021-03-28 20:33:08 +02:00
commit eb1e885c0a
6 changed files with 386 additions and 222 deletions

View File

@ -1,26 +1,26 @@
--- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon.
--
--
-- ===
--
--
-- ## Features:
--
--
-- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible.
-- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars.
--
--
-- ===
--
--
-- ## Missions:
--
--
-- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion)
--
--
-- ===
--
--
-- ### Authors: **FlightControl**, **applevangelist**
--
--
-- Last Update: Feb 2021
--
--
-- ===
--
--
-- @module Functional.Sead
-- @image SEAD.JPG
@ -28,24 +28,24 @@
-- @extends Core.Base#BASE
--- Make SAM sites execute evasive and defensive behaviour when being fired upon.
--
--
-- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon.
--
--
-- # Constructor:
--
--
-- Use the @{#SEAD.New}() constructor to create a new SEAD object.
--
--
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
--
--
-- @field #SEAD
SEAD = {
ClassName = "SEAD",
ClassName = "SEAD",
TargetSkill = {
Average = { Evade = 30, DelayOn = { 40, 60 } } ,
Good = { Evade = 20, DelayOn = { 30, 50 } } ,
High = { Evade = 15, DelayOn = { 20, 40 } } ,
Excellent = { Evade = 10, DelayOn = { 10, 30 } }
},
Excellent = { Evade = 10, DelayOn = { 10, 30 } }
},
SEADGroupPrefixes = {},
SuppressedGroups = {},
EngagementRange = 75 -- default 75% engagement range Feature Request #1355
@ -84,7 +84,7 @@ SEAD = {
["X_31"] = "X_31",
["Kh25"] = "Kh25",
}
--- 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...
-- Chances are big that the missile will miss.
@ -99,7 +99,7 @@ function SEAD:New( SEADGroupPrefixes )
local self = BASE:Inherit( self, BASE:New() )
self:F( SEADGroupPrefixes )
if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
@ -107,7 +107,7 @@ function SEAD:New( SEADGroupPrefixes )
else
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
end
self:HandleEvent( EVENTS.Shot )
self:I("*** SEAD - Started Version 0.2.5")
return self
@ -120,7 +120,7 @@ end
function SEAD:UpdateSet( SEADGroupPrefixes )
self:F( SEADGroupPrefixes )
if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
@ -174,10 +174,10 @@ function SEAD:OnEventShot( EventData )
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
self:T({ SEADWeapon })
--[[check for SEAD missiles
if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired
or
or
SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired
or
SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired
@ -205,7 +205,7 @@ function SEAD:OnEventShot( EventData )
SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired
--]]
if self:_CheckHarms(SEADWeaponName) then
local _evade = math.random (1,100) -- random number for chance of evading action
local _targetMim = EventData.Weapon:getTarget() -- Identify target
local _targetMimname = Unit.getName(_targetMim) -- Unit name
@ -222,7 +222,7 @@ function SEAD:OnEventShot( EventData )
self:T( '*** SEAD - Group Found' )
break
end
end
end
if SEADGroupFound == true then -- yes we are being attacked
if _targetskill == "Random" then -- when skill is random, choose a skill
local Skills = { "Average", "Good", "High", "Excellent" }
@ -231,42 +231,26 @@ function SEAD:OnEventShot( EventData )
self:T( _targetskill )
if self.TargetSkill[_targetskill] then
if (_evade > self.TargetSkill[_targetskill].Evade) then
self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) )
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
local _targetMimcont= _targetMimgroup:getController()
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly
--tracker ID table to switch groups off and on again
local id = {
local id = {
groupName = _targetMimgroup,
ctrl = _targetMimcont
}
local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2])
if SuppressedGroups1[id.groupName] == nil then
SuppressedGroups1[id.groupName] = {
SuppressionEndTime1 = timer.getTime() + delay1,
SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function
}
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function
--trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20)
end
local SuppressedGroups = {}
local function SuppressionEnd(id)
local function SuppressionEnd(id) --switch group back on
local range = self.EngagementRange -- Feature Request #1355
--env.info(string.format("*** SEAD - Engagement Range is %d", range))
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.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355
SuppressedGroups[id.groupName] = nil
self.SuppressedGroups[id.groupName] = nil --delete group id from table when done
end
-- randomize switch-on time
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])

View File

@ -39,7 +39,7 @@
-- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
-- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI)
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**]
-- * [T-45C Goshawk (VNAO)(Player & AI)]
-- * [T-45C Goshawk](https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**]
-- * F/A-18C Hornet (AI)
-- * F-14A Tomcat (AI)
-- * E-2D Hawkeye (AI)
@ -573,7 +573,10 @@
-- * **L**ined **U**p **L**eft or **R**ight: LUL, LUR
-- * Too **H**igh or too **LO**w: H, LO
-- * Too **F**ast or too **SLO**w: F, SLO
-- * **Fly through** glideslope **down** or **up**: \\ , /
-- * **O**ver**S**hoot: OS, only referenced during **X**
-- * **Fly through** glideslope **down** or **up**: \\ , /, advisory only
-- * **D**rift **L**eft or **R**ight:DL, DR, advisory only
-- * **A**ngled **A**pproach: Angled approach (wings level and LUL): AA, advisory only
--
-- Each grading, x, is subdivided by
--
@ -634,7 +637,7 @@
--
-- ## Foul Deck Waveoff
--
-- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is crossing the ship's wake during Case I/II operations,
-- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is at position IM-IC during Case I/II operations,
-- or with an aircraft approaching the 3/4 NM during Case III operations.
--
-- The approaching aircraft will be notified via LSO radio comms and is supposed to overfly the landing area to enter the Bolter pattern. **This pass is not graded**.
@ -1264,6 +1267,7 @@ AIRBOSS = {
-- @field #string S3BTANKER Lockheed S-3B Viking tanker.
-- @field #string E2D Grumman E-2D Hawkeye AWACS.
-- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod.
-- @field #string T45C T-45C by VNAO
AIRBOSS.AircraftCarrier={
AV8B="AV8BNA",
HORNET="FA-18C_hornet",
@ -1340,6 +1344,8 @@ AIRBOSS.CarrierType={
-- @field #number _min Min _OK_ value. Default -0.5 deg.
-- @field #number Left (LUR) threshold. Default -1.0 deg.
-- @field #number Right (LUL) threshold. Default 1.0 deg.
-- @field #number LeftMed threshold for AA/OS measuring. Default -2.0 deg.
-- @field #number RightMed threshold for AA/OS measuring. Default 2.0 deg.
-- @field #number LEFT LUR threshold. Default -3.0 deg.
-- @field #number RIGHT LUL threshold. Default 3.0 deg.
@ -2650,10 +2656,10 @@ end
--- Set multiplayer environment wire correction.
-- @param #AIRBOSS self
-- @param #number Dcorr Correction distance in meters. Default 8.7 m.
-- @param #number Dcorr Correction distance in meters. Default 12 m.
-- @return #AIRBOSS self
function AIRBOSS:SetMPWireCorrection(Dcorr)
self.mpWireCorrection=Dcorr or 8.7
self.mpWireCorrection=Dcorr or 12
return self
end
@ -2815,16 +2821,20 @@ end
-- @param #number _max
-- @param #number _min
-- @param #number Left
-- @param #number LeftMed
-- @param #number LEFT
-- @param #number Right
-- @param #number RightMed
-- @param #number RIGHT
-- @return #AIRBOSS self
function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LEFT, Right, RIGHT)
function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT)
self.lue._max=_max or 0.5
self.lue._min=_min or -0.5
self.lue.Left=Left or -1.0
self.lue.LeftMed=LeftMed or -2.0
self.lue.LEFT=LEFT or -3.0
self.lue.Right=Right or 1.0
self.lue.RightMed=RightMed or 2.0
self.lue.RIGHT=RIGHT or 3.0
return self
end
@ -10096,6 +10106,27 @@ function AIRBOSS:_Groove(playerData)
-- Distance in NM.
local d=UTILS.MetersToNM(rho)
-- Drift on lineup.
if rho>=RAR and rho<=RIM then
if gd.LUE>0.21 and lineupError<-0.21 then
env.info" Drift Right across centre ==> DR-"
gd.Drift=" DR"
self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError))
elseif gd.LUE<-0.21 and lineupError>0.21 then
env.info" Drift Left ==> DL-"
gd.Drift=" DL"
self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError))
elseif gd.LUE>0.12 and lineupError<-0.12 then
env.info" Little Drift Right across centre ==> (DR-)"
gd.Drift=" (DR)"
self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError))
elseif gd.LUE<-0.12 and lineupError>0.12 then
env.info" Little Drift Left across centre ==> (DL-)"
gd.Drift=" (DL)"
self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError))
end
end
-- Update max deviation of line up error.
if math.abs(lineupError)>math.abs(gd.LUE) then
self:T(self.lid..string.format("Got bigger LUE at step %s, d=%.3f: LUE %.3f>%.3f", gs, d, lineupError, gd.LUE))
@ -12092,7 +12123,7 @@ function AIRBOSS:_LSOgrade(playerData)
local nS=count(G, '%(')
local nN=N-nS-nL
-- Groove time 16-18 sec for a unicorn.
-- Groove time 15-18.99 sec for a unicorn.
local Tgroove=playerData.Tgroove
local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false
@ -12227,7 +12258,35 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
-- Aircraft specific AoA values.
local acaoa=self:_GetAircraftAoA(playerData)
--Angled Approach.
local P=nil
if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=4.0 then
if LUE>self.lue.RIGHT then
P=underline("AA")
elseif
LUE>self.lue.RightMed then
P="AA "
elseif
LUE>self.lue.Right then
P=little("AA")
end
end
--Overshoot Start.
local O=nil
if step==AIRBOSS.PatternStep.GROOVE_XX then
if LUE<self.lue.LEFT then
O=underline("OS")
elseif
LUE<self.lue.Left then
O="OS"
elseif
LUE<self.lue._min then
O=little("OS")
end
end
-- Speed via AoA. Depends on aircraft type.
local S=nil
if AOA>acaoa.SLOW then
@ -12260,7 +12319,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
A=little("LO")
end
-- Line up. Good [-0.5, 0.5]
-- Line up. XX Step replaced by Overshoot start (OS). Good [-0.5, 0.5]
local D=nil
if LUE>self.lue.RIGHT then
D=underline("LUL")
@ -12268,11 +12327,11 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
D="LUL"
elseif LUE>self.lue._max then
D=little("LUL")
elseif LUE<self.lue.LEFT then
elseif LUE<self.lue.LEFT and step~=AIRBOSS.PatternStep.GROOVE_XX then
D=underline("LUR")
elseif LUE<self.lue.Left then
elseif LUE<self.lue.Left and step~=AIRBOSS.PatternStep.GROOVE_XX then
D="LUR"
elseif LUE<self.lue._min then
elseif LUE<self.lue._min and step~=AIRBOSS.PatternStep.GROOVE_XX then
D=little("LUR")
end
@ -12283,6 +12342,11 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
if fdata.FlyThrough then
G=G..fdata.FlyThrough
end
-- Angled Approach - doesn't affect score, advisory only.
if P then
G=G..P
n=n
end
-- Speed.
if S then
G=G..S
@ -12298,7 +12362,17 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
G=G..D
n=n+1
end
--Drift in Lineup
if fdata.Drift then
G=G..fdata.Drift
n=n -- Drift doesn't affect score, advisory only.
end
-- Overshoot.
if O then
G=G..O
n=n+1
end
-- Add current step.
local step=self:_GS(step)
step=step:gsub("XX","X")

View File

@ -1,8 +1,11 @@
--- **Ops** - Office of Military Intelligence.
--
-- **Main Features:**
-- ## Main Features:
--
-- * Stuff
-- * Detect and track contacts consistently
-- * Detect and track clusters of contacts consistently
-- * Use FSM events to link functionality into your scripts
-- * Easy setup
--
-- ===
--
@ -41,9 +44,42 @@
-- ![Banner Image](..\Presentations\CarrierAirWing\INTEL_Main.jpg)
--
-- # The INTEL Concept
--
-- * Lightweight replacement for @{Functional.Detection#DETECTION}
-- * Detect and track contacts consistently
-- * Detect and track clusters of contacts consistently
-- * Once detected and still alive, planes will be tracked 10 minutes, helicopters 20 minutes, ships and trains 1 hour, ground units 2 hours
-- * Use FSM events to link functionality into your scripts
--
-- # Basic Usage
--
--
--
-- ## set up a detection SET_GROUP
--
-- `Red_DetectionSetGroup = SET_GROUP:New()`
-- `Red_DetectionSetGroup:FilterPrefixes( { "Red EWR" } )`
-- `Red_DetectionSetGroup:FilterOnce()`
--
-- ## New Intel type detection for the red side, logname "KGB"
--
-- `RedIntel = INTEL:New(Red_DetectionSetGroup,"red","KGB")`
-- `RedIntel:SetClusterAnalysis(true,true)`
-- `RedIntel:SetVerbosity(2)`
-- `RedIntel:Start()`
--
-- ## Hook into new contacts found
--
-- `function RedIntel:OnAfterNewContact(From, Event, To, Contact)`
-- `local text = string.format("NEW contact %s detected by %s", Contact.groupname, Contact.recce or "unknown")`
-- `local m = MESSAGE:New(text,15,"KGB"):ToAll()`
-- `end`
--
-- ## And/or new clusters found
--
-- `function RedIntel:OnAfterNewCluster(From, Event, To, Contact, Cluster)`
-- `local text = string.format("NEW cluster %d size %d with contact %s", Cluster.index, Cluster.size, Contact.groupname)`
-- `local m = MESSAGE:New(text,15,"KGB"):ToAll()`
-- `end`
--
-- @field #INTEL
INTEL = {
ClassName = "INTEL",
@ -57,7 +93,7 @@ INTEL = {
ContactsUnknown = {},
Clusters = {},
clustercounter = 1,
clusterradius = 15,
clusterradius = 10,
}
--- Detected item info.
@ -314,7 +350,7 @@ function INTEL:RemoveRejectZone(RejectZone)
return self
end
--- Set forget contacts time interval.
--- Set forget contacts time interval. For unknown contacts only.
-- Previously known contacts that are not detected any more, are "lost" after this time.
-- This avoids fast oscillations between a contact being detected and undetected.
-- @param #INTEL self
@ -429,7 +465,7 @@ end
-- @param #number radius The radius of the clusters
-- @return #INTEL self
function INTEL:SetClusterRadius(radius)
local radius = radius or 15
local radius = radius or 10
self.clusterradius = radius
return self
end
@ -1046,7 +1082,7 @@ function INTEL:CalcClusterThreatlevelSum(cluster)
threatlevel=threatlevel+contact.threatlevel
end
cluster.threatlevelSum = threatlevel
return threatlevel
end
@ -1058,7 +1094,7 @@ function INTEL:CalcClusterThreatlevelAverage(cluster)
local threatlevel=self:CalcClusterThreatlevelSum(cluster)
threatlevel=threatlevel/cluster.size
cluster.threatlevelAve = threatlevel
return threatlevel
end
@ -1078,7 +1114,7 @@ function INTEL:CalcClusterThreatlevelMax(cluster)
end
end
cluster.threatlevelMax = threatlevel
return threatlevel
end
@ -1119,7 +1155,7 @@ function INTEL:IsContactConnectedToCluster(contact, cluster)
--local dist=Contact.position:Get2DDistance(contact.position)
local dist=Contact.position:DistanceFromPointVec2(contact.position)
local radius = self.clusterradius or 15
local radius = self.clusterradius or 10
if dist<radius*1000 then
return true
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.
--
-- ### Authors:
--
--
-- ### Authors:
--
-- * Grimes : Design & Programming of the MIST framework.
--
--
-- ### Contributions:
--
-- * FlightControl : Rework to OO framework
--
--
-- * FlightControl : Rework to OO framework
--
-- @module Utils
-- @image MOOSE.JPG
@ -18,7 +18,7 @@
-- @field White
-- @field Orange
-- @field Blue
SMOKECOLOR = trigger.smokeColor -- #SMOKECOLOR
--- @type FLARECOLOR
@ -92,7 +92,7 @@ CALLSIGN={
Texaco=1,
Arco=2,
Shell=3,
},
},
-- JTAC
JTAC={
Axeman=1,
@ -161,31 +161,31 @@ UTILS = {
UTILS.IsInstanceOf = function( object, className )
-- Is className NOT a string ?
if not type( className ) == 'string' then
-- Is className a Moose class ?
if type( className ) == 'table' and className.IsInstanceOf ~= nil then
-- Get the name of the Moose class as a string
className = className.ClassName
-- className is neither a string nor a Moose class, throw an error
else
-- 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 )
return false
-- error( err_str )
end
end
-- Is the object a Moose class instance ?
if type( object ) == 'table' and object.IsInstanceOf ~= nil then
-- Use the IsInstanceOf method of the BASE class
return object:IsInstanceOf( className )
else
-- 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' }
for _, basicDataType in ipairs( basicDataTypes ) do
@ -194,7 +194,7 @@ UTILS.IsInstanceOf = function( object, className )
end
end
end
-- Check failed
return false
end
@ -206,7 +206,7 @@ end
UTILS.DeepCopy = function(object)
local lookup_table = {}
-- Copy function.
local function _copy(object)
if type(object) ~= "table" then
@ -214,20 +214,20 @@ UTILS.DeepCopy = function(object)
elseif lookup_table[object] then
return lookup_table[object]
end
local new_table = {}
lookup_table[object] = new_table
for index, value in pairs(object) do
new_table[_copy(index)] = _copy(value)
end
return setmetatable(new_table, getmetatable(object))
end
local objectreturn = _copy(object)
return objectreturn
end
@ -237,19 +237,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
lookup_table = {}
local function _Serialize( tbl )
if type(tbl) == 'table' then --function only works for tables!
if lookup_table[tbl] then
return lookup_table[object]
end
local tbl_str = {}
lookup_table[tbl] = tbl_str
tbl_str[#tbl_str + 1] = '{'
for ind,val in pairs(tbl) do -- serialize its fields
@ -297,7 +297,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( debug.traceback() )
end
end
tbl_str[#tbl_str + 1] = '}'
return table.concat(tbl_str)
@ -305,7 +305,7 @@ UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a s
return tostring(tbl)
end
end
local objectreturn = _Serialize(tbl)
return objectreturn
end
@ -414,7 +414,7 @@ end
-- @param #number Celcius Temperature in degrees Celsius.
-- @return #number Temperature in degrees Farenheit.
UTILS.CelciusToFarenheit = function( Celcius )
return Celcius * 9/5 + 32
return Celcius * 9/5 + 32
end
--- Convert pressure from hecto Pascal (hPa) to inches of mercury (inHg).
@ -424,6 +424,14 @@ UTILS.hPa2inHg = function( hPa )
return hPa * 0.0295299830714
end
--- Convert knots to alitude corrected KIAS, e.g. for tankers.
-- @param #number knots Speed in knots.
-- @param #number altitude Altitude in feet
-- @return #number Corrected KIAS
UTILS.KnotsToAltKIAS = function( knots, altitude )
return (knots * 0.018 * (altitude / 1000)) + knots
end
--- Convert pressure from hecto Pascal (hPa) to millimeters of mercury (mmHg).
-- @param #number hPa Pressure in hPa.
-- @return #number Pressure in mmHg.
@ -540,23 +548,23 @@ UTILS.tostringMGRS = function(MGRS, acc) --R2.1
-- Test if Easting/Northing have less than 4 digits.
--MGRS.Easting=123 -- should be 00123
--MGRS.Northing=5432 -- should be 05432
-- Truncate rather than round MGRS grid!
local Easting=tostring(MGRS.Easting)
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.
local nE=5-string.len(Easting)
local nE=5-string.len(Easting)
local nN=5-string.len(Northing)
-- Get leading zeros (if any).
for i=1,nE do Easting="0"..Easting end
for i=1,nN do Northing="0"..Northing end
-- Return MGRS string.
return string.format("%s %s %s %s", MGRS.UTMZone, MGRS.MGRSDigraph, string.sub(Easting, 1, acc), string.sub(Northing, 1, acc))
end
end
@ -584,7 +592,7 @@ function UTILS.spairs( t, order )
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,
-- otherwise just sort the keys
-- otherwise just sort the keys
if order then
table.sort(keys, function(a,b) return order(t, a, b) end)
else
@ -610,7 +618,7 @@ function UTILS.kpairs( t, getkey, order )
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,
-- otherwise just sort the keys
-- otherwise just sort the keys
if order then
table.sort(keys, function(a,b) return order(t, a, b) end)
else
@ -630,7 +638,7 @@ end
-- 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 )
-- collect the keys
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
@ -641,7 +649,7 @@ function UTILS.rpairs( t )
random[i] = keys[k]
table.remove( keys, k )
end
-- return the iterator function
local i = 0
return function()
@ -742,15 +750,15 @@ end
-- @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).
function UTILS.SecondsToClock(seconds, short)
-- Nil check.
if seconds==nil then
return nil
end
-- Seconds
local seconds = tonumber(seconds)
-- Seconds of this day.
local _seconds=seconds%(60*60*24)
@ -780,10 +788,10 @@ function UTILS.SecondsOfToday()
-- Time in seconds.
local time=timer.getAbsTime()
-- Short format without days since mission start.
local clock=UTILS.SecondsToClock(time, true)
-- Time is now the seconds passed since last midnight.
return UTILS.ClockToSeconds(clock)
end
@ -798,24 +806,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.
-- @return #number Seconds. Corresponds to what you cet from timer.getAbsTime() function.
function UTILS.ClockToSeconds(clock)
-- Nil check.
if clock==nil then
return nil
end
-- Seconds init.
local seconds=0
-- Split additional days.
local dsplit=UTILS.Split(clock, "+")
-- Convert days to seconds.
if #dsplit>1 then
seconds=seconds+tonumber(dsplit[2])*60*60*24
end
-- Split hours, minutes, seconds
-- Split hours, minutes, seconds
local tsplit=UTILS.Split(dsplit[1], ":")
-- Get time in seconds
@ -833,7 +841,7 @@ function UTILS.ClockToSeconds(clock)
end
i=i+1
end
return seconds
end
@ -845,12 +853,12 @@ function UTILS.DisplayMissionTime(duration)
local mission_time=Tnow-timer.getTime0()
local mission_time_minutes=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)
MESSAGE:New(text, duration):ToAll()
end
--- Replace illegal characters [<>|/?*:\\] in a string.
--- Replace illegal characters [<>|/?*:\\] in a string.
-- @param #string Text Input text.
-- @param #string ReplaceBy Replace illegal characters by this character or string. Default underscore "_".
-- @return #string The input text with illegal chars replaced.
@ -871,28 +879,28 @@ function UTILS.RandomGaussian(x0, sigma, xmin, xmax, imax)
-- Standard deviation. Default 10 if not given.
sigma=sigma or 10
-- Max attempts.
imax=imax or 100
local r
local gotit=false
local i=0
while not gotit do
-- Uniform numbers in [0,1). We need two.
local x1=math.random()
local x2=math.random()
-- Transform to Gaussian exp(-(x-x0)²/(2*sigma²).
r = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.cos(2*math.pi * x2) + x0
i=i+1
if (r>=xmin and r<=xmax) or i>imax then
gotit=true
end
end
return r
end
@ -917,9 +925,9 @@ function UTILS.Randomize(value, fac, lower, upper)
else
max=value+value*fac
end
local r=math.random(min, max)
return r
end
@ -961,7 +969,7 @@ end
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
@ -975,7 +983,7 @@ end
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
@ -989,7 +997,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}
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 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.
@ -997,7 +1005,7 @@ function UTILS.VecSubstract(a, b)
return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z}
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 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.
@ -1005,14 +1013,14 @@ function UTILS.VecAdd(a, b)
return {x=a.x+b.x, y=a.y+b.y, z=a.z+b.z}
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 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)
local cosalpha=UTILS.VecDot(a,b)/(UTILS.VecNorm(a)*UTILS.VecNorm(b))
local alpha=0
if cosalpha>=0.9999999999 then --acos(1) is not defined.
alpha=0
@ -1020,8 +1028,8 @@ function UTILS.VecAngle(a, b)
alpha=math.pi
else
alpha=math.acos(cosalpha)
end
end
return math.deg(alpha)
end
@ -1056,18 +1064,18 @@ function UTILS.HdgDiff(h1, h2)
-- Angle in rad.
local alpha= math.rad(tonumber(h1))
local beta = math.rad(tonumber(h2))
-- Runway vector.
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 delta=UTILS.VecAngle(v1, v2)
return math.abs(delta)
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 #number distance The distance to translate.
-- @param #number angle Rotation angle in degrees.
@ -1083,7 +1091,7 @@ function UTILS.VecTranslate(a, distance, angle)
return {x=TX, y=a.y, z=TY}
end
--- Translate 2D vector in the 2D (x,z) plane.
--- Translate 2D vector in the 2D (x,z) plane.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @param #number distance The distance to translate.
-- @param #number angle Rotation angle in degrees.
@ -1099,40 +1107,40 @@ function UTILS.Vec2Translate(a, distance, angle)
return {x=TX, y=TY}
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 #number angle Rotation angle in degrees.
-- @return DCS#Vec3 Vector rotated in the (x,z) plane.
function UTILS.Rotate2D(a, angle)
local phi=math.rad(angle)
local x=a.z
local y=a.x
local Z=x*math.cos(phi)-y*math.sin(phi)
local X=x*math.sin(phi)+y*math.cos(phi)
local Y=a.y
local A={x=X, y=Y, z=Z}
return A
end
--- Rotate 2D vector in the 2D (x,z) plane.
--- Rotate 2D vector in the 2D (x,z) plane.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @param #number angle Rotation angle in degrees.
-- @return DCS#Vec2 Vector rotated in the (x,y) plane.
function UTILS.Vec2Rotate2D(a, angle)
local phi=math.rad(angle)
local x=a.x
local y=a.y
local X=x*math.cos(phi)-y*math.sin(phi)
local Y=x*math.sin(phi)+y*math.cos(phi)
local A={x=X, y=Y}
return A
@ -1150,17 +1158,17 @@ function UTILS.TACANToFrequency(TACANChannel, TACANMode)
end
if TACANMode ~= "X" and TACANMode ~= "Y" then
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.
-- I have no idea what it does but it seems to work
local A = 1151 -- 'X', channel >= 64
local B = 64 -- channel >= 64
if TACANChannel < 64 then
B = 1
end
if TACANMode == 'Y' then
A = 1025
if TACANChannel < 64 then
@ -1171,7 +1179,7 @@ function UTILS.TACANToFrequency(TACANChannel, TACANMode)
A = 962
end
end
return (A + TACANChannel - B) * 1000000
end
@ -1198,13 +1206,13 @@ end
-- @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.
function UTILS.GetMissionDay(Time)
Time=Time or timer.getAbsTime()
local clock=UTILS.SecondsToClock(Time, false)
local x=tonumber(UTILS.Split(clock, "+")[2])
return x
end
@ -1214,11 +1222,11 @@ end
function UTILS.GetMissionDayOfYear(Time)
local Date, Year, Month, Day=UTILS.GetDCSMissionDate()
local d=UTILS.GetMissionDay(Time)
return UTILS.GetDayOfYear(Year, Month, Day)+d
end
--- Returns the current date.
@ -1230,20 +1238,20 @@ function UTILS.GetDate()
-- Mission start date
local date, year, month, day=UTILS.GetDCSMissionDate()
local time=timer.getAbsTime()
local clock=UTILS.SecondsToClock(time, false)
local x=tonumber(UTILS.Split(clock, "+")[2])
local day=day+x
end
--- Returns the magnetic declination of the map.
-- Returned values for the current maps are:
--
--
-- * Caucasus +6 (East), year ~ 2011
-- * NTTR +12 (East), year ~ 2011
-- * Normandy -10 (West), year ~ 1944
@ -1254,7 +1262,7 @@ function UTILS.GetMagneticDeclination(map)
-- Map.
map=map or UTILS.GetDCSMap()
local declination=0
if map==DCSMAP.Caucasus then
declination=6
@ -1289,12 +1297,12 @@ function UTILS.FileExists(file)
end
else
return nil
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.
-- @param #boolean output If true, print to DCS log file.
-- @return #number Memory usage in kByte.
-- @param #boolean output If true, print to DCS log file.
-- @return #number Memory usage in kByte.
function UTILS.CheckMemory(output)
local time=timer.getTime()
local clock=UTILS.SecondsToClock(time)
@ -1324,7 +1332,7 @@ function UTILS.GetCoalitionName(Coalition)
else
return "Unknown"
end
end
--- Get the modulation name from its numerical value.
@ -1343,7 +1351,7 @@ function UTILS.GetModulationName(Modulation)
else
return "Unknown"
end
end
--- Get the callsign name from its enumerator value
@ -1356,7 +1364,7 @@ function UTILS.GetCallsignName(Callsign)
return name
end
end
for name, value in pairs(CALLSIGN.AWACS) do
if value==Callsign then
return name
@ -1368,7 +1376,7 @@ function UTILS.GetCallsignName(Callsign)
return name
end
end
for name, value in pairs(CALLSIGN.Tanker) do
if value==Callsign then
return name
@ -1412,11 +1420,11 @@ end
function UTILS.GetDayOfYear(Year, Month, Day)
local floor = math.floor
local n1 = floor(275 * Month / 9)
local n2 = floor((Month + 9) / 12)
local n3 = (1 + floor((Year - 4 * floor(Year / 4) + 2) / 3))
return n1 - (n2 * n3) + Day - 30
end
@ -1429,14 +1437,14 @@ end
-- @return #number Sun rise/set in seconds of the day.
function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal)
-- Defaults
-- Defaults
local zenith=90.83
local latitude=Latitude
local longitude=Longitude
local rising=Rising
local n=DayOfYear
Tlocal=Tlocal or 0
-- Short cuts.
local rad = math.rad
@ -1463,47 +1471,47 @@ function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal)
return val
end
end
-- Convert the longitude to hour value and calculate an approximate time
local lng_hour = longitude / 15
local t
if rising then -- Rising time is desired
t = n + ((6 - lng_hour) / 24)
else -- Setting time is desired
t = n + ((18 - lng_hour) / 24)
end
-- Calculate the Sun's mean anomaly
local M = (0.9856 * t) - 3.289
-- 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)
-- Calculate the Sun's right ascension
local RA = fit_into_range(atan(0.91764 * tan(L)), 0, 360)
-- Right ascension value needs to be in the same quadrant as L
local Lquadrant = floor(L / 90) * 90
local RAquadrant = floor(RA / 90) * 90
RA = RA + Lquadrant - RAquadrant
-- Right ascension value needs to be converted into hours
RA = RA / 15
-- Calculate the Sun's declination
local sinDec = 0.39782 * sin(L)
local cosDec = cos(asin(sinDec))
-- Calculate the Sun's local hour angle
local cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude))
if rising and cosH > 1 then
return "N/R" -- The sun never rises on this location on the specified date
elseif cosH < -1 then
return "N/S" -- The sun never sets on this location on the specified date
end
-- Finish calculating H and convert into hours
local H
if rising then
@ -1512,13 +1520,13 @@ function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal)
H = acos(cosH)
end
H = H / 15
-- Calculate local mean time of rising/setting
local T = H + RA - (0.06571 * t) - 6.622
-- Adjust back to UTC
local UT = fit_into_range(T - lng_hour +Tlocal, 0, 24)
return floor(UT)*60*60+frac(UT)*60*60--+Tlocal*60*60
end
@ -1568,9 +1576,9 @@ end
--@param #table table to be shuffled
--@return #table
function UTILS.ShuffleTable(t)
if t == nil or type(t) ~= "table" then
BASE:I("Error in ShuffleTable: Missing or wrong tyåe of Argument")
return
if t == nil or type(t) ~= "table" then
BASE:I("Error in ShuffleTable: Missing or wrong tyåe of Argument")
return
end
math.random()
math.random()

View File

@ -3687,3 +3687,57 @@ function CONTROLLABLE:OptionAAAttackRange(range)
end
return nil
end
--- Defines the range at which a GROUND unit/group is allowed to use its weapons automatically.
-- @param #CONTROLLABLE self
-- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100.
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionEngageRange(EngageRange)
self:F2( { self.ControllableName } )
-- Set default if not specified.
EngageRange=EngageRange or 100
if EngageRange < 0 or EngageRange > 100 then
EngageRange = 100
end
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if Controller then
if self:IsGround() then
self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange)
end
end
return self
end
return nil
end
--- (GROUND) Relocate controllable to a random point within a given radius; use e.g.for evasive actions; Note that not all ground controllables can actually drive, also the alarm state of the controllable might stop it from moving.
-- @param #CONTROLLABLE self
-- @param #number speed Speed of the controllable, default 20
-- @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 shortcut If true and onroad is set, take a shorter route - if available - off road, default false
function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut)
self:F2( { self.ControllableName } )
local _coord = self:GetCoordinate()
local _radius = radius or 500
local _speed = speed or 20
local _tocoord = _coord:GetRandomCoordinateInRadius(_radius,100)
local _onroad = onroad or true
local _grptsk = {}
local _candoroad = false
local _shortcut = shortcut or false
-- create a DCS Task an push it on the group
-- TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,Shortcut,FromCoordinate,WaypointFunction,WaypointFunctionArguments)
if onroad then
_grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,"Off Road",_shortcut)
self:Route(_grptsk,5)
else
self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,"Off Road")
end
return self
end

View File

@ -598,16 +598,16 @@ function POSITIONABLE:GetHeading()
if DCSPositionable then
local PositionablePosition = DCSPositionable:getPosition()
if PositionablePosition then
local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x )
if PositionableHeading < 0 then
PositionableHeading = PositionableHeading + 2 * math.pi
end
PositionableHeading = PositionableHeading * 180 / math.pi
return PositionableHeading
end
end
@ -1446,6 +1446,7 @@ do -- Cargo
["Dry-cargo ship-2"] = 70000,
["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia).
["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops.
["LST_Mk2"] =2100000, -- Can carry 2100 tons according to wiki source!
}
self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 )
@ -1453,48 +1454,55 @@ do -- Cargo
local Desc = self:GetDesc()
local Weights = {
["M1126 Stryker ICV"] = 9,
["M-113"] = 9,
["AAV7"] = 25,
["M2A1_halftrack"] = 9,
["BMD-1"] = 9,
["Bedford_MWD"] = 8, -- new by kappa
["Blitz_36-6700A"] = 10, -- new by kappa
["BMD-1"] = 9, -- IRL should be 4 passengers
["BMP-1"] = 8,
["BMP-2"] = 7,
["BMP-3"] = 8,
["BMP-3"] = 8, -- IRL should be 7+2 passengers
["Boman"] = 25,
["BTR-80"] = 9,
["BTR_D"] = 12,
["BTR-80"] = 9, -- IRL should be 7 passengers
["BTR-82A"] = 9, -- new by kappa -- IRL should be 7 passengers
["BTR_D"] = 12, -- IRL should be 10 passengers
["Cobra"] = 8,
["Land_Rover_101_FC"] = 11, -- new by kappa
["Land_Rover_109_S3"] = 7, -- new by kappa
["LAV-25"] = 6,
["M-2 Bradley"] = 6,
["M1043 HMMWV Armament"] = 4,
["M1045 HMMWV TOW"] = 4,
["M1126 Stryker ICV"] = 9,
["M1134 Stryker ATGM"] = 9,
["M2A1_halftrack"] = 9,
["M-113"] = 9, -- IRL should be 11 passengers
["Marder"] = 6,
["MCV-80"] = 9,
["MCV-80"] = 9, -- IRL should be 7 passengers
["MLRS FDDM"] = 4,
["MTLB"] = 25,
["TPZ"] = 10,
["Ural-4320 APA-5D"] = 10,
["MTLB"] = 25, -- IRL should be 11 passengers
["GAZ-66"] = 8,
["GAZ-3307"] = 12,
["GAZ-3308"] = 14,
["Tigr_233036"] = 6,
["Grad_FDDM"] = 6, -- new by kappa
["KAMAZ Truck"] = 12,
["KrAZ6322"] = 12,
["M 818"] = 12,
["Tigr_233036"] = 6,
["TPZ"] = 10,
["UAZ-469"] = 4, -- new by kappa
["Ural-375"] = 12,
["Ural-4320-31"] = 14,
["Ural-4320 APA-5D"] = 10,
["Ural-4320T"] = 14,
["ZBD04A"] = 7, -- new by kappa
}
local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 95
self.__.CargoBayWeightLimit = CargoBayWeightLimit
end
end
self:F({CargoBayWeightLimit = self.__.CargoBayWeightLimit})
end
end --- Cargo