From e2929a78c41bfadb6bb012bcb2ba6998449f8bff Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 19 Jul 2023 16:06:44 +0200 Subject: [PATCH] #AICSAR - correct code version --- Moose Development/Moose/Functional/AICSAR.lua | 424 ++++++++++++++++-- 1 file changed, 390 insertions(+), 34 deletions(-) diff --git a/Moose Development/Moose/Functional/AICSAR.lua b/Moose Development/Moose/Functional/AICSAR.lua index 922ae567e..b71050778 100644 --- a/Moose Development/Moose/Functional/AICSAR.lua +++ b/Moose Development/Moose/Functional/AICSAR.lua @@ -1,6 +1,8 @@ ---- **Functional** - AI CSAR system +--- **Functional** - AI CSAR system. +-- +-- === -- --- ## Main Features: +-- ## Features: -- -- * Send out helicopters to downed pilots -- * Rescues players and AI alike @@ -9,6 +11,7 @@ -- * Dedicated MASH zone -- * Some FSM functions to include in your mission scripts -- * Limit number of available helos +-- * SRS voice output via TTS or soundfiles -- -- === -- @@ -18,8 +21,8 @@ -- -- === -- --- ### Author: **applevangelist** --- Last Update April 2022 +-- ### Author: **Applevangelist** +-- Last Update July 2023 -- -- === -- @module Functional.AICSAR @@ -49,6 +52,11 @@ -- @field Core.Set#SET_CLIENT playerset Track if alive heli pilots are available. -- @field #boolean limithelos limit available number of helos going on mission (defaults to true) -- @field #number helonumber number of helos available (default: 3) +-- @field Utilities.FiFo#FIFO PilotStore +-- @field #number Altitude Default altitude setting for the helicopter FLIGHTGROUP 1500ft. +-- @field #number Speed Default speed setting for the helicopter FLIGHTGROUP is 100kn. +-- @field #boolean UseEventEject In case Event LandingAfterEjection isn't working, use set this to true. +-- @field #number Delay In case of UseEventEject wait this long until we spawn a landed pilot. -- @extends Core.Fsm#FSM @@ -74,7 +82,7 @@ -- -- @param #string Alias Name of this instance. -- -- @param #number Coalition Coalition as in coalition.side.BLUE, can also be passed as "blue", "red" or "neutral" -- -- @param #string Pilottemplate Pilot template name. --- -- @param #string Helotemplate Helicopter template name. +-- -- @param #string Helotemplate Helicopter template name. Set the template to "cold start". Hueys work best. -- -- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start. -- -- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue. -- local my_aicsar=AICSAR:New("Luftrettung",coalition.side.BLUE,"Downed Pilot","Rescue Helo",AIRBASE:FindByName("Test FARP"),ZONE:New("MASH")) @@ -85,10 +93,11 @@ -- my_aicsar.rescuezoneradius -- landing zone around downed pilot. Defaults to 200m -- my_aicsar.autoonoff -- stop operations when human helicopter pilots are around. Defaults to true. -- my_aicsar.verbose -- text messages to own coalition about ongoing operations. Defaults to true. --- my_aicsarlimithelos -- limit available number of helos going on mission (defaults to true) +-- my_aicsar.limithelos -- limit available number of helos going on mission (defaults to true) -- my_aicsar.helonumber -- number of helos available (default: 3) +-- my_aicsar.verbose -- boolean, set to `true`for message output on-screen -- --- ## Radio options +-- ## Radio output options -- -- Radio messages, soundfile names and (for SRS) lengths are defined in three enumerators, so you can customize, localize messages and soundfiles to your liking: -- @@ -98,7 +107,7 @@ -- EN = { -- INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!", -- INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!", --- PILOTDOWN = "Pilot down at ", -- note that this will be appended with the position +-- PILOTDOWN = "Mayday, mayday, mayday! Pilot down at ", -- note that this will be appended with the position in MGRS -- PILOTKIA = "Pilot KIA!", -- HELODOWN = "CSAR Helo Down!", -- PILOTRESCUED = "Pilot rescued!", @@ -134,8 +143,31 @@ -- }, -- } -- +-- ## Radio output via SRS and Text-To-Speech (TTS) +-- +-- Radio output can be done via SRS and Text-To-Speech. No extra sound files required! +-- [Initially, Have a look at the guide on setting up SRS TTS for Moose](https://github.com/FlightControl-Master/MOOSE_GUIDES/blob/master/documents/Moose%20TTS%20Setup%20Guide.pdf). +-- The text from the `AICSAR.Messages` table above is converted on the fly to an .ogg-file, which is then played back via SRS on the selected frequency and mdulation. +-- Hint - the small black window popping up shortly is visible in Single-Player only. +-- +-- To set up AICSAR for SRS TTS output, add e.g. the following to your script: +-- +-- -- setup for google TTS, radio 243 AM, SRS server port 5002 with a google standard-quality voice (google cloud account required) +-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\google.json") +-- +-- -- alternatively for MS Desktop TTS (voices need to be installed locally first!) +-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female") +-- +-- -- define a different voice for the downed pilot(s) +-- my_aicsar:SetPilotTTSVoice(MSRS.Voices.Google.Standard.en_AU_Standard_D,"en-AU","male") +-- +-- -- define another voice for the operator +-- my_aicsar:SetOperatorTTSVoice(MSRS.Voices.Google.Standard.en_GB_Standard_A,"en-GB","female") +-- +-- ## Radio output via preproduced soundfiles +-- -- The easiest way to add a soundfile to your mission is to use the "Sound to..." trigger in the mission editor. This will effectively --- save your sound file inside of the .miz mission file. +-- save your sound file inside of the .miz mission file. [Example soundfiles are located on github](https://github.com/FlightControl-Master/MOOSE_SOUND/tree/master/AICSAR) -- -- To customize or localize your texts and sounds, you can take e.g. the following approach to add a German language version: -- @@ -145,7 +177,7 @@ -- -- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so: -- --- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,5002) +-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,nil,5002) -- -- or -- @@ -159,7 +191,7 @@ -- @field #AICSAR AICSAR = { ClassName = "AICSAR", - version = "0.0.8", + version = "0.1.15", lid = "", coalition = coalition.side.BLUE, template = "", @@ -171,7 +203,7 @@ AICSAR = { pilotqueue = {}, pilotindex = 0, helos = {}, - verbose = true, + verbose = false, rescuezoneradius = 200, rescued = {}, autoonoff = true, @@ -192,6 +224,18 @@ AICSAR = { helonumber = 3, gettext = nil, locale ="en", -- default text language + SRSTTSRadio = false, + SRSGoogle = false, + SRSQ = nil, + SRSPilot = nil, + SRSPilotVoice = false, + SRSOperator = nil, + SRSOperatorVoice = false, + PilotStore = nil, + Speed = 100, + Altitude = 1500, + UseEventEject = false, + Delay = 100, } -- TODO Messages @@ -201,7 +245,7 @@ AICSAR.Messages = { EN = { INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!", INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!", - PILOTDOWN = "Pilot down at ", + PILOTDOWN = "Mayday, mayday, mayday! Pilot down at ", PILOTKIA = "Pilot KIA!", HELODOWN = "CSAR Helo Down!", PILOTRESCUED = "Pilot rescued!", @@ -210,7 +254,7 @@ AICSAR.Messages = { DE = { INITIALOK = "Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind!\nEin Hubschrauber sammelt Sie auf!", INITIALNOTOK = "Verstehe, Pilot. Sie sind zu weit weg von uns.\nViel Glück!", - PILOTDOWN = "Pilot abgestürzt: ", + PILOTDOWN = "Mayday, mayday, mayday! Pilot abgestürzt: ", PILOTKIA = "Pilot gefallen!", HELODOWN = "CSAR Hubschrauber verloren!", PILOTRESCUED = "Pilot gerettet!", @@ -303,10 +347,15 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) self.farp = FARP self.farpzone = MASHZone self.playerset = SET_CLIENT:New():FilterActive(true):FilterCategories("helicopter"):FilterStart() + self.UseEventEject = false + self.Delay = 300 -- Radio self.SRS = nil self.SRSRadio = false + self.SRSTTSRadio = false + self.SRSGoogle = false + self.SRSQ = nil self.SRSFrequency = 243 self.SRSPath = "\\" self.SRSModulation = radio.modulation.AM @@ -332,6 +381,9 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) -- Set some string id for output to DCS.log file. self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + --Pilot Store + self.PilotStore = FIFO:New() + -- Start State. self:SetStartState("Stopped") @@ -341,12 +393,15 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) self:AddTransition("*", "Status", "*") -- CSAR status update. self:AddTransition("*", "PilotDown", "*") -- Pilot down self:AddTransition("*", "PilotPickedUp", "*") -- Pilot in helo + self:AddTransition("*", "PilotUnloaded", "*") -- Pilot Unloaded from helo self:AddTransition("*", "PilotRescued", "*") -- Pilot Rescued self:AddTransition("*", "PilotKIA", "*") -- Pilot dead self:AddTransition("*", "HeloDown", "*") -- Helo dead + self:AddTransition("*", "HeloOnDuty", "*") -- Helo spawnd self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - self:HandleEvent(EVENTS.LandingAfterEjection) + self:HandleEvent(EVENTS.LandingAfterEjection,self._EventHandler) + self:HandleEvent(EVENTS.Ejection,self._EjectEventHandler) self:__Start(math.random(2,5)) @@ -400,7 +455,17 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) -- @param #AICSAR self -- @param #string From From state. -- @param #string Event Event. - -- @param #string To To state. + -- @param #string To To state. + -- @param #string PilotName + + --- On after "PilotUnloaded" event. + -- @function [parent=#AICSAR] OnAfterPilotUnloaded + -- @param #AICSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.FlightGroup#FLIGHTGROUP Helo + -- @param Ops.OpsGroup#OPSGROUP OpsGroup --- On after "PilotKIA" event. -- @function [parent=#AICSAR] OnAfterPilotKIA @@ -409,6 +474,14 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) -- @param #string Event Event. -- @param #string To To state. + --- On after "HeloOnDuty" event. + -- @function [parent=#AICSAR] OnAfterHeloOnDuty + -- @param #AICSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Group#GROUP Helo Helo group object + --- On after "HeloDown" event. -- @function [parent=#AICSAR] OnAfterHeloDown -- @param #AICSAR self @@ -451,7 +524,7 @@ function AICSAR:InitLocalization() return self end ---- [User] Switch sound output on and use SRS +--- [User] Switch sound output on and use SRS output for sound files. -- @param #AICSAR self -- @param #boolean OnOff Switch on (true) or off (false). -- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" @@ -462,10 +535,12 @@ end -- @return #AICSAR self function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port) self:T(self.lid .. "SetSRSRadio") - self:T(self.lid .. "SetSRSRadio to "..tostring(OnOff)) self.SRSRadio = OnOff and true + self.SRSTTSRadio = false self.SRSFrequency = Frequency or 243 self.SRSPath = Path or "c:\\" + self.SRS:SetLabel("ACSR") + self.SRS:SetCoalition(self.coalition) self.SRSModulation = Modulation or radio.modulation.AM local soundpath = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT" -- defaults to "l10n/DEFAULT/", i.e. add messages by "Sound to..." in the ME self.SRSSoundPath = SoundPath or soundpath @@ -477,6 +552,88 @@ function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port) return self end +--- [User] Switch sound output on and use SRS-TTS output. The voice will be used across all outputs, unless you define an extra voice for downed pilots and/or the operator. +-- See `AICSAR:SetPilotTTSVoice()` and `AICSAR:SetOperatorTTSVoice()` +-- @param #AICSAR self +-- @param #boolean OnOff Switch on (true) or off (false). +-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" +-- @param #number Frequency (Optional) Defaults to 243 (guard) +-- @param #number Modulation (Optional) Radio modulation. Defaults to radio.modulation.AM +-- @param #number Port (Optional) Port of the SRS, defaults to 5002. +-- @param #string Voice (Optional) The voice to be used. +-- @param #string Culture (Optional) The culture to be used, defaults to "en-GB" +-- @param #string Gender (Optional) The gender to be used, defaults to "male" +-- @param #string GoogleCredentials (Optional) Path to google credentials +-- @return #AICSAR self +function AICSAR:SetSRSTTSRadio(OnOff,Path,Frequency,Modulation,Port,Voice,Culture,Gender,GoogleCredentials) + self:T(self.lid .. "SetSRSTTSRadio") + self.SRSTTSRadio = OnOff and true + self.SRSRadio = false + self.SRSFrequency = Frequency or 243 + self.SRSPath = Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.SRSModulation = Modulation or radio.modulation.AM + self.SRSPort = Port or 5002 + if OnOff then + self.SRS = MSRS:New(Path,Frequency,Modulation,1) + self.SRS:SetPort(self.SRSPort) + self.SRS:SetCoalition(self.coalition) + self.SRS:SetLabel("ACSR") + self.SRS:SetVoice(Voice) + self.SRS:SetCulture(Culture) + self.SRS:SetGender(Gender) + if GoogleCredentials then + self.SRS:SetGoogle(GoogleCredentials) + self.SRSGoogle = true + end + self.SRSQ = MSRSQUEUE:New(self.alias) + end + return self +end + +--- [User] Set SRS TTS Voice of downed pilot. `AICSAR:SetSRSTTSRadio()` needs to be set first! +-- @param #AICSAR self +-- @param #string Voice The voice to be used, e.g. `MSRS.Voices.Google.Standard.en_US_Standard_J` for Google or `MSRS.Voices.Microsoft.David` for Microsoft. +-- Specific voices override culture and gender! +-- @param #string Culture (Optional) The culture to be used, defaults to "en-US" +-- @param #string Gender (Optional) The gender to be used, defaults to "male" +-- @return #AICSAR self +function AICSAR:SetPilotTTSVoice(Voice,Culture,Gender) + self:T(self.lid .. "SetPilotTTSVoice") + self.SRSPilotVoice = true + self.SRSPilot = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation,1) + self.SRSPilot:SetCoalition(self.coalition) + self.SRSPilot:SetVoice(Voice) + self.SRSPilot:SetCulture(Culture or "en-US") + self.SRSPilot:SetGender(Gender or "male") + self.SRSPilot:SetLabel("PILOT") + if self.SRS.google then + self.SRSPilot:SetGoogle(self.SRS.google) + end + return self +end + +--- [User] Set SRS TTS Voice of the rescue operator. `AICSAR:SetSRSTTSRadio()` needs to be set first! +-- @param #AICSAR self +-- @param #string Voice The voice to be used, e.g. `MSRS.Voices.Google.Standard.en_US_Standard_J` for Google or `MSRS.Voices.Microsoft.David` for Microsoft. +-- Specific voices override culture and gender! +-- @param #string Culture (Optional) The culture to be used, defaults to "en-GB" +-- @param #string Gender (Optional) The gender to be used, defaults to "female" +-- @return #AICSAR self +function AICSAR:SetOperatorTTSVoice(Voice,Culture,Gender) + self:T(self.lid .. "SetOperatorTTSVoice") + self.SRSOperatorVoice = true + self.SRSOperator = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation,1) + self.SRSOperator:SetCoalition(self.coalition) + self.SRSOperator:SetVoice(Voice) + self.SRSOperator:SetCulture(Culture or "en-GB") + self.SRSOperator:SetGender(Gender or "female") + self.SRSPilot:SetLabel("RESCUE") + if self.SRS.google then + self.SRSOperator:SetGoogle(self.SRS.google) + end + return self +end + --- [User] Switch sound output on and use normale (DCS) radio -- @param #AICSAR self -- @param #boolean OnOff Switch on (true) or off (false). @@ -517,11 +674,103 @@ function AICSAR:DCSRadioBroadcast(Soundfile,Duration,Subtitle) return self end ---- [Internal] Catch the landing after ejection and spawn a pilot in situ. +--- [Internal] Catch the ejection and save the pilot name -- @param #AICSAR self -- @param Core.Event#EVENTDATA EventData -- @return #AICSAR self -function AICSAR:OnEventLandingAfterEjection(EventData) +function AICSAR:_EjectEventHandler(EventData) + local _event = EventData -- Core.Event#EVENTDATA + if _event.IniPlayerName then + self.PilotStore:Push(_event.IniPlayerName) + self:T(self.lid.."Pilot Ejected: ".._event.IniPlayerName) + if self.UseEventEject then + -- get position and spawn in a template pilot + local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) + local _country = _event.initiator:getCountry() + local _coalition = coalition.getCountryCoalition( _country ) + local data = UTILS.DeepCopy(EventData) + Unit.destroy(_event.initiator) -- shagrat remove static Pilot model + self:ScheduleOnce(self.Delay,self._DelayedSpawnPilot,self,_LandingPos,_coalition) + end + end + return self +end + +--- [Internal] Spawn a pilot +-- @param #AICSAR self +-- @param Core.Point#COORDINATE _LandingPos Landing Postion +-- @param #number _coalition Coalition side +-- @return #AICSAR self +function AICSAR:_DelayedSpawnPilot(_LandingPos,_coalition) + + local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate()) + -- Mayday Message + local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale) + local text = "" + local setting = {} + setting.MGRS_Accuracy = self.MGRS_Accuracy + local location = _LandingPos:ToStringMGRS(setting) + local msgtxt = Text..location.."!" + location = string.gsub(location,"MGRS ","") + location = string.gsub(location,"%s+","") + location = string.gsub(location,"([%a%d])","%1;") -- "0 5 1 " + location = string.gsub(location,"0","zero") + location = string.gsub(location,"9","niner") + location = "MGRS;"..location + if self.SRSGoogle then + location = string.format("%s",location) + end + text = Text .. location .. "!" + local ttstext = Text .. location .. "! Repeat! "..location + if _coalition == self.coalition then + if self.verbose then + MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition) + -- MESSAGE:New(msgtxt,15,"AICSAR"):ToLog() + end + if self.SRSRadio then + local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) + sound:SetPlayWithSRS(true) + self.SRS:PlaySoundFile(sound,2) + elseif self.DCSRadio then + self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + if self.SRSPilotVoice then + self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1) + else + self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1) + end + end + end + + -- further processing + if _coalition == self.coalition and distancetofarp <= self.maxdistance then + -- in reach + self:T(self.lid .. "Spawning new Pilot") + self.pilotindex = self.pilotindex + 1 + local newpilot = SPAWN:NewWithAlias(self.template,string.format("%s-AICSAR-%d",self.template, self.pilotindex)) + newpilot:InitDelayOff() + newpilot:OnSpawnGroup( + function (grp) + self.pilotqueue[self.pilotindex] = grp + end + ) + newpilot:SpawnFromCoordinate(_LandingPos) + + self:__PilotDown(2,_LandingPos,true) + elseif _coalition == self.coalition and distancetofarp > self.maxdistance then + -- out of reach, apologies, too far off + self:T(self.lid .. "Pilot out of reach") + self:__PilotDown(2,_LandingPos,false) + end + return self +end + +--- [Internal] Catch the landing after ejection and spawn a pilot in situ. +-- @param #AICSAR self +-- @param Core.Event#EVENTDATA EventData +-- @param #boolean FromEject +-- @return #AICSAR self +function AICSAR:_EventHandler(EventData, FromEject) self:T(self.lid .. "OnEventLandingAfterEjection ID=" .. EventData.id) -- autorescue on off? @@ -531,25 +780,39 @@ function AICSAR:OnEventLandingAfterEjection(EventData) end end + if self.UseEventEject and (not FromEject) then return self end + local _event = EventData -- Core.Event#EVENTDATA -- get position and spawn in a template pilot local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) local _country = _event.initiator:getCountry() local _coalition = coalition.getCountryCoalition( _country ) - + -- DONE: add distance check local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate()) -- Mayday Message local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale) local text = "" + local setting = {} + setting.MGRS_Accuracy = self.MGRS_Accuracy + local location = _LandingPos:ToStringMGRS(setting) + local msgtxt = Text..location.."!" + location = string.gsub(location,"MGRS ","") + location = string.gsub(location,"%s+","") + location = string.gsub(location,"([%a%d])","%1;") -- "0 5 1 " + location = string.gsub(location,"0","zero") + location = string.gsub(location,"9","niner") + location = "MGRS;"..location + if self.SRSGoogle then + location = string.format("%s",location) + end + text = Text .. location .. "!" + local ttstext = Text .. location .. "! Repeat! "..location if _coalition == self.coalition then if self.verbose then - local setting = {} - setting.MGRS_Accuracy = self.MGRS_Accuracy - local location = _LandingPos:ToStringMGRS(setting) - text = Text .. location .. "!" - MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition) + MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition) + -- MESSAGE:New(msgtxt,15,"AICSAR"):ToLog() end if self.SRSRadio then local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) @@ -557,6 +820,12 @@ function AICSAR:OnEventLandingAfterEjection(EventData) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + if self.SRSPilotVoice then + self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1) + else + self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1) + end end end @@ -593,6 +862,11 @@ function AICSAR:_GetFlight() local newhelo = SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.random(1,10000)) :InitDelayOff() :InitUnControlled(true) + :OnSpawnGroup( + function(Group) + self:__HeloOnDuty(1,Group) + end + ) :Spawn() local nhelo=FLIGHTGROUP:New(newhelo) @@ -616,10 +890,15 @@ function AICSAR:_InitMission(Pilot,Index) -- Cargo transport assignment. local opstransport=OPSTRANSPORT:New(Pilot, pickupzone, self.farpzone) + local helo = self:_GetFlight() -- inject reservation helo.AICSARReserved = true + helo:SetDefaultAltitude(self.Altitude or 1500) + + helo:SetDefaultSpeed(self.Speed or 100) + -- Cargo transport assignment to first Huey group. helo:AddOpsTransport(opstransport) @@ -632,6 +911,10 @@ function AICSAR:_InitMission(Pilot,Index) self:__HeloDown(2,Helo,Index) end + local function AICHeloUnloaded(Helo,OpsGroup) + self:__PilotUnloaded(2,Helo,OpsGroup) + end + function helo:OnAfterLoadingDone(From,Event,To) AICPickedUp(helo,helo:GetCargoGroups(),Index) end @@ -640,6 +923,10 @@ function AICSAR:_InitMission(Pilot,Index) AICHeloDead(helo,Index) end + function helo:OnAfterUnloaded(From,Event,To,OpsGroupCargo) + AICHeloUnloaded(helo,OpsGroupCargo) + end + self.helos[Index] = helo return self @@ -650,7 +937,7 @@ end -- @param Wrapper.Group#GROUP Pilot The pilot to be rescued. -- @return #boolean outcome function AICSAR:_CheckInMashZone(Pilot) - self:T(self.lid .. "_CheckQueue") + self:T(self.lid .. "_CheckInMashZone") if Pilot:IsInZone(self.farpzone) then return true else @@ -658,6 +945,26 @@ function AICSAR:_CheckInMashZone(Pilot) end end +--- [User] Set default helo speed. Note - AI might have other ideas. Defaults to 100kn. +-- @param #AICSAR self +-- @param #number Knots Speed in knots. +-- @return #AICSAR self +function AICSAR:SetDefaultSpeed(Knots) + self:T(self.lid .. "SetDefaultSpeed") + self.Speed = Knots or 100 + return self +end + +--- [User] Set default helo altitudeAGL. Note - AI might have other ideas. Defaults to 1500ft. +-- @param #AICSAR self +-- @param #number Feet AGL set in feet. +-- @return #AICSAR self +function AICSAR:SetDefaultAltitude(Feet) + self:T(self.lid .. "SetDefaultAltitude") + self.Altitude = Feet or 1500 + return self +end + --- [Internal] Check helo queue -- @param #AICSAR self -- @return #AICSAR self @@ -694,12 +1001,14 @@ end --- [Internal] Check pilot queue for next mission -- @param #AICSAR self +-- @param Ops.OpsGroup#OPSGROUP OpsGroup -- @return #AICSAR self -function AICSAR:_CheckQueue() +function AICSAR:_CheckQueue(OpsGroup) self:T(self.lid .. "_CheckQueue") for _index, _pilot in pairs(self.pilotqueue) do local classname = _pilot.ClassName and _pilot.ClassName or "NONE" local name = _pilot.GroupName and _pilot.GroupName or "NONE" + local playername = "John Doe" local helocount = self:_CountHelos() --self:T("Looking at " .. classname .. " " .. name) -- find one w/o mission @@ -707,11 +1016,18 @@ function AICSAR:_CheckQueue() local flightgroup = self.helos[_index] -- Ops.FlightGroup#FLIGHTGROUP -- rescued? if self:_CheckInMashZone(_pilot) then - self:T("Pilot" .. _pilot.GroupName .. " rescued!") - _pilot:Destroy(false) + self:T("Pilot" .. _pilot.GroupName .. " rescued!") + if OpsGroup then + OpsGroup:Despawn(10) + else + _pilot:Destroy(true,10) + end self.pilotqueue[_index] = nil self.rescued[_index] = true - self:__PilotRescued(2) + if self.PilotStore:Count() > 0 then + playername = self.PilotStore:Pull() + end + self:__PilotRescued(2,playername) if flightgroup then flightgroup.AICSARReserved = false end @@ -765,7 +1081,7 @@ end -- @return #AICSAR self function AICSAR:onafterStatus(From, Event, To) self:T({From, Event, To}) - self:_CheckQueue() + --self:_CheckQueue() self:_CheckHelos() self:__Status(30) return self @@ -812,6 +1128,12 @@ function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + if self.SRSOperatorVoice then + self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) + else + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) + end end else local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALNOTOK",self.locale) @@ -826,8 +1148,15 @@ function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + if self.SRSOperatorVoice then + self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) + else + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) + end end end + self:_CheckQueue() return self end @@ -849,7 +1178,9 @@ function AICSAR:onafterPilotKIA(From, Event, To) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) - end + elseif self.SRSTTSRadio then + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) + end return self end @@ -873,6 +1204,12 @@ function AICSAR:onafterHeloDown(From, Event, To, Helo, Index) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + if self.SRSOperatorVoice then + self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) + else + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) + end end local findex = 0 local fhname = Helo:GetName() @@ -912,8 +1249,9 @@ end -- @param #string From -- @param #string Event -- @param #string To +-- @param #string PilotName -- @return #AICSAR self -function AICSAR:onafterPilotRescued(From, Event, To) +function AICSAR:onafterPilotRescued(From, Event, To, PilotName) self:T({From, Event, To}) local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTRESCUED",self.locale) if self.verbose then @@ -925,10 +1263,26 @@ function AICSAR:onafterPilotRescued(From, Event, To) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) end return self end +--- [Internal] onafterPilotUnloaded +-- @param #AICSAR self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Ops.FlightGroup#FLIGHTGROUP Helo +-- @param Ops.OpsGroup#OPSGROUP OpsGroup +-- @return #AICSAR self +function AICSAR:onafterPilotUnloaded(From, Event, To, Helo, OpsGroup) + self:T({From, Event, To}) + self:_CheckQueue(OpsGroup) + return self +end + --- [Internal] onafterPilotPickedUp -- @param #AICSAR self -- @param #string From @@ -950,6 +1304,8 @@ function AICSAR:onafterPilotPickedUp(From, Event, To, Helo, CargoTable, Index) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) end local findex = 0 local fhname = Helo:GetName()