This commit is contained in:
Applevangelist
2026-04-06 18:47:29 +02:00
parent 29dfbbf314
commit d136470149
7 changed files with 369 additions and 159 deletions
+84 -8
View File
@@ -171,6 +171,21 @@ REDGCI.version = "2.0.0"
-- @field #number WP_DISTANCE_FACTOR -- @field #number WP_DISTANCE_FACTOR
REDGCI.WP_DISTANCE_FACTOR = 5.0 REDGCI.WP_DISTANCE_FACTOR = 5.0
---
--@field #table RadioChannels
REDGCI.RadioChannels = {
[1] = 125,
[2] = 125.5,
[3] = 126,
[4] = 126.5,
[5] = 127,
[6] = 127.5,
[7] = 128,
[8] = 128.5,
[9] = 129,
[10] = 129.5,
}
-- ───────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────
-- Localized messages — embedded (gci_messages.lua no longer needed) -- Localized messages — embedded (gci_messages.lua no longer needed)
-- ───────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────
@@ -205,6 +220,7 @@ REDGCI.Messages = {
MERGE_SPLASH = "{CALLSIGN}, good kill. RTB.", MERGE_SPLASH = "{CALLSIGN}, good kill. RTB.",
RADAR_ON = "{CALLSIGN}, radar on.", RADAR_ON = "{CALLSIGN}, radar on.",
WEAPONS_FREE = "{CALLSIGN}, WEAPONS FREE.", WEAPONS_FREE = "{CALLSIGN}, WEAPONS FREE.",
RADIO_SWITCH_CHANNEL = "{CALLSIGN}, switch to channel {CHNL} for GCI.",
-- Pilot → GCI acknowledgements -- Pilot → GCI acknowledgements
ACK_VECTOR = "Copy, {HDG}.", ACK_VECTOR = "Copy, {HDG}.",
ACK_COMMIT = "Copy.", ACK_COMMIT = "Copy.",
@@ -228,7 +244,7 @@ REDGCI.Messages = {
NOTCH_ENTRY = "{CALLSIGN}, Ziel manövriert. Warten.", NOTCH_ENTRY = "{CALLSIGN}, Ziel manövriert. Warten.",
NOTCH_UPDATE = "{CALLSIGN}, Ziel {DIR_RL}, {RNG} Kilometer.", NOTCH_UPDATE = "{CALLSIGN}, Ziel {DIR_RL}, {RNG} Kilometer.",
ABORT_BINGO = "{CALLSIGN}, BINGO. Abbruch. Kurs {HDG}.", ABORT_BINGO = "{CALLSIGN}, BINGO. Abbruch. Kurs {HDG}.",
ABORT_THREAT = "{CALLSIGN}, BEDROHUNG. Abbruch. Kurs {HDG}.", ABORT_THREAT = "{CALLSIGN}, GEFAHR. Abbruch. Kurs {HDG}.",
MERGE_ENTRY = "{CALLSIGN}, Kontakt {DIR_RL}, {ASPECT} Grad. Angriff.", MERGE_ENTRY = "{CALLSIGN}, Kontakt {DIR_RL}, {ASPECT} Grad. Angriff.",
MERGE_OVERSHOOT = "{CALLSIGN}, Überschuss. {DIR_LR} ausbrechen.", MERGE_OVERSHOOT = "{CALLSIGN}, Überschuss. {DIR_LR} ausbrechen.",
MERGE_SEPARATION = "{CALLSIGN}, trennen. Steigen und neu ansetzen.", MERGE_SEPARATION = "{CALLSIGN}, trennen. Steigen und neu ansetzen.",
@@ -237,6 +253,7 @@ REDGCI.Messages = {
MERGE_SPLASH = "{CALLSIGN}, Treffer. Heimkurs.", MERGE_SPLASH = "{CALLSIGN}, Treffer. Heimkurs.",
RADAR_ON = "{CALLSIGN}, Radar an.", RADAR_ON = "{CALLSIGN}, Radar an.",
WEAPONS_FREE = "{CALLSIGN}, Feuer frei.", WEAPONS_FREE = "{CALLSIGN}, Feuer frei.",
RADIO_SWITCH_CHANNEL = "{CALLSIGN}, umschalten auf Kanal {CHNL} für Anweisungen.",
-- Pilot → GCI acknowledgements -- Pilot → GCI acknowledgements
ACK_VECTOR = "Verstanden, Kurs {HDG}.", ACK_VECTOR = "Verstanden, Kurs {HDG}.",
ACK_COMMIT = "Verstanden.", ACK_COMMIT = "Verstanden.",
@@ -269,6 +286,7 @@ REDGCI.Messages = {
MERGE_SPLASH = "{CALLSIGN}, молодец. Домой.", MERGE_SPLASH = "{CALLSIGN}, молодец. Домой.",
RADAR_ON = "{CALLSIGN}, локатор.", RADAR_ON = "{CALLSIGN}, локатор.",
WEAPONS_FREE = "{CALLSIGN}, цель разрешена.", WEAPONS_FREE = "{CALLSIGN}, цель разрешена.",
RADIO_SWITCH_CHANNEL = "{CALLSIGN}, переключись на канал {CHNL} для указаний.",
-- Pilot → GCI acknowledgements (kurz und militärisch) -- Pilot → GCI acknowledgements (kurz und militärisch)
ACK_VECTOR = "Понял, курс {HDG}.", ACK_VECTOR = "Понял, курс {HDG}.",
ACK_COMMIT = "Понял.", ACK_COMMIT = "Понял.",
@@ -299,8 +317,8 @@ REDGCI.DirTokens = {
-- @field #string de -- @field #string de
-- @field #string ru -- @field #string ru
REDGCI.CountTokens = { REDGCI.CountTokens = {
en = { single="single", pair="pair", group="group", biggroup="big group" }, en = { single="single", pair="two", group="group", biggroup="big group" },
de = { single="einzeln", pair="Paar", group="Gruppe", biggroup="große Gruppe" }, de = { single="einzel", pair="zwei", group="Gruppe", biggroup="große Gruppe" },
ru = { single="одиночная", pair="пара", group="группа", biggroup="большая группа"}, ru = { single="одиночная", pair="пара", group="группа", biggroup="большая группа"},
} }
@@ -465,6 +483,42 @@ function REDGCI:SetSRS(Path, Frequency, Modulation, Culture, Voice, Port, Speed)
return self return self
end end
--- Enable SRS autotranslation, do not forget to set voices according to language! Requires HOUND as SRS backend!
-- @param #REDGCI self
-- @param #string languagecode Language to translate to, defaults to "fr". Takes [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) language codes.
-- @param #string provider (optional) Translation provider, defaults to `MSRS.Provider.GOOGLE`
-- @return #REDGCI2v2 self
function REDGCI:EnableSRSAutoTranslate(languagecode,provider)
self.translateEnabled = true
self.translateLanguage = languagecode or "fr"
self.translateProvider = provider or MSRS.Provider.GOOGLE
return self
end
--- Configure available channel numbers and frequencies for pilots.
-- @param #REDGCI self
-- @param #table RadioTable Table of available channel numbers and their frequencies, indexed by channel number
-- @return #REDGCI self
-- @usage
-- Use as follows, e.g.
-- local RadioTable = {
-- [1] = 125,
-- [2] = 125.5,
-- [3] = 126,
-- [4] = 126.5,
-- [5] = 127,
-- [6] = 127.5,
-- [7] = 128,
-- [8] = 128.5,
-- [9] = 129,
-- [10] = 129.5,
-- }
-- dispatch:SetRadioChannelList(RadioTable)
function REDGCI:SetRadioChannelList(RadioTable)
self.RadioChannels = RadioTable
return self
end
--- Set SRS Provider --- Set SRS Provider
--@param #REDGCI self --@param #REDGCI self
--@param #string Provider --@param #string Provider
@@ -502,6 +556,8 @@ function REDGCI:SetPilotSRS(PilotCallsign, Culture, Voice, Speaker)
return self return self
end end
--- Set the target count manually (used when no INTEL source is attached). --- Set the target count manually (used when no INTEL source is attached).
-- Overridden automatically when INTEL is active. -- Overridden automatically when INTEL is active.
-- @param #REDGCI self -- @param #REDGCI self
@@ -688,7 +744,7 @@ end
-- @param #REDGCI self -- @param #REDGCI self
function REDGCI:_InitSRS() function REDGCI:_InitSRS()
-- GCI controller voice -- GCI controller voice
self._msrs = MSRS:New(self.SRSPath, self.SRSFreq, self.SRSMod) -- Sound.MSRS#MSRS self._msrs = MSRS:New(self.SRSPath, self.SRSFreq, self.SRSMod)
self._msrs:SetPort(self.SRSPort) self._msrs:SetPort(self.SRSPort)
self._msrs:SetLabel("GCI") self._msrs:SetLabel("GCI")
self._msrs:SetCulture(self.SRSCulture) self._msrs:SetCulture(self.SRSCulture)
@@ -700,6 +756,9 @@ function REDGCI:_InitSRS()
if self.SRSSpeaker then if self.SRSSpeaker then
self._msrs:SetSpeakerPiper(self.SRSSpeaker) self._msrs:SetSpeakerPiper(self.SRSSpeaker)
end end
if self.translateEnabled == true then
self._msrs:SetAutoTranslate(self.translateProvider,self.translateLanguage)
end
self._srs_queue = MSRSQUEUE:New("REDGCI_" .. self.Callsign) -- Sound.MSRS#MSRSQUEUE self._srs_queue = MSRSQUEUE:New("REDGCI_" .. self.Callsign) -- Sound.MSRS#MSRSQUEUE
-- Pilot voice (only when a pilot callsign has been configured) -- Pilot voice (only when a pilot callsign has been configured)
@@ -714,9 +773,11 @@ function REDGCI:_InitSRS()
self._pilot_msrs:SetProvider(self.SRSProvider) self._pilot_msrs:SetProvider(self.SRSProvider)
end end
if self.SRSSpeaker then if self.SRSSpeaker then
self._msrs:SetSpeakerPiper(self.PilotSRSSpeaker) self._pilot_msrs:SetSpeakerPiper(self.PilotSRSSpeaker)
end
if self.translateEnabled == true then
self._pilot_msrs:SetAutoTranslate(self.translateProvider,self.translateLanguage)
end end
--self._pilot_queue = MSRSQUEUE:New("REDGCI_PILOT_" .. self.PilotCallsign)
self._pilot_queue = self._srs_queue self._pilot_queue = self._srs_queue
self:_Log("Pilot SRS ready: " .. self.PilotCallsign) self:_Log("Pilot SRS ready: " .. self.PilotCallsign)
end end
@@ -774,7 +835,7 @@ function REDGCI:_ParseTokens(TokenStr)
end end
end end
end end
--UTILS.PrintTableToLog(result) UTILS.PrintTableToLog(result)
return result return result
end end
@@ -926,6 +987,7 @@ function REDGCI:_Transmit(TokenStr, DirLR, DirRL, Priority)
BRG = tok.brg and string.format("%03d", tok.brg) or "", BRG = tok.brg and string.format("%03d", tok.brg) or "",
COUNT = self:_CountToken(self._target_count), COUNT = self:_CountToken(self._target_count),
TYPE = self:_TypeToken(self._target_type), TYPE = self:_TypeToken(self._target_type),
CHNL = tok.channel or 1,
} }
local text = self:_FillTemplate(template, vars) local text = self:_FillTemplate(template, vars)
@@ -1419,7 +1481,21 @@ function REDGCI:onafterStart(From, Event, To)
self:T(self.lid .. "Ready. Fighter='" .. self.FighterGroupName .. self:T(self.lid .. "Ready. Fighter='" .. self.FighterGroupName ..
"' Target='" .. self.TargetGroupName .. "' Target='" .. self.TargetGroupName ..
"' Mode=" .. (self.IsAIPlane and "AI" or "Human")) "' Mode=" .. (self.IsAIPlane and "AI" or "Human"))
local function GetChannel(Freq)
for key,val in pairs(self.RadioChannels) do
if tonumber(val)==tonumber(Freq) then
return key
end
end
return 1
end
local channel = 1
if self.RadioChannels then channel = GetChannel(self.SRSFreq) end
self:_Transmit("RADIO_SWITCH_CHANNEL|channel="..channel,nil,nil,100)
-- Schedule first tick after short delay -- Schedule first tick after short delay
self:__Status(-2) self:__Status(-2)
end end
@@ -220,6 +220,21 @@ REDGCI2v2.SPLIT_RANGE = 60000 -- 60km pre-COMMIT split
-- @field #number OVERRIDE_TICKS -- @field #number OVERRIDE_TICKS
REDGCI2v2.OVERRIDE_TICKS = 7 REDGCI2v2.OVERRIDE_TICKS = 7
---
--@field #table RadioChannels
REDGCI2v2.RadioChannels = {
[1] = 125,
[2] = 125.5,
[3] = 126,
[4] = 126.5,
[5] = 127,
[6] = 127.5,
[7] = 128,
[8] = 128.5,
[9] = 129,
[10] = 129.5,
}
-- ───────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────
-- Constructor -- Constructor
-- ───────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────
@@ -276,6 +291,7 @@ function REDGCI2v2:New(Fighter1Group, Fighter2Group,
self.SRSVoice = MSRS.Voices.Google.Standard.ru_RU_Standard_D self.SRSVoice = MSRS.Voices.Google.Standard.ru_RU_Standard_D
self.SRSPort = 5002 self.SRSPort = 5002
self.FreqOffset = 0.5 self.FreqOffset = 0.5
self.PilotRadios = {}
-- ── Tactic state ────────────────────────────────────────── -- ── Tactic state ──────────────────────────────────────────
self._tactic = nil -- chosen at COMMIT self._tactic = nil -- chosen at COMMIT
@@ -340,6 +356,42 @@ function REDGCI2v2:SetSRS(Path, Frequency, Modulation, Culture, Voice, Port, Spe
return self return self
end end
--- Enable SRS autotranslation, do not forget to set voices according to language! Requires HOUND as SRS backend!
-- @param #REDGCI2v2 self
-- @param #string languagecode Language to translate to, defaults to "fr". Takes [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) language codes.
-- @param #string provider (optional) Translation provider, defaults to `MSRS.Provider.GOOGLE`
-- @return #REDGCI2v2 self
function REDGCI2v2:EnableSRSAutoTranslate(languagecode,provider)
self.translateEnabled = true
self.translateLanguage = languagecode or "fr"
self.translateProvider = provider or MSRS.Provider.GOOGLE
return self
end
--- Configure available channel numbers and frequencies for pilots.
-- @param #REDGCI2v2 self
-- @param #table RadioTable Table of available channel numbers and their frequencies, indexed by channel number
-- @return #REDGCI2v2 self
-- @usage
-- Use as follows, e.g.
-- local RadioTable = {
-- [1] = 125,
-- [2] = 125.5,
-- [3] = 126,
-- [4] = 126.5,
-- [5] = 127,
-- [6] = 127.5,
-- [7] = 128,
-- [8] = 128.5,
-- [9] = 129,
-- [10] = 129.5,
-- }
-- dispatch:SetRadioChannelList(RadioTable)
function REDGCI2v2:SetRadioChannelList(RadioTable)
self.RadioChannels = RadioTable
return self
end
--- Set SRS Provider --- Set SRS Provider
--@param #REDGCI2v2 self --@param #REDGCI2v2 self
--@param #string Provider --@param #string Provider
@@ -492,6 +544,9 @@ function REDGCI2v2:_MakeGCI(FighterGroup, TargetGroup, Callsign, FreqOffSet)
if self.SRSProvider then if self.SRSProvider then
gci:SetSRSProvider(self.SRSProvider) gci:SetSRSProvider(self.SRSProvider)
end end
if self.translateEnabled == true then
self._msrs:SetAutoTranslate(self.translateProvider,self.translateLanguage)
end
if self.WFRange then if self.WFRange then
gci:SetWFRange(self.WFRange) gci:SetWFRange(self.WFRange)
end end
@@ -500,6 +555,7 @@ function REDGCI2v2:_MakeGCI(FighterGroup, TargetGroup, Callsign, FreqOffSet)
gci:SetAltOffset(self.AltOffset) gci:SetAltOffset(self.AltOffset)
gci:SetDebug(self.Debug) gci:SetDebug(self.Debug)
gci:SetMissileFiringFlag(self._missilerangeflag) gci:SetMissileFiringFlag(self._missilerangeflag)
gci:SetRadioChannelList(self.RadioChannels)
return gci return gci
end end
@@ -11,13 +11,13 @@
-- ### Author: **Applevangelist** -- ### Author: **Applevangelist**
-- --
-- === -- ===
-- @module Functional.REDGCI_DISPATCHER -- @module Functional.REDGCIDISPATCHER
-- @image Func_RedGCI.png -- @image Func_RedGCI.png
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- REDGCI_DISPATCHER class --- **REDGCIDISPATCHER** class, extends Core.Fsm#FSM class
-- @type REDGCI_DISPATCHER #REDGCI\_DISPATCHER -- @type REDGCIDISPATCHER
-- @field #string ClassName -- @field #string ClassName
-- @field #string version -- @field #string version
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@@ -122,7 +122,7 @@
-- --
-- --- -- ---
-- --
-- ## Dispatcher & CAP Flow (REDGCI_DISPATCHER) -- ## Dispatcher & CAP Flow (REDGCIDISPATCHER)
-- --
-- When using the dispatcher layer, the full operational flow is: -- When using the dispatcher layer, the full operational flow is:
-- --
@@ -161,9 +161,9 @@
-- --
-- **The Soviet system is not inferior** — it is optimized for a different kind of pilot and a different operational context. Mass interception of large NATO strike packages over defended Soviet airspace demanded centralized, efficient, high-throughput GCI control. RedGCI brings that experience to DCS. -- **The Soviet system is not inferior** — it is optimized for a different kind of pilot and a different operational context. Mass interception of large NATO strike packages over defended Soviet airspace demanded centralized, efficient, high-throughput GCI control. RedGCI brings that experience to DCS.
-- --
-- @field #REDGCI_DISPATCHER #REDGCI\_DISPATCHER -- @field #REDGCIDISPATCHER
REDGCI_DISPATCHER = { REDGCIDISPATCHER = {
ClassName = "REDGCI_DISPATCHER", ClassName = "REDGCIDISPATCHER",
version = "2.0.0", version = "2.0.0",
} }
@@ -181,23 +181,24 @@ REDGCI_DISPATCHER = {
-- @field #string STATE_ENGAGED -- @field #string STATE_ENGAGED
-- @field #string STATE_RTB -- @field #string STATE_RTB
-- @field #string STATE_UNKNOWN -- @field #string STATE_UNKNOWN
REDGCI_DISPATCHER.TICK_INTERVAL = 30.0 REDGCIDISPATCHER.TICK_INTERVAL = 30.0
REDGCI_DISPATCHER.UNITS_PER_PAIR = 2 REDGCIDISPATCHER.UNITS_PER_PAIR = 2
REDGCI_DISPATCHER.ORBIT_SPEED_KMPH = 600 REDGCIDISPATCHER.ORBIT_SPEED_KMPH = 600
REDGCI_DISPATCHER.ORBIT_ALT_M = 4500 REDGCIDISPATCHER.ORBIT_ALT_M = 4500
REDGCI_DISPATCHER.AI_PER_ZONE = 2 REDGCIDISPATCHER.AI_PER_ZONE = 2
REDGCI_DISPATCHER.STATE_CAP = "CAP" REDGCIDISPATCHER.STATE_CAP = "CAP"
REDGCI_DISPATCHER.STATE_ENGAGED = "ENGAGED" REDGCIDISPATCHER.STATE_ENGAGED = "ENGAGED"
REDGCI_DISPATCHER.STATE_RTB = "RTB" REDGCIDISPATCHER.STATE_RTB = "RTB"
REDGCI_DISPATCHER.STATE_UNKNOWN = "UNKNOWN" REDGCIDISPATCHER.STATE_UNKNOWN = "UNKNOWN"
-- ───────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────
-- Localized messages (token system, analogous to REDGCI) -- Localized messages (token system, analogous to REDGCI)
-- ───────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────
--- @type REDGCI_DISPATCHER.Messages ---
REDGCI_DISPATCHER.Messages = { -- @type Messages
REDGCIDISPATCHER.Messages = {
en = { en = {
RTB_CALL = "{CALLSIGN}, mission complete. RTB, refuel and rearm.", RTB_CALL = "{CALLSIGN}, mission complete. RTB, refuel and rearm.",
INTEL_CONTACT = "Attention, radar contact. {COUNT}, {TYPE}, {RNG} kilometers.", INTEL_CONTACT = "Attention, radar contact. {COUNT}, {TYPE}, {RNG} kilometers.",
@@ -215,21 +216,36 @@ REDGCI_DISPATCHER.Messages = {
}, },
} }
---
--@field #table RadioChannels
REDGCIDISPATCHER.RadioChannels = {
[1] = 125,
[2] = 125.5,
[3] = 126,
[4] = 126.5,
[5] = 127,
[6] = 127.5,
[7] = 128,
[8] = 128.5,
[9] = 129,
[10] = 129.5,
}
-- ───────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────
-- Constructor -- Constructor
-- ───────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────
--- Create a new REDGCI_DISPATCHER instance. --- Create a new REDGCIDISPATCHER instance.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #string TemplateName Name of the late-activated template group (1 unit) -- @param #string TemplateName Name of the late-activated template group (1 unit)
-- @param Core.Set#SET_ZONE ZoneSet SET_ZONE mit CAP-Holding-Zonen -- @param Core.Set#SET_ZONE ZoneSet SET_ZONE mit CAP-Holding-Zonen
-- @param Ops.Intel#INTEL Intel Laufende INTEL-Instanz -- @param Ops.Intel#INTEL Intel Laufende INTEL-Instanz
-- @param #number Coalition coalition.side.RED / BLUE -- @param #number Coalition coalition.side.RED / BLUE
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:New(TemplateName, ZoneSet, Intel, Coalition) function REDGCIDISPATCHER:New(TemplateName, ZoneSet, Intel, Coalition)
local self = BASE:Inherit(self, FSM:New()) local self = BASE:Inherit(self, FSM:New())
self.lid = "REDGCI_DISPATCHER | " self.lid = "REDGCIDISPATCHER | "
self.TemplateName = TemplateName self.TemplateName = TemplateName
self.ZoneSet = ZoneSet self.ZoneSet = ZoneSet
self.Intel = Intel self.Intel = Intel
@@ -241,9 +257,9 @@ function REDGCI_DISPATCHER:New(TemplateName, ZoneSet, Intel, Coalition)
self.Debug = true self.Debug = true
self.AltOffset = -700 self.AltOffset = -700
self.WFRange = 20000 self.WFRange = 20000
self.OrbitAlt = REDGCI_DISPATCHER.ORBIT_ALT_M self.OrbitAlt = REDGCIDISPATCHER.ORBIT_ALT_M
self.OrbitSpeed = REDGCI_DISPATCHER.ORBIT_SPEED_KMPH self.OrbitSpeed = REDGCIDISPATCHER.ORBIT_SPEED_KMPH
self.AiPerZone = REDGCI_DISPATCHER.AI_PER_ZONE self.AiPerZone = REDGCIDISPATCHER.AI_PER_ZONE
self.StartCallsign = 100 self.StartCallsign = 100
self.SRSPath = nil self.SRSPath = nil
@@ -254,6 +270,7 @@ function REDGCI_DISPATCHER:New(TemplateName, ZoneSet, Intel, Coalition)
self.SRSPort = 5002 self.SRSPort = 5002
self.SRSSpeed = 1 self.SRSSpeed = 1
self.SRSProvider = nil self.SRSProvider = nil
self.SRSFreqPilotsStart = self.SRSFreq + 1
self.ClientSet = nil self.ClientSet = nil
@@ -275,7 +292,7 @@ function REDGCI_DISPATCHER:New(TemplateName, ZoneSet, Intel, Coalition)
self:AddTransition("Running", "Status", "Running") self:AddTransition("Running", "Status", "Running")
self:AddTransition("Running", "Stop", "Stopped") self:AddTransition("Running", "Stop", "Stopped")
self:I(self.lid .. "v" .. REDGCI_DISPATCHER.version .. " created.") self:I(self.lid .. "v" .. REDGCIDISPATCHER.version .. " created.")
return self return self
end end
@@ -284,10 +301,10 @@ end
-- ───────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────
--- Set the home plate. --- Set the home plate.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #string BaseName -- @param #string BaseName
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetHomeBase(BaseName) function REDGCIDISPATCHER:SetHomeBase(BaseName)
self.HomeBaseName = BaseName self.HomeBaseName = BaseName
local ab = AIRBASE:FindByName(BaseName) local ab = AIRBASE:FindByName(BaseName)
if ab then self.HomeBase = ab if ab then self.HomeBase = ab
@@ -296,55 +313,55 @@ function REDGCI_DISPATCHER:SetHomeBase(BaseName)
end end
--- Set the client SET. --- Set the client SET.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param Core.Set#SET_CLIENT ClientSet -- @param Core.Set#SET_CLIENT ClientSet
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetClientSet(ClientSet) function REDGCIDISPATCHER:SetClientSet(ClientSet)
self.ClientSet = ClientSet self.ClientSet = ClientSet
return self return self
end end
--- Set orbit altitude and speed. --- Set orbit altitude and speed.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #number AltMSL Altitude MSL in meters (default 4500) -- @param #number AltMSL Altitude MSL in meters (default 4500)
-- @param #number SpeedKmph Speed in kph (km/h) (default 600) -- @param #number SpeedKmph Speed in kph (km/h) (default 600)
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetOrbitParameters(AltMSL, SpeedKmph) function REDGCIDISPATCHER:SetOrbitParameters(AltMSL, SpeedKmph)
self.OrbitAlt = AltMSL or REDGCI_DISPATCHER.ORBIT_ALT_M self.OrbitAlt = AltMSL or REDGCIDISPATCHER.ORBIT_ALT_M
self.OrbitSpeed = SpeedKmph or REDGCI_DISPATCHER.ORBIT_SPEED_KMPH self.OrbitSpeed = SpeedKmph or REDGCIDISPATCHER.ORBIT_SPEED_KMPH
return self return self
end end
--- Set number of AI spawned per CAP zone. --- Set number of AI spawned per CAP zone.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #number N Default 2 -- @param #number N Default 2
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetAiPerZone(N) function REDGCIDISPATCHER:SetAiPerZone(N)
self.AiPerZone = N or 2 self.AiPerZone = N or 2
return self return self
end end
--- Set starting callsign number. --- Set starting callsign number.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #number N Default 100 -- @param #number N Default 100
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetStartCallsign(N) function REDGCIDISPATCHER:SetStartCallsign(N)
self.StartCallsign = N or 100 self.StartCallsign = N or 100
self._callsign_counter = self.StartCallsign self._callsign_counter = self.StartCallsign
return self return self
end end
--- Set locale for radio messages. --- Set locale for radio messages.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #string Locale "ru", "de", "en" -- @param #string Locale "ru", "de", "en"
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetLocale(Locale) function REDGCIDISPATCHER:SetLocale(Locale)
self.Locale = Locale or "ru" self.Locale = Locale or "ru"
return self return self
end end
--- Configure Dispatcher SRS. --- Configure Dispatcher SRS.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #string Path (Optional) Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- @param #string Path (Optional) Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
-- @param #number Frequency Single Frequency, e.g. 124 -- @param #number Frequency Single Frequency, e.g. 124
-- @param #number Modulation Modluation e.g. radio.modulation.AM -- @param #number Modulation Modluation e.g. radio.modulation.AM
@@ -352,8 +369,8 @@ end
-- @param #string Voice The voice name e.g. MSRS.Voices.Google.Wavenet.de_DE_Wavenet_G -- @param #string Voice The voice name e.g. MSRS.Voices.Google.Wavenet.de_DE_Wavenet_G
-- @param #number Port (Optional) The SRS Server port, defaults to 5002. -- @param #number Port (Optional) The SRS Server port, defaults to 5002.
-- @param #number Speed (Optional) Voice speed, defaults to 1.0 (100%) -- @param #number Speed (Optional) Voice speed, defaults to 1.0 (100%)
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetSRS(Path, Frequency, Modulation, Culture, Voice, Port, Speed) function REDGCIDISPATCHER:SetSRS(Path, Frequency, Modulation, Culture, Voice, Port, Speed)
self.SRSPath = Path self.SRSPath = Path
self.SRSFreq = Frequency or self.SRSFreq self.SRSFreq = Frequency or self.SRSFreq
self.SRSMod = Modulation or self.SRSMod self.SRSMod = Modulation or self.SRSMod
@@ -361,15 +378,52 @@ function REDGCI_DISPATCHER:SetSRS(Path, Frequency, Modulation, Culture, Voice, P
self.SRSVoice = Voice or self.SRSVoice self.SRSVoice = Voice or self.SRSVoice
self.SRSPort = Port or self.SRSPort self.SRSPort = Port or self.SRSPort
self.SRSSpeed = Speed or 1 self.SRSSpeed = Speed or 1
self.SRSFreqPilotsStart = self.SRSFreq + 1
return self return self
end end
--- Enable SRS autotranslation, do not forget to set voices according to language! Requires HOUND as SRS backend!
-- @param #REDGCIDISPATCHER self
-- @param #string languagecode Language to translate to, defaults to "fr". Takes [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) language codes.
-- @param #string provider (optional) Translation provider, defaults to `MSRS.Provider.GOOGLE`
-- @return #REDGCIDISPATCHER self
function REDGCIDISPATCHER:EnableSRSAutoTranslate(languagecode,provider)
self.translateEnabled = true
self.translateLanguage = languagecode or "fr"
self.translateProvider = provider or MSRS.Provider.GOOGLE
return self
end
--- Configure available channel numbers and frequencies for pilots.
-- @param #REDGCIDISPATCHER self
-- @param #table RadioTable Table of available channel numbers and their frequencies, indexed by channel number
-- @return #REDGCIDISPATCHER self
-- @usage
-- Use as follows, e.g.
-- local RadioTable = {
-- [1] = 125,
-- [2] = 125.5,
-- [3] = 126,
-- [4] = 126.5,
-- [5] = 127,
-- [6] = 127.5,
-- [7] = 128,
-- [8] = 128.5,
-- [9] = 129,
-- [10] = 129.5,
-- }
-- dispatch:SetRadioChannelList(RadioTable)
function REDGCIDISPATCHER:SetRadioChannelList(RadioTable)
self.RadioChannels = RadioTable
return self
end
--- Configure GCI SRS (the one that guides the pilots). --- Configure GCI SRS (the one that guides the pilots).
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #number Frequency Single Frequency, e.g. 125 -- @param #number Frequency Single Frequency, e.g. 125
-- @param #string Voice The SRS Voice to be used. -- @param #string Voice The SRS Voice to be used.
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetSRSGCIDetails(StartFrequency,Voice) function REDGCIDISPATCHER:SetSRSGCIDetails(StartFrequency,Voice)
self:I({F=StartFrequency,V=Voice}) self:I({F=StartFrequency,V=Voice})
self.SRSGCIFrequency = StartFrequency or 124 self.SRSGCIFrequency = StartFrequency or 124
self.SRSGCIVoice = Voice or self.SRSVoice or MSRS.Voices.Google.Wavenet.de_DE_Wavenet_B self.SRSGCIVoice = Voice or self.SRSVoice or MSRS.Voices.Google.Wavenet.de_DE_Wavenet_B
@@ -377,39 +431,39 @@ function REDGCI_DISPATCHER:SetSRSGCIDetails(StartFrequency,Voice)
end end
--- Set SRS provider. --- Set SRS provider.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetSRSProvider(Provider) function REDGCIDISPATCHER:SetSRSProvider(Provider)
self.SRSProvider = Provider self.SRSProvider = Provider
return self return self
end end
--- Set altitude offset for intercept geometry. --- Set altitude offset for intercept geometry.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #number Meters Default -700 (shoot up) -- @param #number Meters Default -700 (shoot up)
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetAltOffset(Meters) function REDGCIDISPATCHER:SetAltOffset(Meters)
self.AltOffset = Meters or -700 self.AltOffset = Meters or -700
return self return self
end end
--- Set weapons-free range. --- Set weapons-free range.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #number Meters Default 20000 (20 km) -- @param #number Meters Default 20000 (20 km)
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetWFRange(Meters) function REDGCIDISPATCHER:SetWFRange(Meters)
self.WFRange = Meters or 20000 self.WFRange = Meters or 20000
return self return self
end end
--- Enable or disable automatic respawn after engagement. --- Enable or disable automatic respawn after engagement.
-- New AI will be spawned into the same CAP zone after RespawnDelay seconds. -- New AI will be spawned into the same CAP zone after RespawnDelay seconds.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #boolean Enabled Default true -- @param #boolean Enabled Default true
-- @param #number Delay Seconds after RTB before respawn (default 300) -- @param #number Delay Seconds after RTB before respawn (default 300)
-- @param #number Count Number of AI to spawn (default 2) -- @param #number Count Number of AI to spawn (default 2)
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetRespawn(Enabled, Delay, Count) function REDGCIDISPATCHER:SetRespawn(Enabled, Delay, Count)
self.RespawnEnabled = Enabled ~= false self.RespawnEnabled = Enabled ~= false
self.RespawnDelay = Delay or 300 self.RespawnDelay = Delay or 300
self.RespawnCount = Count or 2 self.RespawnCount = Count or 2
@@ -417,10 +471,10 @@ function REDGCI_DISPATCHER:SetRespawn(Enabled, Delay, Count)
end end
--- Enable debug logging. --- Enable debug logging.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #boolean OnOff -- @param #boolean OnOff
-- @return #REDGCI_DISPATCHER self -- @return #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:SetDebug(OnOff) function REDGCIDISPATCHER:SetDebug(OnOff)
self.Debug = OnOff ~= false self.Debug = OnOff ~= false
return self return self
end end
@@ -430,16 +484,16 @@ end
-- ───────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────
--- [INTERNAL] --- [INTERNAL]
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:_Log(msg) function REDGCIDISPATCHER:_Log(msg)
if self.Debug then env.info(self.lid .. msg) end if self.Debug then env.info(self.lid .. msg) end
end end
--- [INTERNAL] Initialize TEXTANDSOUND localization. --- [INTERNAL] Initialize TEXTANDSOUND localization.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:_InitLocalization() function REDGCIDISPATCHER:_InitLocalization()
self._gettext = TEXTANDSOUND:New("REDGCI_DISPATCHER", "en") self._gettext = TEXTANDSOUND:New("REDGCIDISPATCHER", "en")
for locale, entries in pairs(REDGCI_DISPATCHER.Messages) do for locale, entries in pairs(REDGCIDISPATCHER.Messages) do
local loc = string.lower(tostring(locale)) local loc = string.lower(tostring(locale))
for id, text in pairs(entries) do for id, text in pairs(entries) do
self._gettext:AddEntry(loc, tostring(id), text) self._gettext:AddEntry(loc, tostring(id), text)
@@ -448,11 +502,11 @@ function REDGCI_DISPATCHER:_InitLocalization()
end end
--- [INTERNAL] Fill {PLACEHOLDER} tokens in a template string. --- [INTERNAL] Fill {PLACEHOLDER} tokens in a template string.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #string Template -- @param #string Template
-- @param #table Vars -- @param #table Vars
-- @return #string -- @return #string
function REDGCI_DISPATCHER:_FillTemplate(Template, Vars) function REDGCIDISPATCHER:_FillTemplate(Template, Vars)
self:I({T=Template,V=Vars}) self:I({T=Template,V=Vars})
return (string.gsub(Template, "{([%w_]+)}", function(key) return (string.gsub(Template, "{([%w_]+)}", function(key)
return tostring(Vars[key] or "") return tostring(Vars[key] or "")
@@ -460,11 +514,11 @@ function REDGCI_DISPATCHER:_FillTemplate(Template, Vars)
end end
--- [INTERNAL] Dispatch a radio transmission via SRS queue. --- [INTERNAL] Dispatch a radio transmission via SRS queue.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #string Key Message key from Messages table -- @param #string Key Message key from Messages table
-- @param #table Vars Token variables -- @param #table Vars Token variables
-- @param #string GroupName For subtitle targeting (optional) -- @param #string GroupName For subtitle targeting (optional)
function REDGCI_DISPATCHER:_Transmit(Key, Vars, GroupName) function REDGCIDISPATCHER:_Transmit(Key, Vars, GroupName)
if not self._gettext or not self._msrs or not self._srs_queue then return end if not self._gettext or not self._msrs or not self._srs_queue then return end
local template = self._gettext:GetEntry(Key, self.Locale) local template = self._gettext:GetEntry(Key, self.Locale)
@@ -488,19 +542,19 @@ function REDGCI_DISPATCHER:_Transmit(Key, Vars, GroupName)
end end
--- [INTERNAL] Next callsign number. --- [INTERNAL] Next callsign number.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @return #number -- @return #number
function REDGCI_DISPATCHER:_NextCallsign() function REDGCIDISPATCHER:_NextCallsign()
local cs = self._callsign_counter local cs = self._callsign_counter
self._callsign_counter = self._callsign_counter + 1 self._callsign_counter = self._callsign_counter + 1
return cs return cs
end end
--- [INTERNAL] Localized tactic name for radio. --- [INTERNAL] Localized tactic name for radio.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #string Tactic "PINCER","HIGH_LOW","STAGGER","TRAIL","GIRAFFE" -- @param #string Tactic "PINCER","HIGH_LOW","STAGGER","TRAIL","GIRAFFE"
-- @return #string -- @return #string
function REDGCI_DISPATCHER:_TacticToken(Tactic) function REDGCIDISPATCHER:_TacticToken(Tactic)
local tokens = { local tokens = {
en = { PINCER="pincer", HIGH_LOW="high-low", STAGGER="stagger", en = { PINCER="pincer", HIGH_LOW="high-low", STAGGER="stagger",
TRAIL="trail", GIRAFFE="giraffe" }, TRAIL="trail", GIRAFFE="giraffe" },
@@ -514,10 +568,10 @@ function REDGCI_DISPATCHER:_TacticToken(Tactic)
end end
--- [INTERNAL] Send transmission to all CAP fighters in pool. --- [INTERNAL] Send transmission to all CAP fighters in pool.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #string Key Message key -- @param #string Key Message key
-- @param #table Vars Token variables -- @param #table Vars Token variables
function REDGCI_DISPATCHER:_TransmitToAllCAP(Key, Vars) function REDGCIDISPATCHER:_TransmitToAllCAP(Key, Vars)
if not self._gettext or not self._msrs or not self._srs_queue then return end if not self._gettext or not self._msrs or not self._srs_queue then return end
local template = self._gettext:GetEntry(Key, self.Locale) local template = self._gettext:GetEntry(Key, self.Locale)
@@ -551,10 +605,10 @@ end
--- [INTERNAL] Get callsign string from group. --- [INTERNAL] Get callsign string from group.
-- Reads part after '#' in group name, e.g. "TEMPLATE#101" → "101". -- Reads part after '#' in group name, e.g. "TEMPLATE#101" → "101".
-- Falls back to GetCustomCallSign(), then last part of group name. -- Falls back to GetCustomCallSign(), then last part of group name.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param Wrapper.Group#GROUP Grp -- @param Wrapper.Group#GROUP Grp
-- @return #string -- @return #string
function REDGCI_DISPATCHER:_GetCallsign(Grp) function REDGCIDISPATCHER:_GetCallsign(Grp)
if not Grp then return "GCI" end if not Grp then return "GCI" end
local name = Grp:GetName() or "GCI" local name = Grp:GetName() or "GCI"
local after_hash = string.match(name, "#(%d+)") local after_hash = string.match(name, "#(%d+)")
@@ -565,10 +619,10 @@ function REDGCI_DISPATCHER:_GetCallsign(Grp)
end end
--- [INTERNAL] Check if group has a human pilot. --- [INTERNAL] Check if group has a human pilot.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param Wrapper.Group#GROUP Grp -- @param Wrapper.Group#GROUP Grp
-- @return #boolean -- @return #boolean
function REDGCI_DISPATCHER:_IsHuman(Grp) function REDGCIDISPATCHER:_IsHuman(Grp)
if not Grp then return false end if not Grp then return false end
if self.ClientSet then if self.ClientSet then
local found = false local found = false
@@ -587,26 +641,29 @@ function REDGCI_DISPATCHER:_IsHuman(Grp)
end end
--- [INTERNAL] Initialize SRS. --- [INTERNAL] Initialize SRS.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:_InitSRS() function REDGCIDISPATCHER:_InitSRS()
self._msrs = MSRS:New(self.SRSPath, 121.5, self.SRSMod) self._msrs = MSRS:New(self.SRSPath, self.SRSFreq, self.SRSMod)
self._msrs:SetPort(self.SRSPort) self._msrs:SetPort(self.SRSPort)
self._msrs:SetLabel("DISPATCH") self._msrs:SetLabel("DISPATCH")
self._msrs:SetCulture(self.SRSCulture) self._msrs:SetCulture(self.SRSCulture)
self._msrs:SetVoice(self.SRSVoice) self._msrs:SetVoice(self.SRSVoice)
self._msrs:SetCoalition(self.Coalition) self._msrs:SetCoalition(self.Coalition)
if self.translateEnabled == true then
self._msrs:SetAutoTranslate(self.translateProvider,self.translateLanguage)
end
if self.SRSProvider then self._msrs:SetProvider(self.SRSProvider) end if self.SRSProvider then self._msrs:SetProvider(self.SRSProvider) end
self._srs_queue = MSRSQUEUE:New("REDGCI_DISPATCHER") self._srs_queue = MSRSQUEUE:New("REDGCIDISPATCHER")
end end
--- [INTERNAL] Spawn AI group and assign orbit. --- [INTERNAL] Spawn AI group and assign orbit.
-- Uses SPAWN:NewWithAlias() so group name contains callsign. -- Uses SPAWN:NewWithAlias() so group name contains callsign.
-- Template controls parking, hot/cold, heading. -- Template controls parking, hot/cold, heading.
-- OnSpawnGroup callback ensures group is alive before orbit assignment. -- OnSpawnGroup callback ensures group is alive before orbit assignment.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param Core.Zone#ZONE Zone -- @param Core.Zone#ZONE Zone
-- @return Wrapper.Group#GROUP or nil -- @return Wrapper.Group#GROUP or nil
function REDGCI_DISPATCHER:_SpawnAI(Zone) function REDGCIDISPATCHER:_SpawnAI(Zone)
local cs = self:_NextCallsign() local cs = self:_NextCallsign()
local alias = self.TemplateName .. "#" .. cs local alias = self.TemplateName .. "#" .. cs
@@ -625,13 +682,13 @@ function REDGCI_DISPATCHER:_SpawnAI(Zone)
end end
--- [INTERNAL] Toggle radar emission on the fighter group. --- [INTERNAL] Toggle radar emission on the fighter group.
-- @param #REDGCI self -- @param #REDGCIDISPATCHER self
-- @param #boolean On -- @param #boolean On
-- @param #number Delay -- @param #number Delay
function REDGCI_DISPATCHER:_SetRadar(Grp,On,Delay) function REDGCIDISPATCHER:_SetRadar(Grp,On,Delay)
--if not self.IsAIPlane then return end --if not self.IsAIPlane then return end
if Delay then if Delay then
self:ScheduleOnce(Delay,REDGCI_DISPATCHER._SetRadar,self,Grp,On) self:ScheduleOnce(Delay,REDGCIDISPATCHER._SetRadar,self,Grp,On)
return return
end end
local grp = Grp local grp = Grp
@@ -648,13 +705,13 @@ function REDGCI_DISPATCHER:_SetRadar(Grp,On,Delay)
end end
--- [INTERNAL] Toggle weapons free on the fighter group. --- [INTERNAL] Toggle weapons free on the fighter group.
-- @param #REDGCI self -- @param #REDGCIDISPATCHER self
-- @param #boolean On -- @param #boolean On
-- @param #number Delay Delay in seconds -- @param #number Delay Delay in seconds
function REDGCI_DISPATCHER:_SetWeaponsFree(Grp,On,Delay) function REDGCIDISPATCHER:_SetWeaponsFree(Grp,On,Delay)
--if not self.IsAIPlane then return end --if not self.IsAIPlane then return end
if Delay then if Delay then
self:ScheduleOnce(Delay,REDGCI_DISPATCHER._SetWeaponsFree,self,Grp,On) self:ScheduleOnce(Delay,REDGCIDISPATCHER._SetWeaponsFree,self,Grp,On)
return return
end end
local grp = Grp local grp = Grp
@@ -677,10 +734,10 @@ end
--- [INTERNAL] Assign orbit task in CAP zone. --- [INTERNAL] Assign orbit task in CAP zone.
-- WaypointAir expects speed in kph or km/h; TaskOrbit expects mps or m/s. -- WaypointAir expects speed in kph or km/h; TaskOrbit expects mps or m/s.
-- Altitude variation prevents all groups flying at exact same level. -- Altitude variation prevents all groups flying at exact same level.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param Wrapper.Group#GROUP Grp -- @param Wrapper.Group#GROUP Grp
-- @param Core.Zone#ZONE Zone -- @param Core.Zone#ZONE Zone
function REDGCI_DISPATCHER:_AssignOrbit(Grp, Zone) function REDGCIDISPATCHER:_AssignOrbit(Grp, Zone)
if not Grp or not Grp:IsAlive() then return end if not Grp or not Grp:IsAlive() then return end
@@ -733,18 +790,18 @@ function REDGCI_DISPATCHER:_AssignOrbit(Grp, Zone)
end end
--- [INTERNAL] Register fighter in pool. --- [INTERNAL] Register fighter in pool.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param Wrapper.Group#GROUP Grp -- @param Wrapper.Group#GROUP Grp
-- @param Core.Zone#ZONE Zone -- @param Core.Zone#ZONE Zone
-- @param #boolean IsHuman -- @param #boolean IsHuman
function REDGCI_DISPATCHER:_RegisterFighter(Grp, Zone, IsHuman) function REDGCIDISPATCHER:_RegisterFighter(Grp, Zone, IsHuman)
local name = Grp:GetName() local name = Grp:GetName()
self._pool[name] = { self._pool[name] = {
groupName = name, groupName = name,
group = Grp, group = Grp,
zone = Zone, zone = Zone,
isHuman = IsHuman or false, isHuman = IsHuman or false,
state = REDGCI_DISPATCHER.STATE_CAP, state = REDGCIDISPATCHER.STATE_CAP,
pairedWith = nil, pairedWith = nil,
engagement = nil, engagement = nil,
callsign = self:_GetCallsign(Grp), callsign = self:_GetCallsign(Grp),
@@ -755,8 +812,8 @@ function REDGCI_DISPATCHER:_RegisterFighter(Grp, Zone, IsHuman)
end end
--- [INTERNAL] Refresh human pool from ClientSet. --- [INTERNAL] Refresh human pool from ClientSet.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:_RefreshHumanPool() function REDGCIDISPATCHER:_RefreshHumanPool()
if not self.ClientSet then return end if not self.ClientSet then return end
self.ClientSet:ForEachClient(function(client) self.ClientSet:ForEachClient(function(client)
@@ -767,8 +824,8 @@ function REDGCI_DISPATCHER:_RefreshHumanPool()
if self._pool[name] then if self._pool[name] then
self._pool[name].isHuman = true self._pool[name].isHuman = true
if self._pool[name].state ~= REDGCI_DISPATCHER.STATE_ENGAGED and if self._pool[name].state ~= REDGCIDISPATCHER.STATE_ENGAGED and
self._pool[name].state ~= REDGCI_DISPATCHER.STATE_RTB then self._pool[name].state ~= REDGCIDISPATCHER.STATE_RTB then
local in_zone = false local in_zone = false
if self.ZoneSet then if self.ZoneSet then
self.ZoneSet:ForEachZone(function(zone) self.ZoneSet:ForEachZone(function(zone)
@@ -779,7 +836,7 @@ function REDGCI_DISPATCHER:_RefreshHumanPool()
end) end)
end end
self._pool[name].state = in_zone and self._pool[name].state = in_zone and
REDGCI_DISPATCHER.STATE_CAP or REDGCI_DISPATCHER.STATE_UNKNOWN REDGCIDISPATCHER.STATE_CAP or REDGCIDISPATCHER.STATE_UNKNOWN
end end
return return
end end
@@ -805,15 +862,15 @@ end
--- [INTERNAL] Return available fighters sorted by priority. --- [INTERNAL] Return available fighters sorted by priority.
-- Humans first, then by distance to cluster centroid. -- Humans first, then by distance to cluster centroid.
-- Safe iteration — collects dead groups separately before removing. -- Safe iteration — collects dead groups separately before removing.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #table Centroid DCS coordinates {x, z} -- @param #table Centroid DCS coordinates {x, z}
-- @return #table -- @return #table
function REDGCI_DISPATCHER:_AvailableFighters(Centroid) function REDGCIDISPATCHER:_AvailableFighters(Centroid)
local available = {} local available = {}
local to_remove = {} local to_remove = {}
for _, entry in pairs(self._pool) do for _, entry in pairs(self._pool) do
if entry.state == REDGCI_DISPATCHER.STATE_CAP then if entry.state == REDGCIDISPATCHER.STATE_CAP then
local grp = GROUP:FindByName(entry.groupName) local grp = GROUP:FindByName(entry.groupName)
if grp and grp:IsAlive() then if grp and grp:IsAlive() then
if Centroid then if Centroid then
@@ -844,20 +901,20 @@ function REDGCI_DISPATCHER:_AvailableFighters(Centroid)
end end
--- [INTERNAL] Stable cluster key. --- [INTERNAL] Stable cluster key.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #table Cluster -- @param #table Cluster
-- @return #string -- @return #string
function REDGCI_DISPATCHER:_ClusterKey(Cluster) function REDGCIDISPATCHER:_ClusterKey(Cluster)
local c = Cluster.coordinate or {} local c = Cluster.coordinate or {}
local cz = c.z or c.y or 0 local cz = c.z or c.y or 0
return string.format("C_%.0f_%.0f", (c.x or 0)/1000, cz/1000) return string.format("C_%.0f_%.0f", (c.x or 0)/1000, cz/1000)
end end
--- [INTERNAL] Count alive units in cluster. --- [INTERNAL] Count alive units in cluster.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #table Cluster -- @param #table Cluster
-- @return #number -- @return #number
function REDGCI_DISPATCHER:_ClusterSize(Cluster) function REDGCIDISPATCHER:_ClusterSize(Cluster)
local n = 0 local n = 0
for _, contact in pairs(Cluster.Contacts or {}) do for _, contact in pairs(Cluster.Contacts or {}) do
local grp = GROUP:FindByName(contact.groupname) local grp = GROUP:FindByName(contact.groupname)
@@ -868,10 +925,10 @@ end
--- [INTERNAL] Derive localized count and type tokens for a cluster. --- [INTERNAL] Derive localized count and type tokens for a cluster.
-- Reuses REDGCI CountTokens/TypeTokens tables. -- Reuses REDGCI CountTokens/TypeTokens tables.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #table Cluster -- @param #table Cluster
-- @return #string count_str, #string type_str, #number rng_km -- @return #string count_str, #string type_str, #number rng_km
function REDGCI_DISPATCHER:_ClusterPicture(Cluster) function REDGCIDISPATCHER:_ClusterPicture(Cluster)
local size = self:_ClusterSize(Cluster) local size = self:_ClusterSize(Cluster)
-- Count token (reuse REDGCI table if available) -- Count token (reuse REDGCI table if available)
@@ -888,7 +945,7 @@ function REDGCI_DISPATCHER:_ClusterPicture(Cluster)
end end
-- Type token from RCS (reuse REDGCI table if available) -- Type token from RCS (reuse REDGCI table if available)
local type_str = "FIGHTER" local type_str = "fighter"
local rcs_sum, rcs_n = 0.0, 0 local rcs_sum, rcs_n = 0.0, 0
for _, contact in pairs(Cluster.Contacts or {}) do for _, contact in pairs(Cluster.Contacts or {}) do
if contact.rcs then if contact.rcs then
@@ -904,9 +961,11 @@ function REDGCI_DISPATCHER:_ClusterPicture(Cluster)
elseif avg > (REDGCI.RCS_BOMBER_MIN or 20.0) then type_str = t.bomber elseif avg > (REDGCI.RCS_BOMBER_MIN or 20.0) then type_str = t.bomber
else type_str = t.machines else type_str = t.machines
end end
elseif REDGCI and REDGCI.TypeTokens then
local t = REDGCI.TypeTokens[self.Locale] or REDGCI.TypeTokens["en"]
type_str = t[type_str]
end end
-- Range to centroid from nearest CAP fighter
-- Range to centroid from nearest CAP fighter -- Range to centroid from nearest CAP fighter
local rng_km = 0 local rng_km = 0
local centroid = Cluster.coordinate local centroid = Cluster.coordinate
@@ -931,10 +990,10 @@ function REDGCI_DISPATCHER:_ClusterPicture(Cluster)
end end
--- [INTERNAL] Get up to 2 target group names from cluster (closest to centroid). --- [INTERNAL] Get up to 2 target group names from cluster (closest to centroid).
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #table Cluster -- @param #table Cluster
-- @return #string T1name, #string T2name -- @return #string T1name, #string T2name
function REDGCI_DISPATCHER:_ClusterTargets(Cluster) function REDGCIDISPATCHER:_ClusterTargets(Cluster)
local c = Cluster.coordinate or { x=0, z=0, y=0 } local c = Cluster.coordinate or { x=0, z=0, y=0 }
local cz = c.z or c.y or 0 local cz = c.z or c.y or 0
local groups = {} local groups = {}
@@ -953,17 +1012,18 @@ function REDGCI_DISPATCHER:_ClusterTargets(Cluster)
end end
--- [INTERNAL] Dispatch one pair against a cluster. --- [INTERNAL] Dispatch one pair against a cluster.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #table F1entry -- @param #table F1entry
-- @param #table F2entry (may be nil for solo) -- @param #table F2entry (may be nil for solo)
-- @param #table Cluster -- @param #table Cluster
-- @param #string ClusterKey -- @param #string ClusterKey
function REDGCI_DISPATCHER:_DispatchPair(F1entry, F2entry, Cluster, ClusterKey) function REDGCIDISPATCHER:_DispatchPair(F1entry, F2entry, Cluster, ClusterKey)
local t1, t2 = self:_ClusterTargets(Cluster) local t1, t2 = self:_ClusterTargets(Cluster)
if not t1 then if not t1 then
self:_Log("Dispatch: keine Ziele in " .. ClusterKey) self:_Log("Dispatch: keine Ziele in " .. ClusterKey)
return return
end end
if not self.dispatchcount then self.dispatchcount = 0 end
local f1 = F1entry.groupName local f1 = F1entry.groupName
local f2 = F2entry and F2entry.groupName or nil local f2 = F2entry and F2entry.groupName or nil
@@ -974,11 +1034,11 @@ function REDGCI_DISPATCHER:_DispatchPair(F1entry, F2entry, Cluster, ClusterKey)
"DISPATCH %s(%s)+%s(%s) -> %s/%s [%s]", "DISPATCH %s(%s)+%s(%s) -> %s/%s [%s]",
f1, cs1, f2 or "-", cs2, t1, t2 or "-", ClusterKey)) f1, cs1, f2 or "-", cs2, t1, t2 or "-", ClusterKey))
F1entry.state = REDGCI_DISPATCHER.STATE_ENGAGED F1entry.state = REDGCIDISPATCHER.STATE_ENGAGED
F1entry.pairedWith = f2 F1entry.pairedWith = f2
F1entry.engagement = ClusterKey F1entry.engagement = ClusterKey
if F2entry then if F2entry then
F2entry.state = REDGCI_DISPATCHER.STATE_ENGAGED F2entry.state = REDGCIDISPATCHER.STATE_ENGAGED
F2entry.pairedWith = f1 F2entry.pairedWith = f1
F2entry.engagement = ClusterKey F2entry.engagement = ClusterKey
end end
@@ -988,13 +1048,14 @@ function REDGCI_DISPATCHER:_DispatchPair(F1entry, F2entry, Cluster, ClusterKey)
gci2v2:SetLocale(self.Locale) gci2v2:SetLocale(self.Locale)
gci2v2:SetAIMode(true, self.HomeBaseName) gci2v2:SetAIMode(true, self.HomeBaseName)
gci2v2:SetSRS(self.SRSPath, self.SRSGCIFrequency, self.SRSMod, gci2v2:SetSRS(self.SRSPath, self.SRSGCIFrequency+self.dispatchcount, self.SRSMod,
self.SRSCulture, self.SRSGCIVoice, self.SRSPort, self.SRSSpeed) self.SRSCulture, self.SRSGCIVoice, self.SRSPort, self.SRSSpeed)
if self.SRSProvider then gci2v2:SetSRSProvider(self.SRSProvider) end if self.SRSProvider then gci2v2:SetSRSProvider(self.SRSProvider) end
gci2v2:SetAltOffset(self.AltOffset) gci2v2:SetAltOffset(self.AltOffset)
gci2v2:SetWFRange(self.WFRange) gci2v2:SetWFRange(self.WFRange)
gci2v2:SetDebug(self.Debug) gci2v2:SetDebug(self.Debug)
gci2v2.Coalition = self.Coalition gci2v2.Coalition = self.Coalition
gci2v2:SetRadioChannelList(self.RadioChannels)
-- Stop-Hook: release fighters back to pool -- Stop-Hook: release fighters back to pool
local dr = self local dr = self
@@ -1029,16 +1090,19 @@ function REDGCI_DISPATCHER:_DispatchPair(F1entry, F2entry, Cluster, ClusterKey)
clusterKey = ClusterKey, clusterKey = ClusterKey,
startTime = timer.getTime(), startTime = timer.getTime(),
} }
self.dispatchcount = (self.dispatchcount+1)%11
return self
end end
--- [INTERNAL] Called when engagement ends (REDGCI2v2 Stop fires). --- [INTERNAL] Called when engagement ends (REDGCI2v2 Stop fires).
-- AI → RTB waypoint then removed from pool. -- AI → RTB waypoint then removed from pool.
-- Human → RTB radio call, re-enters pool after 5 min. -- Human → RTB radio call, re-enters pool after 5 min.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #string ClusterKey -- @param #string ClusterKey
-- @param #table F1entry -- @param #table F1entry
-- @param #table F2entry -- @param #table F2entry
function REDGCI_DISPATCHER:_OnEngagementEnd(ClusterKey, F1entry, F2entry) function REDGCIDISPATCHER:_OnEngagementEnd(ClusterKey, F1entry, F2entry)
self:I(self.lid .. "Engagement end: " .. ClusterKey) self:I(self.lid .. "Engagement end: " .. ClusterKey)
self._engagements[ClusterKey] = nil self._engagements[ClusterKey] = nil
@@ -1048,19 +1112,19 @@ function REDGCI_DISPATCHER:_OnEngagementEnd(ClusterKey, F1entry, F2entry)
entry.engagement = nil entry.engagement = nil
if entry.isHuman then if entry.isHuman then
entry.state = REDGCI_DISPATCHER.STATE_RTB entry.state = REDGCIDISPATCHER.STATE_RTB
self:_Transmit("RTB_CALL", self:_Transmit("RTB_CALL",
{ CALLSIGN = entry.callsign or entry.groupName }, { CALLSIGN = entry.callsign or entry.groupName },
entry.groupName) entry.groupName)
local name = entry.groupName local name = entry.groupName
self:ScheduleOnce(300, function() self:ScheduleOnce(300, function()
if self._pool[name] then if self._pool[name] then
self._pool[name].state = REDGCI_DISPATCHER.STATE_UNKNOWN self._pool[name].state = REDGCIDISPATCHER.STATE_UNKNOWN
self:I(self.lid .. name .. " [HUMAN] released") self:I(self.lid .. name .. " [HUMAN] released")
end end
end) end)
else else
entry.state = REDGCI_DISPATCHER.STATE_RTB entry.state = REDGCIDISPATCHER.STATE_RTB
self:_RTBAircraft(entry.groupName) self:_RTBAircraft(entry.groupName)
local name = entry.groupName local name = entry.groupName
self:ScheduleOnce(600, function() self:ScheduleOnce(600, function()
@@ -1084,9 +1148,9 @@ end
--- [INTERNAL] Push RTB waypoint for AI group. --- [INTERNAL] Push RTB waypoint for AI group.
-- WaypointAir expects speed in km/h. -- WaypointAir expects speed in km/h.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
-- @param #string GroupName -- @param #string GroupName
function REDGCI_DISPATCHER:_RTBAircraft(GroupName) function REDGCIDISPATCHER:_RTBAircraft(GroupName)
if not self.HomeBase then return end if not self.HomeBase then return end
local grp = GROUP:FindByName(GroupName) local grp = GROUP:FindByName(GroupName)
if not grp or not grp:IsAlive() then return end if not grp or not grp:IsAlive() then return end
@@ -1114,9 +1178,9 @@ end
-- ───────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────
--- [INTERNAL] Start handler. --- [INTERNAL] Start handler.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:onafterStart(From, Event, To) function REDGCIDISPATCHER:onafterStart(From, Event, To)
self:I(self.lid .. "Start v" .. REDGCI_DISPATCHER.version) self:I(self.lid .. "Start v" .. REDGCIDISPATCHER.version)
if not self.Intel then self:E(self.lid .. "ERROR: no INTEL set!") return end if not self.Intel then self:E(self.lid .. "ERROR: no INTEL set!") return end
if not self.ZoneSet then self:E(self.lid .. "ERROR: no ZoneSet!") return end if not self.ZoneSet then self:E(self.lid .. "ERROR: no ZoneSet!") return end
@@ -1133,12 +1197,12 @@ function REDGCI_DISPATCHER:onafterStart(From, Event, To)
end end
end) end)
self:__Status(-REDGCI_DISPATCHER.TICK_INTERVAL) self:__Status(-REDGCIDISPATCHER.TICK_INTERVAL)
end end
--- [INTERNAL] Main dispatch tick. --- [INTERNAL] Main dispatch tick.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:onafterStatus(From, Event, To) function REDGCIDISPATCHER:onafterStatus(From, Event, To)
self:_RefreshHumanPool() self:_RefreshHumanPool()
@@ -1148,7 +1212,7 @@ function REDGCI_DISPATCHER:onafterStatus(From, Event, To)
if not entry.isHuman then if not entry.isHuman then
local grp = GROUP:FindByName(name) local grp = GROUP:FindByName(name)
if (not grp or not grp:IsAlive()) and if (not grp or not grp:IsAlive()) and
entry.state ~= REDGCI_DISPATCHER.STATE_RTB then entry.state ~= REDGCIDISPATCHER.STATE_RTB then
to_remove[#to_remove + 1] = name to_remove[#to_remove + 1] = name
end end
end end
@@ -1160,7 +1224,7 @@ function REDGCI_DISPATCHER:onafterStatus(From, Event, To)
local clusters = self.Intel:GetClusterTable() local clusters = self.Intel:GetClusterTable()
if not clusters then if not clusters then
self:__Status(-REDGCI_DISPATCHER.TICK_INTERVAL) self:__Status(-REDGCIDISPATCHER.TICK_INTERVAL)
return return
end end
@@ -1202,7 +1266,7 @@ function REDGCI_DISPATCHER:onafterStatus(From, Event, To)
if string.find(ek, key, 1, true) then active = active + 1 end if string.find(ek, key, 1, true) then active = active + 1 end
end end
local needed = math.ceil(size / REDGCI_DISPATCHER.UNITS_PER_PAIR) local needed = math.ceil(size / REDGCIDISPATCHER.UNITS_PER_PAIR)
local to_send = math.max(0, needed - active) local to_send = math.max(0, needed - active)
if to_send > 0 then if to_send > 0 then
@@ -1232,12 +1296,12 @@ function REDGCI_DISPATCHER:onafterStatus(From, Event, To)
end end
end end
self:__Status(-REDGCI_DISPATCHER.TICK_INTERVAL) self:__Status(-REDGCIDISPATCHER.TICK_INTERVAL)
end end
--- [INTERNAL] Stop handler. --- [INTERNAL] Stop handler.
-- @param #REDGCI_DISPATCHER self -- @param #REDGCIDISPATCHER self
function REDGCI_DISPATCHER:onafterStop(From, Event, To) function REDGCIDISPATCHER:onafterStop(From, Event, To)
self:I(self.lid .. "Stopped.") self:I(self.lid .. "Stopped.")
end end
+5
View File
@@ -80,6 +80,11 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Stratego.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ClientWatch.lua') __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ClientWatch.lua')
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Formation.lua') __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Formation.lua')
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/RedGCIKernel.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/RedGCI.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/RedGCI2v2.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/RedGCIDispatcher.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Airboss.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Airboss.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RecoveryTanker.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RecoveryTanker.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RescueHelo.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RescueHelo.lua' )
@@ -1270,6 +1270,15 @@ function INTEL:_UpdateContact(Contact)
else else
Contact.maneuvering = false Contact.maneuvering = false
end end
local typename = Contact.group:GetTypeName()
local base_rcs = INTEL.RCS_Table[typename]
if not base_rcs then
-- Fallback: category default
local cat = Contact.group:GetCategory()
base_rcs = (cat and INTEL.RCS_CategoryDefault[cat]) or INTEL.RCS_Reference
end
Contact.rcs = base_rcs
end end
end end
+4 -4
View File
@@ -76,10 +76,10 @@ Functional/Tiresias.lua
Functional/Stratego.lua Functional/Stratego.lua
Functional/ClientWatch.lua Functional/ClientWatch.lua
Functional/Formation.lua Functional/Formation.lua
Functional/REDGCI_KERNEL.lua Functional/RedGCIKernel.lua
Functional/REDGCI.lua Functional/RedGCI.lua
Functional/REDGCI2v2.lua Functional/RedGCI2v2.lua
Functional/REDGCI_DISPATCHER.lua Functional/RedGCIDispatcher.lua
Ops/Airboss.lua Ops/Airboss.lua
Ops/RecoveryTanker.lua Ops/RecoveryTanker.lua