diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 0d74a86d2..0c6450c2c 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -3511,12 +3511,12 @@ do -- AI_A2G_DISPATCHER self:F({"Defender Takeoff", DefenderGroup:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - local DefenderName = DefenderGroup:GetCallsign() + local DefenderName = DefenderGroup:GetCallsign() -- #string local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne.", "Wheels_up.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", wheels up.", DefenderGroup ) AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit end end @@ -3529,7 +3529,7 @@ do -- AI_A2G_DISPATCHER local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " patrolling.", "Patrolling.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", patrolling.", DefenderGroup ) end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) @@ -3548,7 +3548,7 @@ do -- AI_A2G_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Moving_on_to_ground_target.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end end @@ -3563,7 +3563,7 @@ do -- AI_A2G_DISPATCHER if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Engaging_ground_target.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end end @@ -3574,7 +3574,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", returning to base.", DefenderGroup ) Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end @@ -3587,7 +3587,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) + Dispatcher:MessageToPlayers( DefenderName .. ", lost control." ) if DefenderGroup:IsAboveRunway() then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) DefenderGroup:Destroy() @@ -3602,7 +3602,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", landing at base.", DefenderGroup ) if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) @@ -3659,7 +3659,7 @@ do -- AI_A2G_DISPATCHER self:F( { DefenderTarget = DefenderTarget } ) if DefenderTarget then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging!", "Wheels_up.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", wheels up.", DefenderGroup ) AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end end @@ -3675,7 +3675,7 @@ do -- AI_A2G_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Moving_on_to_ground_target.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) end @@ -3691,7 +3691,7 @@ do -- AI_A2G_DISPATCHER if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Engaging_ground_target.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end end @@ -3701,7 +3701,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", returning to base.", DefenderGroup ) self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) @@ -3732,7 +3732,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", landing at base.", DefenderGroup ) if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) diff --git a/Moose Development/Moose/Core/RadioSpeech.lua b/Moose Development/Moose/Core/RadioSpeech.lua new file mode 100644 index 000000000..618cb5161 --- /dev/null +++ b/Moose Development/Moose/Core/RadioSpeech.lua @@ -0,0 +1,310 @@ +--- **Core** - Makes the radio talk. +-- +-- === +-- +-- ## Features: +-- +-- * Send text strings using a vocabulary that is converted in spoken language. +-- * Possiblity to implement multiple language. +-- +-- === +-- +-- ### Authors: FlightControl +-- +-- @module Core.Speech +-- @image Core_Radio.JPG + +--- Makes the radio speak. +-- +-- # RADIOSPEECH usage +-- +-- +-- @type RADIOSPEECH +-- @extends Core.RadioQueue#RADIOQUEUE +RADIOSPEECH = { + ClassName = "RADIOSPEECH", + Vocabulary = { + EN = {}, + } +} + + +RADIOSPEECH.Vocabulary.EN = { + ["1"] = { "1", 0.25 }, + ["2"] = { "2", 0.25 }, + ["3"] = { "3", 0.30 }, + ["4"] = { "4", 0.35 }, + ["5"] = { "5", 0.35 }, + ["6"] = { "6", 0.42 }, + ["7"] = { "7", 0.38 }, + ["8"] = { "8", 0.20 }, + ["9"] = { "9", 0.32 }, + ["10"] = { "10", 0.35 }, + ["11"] = { "11", 0.40 }, + ["12"] = { "12", 0.42 }, + ["13"] = { "13", 0.38 }, + ["14"] = { "14", 0.42 }, + ["15"] = { "15", 0.42 }, + ["16"] = { "16", 0.52 }, + ["17"] = { "17", 0.59 }, + ["18"] = { "18", 0.40 }, + ["19"] = { "19", 0.47 }, + ["20"] = { "20", 0.38 }, + ["30"] = { "30", 0.29 }, + ["40"] = { "40", 0.35 }, + ["50"] = { "50", 0.32 }, + ["60"] = { "60", 0.44 }, + ["70"] = { "70", 0.48 }, + ["80"] = { "80", 0.26 }, + ["90"] = { "90", 0.36 }, + ["100"] = { "100", 0.55 }, + ["1000"] = { "1000", 1 }, + + ["chevy"] = { "chevy", 0.35 }, + ["colt"] = { "colt", 0.35 }, + ["springfield"] = { "springfield", 0.65 }, + ["dodge"] = { "dodge", 0.35 }, + ["enfield"] = { "enfield", 0.5 }, + ["ford"] = { "ford", 0.32 }, + ["pontiac"] = { "pontiac", 0.55 }, + ["uzi"] = { "uzi", 0.28 }, + + ["degrees"] = { "degrees", 0.5 }, + ["�"] = { "degrees", 0.5 }, + ["kilometers"] = { "kilometers", 0.65 }, + ["km"] = { "kilometers", 0.65 }, + ["miles"] = { "miles", 0.45 }, + ["mi"] = { "miles", 0.45 }, + + ["br"] = { "br", 1.1 }, + ["bra"] = { "bra", 0.3 }, + + + ["returning to base"] = { "returning_to_base", 0.85 }, + ["moving on to ground target"] = { "moving_on_to_ground_target", 1.20 }, + ["engaging ground target"] = { "engaging_ground_target", 1.20 }, + ["wheels up"] = { "wheels_up", 0.42 }, + ["landing at base"] = { "landing at base", 0.8 }, + ["patrolling"] = { "patrolling", 0.55 }, + + ["for"] = { "for", 0.31 }, + ["and"] = { "and", 0.31 }, + ["at"] = { "at", 0.3 }, + ["dot"] = { "dot", 0.26 }, + ["defender"] = { "defender", 0.45 }, +} + + + +--- Create a new RADIOSPEECH object for a given radio frequency/modulation. +-- @param #RADIOSPEECH self +-- @param #number frequency The radio frequency in MHz. +-- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM. +-- @return #RADIOSPEECH self The RADIOSPEECH object. +function RADIOSPEECH:New(frequency, modulation) + + -- Inherit base + local self = BASE:Inherit( self, RADIOQUEUE:New( frequency, modulation ) ) -- #RADIOSPEECH + + self.Language = "EN" + + self:BuildTree() + + return self +end + + +--- Add Sentence to the Speech collection. +-- @param #RADIOSPEECH self +-- @param #string RemainingSentence The remaining sentence during recursion. +-- @param #table Speech The speech node. +-- @param #string Sentence The full sentence. +-- @param #string Data The speech data. +-- @return #RADIOSPEECH self The RADIOSPEECH object. +function RADIOSPEECH:AddSentenceToSpeech( RemainingSentence, Speech, Sentence, Data ) + + self:I( { RemainingSentence, Speech, Sentence, Data } ) + + local Token, RemainingSentence = RemainingSentence:match( "^ *(%w+)(.*)" ) + self:I( { Token = Token, RemainingSentence = RemainingSentence } ) + + -- Is there a Token? + if Token then + + -- We check if the Token is already in the Speech collection. + if not Speech[Token] then + + -- There is not yet a vocabulary registered for this. + Speech[Token] = {} + + if RemainingSentence and RemainingSentence ~= "" then + -- We use recursion to iterate through the complete Sentence, and make a chain of Tokens. + -- The last Speech node in the collection contains the Sentence and the Data to be spoken. + -- This to ensure that during the actual speech: + -- - Complete sentences are being understood. + -- - Words without speech are ignored. + -- - Incorrect sequence of words are ignored. + Speech[Token].Next = {} + self:AddSentenceToSpeech( RemainingSentence, Speech[Token].Next, Sentence, Data ) + else + -- There is no remaining sentence, so we add speech to the Sentence. + -- The recursion stops here. + Speech[Token].Sentence = Sentence + Speech[Token].Data = Data + end + end + end +end + +--- Build the tree structure based on the language words, in order to find the correct sentences and to ignore incomprehensible words. +-- @param #RADIOSPEECH self +-- @return #RADIOSPEECH self The RADIOSPEECH object. +function RADIOSPEECH:BuildTree() + + self.Speech = {} + + for Language, Sentences in pairs( self.Vocabulary ) do + self:I( { Language = Language, Sentences = Sentences }) + self.Speech[Language] = {} + for Sentence, Data in pairs( Sentences ) do + self:I( { Sentence = Sentence, Data = Data } ) + self:AddSentenceToSpeech( Sentence, self.Speech[Language], Sentence, Data ) + end + end + + self:I( { Speech = self.Speech } ) + + return self +end + +--- Speak a sentence. +-- @param #RADIOSPEECH self +-- @param #string Sentence The sentence to be spoken. +function RADIOSPEECH:SpeakWords( Sentence, Speech ) + + local Word, RemainderSentence = Sentence:match( "^[^%d%a]*(%a*)(.*)" ) + + self:I( { Word = Word, Speech = Speech[Word], RemainderSentence = RemainderSentence } ) + + if Word and Word ~= "" then + + -- Construct of words + Word = Word:lower() + if Speech[Word] then + -- The end of the sentence has been reached. Now Speech.Next should be nil, otherwise there is an error. + if Speech[Word].Next == nil then + self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } ) + self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], "EN/" ) + else + if RemainderSentence and RemainderSentence ~= "" then + RemainderSentence = self:SpeakWords( RemainderSentence, Speech[Word].Next ) + end + end + end + end + + return RemainderSentence + +end + +--- Speak a sentence. +-- @param #RADIOSPEECH self +-- @param #string Sentence The sentence to be spoken. +function RADIOSPEECH:SpeakDigits( Sentence, Speech ) + + local Digits, RemainderSentence = Sentence:match( "^[^%a%d]*(%d*)(.*)" ) + + self:I( { Digits = Digits, Speech = Speech[Digits], RemainderSentence = RemainderSentence } ) + + if Digits and Digits ~= "" then + + -- Construct numbers + local Number = tonumber( Digits ) + local Multiple = nil + while Number >= 0 do + if Number > 1000 then + Multiple = math.floor( Number / 1000 ) * 1000 + elseif Number > 100 then + Multiple = math.floor( Number / 100 ) * 100 + elseif Number > 20 then + Multiple = math.floor( Number / 10 ) * 10 + elseif Number >= 0 then + Multiple = Number + end + Sentence = tostring( Multiple ) + if Speech[Sentence] then + self:I( { Speech = Speech[Sentence].Sentence, Data = Speech[Sentence].Data } ) + self:NewTransmission( Speech[Sentence].Data[1] .. ".wav", Speech[Sentence].Data[2], "EN/" ) + end + Number = Number - Multiple + Number = ( Number == 0 ) and -1 or Number + end + end + + return RemainderSentence +end + + +--- Speak a sentence. +-- @param #RADIOSPEECH self +-- @param #string Sentence The sentence to be spoken. +function RADIOSPEECH:SpeakSymbols( Sentence, Speech ) + + local Symbol, RemainderSentence = Sentence:match( "^[^%a%d]*(°*)(.*)" ) + + self:I( { Sentence = Sentence, Symbol = Symbol, Speech = Speech[Symbol], RemainderSentence = RemainderSentence } ) + + if Symbol and Symbol ~= "" then + local Word = nil + if Symbol == "°" then + Word = "degrees" + end + if Word then + if Speech[Word] then + self:I( { Speech = Speech[Word].Sentence, Data = Speech[Word].Data } ) + self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], "EN/" ) + end + end + end + + return RemainderSentence +end + + +--- Speak a sentence. +-- @param #RADIOSPEECH self +-- @param #string Sentence The sentence to be spoken. +function RADIOSPEECH:Speak( Sentence, Speech ) + + self:I( { Sentence, Speech } ) + + local Language = self.Language + + -- If there is no node for Speech, then we start at the first nodes of the language. + if not Speech then + Speech = self.Speech[Language] + end + + self:I( { Speech = Speech, Language = Language } ) + + self:NewTransmission( "_In.wav", 0.52, "EN/" ) + + repeat + + Sentence = self:SpeakWords( Sentence, Speech ) + + self:I( { Sentence = Sentence } ) + + Sentence = self:SpeakDigits( Sentence, Speech ) + + self:I( { Sentence = Sentence } ) + +-- Sentence = self:SpeakSymbols( Sentence, Speech ) +-- +-- self:I( { Sentence = Sentence } ) + + until not Sentence or Sentence == "" + + self:NewTransmission( "_Out.wav", 0.28, "EN/" ) + +end diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index eb0d8f61e..0a499fe25 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -21,6 +21,7 @@ __Moose.Include( 'Scripts/Moose/Core/Message.lua' ) __Moose.Include( 'Scripts/Moose/Core/Fsm.lua' ) __Moose.Include( 'Scripts/Moose/Core/Radio.lua' ) __Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' ) +__Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spawn.lua' ) __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' ) __Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index 8bec92065..de21616bd 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -225,7 +225,6 @@ do -- DETECTION MANAGER --- Set a command center to communicate actions to the players reporting to the command center. -- @param #DETECTION_MANAGER self - -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. -- @return #DETECTION_MANGER self function DETECTION_MANAGER:SetTacticalMenu( DispatcherMainMenuText, DispatcherMenuText ) @@ -279,7 +278,7 @@ do -- DETECTION MANAGER self.RadioQueue = nil - self.RadioQueue = RADIOQUEUE:New( self.RadioFrequency, self.RadioModulation ) + self.RadioQueue = RADIOSPEECH:New( self.RadioFrequency, self.RadioModulation ) self.RadioQueue.power = self.RadioPower self.RadioQueue:Start( 0.5 ) end @@ -292,27 +291,32 @@ do -- DETECTION MANAGER -- @param #string SoundPath The path pointing to the folder in the mission file. -- @param Wrapper.Group#GROUP DefenderGroup The defender group sending the message. -- @return #DETECTION_MANGER self - function DETECTION_MANAGER:MessageToPlayers( Message, SoundFile, SoundDuration, SoundPath, DefenderGroup ) + function DETECTION_MANAGER:MessageToPlayers( Message, DefenderGroup ) self:F( { Message = Message } ) - if not self.PreviousMessage or self.PreviousMessage ~= Message then - self.PreviousMessage = Message - if self.CC then - self.CC:MessageToCoalition( Message ) - end +-- if not self.PreviousMessage or self.PreviousMessage ~= Message then +-- self.PreviousMessage = Message +-- if self.CC then +-- self.CC:MessageToCoalition( Message ) +-- end +-- end + + if self.CC then + self.CC:MessageToCoalition( Message ) end - -- Here we handle the transmission of the voice over. - -- If for a certain reason the Defender does not exist, we use the coordinate of the airbase to send the message from. - if SoundFile then - local RadioQueue = self.RadioQueue -- Core.RadioQueue#RADIOQUEUE - local DefenderUnit = DefenderGroup:GetUnit(1) - if DefenderUnit and DefenderUnit:IsAlive() then - RadioQueue:SetSenderUnitName( DefenderUnit:GetName() ) - end - RadioQueue:NewTransmission( SoundFile, SoundDuration, SoundPath ) + Message = Message:gsub( "°", "degrees" ) + Message = Message:gsub( "(%d)%.(%d)", "%1dot%2" ) + + -- Here we handle the transmission of the voice over. + -- If for a certain reason the Defender does not exist, we use the coordinate of the airbase to send the message from. + local RadioQueue = self.RadioQueue -- Core.RadioQueue#RADIOQUEUE + local DefenderUnit = DefenderGroup:GetUnit(1) + if DefenderUnit and DefenderUnit:IsAlive() then + RadioQueue:SetSenderUnitName( DefenderUnit:GetName() ) end + RadioQueue:Speak( Message ) return self end