From d136470149026c5f409072dd244fd27fcf1c4741 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 6 Apr 2026 18:47:29 +0200 Subject: [PATCH] xx --- Moose Development/Moose/Functional/REDGCI.lua | 92 ++++- .../Moose/Functional/RedGCI2v2.lua | 56 +++ ...CI_DISPATCHER.lua => RedGCIDispatcher.lua} | 358 +++++++++++------- .../{REDGCI_KERNEL.lua => RedGCIKernelL.lua} | 0 Moose Development/Moose/Modules.lua | 5 + Moose Development/Moose/Ops/Intelligence.lua | 9 + Moose Setup/Moose.files | 8 +- 7 files changed, 369 insertions(+), 159 deletions(-) rename Moose Development/Moose/Functional/{REDGCI_DISPATCHER.lua => RedGCIDispatcher.lua} (83%) rename Moose Development/Moose/Functional/{REDGCI_KERNEL.lua => RedGCIKernelL.lua} (100%) diff --git a/Moose Development/Moose/Functional/REDGCI.lua b/Moose Development/Moose/Functional/REDGCI.lua index 15b23ba99..62a3e80cd 100644 --- a/Moose Development/Moose/Functional/REDGCI.lua +++ b/Moose Development/Moose/Functional/REDGCI.lua @@ -171,6 +171,21 @@ REDGCI.version = "2.0.0" -- @field #number WP_DISTANCE_FACTOR 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) -- ───────────────────────────────────────────────────────────── @@ -205,6 +220,7 @@ REDGCI.Messages = { MERGE_SPLASH = "{CALLSIGN}, good kill. RTB.", RADAR_ON = "{CALLSIGN}, radar on.", WEAPONS_FREE = "{CALLSIGN}, WEAPONS FREE.", + RADIO_SWITCH_CHANNEL = "{CALLSIGN}, switch to channel {CHNL} for GCI.", -- Pilot → GCI acknowledgements ACK_VECTOR = "Copy, {HDG}.", ACK_COMMIT = "Copy.", @@ -228,7 +244,7 @@ REDGCI.Messages = { NOTCH_ENTRY = "{CALLSIGN}, Ziel manövriert. Warten.", NOTCH_UPDATE = "{CALLSIGN}, Ziel {DIR_RL}, {RNG} Kilometer.", 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_OVERSHOOT = "{CALLSIGN}, Überschuss. {DIR_LR} ausbrechen.", MERGE_SEPARATION = "{CALLSIGN}, trennen. Steigen und neu ansetzen.", @@ -237,6 +253,7 @@ REDGCI.Messages = { MERGE_SPLASH = "{CALLSIGN}, Treffer. Heimkurs.", RADAR_ON = "{CALLSIGN}, Radar an.", WEAPONS_FREE = "{CALLSIGN}, Feuer frei.", + RADIO_SWITCH_CHANNEL = "{CALLSIGN}, umschalten auf Kanal {CHNL} für Anweisungen.", -- Pilot → GCI acknowledgements ACK_VECTOR = "Verstanden, Kurs {HDG}.", ACK_COMMIT = "Verstanden.", @@ -269,6 +286,7 @@ REDGCI.Messages = { MERGE_SPLASH = "{CALLSIGN}, молодец. Домой.", RADAR_ON = "{CALLSIGN}, локатор.", WEAPONS_FREE = "{CALLSIGN}, цель разрешена.", + RADIO_SWITCH_CHANNEL = "{CALLSIGN}, переключись на канал {CHNL} для указаний.", -- Pilot → GCI acknowledgements (kurz und militärisch) ACK_VECTOR = "Понял, курс {HDG}.", ACK_COMMIT = "Понял.", @@ -299,8 +317,8 @@ REDGCI.DirTokens = { -- @field #string de -- @field #string ru REDGCI.CountTokens = { - en = { single="single", pair="pair", group="group", biggroup="big group" }, - de = { single="einzeln", pair="Paar", group="Gruppe", biggroup="große Gruppe" }, + en = { single="single", pair="two", group="group", biggroup="big group" }, + de = { single="einzel", pair="zwei", group="Gruppe", biggroup="große Gruppe" }, ru = { single="одиночная", pair="пара", group="группа", biggroup="большая группа"}, } @@ -465,6 +483,42 @@ function REDGCI:SetSRS(Path, Frequency, Modulation, Culture, Voice, Port, Speed) return self 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 --@param #REDGCI self --@param #string Provider @@ -502,6 +556,8 @@ function REDGCI:SetPilotSRS(PilotCallsign, Culture, Voice, Speaker) return self end + + --- Set the target count manually (used when no INTEL source is attached). -- Overridden automatically when INTEL is active. -- @param #REDGCI self @@ -688,7 +744,7 @@ end -- @param #REDGCI self function REDGCI:_InitSRS() -- 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:SetLabel("GCI") self._msrs:SetCulture(self.SRSCulture) @@ -700,6 +756,9 @@ function REDGCI:_InitSRS() if self.SRSSpeaker then self._msrs:SetSpeakerPiper(self.SRSSpeaker) 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 -- Pilot voice (only when a pilot callsign has been configured) @@ -714,9 +773,11 @@ function REDGCI:_InitSRS() self._pilot_msrs:SetProvider(self.SRSProvider) end 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 - --self._pilot_queue = MSRSQUEUE:New("REDGCI_PILOT_" .. self.PilotCallsign) self._pilot_queue = self._srs_queue self:_Log("Pilot SRS ready: " .. self.PilotCallsign) end @@ -774,7 +835,7 @@ function REDGCI:_ParseTokens(TokenStr) end end end - --UTILS.PrintTableToLog(result) + UTILS.PrintTableToLog(result) return result end @@ -926,6 +987,7 @@ function REDGCI:_Transmit(TokenStr, DirLR, DirRL, Priority) BRG = tok.brg and string.format("%03d", tok.brg) or "", COUNT = self:_CountToken(self._target_count), TYPE = self:_TypeToken(self._target_type), + CHNL = tok.channel or 1, } local text = self:_FillTemplate(template, vars) @@ -1419,7 +1481,21 @@ function REDGCI:onafterStart(From, Event, To) self:T(self.lid .. "Ready. Fighter='" .. self.FighterGroupName .. "' Target='" .. self.TargetGroupName .. "' 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 self:__Status(-2) end diff --git a/Moose Development/Moose/Functional/RedGCI2v2.lua b/Moose Development/Moose/Functional/RedGCI2v2.lua index 68b6380c8..8b249a500 100644 --- a/Moose Development/Moose/Functional/RedGCI2v2.lua +++ b/Moose Development/Moose/Functional/RedGCI2v2.lua @@ -220,6 +220,21 @@ REDGCI2v2.SPLIT_RANGE = 60000 -- 60km pre-COMMIT split -- @field #number OVERRIDE_TICKS 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 -- ───────────────────────────────────────────────────────────── @@ -276,6 +291,7 @@ function REDGCI2v2:New(Fighter1Group, Fighter2Group, self.SRSVoice = MSRS.Voices.Google.Standard.ru_RU_Standard_D self.SRSPort = 5002 self.FreqOffset = 0.5 + self.PilotRadios = {} -- ── Tactic state ────────────────────────────────────────── self._tactic = nil -- chosen at COMMIT @@ -340,6 +356,42 @@ function REDGCI2v2:SetSRS(Path, Frequency, Modulation, Culture, Voice, Port, Spe return self 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 --@param #REDGCI2v2 self --@param #string Provider @@ -492,6 +544,9 @@ function REDGCI2v2:_MakeGCI(FighterGroup, TargetGroup, Callsign, FreqOffSet) if self.SRSProvider then gci:SetSRSProvider(self.SRSProvider) end + if self.translateEnabled == true then + self._msrs:SetAutoTranslate(self.translateProvider,self.translateLanguage) + end if self.WFRange then gci:SetWFRange(self.WFRange) end @@ -500,6 +555,7 @@ function REDGCI2v2:_MakeGCI(FighterGroup, TargetGroup, Callsign, FreqOffSet) gci:SetAltOffset(self.AltOffset) gci:SetDebug(self.Debug) gci:SetMissileFiringFlag(self._missilerangeflag) + gci:SetRadioChannelList(self.RadioChannels) return gci end diff --git a/Moose Development/Moose/Functional/REDGCI_DISPATCHER.lua b/Moose Development/Moose/Functional/RedGCIDispatcher.lua similarity index 83% rename from Moose Development/Moose/Functional/REDGCI_DISPATCHER.lua rename to Moose Development/Moose/Functional/RedGCIDispatcher.lua index d7a4f71ee..40f7dff29 100644 --- a/Moose Development/Moose/Functional/REDGCI_DISPATCHER.lua +++ b/Moose Development/Moose/Functional/RedGCIDispatcher.lua @@ -11,13 +11,13 @@ -- ### Author: **Applevangelist** -- -- === --- @module Functional.REDGCI_DISPATCHER +-- @module Functional.REDGCIDISPATCHER -- @image Func_RedGCI.png ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- REDGCI_DISPATCHER class --- @type REDGCI_DISPATCHER #REDGCI\_DISPATCHER +--- **REDGCIDISPATCHER** class, extends Core.Fsm#FSM class +-- @type REDGCIDISPATCHER -- @field #string ClassName -- @field #string version -- @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: -- @@ -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. -- --- @field #REDGCI_DISPATCHER #REDGCI\_DISPATCHER -REDGCI_DISPATCHER = { - ClassName = "REDGCI_DISPATCHER", +-- @field #REDGCIDISPATCHER +REDGCIDISPATCHER = { + ClassName = "REDGCIDISPATCHER", version = "2.0.0", } @@ -181,23 +181,24 @@ REDGCI_DISPATCHER = { -- @field #string STATE_ENGAGED -- @field #string STATE_RTB -- @field #string STATE_UNKNOWN -REDGCI_DISPATCHER.TICK_INTERVAL = 30.0 -REDGCI_DISPATCHER.UNITS_PER_PAIR = 2 -REDGCI_DISPATCHER.ORBIT_SPEED_KMPH = 600 -REDGCI_DISPATCHER.ORBIT_ALT_M = 4500 -REDGCI_DISPATCHER.AI_PER_ZONE = 2 +REDGCIDISPATCHER.TICK_INTERVAL = 30.0 +REDGCIDISPATCHER.UNITS_PER_PAIR = 2 +REDGCIDISPATCHER.ORBIT_SPEED_KMPH = 600 +REDGCIDISPATCHER.ORBIT_ALT_M = 4500 +REDGCIDISPATCHER.AI_PER_ZONE = 2 -REDGCI_DISPATCHER.STATE_CAP = "CAP" -REDGCI_DISPATCHER.STATE_ENGAGED = "ENGAGED" -REDGCI_DISPATCHER.STATE_RTB = "RTB" -REDGCI_DISPATCHER.STATE_UNKNOWN = "UNKNOWN" +REDGCIDISPATCHER.STATE_CAP = "CAP" +REDGCIDISPATCHER.STATE_ENGAGED = "ENGAGED" +REDGCIDISPATCHER.STATE_RTB = "RTB" +REDGCIDISPATCHER.STATE_UNKNOWN = "UNKNOWN" -- ───────────────────────────────────────────────────────────── -- Localized messages (token system, analogous to REDGCI) -- ───────────────────────────────────────────────────────────── ---- @type REDGCI_DISPATCHER.Messages -REDGCI_DISPATCHER.Messages = { +--- +-- @type Messages +REDGCIDISPATCHER.Messages = { en = { RTB_CALL = "{CALLSIGN}, mission complete. RTB, refuel and rearm.", 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 -- ───────────────────────────────────────────────────────────── ---- Create a new REDGCI_DISPATCHER instance. --- @param #REDGCI_DISPATCHER self +--- Create a new REDGCIDISPATCHER instance. +-- @param #REDGCIDISPATCHER self -- @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 Ops.Intel#INTEL Intel Laufende INTEL-Instanz -- @param #number Coalition coalition.side.RED / BLUE --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:New(TemplateName, ZoneSet, Intel, Coalition) +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:New(TemplateName, ZoneSet, Intel, Coalition) local self = BASE:Inherit(self, FSM:New()) - self.lid = "REDGCI_DISPATCHER | " + self.lid = "REDGCIDISPATCHER | " self.TemplateName = TemplateName self.ZoneSet = ZoneSet self.Intel = Intel @@ -241,9 +257,9 @@ function REDGCI_DISPATCHER:New(TemplateName, ZoneSet, Intel, Coalition) self.Debug = true self.AltOffset = -700 self.WFRange = 20000 - self.OrbitAlt = REDGCI_DISPATCHER.ORBIT_ALT_M - self.OrbitSpeed = REDGCI_DISPATCHER.ORBIT_SPEED_KMPH - self.AiPerZone = REDGCI_DISPATCHER.AI_PER_ZONE + self.OrbitAlt = REDGCIDISPATCHER.ORBIT_ALT_M + self.OrbitSpeed = REDGCIDISPATCHER.ORBIT_SPEED_KMPH + self.AiPerZone = REDGCIDISPATCHER.AI_PER_ZONE self.StartCallsign = 100 self.SRSPath = nil @@ -254,6 +270,7 @@ function REDGCI_DISPATCHER:New(TemplateName, ZoneSet, Intel, Coalition) self.SRSPort = 5002 self.SRSSpeed = 1 self.SRSProvider = nil + self.SRSFreqPilotsStart = self.SRSFreq + 1 self.ClientSet = nil @@ -275,7 +292,7 @@ function REDGCI_DISPATCHER:New(TemplateName, ZoneSet, Intel, Coalition) self:AddTransition("Running", "Status", "Running") self:AddTransition("Running", "Stop", "Stopped") - self:I(self.lid .. "v" .. REDGCI_DISPATCHER.version .. " created.") + self:I(self.lid .. "v" .. REDGCIDISPATCHER.version .. " created.") return self end @@ -284,10 +301,10 @@ end -- ───────────────────────────────────────────────────────────── --- Set the home plate. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #string BaseName --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetHomeBase(BaseName) +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetHomeBase(BaseName) self.HomeBaseName = BaseName local ab = AIRBASE:FindByName(BaseName) if ab then self.HomeBase = ab @@ -296,55 +313,55 @@ function REDGCI_DISPATCHER:SetHomeBase(BaseName) end --- Set the client SET. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param Core.Set#SET_CLIENT ClientSet --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetClientSet(ClientSet) +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetClientSet(ClientSet) self.ClientSet = ClientSet return self end --- Set orbit altitude and speed. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #number AltMSL Altitude MSL in meters (default 4500) -- @param #number SpeedKmph Speed in kph (km/h) (default 600) --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetOrbitParameters(AltMSL, SpeedKmph) - self.OrbitAlt = AltMSL or REDGCI_DISPATCHER.ORBIT_ALT_M - self.OrbitSpeed = SpeedKmph or REDGCI_DISPATCHER.ORBIT_SPEED_KMPH +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetOrbitParameters(AltMSL, SpeedKmph) + self.OrbitAlt = AltMSL or REDGCIDISPATCHER.ORBIT_ALT_M + self.OrbitSpeed = SpeedKmph or REDGCIDISPATCHER.ORBIT_SPEED_KMPH return self end --- Set number of AI spawned per CAP zone. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #number N Default 2 --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetAiPerZone(N) +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetAiPerZone(N) self.AiPerZone = N or 2 return self end --- Set starting callsign number. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #number N Default 100 --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetStartCallsign(N) +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetStartCallsign(N) self.StartCallsign = N or 100 self._callsign_counter = self.StartCallsign return self end --- Set locale for radio messages. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #string Locale "ru", "de", "en" --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetLocale(Locale) +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetLocale(Locale) self.Locale = Locale or "ru" return self end --- Configure Dispatcher SRS. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #string Path (Optional) Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- @param #number Frequency Single Frequency, e.g. 124 -- @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 #number Port (Optional) The SRS Server port, defaults to 5002. -- @param #number Speed (Optional) Voice speed, defaults to 1.0 (100%) --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetSRS(Path, Frequency, Modulation, Culture, Voice, Port, Speed) +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetSRS(Path, Frequency, Modulation, Culture, Voice, Port, Speed) self.SRSPath = Path self.SRSFreq = Frequency or self.SRSFreq 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.SRSPort = Port or self.SRSPort self.SRSSpeed = Speed or 1 + self.SRSFreqPilotsStart = self.SRSFreq + 1 return self 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). --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #number Frequency Single Frequency, e.g. 125 -- @param #string Voice The SRS Voice to be used. --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetSRSGCIDetails(StartFrequency,Voice) +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetSRSGCIDetails(StartFrequency,Voice) self:I({F=StartFrequency,V=Voice}) self.SRSGCIFrequency = StartFrequency or 124 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 --- Set SRS provider. --- @param #REDGCI_DISPATCHER self --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetSRSProvider(Provider) +-- @param #REDGCIDISPATCHER self +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetSRSProvider(Provider) self.SRSProvider = Provider return self end --- Set altitude offset for intercept geometry. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #number Meters Default -700 (shoot up) --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetAltOffset(Meters) +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetAltOffset(Meters) self.AltOffset = Meters or -700 return self end --- Set weapons-free range. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #number Meters Default 20000 (20 km) --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetWFRange(Meters) +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetWFRange(Meters) self.WFRange = Meters or 20000 return self end --- Enable or disable automatic respawn after engagement. -- 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 #number Delay Seconds after RTB before respawn (default 300) -- @param #number Count Number of AI to spawn (default 2) --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetRespawn(Enabled, Delay, Count) +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetRespawn(Enabled, Delay, Count) self.RespawnEnabled = Enabled ~= false self.RespawnDelay = Delay or 300 self.RespawnCount = Count or 2 @@ -417,10 +471,10 @@ function REDGCI_DISPATCHER:SetRespawn(Enabled, Delay, Count) end --- Enable debug logging. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #boolean OnOff --- @return #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:SetDebug(OnOff) +-- @return #REDGCIDISPATCHER self +function REDGCIDISPATCHER:SetDebug(OnOff) self.Debug = OnOff ~= false return self end @@ -430,16 +484,16 @@ end -- ───────────────────────────────────────────────────────────── --- [INTERNAL] --- @param #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:_Log(msg) +-- @param #REDGCIDISPATCHER self +function REDGCIDISPATCHER:_Log(msg) if self.Debug then env.info(self.lid .. msg) end end --- [INTERNAL] Initialize TEXTANDSOUND localization. --- @param #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:_InitLocalization() - self._gettext = TEXTANDSOUND:New("REDGCI_DISPATCHER", "en") - for locale, entries in pairs(REDGCI_DISPATCHER.Messages) do +-- @param #REDGCIDISPATCHER self +function REDGCIDISPATCHER:_InitLocalization() + self._gettext = TEXTANDSOUND:New("REDGCIDISPATCHER", "en") + for locale, entries in pairs(REDGCIDISPATCHER.Messages) do local loc = string.lower(tostring(locale)) for id, text in pairs(entries) do self._gettext:AddEntry(loc, tostring(id), text) @@ -448,11 +502,11 @@ function REDGCI_DISPATCHER:_InitLocalization() end --- [INTERNAL] Fill {PLACEHOLDER} tokens in a template string. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #string Template -- @param #table Vars -- @return #string -function REDGCI_DISPATCHER:_FillTemplate(Template, Vars) +function REDGCIDISPATCHER:_FillTemplate(Template, Vars) self:I({T=Template,V=Vars}) return (string.gsub(Template, "{([%w_]+)}", function(key) return tostring(Vars[key] or "") @@ -460,11 +514,11 @@ function REDGCI_DISPATCHER:_FillTemplate(Template, Vars) end --- [INTERNAL] Dispatch a radio transmission via SRS queue. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #string Key Message key from Messages table -- @param #table Vars Token variables -- @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 local template = self._gettext:GetEntry(Key, self.Locale) @@ -488,19 +542,19 @@ function REDGCI_DISPATCHER:_Transmit(Key, Vars, GroupName) end --- [INTERNAL] Next callsign number. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @return #number -function REDGCI_DISPATCHER:_NextCallsign() +function REDGCIDISPATCHER:_NextCallsign() local cs = self._callsign_counter self._callsign_counter = self._callsign_counter + 1 return cs end --- [INTERNAL] Localized tactic name for radio. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #string Tactic "PINCER","HIGH_LOW","STAGGER","TRAIL","GIRAFFE" -- @return #string -function REDGCI_DISPATCHER:_TacticToken(Tactic) +function REDGCIDISPATCHER:_TacticToken(Tactic) local tokens = { en = { PINCER="pincer", HIGH_LOW="high-low", STAGGER="stagger", TRAIL="trail", GIRAFFE="giraffe" }, @@ -514,10 +568,10 @@ function REDGCI_DISPATCHER:_TacticToken(Tactic) end --- [INTERNAL] Send transmission to all CAP fighters in pool. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #string Key Message key -- @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 local template = self._gettext:GetEntry(Key, self.Locale) @@ -551,10 +605,10 @@ end --- [INTERNAL] Get callsign string from group. -- Reads part after '#' in group name, e.g. "TEMPLATE#101" → "101". -- Falls back to GetCustomCallSign(), then last part of group name. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param Wrapper.Group#GROUP Grp -- @return #string -function REDGCI_DISPATCHER:_GetCallsign(Grp) +function REDGCIDISPATCHER:_GetCallsign(Grp) if not Grp then return "GCI" end local name = Grp:GetName() or "GCI" local after_hash = string.match(name, "#(%d+)") @@ -565,10 +619,10 @@ function REDGCI_DISPATCHER:_GetCallsign(Grp) end --- [INTERNAL] Check if group has a human pilot. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param Wrapper.Group#GROUP Grp -- @return #boolean -function REDGCI_DISPATCHER:_IsHuman(Grp) +function REDGCIDISPATCHER:_IsHuman(Grp) if not Grp then return false end if self.ClientSet then local found = false @@ -587,26 +641,29 @@ function REDGCI_DISPATCHER:_IsHuman(Grp) end --- [INTERNAL] Initialize SRS. --- @param #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:_InitSRS() - self._msrs = MSRS:New(self.SRSPath, 121.5, self.SRSMod) +-- @param #REDGCIDISPATCHER self +function REDGCIDISPATCHER:_InitSRS() + self._msrs = MSRS:New(self.SRSPath, self.SRSFreq, self.SRSMod) self._msrs:SetPort(self.SRSPort) self._msrs:SetLabel("DISPATCH") self._msrs:SetCulture(self.SRSCulture) self._msrs:SetVoice(self.SRSVoice) 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 - self._srs_queue = MSRSQUEUE:New("REDGCI_DISPATCHER") + self._srs_queue = MSRSQUEUE:New("REDGCIDISPATCHER") end --- [INTERNAL] Spawn AI group and assign orbit. -- Uses SPAWN:NewWithAlias() so group name contains callsign. -- Template controls parking, hot/cold, heading. -- OnSpawnGroup callback ensures group is alive before orbit assignment. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param Core.Zone#ZONE Zone -- @return Wrapper.Group#GROUP or nil -function REDGCI_DISPATCHER:_SpawnAI(Zone) +function REDGCIDISPATCHER:_SpawnAI(Zone) local cs = self:_NextCallsign() local alias = self.TemplateName .. "#" .. cs @@ -625,13 +682,13 @@ function REDGCI_DISPATCHER:_SpawnAI(Zone) end --- [INTERNAL] Toggle radar emission on the fighter group. --- @param #REDGCI self +-- @param #REDGCIDISPATCHER self -- @param #boolean On -- @param #number Delay -function REDGCI_DISPATCHER:_SetRadar(Grp,On,Delay) +function REDGCIDISPATCHER:_SetRadar(Grp,On,Delay) --if not self.IsAIPlane then return end if Delay then - self:ScheduleOnce(Delay,REDGCI_DISPATCHER._SetRadar,self,Grp,On) + self:ScheduleOnce(Delay,REDGCIDISPATCHER._SetRadar,self,Grp,On) return end local grp = Grp @@ -648,13 +705,13 @@ function REDGCI_DISPATCHER:_SetRadar(Grp,On,Delay) end --- [INTERNAL] Toggle weapons free on the fighter group. --- @param #REDGCI self +-- @param #REDGCIDISPATCHER self -- @param #boolean On -- @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 Delay then - self:ScheduleOnce(Delay,REDGCI_DISPATCHER._SetWeaponsFree,self,Grp,On) + self:ScheduleOnce(Delay,REDGCIDISPATCHER._SetWeaponsFree,self,Grp,On) return end local grp = Grp @@ -677,10 +734,10 @@ end --- [INTERNAL] Assign orbit task in CAP zone. -- WaypointAir expects speed in kph or km/h; TaskOrbit expects mps or m/s. -- Altitude variation prevents all groups flying at exact same level. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param Wrapper.Group#GROUP Grp -- @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 @@ -733,18 +790,18 @@ function REDGCI_DISPATCHER:_AssignOrbit(Grp, Zone) end --- [INTERNAL] Register fighter in pool. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param Wrapper.Group#GROUP Grp -- @param Core.Zone#ZONE Zone -- @param #boolean IsHuman -function REDGCI_DISPATCHER:_RegisterFighter(Grp, Zone, IsHuman) +function REDGCIDISPATCHER:_RegisterFighter(Grp, Zone, IsHuman) local name = Grp:GetName() self._pool[name] = { groupName = name, group = Grp, zone = Zone, isHuman = IsHuman or false, - state = REDGCI_DISPATCHER.STATE_CAP, + state = REDGCIDISPATCHER.STATE_CAP, pairedWith = nil, engagement = nil, callsign = self:_GetCallsign(Grp), @@ -755,8 +812,8 @@ function REDGCI_DISPATCHER:_RegisterFighter(Grp, Zone, IsHuman) end --- [INTERNAL] Refresh human pool from ClientSet. --- @param #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:_RefreshHumanPool() +-- @param #REDGCIDISPATCHER self +function REDGCIDISPATCHER:_RefreshHumanPool() if not self.ClientSet then return end self.ClientSet:ForEachClient(function(client) @@ -767,8 +824,8 @@ function REDGCI_DISPATCHER:_RefreshHumanPool() if self._pool[name] then self._pool[name].isHuman = true - if self._pool[name].state ~= REDGCI_DISPATCHER.STATE_ENGAGED and - self._pool[name].state ~= REDGCI_DISPATCHER.STATE_RTB then + if self._pool[name].state ~= REDGCIDISPATCHER.STATE_ENGAGED and + self._pool[name].state ~= REDGCIDISPATCHER.STATE_RTB then local in_zone = false if self.ZoneSet then self.ZoneSet:ForEachZone(function(zone) @@ -779,7 +836,7 @@ function REDGCI_DISPATCHER:_RefreshHumanPool() end) end self._pool[name].state = in_zone and - REDGCI_DISPATCHER.STATE_CAP or REDGCI_DISPATCHER.STATE_UNKNOWN + REDGCIDISPATCHER.STATE_CAP or REDGCIDISPATCHER.STATE_UNKNOWN end return end @@ -805,15 +862,15 @@ end --- [INTERNAL] Return available fighters sorted by priority. -- Humans first, then by distance to cluster centroid. -- Safe iteration — collects dead groups separately before removing. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #table Centroid DCS coordinates {x, z} -- @return #table -function REDGCI_DISPATCHER:_AvailableFighters(Centroid) +function REDGCIDISPATCHER:_AvailableFighters(Centroid) local available = {} local to_remove = {} 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) if grp and grp:IsAlive() then if Centroid then @@ -844,20 +901,20 @@ function REDGCI_DISPATCHER:_AvailableFighters(Centroid) end --- [INTERNAL] Stable cluster key. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #table Cluster -- @return #string -function REDGCI_DISPATCHER:_ClusterKey(Cluster) +function REDGCIDISPATCHER:_ClusterKey(Cluster) local c = Cluster.coordinate or {} local cz = c.z or c.y or 0 return string.format("C_%.0f_%.0f", (c.x or 0)/1000, cz/1000) end --- [INTERNAL] Count alive units in cluster. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #table Cluster -- @return #number -function REDGCI_DISPATCHER:_ClusterSize(Cluster) +function REDGCIDISPATCHER:_ClusterSize(Cluster) local n = 0 for _, contact in pairs(Cluster.Contacts or {}) do local grp = GROUP:FindByName(contact.groupname) @@ -868,10 +925,10 @@ end --- [INTERNAL] Derive localized count and type tokens for a cluster. -- Reuses REDGCI CountTokens/TypeTokens tables. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #table Cluster -- @return #string count_str, #string type_str, #number rng_km -function REDGCI_DISPATCHER:_ClusterPicture(Cluster) +function REDGCIDISPATCHER:_ClusterPicture(Cluster) local size = self:_ClusterSize(Cluster) -- Count token (reuse REDGCI table if available) @@ -888,7 +945,7 @@ function REDGCI_DISPATCHER:_ClusterPicture(Cluster) end -- 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 for _, contact in pairs(Cluster.Contacts or {}) do 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 else type_str = t.machines end + elseif REDGCI and REDGCI.TypeTokens then + local t = REDGCI.TypeTokens[self.Locale] or REDGCI.TypeTokens["en"] + type_str = t[type_str] end - -- Range to centroid from nearest CAP fighter -- Range to centroid from nearest CAP fighter local rng_km = 0 local centroid = Cluster.coordinate @@ -931,10 +990,10 @@ function REDGCI_DISPATCHER:_ClusterPicture(Cluster) end --- [INTERNAL] Get up to 2 target group names from cluster (closest to centroid). --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #table Cluster -- @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 cz = c.z or c.y or 0 local groups = {} @@ -953,17 +1012,18 @@ function REDGCI_DISPATCHER:_ClusterTargets(Cluster) end --- [INTERNAL] Dispatch one pair against a cluster. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #table F1entry -- @param #table F2entry (may be nil for solo) -- @param #table Cluster -- @param #string ClusterKey -function REDGCI_DISPATCHER:_DispatchPair(F1entry, F2entry, Cluster, ClusterKey) +function REDGCIDISPATCHER:_DispatchPair(F1entry, F2entry, Cluster, ClusterKey) local t1, t2 = self:_ClusterTargets(Cluster) if not t1 then self:_Log("Dispatch: keine Ziele in " .. ClusterKey) return end + if not self.dispatchcount then self.dispatchcount = 0 end local f1 = F1entry.groupName 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]", f1, cs1, f2 or "-", cs2, t1, t2 or "-", ClusterKey)) - F1entry.state = REDGCI_DISPATCHER.STATE_ENGAGED + F1entry.state = REDGCIDISPATCHER.STATE_ENGAGED F1entry.pairedWith = f2 F1entry.engagement = ClusterKey if F2entry then - F2entry.state = REDGCI_DISPATCHER.STATE_ENGAGED + F2entry.state = REDGCIDISPATCHER.STATE_ENGAGED F2entry.pairedWith = f1 F2entry.engagement = ClusterKey end @@ -988,13 +1048,14 @@ function REDGCI_DISPATCHER:_DispatchPair(F1entry, F2entry, Cluster, ClusterKey) gci2v2:SetLocale(self.Locale) 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) if self.SRSProvider then gci2v2:SetSRSProvider(self.SRSProvider) end gci2v2:SetAltOffset(self.AltOffset) gci2v2:SetWFRange(self.WFRange) gci2v2:SetDebug(self.Debug) gci2v2.Coalition = self.Coalition + gci2v2:SetRadioChannelList(self.RadioChannels) -- Stop-Hook: release fighters back to pool local dr = self @@ -1029,16 +1090,19 @@ function REDGCI_DISPATCHER:_DispatchPair(F1entry, F2entry, Cluster, ClusterKey) clusterKey = ClusterKey, startTime = timer.getTime(), } + + self.dispatchcount = (self.dispatchcount+1)%11 + return self end --- [INTERNAL] Called when engagement ends (REDGCI2v2 Stop fires). -- AI → RTB waypoint then removed from pool. -- Human → RTB radio call, re-enters pool after 5 min. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #string ClusterKey -- @param #table F1entry -- @param #table F2entry -function REDGCI_DISPATCHER:_OnEngagementEnd(ClusterKey, F1entry, F2entry) +function REDGCIDISPATCHER:_OnEngagementEnd(ClusterKey, F1entry, F2entry) self:I(self.lid .. "Engagement end: " .. ClusterKey) self._engagements[ClusterKey] = nil @@ -1048,19 +1112,19 @@ function REDGCI_DISPATCHER:_OnEngagementEnd(ClusterKey, F1entry, F2entry) entry.engagement = nil if entry.isHuman then - entry.state = REDGCI_DISPATCHER.STATE_RTB + entry.state = REDGCIDISPATCHER.STATE_RTB self:_Transmit("RTB_CALL", { CALLSIGN = entry.callsign or entry.groupName }, entry.groupName) local name = entry.groupName self:ScheduleOnce(300, function() 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") end end) else - entry.state = REDGCI_DISPATCHER.STATE_RTB + entry.state = REDGCIDISPATCHER.STATE_RTB self:_RTBAircraft(entry.groupName) local name = entry.groupName self:ScheduleOnce(600, function() @@ -1084,9 +1148,9 @@ end --- [INTERNAL] Push RTB waypoint for AI group. -- WaypointAir expects speed in km/h. --- @param #REDGCI_DISPATCHER self +-- @param #REDGCIDISPATCHER self -- @param #string GroupName -function REDGCI_DISPATCHER:_RTBAircraft(GroupName) +function REDGCIDISPATCHER:_RTBAircraft(GroupName) if not self.HomeBase then return end local grp = GROUP:FindByName(GroupName) if not grp or not grp:IsAlive() then return end @@ -1114,9 +1178,9 @@ end -- ───────────────────────────────────────────────────────────── --- [INTERNAL] Start handler. --- @param #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:onafterStart(From, Event, To) - self:I(self.lid .. "Start v" .. REDGCI_DISPATCHER.version) +-- @param #REDGCIDISPATCHER self +function REDGCIDISPATCHER:onafterStart(From, Event, To) + 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.ZoneSet then self:E(self.lid .. "ERROR: no ZoneSet!") return end @@ -1133,12 +1197,12 @@ function REDGCI_DISPATCHER:onafterStart(From, Event, To) end end) - self:__Status(-REDGCI_DISPATCHER.TICK_INTERVAL) + self:__Status(-REDGCIDISPATCHER.TICK_INTERVAL) end --- [INTERNAL] Main dispatch tick. --- @param #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:onafterStatus(From, Event, To) +-- @param #REDGCIDISPATCHER self +function REDGCIDISPATCHER:onafterStatus(From, Event, To) self:_RefreshHumanPool() @@ -1148,7 +1212,7 @@ function REDGCI_DISPATCHER:onafterStatus(From, Event, To) if not entry.isHuman then local grp = GROUP:FindByName(name) 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 end end @@ -1160,7 +1224,7 @@ function REDGCI_DISPATCHER:onafterStatus(From, Event, To) local clusters = self.Intel:GetClusterTable() if not clusters then - self:__Status(-REDGCI_DISPATCHER.TICK_INTERVAL) + self:__Status(-REDGCIDISPATCHER.TICK_INTERVAL) return end @@ -1202,7 +1266,7 @@ function REDGCI_DISPATCHER:onafterStatus(From, Event, To) if string.find(ek, key, 1, true) then active = active + 1 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) if to_send > 0 then @@ -1232,12 +1296,12 @@ function REDGCI_DISPATCHER:onafterStatus(From, Event, To) end end - self:__Status(-REDGCI_DISPATCHER.TICK_INTERVAL) + self:__Status(-REDGCIDISPATCHER.TICK_INTERVAL) end --- [INTERNAL] Stop handler. --- @param #REDGCI_DISPATCHER self -function REDGCI_DISPATCHER:onafterStop(From, Event, To) +-- @param #REDGCIDISPATCHER self +function REDGCIDISPATCHER:onafterStop(From, Event, To) self:I(self.lid .. "Stopped.") end diff --git a/Moose Development/Moose/Functional/REDGCI_KERNEL.lua b/Moose Development/Moose/Functional/RedGCIKernelL.lua similarity index 100% rename from Moose Development/Moose/Functional/REDGCI_KERNEL.lua rename to Moose Development/Moose/Functional/RedGCIKernelL.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 528c86bac..1edbaf066 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -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/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/RecoveryTanker.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RescueHelo.lua' ) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 6392932d6..f69accb57 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -1270,6 +1270,15 @@ function INTEL:_UpdateContact(Contact) else Contact.maneuvering = false 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 diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 449d544dc..8fa46d998 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -76,10 +76,10 @@ Functional/Tiresias.lua Functional/Stratego.lua Functional/ClientWatch.lua Functional/Formation.lua -Functional/REDGCI_KERNEL.lua -Functional/REDGCI.lua -Functional/REDGCI2v2.lua -Functional/REDGCI_DISPATCHER.lua +Functional/RedGCIKernel.lua +Functional/RedGCI.lua +Functional/RedGCI2v2.lua +Functional/RedGCIDispatcher.lua Ops/Airboss.lua Ops/RecoveryTanker.lua