mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Merge branch 'develop' into FF/OpsDev
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
-- * Option to present information in imperial or metric units
|
||||
-- * Runway length and airfield elevation (optional)
|
||||
-- * Frequencies/channels of nav aids (ILS, VOR, NDB, TACAN, PRMG, RSBN) (optional)
|
||||
-- * SRS Simple-Text-To-Speech (STTS) integration (no sound files necessary)
|
||||
-- * SRS Simple-Text-To-Speech (MSRS) integration (no sound files necessary)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -291,7 +291,7 @@
|
||||
-- ## Nevada: Nellis AFB
|
||||
--
|
||||
-- -- ATIS Nellis AFB on 270.10 MHz AM.
|
||||
-- atisNellis=ATIS:New(AIRBASE.Nevada.Nellis_AFB, 270.1)
|
||||
-- atisNellis=ATIS:New(AIRBASE.Nevada.Nellis, 270.1)
|
||||
-- atisNellis:SetRadioRelayUnitName("Radio Relay Nellis")
|
||||
-- atisNellis:SetActiveRunway("21L")
|
||||
-- atisNellis:SetTowerFrequencies({327.000, 132.550})
|
||||
@@ -302,7 +302,7 @@
|
||||
-- ## Persian Gulf: Abu Dhabi International Airport
|
||||
--
|
||||
-- -- ATIS Abu Dhabi International on 125.1 MHz AM.
|
||||
-- atisAbuDhabi=ATIS:New(AIRBASE.PersianGulf.Abu_Dhabi_International_Airport, 125.1)
|
||||
-- atisAbuDhabi=ATIS:New(AIRBASE.PersianGulf.Abu_Dhabi_Intl, 125.1)
|
||||
-- atisAbuDhabi:SetRadioRelayUnitName("Radio Relay Abu Dhabi International Airport")
|
||||
-- atisAbuDhabi:SetMetricUnits()
|
||||
-- atisAbuDhabi:SetActiveRunway("L")
|
||||
@@ -498,6 +498,9 @@ ATIS.Alphabet = {
|
||||
-- @field #number Syria +5° (East).
|
||||
-- @field #number MarianaIslands +2° (East).
|
||||
-- @field #number SinaiMap +5° (East).
|
||||
-- @field #number Kola +15° (East).
|
||||
-- @field #number Afghanistan +3° (East).
|
||||
-- @field #number Iraq +4.4° (East).
|
||||
ATIS.RunwayM2T = {
|
||||
Caucasus = 0,
|
||||
Nevada = 12,
|
||||
@@ -508,6 +511,9 @@ ATIS.RunwayM2T = {
|
||||
MarianaIslands = 2,
|
||||
Falklands = 12,
|
||||
SinaiMap = 5,
|
||||
Kola = 15,
|
||||
Afghanistan = 3,
|
||||
Iraq=4.4
|
||||
}
|
||||
|
||||
--- Whether ICAO phraseology is used for ATIS broadcasts.
|
||||
@@ -521,6 +527,9 @@ ATIS.RunwayM2T = {
|
||||
-- @field #boolean MarianaIslands true.
|
||||
-- @field #boolean Falklands true.
|
||||
-- @field #boolean SinaiMap true.
|
||||
-- @field #boolean Kola true.
|
||||
-- @field #boolean Afghanistan true.
|
||||
-- @field #boolean Iraq true.
|
||||
ATIS.ICAOPhraseology = {
|
||||
Caucasus = true,
|
||||
Nevada = false,
|
||||
@@ -531,6 +540,9 @@ ATIS.ICAOPhraseology = {
|
||||
MarianaIslands = true,
|
||||
Falklands = true,
|
||||
SinaiMap = true,
|
||||
Kola = true,
|
||||
Afghanistan = true,
|
||||
Iraq = true,
|
||||
}
|
||||
|
||||
--- Nav point data.
|
||||
@@ -619,83 +631,83 @@ ATIS.ICAOPhraseology = {
|
||||
-- @field #ATIS.Soundfile TACANChannel
|
||||
-- @field #ATIS.Soundfile VORFrequency
|
||||
ATIS.Sound = {
|
||||
ActiveRunway = { filename = "ActiveRunway.ogg", duration = 0.99 },
|
||||
ActiveRunwayDeparture = { filename = "ActiveRunwayDeparture.ogg", duration = 0.99 },
|
||||
ActiveRunwayArrival = { filename = "ActiveRunwayArrival.ogg", duration = 0.99 },
|
||||
AdviceOnInitial = { filename = "AdviceOnInitial.ogg", duration = 3.00 },
|
||||
Airport = { filename = "Airport.ogg", duration = 0.66 },
|
||||
Altimeter = { filename = "Altimeter.ogg", duration = 0.68 },
|
||||
At = { filename = "At.ogg", duration = 0.41 },
|
||||
CloudBase = { filename = "CloudBase.ogg", duration = 0.82 },
|
||||
CloudCeiling = { filename = "CloudCeiling.ogg", duration = 0.61 },
|
||||
CloudsBroken = { filename = "CloudsBroken.ogg", duration = 1.07 },
|
||||
CloudsFew = { filename = "CloudsFew.ogg", duration = 0.99 },
|
||||
CloudsNo = { filename = "CloudsNo.ogg", duration = 1.01 },
|
||||
CloudsNotAvailable = { filename = "CloudsNotAvailable.ogg", duration = 2.35 },
|
||||
CloudsOvercast = { filename = "CloudsOvercast.ogg", duration = 0.83 },
|
||||
CloudsScattered = { filename = "CloudsScattered.ogg", duration = 1.18 },
|
||||
Decimal = { filename = "Decimal.ogg", duration = 0.54 },
|
||||
DegreesCelsius = { filename = "DegreesCelsius.ogg", duration = 1.27 },
|
||||
DegreesFahrenheit = { filename = "DegreesFahrenheit.ogg", duration = 1.23 },
|
||||
DewPoint = { filename = "DewPoint.ogg", duration = 0.65 },
|
||||
Dust = { filename = "Dust.ogg", duration = 0.54 },
|
||||
Elevation = { filename = "Elevation.ogg", duration = 0.78 },
|
||||
EndOfInformation = { filename = "EndOfInformation.ogg", duration = 1.15 },
|
||||
Feet = { filename = "Feet.ogg", duration = 0.45 },
|
||||
Fog = { filename = "Fog.ogg", duration = 0.47 },
|
||||
Gusting = { filename = "Gusting.ogg", duration = 0.55 },
|
||||
HectoPascal = { filename = "HectoPascal.ogg", duration = 1.15 },
|
||||
Hundred = { filename = "Hundred.ogg", duration = 0.47 },
|
||||
InchesOfMercury = { filename = "InchesOfMercury.ogg", duration = 1.16 },
|
||||
Information = { filename = "Information.ogg", duration = 0.85 },
|
||||
Kilometers = { filename = "Kilometers.ogg", duration = 0.78 },
|
||||
Knots = { filename = "Knots.ogg", duration = 0.59 },
|
||||
Left = { filename = "Left.ogg", duration = 0.54 },
|
||||
MegaHertz = { filename = "MegaHertz.ogg", duration = 0.87 },
|
||||
Meters = { filename = "Meters.ogg", duration = 0.59 },
|
||||
MetersPerSecond = { filename = "MetersPerSecond.ogg", duration = 1.14 },
|
||||
Miles = { filename = "Miles.ogg", duration = 0.60 },
|
||||
MillimetersOfMercury = { filename = "MillimetersOfMercury.ogg", duration = 1.53 },
|
||||
Minus = { filename = "Minus.ogg", duration = 0.64 },
|
||||
N0 = { filename = "N-0.ogg", duration = 0.55 },
|
||||
N1 = { filename = "N-1.ogg", duration = 0.41 },
|
||||
N2 = { filename = "N-2.ogg", duration = 0.37 },
|
||||
N3 = { filename = "N-3.ogg", duration = 0.41 },
|
||||
N4 = { filename = "N-4.ogg", duration = 0.37 },
|
||||
N5 = { filename = "N-5.ogg", duration = 0.43 },
|
||||
N6 = { filename = "N-6.ogg", duration = 0.55 },
|
||||
N7 = { filename = "N-7.ogg", duration = 0.43 },
|
||||
N8 = { filename = "N-8.ogg", duration = 0.38 },
|
||||
N9 = { filename = "N-9.ogg", duration = 0.55 },
|
||||
NauticalMiles = { filename = "NauticalMiles.ogg", duration = 1.04 },
|
||||
None = { filename = "None.ogg", duration = 0.43 },
|
||||
QFE = { filename = "QFE.ogg", duration = 0.63 },
|
||||
QNH = { filename = "QNH.ogg", duration = 0.71 },
|
||||
Rain = { filename = "Rain.ogg", duration = 0.41 },
|
||||
Right = { filename = "Right.ogg", duration = 0.44 },
|
||||
Snow = { filename = "Snow.ogg", duration = 0.48 },
|
||||
SnowStorm = { filename = "SnowStorm.ogg", duration = 0.82 },
|
||||
StatuteMiles = { filename = "StatuteMiles.ogg", duration = 1.15 },
|
||||
SunriseAt = { filename = "SunriseAt.ogg", duration = 0.92 },
|
||||
SunsetAt = { filename = "SunsetAt.ogg", duration = 0.95 },
|
||||
Temperature = { filename = "Temperature.ogg", duration = 0.64 },
|
||||
Thousand = { filename = "Thousand.ogg", duration = 0.55 },
|
||||
ThunderStorm = { filename = "ThunderStorm.ogg", duration = 0.81 },
|
||||
TimeLocal = { filename = "TimeLocal.ogg", duration = 0.90 },
|
||||
TimeZulu = { filename = "TimeZulu.ogg", duration = 0.86 },
|
||||
TowerFrequency = { filename = "TowerFrequency.ogg", duration = 1.19 },
|
||||
Visibilty = { filename = "Visibility.ogg", duration = 0.79 },
|
||||
WeatherPhenomena = { filename = "WeatherPhenomena.ogg", duration = 1.07 },
|
||||
WindFrom = { filename = "WindFrom.ogg", duration = 0.60 },
|
||||
ActiveRunway = { filename = "ActiveRunway.ogg", duration = 0.85 },
|
||||
ActiveRunwayDeparture = { filename = "ActiveRunwayDeparture.ogg", duration = 1.50 },
|
||||
ActiveRunwayArrival = { filename = "ActiveRunwayArrival.ogg", duration = 1.38 },
|
||||
AdviceOnInitial = { filename = "AdviceOnInitial.ogg", duration = 2.98 },
|
||||
Airport = { filename = "Airport.ogg", duration = 0.55 },
|
||||
Altimeter = { filename = "Altimeter.ogg", duration = 0.91 },
|
||||
At = { filename = "At.ogg", duration = 0.32 },
|
||||
CloudBase = { filename = "CloudBase.ogg", duration = 0.69 },
|
||||
CloudCeiling = { filename = "CloudCeiling.ogg", duration = 0.53 },
|
||||
CloudsBroken = { filename = "CloudsBroken.ogg", duration = 0.81 },
|
||||
CloudsFew = { filename = "CloudsFew.ogg", duration = 0.74 },
|
||||
CloudsNo = { filename = "CloudsNo.ogg", duration = 0.69},
|
||||
CloudsNotAvailable = { filename = "CloudsNotAvailable.ogg", duration = 2.64 },
|
||||
CloudsOvercast = { filename = "CloudsOvercast.ogg", duration = 0.82 },
|
||||
CloudsScattered = { filename = "CloudsScattered.ogg", duration = 0.89 },
|
||||
Decimal = { filename = "Decimal.ogg", duration = 0.71 },
|
||||
DegreesCelsius = { filename = "DegreesCelsius.ogg", duration = 1.08 },
|
||||
DegreesFahrenheit = { filename = "DegreesFahrenheit.ogg", duration = 1.07 },
|
||||
DewPoint = { filename = "DewPoint.ogg", duration = 0.59 },
|
||||
Dust = { filename = "Dust.ogg", duration = 0.37 },
|
||||
Elevation = { filename = "Elevation.ogg", duration = 0.92 },
|
||||
EndOfInformation = { filename = "EndOfInformation.ogg", duration = 1.24 },
|
||||
Feet = { filename = "Feet.ogg", duration = 0.34 },
|
||||
Fog = { filename = "Fog.ogg", duration = 0.41 },
|
||||
Gusting = { filename = "Gusting.ogg", duration = 0.58 },
|
||||
HectoPascal = { filename = "HectoPascal.ogg", duration = 0.92 },
|
||||
Hundred = { filename = "Hundred.ogg", duration = 0.53 },
|
||||
ILSFrequency = { filename = "ILSFrequency.ogg", duration = 1.30 },
|
||||
InnerNDBFrequency = { filename = "InnerNDBFrequency.ogg", duration = 1.56 },
|
||||
OuterNDBFrequency = { filename = "OuterNDBFrequency.ogg", duration = 1.59 },
|
||||
RunwayLength = { filename = "RunwayLength.ogg", duration = 0.91 },
|
||||
VORFrequency = { filename = "VORFrequency.ogg", duration = 1.38 },
|
||||
TACANChannel = { filename = "TACANChannel.ogg", duration = 0.88 },
|
||||
PRMGChannel = { filename = "PRMGChannel.ogg", duration = 1.18 },
|
||||
RSBNChannel = { filename = "RSBNChannel.ogg", duration = 1.14 },
|
||||
Zulu = { filename = "Zulu.ogg", duration = 0.62 },
|
||||
InchesOfMercury = { filename = "InchesOfMercury.ogg", duration = 1.26 },
|
||||
Information = { filename = "Information.ogg", duration = 0.99 },
|
||||
InnerNDBFrequency = { filename = "InnerNDBFrequency.ogg", duration = 1.69 },
|
||||
Kilometers = { filename = "Kilometers.ogg", duration = 0.93 },
|
||||
Knots = { filename = "Knots.ogg", duration = 0.46 },
|
||||
Left = { filename = "Left.ogg", duration = 0.41 },
|
||||
MegaHertz = { filename = "MegaHertz.ogg", duration = 0.83 },
|
||||
Meters = { filename = "Meters.ogg", duration = 0.55 },
|
||||
MetersPerSecond = { filename = "MetersPerSecond.ogg", duration = 1.03 },
|
||||
Miles = { filename = "Miles.ogg", duration = 0.44 },
|
||||
MillimetersOfMercury = { filename = "MillimetersOfMercury.ogg", duration = 1.59 },
|
||||
Minus = { filename = "Minus.ogg", duration = 0.55 },
|
||||
N0 = { filename = "N-0.ogg", duration = 0.52 },
|
||||
N1 = { filename = "N-1.ogg", duration = 0.35 },
|
||||
N2 = { filename = "N-2.ogg", duration = 0.41 },
|
||||
N3 = { filename = "N-3.ogg", duration = 0.34 },
|
||||
N4 = { filename = "N-4.ogg", duration = 0.37 },
|
||||
N5 = { filename = "N-5.ogg", duration = 0.40 },
|
||||
N6 = { filename = "N-6.ogg", duration = 0.46 },
|
||||
N7 = { filename = "N-7.ogg", duration = 0.52 },
|
||||
N8 = { filename = "N-8.ogg", duration = 0.36 },
|
||||
N9 = { filename = "N-9.ogg", duration = 0.51 },
|
||||
NauticalMiles = { filename = "NauticalMiles.ogg", duration = 0.93 },
|
||||
None = { filename = "None.ogg", duration = 0.33 },
|
||||
OuterNDBFrequency = { filename = "OuterNDBFrequency.ogg", duration = 1.70 },
|
||||
PRMGChannel = { filename = "PRMGChannel.ogg", duration = 1.27 },
|
||||
QFE = { filename = "QFE.ogg", duration = 0.90 },
|
||||
QNH = { filename = "QNH.ogg", duration = 0.94 },
|
||||
Rain = { filename = "Rain.ogg", duration = 0.35 },
|
||||
Right = { filename = "Right.ogg", duration = 0.31 },
|
||||
RSBNChannel = { filename = "RSBNChannel.ogg", duration = 1.26 },
|
||||
RunwayLength = { filename = "RunwayLength.ogg", duration = 0.81 },
|
||||
Snow = { filename = "Snow.ogg", duration = 0.40 },
|
||||
SnowStorm = { filename = "SnowStorm.ogg", duration = 0.73 },
|
||||
StatuteMiles = { filename = "StatuteMiles.ogg", duration = 0.90 },
|
||||
SunriseAt = { filename = "SunriseAt.ogg", duration = 0.82 },
|
||||
SunsetAt = { filename = "SunsetAt.ogg", duration = 0.87 },
|
||||
TACANChannel = { filename = "TACANChannel.ogg", duration = 0.81 },
|
||||
Temperature = { filename = "Temperature.ogg", duration = 0.70 },
|
||||
Thousand = { filename = "Thousand.ogg", duration = 0.58 },
|
||||
ThunderStorm = { filename = "ThunderStorm.ogg", duration = 0.79 },
|
||||
TimeLocal = { filename = "TimeLocal.ogg", duration = 0.83 },
|
||||
TimeZulu = { filename = "TimeZulu.ogg", duration = 0.83 },
|
||||
TowerFrequency = { filename = "TowerFrequency.ogg", duration = 1.05 },
|
||||
Visibilty = { filename = "Visibility.ogg", duration = 1.16 },
|
||||
VORFrequency = { filename = "VORFrequency.ogg", duration = 1.28 },
|
||||
WeatherPhenomena = { filename = "WeatherPhenomena.ogg", duration = 1.09 },
|
||||
WindFrom = { filename = "WindFrom.ogg", duration = 0.63 },
|
||||
Zulu = { filename = "Zulu.ogg", duration = 0.51 },
|
||||
}
|
||||
|
||||
---
|
||||
@@ -882,6 +894,66 @@ ATIS.Messages = {
|
||||
FARP = "Farp",
|
||||
DELIMITER = "Punto", -- decimal delimiter
|
||||
},
|
||||
-- French messages thanks to @Wojtech and Bing
|
||||
FR = {
|
||||
HOURS = "Heures",
|
||||
TIME = "Temps",
|
||||
NOCLOUDINFO = "Informations sur la couverture nuageuse non disponibles",
|
||||
OVERCAST = "Ciel couvert",
|
||||
BROKEN = "Nuages fragmentés",
|
||||
SCATTERED = "Nuages épars",
|
||||
FEWCLOUDS = "Nuages rares",
|
||||
NOCLOUDS = "Clair",
|
||||
AIRPORT = "Aéroport",
|
||||
INFORMATION ="Information",
|
||||
SUNRISEAT = "Levé du soleil à %s heure locale",
|
||||
SUNSETAT = "Couché du soleil à %s heure locale",
|
||||
WINDFROMMS = "Vent du %s pour %s mètres par seconde",
|
||||
WINDFROMKNOTS = "Vent du %s pour %s noeuds",
|
||||
GUSTING = "Rafale de vent",
|
||||
VISIKM = "Visibilité %s kilomètres",
|
||||
VISISM = "Visibilité %s Miles",
|
||||
RAIN = "Pluie",
|
||||
TSTORM = "Orage",
|
||||
SNOW = "Neige",
|
||||
SSTROM = "Tempête de neige",
|
||||
FOG = "Brouillard",
|
||||
DUST = "Poussière",
|
||||
PHENOMENA = "Phénomène météorologique",
|
||||
CLOUDBASEM = "Couverture nuageuse de %s à %s mètres",
|
||||
CLOUDBASEFT = "Couverture nuageuse de %s à %s pieds",
|
||||
TEMPERATURE = "Température",
|
||||
DEWPOINT = "Point de rosée",
|
||||
ALTIMETER = "Altimètre",
|
||||
ACTIVERUN = "Décollages piste",
|
||||
ACTIVELANDING = "Atterrissages piste",
|
||||
LEFT = "Gauche",
|
||||
RIGHT = "Droite",
|
||||
RWYLENGTH = "Longueur de piste",
|
||||
METERS = "Mètre",
|
||||
FEET = "Pieds",
|
||||
ELEVATION = "Hauteur",
|
||||
TOWERFREQ = "Fréquences de la tour",
|
||||
ILSFREQ = "Fréquences ILS",
|
||||
OUTERNDB = "Fréquences Outer NDB",
|
||||
INNERNDB = "Fréquences Inner NDB",
|
||||
VORFREQ = "Fréquences VOR",
|
||||
VORFREQTTS = "Fréquences V O R",
|
||||
TACANCH = "Canal TACAN %d",
|
||||
RSBNCH = "Canal RSBN",
|
||||
PRMGCH = "Canal PRMG",
|
||||
ADVISE = "Informez le contrôle que vous avez copié l'information",
|
||||
STATUTE = "Statute Miles",
|
||||
DEGREES = "Degré celcius",
|
||||
FAHRENHEIT = "Degré Fahrenheit",
|
||||
INCHHG = "Pouces de mercure",
|
||||
MMHG = "Millimètres de mercure",
|
||||
HECTO = "Hectopascals",
|
||||
METERSPER = "Mètres par seconde",
|
||||
TACAN = "TAKAN",
|
||||
FARP = "FARPE",
|
||||
DELIMITER = "Décimal", -- decimal delimiter
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
@@ -894,7 +966,7 @@ _ATIS = {}
|
||||
|
||||
--- ATIS class version.
|
||||
-- @field #string version
|
||||
ATIS.version = "1.0.0"
|
||||
ATIS.version = "1.0.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -1061,7 +1133,7 @@ end
|
||||
-- @return #ATIS self
|
||||
function ATIS:_InitLocalization()
|
||||
self:T(self.lid.."_InitLocalization")
|
||||
self.gettext = TEXTANDSOUND:New("AWACS","en") -- Core.TextAndSound#TEXTANDSOUND
|
||||
self.gettext = TEXTANDSOUND:New("ATIS","en") -- Core.TextAndSound#TEXTANDSOUND
|
||||
self.locale = "en"
|
||||
for locale,table in pairs(self.Messages) do
|
||||
local Locale = string.lower(tostring(locale))
|
||||
@@ -1975,17 +2047,28 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
|
||||
local hours = self.gettext:GetEntry("HOURS",self.locale)
|
||||
local sunrise = coord:GetSunrise()
|
||||
sunrise = UTILS.Split( sunrise, ":" )
|
||||
local SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] )
|
||||
if self.useSRS then
|
||||
SUNRISE = string.format( "%s %s %s", sunrise[1], sunrise[2], hours )
|
||||
--self:I(sunrise)
|
||||
local SUNRISE = "no time"
|
||||
local NorthPolar = true
|
||||
if tostring(sunrise) ~= "N/S" and tostring(sunrise) ~= "N/R" then
|
||||
sunrise = UTILS.Split( sunrise, ":" )
|
||||
SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] )
|
||||
if self.useSRS then
|
||||
SUNRISE = string.format( "%s %s %s", sunrise[1], sunrise[2], hours )
|
||||
end
|
||||
NorthPolar = false
|
||||
end
|
||||
|
||||
|
||||
local sunset = coord:GetSunset()
|
||||
sunset = UTILS.Split( sunset, ":" )
|
||||
local SUNSET = string.format( "%s%s", sunset[1], sunset[2] )
|
||||
if self.useSRS then
|
||||
SUNSET = string.format( "%s %s %s", sunset[1], sunset[2], hours )
|
||||
--self:I(sunset)
|
||||
local SUNSET = "no time"
|
||||
if tostring(sunset) ~= "N/S" and tostring(sunset) ~= "N/R" then
|
||||
sunset = UTILS.Split( sunset, ":" )
|
||||
SUNSET = string.format( "%s%s", sunset[1], sunset[2] )
|
||||
if self.useSRS then
|
||||
SUNSET = string.format( "%s %s %s", sunset[1], sunset[2], hours )
|
||||
end
|
||||
NorthPolar = false
|
||||
end
|
||||
|
||||
---------------------------------
|
||||
@@ -2012,34 +2095,32 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
---------------
|
||||
|
||||
-- Get mission weather info. Most of this is static.
|
||||
local clouds, visibility, turbulence, fog, dust, static = self:GetMissionWeather()
|
||||
|
||||
-- Check that fog is actually "thick" enough to reach the airport. If an airport is in the mountains, fog might not affect it as it is measured from sea level.
|
||||
if fog and fog.thickness < height + 25 then
|
||||
fog = nil
|
||||
end
|
||||
|
||||
-- Dust only up to 1500 ft = 457 m ASL.
|
||||
if dust and height + 25 > UTILS.FeetToMeters( 1500 ) then
|
||||
dust = nil
|
||||
end
|
||||
local clouds, visibility, turbulence, dustdens, static = self:GetMissionWeather()
|
||||
|
||||
local dust=false
|
||||
local fog=false
|
||||
|
||||
------------------
|
||||
--- Visibility ---
|
||||
------------------
|
||||
|
||||
-- Get min visibility.
|
||||
local visibilitymin = visibility
|
||||
|
||||
if fog then
|
||||
if fog.visibility < visibilitymin then
|
||||
visibilitymin = fog.visibility
|
||||
if dustdens then
|
||||
|
||||
-- Dust only up to 1500 ft = 457 m ASL.
|
||||
if UTILS.FeetToMeters( 1500 )> height+25 then
|
||||
dust=true
|
||||
visibility=math.min(visibility, dustdens)
|
||||
end
|
||||
end
|
||||
|
||||
if dust then
|
||||
if dust < visibilitymin then
|
||||
visibilitymin = dust
|
||||
|
||||
else -- As of DCS 2.9.10.3948 (December 2024), fog and dust are mutually exclusive!
|
||||
|
||||
-- Get current fog visibility and thickness
|
||||
local fvis=world.weather.getFogVisibilityDistance()
|
||||
local fheight=world.weather.getFogThickness()
|
||||
|
||||
if fvis>0 and fheight>height+25 then
|
||||
fog=true
|
||||
visibility=math.min(visibility, fvis)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2047,7 +2128,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
|
||||
if self.metric then
|
||||
-- Visibility in km.
|
||||
local reportedviz = UTILS.Round( visibilitymin / 1000 )
|
||||
local reportedviz = UTILS.Round( visibility / 1000 )
|
||||
-- max reported visibility 9999 m
|
||||
if reportedviz > 10 then
|
||||
reportedviz = 10
|
||||
@@ -2055,7 +2136,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
VISIBILITY = string.format( "%d", reportedviz )
|
||||
else
|
||||
-- max reported visibility 10 NM
|
||||
local reportedviz = UTILS.Round( UTILS.MetersToSM( visibilitymin ) )
|
||||
local reportedviz = UTILS.Round( UTILS.MetersToSM( visibility ) )
|
||||
if reportedviz > 10 then
|
||||
reportedviz = 10
|
||||
end
|
||||
@@ -2069,7 +2150,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
local cloudbase = clouds.base
|
||||
local cloudceil = clouds.base + clouds.thickness
|
||||
local clouddens = clouds.density
|
||||
|
||||
|
||||
-- Cloud preset (DCS 2.7)
|
||||
local cloudspreset = clouds.preset or "Nothing"
|
||||
|
||||
@@ -2100,6 +2181,39 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
else
|
||||
precepitation = 3 -- snow
|
||||
end
|
||||
elseif cloudspreset:find( "RainyPreset4" ) then
|
||||
-- Overcast + Rain
|
||||
clouddens = 5
|
||||
if temperature > 5 then
|
||||
precepitation = 1 -- rain
|
||||
else
|
||||
precepitation = 3 -- snow
|
||||
end
|
||||
elseif cloudspreset:find( "RainyPreset5" ) then
|
||||
-- Overcast + Rain
|
||||
clouddens = 5
|
||||
if temperature > 5 then
|
||||
precepitation = 1 -- rain
|
||||
else
|
||||
precepitation = 3 -- snow
|
||||
end
|
||||
elseif cloudspreset:find( "RainyPreset6" ) then
|
||||
-- Overcast + Rain
|
||||
clouddens = 5
|
||||
if temperature > 5 then
|
||||
precepitation = 1 -- rain
|
||||
else
|
||||
precepitation = 3 -- snow
|
||||
end
|
||||
-- NEWRAINPRESET4
|
||||
elseif cloudspreset:find( "NEWRAINPRESET4" ) then
|
||||
-- Overcast + Rain
|
||||
clouddens = 5
|
||||
if temperature > 5 then
|
||||
precepitation = 1 -- rain
|
||||
else
|
||||
precepitation = 3 -- snow
|
||||
end
|
||||
elseif cloudspreset:find( "RainyPreset" ) then
|
||||
-- Overcast + Rain
|
||||
clouddens = 9
|
||||
@@ -2294,7 +2408,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
local sunrise = self.gettext:GetEntry("SUNRISEAT",self.locale)
|
||||
--subtitle = string.format( "Sunrise at %s local time", SUNRISE )
|
||||
subtitle = string.format( sunrise, SUNRISE )
|
||||
if not self.useSRS then
|
||||
if not self.useSRS and NorthPolar == false then
|
||||
self:Transmission( self.Sound.SunriseAt, 0.5, subtitle )
|
||||
self.radioqueue:Number2Transmission( SUNRISE, nil, 0.2 )
|
||||
self:Transmission( self.Sound.TimeLocal, 0.2 )
|
||||
@@ -2305,7 +2419,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
local sunset = self.gettext:GetEntry("SUNSETAT",self.locale)
|
||||
--subtitle = string.format( "Sunset at %s local time", SUNSET )
|
||||
subtitle = string.format( sunset, SUNSET )
|
||||
if not self.useSRS then
|
||||
if not self.useSRS and NorthPolar == false then
|
||||
self:Transmission( self.Sound.SunsetAt, 0.5, subtitle )
|
||||
self.radioqueue:Number2Transmission( SUNSET, nil, 0.5 )
|
||||
self:Transmission( self.Sound.TimeLocal, 0.2 )
|
||||
@@ -2631,7 +2745,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
if not self.ATISforFARPs then
|
||||
-- Active runway.
|
||||
local subtitle = ""
|
||||
if runwayLanding then
|
||||
if runwayLanding and runwayLanding ~= runwayTakeoff then
|
||||
|
||||
local actrun = self.gettext:GetEntry("ACTIVELANDING",self.locale)
|
||||
|
||||
@@ -3249,28 +3363,13 @@ function ATIS:GetMissionWeather()
|
||||
dust = weather.dust_density
|
||||
end
|
||||
|
||||
-- Fog
|
||||
--[[
|
||||
["enable_fog"] = false,
|
||||
["fog"] =
|
||||
{
|
||||
["thickness"] = 0,
|
||||
["visibility"] = 25,
|
||||
}, -- end of ["fog"]
|
||||
]]
|
||||
local fog = nil
|
||||
if weather.enable_fog == true then
|
||||
fog = weather.fog
|
||||
end
|
||||
|
||||
self:T( "FF weather:" )
|
||||
self:T( { clouds = clouds } )
|
||||
self:T( { visibility = visibility } )
|
||||
self:T( { turbulence = turbulence } )
|
||||
self:T( { fog = fog } )
|
||||
self:T( { dust = dust } )
|
||||
self:T( { static = static } )
|
||||
return clouds, visibility, turbulence, fog, dust, static
|
||||
return clouds, visibility, turbulence, dust, static
|
||||
end
|
||||
|
||||
--- Get thousands of a number.
|
||||
|
||||
@@ -187,7 +187,7 @@ AIRWING = {
|
||||
|
||||
--- AIRWING class version.
|
||||
-- @field #string version
|
||||
AIRWING.version="0.9.5"
|
||||
AIRWING.version="0.9.6"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@@ -1041,6 +1041,9 @@ function AIRWING:onafterStatus(From, Event, To)
|
||||
|
||||
-- Check Recon missions.
|
||||
self:CheckRECON()
|
||||
|
||||
-- Display tactival overview.
|
||||
self:_TacticalOverview()
|
||||
|
||||
----------------
|
||||
-- Transport ---
|
||||
@@ -1362,16 +1365,20 @@ function AIRWING:CheckRescuhelo()
|
||||
|
||||
local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO})
|
||||
|
||||
local name=self.airbase:GetName()
|
||||
|
||||
local carrier=UNIT:FindByName(name)
|
||||
|
||||
for i=1,self.nflightsRescueHelo-N do
|
||||
|
||||
local mission=AUFTRAG:NewRESCUEHELO(carrier)
|
||||
|
||||
self:AddMission(mission)
|
||||
|
||||
if self.airbase then
|
||||
|
||||
local name=self.airbase:GetName()
|
||||
|
||||
local carrier=UNIT:FindByName(name)
|
||||
|
||||
for i=1,self.nflightsRescueHelo-N do
|
||||
|
||||
local mission=AUFTRAG:NewRESCUEHELO(carrier)
|
||||
|
||||
self:AddMission(mission)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
|
||||
@@ -3615,7 +3615,7 @@ function AIRBOSS:onafterStart( From, Event, To )
|
||||
|
||||
-- Handle events.
|
||||
self:HandleEvent( EVENTS.Birth )
|
||||
self:HandleEvent( EVENTS.Land )
|
||||
self:HandleEvent( EVENTS.RunwayTouch )
|
||||
self:HandleEvent( EVENTS.EngineShutdown )
|
||||
self:HandleEvent( EVENTS.Takeoff )
|
||||
self:HandleEvent( EVENTS.Crash )
|
||||
@@ -3623,6 +3623,7 @@ function AIRBOSS:onafterStart( From, Event, To )
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self._PlayerLeft )
|
||||
self:HandleEvent( EVENTS.MissionEnd )
|
||||
self:HandleEvent( EVENTS.RemoveUnit )
|
||||
self:HandleEvent( EVENTS.UnitLost, self.OnEventRemoveUnit )
|
||||
|
||||
-- self.StatusScheduler=SCHEDULER:New(self)
|
||||
-- self.StatusScheduler:Schedule(self, self._Status, {}, 1, 0.5)
|
||||
@@ -4380,7 +4381,7 @@ function AIRBOSS:onafterStop( From, Event, To )
|
||||
|
||||
-- Unhandle events.
|
||||
self:UnHandleEvent( EVENTS.Birth )
|
||||
self:UnHandleEvent( EVENTS.Land )
|
||||
self:UnHandleEvent( EVENTS.RunwayTouch )
|
||||
self:UnHandleEvent( EVENTS.EngineShutdown )
|
||||
self:UnHandleEvent( EVENTS.Takeoff )
|
||||
self:UnHandleEvent( EVENTS.Crash )
|
||||
@@ -8290,7 +8291,7 @@ end
|
||||
--- Airboss event handler for event land.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AIRBOSS:OnEventLand( EventData )
|
||||
function AIRBOSS:OnEventRunwayTouch( EventData )
|
||||
self:F3( { eventland = EventData } )
|
||||
|
||||
-- Nil checks.
|
||||
@@ -14679,7 +14680,7 @@ function AIRBOSS:_GetPlayerUnitAndName( _unitName )
|
||||
-- Get DCS unit from its name.
|
||||
local DCSunit = Unit.getByName( _unitName )
|
||||
|
||||
if DCSunit then
|
||||
if DCSunit and DCSunit.getPlayerName then
|
||||
|
||||
-- Get player name if any.
|
||||
local playername = DCSunit:getPlayerName()
|
||||
@@ -15650,7 +15651,7 @@ function AIRBOSS:_Number2Sound( playerData, sender, number, delay )
|
||||
end
|
||||
|
||||
-- Split string into characters.
|
||||
local numbers = _split( number )
|
||||
local numbers = _split( tostring(number) )
|
||||
|
||||
local wait = 0
|
||||
for i = 1, #numbers do
|
||||
@@ -15718,7 +15719,7 @@ function AIRBOSS:_Number2Radio( radio, number, delay, interval, pilotcall )
|
||||
end
|
||||
|
||||
-- Split string into characters.
|
||||
local numbers = _split( number )
|
||||
local numbers = _split( tostring(number) )
|
||||
|
||||
local wait = 0
|
||||
for i = 1, #numbers do
|
||||
@@ -18086,7 +18087,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare )
|
||||
self:_GetZoneArcIn( case ):FlareZone( FLARECOLOR.White, 45 )
|
||||
text = text .. "\n* arc turn in with WHITE flares"
|
||||
self:_GetZoneArcOut( case ):FlareZone( FLARECOLOR.White, 45 )
|
||||
text = text .. "\n* arc trun out with WHITE flares"
|
||||
text = text .. "\n* arc turn out with WHITE flares"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18138,7 +18139,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare )
|
||||
self:_GetZoneArcIn( case ):SmokeZone( SMOKECOLOR.Blue, 45 )
|
||||
text = text .. "\n* arc turn in with BLUE smoke"
|
||||
self:_GetZoneArcOut( case ):SmokeZone( SMOKECOLOR.Blue, 45 )
|
||||
text = text .. "\n* arc trun out with BLUE smoke"
|
||||
text = text .. "\n* arc turn out with BLUE smoke"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ ARMYGROUP = {
|
||||
|
||||
--- Army Group version.
|
||||
-- @field #string version
|
||||
ARMYGROUP.version="1.0.1"
|
||||
ARMYGROUP.version="1.0.3"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -403,6 +403,7 @@ function ARMYGROUP:New(group)
|
||||
self:HandleEvent(EVENTS.Birth, self.OnEventBirth)
|
||||
self:HandleEvent(EVENTS.Dead, self.OnEventDead)
|
||||
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit)
|
||||
self:HandleEvent(EVENTS.UnitLost, self.OnEventRemoveUnit)
|
||||
self:HandleEvent(EVENTS.Hit, self.OnEventHit)
|
||||
|
||||
-- Start the status monitoring.
|
||||
@@ -2048,114 +2049,70 @@ end
|
||||
--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil.
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param #table Template Template used to init the group. Default is `self.template`.
|
||||
-- @param #number Delay Delay in seconds before group is initialized. Default `nil`, *i.e.* instantaneous.
|
||||
-- @return #ARMYGROUP self
|
||||
function ARMYGROUP:_InitGroup(Template, Delay)
|
||||
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, ARMYGROUP._InitGroup, self, Template, 0)
|
||||
else
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Get template of group.
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
-- Ground are always AI.
|
||||
self.isAI=true
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=template.lateActivation
|
||||
|
||||
-- Ground groups cannot be uncontrolled.
|
||||
self.isUncontrolled=false
|
||||
|
||||
-- Max speed in km/h.
|
||||
self.speedMax=self.group:GetSpeedMax()
|
||||
|
||||
-- Is group mobile?
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
-- Cruise speed in km/h
|
||||
self.speedCruise=self.speedMax*0.7
|
||||
|
||||
-- Group ammo.
|
||||
self.ammo=self:GetAmmoTot()
|
||||
|
||||
-- Radio parameters from template.
|
||||
self.radio.On=false -- Radio is always OFF for ground.
|
||||
self.radio.Freq=133
|
||||
self.radio.Modu=radio.modulation.AM
|
||||
|
||||
-- Set default radio.
|
||||
self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On)
|
||||
|
||||
-- Get current formation from first waypoint.
|
||||
self.option.Formation=template.route.points[1].action
|
||||
|
||||
-- Set default formation to "on road".
|
||||
self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad
|
||||
|
||||
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
|
||||
self:T(self.lid.."FF Initializing Group")
|
||||
|
||||
|
||||
-- Get template of group.
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
|
||||
-- Ground are always AI.
|
||||
self.isAI=true
|
||||
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=template.lateActivation
|
||||
|
||||
|
||||
-- Ground groups cannot be uncontrolled.
|
||||
self.isUncontrolled=false
|
||||
|
||||
|
||||
-- Max speed in km/h.
|
||||
self.speedMax=self.group:GetSpeedMax()
|
||||
|
||||
|
||||
-- Is group mobile?
|
||||
if self.speedMax>3.6 then
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
|
||||
-- Cruise speed in km/h
|
||||
self.speedCruise=self.speedMax*0.7
|
||||
|
||||
|
||||
-- Group ammo.
|
||||
self.ammo=self:GetAmmoTot()
|
||||
|
||||
|
||||
-- Radio parameters from template.
|
||||
self.radio.On=false -- Radio is always OFF for ground.
|
||||
self.radio.Freq=133
|
||||
self.radio.Modu=radio.modulation.AM
|
||||
|
||||
|
||||
-- Set default radio.
|
||||
self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On)
|
||||
|
||||
|
||||
-- Get current formation from first waypoint.
|
||||
self.option.Formation=template.route.points[1].action
|
||||
|
||||
|
||||
-- Set default formation to "on road".
|
||||
self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad
|
||||
|
||||
-- Default TACAN off.
|
||||
self:SetDefaultTACAN(nil, nil, nil, nil, true)
|
||||
self.tacan=UTILS.DeepCopy(self.tacanDefault)
|
||||
if not self.tacanDefault then
|
||||
self:SetDefaultTACAN(nil, nil, nil, nil, true)
|
||||
end
|
||||
if not self.tacan then
|
||||
self.tacan=UTILS.DeepCopy(self.tacanDefault)
|
||||
end
|
||||
|
||||
-- Units of the group.
|
||||
local units=self.group:GetUnits()
|
||||
@@ -2176,7 +2133,6 @@ function ARMYGROUP:_InitGroup(Template, Delay)
|
||||
self:_AddElementByName(unitname)
|
||||
end
|
||||
|
||||
|
||||
-- Init done.
|
||||
self.groupinitialized=true
|
||||
end
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
-- @field #number engageWeaponType Weapon type used.
|
||||
-- @field #number engageWeaponExpend How many weapons are used.
|
||||
-- @field #boolean engageAsGroup Group attack.
|
||||
-- @field #number engageLength Length of engage (carpet or strafing) in meters.
|
||||
-- @field #number engageMaxDistance Max engage distance.
|
||||
-- @field #number refuelSystem Refuel type (boom or probe) for TANKER missions.
|
||||
--
|
||||
@@ -161,6 +162,7 @@
|
||||
-- @field #number missionRange Mission range in meters. Used by LEGION classes (AIRWING, BRIGADE, ...).
|
||||
-- @field Core.Point#COORDINATE missionWaypointCoord Mission waypoint coordinate.
|
||||
-- @field Core.Point#COORDINATE missionEgressCoord Mission egress waypoint coordinate.
|
||||
-- @field Core.Point#COORDINATE missionIngressCoord Mission Ingress waypoint coordinate.
|
||||
-- @field #number missionWaypointRadius Random radius in meters.
|
||||
-- @field #boolean legionReturn If `true`, assets return to their legion (default). If `false`, they will stay alive.
|
||||
--
|
||||
@@ -239,6 +241,10 @@
|
||||
-- ## Bombing Carpet
|
||||
--
|
||||
-- A carpet bombing mission can be created with the @{#AUFTRAG.NewBOMBCARPET}() function.
|
||||
--
|
||||
-- ## Strafing
|
||||
--
|
||||
-- A strafing mission can be created with the @{#AUFTRAG.NewSTRAFING}() function.
|
||||
--
|
||||
-- ## CAP
|
||||
--
|
||||
@@ -445,6 +451,7 @@ _AUFTRAGSNR=0
|
||||
-- @field #string CAPTUREZONE Capture zone mission.
|
||||
-- @field #string NOTHING Nothing.
|
||||
-- @field #string PATROLRACETRACK Patrol Racetrack.
|
||||
-- @field #string STRAFING Strafing run.
|
||||
AUFTRAG.Type={
|
||||
ANTISHIP="Anti Ship",
|
||||
AWACS="AWACS",
|
||||
@@ -491,6 +498,7 @@ AUFTRAG.Type={
|
||||
CAPTUREZONE="Capture Zone",
|
||||
NOTHING="Nothing",
|
||||
PATROLRACETRACK="Patrol Racetrack",
|
||||
STRAFING="Strafing",
|
||||
}
|
||||
|
||||
--- Special task description.
|
||||
@@ -1062,8 +1070,10 @@ end
|
||||
-- @param #number Time Time in seconds to stay. Default 300 seconds.
|
||||
-- @param #number Speed Speed in knots to fly to the target coordinate. Default 150kn.
|
||||
-- @param #number MissionAlt Altitude to fly towards the mission in feet AGL. Default 1000ft.
|
||||
-- @param #boolean CombatLanding (Optional) If true, set the Combat Landing option.
|
||||
-- @param #number DirectionAfterLand (Optional) Heading after landing in degrees.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewLANDATCOORDINATE(Coordinate, OuterRadius, InnerRadius, Time, Speed, MissionAlt)
|
||||
function AUFTRAG:NewLANDATCOORDINATE(Coordinate, OuterRadius, InnerRadius, Time, Speed, MissionAlt, CombatLanding, DirectionAfterLand)
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.LANDATCOORDINATE)
|
||||
|
||||
@@ -1071,6 +1081,8 @@ function AUFTRAG:NewLANDATCOORDINATE(Coordinate, OuterRadius, InnerRadius, Time,
|
||||
|
||||
mission.stayTime = Time or 300
|
||||
mission.stayAt = Coordinate
|
||||
mission.combatLand = CombatLanding
|
||||
mission.directionAfter = DirectionAfterLand
|
||||
self:SetMissionSpeed(Speed or 150)
|
||||
self:SetMissionAltitude(MissionAlt or 1000)
|
||||
|
||||
@@ -1707,15 +1719,16 @@ end
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC or TARGET object.
|
||||
-- @param #number Altitude Engage altitude in feet. Default 2000 ft.
|
||||
-- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewSTRIKE(Target, Altitude)
|
||||
function AUFTRAG:NewSTRIKE(Target, Altitude, EngageWeaponType)
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.STRIKE)
|
||||
|
||||
mission:_TargetFromObject(Target)
|
||||
|
||||
-- DCS Task options:
|
||||
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
|
||||
mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto
|
||||
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
|
||||
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000)
|
||||
|
||||
@@ -1734,18 +1747,20 @@ function AUFTRAG:NewSTRIKE(Target, Altitude)
|
||||
end
|
||||
|
||||
--- **[AIR]** Create a BOMBING mission. Flight will drop bombs a specified coordinate.
|
||||
-- See [DCS task bombing](https://wiki.hoggitworld.com/view/DCS_task_bombing).
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC or TARGET object.
|
||||
-- @param #number Altitude Engage altitude in feet. Default 25000 ft.
|
||||
-- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewBOMBING(Target, Altitude)
|
||||
function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType)
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING)
|
||||
|
||||
mission:_TargetFromObject(Target)
|
||||
|
||||
-- DCS task options:
|
||||
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
|
||||
mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto
|
||||
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
|
||||
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000)
|
||||
|
||||
@@ -1767,9 +1782,47 @@ function AUFTRAG:NewBOMBING(Target, Altitude)
|
||||
return mission
|
||||
end
|
||||
|
||||
--- **[AIR]** Create a STRAFING mission. Assigns a point on the ground for which the AI will do a strafing run with guns or rockets.
|
||||
-- See [DCS task strafing](https://wiki.hoggitworld.com/view/DCS_task_strafing).
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC or TARGET object.
|
||||
-- @param #number Altitude Engage altitude in feet. Default 1000 ft.
|
||||
-- @param #number Length The total length of the strafing target in meters. Default `nil`.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewSTRAFING(Target, Altitude, Length)
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.STRAFING)
|
||||
|
||||
mission:_TargetFromObject(Target)
|
||||
|
||||
-- DCS task options:
|
||||
mission.engageWeaponType=805337088 -- Corresponds to guns/cannons (805306368) + any rocket (30720). This is the default when selecting this task in the ME.
|
||||
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
|
||||
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 1000)
|
||||
mission.engageLength=Length
|
||||
|
||||
-- Mission options:
|
||||
mission.missionTask=ENUMS.MissionTask.GROUNDATTACK
|
||||
mission.missionAltitude=mission.engageAltitude*0.8
|
||||
mission.missionFraction=0.5
|
||||
mission.optionROE=ENUMS.ROE.OpenFire
|
||||
mission.optionROT=ENUMS.ROT.NoReaction -- No reaction is better.
|
||||
|
||||
-- Evaluate result after 5 min. We might need time until the bombs have dropped and targets have been detroyed.
|
||||
mission.dTevaluate=5*60
|
||||
|
||||
mission.categories={AUFTRAG.Category.AIRCRAFT}
|
||||
|
||||
-- Get DCS task.
|
||||
mission.DCStask=mission:GetDCSMissionTask()
|
||||
|
||||
return mission
|
||||
end
|
||||
|
||||
|
||||
--- **[AIR]** Create a BOMBRUNWAY mission.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Wrapper.Airbase#AIRBASE Airdrome The airbase to bomb. This must be an airdrome (not a FARP or ship) as these to not have a runway.
|
||||
-- @param Wrapper.Airbase#AIRBASE Airdrome The airbase to bomb. This must be an airdrome (not a FARP or ship) as these do not have a runway.
|
||||
-- @param #number Altitude Engage altitude in feet. Default 25000 ft.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude)
|
||||
@@ -1821,7 +1874,7 @@ function AUFTRAG:NewBOMBCARPET(Target, Altitude, CarpetLength)
|
||||
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
|
||||
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
|
||||
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000)
|
||||
mission.engageCarpetLength=CarpetLength or 500
|
||||
mission.engageLength=CarpetLength or 500
|
||||
mission.engageAsGroup=false -- Looks like this must be false or the task is not executed. It is not available in the ME anyway but in the task of the mission file.
|
||||
mission.engageDirection=nil -- This is also not available in the ME.
|
||||
|
||||
@@ -2105,7 +2158,11 @@ end
|
||||
|
||||
]]
|
||||
|
||||
--- **[GROUND, NAVAL]** Create an ARTY mission.
|
||||
--- **[GROUND, NAVAL]** Create an ARTY mission ("Fire at point" task).
|
||||
--
|
||||
-- If the group has more than one weapon type supporting the "Fire at point" task, the employed weapon type can be set via the `AUFTRAG:SetWeaponType()` function.
|
||||
--
|
||||
-- **Note** that it is recommended to set the weapon range via the `OPSGROUP:AddWeaponRange()` function as this cannot be retrieved from the DCS API.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Target Center of the firing solution.
|
||||
-- @param #number Nshots Number of shots to be fired. Default `#nil`.
|
||||
@@ -2128,6 +2185,7 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius, Altitude)
|
||||
mission.optionAlarm=0
|
||||
|
||||
mission.missionFraction=0.0
|
||||
mission.missionWaypointRadius=0.0
|
||||
|
||||
-- Evaluate after 8 min.
|
||||
mission.dTevaluate=8*60
|
||||
@@ -2252,7 +2310,7 @@ function AUFTRAG:NewCAPTUREZONE(OpsZone, Coalition, Speed, Altitude, Formation)
|
||||
params.formation=Formation or "Off Road"
|
||||
params.zone=mission:GetObjective()
|
||||
params.altitude=mission.missionAltitude
|
||||
params.speed=mission.missionSpeed
|
||||
params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed) or nil
|
||||
|
||||
mission.DCStask.params=params
|
||||
|
||||
@@ -2302,7 +2360,7 @@ function AUFTRAG:NewGROUNDATTACK(Target, Speed, Formation)
|
||||
|
||||
mission.DCStask=mission:GetDCSMissionTask()
|
||||
|
||||
mission.DCStask.params.speed=Speed
|
||||
mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed) or nil
|
||||
mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee
|
||||
|
||||
return mission
|
||||
@@ -2611,6 +2669,8 @@ function AUFTRAG:NewFromTarget(Target, MissionType)
|
||||
mission=self:NewBOMBING(Target, Altitude)
|
||||
elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then
|
||||
mission=self:NewBOMBRUNWAY(Target, Altitude)
|
||||
elseif MissionType==AUFTRAG.Type.STRAFING then
|
||||
mission=self:NewSTRAFING(Target, Altitude)
|
||||
elseif MissionType==AUFTRAG.Type.CAS then
|
||||
mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000), Altitude, Speed, Target:GetAverageCoordinate(), Heading, Leg, TargetTypes)
|
||||
elseif MissionType==AUFTRAG.Type.CASENHANCED then
|
||||
@@ -3429,7 +3489,7 @@ end
|
||||
|
||||
--- Set Rules of Engagement (ROE) for this mission.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param #string roe Mission ROE.
|
||||
-- @param #number roe Mission ROE, e.g. `ENUMS.ROE.ReturnFire` (whiche equals 3)
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetROE(roe)
|
||||
|
||||
@@ -3441,7 +3501,7 @@ end
|
||||
|
||||
--- Set Reaction on Threat (ROT) for this mission.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param #string rot Mission ROT.
|
||||
-- @param #number rot Mission ROT, e.g. `ENUMS.ROT.NoReaction` (whiche equals 0)
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetROT(rot)
|
||||
|
||||
@@ -4605,6 +4665,16 @@ function AUFTRAG:SetGroupWaypointCoordinate(opsgroup, coordinate)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Air] Set mission (ingress) waypoint coordinate for FLIGHT group.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE coordinate Waypoint Coordinate.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetIngressCoordinate(coordinate)
|
||||
self.missionIngressCoord = coordinate
|
||||
self.missionIngressCoordAlt = UTILS.MetersToFeet(coordinate.y) or 10000
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get mission (ingress) waypoint coordinate of OPS group
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group.
|
||||
@@ -5660,7 +5730,7 @@ function AUFTRAG:GetMissionTypesText(MissionTypes)
|
||||
return text
|
||||
end
|
||||
|
||||
--- Set the mission waypoint coordinate where the mission is executed. Note that altitude is set via `:SetMissionAltitude`.
|
||||
--- [NON-AIR] Set the mission waypoint coordinate from where the mission is executed. Note that altitude is set via `:SetMissionAltitude`.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where the mission is executed.
|
||||
-- @return #AUFTRAG self
|
||||
@@ -5688,8 +5758,9 @@ end
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Coordinate Egrees coordinate.
|
||||
-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate.
|
||||
-- @param #number Speed (Optional) Speed in knots to reach this waypoint. Defaults to mission speed.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude)
|
||||
function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude, Speed)
|
||||
|
||||
-- Obviously a zone was passed. We get the coordinate.
|
||||
if Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
@@ -5700,7 +5771,64 @@ function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude)
|
||||
|
||||
if Altitude then
|
||||
self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude)
|
||||
self.missionEgressCoordAlt = UTILS.FeetToMeters(Altitude)
|
||||
end
|
||||
|
||||
self.missionEgressCoordSpeed=Speed and Speed or nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Air] Set the mission ingress coordinate. This is the coordinate where the assigned group will fly before the actual mission coordinate.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Coordinate Ingrees coordinate.
|
||||
-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate.
|
||||
-- @param #number Speed (Optional) Speed in knots to reach this waypoint. Defaults to mission speed.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetMissionIngressCoord(Coordinate, Altitude, Speed)
|
||||
|
||||
-- Obviously a zone was passed. We get the coordinate.
|
||||
if Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
Coordinate=Coordinate:GetCoordinate()
|
||||
end
|
||||
|
||||
self.missionIngressCoord=Coordinate
|
||||
|
||||
if Altitude then
|
||||
self.missionIngressCoord.y=UTILS.FeetToMeters(Altitude)
|
||||
self.missionIngressCoordAlt = UTILS.FeetToMeters(Altitude or 10000)
|
||||
end
|
||||
|
||||
self.missionIngressCoordSpeed=Speed and Speed or nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Air] Set the mission holding coordinate. This is the coordinate where the assigned group will fly before the actual mission execution starts. Do not forget to add a push condition, too!
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Coordinate Holding coordinate.
|
||||
-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate.
|
||||
-- @param #number Speed (Optional) Speed in knots to reach this waypoint and hold there. Defaults to mission speed.
|
||||
-- @param #number Duration (Optional) Duration in seconds on how long to hold, defaults to 15 minutes. Mission continues if either a push condition is met or the time is up.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetMissionHoldingCoord(Coordinate, Altitude, Speed, Duration)
|
||||
|
||||
-- Obviously a zone was passed. We get the coordinate.
|
||||
if Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
Coordinate=Coordinate:GetCoordinate()
|
||||
end
|
||||
|
||||
self.missionHoldingCoord=Coordinate
|
||||
self.missionHoldingDuration=Duration or 900
|
||||
|
||||
if Altitude then
|
||||
self.missionHoldingCoord.y=UTILS.FeetToMeters(Altitude)
|
||||
self.missionHoldingCoordAlt = UTILS.FeetToMeters(Altitude or 10000)
|
||||
end
|
||||
|
||||
self.missionHoldingCoordSpeed=Speed and Speed or nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the mission egress coordinate if this was defined.
|
||||
@@ -5710,6 +5838,20 @@ function AUFTRAG:GetMissionEgressCoord()
|
||||
return self.missionEgressCoord
|
||||
end
|
||||
|
||||
--- Get the mission ingress coordinate if this was defined.
|
||||
-- @param #AUFTRAG self
|
||||
-- @return Core.Point#COORDINATE Coordinate Coordinate or nil.
|
||||
function AUFTRAG:GetMissionIngressCoord()
|
||||
return self.missionIngressCoord
|
||||
end
|
||||
|
||||
--- Get the mission holding coordinate if this was defined.
|
||||
-- @param #AUFTRAG self
|
||||
-- @return Core.Point#COORDINATE Coordinate Coordinate or nil.
|
||||
function AUFTRAG:GetMissionHoldingCoord()
|
||||
return self.missionHoldingCoord
|
||||
end
|
||||
|
||||
--- Get coordinate which was set as mission waypoint coordinate.
|
||||
-- @param #AUFTRAG self
|
||||
-- @return Core.Point#COORDINATE Coordinate where the mission is executed or `#nil`.
|
||||
@@ -5744,10 +5886,27 @@ function AUFTRAG:GetMissionWaypointCoord(group, randomradius, surfacetypes)
|
||||
end
|
||||
return coord
|
||||
end
|
||||
|
||||
local coord=group:GetCoordinate()
|
||||
|
||||
-- Check if an ingress or holding coord has been explicitly set.
|
||||
if self.missionHoldingCoord then
|
||||
coord=self.missionHoldingCoord
|
||||
if self.missionHoldingCoorddAlt then
|
||||
coord:SetAltitude(self.missionHoldingCoordAlt, true)
|
||||
end
|
||||
end
|
||||
|
||||
if self.missionIngressCoord then
|
||||
coord=self.missionIngressCoord
|
||||
if self.missionIngressCoordAlt then
|
||||
coord:SetAltitude(self.missionIngressCoordAlt, true)
|
||||
end
|
||||
end
|
||||
|
||||
-- Create waypoint coordinate half way between us and the target.
|
||||
local waypointcoord=COORDINATE:New(0,0,0)
|
||||
local coord=group:GetCoordinate()
|
||||
|
||||
if coord then
|
||||
waypointcoord=coord:GetIntermediateCoordinate(self:GetTargetCoordinate(), self.missionFraction)
|
||||
else
|
||||
@@ -5952,6 +6111,16 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
local DCStask=CONTROLLABLE.TaskBombing(nil, self:GetTargetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType, Divebomb)
|
||||
|
||||
table.insert(DCStasks, DCStask)
|
||||
|
||||
elseif self.type==AUFTRAG.Type.STRAFING then
|
||||
|
||||
----------------------
|
||||
-- STRAFING Mission --
|
||||
----------------------
|
||||
|
||||
local DCStask=CONTROLLABLE.TaskStrafing(nil,self:GetTargetVec2(), self.engageQuantity, self.engageLength,self.engageWeaponType,self.engageWeaponExpend,self.engageDirection,self.engageAsGroup)
|
||||
|
||||
table.insert(DCStasks, DCStask)
|
||||
|
||||
elseif self.type==AUFTRAG.Type.BOMBRUNWAY then
|
||||
|
||||
@@ -5969,7 +6138,7 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
-- BOMBCARPET Mission --
|
||||
------------------------
|
||||
|
||||
local DCStask=CONTROLLABLE.TaskCarpetBombing(nil, self:GetTargetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType, self.engageCarpetLength)
|
||||
local DCStask=CONTROLLABLE.TaskCarpetBombing(nil, self:GetTargetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType, self.engageLength)
|
||||
|
||||
table.insert(DCStasks, DCStask)
|
||||
|
||||
@@ -6037,7 +6206,7 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
local param={}
|
||||
param.zone=self:GetObjective()
|
||||
param.altitude=self.missionAltitude
|
||||
param.speed=self.missionSpeed
|
||||
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
|
||||
|
||||
DCStask.params=param
|
||||
|
||||
@@ -6117,7 +6286,7 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
local param={}
|
||||
param.target=self.engageTarget
|
||||
param.altitude=self.missionAltitude
|
||||
param.speed=self.missionSpeed
|
||||
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
|
||||
param.lastindex=nil
|
||||
|
||||
DCStask.params=param
|
||||
@@ -6290,7 +6459,7 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
local param={}
|
||||
param.zone=self:GetObjective()
|
||||
param.altitude=self.missionAltitude
|
||||
param.speed=self.missionSpeed
|
||||
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
|
||||
|
||||
DCStask.params=param
|
||||
|
||||
@@ -6326,7 +6495,7 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
local param={}
|
||||
param.zone=self:GetObjective()
|
||||
param.altitude=self.missionAltitude
|
||||
param.speed=self.missionSpeed
|
||||
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
|
||||
|
||||
DCStask.params=param
|
||||
|
||||
@@ -6346,7 +6515,7 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
local param={}
|
||||
param.target=self:GetTargetData()
|
||||
param.action="Wedge"
|
||||
param.speed=self.missionSpeed
|
||||
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
|
||||
|
||||
DCStask.params=param
|
||||
|
||||
@@ -6492,8 +6661,7 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
|
||||
local DCStask={}
|
||||
local Vec2 = self.stayAt:GetVec2()
|
||||
local DCStask = CONTROLLABLE.TaskLandAtVec2(nil,Vec2,self.stayTime)
|
||||
|
||||
local DCStask = CONTROLLABLE.TaskLandAtVec2(nil,Vec2,self.stayTime, self.combatLand, self.directionAfter)
|
||||
table.insert(DCStasks, DCStask)
|
||||
|
||||
elseif self.type==AUFTRAG.Type.ONGUARD or self.type==AUFTRAG.Type.ARMOREDGUARD then
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
-- @date Last Update Jan 2024
|
||||
-- @date Last Update Jan 2025
|
||||
-- @module Ops.AWACS
|
||||
-- @image OPS_AWACS.jpg
|
||||
|
||||
@@ -122,6 +122,7 @@ do
|
||||
-- @field #number TacticalModulation
|
||||
-- @field #number TacticalInterval
|
||||
-- @field Core.Set#SET_GROUP DetectionSet
|
||||
-- @field #number MaxMissionRange
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
@@ -183,7 +184,7 @@ do
|
||||
--
|
||||
-- Add Escorts Squad (recommended, optional)
|
||||
--
|
||||
-- local Squad_Two = SQUADRON:New("Escorts",4,"Escorts North")
|
||||
-- local Squad_Two = SQUADRON:New("Escorts",4,"Escorts North") -- taking a template with 2 planes here, will result in a group of 2 escorts which can fly in formation escorting the AWACS.
|
||||
-- Squad_Two:AddMissionCapability({AUFTRAG.Type.ESCORT})
|
||||
-- Squad_Two:SetFuelLowRefuel(true)
|
||||
-- Squad_Two:SetFuelLowThreshold(0.3)
|
||||
@@ -231,8 +232,8 @@ do
|
||||
-- -- set up in the mission editor with a late activated helo named "Rock#ZONE_POLYGON". Note this also sets the BullsEye to be referenced as "Rock".
|
||||
-- -- The CAP station zone is called "Fremont". We will be on 255 AM.
|
||||
-- local testawacs = AWACS:New("AWACS North",AwacsAW,"blue",AIRBASE.Caucasus.Kutaisi,"Awacs Orbit",ZONE:FindByName("Rock"),"Fremont",255,radio.modulation.AM )
|
||||
-- -- set two escorts
|
||||
-- testawacs:SetEscort(2)
|
||||
-- -- set one escort group; this example has two units in the template group, so they can fly a nice formation.
|
||||
-- testawacs:SetEscort(1,ENUMS.Formation.FixedWing.FingerFour.Group,{x=-500,y=50,z=500},45)
|
||||
-- -- Callsign will be "Focus". We'll be a Angels 30, doing 300 knots, orbit leg to 88deg with a length of 25nm.
|
||||
-- testawacs:SetAwacsDetails(CALLSIGN.AWACS.Focus,1,30,300,88,25)
|
||||
-- -- Set up SRS on port 5010 - change the below to your path and port
|
||||
@@ -508,7 +509,7 @@ do
|
||||
-- @field #AWACS
|
||||
AWACS = {
|
||||
ClassName = "AWACS", -- #string
|
||||
version = "0.2.64", -- #string
|
||||
version = "0.2.71", -- #string
|
||||
lid = "", -- #string
|
||||
coalition = coalition.side.BLUE, -- #number
|
||||
coalitiontxt = "blue", -- #string
|
||||
@@ -605,6 +606,7 @@ AWACS = {
|
||||
TacticalModulation = radio.modulation.AM,
|
||||
TacticalInterval = 120,
|
||||
DetectionSet = nil,
|
||||
MaxMissionRange = 125,
|
||||
}
|
||||
|
||||
---
|
||||
@@ -935,7 +937,7 @@ AWACS.TaskStatus = {
|
||||
--@field #boolean FromAI
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO-List 0.2.53
|
||||
-- TODO-List 0.2.54
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
--
|
||||
-- DONE - WIP - Player tasking, VID
|
||||
@@ -1572,6 +1574,15 @@ function AWACS:SetLocale(Locale)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set the max mission range flights can be away from their home base.
|
||||
-- @param #AWACS self
|
||||
-- @param #number NM Distance in nautical miles
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetMaxMissionRange(NM)
|
||||
self.MaxMissionRange = NM or 125
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Add additional frequency and modulation for AWACS SRS output.
|
||||
-- @param #AWACS self
|
||||
-- @param #number Frequency The frequency to add, e.g. 132.5
|
||||
@@ -1762,7 +1773,7 @@ function AWACS:_EventHandler(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
if Event.id == EVENTS.PlayerLeaveUnit then --player left unit
|
||||
if Event.id == EVENTS.PlayerLeaveUnit and Event.IniGroupName then --player left unit
|
||||
-- check known player?
|
||||
self:T("Player group left unit: " .. Event.IniGroupName)
|
||||
self:T("Player name left: " .. Event.IniPlayerName)
|
||||
@@ -2158,9 +2169,12 @@ end
|
||||
|
||||
--- [User] Set AWACS Escorts Template
|
||||
-- @param #AWACS self
|
||||
-- @param #number EscortNumber Number of fighther planes to accompany this AWACS. 0 or nil means no escorts.
|
||||
-- @param #number EscortNumber Number of fighther plane GROUPs to accompany this AWACS. 0 or nil means no escorts. If you want >1 plane in an escort group, you can either set the respective squadron grouping to the desired number, or use a template for escorts with >1 unit.
|
||||
-- @param #number Formation Formation the escort should take (if more than one plane), e.g. `ENUMS.Formation.FixedWing.FingerFour.Group`. Formation is used on GROUP level, multiple groups of one unit will NOT conform to this formation.
|
||||
-- @param #table OffsetVector Offset the escorts should fly behind the AWACS, given as table, distance in meters, e.g. `{x=-500,y=0,z=500}` - 500m behind (negative value) and to the right (negative for left), no vertical separation (positive over, negative under the AWACS flight). For multiple groups, the vectors will be slightly changed to avoid collisions.
|
||||
-- @param #number EscortEngageMaxDistance Escorts engage air targets max this NM away, defaults to 45NM.
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetEscort(EscortNumber)
|
||||
function AWACS:SetEscort(EscortNumber,Formation,OffsetVector,EscortEngageMaxDistance)
|
||||
self:T(self.lid.."SetEscort")
|
||||
if EscortNumber and EscortNumber > 0 then
|
||||
self.HasEscorts = true
|
||||
@@ -2169,6 +2183,9 @@ function AWACS:SetEscort(EscortNumber)
|
||||
self.HasEscorts = false
|
||||
self.EscortNumber = 0
|
||||
end
|
||||
self.EscortFormation = Formation
|
||||
self.OffsetVec = OffsetVector or {x=500,y=100,z=500}
|
||||
self.EscortEngageMaxDistance = EscortEngageMaxDistance or 45
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -2223,11 +2240,26 @@ function AWACS:_StartEscorts(Shiftchange)
|
||||
local group = AwacsFG:GetGroup()
|
||||
|
||||
local timeonstation = (self.EscortsTimeOnStation + self.ShiftChangeTime) * 3600 -- hours to seconds
|
||||
local OffsetX = 500
|
||||
local OffsetY = 500
|
||||
local OffsetZ = 500
|
||||
if self.OffsetVec then
|
||||
OffsetX = self.OffsetVec.x or 500
|
||||
OffsetY = self.OffsetVec.y or 500
|
||||
OffsetZ = self.OffsetVec.z or 500
|
||||
end
|
||||
|
||||
for i=1,self.EscortNumber do
|
||||
-- every
|
||||
local escort = AUFTRAG:NewESCORT(group, {x= -100*((i + (i%2))/2), y=0, z=(100 + 100*((i + (i%2))/2))*(-1)^i},45,{"Air"})
|
||||
escort:SetRequiredAssets(1)
|
||||
-- every
|
||||
local escort = AUFTRAG:NewESCORT(group, {x= OffsetX*((i + (i%2))/2), y=OffsetY*((i + (i%2))/2), z=(OffsetZ + OffsetZ*((i + (i%2))/2))*(-1)^i},self.EscortEngageMaxDistance,{"Air"})
|
||||
--local escort = AUFTRAG:NewESCORT(group,self.OffsetVec,self.EscortEngageMaxDistance,{"Air"})
|
||||
--escort:SetRequiredAssets(self.EscortNumber)
|
||||
escort:SetTime(nil,timeonstation)
|
||||
if self.Escortformation then
|
||||
escort:SetFormation(self.Escortformation)
|
||||
end
|
||||
escort:SetMissionRange(self.MaxMissionRange)
|
||||
|
||||
self.AirWing:AddMission(escort)
|
||||
self.CatchAllMissions[#self.CatchAllMissions+1] = escort
|
||||
|
||||
@@ -2434,7 +2466,7 @@ function AWACS:_GetCallSign(Group,GID, IsPlayer)
|
||||
|
||||
local callsign = "Ghost 1"
|
||||
if Group and Group:IsAlive() then
|
||||
callsign = Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations)
|
||||
callsign = Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations,self.callsignCustomFunc,self.callsignCustomArgs)
|
||||
end
|
||||
return callsign
|
||||
end
|
||||
@@ -2443,10 +2475,12 @@ end
|
||||
-- @param #AWACS self
|
||||
-- @param #boolean ShortCallsign If true, only call out the major flight number
|
||||
-- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name as-is, no amendments or numbers.
|
||||
-- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized
|
||||
-- @param #table CallsignTranslations (Optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized.
|
||||
-- callsigns from playername or group name.
|
||||
-- @param #func CallsignCustomFunc (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first two arguments.
|
||||
-- @param #arg ... (Optional) Comma separated arguments to add to the custom function call after groupname and playername.
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
|
||||
function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...)
|
||||
if not ShortCallsign or ShortCallsign == false then
|
||||
self.callsignshort = false
|
||||
else
|
||||
@@ -2454,6 +2488,8 @@ function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
|
||||
end
|
||||
self.keepnumber = Keepnumber or false
|
||||
self.callsignTranslations = CallsignTranslations
|
||||
self.callsignCustomFunc = CallsignCustomFunc
|
||||
self.callsignCustomArgs = arg or {}
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -3626,7 +3662,7 @@ function AWACS:_CheckIn(Group)
|
||||
managedgroup.LastTasking = timer.getTime()
|
||||
|
||||
GID = managedgroup.GID
|
||||
self.ManagedGrps[self.ManagedGrpID]=managedgroup
|
||||
self.ManagedGrps[self.ManagedGrpID]=managedgroup
|
||||
|
||||
local alphacheckbulls = self:_ToStringBULLS(Group:GetCoordinate())
|
||||
local alphacheckbullstts = self:_ToStringBULLS(Group:GetCoordinate(),false,true)
|
||||
@@ -3893,6 +3929,12 @@ function AWACS:_SetClientMenus()
|
||||
checkin = checkin,
|
||||
}
|
||||
self.clientmenus:Push(menus,cgrpname)
|
||||
-- catch errors - when this entry is built we should NOT have a managed entry
|
||||
local GID,hasentry = self:_GetManagedGrpID(cgrp)
|
||||
if hasentry then
|
||||
-- this user is checked in but has the check in entry ... not good.
|
||||
self:_CheckOut(cgrp,GID,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
@@ -5585,6 +5627,12 @@ function AWACS:_ThreatRangeCall(GID,Contact)
|
||||
local grptxt = self.gettext:GetEntry("GROUP",self.locale)
|
||||
local thrt = self.gettext:GetEntry("THREAT",self.locale)
|
||||
local text = string.format("%s. %s. %s %s, %s. %s",self.callsigntxt,pilotcallsign,contacttag,grptxt, thrt, BRATExt)
|
||||
-- DONE MS TTS - fix spelling out B-R-A in this case
|
||||
if string.find(text,"BRAA",1,true) then
|
||||
text = string.gsub(text,"BRAA","brah")
|
||||
elseif string.find(text,"BRA",1,true) then
|
||||
text = string.gsub(text,"BRA","brah")
|
||||
end
|
||||
if IsSub == false then
|
||||
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
|
||||
end
|
||||
@@ -5733,7 +5781,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets)
|
||||
local intercept = AUFTRAG:NewINTERCEPT(Target.Target)
|
||||
intercept:SetWeaponExpend(AI.Task.WeaponExpend.ALL)
|
||||
intercept:SetWeaponType(ENUMS.WeaponFlag.Auto)
|
||||
|
||||
intercept:SetMissionRange(self.MaxMissionRange)
|
||||
-- TODO
|
||||
-- now this is going to be interesting...
|
||||
-- Check if the target left the "hot" area or is dead already
|
||||
@@ -5781,6 +5829,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets)
|
||||
AnchorSpeed = UTILS.KnotsToAltKIAS(AnchorSpeed,Angels)
|
||||
local Anchor = self.AnchorStacks:ReadByPointer(Pilot.AnchorStackNo) -- #AWACS.AnchorData
|
||||
local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels,AnchorSpeed,Anchor.StationZoneCoordinate,0,15,{})
|
||||
capauftrag:SetMissionRange(self.MaxMissionRange)
|
||||
capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60)))
|
||||
Pilot.FlightGroup:AddMission(capauftrag)
|
||||
|
||||
@@ -5898,6 +5947,8 @@ function AWACS:onafterStart(From, Event, To)
|
||||
-- set up the AWACS and let it orbit
|
||||
local AwacsAW = self.AirWing -- Ops.Airwing#AIRWING
|
||||
local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg)
|
||||
mission:SetMissionRange(self.MaxMissionRange)
|
||||
mission:SetRequiredAttribute({ GROUP.Attribute.AIR_AWACS }) -- prefered plane type, thanks to Heart8reaker
|
||||
local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600
|
||||
mission:SetTime(nil,timeonstation)
|
||||
self.CatchAllMissions[#self.CatchAllMissions+1] = mission
|
||||
@@ -6040,6 +6091,7 @@ function AWACS:_CheckAwacsStatus()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------
|
||||
-- AWACS
|
||||
--------------------------------
|
||||
@@ -6188,12 +6240,13 @@ function AWACS:_CheckAwacsStatus()
|
||||
|
||||
report:Add("====================")
|
||||
|
||||
local RESMission
|
||||
-- Check for replacement mission - if any
|
||||
if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then -- Ops.Auftrag#AUFTRAG
|
||||
ESmission = self.EscortMissionReplacement[i]
|
||||
local esstatus = ESmission:GetState()
|
||||
local ESmissiontime = (timer.getTime() - self.EscortsTimeStamp)
|
||||
local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds
|
||||
RESMission = self.EscortMissionReplacement[i]
|
||||
local esstatus = RESMission:GetState()
|
||||
local RESMissiontime = (timer.getTime() - self.EscortsTimeStamp)
|
||||
local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - RESMissiontime),0) -- seconds
|
||||
ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes
|
||||
local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0)
|
||||
|
||||
@@ -6201,7 +6254,7 @@ function AWACS:_CheckAwacsStatus()
|
||||
report:Add(string.format("Auftrag Status: %s",esstatus))
|
||||
report:Add(string.format("TOS Left: %d min",ESTOSLeft))
|
||||
|
||||
local OpsGroups = ESmission:GetOpsGroups()
|
||||
local OpsGroups = RESMission:GetOpsGroups()
|
||||
local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP
|
||||
if OpsGroup then
|
||||
local OpsName = OpsGroup:GetName() or "Unknown"
|
||||
@@ -6213,13 +6266,13 @@ function AWACS:_CheckAwacsStatus()
|
||||
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
|
||||
end
|
||||
|
||||
if ESmission:IsExecuting() then
|
||||
if RESMission and RESMission:IsExecuting() then
|
||||
-- make the actual change in the queue
|
||||
self.ShiftChangeEscortsFlag = false
|
||||
self.ShiftChangeEscortsRequested = false
|
||||
-- cancel old mission
|
||||
if ESmission and ESmission:IsNotOver() then
|
||||
ESmission:Cancel()
|
||||
ESmission:__Cancel(1)
|
||||
end
|
||||
self.EscortMission[i] = self.EscortMissionReplacement[i]
|
||||
self.EscortMissionReplacement[i] = nil
|
||||
@@ -6471,6 +6524,7 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo
|
||||
if auftragtype == AUFTRAG.Type.ALERT5 then
|
||||
-- all correct
|
||||
local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels*1000,AnchorSpeed,Anchor.StationZone:GetCoordinate(),0,15,{})
|
||||
capauftrag:SetMissionRange(self.MaxMissionRange)
|
||||
capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60)))
|
||||
capauftrag:AddAsset(managedgroup.FlightGroup)
|
||||
self.CatchAllMissions[#self.CatchAllMissions+1] = capauftrag
|
||||
@@ -6834,7 +6888,8 @@ function AWACS:onafterAwacsShiftChange(From,Event,To)
|
||||
self.CatchAllMissions[#self.CatchAllMissions+1] = mission
|
||||
local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600
|
||||
mission:SetTime(nil,timeonstation)
|
||||
|
||||
mission:SetMissionRange(self.MaxMissionRange)
|
||||
|
||||
AwacsAW:AddMission(mission)
|
||||
|
||||
self.AwacsMissionReplacement = mission
|
||||
|
||||
@@ -491,6 +491,9 @@ function BRIGADE:onafterStatus(From, Event, To)
|
||||
-- Info ---
|
||||
-----------
|
||||
|
||||
-- Display tactival overview.
|
||||
self:_TacticalOverview()
|
||||
|
||||
-- General info:
|
||||
if self.verbose>=1 then
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
-- @image OPS_CSAR.jpg
|
||||
|
||||
---
|
||||
-- Last Update April 2024
|
||||
-- Last Update Jan 2025
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
|
||||
@@ -41,6 +41,7 @@
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`.
|
||||
-- @field Core.Set#SET_GROUP allheligroupset Set of CSAR heli groups.
|
||||
-- @field Core.Set#SET_GROUP UserSetGroup Set of CSAR heli groups as designed by the mission designer (if any set).
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *Combat search and rescue (CSAR) are search and rescue operations that are carried out during war that are within or near combat zones.* (Wikipedia)
|
||||
@@ -91,7 +92,7 @@
|
||||
-- mycsar.immortalcrew = true -- Set to true to make wounded crew immortal.
|
||||
-- mycsar.invisiblecrew = false -- Set to true to make wounded crew insvisible.
|
||||
-- mycsar.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters.
|
||||
-- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes.
|
||||
-- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. Will also try to add ZONE and STATIC objects with this prefix once at startup.
|
||||
-- mycsar.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined.
|
||||
-- mycsar.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages.
|
||||
-- mycsar.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons.
|
||||
@@ -116,8 +117,17 @@
|
||||
-- mycsar.topmenuname = "CSAR" -- set the menu entry name
|
||||
-- mycsar.ADFRadioPwr = 1000 -- ADF Beacons sending with 1KW as default
|
||||
-- mycsar.PilotWeight = 80 -- Loaded pilots weigh 80kgs each
|
||||
-- mycsar.AllowIRStrobe = false -- Allow a menu item to request an IR strobe to find a downed pilot at night (requires NVGs to see it).
|
||||
-- mycsar.IRStrobeRuntime = 300 -- If an IR Strobe is activated, it runs for 300 seconds (5 mins).
|
||||
--
|
||||
-- ## 2.1 Create own SET_GROUP to manage CTLD Pilot groups
|
||||
--
|
||||
-- -- Parameter: Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups.
|
||||
-- -- Needs to be set before starting the CSAR instance.
|
||||
-- local myset = SET_GROUP:New():FilterPrefixes("Helikopter"):FilterCoalitions("red"):FilterStart()
|
||||
-- mycsar:SetOwnSetPilotGroups(myset)
|
||||
--
|
||||
-- ## 2.1 SRS Features and Other Features
|
||||
-- ## 2.2 SRS Features and Other Features
|
||||
--
|
||||
-- mycsar.useSRS = false -- Set true to use FF\'s SRS integration
|
||||
-- mycsar.SRSPath = "C:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!)
|
||||
@@ -136,6 +146,7 @@
|
||||
-- mycsar.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection. Requires mycsar.enableForAI to be set to true. --shagrat
|
||||
-- mycsar.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases.
|
||||
-- mycsar.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane
|
||||
-- mycsar.CreateRadioBeacons = true -- set to false to disallow creating ADF radio beacons.
|
||||
--
|
||||
-- ## 3. Results
|
||||
--
|
||||
@@ -256,6 +267,10 @@ CSAR = {
|
||||
topmenuname = "CSAR",
|
||||
ADFRadioPwr = 1000,
|
||||
PilotWeight = 80,
|
||||
CreateRadioBeacons = true,
|
||||
UserSetGroup = nil,
|
||||
AllowIRStrobe = false,
|
||||
IRStrobeRuntime = 300,
|
||||
}
|
||||
|
||||
--- Downed pilots info.
|
||||
@@ -272,6 +287,7 @@ CSAR = {
|
||||
-- @field #number timestamp Timestamp for approach process.
|
||||
-- @field #boolean alive Group is alive or dead/rescued.
|
||||
-- @field #boolean wetfeet Group is spawned over (deep) water.
|
||||
-- @field #string BeaconName Name of radio beacon - if any.
|
||||
|
||||
--- All slot / Limit settings
|
||||
-- @type CSAR.AircraftType
|
||||
@@ -293,10 +309,11 @@ CSAR.AircraftType["Bronco-OV-10A"] = 2
|
||||
CSAR.AircraftType["MH-60R"] = 10
|
||||
CSAR.AircraftType["OH-6A"] = 2
|
||||
CSAR.AircraftType["OH58D"] = 2
|
||||
CSAR.AircraftType["CH-47Fbl1"] = 31
|
||||
|
||||
--- CSAR class version.
|
||||
-- @field #string version
|
||||
CSAR.version="1.0.24"
|
||||
CSAR.version="1.0.30"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@@ -455,6 +472,9 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
|
||||
-- added 1.0.16
|
||||
self.PilotWeight = 80
|
||||
|
||||
-- Own SET_GROUP if any
|
||||
self.UserSetGroup = nil
|
||||
|
||||
-- WARNING - here\'ll be dragons
|
||||
-- for this to work you need to de-sanitize your mission environment in <DCS root>\Scripts\MissionScripting.lua
|
||||
@@ -633,7 +653,7 @@ end
|
||||
-- @param #string Playername Name of Player (if applicable)
|
||||
-- @param #boolean Wetfeet Ejected over water
|
||||
-- @return #CSAR self.
|
||||
function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet)
|
||||
function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet,BeaconName)
|
||||
self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername})
|
||||
|
||||
-- create new entry
|
||||
@@ -641,7 +661,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript
|
||||
DownedPilot.desc = Description or ""
|
||||
DownedPilot.frequency = Frequency or 0
|
||||
DownedPilot.index = self.downedpilotcounter
|
||||
DownedPilot.name = Groupname or ""
|
||||
DownedPilot.name = Groupname or Playername or ""
|
||||
DownedPilot.originalUnit = OriginalUnit or ""
|
||||
DownedPilot.player = Playername or ""
|
||||
DownedPilot.side = Side or 0
|
||||
@@ -650,6 +670,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript
|
||||
DownedPilot.timestamp = 0
|
||||
DownedPilot.alive = true
|
||||
DownedPilot.wetfeet = Wetfeet or false
|
||||
DownedPilot.BeaconName = BeaconName
|
||||
|
||||
-- Add Pilot
|
||||
local PilotTable = self.downedPilots
|
||||
@@ -736,7 +757,6 @@ function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet)
|
||||
:NewWithAlias(template,alias)
|
||||
:InitCoalition(coalition)
|
||||
:InitCountry(country)
|
||||
--:InitAIOnOff(pilotcacontrol)
|
||||
:InitDelayOff()
|
||||
:SpawnFromCoordinate(point)
|
||||
|
||||
@@ -789,6 +809,8 @@ end
|
||||
-- @param #boolean noMessage
|
||||
-- @param #string _description Description
|
||||
-- @param #boolean forcedesc Use the description only for the pilot track entry
|
||||
-- @return Wrapper.Group#GROUP PilotInField Pilot GROUP object
|
||||
-- @return #string AliasName Alias display name
|
||||
function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description, forcedesc )
|
||||
self:T(self.lid .. " _AddCsar")
|
||||
self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description})
|
||||
@@ -818,8 +840,18 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
|
||||
end
|
||||
end
|
||||
|
||||
local BeaconName
|
||||
|
||||
if _playerName then
|
||||
BeaconName = _playerName..math.random(1,10000)
|
||||
elseif _unitName then
|
||||
BeaconName = _unitName..math.random(1,10000)
|
||||
else
|
||||
BeaconName = "Ghost-1-1"..math.random(1,10000)
|
||||
end
|
||||
|
||||
if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0
|
||||
self:_AddBeaconToGroup(_spawnedGroup, _freq)
|
||||
self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName)
|
||||
end
|
||||
|
||||
self:_AddSpecialOptions(_spawnedGroup)
|
||||
@@ -844,11 +876,11 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
|
||||
|
||||
local _GroupName = _spawnedGroup:GetName() or _alias
|
||||
|
||||
self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet)
|
||||
self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet,BeaconName)
|
||||
|
||||
self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage, _playerName) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc.
|
||||
|
||||
return self
|
||||
return _spawnedGroup, _alias
|
||||
end
|
||||
|
||||
--- (Internal) Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene.
|
||||
@@ -962,7 +994,6 @@ end
|
||||
-- @param Core.Point#COORDINATE Point
|
||||
-- @param #number Coalition Coalition.
|
||||
-- @param #string Description (optional) Description.
|
||||
-- @param #boolean addBeacon (optional) yes or no.
|
||||
-- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR.
|
||||
-- @param #string Unitname (optional) Name of the lost unit.
|
||||
-- @param #string Typename (optional) Type of plane.
|
||||
@@ -1792,9 +1823,6 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid
|
||||
end
|
||||
_text = string.gsub(_text,"km"," kilometer")
|
||||
_text = string.gsub(_text,"nm"," nautical miles")
|
||||
--self.msrs:SetVoice(self.SRSVoice)
|
||||
--self.SRSQueue:NewTransmission(_text,nil,self.msrs,nil,1)
|
||||
--self:I("Voice = "..self.SRSVoice)
|
||||
self.SRSQueue:NewTransmission(_text,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,self.SRSVoice,volume,label,coord)
|
||||
end
|
||||
return self
|
||||
@@ -1803,8 +1831,9 @@ end
|
||||
--- (Internal) Function to get string of a group\'s position.
|
||||
-- @param #CSAR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object.
|
||||
-- @param Wrapper.Unit#UNIT _Unit Requesting helo pilot unit
|
||||
-- @return #string Coordinates as Text
|
||||
function CSAR:_GetPositionOfWounded(_woundedGroup)
|
||||
function CSAR:_GetPositionOfWounded(_woundedGroup,_Unit)
|
||||
self:T(self.lid .. " _GetPositionOfWounded")
|
||||
local _coordinate = _woundedGroup:GetCoordinate()
|
||||
local _coordinatesText = "None"
|
||||
@@ -1819,6 +1848,26 @@ function CSAR:_GetPositionOfWounded(_woundedGroup)
|
||||
_coordinatesText = _coordinate:ToStringBULLS(self.coalition)
|
||||
end
|
||||
end
|
||||
if _Unit and _Unit:GetPlayerName() then
|
||||
local playername = _Unit:GetPlayerName()
|
||||
if playername then
|
||||
local settings = _DATABASE:GetPlayerSettings(playername) or _SETTINGS
|
||||
if settings then
|
||||
self:T("Get Settings ok!")
|
||||
if settings:IsA2G_MGRS() then
|
||||
_coordinatesText = _coordinate:ToStringMGRS(settings)
|
||||
elseif settings:IsA2G_LL_DMS() then
|
||||
_coordinatesText = _coordinate:ToStringLLDMS(settings)
|
||||
elseif settings:IsA2G_LL_DDM() then
|
||||
_coordinatesText = _coordinate:ToStringLLDDM(settings)
|
||||
elseif settings:IsA2G_BR() then
|
||||
-- attention this is the distance from the ASKING unit to target, not from RECCE to target!
|
||||
local startcoordinate = _Unit:GetCoordinate()
|
||||
_coordinatesText = _coordinate:ToStringBR(startcoordinate,settings)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return _coordinatesText
|
||||
end
|
||||
|
||||
@@ -1844,22 +1893,26 @@ function CSAR:_DisplayActiveSAR(_unitName)
|
||||
self:T({Table=_value})
|
||||
local _woundedGroup = _value.group
|
||||
if _woundedGroup and _value.alive then
|
||||
local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup)
|
||||
local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup,_heli)
|
||||
local _helicoord = _heli:GetCoordinate()
|
||||
local _woundcoord = _woundedGroup:GetCoordinate()
|
||||
local _distance = self:_GetDistance(_helicoord, _woundcoord)
|
||||
self:T({_distance = _distance})
|
||||
local distancetext = ""
|
||||
if _SETTINGS:IsImperial() then
|
||||
local settings = _SETTINGS
|
||||
if _heli:GetPlayerName() then
|
||||
settings = _DATABASE:GetPlayerSettings(_heli:GetPlayerName()) or _SETTINGS
|
||||
end
|
||||
if settings:IsImperial() then
|
||||
distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance))
|
||||
else
|
||||
distancetext = string.format("%.1fkm", _distance/1000.0)
|
||||
end
|
||||
if _value.frequency == 0 then--shagrat insert CASEVAC without Frequency
|
||||
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) })
|
||||
else
|
||||
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) })
|
||||
end
|
||||
if _value.frequency == 0 or self.CreateRadioBeacons == false then--shagrat insert CASEVAC without Frequency
|
||||
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) })
|
||||
else
|
||||
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1940,7 +1993,7 @@ function CSAR:_SignalFlare(_unitName)
|
||||
else
|
||||
_distance = string.format("%.1fkm",_closest.distance/1000)
|
||||
end
|
||||
local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance)
|
||||
local _msg = string.format("%s - Firing signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance)
|
||||
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true)
|
||||
|
||||
local _coord = _closest.pilot:GetCoordinate()
|
||||
@@ -1974,7 +2027,7 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen)
|
||||
if self.msrs:GetProvider() == MSRS.Provider.WINDOWS then
|
||||
voice = self.CSARVoiceMS or MSRS.Voices.Microsoft.Hedda
|
||||
end
|
||||
self:F("Voice = "..voice)
|
||||
--self:F("Voice = "..voice)
|
||||
self.SRSQueue:NewTransmission(_message,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,voice,volume,label,self.coordinate)
|
||||
end
|
||||
if ToScreen == true or ToScreen == nil then
|
||||
@@ -1988,6 +2041,41 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen)
|
||||
return self
|
||||
end
|
||||
|
||||
---(Internal) Request IR Strobe at closest downed pilot.
|
||||
--@param #CSAR self
|
||||
--@param #string _unitName Name of the helicopter
|
||||
function CSAR:_ReqIRStrobe( _unitName )
|
||||
self:T(self.lid .. " _ReqIRStrobe")
|
||||
local _heli = self:_GetSARHeli(_unitName)
|
||||
if _heli == nil then
|
||||
return
|
||||
end
|
||||
local smokedist = 8000
|
||||
if smokedist < self.approachdist_far then smokedist = self.approachdist_far end
|
||||
local _closest = self:_GetClosestDownedPilot(_heli)
|
||||
if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then
|
||||
local _clockDir = self:_GetClockDirection(_heli, _closest.pilot)
|
||||
local _distance = string.format("%.1fkm",_closest.distance/1000)
|
||||
if _SETTINGS:IsImperial() then
|
||||
_distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance))
|
||||
else
|
||||
_distance = string.format("%.1fkm",_closest.distance/1000)
|
||||
end
|
||||
local _msg = string.format("%s - IR Strobe active at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance)
|
||||
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true)
|
||||
_closest.pilot:NewIRMarker(true,self.IRStrobeRuntime or 300)
|
||||
else
|
||||
local _distance = string.format("%.1fkm",smokedist/1000)
|
||||
if _SETTINGS:IsImperial() then
|
||||
_distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist))
|
||||
else
|
||||
_distance = string.format("%.1fkm",smokedist/1000)
|
||||
end
|
||||
self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---(Internal) Request smoke at closest downed pilot.
|
||||
--@param #CSAR self
|
||||
--@param #string _unitName Name of the helicopter
|
||||
@@ -2111,12 +2199,12 @@ function CSAR:_AddMedevacMenuItem()
|
||||
local coalition = self.coalition
|
||||
local allheligroupset = self.allheligroupset -- Core.Set#SET_GROUP
|
||||
local _allHeliGroups = allheligroupset:GetSetObjects()
|
||||
|
||||
-- rebuild units table
|
||||
local _UnitList = {}
|
||||
for _key, _group in pairs (_allHeliGroups) do
|
||||
local _unit = _group:GetUnit(1) -- Asume that there is only one unit in the flight for players
|
||||
if _unit then
|
||||
local _unit = _group:GetFirstUnitAlive() -- Asume that there is only one unit in the flight for players
|
||||
if _unit then
|
||||
--self:T("Unitname ".._unit:GetName().." IsAlive "..tostring(_unit:IsAlive()).." IsPlayer "..tostring(_unit:IsPlayer()))
|
||||
if _unit:IsAlive() and _unit:IsPlayer() then
|
||||
local unitName = _unit:GetName()
|
||||
_UnitList[unitName] = unitName
|
||||
@@ -2139,7 +2227,12 @@ function CSAR:_AddMedevacMenuItem()
|
||||
local _rootMenu1 = MENU_GROUP_COMMAND:New(_group,"List Active CSAR",_rootPath, self._DisplayActiveSAR,self,_unitName)
|
||||
local _rootMenu2 = MENU_GROUP_COMMAND:New(_group,"Check Onboard",_rootPath, self._CheckOnboard,self,_unitName)
|
||||
local _rootMenu3 = MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, self._SignalFlare,self,_unitName)
|
||||
local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName):Refresh()
|
||||
local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName)
|
||||
if self.AllowIRStrobe then
|
||||
local _rootMenu5 = MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath, self._ReqIRStrobe,self,_unitName):Refresh()
|
||||
else
|
||||
_rootMenu4:Refresh()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2230,9 +2323,13 @@ end
|
||||
-- @param #CSAR self
|
||||
-- @param Wrapper.Group#GROUP _group Group #GROUP object.
|
||||
-- @param #number _freq Frequency to use
|
||||
function CSAR:_AddBeaconToGroup(_group, _freq)
|
||||
-- @param #string _name Beacon Name to use
|
||||
-- @return #CSAR self
|
||||
function CSAR:_AddBeaconToGroup(_group, _freq, _name)
|
||||
self:T(self.lid .. " _AddBeaconToGroup")
|
||||
if self.CreateRadioBeacons == false then return end
|
||||
local _group = _group
|
||||
|
||||
if _group == nil then
|
||||
--return frequency to pool of available
|
||||
for _i, _current in ipairs(self.UsedVHFFrequencies) do
|
||||
@@ -2247,22 +2344,24 @@ function CSAR:_AddBeaconToGroup(_group, _freq)
|
||||
if _group:IsAlive() then
|
||||
local _radioUnit = _group:GetUnit(1)
|
||||
if _radioUnit then
|
||||
local name = _radioUnit:GetName()
|
||||
local name = _radioUnit:GetName()
|
||||
local Frequency = _freq -- Freq in Hertz
|
||||
local name = _radioUnit:GetName()
|
||||
local Sound = "l10n/DEFAULT/"..self.radioSound
|
||||
local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0}
|
||||
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,name..math.random(1,10000)) -- Beacon in MP only runs for exactly 30secs straight
|
||||
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,_name) -- Beacon in MP only runs for exactly 30secs straight
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Helper function to (re-)add beacon to downed pilot.
|
||||
-- @param #CSAR self
|
||||
-- @param #table _args Arguments
|
||||
-- @return #CSAR self
|
||||
function CSAR:_RefreshRadioBeacons()
|
||||
self:T(self.lid .. " _RefreshRadioBeacons")
|
||||
if self.CreateRadioBeacons == false then return end
|
||||
if self:_CountActiveDownedPilots() > 0 then
|
||||
local PilotTable = self.downedPilots
|
||||
for _,_pilot in pairs (PilotTable) do
|
||||
@@ -2270,8 +2369,10 @@ function CSAR:_RefreshRadioBeacons()
|
||||
local pilot = _pilot -- #CSAR.DownedPilot
|
||||
local group = pilot.group
|
||||
local frequency = pilot.frequency or 0 -- thanks to @Thrud
|
||||
local bname = pilot.BeaconName or pilot.name..math.random(1,100000)
|
||||
trigger.action.stopRadioTransmission(bname)
|
||||
if group and group:IsAlive() and frequency > 0 then
|
||||
self:_AddBeaconToGroup(group,frequency)
|
||||
self:_AddBeaconToGroup(group,frequency,bname)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2308,6 +2409,16 @@ function CSAR:_ReachedPilotLimit()
|
||||
end
|
||||
end
|
||||
|
||||
--- User - Function to add onw SET_GROUP Set-up for pilot filtering and assignment.
|
||||
-- Needs to be set before starting the CSAR instance.
|
||||
-- @param #CSAR self
|
||||
-- @param Core.Set#SET_GROUP Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups.
|
||||
-- @return #CSAR self
|
||||
function CSAR:SetOwnSetPilotGroups(Set)
|
||||
self.UserSetGroup = Set
|
||||
return self
|
||||
end
|
||||
|
||||
------------------------------
|
||||
--- FSM internal Functions ---
|
||||
------------------------------
|
||||
@@ -2329,7 +2440,9 @@ function CSAR:onafterStart(From, Event, To)
|
||||
self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.PilotDead, self._EventHandler)
|
||||
|
||||
if self.allowbronco then
|
||||
if self.UserSetGroup then
|
||||
self.allheligroupset = self.UserSetGroup
|
||||
elseif self.allowbronco then
|
||||
local prefixes = self.csarPrefix or {}
|
||||
self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterStart()
|
||||
elseif self.useprefix then
|
||||
@@ -2338,7 +2451,24 @@ function CSAR:onafterStart(From, Event, To)
|
||||
else
|
||||
self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart()
|
||||
end
|
||||
self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -- currently only GROUP objects, maybe support STATICs also?
|
||||
|
||||
self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart()
|
||||
|
||||
local staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterOnce()
|
||||
local zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterOnce()
|
||||
|
||||
if staticmashes:Count() > 0 then
|
||||
for _,_mash in pairs(staticmashes.Set) do
|
||||
self.mash:AddObject(_mash)
|
||||
end
|
||||
end
|
||||
|
||||
if zonemashes:Count() > 0 then
|
||||
for _,_mash in pairs(zonemashes.Set) do
|
||||
self.mash:AddObject(_mash)
|
||||
end
|
||||
end
|
||||
|
||||
if not self.coordinate then
|
||||
local csarhq = self.mash:GetRandom()
|
||||
if csarhq then
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@
|
||||
-- @field Ops.Commander#COMMANDER commander Commander of assigned legions.
|
||||
-- @field #number Nsuccess Number of successful missions.
|
||||
-- @field #number Nfailure Number of failed mission.
|
||||
-- @field #table assetNumbers Asset numbers. Each entry is a table of data type `#CHIEF.AssetNumber`.
|
||||
-- @extends Ops.Intel#INTEL
|
||||
|
||||
--- *In preparing for battle I have always found that plans are useless, but planning is indispensable* -- Dwight D Eisenhower
|
||||
@@ -163,7 +164,7 @@
|
||||
--
|
||||
-- Will at a strategic zone with importance 2.
|
||||
--
|
||||
-- If the zone is currently owned by another coalition and enemy ground troops are present in the zone, a CAS and an ARTY mission are lauchned:
|
||||
-- If the zone is currently owned by another coalition and enemy ground troops are present in the zone, a CAS and an ARTY mission are launched:
|
||||
--
|
||||
-- * A mission of type `AUFTRAG.Type.CASENHANCED` is started if assets are available that can carry out this mission type.
|
||||
-- * A mission of type `AUFTRAG.Type.ARTY` is started provided assets are available.
|
||||
@@ -331,7 +332,7 @@ CHIEF.Strategy = {
|
||||
|
||||
--- CHIEF class version.
|
||||
-- @field #string version
|
||||
CHIEF.version="0.6.0"
|
||||
CHIEF.version="0.6.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -1284,8 +1285,10 @@ end
|
||||
--
|
||||
-- Empty:
|
||||
--
|
||||
-- * `AUFTRAG.Type.ONGURAD` with Nmin=1 and Nmax=1 assets, Attribute=`GROUP.Attribute.GROUND_TANK`.
|
||||
-- * `AUFTRAG.Type.ONGURAD` with Nmin=0 and Nmax=1 assets, Attribute=`GROUP.Attribute.GROUND_TANK`.
|
||||
-- * `AUFTRAG.Type.ONGURAD` with Nmin=0 and Nmax=1 assets, Attribute=`GROUP.Attribute.GROUND_IFV`.
|
||||
-- * `AUFTRAG.Type.ONGUARD` with Nmin=1 and Nmax=3 assets, Attribute=`GROUP.Attribute.GROUND_INFANTRY`.
|
||||
-- * `AUFTRAG.Type.OPSTRANSPORT` with Nmin=0 and Nmax=1 assets, Attribute=`GROUP.Attribute.AIR_TRANSPORTHELO` or `GROUP.Attribute.GROUND_APC`. This asset is used to transport the infantry groups.
|
||||
--
|
||||
-- Resources can be created with the @{#CHIEF.CreateResource} and @{#CHIEF.AddToResource} functions.
|
||||
--
|
||||
@@ -3033,10 +3036,13 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource)
|
||||
end
|
||||
|
||||
-- Recruite infantry assets.
|
||||
self:T(self.lid..string.format("Recruiting assets for zone %s", StratZone.opszone:GetName()))
|
||||
self:T(self.lid.."Missiontype="..MissionType)
|
||||
self:T({categories=Categories})
|
||||
self:T({attributes=Attributes})
|
||||
self:T({properties=Properties})
|
||||
|
||||
|
||||
local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2, nil, RangeMax, nil, nil, nil, nil, Categories, Attributes, Properties)
|
||||
|
||||
if recruited then
|
||||
@@ -3052,9 +3058,12 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource)
|
||||
local TargetCoord = TargetZone:GetCoordinate()
|
||||
|
||||
-- First check if we need a transportation.
|
||||
local transport=nil
|
||||
local transport=nil --Ops.OpsTransport#OPSTRANSPORT
|
||||
local Ntransports=0
|
||||
if Resource.carrierNmin and Resource.carrierNmax and Resource.carrierNmax>0 then
|
||||
|
||||
self:T(self.lid..string.format("Recruiting carrier assets: Nmin=%s, Nmax=%s", tostring(Resource.carrierNmin), tostring(Resource.carrierNmax)))
|
||||
|
||||
-- Filter only those assets that shall be transported.
|
||||
local cargoassets=CHIEF._FilterAssets(assets, Resource.Categories, Resource.Attributes, Resource.Properties)
|
||||
|
||||
@@ -3064,6 +3073,10 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource)
|
||||
recruited, transport=LEGION.AssignAssetsForTransport(self.commander, self.commander.legions, cargoassets,
|
||||
Resource.carrierNmin, Resource.carrierNmax, TargetZone, nil, Resource.carrierCategories, Resource.carrierAttributes, Resource.carrierProperties)
|
||||
|
||||
Ntransports=transport~=nil and #transport.assets or 0
|
||||
|
||||
self:T(self.lid..string.format("Recruited %d transport carrier assets success=%s", Ntransports, tostring(recruited)))
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -3076,7 +3089,7 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource)
|
||||
return false
|
||||
end
|
||||
|
||||
-- Debug messgage.
|
||||
-- Debug message
|
||||
self:T2(self.lid..string.format("Recruited %d assets for mission %s", #assets, MissionType))
|
||||
|
||||
|
||||
@@ -3224,10 +3237,13 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource)
|
||||
-- Attach mission to ops zone.
|
||||
StratZone.opszone:_AddMission(self.coalition, MissionType, mission)
|
||||
|
||||
mission:SetName(string.format("Stratzone %s-%d", StratZone.opszone:GetName(), mission.auftragsnummer))
|
||||
|
||||
-- Attach mission to resource.
|
||||
Resource.mission=mission
|
||||
|
||||
if transport then
|
||||
-- Check if transport assets could be allocated. If carrier Nmin=0 and 0 assets could be allocated, transport would still be created but not usefull obviously
|
||||
if transport and Ntransports>0 then
|
||||
-- Attach OPS transport to mission.
|
||||
mission.opstransport=transport
|
||||
-- Set ops zone to transport.
|
||||
|
||||
@@ -88,7 +88,10 @@ COHORT = {
|
||||
|
||||
--- COHORT class version.
|
||||
-- @field #string version
|
||||
COHORT.version="0.3.5"
|
||||
COHORT.version="0.3.6"
|
||||
|
||||
--- Global variable to store the unique(!) cohort names
|
||||
_COHORTNAMES={}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -110,6 +113,17 @@ COHORT.version="0.3.5"
|
||||
-- @return #COHORT self
|
||||
function COHORT:New(TemplateGroupName, Ngroups, CohortName)
|
||||
|
||||
-- Name of the cohort.
|
||||
local name=tostring(CohortName or TemplateGroupName)
|
||||
|
||||
-- Cohort name has to be unique or we will get serious problems!
|
||||
if UTILS.IsAnyInTable(_COHORTNAMES, name) then
|
||||
env.error(string.format('ERROR: cannot create cohort "%s" because another cohort with that name already exists. Names must be unique!', name))
|
||||
return nil
|
||||
else
|
||||
table.insert(_COHORTNAMES, name)
|
||||
end
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #COHORT
|
||||
|
||||
@@ -117,7 +131,7 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName)
|
||||
self.templatename=TemplateGroupName
|
||||
|
||||
-- Cohort name.
|
||||
self.name=tostring(CohortName or TemplateGroupName)
|
||||
self.name=name
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("COHORT %s | ", self.name)
|
||||
@@ -577,7 +591,7 @@ function COHORT:AddAsset(Asset)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove asset from chort.
|
||||
--- Remove specific asset from chort.
|
||||
-- @param #COHORT self
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset.
|
||||
-- @return #COHORT self
|
||||
@@ -609,6 +623,40 @@ function COHORT:DelGroup(GroupName)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove assets from pool. Not that assets must not be spawned or already reserved or requested.
|
||||
-- @param #COHORT self
|
||||
-- @param #number N Number of assets to be removed. Default 1.
|
||||
-- @return #COHORT self
|
||||
function COHORT:RemoveAssets(N)
|
||||
self:T2(self.lid..string.format("Remove %d assets of Cohort", N))
|
||||
|
||||
N=N or 1
|
||||
|
||||
local n=0
|
||||
for i=#self.assets,1,-1 do
|
||||
local asset=self.assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
|
||||
self:T2(self.lid..string.format("Checking removing asset %s", asset.spawngroupname))
|
||||
if not (asset.requested or asset.spawned or asset.isReserved) then
|
||||
self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname))
|
||||
table.remove(self.assets, i)
|
||||
n=n+1
|
||||
else
|
||||
self:T2(self.lid..string.format("Could NOT Remove asset %s", asset.spawngroupname))
|
||||
end
|
||||
|
||||
if n>=N then
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d", n, N, #self.assets))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get name of the cohort.
|
||||
-- @param #COHORT self
|
||||
-- @return #string Name of the cohort.
|
||||
@@ -975,6 +1023,7 @@ function COHORT:CanMission(Mission)
|
||||
|
||||
if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then
|
||||
-- Correct refueling system.
|
||||
self:T(self.lid..string.format("INFO: Correct refueling system requested=%s != %s=available", tostring(Mission.refuelSystem), tostring(self.tankerSystem)))
|
||||
else
|
||||
self:T(self.lid..string.format("INFO: Wrong refueling system requested=%s != %s=available", tostring(Mission.refuelSystem), tostring(self.tankerSystem)))
|
||||
return false
|
||||
|
||||
@@ -1774,8 +1774,10 @@ function COMMANDER:RecruitAssetsForMission(Mission)
|
||||
MaxWeight=cohort.cargobayLimit
|
||||
end
|
||||
end
|
||||
|
||||
self:T(self.lid..string.format("Largest cargo bay available=%.1f", MaxWeight))
|
||||
|
||||
if MaxWeight then
|
||||
self:T(self.lid..string.format("Largest cargo bay available=%.1f", MaxWeight))
|
||||
end
|
||||
end
|
||||
|
||||
local legions=self.legions
|
||||
@@ -2165,4 +2167,4 @@ end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
--
|
||||
-------------------------------------------------------------------------
|
||||
-- Date: September 2023
|
||||
-- Last Update: July 2024
|
||||
-------------------------------------------------------------------------
|
||||
--
|
||||
--- **Ops** - Easy GCI & CAP Manager
|
||||
@@ -49,7 +50,7 @@
|
||||
-- @field #number capleg
|
||||
-- @field #number maxinterceptsize
|
||||
-- @field #number missionrange
|
||||
-- @field #number noaltert5
|
||||
-- @field #number noalert5
|
||||
-- @field #table ManagedAW
|
||||
-- @field #table ManagedSQ
|
||||
-- @field #table ManagedCP
|
||||
@@ -67,6 +68,7 @@
|
||||
-- @field #table ReadyFlightGroups
|
||||
-- @field #boolean DespawnAfterLanding
|
||||
-- @field #boolean DespawnAfterHolding
|
||||
-- @field #list<Ops.Auftrag#AUFTRAG> ListOfAuftrag
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown.
|
||||
@@ -95,7 +97,8 @@
|
||||
--
|
||||
-- ### Prerequisites
|
||||
--
|
||||
-- You have to put a STATIC object on the airbase with the UNIT name according to the name of the airbase. E.g. for Kuitaisi this has to have the name Kutaisi. This object symbolizes the AirWing HQ.
|
||||
-- You have to put a **STATIC WAREHOUSE** object on the airbase with the UNIT name according to the name of the airbase. **Do not put any other static type or it creates a conflict with the airbase name!**
|
||||
-- E.g. for Kuitaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ.
|
||||
-- Next put a late activated template group for your CAP/GCI Squadron on the map. Last, put a zone on the map for the CAP operations, let's name it "Blue Zone 1". Size of the zone plays no role.
|
||||
-- Put an EW radar system on the map and name it aptly, like "Blue EWR".
|
||||
--
|
||||
@@ -164,7 +167,7 @@
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPLeg}: Set the length of the CAP leg, default is 15 NM.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 2.
|
||||
-- * @{#EASYGCICAP.SetDefaultMissionRange}: Set how many NM the planes can go from the home base, defaults to 100.
|
||||
-- * @{#EASYGCICAP.SetDefaultNumberAlter5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2.
|
||||
-- * @{#EASYGCICAP.SetDefaultNumberAlert5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2.
|
||||
-- * @{#EASYGCICAP.SetDefaultEngageRange}: Set max engage range for CAP flights if they detect intruders, defaults to 50.
|
||||
-- * @{#EASYGCICAP.SetMaxAliveMissions}: Set max parallel missions can be done (CAP+GCI+Alert5+Tanker+AWACS), defaults to 8.
|
||||
-- * @{#EASYGCICAP.SetDefaultRepeatOnFailure}: Set max repeats on failure for intercepting/killing intruders, defaults to 3.
|
||||
@@ -194,7 +197,7 @@ EASYGCICAP = {
|
||||
capleg = 15,
|
||||
maxinterceptsize = 2,
|
||||
missionrange = 100,
|
||||
noaltert5 = 4,
|
||||
noalert5 = 4,
|
||||
ManagedAW = {},
|
||||
ManagedSQ = {},
|
||||
ManagedCP = {},
|
||||
@@ -213,6 +216,7 @@ EASYGCICAP = {
|
||||
ReadyFlightGroups = {},
|
||||
DespawnAfterLanding = false,
|
||||
DespawnAfterHolding = true,
|
||||
ListOfAuftrag = {}
|
||||
}
|
||||
|
||||
--- Internal Squadron data type
|
||||
@@ -248,7 +252,7 @@ EASYGCICAP = {
|
||||
|
||||
--- EASYGCICAP class version.
|
||||
-- @field #string version
|
||||
EASYGCICAP.version="0.1.11"
|
||||
EASYGCICAP.version="0.1.17"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -265,7 +269,7 @@ EASYGCICAP.version="0.1.11"
|
||||
-- @param #string Alias A Name for this GCICAP
|
||||
-- @param #string AirbaseName Name of the Home Airbase
|
||||
-- @param #string Coalition Coalition, e.g. "blue" or "red"
|
||||
-- @param #string EWRName (Partial) group name of the EWR system of the coalition, e.g. "Red EWR"
|
||||
-- @param #string EWRName (Partial) group name of the EWR system of the coalition, e.g. "Red EWR", can be handed in as table of names, e.g.{"EWR","Radar","SAM"}
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
-- Inherit everything from FSM class.
|
||||
@@ -274,9 +278,10 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
-- defaults
|
||||
self.alias = Alias or AirbaseName.." CAP Wing"
|
||||
self.coalitionname = string.lower(Coalition) or "blue"
|
||||
self.coalition = self.coaltitionname == "blue" and coalition.side.BLUE or coalition.side.RED
|
||||
self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED
|
||||
self.wings = {}
|
||||
self.EWRName = EWRName or self.coalitionname.." EWR"
|
||||
if type(EWRName) == "string" then EWRName = {EWRName} end
|
||||
self.EWRName = EWRName --or self.coalitionname.." EWR"
|
||||
--self.CapZoneName = CapZoneName
|
||||
self.airbasename = AirbaseName
|
||||
self.airbase = AIRBASE:FindByName(self.airbasename)
|
||||
@@ -289,7 +294,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
self.capleg = 15
|
||||
self.capgrouping = 2
|
||||
self.missionrange = 100
|
||||
self.noaltert5 = 2
|
||||
self.noalert5 = 2
|
||||
self.MaxAliveMissions = 8
|
||||
self.engagerange = 50
|
||||
self.repeatsonfailure = 3
|
||||
@@ -298,6 +303,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
self.CapFormation = ENUMS.Formation.FixedWing.FingerFour.Group
|
||||
self.DespawnAfterLanding = false
|
||||
self.DespawnAfterHolding = true
|
||||
self.ListOfAuftrag = {}
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("EASYGCICAP %s | ", self.alias)
|
||||
@@ -342,9 +348,23 @@ function EASYGCICAP:SetTankerAndAWACSInvisible(Switch)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set Maximum of alive missions to stop airplanes spamming the map
|
||||
--- Count alive missions in our internal stack.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Cap-Missions + Intercept-Missions + Alert5-Missionsm default is 6
|
||||
-- @return #number count
|
||||
function EASYGCICAP:_CountAliveAuftrags()
|
||||
local alive = 0
|
||||
for _,_auftrag in pairs(self.ListOfAuftrag) do
|
||||
local auftrag = _auftrag -- Ops.Auftrag#AUFTRAG
|
||||
if auftrag and (not (auftrag:IsCancelled() or auftrag:IsDone() or auftrag:IsOver())) then
|
||||
alive = alive + 1
|
||||
end
|
||||
end
|
||||
return alive
|
||||
end
|
||||
|
||||
--- Set Maximum of alive missions created by this instance to stop airplanes spamming the map
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Intercept-Missions + Alert5-Missions, default is 8
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetMaxAliveMissions(Maxiumum)
|
||||
self:T(self.lid.."SetMaxAliveMissions")
|
||||
@@ -436,9 +456,9 @@ end
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Airframes defaults to 2
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetDefaultNumberAlter5Standby(Airframes)
|
||||
self:T(self.lid.."SetDefaultNumberAlter5Standby")
|
||||
self.noaltert5 = math.abs(Airframes) or 2
|
||||
function EASYGCICAP:SetDefaultNumberAlert5Standby(Airframes)
|
||||
self:T(self.lid.."SetDefaultNumberAlert5Standby")
|
||||
self.noalert5 = math.abs(Airframes) or 2
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -447,7 +467,7 @@ end
|
||||
-- @param #number Range defaults to 50NM
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetDefaultEngageRange(Range)
|
||||
self:T(self.lid.."SetDefaultNumberAlter5Standby")
|
||||
self:T(self.lid.."SetDefaultEngageRange")
|
||||
self.engagerange = Range or 50
|
||||
return self
|
||||
end
|
||||
@@ -579,7 +599,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
|
||||
local TankerInvisible = self.TankerInvisible
|
||||
|
||||
function CAP_Wing:OnAfterFlightOnMission(From, Event, To, Flightgroup, Mission)
|
||||
function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission)
|
||||
local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP
|
||||
if DespawnAfterLanding then
|
||||
flightgroup:SetDespawnAfterLanding()
|
||||
@@ -609,17 +629,18 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
flightgroup:SetFuelLowRTB(true)
|
||||
Intel:AddAgent(flightgroup)
|
||||
if DespawnAfterHolding then
|
||||
function flightgroup:OnAfterHolding(From,Event,To)
|
||||
function flightgroup:onbeforeHolding(From,Event,To)
|
||||
self:Despawn(1,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.noaltert5 > 0 then
|
||||
if self.noalert5 > 0 then
|
||||
local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
|
||||
alert:SetRequiredAssets(self.noaltert5)
|
||||
alert:SetRequiredAssets(self.noalert5)
|
||||
alert:SetRepeat(99)
|
||||
CAP_Wing:AddMission(alert)
|
||||
table.insert(self.ListOfAuftrag,alert)
|
||||
end
|
||||
|
||||
self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename }
|
||||
@@ -1156,7 +1177,7 @@ function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group
|
||||
return assigned, wingsize
|
||||
end
|
||||
|
||||
--- Add a zone to the rejected zones set.
|
||||
--- Here, we'll decide if we need to launch an intercepting flight, and from where
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param Ops.Intel#INTEL.Cluster Cluster
|
||||
-- @return #EASYGCICAP self
|
||||
@@ -1170,7 +1191,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
|
||||
local wings = self.wings
|
||||
local ctlpts = self.ManagedCP
|
||||
local MaxAliveMissions = self.MaxAliveMissions * self.capgrouping
|
||||
local MaxAliveMissions = self.MaxAliveMissions --* self.capgrouping
|
||||
local nogozoneset = self.NoGoZoneSet
|
||||
local ReadyFlightGroups = self.ReadyFlightGroups
|
||||
|
||||
@@ -1200,9 +1221,11 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
local zone = _data[2] -- Core.Zone#ZONE
|
||||
local zonecoord = zone:GetCoordinate()
|
||||
local name = _data[3] -- #string
|
||||
local coa = AIRBASE:FindByName(name):GetCoalition()
|
||||
local distance = position:DistanceFromPointVec2(zonecoord)
|
||||
local airframes = airwing:CountAssets(true)
|
||||
if distance < bestdistance and airframes >= wingsize then
|
||||
local samecoalitionab = coa == self.coalition and true or false
|
||||
if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then
|
||||
bestdistance = distance
|
||||
targetairwing = airwing
|
||||
targetawname = name
|
||||
@@ -1218,10 +1241,11 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
local name = data.AirbaseName
|
||||
local zonecoord = data.Coordinate
|
||||
local airwing = wings[name][1]
|
||||
|
||||
local coa = AIRBASE:FindByName(name):GetCoalition()
|
||||
local samecoalitionab = coa == self.coalition and true or false
|
||||
local distance = position:DistanceFromPointVec2(zonecoord)
|
||||
local airframes = airwing:CountAssets(true)
|
||||
if distance < bestdistance and airframes >= wingsize then
|
||||
if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then
|
||||
bestdistance = distance
|
||||
targetairwing = airwing -- Ops.Airwing#AIRWING
|
||||
targetawname = name
|
||||
@@ -1232,9 +1256,10 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
-- Do we have a matching airwing?
|
||||
if targetairwing then
|
||||
local AssetCount = targetairwing:CountAssetsOnMission(MissionTypes,Cohort)
|
||||
local missioncount = self:_CountAliveAuftrags()
|
||||
-- Enough airframes on mission already?
|
||||
self:T(self.lid.." Assets on Mission "..AssetCount)
|
||||
if AssetCount <= MaxAliveMissions then
|
||||
if missioncount < MaxAliveMissions then
|
||||
local repeats = repeatsonfailure
|
||||
local InterceptAuftrag = AUFTRAG:NewINTERCEPT(contact.group)
|
||||
:SetMissionRange(150)
|
||||
@@ -1260,6 +1285,8 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
nogozoneset
|
||||
)
|
||||
end
|
||||
|
||||
table.insert(self.ListOfAuftrag,InterceptAuftrag)
|
||||
local assigned, rest = self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize)
|
||||
if not assigned then
|
||||
InterceptAuftrag:SetRequiredAssets(rest)
|
||||
@@ -1280,11 +1307,11 @@ function EASYGCICAP:_StartIntel()
|
||||
self:T(self.lid.."_StartIntel")
|
||||
-- Border GCI Detection
|
||||
local BlueAir_DetectionSetGroup = SET_GROUP:New()
|
||||
BlueAir_DetectionSetGroup:FilterPrefixes( { self.EWRName } )
|
||||
BlueAir_DetectionSetGroup:FilterPrefixes( self.EWRName )
|
||||
BlueAir_DetectionSetGroup:FilterStart()
|
||||
|
||||
-- Intel type detection
|
||||
local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.EWRName)
|
||||
local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.alias)
|
||||
BlueIntel:SetClusterAnalysis(true,false,false)
|
||||
BlueIntel:SetForgetTime(300)
|
||||
BlueIntel:SetAcceptZones(self.GoZoneSet)
|
||||
@@ -1300,7 +1327,7 @@ function EASYGCICAP:_StartIntel()
|
||||
self:_AssignIntercept(Cluster)
|
||||
end
|
||||
|
||||
function BlueIntel:OnAfterNewCluster(From,Event,To,Cluster)
|
||||
function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster)
|
||||
AssignCluster(Cluster)
|
||||
end
|
||||
|
||||
@@ -1351,6 +1378,20 @@ end
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:onafterStatus(From,Event,To)
|
||||
self:T({From,Event,To})
|
||||
-- cleanup
|
||||
local cleaned = false
|
||||
local cleanlist = {}
|
||||
for _,_auftrag in pairs(self.ListOfAuftrag) do
|
||||
local auftrag = _auftrag -- Ops.Auftrag#AUFTRAG
|
||||
if auftrag and (not (auftrag:IsCancelled() or auftrag:IsDone() or auftrag:IsOver())) then
|
||||
table.insert(cleanlist,auftrag)
|
||||
cleaned = true
|
||||
end
|
||||
end
|
||||
if cleaned == true then
|
||||
self.ListOfAuftrag = nil
|
||||
self.ListOfAuftrag = cleanlist
|
||||
end
|
||||
-- Gather Some Stats
|
||||
local function counttable(tbl)
|
||||
local count = 0
|
||||
@@ -1403,12 +1444,14 @@ function EASYGCICAP:onafterStatus(From,Event,To)
|
||||
local text = "GCICAP "..self.alias
|
||||
text = text.."\nWings: "..wings.."\nSquads: "..squads.."\nCapPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock
|
||||
text = text.."\nThreats: "..threatcount
|
||||
text = text.."\nMissions: "..capmission+interceptmission
|
||||
text = text.."\nAirWing managed Missions: "..capmission+awacsmission+tankermission+reconmission
|
||||
text = text.."\n - CAP: "..capmission
|
||||
text = text.."\n - Intercept: "..interceptmission
|
||||
text = text.."\n - AWACS: "..awacsmission
|
||||
text = text.."\n - TANKER: "..tankermission
|
||||
text = text.."\n - Recon: "..reconmission
|
||||
text = text.."\nSelf managed Missions:"
|
||||
text = text.."\n - Mission Limit: "..self.MaxAliveMissions
|
||||
text = text.."\n - Alert5+Intercept "..self:_CountAliveAuftrags()
|
||||
MESSAGE:New(text,15,"GCICAP"):ToAll():ToLogIf(self.debug)
|
||||
end
|
||||
self:__Status(30)
|
||||
|
||||
@@ -334,6 +334,9 @@ function FLEET:onafterStatus(From, Event, To)
|
||||
-- Info ---
|
||||
-----------
|
||||
|
||||
-- Display tactival overview.
|
||||
self:_TacticalOverview()
|
||||
|
||||
-- General info:
|
||||
if self.verbose>=1 then
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
||||
-- @field #table cohorts Cohorts of this legion.
|
||||
-- @field Ops.Commander#COMMANDER commander Commander of this legion.
|
||||
-- @field Ops.Chief#CHIEF chief Chief of this legion.
|
||||
-- @field #boolean tacview If `true`, show tactical overview on status update.
|
||||
-- @extends Functional.Warehouse#WAREHOUSE
|
||||
|
||||
--- *Per aspera ad astra.*
|
||||
@@ -52,7 +53,7 @@ LEGION.RandomAssetScore=1
|
||||
|
||||
--- LEGION class version.
|
||||
-- @field #string version
|
||||
LEGION.version="0.5.0"
|
||||
LEGION.version="0.5.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@@ -322,6 +323,14 @@ function LEGION:SetVerbosity(VerbosityLevel)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set tactical overview on.
|
||||
-- @param #LEGION self
|
||||
-- @return #LEGION self
|
||||
function LEGION:SetTacticalOverviewOn()
|
||||
self.tacview=true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a mission for the legion. It will pick the best available assets for the mission and lauch it when ready.
|
||||
-- @param #LEGION self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission Mission for this legion.
|
||||
@@ -436,6 +445,21 @@ function LEGION:DelCohort(Cohort)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove specific asset from legion.
|
||||
-- @param #LEGION self
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset.
|
||||
-- @return #LEGION self
|
||||
function LEGION:DelAsset(Asset)
|
||||
|
||||
if Asset.cohort then
|
||||
Asset.cohort:DelAsset(Asset)
|
||||
else
|
||||
self:E(self.lid..string.format("ERROR: Asset has not cohort attached. Cannot remove it from legion!"))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Relocate a cohort to another legion.
|
||||
-- Assets in stock are spawned and routed to the new legion.
|
||||
@@ -1634,6 +1658,9 @@ function LEGION:onafterAssetDead(From, Event, To, asset, request)
|
||||
if self.commander and self.commander.chief then
|
||||
self.commander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname})
|
||||
end
|
||||
|
||||
-- Remove asset from cohort and legion.
|
||||
self:DelAsset(asset)
|
||||
|
||||
-- Remove asset from mission is done via Mission:AssetDead() call from flightgroup onafterFlightDead function
|
||||
-- Remove asset from squadron same
|
||||
@@ -1772,7 +1799,7 @@ end
|
||||
-- Mission Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new flight group after an asset was spawned.
|
||||
--- Create a new OPS group after an asset was spawned.
|
||||
-- @param #LEGION self
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem asset The asset.
|
||||
-- @return Ops.FlightGroup#FLIGHTGROUP The created flightgroup object.
|
||||
@@ -1818,6 +1845,9 @@ function LEGION:_CreateFlightGroup(asset)
|
||||
-- Set home base.
|
||||
opsgroup.homebase=self.airbase
|
||||
|
||||
-- Set destination base
|
||||
opsgroup.destbase=self.airbase
|
||||
|
||||
-- Set home zone.
|
||||
opsgroup.homezone=self.spawnzone
|
||||
|
||||
@@ -1836,6 +1866,53 @@ function LEGION:_CreateFlightGroup(asset)
|
||||
return opsgroup
|
||||
end
|
||||
|
||||
--- Display tactical overview.
|
||||
-- @param #LEGION self
|
||||
function LEGION:_TacticalOverview()
|
||||
|
||||
if self.tacview then
|
||||
|
||||
local NassetsTotal=self:CountAssets(nil)
|
||||
local NassetsStock=self:CountAssets(true)
|
||||
local NassetsActiv=self:CountAssets(false)
|
||||
|
||||
local NmissionsTotal=#self.missionqueue
|
||||
local NmissionsRunni=self:CountMissionsInQueue()
|
||||
|
||||
-- Info message
|
||||
local text=string.format("Tactical Overview %s\n", self.alias)
|
||||
text=text..string.format("===================================\n")
|
||||
|
||||
-- Asset info.
|
||||
text=text..string.format("Assets: %d [Active=%d, Stock=%d]\n", NassetsTotal, NassetsActiv, NassetsStock)
|
||||
|
||||
-- Mission info.
|
||||
text=text..string.format("Missions: %d [Running=%d]\n", NmissionsTotal, NmissionsRunni)
|
||||
for _,mtype in pairs(AUFTRAG.Type) do
|
||||
local n=self:CountMissionsInQueue(mtype)
|
||||
if n>0 then
|
||||
local N=self:CountMissionsInQueue(mtype)
|
||||
text=text..string.format(" - %s: %d [Running=%d]\n", mtype, n, N)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local Ntransports=#self.transportqueue
|
||||
if Ntransports>0 then
|
||||
text=text..string.format("Transports: %d\n", Ntransports)
|
||||
for _,_transport in pairs(self.transportqueue) do
|
||||
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
|
||||
text=text..string.format(" - %s", transport:GetState())
|
||||
end
|
||||
end
|
||||
|
||||
-- Message to coalition.
|
||||
MESSAGE:New(text, 60, nil, true):ToCoalition(self:GetCoalition())
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Check if an asset is currently on a mission (STARTED or EXECUTING).
|
||||
-- @param #LEGION self
|
||||
@@ -2563,6 +2640,8 @@ function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properti
|
||||
local RangeMax = RangeMax or 0
|
||||
local InRange=(RangeMax and math.max(RangeMax, Rmax) or Rmax) >= TargetDistance
|
||||
|
||||
--env.info(string.format("Range TargetDist=%.1f Rmax=%.1f RangeMax=%.1f InRange=%s", TargetDistance, Rmax, RangeMax, tostring(InRange)))
|
||||
|
||||
return InRange
|
||||
end
|
||||
|
||||
@@ -2628,7 +2707,7 @@ function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properti
|
||||
else
|
||||
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of category", Cohort.name))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if can then
|
||||
can=CheckAttribute(Cohort)
|
||||
@@ -2684,7 +2763,7 @@ function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properti
|
||||
else
|
||||
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of max weight", Cohort.name))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
@@ -2728,6 +2807,8 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt,
|
||||
|
||||
-- Check if cohort can do the mission.
|
||||
local can=LEGION._CohortCan(cohort, MissionTypeRecruit, Categories, Attributes, Properties, WeaponTypes, TargetVec2, RangeMax, RefuelSystem, CargoWeight, MaxWeight)
|
||||
|
||||
--env.info(string.format("RecruitCohortAssets %s Cohort=%s can=%s", MissionTypeRecruit, cohort:GetName(), tostring(can)))
|
||||
|
||||
-- Check OnDuty, capable, in range and refueling type (if TANKER).
|
||||
if can then
|
||||
@@ -2744,6 +2825,12 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt,
|
||||
|
||||
end
|
||||
|
||||
-- Break if no assets could be found
|
||||
if #Assets==0 then
|
||||
--env.info(string.format("LEGION.RecruitCohortAssets: No assets could be recruited for mission type %s [Nmin=%s, Nmax=%s]", MissionTypeRecruit, tostring(NreqMin), tostring(NreqMax)))
|
||||
return false, {}, {}
|
||||
end
|
||||
|
||||
-- Now we have a long list with assets.
|
||||
LEGION._OptimizeAssetSelection(Assets, MissionTypeOpt, TargetVec2, false, TotalWeight)
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
-- @field #number pathCorridor Path corrdidor width in meters.
|
||||
-- @field #boolean ispathfinding If true, group is currently path finding.
|
||||
-- @field #NAVYGROUP.Target engage Engage target.
|
||||
-- @field #boolean intowindold Use old calculation to determine heading into wind.
|
||||
-- @extends Ops.OpsGroup#OPSGROUP
|
||||
|
||||
--- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson
|
||||
@@ -90,7 +91,7 @@ NAVYGROUP = {
|
||||
|
||||
--- NavyGroup version.
|
||||
-- @field #string version
|
||||
NAVYGROUP.version="1.0.2"
|
||||
NAVYGROUP.version="1.0.3"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -393,7 +394,8 @@ function NAVYGROUP:New(group)
|
||||
-- Handle events:
|
||||
self:HandleEvent(EVENTS.Birth, self.OnEventBirth)
|
||||
self:HandleEvent(EVENTS.Dead, self.OnEventDead)
|
||||
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit)
|
||||
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit)
|
||||
self:HandleEvent(EVENTS.UnitLost, self.OnEventRemoveUnit)
|
||||
|
||||
-- Start the status monitoring.
|
||||
self.timerStatus=TIMER:New(self.Status, self):Start(1, 30)
|
||||
@@ -455,6 +457,18 @@ function NAVYGROUP:SetPathfindingOff()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set if old into wind calculation is used when carrier turns into the wind for a recovery.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param #boolean SwitchOn If `true` or `nil`, use old into wind calculation.
|
||||
-- @return #NAVYGROUP self
|
||||
function NAVYGROUP:SetIntoWindLegacy( SwitchOn )
|
||||
if SwitchOn==nil then
|
||||
SwitchOn=true
|
||||
end
|
||||
self.intowindold=SwitchOn
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Add a *scheduled* task.
|
||||
-- @param #NAVYGROUP self
|
||||
@@ -600,6 +614,58 @@ function NAVYGROUP:AddTurnIntoWind(starttime, stoptime, speed, uturn, offset)
|
||||
return recovery
|
||||
end
|
||||
|
||||
--- Get "Turn Into Wind" data. You can specify a certain ID.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param #number TID (Optional) Turn Into wind ID. If not given, the currently open "Turn into Wind" data is return (if there is any).
|
||||
-- @return #NAVYGROUP.IntoWind Turn into window data table.
|
||||
function NAVYGROUP:GetTurnIntoWind(TID)
|
||||
|
||||
if TID then
|
||||
|
||||
-- Look for a specific ID.
|
||||
for _,_turn in pairs(self.Qintowind) do
|
||||
local turn=_turn --#NAVYGROUP.IntoWind
|
||||
if turn.Id==TID then
|
||||
return turn
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Return currently open window.
|
||||
return self.intowind
|
||||
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Extend duration of turn into wind.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param #number Duration Duration in seconds. Default 300 sec.
|
||||
-- @param #NAVYGROUP.IntoWind TurnIntoWind (Optional) Turn into window data table. If not given, the currently open one is used (if there is any).
|
||||
-- @return #NAVYGROUP self
|
||||
function NAVYGROUP:ExtendTurnIntoWind(Duration, TurnIntoWind)
|
||||
|
||||
Duration=Duration or 300
|
||||
|
||||
-- ID of turn or nil
|
||||
local TID=TurnIntoWind and TurnIntoWind.Id or nil
|
||||
|
||||
-- Get turn data.
|
||||
local turn=self:GetTurnIntoWind(TID)
|
||||
|
||||
if turn then
|
||||
turn.Tstop=turn.Tstop+Duration
|
||||
self:T(self.lid..string.format("Extending turn into wind by %d seconds. New stop time is %s", Duration, UTILS.SecondsToClock(turn.Tstop)))
|
||||
else
|
||||
self:E(self.lid.."Could not get turn into wind to extend!")
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Remove steam into wind window from queue. If the window is currently active, it is stopped first.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param #NAVYGROUP.IntoWind IntoWindData Turn into window data table.
|
||||
@@ -709,7 +775,7 @@ end
|
||||
|
||||
--- Update status.
|
||||
-- @param #NAVYGROUP self
|
||||
function NAVYGROUP:Status(From, Event, To)
|
||||
function NAVYGROUP:Status()
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
@@ -912,6 +978,35 @@ function NAVYGROUP:Status(From, Event, To)
|
||||
|
||||
end
|
||||
|
||||
---
|
||||
-- Elements
|
||||
---
|
||||
|
||||
if self.verbose>=2 then
|
||||
local text="Elements:"
|
||||
for i,_element in pairs(self.elements) do
|
||||
local element=_element --Ops.OpsGroup#OPSGROUP.Element
|
||||
|
||||
local name=element.name
|
||||
local status=element.status
|
||||
local unit=element.unit
|
||||
local life,life0=self:GetLifePoints(element)
|
||||
|
||||
local life0=element.life0
|
||||
|
||||
-- Get ammo.
|
||||
local ammo=self:GetAmmoElement(element)
|
||||
|
||||
-- Output text for element.
|
||||
text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg",
|
||||
i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles, element.weightCargo, element.weightMaxCargo)
|
||||
end
|
||||
if #self.elements==0 then
|
||||
text=text.." none!"
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
---
|
||||
-- Engage Detected Targets
|
||||
---
|
||||
@@ -975,7 +1070,7 @@ function NAVYGROUP:onafterSpawned(From, Event, To)
|
||||
|
||||
-- Debug info.
|
||||
if self.verbose>=1 then
|
||||
local text=string.format("Initialized Navy Group %s:\n", self.groupname)
|
||||
local text=string.format("Initialized Navy Group %s [GID=%d]:\n", self.groupname, self.group:GetID())
|
||||
text=text..string.format("Unit type = %s\n", self.actype)
|
||||
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax))
|
||||
text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise))
|
||||
@@ -1203,7 +1298,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Depth)
|
||||
if self.verbose>=10 then
|
||||
for i=1,#waypoints do
|
||||
local wp=waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint
|
||||
local text=string.format("%s Waypoint [%d] UID=%d speed=%d", self.groupname, i-1, wp.uid or -1, wp.speed)
|
||||
local text=string.format("%s Waypoint [%d] UID=%d speed=%d m/s", self.groupname, i-1, wp.uid or -1, wp.speed)
|
||||
self:I(self.lid..text)
|
||||
COORDINATE:NewFromWaypoint(wp):MarkToAll(text)
|
||||
end
|
||||
@@ -1775,81 +1870,96 @@ end
|
||||
--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param #table Template Template used to init the group. Default is `self.template`.
|
||||
-- @param #number Delay Delay in seconds before group is initialized. Default `nil`, *i.e.* instantaneous.
|
||||
-- @return #NAVYGROUP self
|
||||
function NAVYGROUP:_InitGroup(Template)
|
||||
function NAVYGROUP:_InitGroup(Template, Delay)
|
||||
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Get template of group.
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
-- Ships are always AI.
|
||||
self.isAI=true
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=template.lateActivation
|
||||
|
||||
-- Naval groups cannot be uncontrolled.
|
||||
self.isUncontrolled=false
|
||||
|
||||
-- Max speed in km/h.
|
||||
self.speedMax=self.group:GetSpeedMax()
|
||||
|
||||
-- Is group mobile?
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
if Delay and Delay>0 then
|
||||
-- Delayed call
|
||||
self:ScheduleOnce(Delay, NAVYGROUP._InitGroup, self, Template, 0)
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
-- Cruise speed: 70% of max speed.
|
||||
self.speedCruise=self.speedMax*0.7
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Group ammo.
|
||||
self.ammo=self:GetAmmoTot()
|
||||
-- Get template of group.
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
-- Radio parameters from template. Default is set on spawn if not modified by the user.
|
||||
self.radio.On=true -- Radio is always on for ships.
|
||||
self.radio.Freq=tonumber(template.units[1].frequency)/1000000
|
||||
self.radio.Modu=tonumber(template.units[1].modulation)
|
||||
-- Ships are always AI.
|
||||
self.isAI=true
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=template.lateActivation
|
||||
|
||||
-- Naval groups cannot be uncontrolled.
|
||||
self.isUncontrolled=false
|
||||
|
||||
-- Max speed in km/h.
|
||||
self.speedMax=self.group:GetSpeedMax()
|
||||
|
||||
-- Is group mobile?
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
-- Cruise speed: 70% of max speed.
|
||||
self.speedCruise=self.speedMax*0.7
|
||||
|
||||
-- Group ammo.
|
||||
self.ammo=self:GetAmmoTot()
|
||||
|
||||
-- Radio parameters from template. Default is set on spawn if not modified by the user.
|
||||
self.radio.On=true -- Radio is always on for ships.
|
||||
self.radio.Freq=tonumber(template.units[1].frequency)/1000000
|
||||
self.radio.Modu=tonumber(template.units[1].modulation)
|
||||
|
||||
-- Set default formation. No really applicable for ships.
|
||||
self.optionDefault.Formation="Off Road"
|
||||
self.option.Formation=self.optionDefault.Formation
|
||||
|
||||
-- Set default formation. No really applicable for ships.
|
||||
self.optionDefault.Formation="Off Road"
|
||||
self.option.Formation=self.optionDefault.Formation
|
||||
|
||||
-- Default TACAN off.
|
||||
self:SetDefaultTACAN(nil, nil, nil, nil, true)
|
||||
self.tacan=UTILS.DeepCopy(self.tacanDefault)
|
||||
-- Default TACAN off (we check if something is set already to keep those values in case of respawn)
|
||||
if not self.tacanDefault then
|
||||
self:SetDefaultTACAN(nil, nil, nil, nil, true)
|
||||
end
|
||||
if not self.tacan then
|
||||
self.tacan=UTILS.DeepCopy(self.tacanDefault)
|
||||
end
|
||||
|
||||
-- Default ICLS off.
|
||||
if not self.iclsDefault then
|
||||
self:SetDefaultICLS(nil, nil, nil, true)
|
||||
end
|
||||
if not self.icls then
|
||||
self.icls=UTILS.DeepCopy(self.iclsDefault)
|
||||
end
|
||||
|
||||
-- Get all units of the group.
|
||||
local units=self.group:GetUnits()
|
||||
|
||||
-- Default ICLS off.
|
||||
self:SetDefaultICLS(nil, nil, nil, true)
|
||||
self.icls=UTILS.DeepCopy(self.iclsDefault)
|
||||
|
||||
-- Get all units of the group.
|
||||
local units=self.group:GetUnits()
|
||||
|
||||
-- DCS group.
|
||||
local dcsgroup=Group.getByName(self.groupname)
|
||||
local size0=dcsgroup:getInitialSize()
|
||||
|
||||
-- Quick check.
|
||||
if #units~=size0 then
|
||||
self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0))
|
||||
-- DCS group.
|
||||
local dcsgroup=Group.getByName(self.groupname)
|
||||
local size0=dcsgroup:getInitialSize()
|
||||
|
||||
-- Quick check.
|
||||
if #units~=size0 then
|
||||
self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0))
|
||||
end
|
||||
|
||||
-- Add elemets.
|
||||
for _,unit in pairs(units) do
|
||||
self:_AddElementByName(unit:GetName())
|
||||
end
|
||||
|
||||
-- Init done.
|
||||
self.groupinitialized=true
|
||||
end
|
||||
|
||||
-- Add elemets.
|
||||
for _,unit in pairs(units) do
|
||||
self:_AddElementByName(unit:GetName())
|
||||
end
|
||||
|
||||
-- Init done.
|
||||
self.groupinitialized=true
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -2068,8 +2178,43 @@ end
|
||||
--- Get heading of group into the wind.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param #number Offset Offset angle in degrees, e.g. to account for an angled runway.
|
||||
-- @param #number vdeck Desired wind speed on deck in Knots.
|
||||
-- @return #number Carrier heading in degrees.
|
||||
function NAVYGROUP:GetHeadingIntoWind_old(Offset)
|
||||
-- @return #number Carrier speed in knots.
|
||||
function NAVYGROUP:GetHeadingIntoWind_old(Offset, vdeck)
|
||||
|
||||
local function adjustDegreesForWindSpeed(windSpeed)
|
||||
local degreesAdjustment = 0
|
||||
-- the windspeeds are in m/s
|
||||
-- +0 degrees at 15m/s = 37kts
|
||||
-- +0 degrees at 14m/s = 35kts
|
||||
-- +0 degrees at 13m/s = 33kts
|
||||
-- +4 degrees at 12m/s = 31kts
|
||||
-- +4 degrees at 11m/s = 29kts
|
||||
-- +4 degrees at 10m/s = 27kts
|
||||
-- +4 degrees at 9m/s = 27kts
|
||||
-- +4 degrees at 8m/s = 27kts
|
||||
-- +8 degrees at 7m/s = 27kts
|
||||
-- +8 degrees at 6m/s = 27kts
|
||||
-- +8 degrees at 5m/s = 26kts
|
||||
-- +20 degrees at 4m/s = 26kts
|
||||
-- +20 degrees at 3m/s = 26kts
|
||||
-- +30 degrees at 2m/s = 26kts 1s
|
||||
|
||||
if windSpeed > 0 and windSpeed < 3 then
|
||||
degreesAdjustment = 30
|
||||
elseif windSpeed >= 3 and windSpeed < 5 then
|
||||
degreesAdjustment = 20
|
||||
elseif windSpeed >= 5 and windSpeed < 8 then
|
||||
degreesAdjustment = 8
|
||||
elseif windSpeed >= 8 and windSpeed < 13 then
|
||||
degreesAdjustment = 4
|
||||
elseif windSpeed >= 13 then
|
||||
degreesAdjustment = 0
|
||||
end
|
||||
|
||||
return degreesAdjustment
|
||||
end
|
||||
|
||||
Offset=Offset or 0
|
||||
|
||||
@@ -2077,7 +2222,7 @@ function NAVYGROUP:GetHeadingIntoWind_old(Offset)
|
||||
local windfrom, vwind=self:GetWind()
|
||||
|
||||
-- Actually, we want the runway in the wind.
|
||||
local intowind=windfrom-Offset
|
||||
local intowind = windfrom - Offset + adjustDegreesForWindSpeed(vwind)
|
||||
|
||||
-- If no wind, take current heading.
|
||||
if vwind<0.1 then
|
||||
@@ -2088,8 +2233,11 @@ function NAVYGROUP:GetHeadingIntoWind_old(Offset)
|
||||
if intowind<0 then
|
||||
intowind=intowind+360
|
||||
end
|
||||
|
||||
-- Speed of carrier in m/s but at least 4 knots.
|
||||
local vtot = math.max(vdeck-UTILS.MpsToKnots(vwind), 4)
|
||||
|
||||
return intowind
|
||||
return intowind, vtot
|
||||
end
|
||||
|
||||
|
||||
@@ -2099,7 +2247,8 @@ end
|
||||
-- @param #number Offset Offset angle in degrees, e.g. to account for an angled runway.
|
||||
-- @param #number vdeck Desired wind speed on deck in Knots.
|
||||
-- @return #number Carrier heading in degrees.
|
||||
function NAVYGROUP:GetHeadingIntoWind(Offset, vdeck)
|
||||
-- @return #number Carrier speed in knots.
|
||||
function NAVYGROUP:GetHeadingIntoWind_new(Offset, vdeck)
|
||||
|
||||
-- Default offset angle.
|
||||
Offset=Offset or 0
|
||||
@@ -2180,6 +2329,25 @@ function NAVYGROUP:GetHeadingIntoWind(Offset, vdeck)
|
||||
return intowind, v
|
||||
end
|
||||
|
||||
--- Get heading of group into the wind. This minimizes the cross wind for an angled runway.
|
||||
-- Implementation based on [Mags & Bami](https://magwo.github.io/carrier-cruise/) work.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param #number Offset Offset angle in degrees, e.g. to account for an angled runway.
|
||||
-- @param #number vdeck Desired wind speed on deck in Knots.
|
||||
-- @return #number Carrier heading in degrees.
|
||||
-- @return #number Carrier speed in knots.
|
||||
function NAVYGROUP:GetHeadingIntoWind(Offset, vdeck)
|
||||
|
||||
if self.intowindold then
|
||||
--env.info("FF use OLD into wind")
|
||||
return self:GetHeadingIntoWind_old(Offset, vdeck)
|
||||
else
|
||||
--env.info("FF use NEW into wind")
|
||||
return self:GetHeadingIntoWind_new(Offset, vdeck)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Find free path to next waypoint.
|
||||
-- @param #NAVYGROUP self
|
||||
|
||||
@@ -513,7 +513,7 @@ OPSGROUP.CargoStatus={
|
||||
|
||||
--- OpsGroup version.
|
||||
-- @field #string version
|
||||
OPSGROUP.version="1.0.1"
|
||||
OPSGROUP.version="1.0.4"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -1047,7 +1047,7 @@ function OPSGROUP:SetReturnToLegion(Switch)
|
||||
else
|
||||
self.legionReturn=true
|
||||
end
|
||||
self:T(self.lid..string.format("Setting ReturnToLetion=%s", tostring(self.legionReturn)))
|
||||
self:T(self.lid..string.format("Setting ReturnToLegion=%s", tostring(self.legionReturn)))
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1339,8 +1339,9 @@ end
|
||||
-- @param Core.Point#COORDINATE TargetCoord Coordinate of the target.
|
||||
-- @param #number WeaponBitType Weapon type.
|
||||
-- @param Core.Point#COORDINATE RefCoord Reference coordinate.
|
||||
-- @param #table SurfaceTypes Valid surfaces types of the coordinate. Default any (nil).
|
||||
-- @return Core.Point#COORDINATE Coordinate in weapon range
|
||||
function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord)
|
||||
function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord, SurfaceTypes)
|
||||
|
||||
local coordInRange=nil --Core.Point#COORDINATE
|
||||
|
||||
@@ -1349,35 +1350,58 @@ function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord)
|
||||
-- Get weapon range.
|
||||
local weapondata=self:GetWeaponData(WeaponBitType)
|
||||
|
||||
-- Heading intervals to search for a possible new coordinate in range.
|
||||
local dh={0, -5, 5, -10, 10, -15, 15, -20, 20, -25, 25, -30, 30, -35, 35, -40, 40, -45, 45, -50, 50, -55, 55, -60, 60, -65, 65, -70, 70, -75, 75, -80, 80}
|
||||
|
||||
-- Function that checks if the given surface type is valid
|
||||
local function _checkSurface(point)
|
||||
if SurfaceTypes then
|
||||
local stype=point:GetSurfaceType()
|
||||
for _,sf in pairs(SurfaceTypes) do
|
||||
if sf==stype then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if weapondata then
|
||||
|
||||
-- Heading to target.
|
||||
local heading=RefCoord:HeadingTo(TargetCoord)
|
||||
local heading=TargetCoord:HeadingTo(RefCoord)
|
||||
|
||||
-- Distance to target.
|
||||
local dist=RefCoord:Get2DDistance(TargetCoord)
|
||||
|
||||
local range=nil
|
||||
if dist>weapondata.RangeMax then
|
||||
range=weapondata.RangeMax
|
||||
self:T(self.lid..string.format("Out of max range = %.1f km by %.1f km for weapon %s", weapondata.RangeMax/1000, (weapondata.RangeMax-dist)/1000, tostring(WeaponBitType)))
|
||||
elseif dist<weapondata.RangeMin then
|
||||
range=weapondata.RangeMin
|
||||
self:T(self.lid..string.format("Out of min range = %.1f km by %.1f km for weapon %s", weapondata.RangeMin/1000, (weapondata.RangeMin-dist)/1000, tostring(WeaponBitType)))
|
||||
end
|
||||
|
||||
-- Check if we are within range.
|
||||
if dist>weapondata.RangeMax then
|
||||
|
||||
local d=(dist-weapondata.RangeMax)*1.05
|
||||
|
||||
-- New waypoint coord.
|
||||
coordInRange=RefCoord:Translate(d, heading)
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Out of max range = %.1f km for weapon %s", weapondata.RangeMax/1000, tostring(WeaponBitType)))
|
||||
elseif dist<weapondata.RangeMin then
|
||||
|
||||
local d=(dist-weapondata.RangeMin)*1.05
|
||||
|
||||
-- New waypoint coord.
|
||||
coordInRange=RefCoord:Translate(d, heading)
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Out of min range = %.1f km for weapon %s", weapondata.RangeMax/1000, tostring(WeaponBitType)))
|
||||
else
|
||||
if range then
|
||||
|
||||
for _,delta in pairs(dh) do
|
||||
|
||||
local h=heading+delta
|
||||
|
||||
-- New waypoint coord.
|
||||
coordInRange=TargetCoord:Translate(range, h)
|
||||
|
||||
if _checkSurface(coordInRange) then
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Already in range for weapon %s", tostring(WeaponBitType)))
|
||||
end
|
||||
@@ -1456,11 +1480,14 @@ end
|
||||
-- @param #number RangeMin Minimum range in nautical miles. Default 0 NM.
|
||||
-- @param #number RangeMax Maximum range in nautical miles. Default 10 NM.
|
||||
-- @param #number BitType Bit mask of weapon type for which the given min/max ranges apply. Default is `ENUMS.WeaponFlag.Auto`, i.e. for all weapon types.
|
||||
-- @param #function ConversionToMeters Function that converts input units of ranges to meters. Defaul `UTILS.NMToMeters`.
|
||||
-- @return #OPSGROUP self
|
||||
function OPSGROUP:AddWeaponRange(RangeMin, RangeMax, BitType)
|
||||
function OPSGROUP:AddWeaponRange(RangeMin, RangeMax, BitType, ConversionToMeters)
|
||||
|
||||
RangeMin=UTILS.NMToMeters(RangeMin or 0)
|
||||
RangeMax=UTILS.NMToMeters(RangeMax or 10)
|
||||
ConversionToMeters=ConversionToMeters or UTILS.NMToMeters
|
||||
|
||||
RangeMin=ConversionToMeters(RangeMin or 0)
|
||||
RangeMax=ConversionToMeters(RangeMax or 10)
|
||||
|
||||
local weapon={} --#OPSGROUP.WeaponData
|
||||
|
||||
@@ -4175,7 +4202,7 @@ function OPSGROUP:onbeforeTaskExecute(From, Event, To, Task)
|
||||
if self:IsWaiting() then
|
||||
-- Group is already waiting
|
||||
else
|
||||
-- Wait indefinately.
|
||||
-- Wait indefinitely.
|
||||
local alt=Mission.missionAltitude and UTILS.MetersToFeet(Mission.missionAltitude) or nil
|
||||
self:Wait(nil, alt)
|
||||
end
|
||||
@@ -4486,7 +4513,7 @@ function OPSGROUP:_UpdateTask(Task, Mission)
|
||||
-- Set speed. Default max.
|
||||
local speed=self.speedMax and UTILS.KmphToKnots(self.speedMax) or nil
|
||||
if Task.dcstask.params.speed then
|
||||
speed=Task.dcstask.params.speed
|
||||
speed=UTILS.MpsToKnots(Task.dcstask.params.speed)
|
||||
end
|
||||
|
||||
if target then
|
||||
@@ -6079,17 +6106,16 @@ function OPSGROUP:RouteToMission(mission, delay)
|
||||
-- Target Coord.
|
||||
local targetcoord=mission:GetTargetCoordinate()
|
||||
|
||||
|
||||
-- In range already?
|
||||
local inRange=self:InWeaponRange(targetcoord, mission.engageWeaponType)
|
||||
local inRange=self:InWeaponRange(targetcoord, mission.engageWeaponType, waypointcoord)
|
||||
|
||||
if inRange then
|
||||
|
||||
waypointcoord=self:GetCoordinate(true)
|
||||
--waypointcoord=self:GetCoordinate(true)
|
||||
|
||||
else
|
||||
|
||||
local coordInRange=self:GetCoordinateInRange(targetcoord, mission.engageWeaponType, waypointcoord)
|
||||
local coordInRange=self:GetCoordinateInRange(targetcoord, mission.engageWeaponType, waypointcoord, surfacetypes)
|
||||
|
||||
if coordInRange then
|
||||
|
||||
@@ -6124,7 +6150,32 @@ function OPSGROUP:RouteToMission(mission, delay)
|
||||
-- Add mission execution (ingress) waypoint.
|
||||
local waypoint=nil --#OPSGROUP.Waypoint
|
||||
if self:IsFlightgroup() then
|
||||
|
||||
|
||||
|
||||
local ingresscoord = mission:GetMissionIngressCoord()
|
||||
local holdingcoord = mission:GetMissionHoldingCoord()
|
||||
|
||||
if holdingcoord then
|
||||
waypoint=FLIGHTGROUP.AddWaypoint(self, holdingcoord, mission.missionHoldingCoordSpeed or SpeedToMission, uid, UTILS.MetersToFeet(mission.missionHoldingCoordAlt or self.altitudeCruise), false)
|
||||
uid=waypoint.uid
|
||||
-- Orbit until flaghold=1 (true) but max 5 min
|
||||
self.flaghold:Set(0)
|
||||
local TaskOrbit = self.group:TaskOrbit(holdingcoord, mission.missionHoldingCoordAlt, UTILS.KnotsToMps(mission.missionHoldingCoordSpeed or SpeedToMission))
|
||||
local TaskStop = self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1, nil, mission.missionHoldingDuration or 900)
|
||||
local TaskCntr = self.group:TaskControlled(TaskOrbit, TaskStop)
|
||||
local TaskOver = self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting", self)
|
||||
local DCSTasks=self.group:TaskCombo({TaskCntr, TaskOver})
|
||||
-- Add waypoint task. UpdateRoute is called inside.
|
||||
local waypointtask=self:AddTaskWaypoint(DCSTasks, waypoint, "Holding")
|
||||
waypointtask.ismission=false
|
||||
self.isHoldingAtHoldingPoint = true
|
||||
end
|
||||
|
||||
if ingresscoord then
|
||||
waypoint=FLIGHTGROUP.AddWaypoint(self, ingresscoord, mission.missionIngressCoordSpeed or SpeedToMission, uid, UTILS.MetersToFeet(mission.missionIngressCoordAlt or self.altitudeCruise), false)
|
||||
uid=waypoint.uid
|
||||
end
|
||||
|
||||
waypoint=FLIGHTGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false)
|
||||
|
||||
elseif self:IsArmygroup() then
|
||||
@@ -6163,7 +6214,7 @@ function OPSGROUP:RouteToMission(mission, delay)
|
||||
if egresscoord then
|
||||
local Ewaypoint=nil --#OPSGROUP.Waypoint
|
||||
if self:IsFlightgroup() then
|
||||
Ewaypoint=FLIGHTGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false)
|
||||
Ewaypoint=FLIGHTGROUP.AddWaypoint(self, egresscoord, mission.missionEgressCoordSpeed or SpeedToMission, waypoint.uid, UTILS.MetersToFeet(mission.missionEgressCoordAlt or self.altitudeCruise), false)
|
||||
elseif self:IsArmygroup() then
|
||||
Ewaypoint=ARMYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, mission.optionFormation, false)
|
||||
elseif self:IsNavygroup() then
|
||||
@@ -7681,6 +7732,7 @@ function OPSGROUP:Teleport(Coordinate, Delay, NoPauseMission)
|
||||
unit.heading=math.rad(heading)
|
||||
unit.psi=-unit.heading
|
||||
else
|
||||
-- Remove unit from spawn template because it is already dead
|
||||
table.remove(units, i)
|
||||
end
|
||||
end
|
||||
@@ -7768,25 +7820,41 @@ function OPSGROUP:_Respawn(Delay, Template, Reset)
|
||||
-- Despawn old group. Dont trigger any remove unit event since this is a respawn.
|
||||
self:Despawn(0, true)
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Group is NOT ALIVE
|
||||
---
|
||||
|
||||
-- Ensure elements in utero.
|
||||
for _,_element in pairs(self.elements) do
|
||||
local element=_element --#OPSGROUP.Element
|
||||
self:ElementInUtero(element)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Ensure elements in utero.
|
||||
for _,_element in pairs(self.elements) do
|
||||
local element=_element --#OPSGROUP.Element
|
||||
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
|
||||
self:ElementInUtero(element)
|
||||
end
|
||||
end
|
||||
|
||||
-- Spawn with a little delay (especially Navy groups caused problems if they were instantly respawned)
|
||||
self:_Spawn(0.01, Template)
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Spawn group from a given template.
|
||||
-- @param #OPSGROUP self
|
||||
-- @param #number Delay Delay in seconds before respawn happens. Default 0.
|
||||
-- @param DCS#Template Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself.
|
||||
-- @return #OPSGROUP self
|
||||
function OPSGROUP:_Spawn(Delay, Template)
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, OPSGROUP._Spawn, self, 0, Template)
|
||||
else
|
||||
-- Debug output.
|
||||
self:T({Template=Template})
|
||||
self:T2({Template=Template})
|
||||
|
||||
-- Spawn new group.
|
||||
self.group=_DATABASE:Spawn(Template)
|
||||
--local countryID=self.group:GetCountry()
|
||||
--local categoryID=self.group:GetCategory()
|
||||
--local dcsgroup=coalition.addGroup(countryID, categoryID, Template)
|
||||
|
||||
-- Set DCS group and controller.
|
||||
self.dcsgroup=self:GetDCSGroup()
|
||||
@@ -7800,7 +7868,6 @@ function OPSGROUP:_Respawn(Delay, Template, Reset)
|
||||
self.isDead=false
|
||||
self.isDestroyed=false
|
||||
|
||||
|
||||
self.groupinitialized=false
|
||||
self.wpcounter=1
|
||||
self.currentwp=1
|
||||
@@ -7808,15 +7875,12 @@ function OPSGROUP:_Respawn(Delay, Template, Reset)
|
||||
-- Init waypoints.
|
||||
self:_InitWaypoints()
|
||||
|
||||
-- Init Group.
|
||||
self:_InitGroup(Template)
|
||||
-- Init Group. This call is delayed because NAVY groups did not like to be initialized just yet (group did not contain any units).
|
||||
self:_InitGroup(Template, 0.001)
|
||||
|
||||
-- Reset events.
|
||||
--self:ResetEvents()
|
||||
|
||||
--self:ResetEvents()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- On after "InUtero" event.
|
||||
@@ -7836,24 +7900,6 @@ end
|
||||
-- @param #string To To state.
|
||||
function OPSGROUP:onafterDamaged(From, Event, To)
|
||||
self:T(self.lid..string.format("Group damaged at t=%.3f", timer.getTime()))
|
||||
|
||||
--[[
|
||||
local lifemin=nil
|
||||
for _,_element in pairs(self.elements) do
|
||||
local element=_element --#OPSGROUP.Element
|
||||
if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then
|
||||
local life, life0=self:GetLifePoints(element)
|
||||
if lifemin==nil or life<lifemin then
|
||||
lifemin=life
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if lifemin and lifemin/self.life<0.5 then
|
||||
self:RTB()
|
||||
end
|
||||
]]
|
||||
|
||||
end
|
||||
|
||||
--- On after "Destroyed" event.
|
||||
@@ -9005,7 +9051,7 @@ function OPSGROUP:AddWeightCargo(UnitName, Weight)
|
||||
self:T(self.lid..string.format("%s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg", UnitName, Weight, element.weightCargo))
|
||||
|
||||
-- For airborne units, we set the weight in game.
|
||||
if self.isFlightgroup then
|
||||
if self.isFlightgroup and element.unit and element.unit:IsAlive() then -- #2272 trying to deduct cargo weight from possibly dead units
|
||||
trigger.action.setUnitInternalCargo(element.name, element.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo
|
||||
end
|
||||
|
||||
@@ -11450,10 +11496,10 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax)
|
||||
if self:IsFlightgroup() then
|
||||
|
||||
-- Get home and destination airbases from waypoints.
|
||||
self.homebase=self.homebase or self:GetHomebaseFromWaypoints()
|
||||
self.homebase=self.homebase or self:GetHomebaseFromWaypoints() -- GetHomebaseFromWaypoints() returns carriers or destroyers if no airbase is found.
|
||||
local destbase=self:GetDestinationFromWaypoints()
|
||||
self.destbase=self.destbase or destbase
|
||||
self.currbase=self:GetHomebaseFromWaypoints()
|
||||
self.currbase=self:GetHomebaseFromWaypoints() -- Skipped To fix RTB issue
|
||||
|
||||
--env.info("FF home base "..(self.homebase and self.homebase:GetName() or "unknown"))
|
||||
--env.info("FF dest base "..(self.destbase and self.destbase:GetName() or "unknown"))
|
||||
@@ -11464,7 +11510,7 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax)
|
||||
end
|
||||
|
||||
-- Set destination to homebase.
|
||||
if self.destbase==nil then
|
||||
if self.destbase==nil then -- Skipped To fix RTB issue
|
||||
self.destbase=self.homebase
|
||||
end
|
||||
|
||||
|
||||
@@ -490,6 +490,19 @@ function OPSZONE:SetDrawZone(Switch)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set if zone is drawn on the F10 map for the owner coalition only.
|
||||
-- @param #OPSZONE self
|
||||
-- @param #boolean Switch If `false` or `nil`, draw zone for all coalitions. If `true`, zone is drawn for the owning coalition only if drawZone is true.
|
||||
-- @return #OPSZONE self
|
||||
function OPSZONE:SetDrawZoneForCoalition(Switch)
|
||||
if Switch==true then
|
||||
self.drawZoneForCoalition=true
|
||||
else
|
||||
self.drawZoneForCoalition=false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set if a marker on the F10 map shows the current zone status.
|
||||
-- @param #OPSZONE self
|
||||
-- @param #boolean Switch If `true`, zone is marked. If `false` or `nil`, zone is not marked.
|
||||
@@ -710,6 +723,7 @@ end
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @return #OPSZONE self
|
||||
function OPSZONE:onafterStart(From, Event, To)
|
||||
|
||||
-- Info.
|
||||
@@ -726,6 +740,7 @@ function OPSZONE:onafterStart(From, Event, To)
|
||||
self:HandleEvent(EVENTS.BaseCaptured)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stop OPSZONE FSM.
|
||||
@@ -837,8 +852,12 @@ function OPSZONE:onafterCaptured(From, Event, To, NewOwnerCoalition)
|
||||
self.zone:UndrawZone()
|
||||
|
||||
local color=self:_GetZoneColor()
|
||||
|
||||
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
|
||||
|
||||
local coalition = nil
|
||||
if self.drawZoneForCoalition then
|
||||
coalition = self.ownerCurrent
|
||||
end
|
||||
self.zone:DrawZone(coalition, color, 1.0, color, 0.5)
|
||||
end
|
||||
|
||||
for _,_chief in pairs(self.chiefs) do
|
||||
@@ -913,8 +932,12 @@ function OPSZONE:onenterGuarded(From, Event, To)
|
||||
self.zone:UndrawZone()
|
||||
|
||||
local color=self:_GetZoneColor()
|
||||
|
||||
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
|
||||
|
||||
local coalition = nil
|
||||
if self.drawZoneForCoalition then
|
||||
coalition = self.ownerCurrent
|
||||
end
|
||||
self.zone:DrawZone(coalition, color, 1.0, color, 0.5)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -954,9 +977,13 @@ function OPSZONE:onenterAttacked(From, Event, To, AttackerCoalition)
|
||||
|
||||
-- Color.
|
||||
local color={1, 204/255, 204/255}
|
||||
|
||||
|
||||
local coalition = nil
|
||||
if self.drawZoneForCoalition then
|
||||
coalition = self.ownerCurrent
|
||||
end
|
||||
-- Draw zone.
|
||||
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
|
||||
self.zone:DrawZone(coalition, color, 1.0, color, 0.5)
|
||||
end
|
||||
|
||||
self:_CleanMissionTable()
|
||||
@@ -987,8 +1014,12 @@ function OPSZONE:onenterEmpty(From, Event, To)
|
||||
self.zone:UndrawZone()
|
||||
|
||||
local color=self:_GetZoneColor()
|
||||
|
||||
self.zone:DrawZone(nil, color, 1.0, color, 0.2)
|
||||
|
||||
local coalition = nil
|
||||
if self.drawZoneForCoalition then
|
||||
coalition = self.ownerCurrent
|
||||
end
|
||||
self.zone:DrawZone(coalition, color, 1.0, color, 0.2)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1285,7 +1316,7 @@ function OPSZONE:EvaluateZone()
|
||||
|
||||
if Nblu>0 then
|
||||
|
||||
if not self:IsAttacked() and self.Tnut>=self.threatlevelCapture then
|
||||
if not self:IsAttacked() and self.Tblu>=self.threatlevelCapture then
|
||||
self:Attacked(coalition.side.BLUE)
|
||||
end
|
||||
|
||||
@@ -1337,7 +1368,7 @@ function OPSZONE:EvaluateZone()
|
||||
|
||||
if Nred>0 then
|
||||
|
||||
if not self:IsAttacked() and self.Tnut>=self.threatlevelCapture then
|
||||
if not self:IsAttacked() and self.Tred>=self.threatlevelCapture then
|
||||
-- Red is attacking blue zone.
|
||||
self:Attacked(coalition.side.RED)
|
||||
end
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
-- @field #boolean smokeownposition
|
||||
-- @field #table SmokeOwn
|
||||
-- @field #boolean smokeaveragetargetpos
|
||||
-- @field #boolean reporttostringbullsonly
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
---
|
||||
@@ -105,7 +106,7 @@ PLAYERRECCE = {
|
||||
ClassName = "PLAYERRECCE",
|
||||
verbose = true,
|
||||
lid = nil,
|
||||
version = "0.1.23",
|
||||
version = "0.1.26",
|
||||
ViewZone = {},
|
||||
ViewZoneVisual = {},
|
||||
ViewZoneLaser = {},
|
||||
@@ -133,7 +134,8 @@ PLAYERRECCE = {
|
||||
TargetCache = nil,
|
||||
smokeownposition = false,
|
||||
SmokeOwn = {},
|
||||
smokeaveragetargetpos = false,
|
||||
smokeaveragetargetpos = true,
|
||||
reporttostringbullsonly = true,
|
||||
}
|
||||
|
||||
---
|
||||
@@ -152,7 +154,8 @@ PLAYERRECCE.LaserRelativePos = {
|
||||
["SA342Minigun"] = { x = 1.7, y = 1.2, z = 0 },
|
||||
["SA342L"] = { x = 1.7, y = 1.2, z = 0 },
|
||||
["Ka-50"] = { x = 6.1, y = -0.85 , z = 0 },
|
||||
["Ka-50_3"] = { x = 6.1, y = -0.85 , z = 0 }
|
||||
["Ka-50_3"] = { x = 6.1, y = -0.85 , z = 0 },
|
||||
["OH58D"] = {x = 0, y = 2.8, z = 0},
|
||||
}
|
||||
|
||||
---
|
||||
@@ -164,7 +167,8 @@ PLAYERRECCE.MaxViewDistance = {
|
||||
["SA342Minigun"] = 8000,
|
||||
["SA342L"] = 8000,
|
||||
["Ka-50"] = 8000,
|
||||
["Ka-50_3"] = 8000,
|
||||
["Ka-50_3"] = 8000,
|
||||
["OH58D"] = 8000,
|
||||
}
|
||||
|
||||
---
|
||||
@@ -176,7 +180,8 @@ PLAYERRECCE.Cameraheight = {
|
||||
["SA342Minigun"] = 2.85,
|
||||
["SA342L"] = 2.85,
|
||||
["Ka-50"] = 0.5,
|
||||
["Ka-50_3"] = 0.5,
|
||||
["Ka-50_3"] = 0.5,
|
||||
["OH58D"] = 4.25,
|
||||
}
|
||||
|
||||
---
|
||||
@@ -188,7 +193,8 @@ PLAYERRECCE.CanLase = {
|
||||
["SA342Minigun"] = false, -- no optics
|
||||
["SA342L"] = true,
|
||||
["Ka-50"] = true,
|
||||
["Ka-50_3"] = true,
|
||||
["Ka-50_3"] = true,
|
||||
["OH58D"] = false, -- has onboard and useable laser
|
||||
}
|
||||
|
||||
---
|
||||
@@ -236,6 +242,8 @@ function PLAYERRECCE:New(Name, Coalition, PlayerSet)
|
||||
|
||||
self.minthreatlevel = 0
|
||||
|
||||
self.reporttostringbullsonly = true
|
||||
|
||||
self.TForget = 600
|
||||
self.TargetCache = FIFO:New()
|
||||
|
||||
@@ -542,7 +550,7 @@ function PLAYERRECCE:SetAttackSet(AttackSet)
|
||||
return self
|
||||
end
|
||||
|
||||
---[Internal] Check Gazelle camera in on
|
||||
---[Internal] Check Helicopter camera in on
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @param Wrapper.Client#CLIENT client
|
||||
-- @param #string playername
|
||||
@@ -558,6 +566,12 @@ function PLAYERRECCE:_CameraOn(client,playername)
|
||||
if vivihorizontal < -0.7 or vivihorizontal > 0.7 then
|
||||
camera = false
|
||||
end
|
||||
elseif string.find(typename,"OH58") then
|
||||
local dcsunit = Unit.getByName(client:GetName())
|
||||
local vivihorizontal = dcsunit:getDrawArgumentValue(528) or 0 -- Kiow
|
||||
if vivihorizontal < -0.527 or vivihorizontal > 0.527 then
|
||||
camera = false
|
||||
end
|
||||
elseif string.find(typename,"Ka-50") then
|
||||
camera = true
|
||||
end
|
||||
@@ -565,6 +579,52 @@ function PLAYERRECCE:_CameraOn(client,playername)
|
||||
return camera
|
||||
end
|
||||
|
||||
--- [Internal] Get the view parameters from a Kiowa MMS camera
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @param Wrapper.Unit#UNIT Kiowa
|
||||
-- @return #number cameraheading in degrees.
|
||||
-- @return #number cameranodding in degrees.
|
||||
-- @return #number maxview in meters.
|
||||
-- @return #boolean cameraison If true, camera is on, else off.
|
||||
function PLAYERRECCE:_GetKiowaMMSSight(Kiowa)
|
||||
self:T(self.lid.."_GetKiowaMMSSight")
|
||||
local unit = Kiowa -- Wrapper.Unit#UNIT
|
||||
if unit and unit:IsAlive() then
|
||||
local dcsunit = Unit.getByName(Kiowa:GetName())
|
||||
--[[
|
||||
shagrat — 01/01/2025 23:13
|
||||
Found the necessary ARGS for the Kiowa MMS angle and rotation:
|
||||
Arg 527 vertical movement
|
||||
0 = neutral
|
||||
-1.0 = max depression (30° max depression angle)
|
||||
+1.0 = max elevation angle (30° max elevation angle)
|
||||
|
||||
Arg 528 horizontal movement
|
||||
0 = forward (0 degr)
|
||||
-0.25 = 90° left
|
||||
-0.5 = rear (180°) left (max 190° = -0.527
|
||||
+0.25 = 90° right
|
||||
+0.5 = 180° right (max 190° = 0.527)
|
||||
--]]
|
||||
local mmshorizontal = dcsunit:getDrawArgumentValue(528) or 0
|
||||
local mmsvertical = dcsunit:getDrawArgumentValue(527) or 0
|
||||
self:T(string.format("Kiowa MMS Arguments Read: H %.3f V %.3f",mmshorizontal,mmsvertical))
|
||||
local mmson = true
|
||||
if mmshorizontal < -0.527 or mmshorizontal > 0.527 then mmson = false end
|
||||
local horizontalview = mmshorizontal / 0.527 * 190
|
||||
local heading = unit:GetHeading()
|
||||
local mmsheading = (heading+horizontalview)%360
|
||||
--local mmsyaw = mmsvertical * 30
|
||||
local mmsyaw = math.atan(mmsvertical)*40
|
||||
local maxview = self:_GetActualMaxLOSight(unit,mmsheading, mmsyaw,not mmson)
|
||||
if maxview > 8000 then maxview = 8000 end
|
||||
self:T(string.format("Kiowa MMS Heading %d, Yaw %d, MaxView %dm MMS On %s",mmsheading,mmsyaw,maxview,tostring(mmson)))
|
||||
return mmsheading,mmsyaw,maxview,mmson
|
||||
end
|
||||
return 0,0,0,false
|
||||
end
|
||||
|
||||
|
||||
--- [Internal] Get the view parameters from a Gazelle camera
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @param Wrapper.Unit#UNIT Gazelle
|
||||
@@ -593,40 +653,15 @@ function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle)
|
||||
vivioff = true
|
||||
return 0,0,0,false
|
||||
end
|
||||
vivivertical = vivivertical / 1.10731 -- normalize
|
||||
|
||||
local horizontalview = vivihorizontal * -180
|
||||
local verticalview = vivivertical * 30 -- ca +/- 30°
|
||||
--self:I(string.format("vivihorizontal=%.5f | vivivertical=%.5f",vivihorizontal,vivivertical))
|
||||
--self:I(string.format("horizontal=%.5f | vertical=%.5f",horizontalview,verticalview))
|
||||
--local verticalview = vivivertical * 30 -- ca +/- 30°
|
||||
local verticalview = math.atan(vivivertical)
|
||||
|
||||
local heading = unit:GetHeading()
|
||||
local viviheading = (heading+horizontalview)%360
|
||||
local maxview = self:_GetActualMaxLOSight(unit,viviheading, verticalview,vivioff)
|
||||
--self:I(string.format("maxview=%.5f",maxview))
|
||||
-- visual skew
|
||||
local factor = 3.15
|
||||
self.GazelleViewFactors = {
|
||||
[1]=1.18,
|
||||
[2]=1.32,
|
||||
[3]=1.46,
|
||||
[4]=1.62,
|
||||
[5]=1.77,
|
||||
[6]=1.85,
|
||||
[7]=2.05,
|
||||
[8]=2.05,
|
||||
[9]=2.3,
|
||||
[10]=2.3,
|
||||
[11]=2.27,
|
||||
[12]=2.27,
|
||||
[13]=2.43,
|
||||
}
|
||||
local lfac = UTILS.Round(maxview,-2)
|
||||
if lfac <= 1300 then
|
||||
--factor = self.GazelleViewFactors[lfac/100]
|
||||
factor = 3.15
|
||||
maxview = math.ceil((maxview*factor)/100)*100
|
||||
end
|
||||
if maxview > 8000 then maxview = 8000 end
|
||||
--self:I(string.format("corrected maxview=%.5f",maxview))
|
||||
return viviheading, verticalview,maxview, not vivioff
|
||||
end
|
||||
return 0,0,0,false
|
||||
@@ -647,20 +682,20 @@ function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading, vnod, vivoff)
|
||||
if unit and unit:IsAlive() then
|
||||
local typename = unit:GetTypeName()
|
||||
maxview = self.MaxViewDistance[typename] or 8000
|
||||
local CamHeight = self.Cameraheight[typename] or 0
|
||||
if vnod < 0 then
|
||||
local CamHeight = self.Cameraheight[typename] or 1
|
||||
if vnod < -2 then
|
||||
-- Looking down
|
||||
-- determine max distance we're looking at
|
||||
local beta = 90
|
||||
local gamma = math.floor(90-vnod)
|
||||
local alpha = math.floor(180-beta-gamma)
|
||||
local gamma = 90-math.abs(vnod)
|
||||
local alpha = 90-gamma
|
||||
local a = unit:GetHeight()-unit:GetCoordinate():GetLandHeight()+CamHeight
|
||||
local b = a / math.sin(math.rad(alpha))
|
||||
local c = b * math.sin(math.rad(gamma))
|
||||
maxview = c*1.2 -- +20%
|
||||
end
|
||||
end
|
||||
return math.abs(maxview)
|
||||
return math.ceil(math.abs(maxview))
|
||||
end
|
||||
|
||||
--- [User] Set callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns.
|
||||
@@ -669,8 +704,10 @@ end
|
||||
-- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers.
|
||||
-- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized
|
||||
-- callsigns from playername or group name.
|
||||
-- @param #func CallsignCustomFunc (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first two arguments.
|
||||
-- @param #arg ... (Optional) Comma separated arguments to add to the custom function call after groupname and playername.
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
|
||||
function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...)
|
||||
if not ShortCallsign or ShortCallsign == false then
|
||||
self.ShortCallsign = false
|
||||
else
|
||||
@@ -678,6 +715,8 @@ function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTransla
|
||||
end
|
||||
self.Keepnumber = Keepnumber or false
|
||||
self.CallsignTranslations = CallsignTranslations
|
||||
self.CallsignCustomFunc = CallsignCustomFunc
|
||||
self.CallsignCustomArgs = arg or {}
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -799,7 +838,7 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser)
|
||||
local minview = 0
|
||||
local typename = unit:GetTypeName()
|
||||
local playername = unit:GetPlayerName()
|
||||
local maxview = self.MaxViewDistance[typename] or 5000
|
||||
local maxview = self.MaxViewDistance[typename] or 8000
|
||||
local heading,nod,maxview,angle = 0,30,8000,10
|
||||
local camon = false
|
||||
local name = unit:GetName()
|
||||
@@ -807,16 +846,25 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser)
|
||||
heading,nod,maxview,camon = self:_GetGazelleVivianneSight(unit)
|
||||
angle=10
|
||||
-- Model nod and actual TV view don't compute
|
||||
maxview = self.MaxViewDistance[typename] or 5000
|
||||
maxview = self.MaxViewDistance[typename] or 8000
|
||||
elseif string.find(typename,"Ka-50") and camera then
|
||||
heading = unit:GetHeading()
|
||||
nod,maxview,camon = 10,1000,true
|
||||
angle = 10
|
||||
maxview = self.MaxViewDistance[typename] or 5000
|
||||
maxview = self.MaxViewDistance[typename] or 8000
|
||||
elseif string.find(typename,"OH58") and camera then
|
||||
--heading = unit:GetHeading()
|
||||
nod,maxview,camon = 0,8000,true
|
||||
heading,nod,maxview,camon = self:_GetKiowaMMSSight(unit)
|
||||
angle = 8
|
||||
if maxview == 0 then
|
||||
maxview = self.MaxViewDistance[typename] or 8000
|
||||
end
|
||||
else
|
||||
-- visual
|
||||
heading = unit:GetHeading()
|
||||
nod,maxview,camon = 10,1000,true
|
||||
nod,maxview,camon = 10,3000,true
|
||||
maxview = self.MaxViewDistance[typename] or 3000
|
||||
angle = 45
|
||||
end
|
||||
if laser then
|
||||
@@ -929,7 +977,8 @@ function PLAYERRECCE:_LaseTarget(client,targetset)
|
||||
if (not oldtarget) or targetset:IsNotInSet(oldtarget) or target:IsDead() or target:IsDestroyed() then
|
||||
-- lost LOS or dead
|
||||
laser:LaseOff()
|
||||
if target:IsDead() or target:IsDestroyed() or target:GetLife() < 2 then
|
||||
self:T(self.lid.."Target Life Points: "..target:GetLife() or "none")
|
||||
if target:IsDead() or target:IsDestroyed() or target:GetDamage() > 79 or target:GetLife() <= 1 then
|
||||
self:__Shack(-1,client,oldtarget)
|
||||
--self.LaserTarget[playername] = nil
|
||||
else
|
||||
@@ -1274,6 +1323,9 @@ self:T(self.lid.."_ReportLaserTargets")
|
||||
report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")")
|
||||
if not self.ReferencePoint then
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings))
|
||||
if self.reporttostringbullsonly ~= true then
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings))
|
||||
end
|
||||
else
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings))
|
||||
end
|
||||
@@ -1317,8 +1369,14 @@ function PLAYERRECCE:_ReportVisualTargets(client,group,playername)
|
||||
report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")")
|
||||
if not self.ReferencePoint then
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings))
|
||||
if self.reporttostringbullsonly ~= true then
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings))
|
||||
end
|
||||
else
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings))
|
||||
if self.reporttostringbullsonly ~= true then
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings))
|
||||
end
|
||||
end
|
||||
report:Add(string.rep("-",15))
|
||||
local text = report:Text()
|
||||
@@ -1347,6 +1405,7 @@ function PLAYERRECCE:_BuildMenus(Client)
|
||||
local client = _client -- Wrapper.Client#CLIENT
|
||||
if client and client:IsAlive() then
|
||||
local playername = client:GetPlayerName()
|
||||
self:T("Menu for "..playername)
|
||||
if not self.UnitLaserCodes[playername] then
|
||||
self:_SetClientLaserCode(nil,nil,playername,1688)
|
||||
end
|
||||
@@ -1355,6 +1414,7 @@ function PLAYERRECCE:_BuildMenus(Client)
|
||||
end
|
||||
local group = client:GetGroup()
|
||||
if not self.ClientMenus[playername] then
|
||||
self:T("Start Menubuild for "..playername)
|
||||
local canlase = self.CanLase[client:GetTypeName()]
|
||||
self.ClientMenus[playername] = MENU_GROUP:New(group,self.MenuName or self.Name or "RECCE")
|
||||
local txtonstation = self.OnStation[playername] and "ON" or "OFF"
|
||||
@@ -1492,8 +1552,9 @@ end
|
||||
-- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS.
|
||||
-- @param #number Volume (Optional) Volume - between 0.0 (silent) and 1.0 (loudest)
|
||||
-- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS
|
||||
-- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey)
|
||||
function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend)
|
||||
self:T(self.lid.."SetSRS")
|
||||
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" --
|
||||
self.Gender = Gender or MSRS.gender or "male" --
|
||||
@@ -1515,6 +1576,9 @@ function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,V
|
||||
self.SRS:SetCulture(self.Culture)
|
||||
self.SRS:SetPort(self.Port)
|
||||
self.SRS:SetVolume(self.Volume)
|
||||
if Backend then
|
||||
self.SRS:SetBackend(Backend)
|
||||
end
|
||||
if self.PathToGoogleKey then
|
||||
self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey)
|
||||
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
|
||||
@@ -1552,6 +1616,16 @@ function PLAYERRECCE:SetMenuName(Name)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set reporting to be BULLS only or BULLS plus playersettings based coordinate.
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @param #boolean OnOff
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:SetReportBullsOnly(OnOff)
|
||||
self:T(self.lid.."SetReportBullsOnly: "..tostring(OnOff))
|
||||
self.reporttostringbullsonly = OnOff
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Enable smoking of own position
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @return #PLAYERRECCE self
|
||||
@@ -1561,6 +1635,15 @@ function PLAYERRECCE:EnableSmokeOwnPosition()
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Enable auto lasing for the Kiowa OH-58D.
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:EnableKiowaAutolase()
|
||||
self:T(self.lid.."EnableKiowaAutolase")
|
||||
self.CanLase.OH58D = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Disable smoking of own position
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @return #PLAYERRECCE
|
||||
@@ -1718,7 +1801,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition)
|
||||
if self.ReferencePoint then
|
||||
@@ -1727,7 +1810,7 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername)
|
||||
end
|
||||
local text1 = "Party time!"
|
||||
local text2 = string.format("All stations, FACA %s on station\nat %s!",callsign, coordtext)
|
||||
local text2tts = string.format("All stations, FACA %s on station at %s!",callsign, coordtext)
|
||||
local text2tts = string.format(" All stations, FACA %s on station at %s!",callsign, coordtext)
|
||||
text2tts = self:_GetTextForSpeech(text2tts)
|
||||
if self.debug then
|
||||
self:T(text2.."\n"..text2tts)
|
||||
@@ -1758,7 +1841,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition)
|
||||
if self.ReferencePoint then
|
||||
@@ -1898,7 +1981,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterIllumination(From, Event, To, Client, Playername, TargetSet)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition)
|
||||
if self.AttackSet then
|
||||
@@ -1941,7 +2024,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterTargetsSmoked(From, Event, To, Client, Playername, TargetSet)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition)
|
||||
if self.AttackSet then
|
||||
@@ -1984,7 +2067,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterTargetsFlared(From, Event, To, Client, Playername, TargetSet)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition)
|
||||
if self.AttackSet then
|
||||
@@ -2028,7 +2111,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Lasercode, Lasingtime)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition,Settings)
|
||||
@@ -2075,7 +2158,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterShack(From, Event, To, Client, Target)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition,Settings)
|
||||
@@ -2122,7 +2205,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterTargetLOSLost(From, Event, To, Client, Target)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition,Settings)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -660,9 +660,9 @@ function RECOVERYTANKER:SetRecoveryAirboss(switch)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set that the group takes the roll of an AWACS instead of a refueling tanker.
|
||||
--- Set that the group takes the role of an AWACS instead of a refueling tanker.
|
||||
-- @param #RECOVERYTANKER self
|
||||
-- @param #boolean switch If true or nil, set roll AWACS.
|
||||
-- @param #boolean switch If true or nil, set role AWACS.
|
||||
-- @param #boolean eplrs If true or nil, enable EPLRS. If false, EPLRS will be off.
|
||||
-- @return #RECOVERYTANKER self
|
||||
function RECOVERYTANKER:SetAWACS(switch, eplrs)
|
||||
@@ -997,6 +997,8 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
|
||||
|
||||
-- Init status updates in 10 seconds.
|
||||
self:__Status(10)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -963,6 +963,8 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
||||
|
||||
-- Init status check
|
||||
self:__Status(1)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- On after Status event. Checks player status.
|
||||
|
||||
@@ -153,13 +153,13 @@ _TARGETID=0
|
||||
|
||||
--- TARGET class version.
|
||||
-- @field #string version
|
||||
TARGET.version="0.6.0"
|
||||
TARGET.version="0.7.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Had cases where target life was 0 but target was not dead. Need to figure out why!
|
||||
-- DONE: Had cases where target life was 0 but target was not dead. Need to figure out why! <== This is due to delayed dead event.
|
||||
-- DONE: Add pseudo functions.
|
||||
-- DONE: Initial object can be nil.
|
||||
|
||||
@@ -243,6 +243,36 @@ function TARGET:New(TargetObject)
|
||||
-- @function [parent=#TARGET] __Status
|
||||
-- @param #TARGET self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
|
||||
--- Triggers the FSM event "ObjectDamaged".
|
||||
-- @function [parent=#TARGET] ObjectDamaged
|
||||
-- @param #TARGET self
|
||||
-- @param #TARGET.Object Target Target object.
|
||||
|
||||
--- Triggers the FSM event "ObjectDestroyed".
|
||||
-- @function [parent=#TARGET] ObjectDestroyed
|
||||
-- @param #TARGET self
|
||||
-- @param #TARGET.Object Target Target object.
|
||||
|
||||
--- Triggers the FSM event "ObjectDead".
|
||||
-- @function [parent=#TARGET] ObjectDead
|
||||
-- @param #TARGET self
|
||||
-- @param #TARGET.Object Target Target object.
|
||||
|
||||
|
||||
--- Triggers the FSM event "Damaged".
|
||||
-- @function [parent=#TARGET] Damaged
|
||||
-- @param #TARGET self
|
||||
|
||||
--- Triggers the FSM event "Destroyed".
|
||||
-- @function [parent=#TARGET] Destroyed
|
||||
-- @param #TARGET self
|
||||
|
||||
--- Triggers the FSM event "Dead".
|
||||
-- @function [parent=#TARGET] Dead
|
||||
-- @param #TARGET self
|
||||
|
||||
|
||||
--- On After "ObjectDamaged" event. A (sub-) target object has been damaged, e.g. a UNIT of a GROUP, or an object of a SET
|
||||
-- @function [parent=#TARGET] OnAfterObjectDamaged
|
||||
@@ -267,22 +297,23 @@ function TARGET:New(TargetObject)
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #TARGET.Object Target Target object.
|
||||
|
||||
|
||||
--- On After "Damaged" event. The (whole) target object has been damaged.
|
||||
--- On After "Damaged" event. Any of the target objects has been damaged.
|
||||
-- @function [parent=#TARGET] OnAfterDamaged
|
||||
-- @param #TARGET self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On After "ObjectDestroyed" event. The (whole) target object has been destroyed.
|
||||
--- On After "Destroyed" event. All target objects have been destroyed.
|
||||
-- @function [parent=#TARGET] OnAfterDestroyed
|
||||
-- @param #TARGET self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On After "ObjectDead" event. The (whole) target object is dead.
|
||||
--- On After "Dead" event. All target objects are dead.
|
||||
-- @function [parent=#TARGET] OnAfterDead
|
||||
-- @param #TARGET self
|
||||
-- @param #string From From state.
|
||||
@@ -290,7 +321,7 @@ function TARGET:New(TargetObject)
|
||||
-- @param #string To To state.
|
||||
|
||||
-- Start.
|
||||
self:__Start(-1)
|
||||
self:__Start(-0.1)
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -356,6 +387,8 @@ function TARGET:AddObject(Object)
|
||||
|
||||
if Object:IsInstanceOf("OPSGROUP") then
|
||||
self:_AddObject(Object:GetGroup()) -- We add the MOOSE GROUP object not the OPSGROUP object.
|
||||
--elseif Object:IsInstanceOf("OPSZONE") then
|
||||
--self:_AddObject(Object:GetZone())
|
||||
else
|
||||
self:_AddObject(Object)
|
||||
end
|
||||
@@ -527,6 +560,11 @@ function TARGET:IsAlive()
|
||||
for _,_target in pairs(self.targets) do
|
||||
local target=_target --Ops.Target#TARGET.Object
|
||||
if target.Status~=TARGET.ObjectStatus.DEAD then
|
||||
if self.isDestroyed then
|
||||
self:E(self.lid..string.format("ERROR: target is DESTROYED but target object status is not DEAD but %s for object %s", target.Status, target.Name))
|
||||
elseif self:IsDead() then
|
||||
self:E(self.lid..string.format("ERROR: target is DEAD but target object status is not DEAD but %s for object %s", target.Status, target.Name))
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
@@ -550,6 +588,25 @@ function TARGET:IsDead()
|
||||
return is
|
||||
end
|
||||
|
||||
--- Check if target object is dead.
|
||||
-- @param #TARGET self
|
||||
-- @param #TARGET.Object TargetObject The target object.
|
||||
-- @return #boolean If true, target is dead.
|
||||
function TARGET:IsTargetDead(TargetObject)
|
||||
local isDead=TargetObject.Status==TARGET.ObjectStatus.DEAD
|
||||
return isDead
|
||||
end
|
||||
|
||||
--- Check if target object is alive.
|
||||
-- @param #TARGET self
|
||||
-- @param #TARGET.Object TargetObject The target object.
|
||||
-- @return #boolean If true, target is dead.
|
||||
function TARGET:IsTargetAlive(TargetObject)
|
||||
local isAlive=TargetObject.Status==TARGET.ObjectStatus.ALIVE
|
||||
return isAlive
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -581,7 +638,8 @@ end
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function TARGET:onafterStatus(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
--self:T({From, Event, To})
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
@@ -592,6 +650,7 @@ function TARGET:onafterStatus(From, Event, To)
|
||||
|
||||
-- old life
|
||||
local life=target.Life
|
||||
|
||||
-- curr life
|
||||
target.Life=self:GetTargetLife(target)
|
||||
|
||||
@@ -603,13 +662,14 @@ function TARGET:onafterStatus(From, Event, To)
|
||||
self.life0 = self.life0+delta
|
||||
end
|
||||
|
||||
-- Check if life decreased ==> damaged
|
||||
if target.Life<life then
|
||||
target.Status = TARGET.ObjectStatus.DAMAGED
|
||||
self:ObjectDamaged(target)
|
||||
--target.Status = TARGET.ObjectStatus.DAMAGED
|
||||
self:ObjectDamaged(target)
|
||||
damaged=true
|
||||
end
|
||||
|
||||
if life < 1 and (not target.Status == TARGET.ObjectStatus.DEAD) then
|
||||
if target.Life<1 and target.Status~=TARGET.ObjectStatus.DEAD then
|
||||
self:E(self.lid..string.format("FF life is zero but no object dead event fired ==> object dead now for target object %s!", tostring(target.Name)))
|
||||
self:ObjectDead(target)
|
||||
damaged = true
|
||||
@@ -624,11 +684,12 @@ function TARGET:onafterStatus(From, Event, To)
|
||||
|
||||
-- Log output verbose=1.
|
||||
if self.verbose>=1 then
|
||||
local text=string.format("%s: Targets=%d/%d Life=%.1f/%.1f Damage=%.1f", fsmstate, self:CountTargets(), self.N0, self:GetLife(), self:GetLife0(), self:GetDamage())
|
||||
local text=string.format("%s: Targets=%d/%d [%d, %d], Life=%.1f/%.1f, Damage=%.1f",
|
||||
fsmstate, self:CountTargets(), self.N0, self.Ndestroyed, self.Ndead, self:GetLife(), self:GetLife0(), self:GetDamage())
|
||||
if self:CountTargets() == 0 or self:GetDamage() >= 100 then
|
||||
text=text.." Dead!"
|
||||
text=text.." - Dead!"
|
||||
elseif damaged then
|
||||
text=text.." Damaged!"
|
||||
text=text.." - Damaged!"
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
@@ -639,19 +700,35 @@ function TARGET:onafterStatus(From, Event, To)
|
||||
for i,_target in pairs(self.targets) do
|
||||
local target=_target --#TARGET.Object
|
||||
local damage=(1-target.Life/target.Life0)*100
|
||||
text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f", i, target.Type, target.Name, target.Status, target.Life, target.Life0, damage)
|
||||
text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f, N0=%d, Ndestroyed=%d, Ndead=%d",
|
||||
i, target.Type, target.Name, target.Status, target.Life, target.Life0, damage, target.N0, target.Ndestroyed, target.Ndead)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
if self:CountTargets() == 0 or self:GetDamage() >= 100 then
|
||||
-- Consitency check if target is still alive but all target objects are dead
|
||||
if self:IsAlive() and (self:CountTargets()==0 or self:GetDamage()>=100) then
|
||||
self:Dead()
|
||||
end
|
||||
|
||||
-- Quick sanity check
|
||||
for i,_target in pairs(self.targets) do
|
||||
local target=_target --#TARGET.Object
|
||||
if target.Ndestroyed>target.N0 then
|
||||
self:E(self.lid..string.format("ERROR: Number of destroyed target objects greater than number of initial target objects: %d>%d!", target.Ndestroyed, target.N0))
|
||||
end
|
||||
if target.Ndestroyed>target.N0 then
|
||||
self:E(self.lid..string.format("ERROR: Number of dead target objects greater than number of initial target objects: %d>%d!", target.Ndead, target.N0))
|
||||
end
|
||||
end
|
||||
|
||||
-- Update status again in 30 sec.
|
||||
if self:IsAlive() then
|
||||
self:__Status(-self.TStatus)
|
||||
else
|
||||
self:I(self.lid..string.format("Target is not alive any more ==> no further status updates are carried out"))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -687,6 +764,10 @@ function TARGET:onafterObjectDestroyed(From, Event, To, Target)
|
||||
-- Increase destroyed counter.
|
||||
self.Ndestroyed=self.Ndestroyed+1
|
||||
|
||||
Target.Ndestroyed=Target.Ndestroyed+1
|
||||
|
||||
Target.Life=0
|
||||
|
||||
-- Call object dead event.
|
||||
self:ObjectDead(Target)
|
||||
|
||||
@@ -707,6 +788,12 @@ function TARGET:onafterObjectDead(From, Event, To, Target)
|
||||
-- Set target status.
|
||||
Target.Status=TARGET.ObjectStatus.DEAD
|
||||
|
||||
-- Increase dead object counter
|
||||
Target.Ndead=Target.Ndead+1
|
||||
|
||||
-- Set target object life to 0.
|
||||
Target.Life=0
|
||||
|
||||
-- Increase dead counter.
|
||||
self.Ndead=self.Ndead+1
|
||||
|
||||
@@ -716,6 +803,7 @@ function TARGET:onafterObjectDead(From, Event, To, Target)
|
||||
local target=_target --#TARGET.Object
|
||||
if target.Status==TARGET.ObjectStatus.ALIVE then
|
||||
dead=false
|
||||
break -- break the loop because we now we are not dead
|
||||
end
|
||||
end
|
||||
|
||||
@@ -759,7 +847,6 @@ end
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function TARGET:onafterDestroyed(From, Event, To)
|
||||
|
||||
self:T({From, Event, To})
|
||||
|
||||
self:T(self.lid..string.format("TARGET destroyed"))
|
||||
@@ -813,23 +900,27 @@ function TARGET:OnEventUnitDeadOrLost(EventData)
|
||||
-- Check if we could find a target object.
|
||||
if target then
|
||||
|
||||
local Ndead=target.Ndead
|
||||
local Ndestroyed=target.Ndestroyed
|
||||
if EventData.id==EVENTS.RemoveUnit then
|
||||
target.Ndead=target.Ndead+1
|
||||
Ndead=Ndead+1
|
||||
else
|
||||
target.Ndestroyed=target.Ndestroyed+1
|
||||
target.Ndead=target.Ndead+1
|
||||
Ndestroyed=Ndestroyed+1
|
||||
Ndead=Ndead+1
|
||||
end
|
||||
|
||||
if target.Ndead==target.N0 then
|
||||
|
||||
if target.Ndestroyed>=target.N0 then
|
||||
-- Check if ALL objects are dead
|
||||
if Ndead==target.N0 then
|
||||
|
||||
if Ndestroyed>=target.N0 then
|
||||
|
||||
-- Debug message.
|
||||
self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed", EventData.id, tostring(target.Name)))
|
||||
|
||||
target.Life = 0
|
||||
|
||||
-- Trigger object destroyed event.
|
||||
-- Trigger object destroyed event. This sets the Life to zero and increases Ndestroyed
|
||||
self:ObjectDestroyed(target)
|
||||
|
||||
else
|
||||
@@ -839,7 +930,7 @@ function TARGET:OnEventUnitDeadOrLost(EventData)
|
||||
|
||||
target.Life = 0
|
||||
|
||||
-- Trigger object dead event.
|
||||
-- Trigger object dead event. This sets the Life to zero and increases Ndead counter
|
||||
self:ObjectDead(target)
|
||||
|
||||
end
|
||||
@@ -979,6 +1070,8 @@ function TARGET:_AddObject(Object)
|
||||
|
||||
target.Life0=1
|
||||
target.Life=1
|
||||
|
||||
target.N0=target.N0+1
|
||||
|
||||
elseif Object:IsInstanceOf("ZONE_BASE") then
|
||||
|
||||
@@ -992,6 +1085,8 @@ function TARGET:_AddObject(Object)
|
||||
|
||||
target.Life0=1
|
||||
target.Life=1
|
||||
|
||||
target.N0=target.N0+1
|
||||
|
||||
elseif Object:IsInstanceOf("OPSZONE") then
|
||||
|
||||
@@ -1203,11 +1298,27 @@ function TARGET:GetTargetThreatLevelMax(Target)
|
||||
return 0
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.ZONE then
|
||||
|
||||
local zone = Target.Object -- Core.Zone#ZONE_RADIUS
|
||||
local foundunits = {}
|
||||
if zone:IsInstanceOf("ZONE_RADIUS") or zone:IsInstanceOf("ZONE_POLYGON") then
|
||||
zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT,Unit.Category.SHIP})
|
||||
foundunits = zone:GetScannedSetUnit()
|
||||
else
|
||||
foundunits = SET_UNIT:New():FilterZones({zone}):FilterOnce()
|
||||
end
|
||||
local ThreatMax = foundunits:GetThreatLevelMax() or 0
|
||||
return ThreatMax
|
||||
|
||||
return 0
|
||||
elseif Target.Type==TARGET.ObjectType.OPSZONE then
|
||||
|
||||
local unitset = Target.Object:GetScannedUnitSet() -- Core.Set#SET_UNIT
|
||||
local ThreatMax = unitset:GetThreatLevelMax()
|
||||
return ThreatMax
|
||||
|
||||
else
|
||||
self:E("ERROR: unknown target object type in GetTargetThreatLevel!")
|
||||
return 0
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -1919,12 +2030,16 @@ function TARGET:CountObjectives(Target, Coalitions)
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.COORDINATE then
|
||||
|
||||
-- No target we can check!
|
||||
-- No target, where we can check the alive status, so we assume it is alive. Changed this because otherwise target count is 0 if we pass a coordinate.
|
||||
-- This is also more consitent with the life and is alive status.
|
||||
N=N+1
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.ZONE then
|
||||
|
||||
-- No target we can check!
|
||||
|
||||
-- No target, where we can check the alive status, so we assume it is alive. Changed this because otherwise target count is 0 if we pass a coordinate.
|
||||
-- This is also more consitent with the life and is alive status.
|
||||
N=N+1
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.OPSZONE then
|
||||
|
||||
local target=Target.Object --Ops.OpsZone#OPSZONE
|
||||
|
||||
Reference in New Issue
Block a user