diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 72cf73a0c..8dd213952 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -315,7 +315,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if imperial. function SETTINGS:IsImperial() - return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsMetric()) + return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsImperial()) end --- Sets the SETTINGS LL accuracy. diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index b22a27939..e37570f35 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1418,7 +1418,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) end -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats. -- if self.Repeat then - -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" ) + -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" ) -- end end @@ -2971,9 +2971,9 @@ end function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - -- if not self.SpawnTemplate then - -- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) - -- end + -- if not self.SpawnTemplate then + -- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) + -- end local SpawnTemplate if self.TweakedTemplate ~= nil and self.TweakedTemplate == true then @@ -3254,20 +3254,20 @@ function SPAWN:_OnDeadOrCrash( EventData ) local unit=UNIT:FindByName(EventData.IniUnitName) - if unit then - - local EventPrefix = self:_GetPrefixFromGroupName(unit.GroupName) - + if unit then + + local EventPrefix = self:_GetPrefixFromGroupName(unit.GroupName) + if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! self:T( { "Dead event: " .. EventPrefix } ) if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - self.AliveUnits = self.AliveUnits - 1 - - self:T( "Alive Units: " .. self.AliveUnits ) - end - + self.AliveUnits = self.AliveUnits - 1 + + self:T( "Alive Units: " .. self.AliveUnits ) + end + end end end @@ -3365,12 +3365,16 @@ end -- @return #boolean True = Continue Scheduler function SPAWN:_SpawnCleanUpScheduler() self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) - + local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + local IsHelo = false + while SpawnGroup do - + + IsHelo = SpawnGroup:IsHelicopter() + local SpawnUnits = SpawnGroup:GetUnits() for UnitID, UnitData in pairs( SpawnUnits ) do @@ -3383,8 +3387,8 @@ function SPAWN:_SpawnCleanUpScheduler() self:T( { SpawnUnitName, Stamp } ) if Stamp.Vec2 then - if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then - local NewVec2 = SpawnUnit:GetVec2() + if (SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1) or IsHelo then + local NewVec2 = SpawnUnit:GetVec2() or {x=0, y=0} if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then @@ -3402,8 +3406,8 @@ function SPAWN:_SpawnCleanUpScheduler() Stamp.Time = nil end else - if SpawnUnit:InAir() == false then - Stamp.Vec2 = SpawnUnit:GetVec2() + if SpawnUnit:InAir() == false or (IsHelo and SpawnUnit:GetLife() <= 1) then + Stamp.Vec2 = SpawnUnit:GetVec2() or {x=0, y=0} if (SpawnUnit:GetVelocityKMH() < 1) then Stamp.Time = timer.getTime() end diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 994a760ba..7894f25f6 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -111,7 +111,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.20" +AUTOLASE.version = "0.1.21" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -430,6 +430,7 @@ function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Cultu self.SRS:SetCulture(self.Culture) self.SRS:SetPort(self.Port) self.SRS:SetVoice(self.Voice) + self.SRS:SetCoalition(self.coalition) if self.PathToGoogleKey then self.SRS:SetGoogle(self.PathToGoogleKey) end @@ -672,10 +673,13 @@ function AUTOLASE:ShowStatus(Group,Unit) if playername then local settings = _DATABASE:GetPlayerSettings(playername) if settings then + --self:I("Get Settings ok!") if settings:IsA2G_MGRS() then locationstring = entry.coordinate:ToStringMGRS(settings) elseif settings:IsA2G_LL_DMS() then - locationstring = entry.coordinate:ToStringLLDMS() + locationstring = entry.coordinate:ToStringLLDMS(settings) + elseif settings:IsA2G_BR() then + locationstring = entry.coordinate:ToStringBR(Group:GetCoordinate() or Unit:GetCoordinate(),settings) end end end @@ -948,7 +952,9 @@ function AUTOLASE:onafterMonitor(From, Event, To) settings.MGRS_Accuracy = precision locationstring = unit:GetCoordinate():ToStringMGRS(settings) elseif _SETTINGS:IsA2G_LL_DMS() then - locationstring = unit:GetCoordinate():ToStringLLDMS() + locationstring = unit:GetCoordinate():ToStringLLDMS(_SETTINGS) + elseif _SETTINGS:IsA2G_BR() then + locationstring = unit:GetCoordinate():ToStringBULLS(self.coalition,_SETTINGS) end local laserspot = { -- #AUTOLASE.LaserSpot diff --git a/Moose Development/Moose/Functional/Designate.lua b/Moose Development/Moose/Functional/Designate.lua index 72aaf9959..d6a6be101 100644 --- a/Moose Development/Moose/Functional/Designate.lua +++ b/Moose Development/Moose/Functional/Designate.lua @@ -213,7 +213,7 @@ do -- DESIGNATE -- In order to prevent an overflow of designations due to many Detected Targets, there is a -- Maximum Designations scope that is set in the DesignationObject. -- - -- The method @{#DESIGNATE.SetMaximumDesignations}() will put a limit on the amount of designations put in scope of the DesignationObject. + -- The method @{#DESIGNATE.SetMaximumDesignations}() will put a limit on the amount of designations (target groups) put in scope of the DesignationObject. -- Using the menu system, the player can "forget" a designation, so that gradually a new designation can be put in scope when detected. -- -- # 4. Laser codes @@ -562,7 +562,8 @@ do -- DESIGNATE end - --- Set the maximum amount of designations. + --- Set the maximum amount of designations (target groups). This will put a limit on the amount of designations in scope. + -- Using the menu system, the player can "forget" a designation, so that gradually a new designation can be put in scope when detected. -- @param #DESIGNATE self -- @param #number MaximumDesignations -- @return #DESIGNATE @@ -602,7 +603,7 @@ do -- DESIGNATE end - --- Set the maximum amount of markings FACs will do, per designated target group. + --- Set the maximum amount of markings FACs will do, per designated target group. This will limit the number of parallelly marked units of a target group. -- @param #DESIGNATE self -- @param #number MaximumMarkings Maximum markings FACs will do, per designated target group. -- @return #DESIGNATE @@ -911,8 +912,8 @@ do -- DESIGNATE for DesignateIndex, Designating in pairs( self.Designating ) do local DetectedItem = DetectedItems[DesignateIndex] if DetectedItem then - local Report = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup ):Text( ", " ) - DetectedReport:Add( string.rep( "-", 140 ) ) + local Report = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup, nil, true ):Text( ", " ) + DetectedReport:Add( string.rep( "-", 40 ) ) DetectedReport:Add( " - " .. Report ) if string.find( Designating, "L" ) then DetectedReport:Add( " - " .. "Lasing Targets" ) @@ -1192,8 +1193,8 @@ do -- DESIGNATE local MarkingCount = 0 local MarkedTypes = {} - local ReportTypes = REPORT:New() - local ReportLaserCodes = REPORT:New() + --local ReportTypes = REPORT:New() + --local ReportLaserCodes = REPORT:New() TargetSetUnit:Flush( self ) @@ -1243,8 +1244,8 @@ do -- DESIGNATE if not Recce then self:F( "Lasing..." ) - self.RecceSet:Flush( self) - + --self.RecceSet:Flush( self) + for RecceGroupID, RecceGroup in pairs( self.RecceSet:GetSet() ) do for UnitID, UnitData in pairs( RecceGroup:GetUnits() or {} ) do @@ -1282,13 +1283,13 @@ do -- DESIGNATE -- OK. We have assigned for the Recce a TargetUnit. We can exit the function. MarkingCount = MarkingCount + 1 local TargetUnitType = TargetUnit:GetTypeName() - --RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.", - -- 5, self.AttackSet, DesignateName ) + RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.", + 10, self.AttackSet, DesignateName ) if not MarkedTypes[TargetUnitType] then MarkedTypes[TargetUnitType] = true - ReportTypes:Add(TargetUnitType) + --ReportTypes:Add(TargetUnitType) end - ReportLaserCodes:Add(RecceUnit.LaserCode) + --ReportLaserCodes:Add(RecceUnit.LaserCode) return end else @@ -1303,16 +1304,16 @@ do -- DESIGNATE if Recce then Recce:LaseOff() - Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() "out of LOS. Cancelling lase!", 5, self.AttackSet, self.DesignateName ) + Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() "out of LOS. Cancelling lase!", 10, self.AttackSet, self.DesignateName ) end else --MarkingCount = MarkingCount + 1 local TargetUnitType = TargetUnit:GetTypeName() if not MarkedTypes[TargetUnitType] then MarkedTypes[TargetUnitType] = true - ReportTypes:Add(TargetUnitType) + --ReportTypes:Add(TargetUnitType) end - ReportLaserCodes:Add(RecceUnit.LaserCode) + --ReportLaserCodes:Add(RecceUnit.LaserCode) end end end @@ -1322,19 +1323,19 @@ do -- DESIGNATE local TargetUnitType = TargetUnit:GetTypeName() if not MarkedTypes[TargetUnitType] then MarkedTypes[TargetUnitType] = true - ReportTypes:Add(TargetUnitType) + --ReportTypes:Add(TargetUnitType) end - ReportLaserCodes:Add(Recce.LaserCode) - --Recce:MessageToSetGroup( self.DesignateName .. ": Marking " .. TargetUnit:GetTypeName() .. " with laser " .. Recce.LaserCode .. ".", 5, self.AttackSet ) + --ReportLaserCodes:Add(Recce.LaserCode) + Recce:MessageToSetGroup( self.DesignateName .. ": Marking " .. TargetUnit:GetTypeName() .. " with laser " .. Recce.LaserCode .. ".", 10, self.AttackSet ) end end end end ) - local MarkedTypesText = ReportTypes:Text(', ') - local MarkedLaserCodesText = ReportLaserCodes:Text(', ') - self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName ) + --local MarkedTypesText = ReportTypes:Text(', ') + --local MarkedLaserCodesText = ReportLaserCodes:Text(', ') + --self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName ) self:__Lasing( -self.LaseDuration, Index, Duration, LaserCodeRequested ) diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index 04f8e237c..d48937517 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -2152,8 +2152,9 @@ do -- DETECTION_UNITS -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. -- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for. -- @param Core.Settings#SETTINGS Settings Message formatting settings to use. + -- @param #boolean ForceA2GCoordinate Set creation of A2G coordinate -- @return Core.Report#REPORT The report of the detection items. - function DETECTION_UNITS:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings ) + function DETECTION_UNITS:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings, ForceA2GCoordinate ) self:F( { DetectedItem = DetectedItem } ) local DetectedItemID = self:GetDetectedItemID( DetectedItem ) @@ -2188,7 +2189,11 @@ do -- DETECTION_UNITS -- TODO: solve Index reference local DetectedItemCoordinate = self:GetDetectedItemCoordinate( DetectedItem ) local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings ) - + + if ForceA2GCoordinate then + DetectedItemCoordText = DetectedItemCoordinate:ToStringA2G(AttackGroup,Settings) + end + local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem ) local Report = REPORT:New() diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 55b6e8548..1e6341a0d 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -492,7 +492,6 @@ end --- Disable F10 menu for all players. -- @param #FOX self --- @param #boolean switch If true debug mode on. If false/nil debug mode off -- @return #FOX self function FOX:SetDisableF10Menu() @@ -501,6 +500,16 @@ function FOX:SetDisableF10Menu() return self end +--- Enable F10 menu for all players. +-- @param #FOX self +-- @return #FOX self +function FOX:SetEnableF10Menu() + + self.menudisabled=false + + return self +end + --- Set default player setting for missile destruction. -- @param #FOX self -- @param #boolean switch If true missiles are destroyed. If false/nil missiles are not destroyed. @@ -1162,7 +1171,7 @@ function FOX:OnEventBirth(EventData) -- Add F10 radio menu for player. if not self.menudisabled then - self:ScheduleOnce(0.1, FOX._AddF10Commands, self, _unitname) + self:ScheduleOnce(0.1, self._AddF10Commands, self, _unitName) end -- Player data. @@ -1429,10 +1438,10 @@ function FOX:_AddF10Commands(_unitName) end else - self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.", _unitName)) + self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.", _unitName or "unknown")) end else - self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.", _unitName)) + self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.", _unitName or "unknown")) end end diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 35cbdddd6..35697c148 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -45,7 +45,8 @@ -- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)** -- -- ### Contributions: [FlightControl](https://forums.eagle.ru/member.php?u=89536), [Ciribob](https://forums.eagle.ru/member.php?u=112175) --- +-- ### SRS Additions: Applevangelist +-- -- === -- @module Functional.Range -- @image Range.JPG @@ -101,6 +102,10 @@ -- @field #boolean targetsheet If true, players can save their target sheets. Rangeboss will not work if targetsheets do not save. -- @field #string targetpath Path where to save the target sheets. -- @field #string targetprefix File prefix for target sheet files. +-- @field Sound.SRS#MSRS controlmsrs +-- @field Sound.SRS#MSRSQUEUE controlsrsQ +-- @field Sound.SRS#MSRS instructmsrs +-- @field Sound.SRS#MSRSQUEUE instructsrsQ -- @extends Core.Fsm#FSM --- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven @@ -225,6 +230,11 @@ -- -- By default, the sound files are placed in the "Range Soundfiles/" folder inside the mission (.miz) file. Another folder can be specified via the @{#RANGE.SetSoundfilesPath}(*path*) function. -- +-- ## Voice output via SRS +-- +-- Alternatively, the voice output can be fully done via SRS, **no sound file additions needed**. Set up SRS with @{#RANGE.SetSRS}(). Range control and instructor frequencies and voices can then be +-- set via @{#RANGE.SetSRSRangeControl}() and @{#RANGE.SetSRSRangeInstructor}() +-- -- # Persistence -- -- To automatically save bombing results to disk, use the @{#RANGE.SetAutosave}() function. Bombing results will be saved as csv file in your "Saved Games\DCS.openbeta\Logs" directory. @@ -568,7 +578,7 @@ RANGE.MenuF10Root = nil --- Range script version. -- @field #string version -RANGE.version = "2.4.0" +RANGE.version = "2.5.0" -- TODO list: -- TODO: Verbosity level for messages. @@ -833,7 +843,7 @@ function RANGE:onafterStart() end -- Init range control. - if self.rangecontrolfreq then + if self.rangecontrolfreq and not self.useSRS then -- Radio queue. self.rangecontrol = RADIOQUEUE:New( self.rangecontrolfreq, nil, self.rangename ) @@ -859,7 +869,7 @@ function RANGE:onafterStart() self.rangecontrol:Start( 1, 0.1 ) -- Init range control. - if self.instructorfreq then + if self.instructorfreq and not self.useSRS then -- Radio queue. self.instructor = RADIOQUEUE:New( self.instructorfreq, nil, self.rangename ) @@ -1177,7 +1187,91 @@ function RANGE:TrackMissilesOFF() return self end ---- Enable range control and set frequency. +--- Use SRS Simple-Text-To-Speech for transmissions. No sound files necessary. +-- @param #RANGE self +-- @param #string PathToSRS Path to SRS directory. +-- @param #number Port SRS port. Default 5002. +-- @param #number Coalition Coalition side, e.g. coalition.side.BLUE or coalition.side.RED +-- @param #number Frequency Frequency to use, defaults to 256 (same as rangecontrol) +-- @param #number Modulation Modulation to use, defaults to radio.modulation.AM +-- @param #number Volume Volume, between 0.0 and 1.0. Defaults to 1.0 +-- @param #string PathToGoogleKey Path to Google TTS credentials. +-- @return #RANGE self +function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, PathToGoogleKey) + if PathToSRS then + + self.useSRS=true + + self.controlmsrs=MSRS:New(PathToSRS, Frequency or 256, Modulation or radio.modulation.AM, Volume or 1.0) + self.controlmsrs:SetPort(Port) + self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE) + self.controlmsrs:SetLabel("RANGEC") + self.controlsrsQ = MSRSQUEUE:New("CONTROL") + + self.instructmsrs=MSRS:New(PathToSRS, Frequency or 305, Modulation or radio.modulation.AM, Volume or 1.0) + self.instructmsrs:SetPort(Port) + self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE) + self.instructmsrs:SetLabel("RANGEI") + self.instructsrsQ = MSRSQUEUE:New("INSTRUCT") + + else + self:E(self.lid..string.format("ERROR: No SRS path specified!")) + end + return self +end + +--- (SRS) Set range control frequency and voice. +-- @param #RANGE self +-- @param #number frequency Frequency in MHz. Default 256 MHz. +-- @param #number modulation Modulation, defaults to radio.modulation.AM. +-- @param #string voice Voice. +-- @param #string culture Culture, defaults to "en-US". +-- @param #string gender Gender, defaults to "female". +-- @param #string relayunitname Name of the unit used for transmission location. +-- @return #RANGE self +function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender, relayunitname ) + self.rangecontrolfreq = frequency or 256 + self.controlmsrs:SetFrequencies(self.rangecontrolfreq) + self.controlmsrs:SetModulations(modulation or radio.modulation.AM) + self.controlmsrs:SetVoice(voice) + self.controlmsrs:SetCulture(culture or "en-US") + self.controlmsrs:SetGender(gender or "female") + self.rangecontrol = true + if relayunitname then + local unit = UNIT:FindByName(relayunitname) + local Coordinate = unit:GetCoordinate() + self.rangecontrolrelayname = relayunitname + end + return self +end + +--- (SRS) Set range instructor frequency and voice. +-- @param #RANGE self +-- @param #number frequency Frequency in MHz. Default 305 MHz. +-- @param #number modulation Modulation, defaults to radio.modulation.AM. +-- @param #string voice Voice. +-- @param #string culture Culture, defaults to "en-US". +-- @param #string gender Gender, defaults to "male". +-- @param #string relayunitname Name of the unit used for transmission location. +-- @return #RANGE self +function RANGE:SetSRSRangeInstructor( frequency, modulation, voice, culture, gender, relayunitname ) + self.instructorfreq = frequency or 305 + self.instructmsrs:SetFrequencies(self.instructorfreq) + self.instructmsrs:SetModulations(modulation or radio.modulation.AM) + self.instructmsrs:SetVoice(voice) + self.instructmsrs:SetCulture(culture or "en-US") + self.instructmsrs:SetGender(gender or "male") + self.instructor = true + if relayunitname then + local unit = UNIT:FindByName(relayunitname) + local Coordinate = unit:GetCoordinate() + self.instructmsrs:SetCoordinate(Coordinate) + self.instructorrelayname = relayunitname + end + return self +end + +--- Enable range control and set frequency (non-SRS). -- @param #RANGE self -- @param #number frequency Frequency in MHz. Default 256 MHz. -- @param #string relayunitname Name of the unit used for transmission. @@ -1188,7 +1282,7 @@ function RANGE:SetRangeControl( frequency, relayunitname ) return self end ---- Enable instructor radio and set frequency. +--- Enable instructor radio and set frequency (non-SRS). -- @param #RANGE self -- @param #number frequency Frequency in MHz. Default 305 MHz. -- @param #string relayunitname Name of the unit used for transmission. @@ -1755,7 +1849,12 @@ function RANGE:OnEventHit( EventData ) -- Too close to the target. if _currentTarget.pastfoulline == false and _unit and _playername then local _d = _currentTarget.zone.foulline + -- DONE - SRS output local text = string.format( "%s, Invalid hit!\nYou already passed foul line distance of %d m for target %s.", self:_myname( _unitName ), _d, targetname ) + if self.useSRS then + local ttstext = string.format( "%s, Invalid hit! You already passed foul line distance of %d meters for target %s.", self:_myname( _unitName ), _d, targetname ) + self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2) + end self:_DisplayMessageToGroup( _unit, text ) self:T2( self.id .. text ) _currentTarget.pastfoulline = true @@ -1810,7 +1909,11 @@ function RANGE:OnEventShot( EventData ) if EventData.IniDCSUnit == nil then return end - + + if EventData.IniPlayerName == nil then + return + end + -- Weapon data. local _weapon = EventData.Weapon:getTypeName() -- should be the same as Event.WeaponTypeName local _weaponStrArray = UTILS.Split( _weapon, "%." ) @@ -1998,11 +2101,21 @@ function RANGE:OnEventShot( EventData ) elseif insidezone then -- Send message. + -- DONE SRS message local _message = string.format( "%s, weapon impacted too far from nearest range target (>%.1f km). No score!", _callsign, self.scorebombdistance / 1000 ) + if self.useSRS then + local ttstext = string.format( "%s, weapon impacted too far from nearest range target, mor than %.1f kilometer. No score!", _callsign, self.scorebombdistance / 1000 ) + self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2) + end self:_DisplayMessageToGroup( _unit, _message, nil, false ) if self.rangecontrol then - self.rangecontrol:NewTransmission( RANGE.Sound.RCWeaponImpactedTooFar.filename, RANGE.Sound.RCWeaponImpactedTooFar.duration, self.soundpath, nil, nil, _message, self.subduration ) + -- weapon impacted too far from the nearest target! No Score! + if self.useSRS then + self.controlsrsQ:NewTransmission(_message,nil,self.controlmsrs,nil,1) + else + self.rangecontrol:NewTransmission( RANGE.Sound.RCWeaponImpactedTooFar.filename, RANGE.Sound.RCWeaponImpactedTooFar.duration, self.soundpath, nil, nil, _message, self.subduration ) + end end else @@ -2085,18 +2198,26 @@ end function RANGE:onafterEnterRange( From, Event, To, player ) if self.instructor and self.rangecontrol then - - -- Range control radio frequency split. - local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." ) - - -- Radio message that player entered the range - self.instructor:NewTransmission( RANGE.Sound.IREnterRange.filename, RANGE.Sound.IREnterRange.duration, self.soundpath ) - self.instructor:Number2Transmission( RF[1] ) - if tonumber( RF[2] ) > 0 then - self.instructor:NewTransmission( RANGE.Sound.IRDecimal.filename, RANGE.Sound.IRDecimal.duration, self.soundpath ) - self.instructor:Number2Transmission( RF[2] ) + + if self.useSRS then + local text = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f MHz", self.rangecontrolfreq) + local ttstext = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f mega hertz.", self.rangecontrolfreq) + local group = player.client:GetGroup() + self.instructsrsQ:NewTransmission(ttstext,nil,self.instructmsrs,nil,1,{group},text,10) + else + -- Range control radio frequency split. + local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." ) + + -- Radio message that player entered the range + -- You entered the bombing range. For hit assessment, contact the range controller at xy MHz + self.instructor:NewTransmission( RANGE.Sound.IREnterRange.filename, RANGE.Sound.IREnterRange.duration, self.soundpath ) + self.instructor:Number2Transmission( RF[1] ) + if tonumber( RF[2] ) > 0 then + self.instructor:NewTransmission( RANGE.Sound.IRDecimal.filename, RANGE.Sound.IRDecimal.duration, self.soundpath ) + self.instructor:Number2Transmission( RF[2] ) + end + self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath ) end - self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath ) end end @@ -2110,7 +2231,14 @@ end function RANGE:onafterExitRange( From, Event, To, player ) if self.instructor then - self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath ) + -- You left the bombing range zone. Have a nice day! + if self.useSRS then + local text = "You left the bombing range zone. Have a nice day!" + local group = player.client:GetGroup() + self.instructsrsQ:NewTransmission(text,nil,self.instructmsrs,nil,1,{group},text,10) + else + self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath ) + end end end @@ -2140,32 +2268,37 @@ function RANGE:onafterImpact( From, Event, To, result, player ) text = text .. string.format( " %s hit.", result.quality ) if self.rangecontrol then - self.rangecontrol:NewTransmission( RANGE.Sound.RCImpact.filename, RANGE.Sound.RCImpact.duration, self.soundpath, nil, nil, text, self.subduration ) - self.rangecontrol:Number2Transmission( string.format( "%03d", result.radial ), nil, 0.1 ) - self.rangecontrol:NewTransmission( RANGE.Sound.RCDegrees.filename, RANGE.Sound.RCDegrees.duration, self.soundpath ) - self.rangecontrol:NewTransmission( RANGE.Sound.RCFor.filename, RANGE.Sound.RCFor.duration, self.soundpath ) - self.rangecontrol:Number2Transmission( string.format( "%d", UTILS.MetersToFeet( result.distance ) ) ) - self.rangecontrol:NewTransmission( RANGE.Sound.RCFeet.filename, RANGE.Sound.RCFeet.duration, self.soundpath ) - if result.quality == "POOR" then - self.rangecontrol:NewTransmission( RANGE.Sound.RCPoorHit.filename, RANGE.Sound.RCPoorHit.duration, self.soundpath, nil, 0.5 ) - elseif result.quality == "INEFFECTIVE" then - self.rangecontrol:NewTransmission( RANGE.Sound.RCIneffectiveHit.filename, RANGE.Sound.RCIneffectiveHit.duration, self.soundpath, nil, 0.5 ) - elseif result.quality == "GOOD" then - self.rangecontrol:NewTransmission( RANGE.Sound.RCGoodHit.filename, RANGE.Sound.RCGoodHit.duration, self.soundpath, nil, 0.5 ) - elseif result.quality == "EXCELLENT" then - self.rangecontrol:NewTransmission( RANGE.Sound.RCExcellentHit.filename, RANGE.Sound.RCExcellentHit.duration, self.soundpath, nil, 0.5 ) + + if self.useSRS then + local group = player.client:GetGroup() + self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1,{group},text,10) + else + self.rangecontrol:NewTransmission( RANGE.Sound.RCImpact.filename, RANGE.Sound.RCImpact.duration, self.soundpath, nil, nil, text, self.subduration ) + self.rangecontrol:Number2Transmission( string.format( "%03d", result.radial ), nil, 0.1 ) + self.rangecontrol:NewTransmission( RANGE.Sound.RCDegrees.filename, RANGE.Sound.RCDegrees.duration, self.soundpath ) + self.rangecontrol:NewTransmission( RANGE.Sound.RCFor.filename, RANGE.Sound.RCFor.duration, self.soundpath ) + self.rangecontrol:Number2Transmission( string.format( "%d", UTILS.MetersToFeet( result.distance ) ) ) + self.rangecontrol:NewTransmission( RANGE.Sound.RCFeet.filename, RANGE.Sound.RCFeet.duration, self.soundpath ) + if result.quality == "POOR" then + self.rangecontrol:NewTransmission( RANGE.Sound.RCPoorHit.filename, RANGE.Sound.RCPoorHit.duration, self.soundpath, nil, 0.5 ) + elseif result.quality == "INEFFECTIVE" then + self.rangecontrol:NewTransmission( RANGE.Sound.RCIneffectiveHit.filename, RANGE.Sound.RCIneffectiveHit.duration, self.soundpath, nil, 0.5 ) + elseif result.quality == "GOOD" then + self.rangecontrol:NewTransmission( RANGE.Sound.RCGoodHit.filename, RANGE.Sound.RCGoodHit.duration, self.soundpath, nil, 0.5 ) + elseif result.quality == "EXCELLENT" then + self.rangecontrol:NewTransmission( RANGE.Sound.RCExcellentHit.filename, RANGE.Sound.RCExcellentHit.duration, self.soundpath, nil, 0.5 ) + end end - end -- Unit. - if player.unitname then + if player.unitname and not self.useSRS then -- Get unit. local unit = UNIT:FindByName( player.unitname ) -- Send message. - self:_DisplayMessageToGroup( unit, text, nil, true ) + self:_DisplayMessageToGroup( unit, text, nil, true ) self:T( self.id .. text ) end @@ -2677,7 +2810,7 @@ function RANGE:_DisplayRangeInfo( _unitname ) -- Check if we have a player. if unit and playername then - + self:I(playername) -- Message text. local text = "" @@ -2746,7 +2879,8 @@ function RANGE:_DisplayRangeInfo( _unitname ) if self.instructorrelayname then local relay = UNIT:FindByName( self.instructorrelayname ) if relay then - alive = tostring( relay:IsAlive() ) + --alive = tostring( relay:IsAlive() ) + alive = relay:IsAlive() and "ok" or "N/A" end end text = text .. string.format( "Instructor %.3f MHz (Relay=%s)\n", self.instructorfreq, alive ) @@ -2757,6 +2891,7 @@ function RANGE:_DisplayRangeInfo( _unitname ) local relay = UNIT:FindByName( self.rangecontrolrelayname ) if relay then alive = tostring( relay:IsAlive() ) + alive = relay:IsAlive() and "ok" or "N/A" end end text = text .. string.format( "Control %.3f MHz (Relay=%s)\n", self.rangecontrolfreq, alive ) @@ -3041,11 +3176,18 @@ function RANGE:_CheckInZone( _unitName ) -- Send message. self:_DisplayMessageToGroup( _unit, _msg, nil, true ) - + if self.rangecontrol then - self.rangecontrol:NewTransmission( RANGE.Sound.RCLeftStrafePitTooQuickly.filename, RANGE.Sound.RCLeftStrafePitTooQuickly.duration, self.soundpath ) + if self.useSRS then + local group = _unit:GetGroup() + local text = "You left the strafing zone too quickly! No score!" + --self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1,{group},text,10) + self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1) + else + -- You left the strafing zone too quickly! No score! + self.rangecontrol:NewTransmission( RANGE.Sound.RCLeftStrafePitTooQuickly.filename, RANGE.Sound.RCLeftStrafePitTooQuickly.duration, self.soundpath ) + end end - else -- Get current ammo. @@ -3056,23 +3198,6 @@ function RANGE:_CheckInZone( _unitName ) local _sound = nil -- #RANGE.Soundfile - --[[ --RangeBoss commented out in order to implement strafe quality based on accuracy percentage, not the number of rounds on target - -- Judge this pass. Text is displayed on summary. - if _result.hits >= _result.zone.goodPass*2 then - _result.text = "EXCELLENT PASS" - _sound=RANGE.Sound.RCExcellentPass - elseif _result.hits >= _result.zone.goodPass then - _result.text = "GOOD PASS" - _sound=RANGE.Sound.RCGoodPass - elseif _result.hits >= _result.zone.goodPass/2 then - _result.text = "INEFFECTIVE PASS" - _sound=RANGE.Sound.RCIneffectivePass - else - _result.text = "POOR PASS" - _sound=RANGE.Sound.RCPoorPass - end - ]] - -- Calculate accuracy of run. Number of hits wrt number of rounds fired. local shots = _result.ammo - _ammo local accur = 0 @@ -3109,10 +3234,13 @@ function RANGE:_CheckInZone( _unitName ) -- Message text. local _text = string.format( "%s, hits on target %s: %d", self:_myname( _unitName ), _result.zone.name, _result.hits ) + local ttstext = string.format( "%s, hits on target %s: %d.", self:_myname( _unitName ), _result.zone.name, _result.hits ) if shots and accur then _text = _text .. string.format( "\nTotal rounds fired %d. Accuracy %.1f %%.", shots, accur ) + ttstext = ttstext .. string.format( ". Total rounds fired %d. Accuracy %.1f percent.", shots, accur ) end _text = _text .. string.format( "\n%s", resulttext ) + ttstext = ttstext .. string.format( " %s", resulttext ) -- Send message. self:_DisplayMessageToGroup( _unit, _text ) @@ -3144,16 +3272,20 @@ function RANGE:_CheckInZone( _unitName ) -- Voice over. if self.rangecontrol then - self.rangecontrol:NewTransmission( RANGE.Sound.RCHitsOnTarget.filename, RANGE.Sound.RCHitsOnTarget.duration, self.soundpath ) - self.rangecontrol:Number2Transmission( string.format( "%d", _result.hits ) ) - if shots and accur then - self.rangecontrol:NewTransmission( RANGE.Sound.RCTotalRoundsFired.filename, RANGE.Sound.RCTotalRoundsFired.duration, self.soundpath, nil, 0.2 ) - self.rangecontrol:Number2Transmission( string.format( "%d", shots ), nil, 0.2 ) - self.rangecontrol:NewTransmission( RANGE.Sound.RCAccuracy.filename, RANGE.Sound.RCAccuracy.duration, self.soundpath, nil, 0.2 ) - self.rangecontrol:Number2Transmission( string.format( "%d", UTILS.Round( accur, 0 ) ) ) - self.rangecontrol:NewTransmission( RANGE.Sound.RCPercent.filename, RANGE.Sound.RCPercent.duration, self.soundpath ) + if self.useSRS then + self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,1) + else + self.rangecontrol:NewTransmission( RANGE.Sound.RCHitsOnTarget.filename, RANGE.Sound.RCHitsOnTarget.duration, self.soundpath ) + self.rangecontrol:Number2Transmission( string.format( "%d", _result.hits ) ) + if shots and accur then + self.rangecontrol:NewTransmission( RANGE.Sound.RCTotalRoundsFired.filename, RANGE.Sound.RCTotalRoundsFired.duration, self.soundpath, nil, 0.2 ) + self.rangecontrol:Number2Transmission( string.format( "%d", shots ), nil, 0.2 ) + self.rangecontrol:NewTransmission( RANGE.Sound.RCAccuracy.filename, RANGE.Sound.RCAccuracy.duration, self.soundpath, nil, 0.2 ) + self.rangecontrol:Number2Transmission( string.format( "%d", UTILS.Round( accur, 0 ) ) ) + self.rangecontrol:NewTransmission( RANGE.Sound.RCPercent.filename, RANGE.Sound.RCPercent.duration, self.soundpath ) + end + self.rangecontrol:NewTransmission( _sound.filename, _sound.duration, self.soundpath, nil, 0.5 ) end - self.rangecontrol:NewTransmission( _sound.filename, _sound.duration, self.soundpath, nil, 0.5 ) end -- Set strafe status to nil. @@ -3192,7 +3324,11 @@ function RANGE:_CheckInZone( _unitName ) local _msg = string.format( "%s, rolling in on strafe pit %s.", self:_myname( _unitName ), target.name ) if self.rangecontrol then - self.rangecontrol:NewTransmission( RANGE.Sound.RCRollingInOnStrafeTarget.filename, RANGE.Sound.RCRollingInOnStrafeTarget.duration, self.soundpath ) + if self.useSRS then + self.controlsrsQ:NewTransmission(_msg,nil,self.controlmsrs,nil,1) + else + self.rangecontrol:NewTransmission( RANGE.Sound.RCRollingInOnStrafeTarget.filename, RANGE.Sound.RCRollingInOnStrafeTarget.duration, self.soundpath ) + end end -- Send message. @@ -3312,10 +3448,10 @@ function RANGE:_AddF10Commands( _unitName ) local _StrPits = MENU_GROUP_COMMAND:New( group, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName ):Refresh() end else - self:E( self.id .. "Could not find group or group ID in AddF10Menu() function. Unit name: " .. _unitName ) + self:E( self.id .. "Could not find group or group ID in AddF10Menu() function. Unit name: " .. _unitName or "N/A") end else - self:E( self.id .. "Player unit does not exist in AddF10Menu() function. Unit name: " .. _unitName ) + self:E( self.id .. "Player unit does not exist in AddF10Menu() function. Unit name: " .. _unitName or "N/A") end end @@ -3919,6 +4055,7 @@ function RANGE:_GetPlayerUnitAndName( _unitName ) self:T2( { DCSunit = DCSunit, unit = unit, playername = playername } ) if DCSunit and unit and playername then + self:F2(playername) return unit, playername end @@ -3935,13 +4072,15 @@ end -- @param #string unitname Name of the player unit. function RANGE:_myname( unitname ) self:F2( unitname ) - + local pname = "Ghost 1 1" local unit = UNIT:FindByName( unitname ) - local pname = unit:GetPlayerName() - -- local csign = unit:GetCallsign() - - -- return string.format("%s (%s)", csign, pname) - return string.format( "%s", pname ) + if unit and unit:IsAlive() then + local grp = unit:GetGroup() + if grp and grp:IsAlive() then + pname = grp:GetCustomCallSign(true,true) + end + end + return pname end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 88e650d99..96c67805c 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -91,6 +91,7 @@ -- @field #boolean useSRS If true, use SRS for transmission. -- @field Sound.SRS#MSRS msrs Moose SRS object. -- @field #number dTQueueCheck Time interval to check the radio queue. Default 5 sec or 90 sec if SRS is used. +-- @field #boolean ReportmBar Report mBar/hpa even if not metric, i.e. for Mirage flights -- @extends Core.Fsm#FSM --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -344,6 +345,7 @@ ATIS = { usemarker = nil, markerid = nil, relHumidity = nil, + ReportmBar = false, } --- NATO alphabet. @@ -586,15 +588,18 @@ _ATIS = {} --- ATIS class version. -- @field #string version -ATIS.version = "0.9.6" +ATIS.version = "0.9.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Add new Normany airfields. +-- TODO: Add new Normandy airfields. -- TODO: Zulu time --> Zulu in output. -- TODO: Correct fog for elevation. +-- DONE: Use new AIRBASE system to set start/landing runway +-- DONE: SetILS doesn't work +-- DONE: Visibility reported twice over SRS -- DONE: Add text report for output. -- DONE: Add stop FMS functions. -- NOGO: Use local time. Not realisitc! @@ -620,8 +625,6 @@ function ATIS:New(AirbaseName, Frequency, Modulation) -- Inherit everything from FSM class. local self = BASE:Inherit( self, FSM:New() ) -- #ATIS - local self=BASE:Inherit(self, FSM:New()) -- #ATIS - self.airbasename=AirbaseName self.airbase=AIRBASE:FindByName(AirbaseName) @@ -653,6 +656,7 @@ function ATIS:New(AirbaseName, Frequency, Modulation) self:SetMapMarks( false ) self:SetRelativeHumidity() self:SetQueueUpdateTime() + self:SetReportmBar(false) -- Start State. self:SetStartState( "Stopped" ) @@ -747,7 +751,7 @@ end -- @return #ATIS self function ATIS:SetSoundfilesPath( path ) self.soundpath = tostring( path or "ATIS Soundfiles/" ) - self:I( self.lid .. string.format( "Setting sound files path to %s", self.soundpath ) ) + self:T( self.lid .. string.format( "Setting sound files path to %s", self.soundpath ) ) return self end @@ -758,7 +762,7 @@ end -- @return #ATIS self function ATIS:SetRadioRelayUnitName( unitname ) self.relayunitname = unitname - self:I( self.lid .. string.format( "Setting radio relay unit to %s", self.relayunitname ) ) + self:T( self.lid .. string.format( "Setting radio relay unit to %s", self.relayunitname ) ) return self end @@ -776,13 +780,40 @@ function ATIS:SetTowerFrequencies( freqs ) return self end ---- Set active runway. This can be used if the automatic runway determination via the wind direction gives incorrect results. +--- Set active runway for **landing** operations. This can be used if the automatic runway determination via the wind direction gives incorrect results. -- For example, use this if there are two runways with the same directions. -- @param #ATIS self -- @param #string runway Active runway, *e.g.* "31L". -- @return #ATIS self function ATIS:SetActiveRunway( runway ) self.activerunway = tostring( runway ) + local prefer = nil + if string.find(string.lower(runway),"l") then + prefer = true + elseif string.find(string.lower(runway),"r") then + prefer = false + end + self.airbase:SetActiveRunway(runway,prefer) + return self +end + +--- Set the active runway for landing. +-- @param #ATIS self +-- @param #string runway : Name of the runway, e.g. "31" or "02L" or "90R". If not given, the runway is determined from the wind direction. +-- @param #boolean preferleft : If true, perfer the left runway. If false, prefer the right runway. If nil (default), do not care about left or right. +-- @return #ATIS self +function ATIS:SetActiveRunwayLanding(runway, preferleft) + self.airbase:SetActiveRunwayLanding(runway,preferleft) + return self +end + +--- Set the active runway for take-off. +-- @param #ATIS self +-- @param #string runway : Name of the runway, e.g. "31" or "02L" or "90R". If not given, the runway is determined from the wind direction. +-- @param #boolean preferleft : If true, perfer the left runway. If false, prefer the right runway. If nil (default), do not care about left or right. +-- @return #ATIS self +function ATIS:SetActiveRunwayTakeoff(runway,preferleft) + self.airbase:SetActiveRunwayTakeoff(runway,preferleft) return self end @@ -853,7 +884,7 @@ function ATIS:SetRunwayHeadingsMagnetic( headings ) end -- Add runway heading to table. - self:I( self.lid .. string.format( "Adding user specified magnetic runway heading %s", heading ) ) + self:T( self.lid .. string.format( "Adding user specified magnetic runway heading %s", heading ) ) table.insert( self.runwaymag, heading ) local h = self:GetRunwayWithoutLR( heading ) @@ -875,7 +906,7 @@ function ATIS:SetRunwayHeadingsMagnetic( headings ) end -- Add inverse runway heading to table. - self:I( self.lid .. string.format( "Adding user specified magnetic runway heading %s (inverse)", head2 ) ) + self:T( self.lid .. string.format( "Adding user specified magnetic runway heading %s (inverse)", head2 ) ) table.insert( self.runwaymag, head2 ) end @@ -949,6 +980,28 @@ function ATIS:SetAltimeterQNH( switch ) return self end +--- Additionally report altimeter QNH/QFE in hPa, even if not set to metric. +-- @param #ATIS self +-- @param #boolean switch If true or nil, report mBar/hPa in addition. +-- @return #ATIS self +function ATIS:SetReportmBar(switch) + if switch == true or switch == nil then + self.ReportmBar = true + else + self.ReportmBar = false + end + return self +end + +--- Additionally report free text, only working with SRS(!) +-- @param #ATIS self +-- @param #string text The text to report at the end of the ATIS message, e.g. runway closure, warnings, etc. +-- @return #ATIS self +function ATIS:SetAdditionalInformation(text) + self.AdditionalInformation = text + return self +end + --- Suppresses QFE readout. Default is to report both QNH and QFE. -- @param #ATIS self -- @return #ATIS self @@ -1128,8 +1181,9 @@ end -- @param #string Culture Culture, e.g. "en-GB" (default). -- @param #string Voice Specific voice. Overrides `Gender` and `Culture`. -- @param #number Port SRS port. Default 5002. +-- @param #string GoogleKey Path to Google JSON-Key. -- @return #ATIS self -function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port) +function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port, GoogleKey) if PathToSRS then self.useSRS=true self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation) @@ -1139,6 +1193,8 @@ function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port) self.msrs:SetPort(Port) self.msrs:SetCoalition(self:GetCoalition()) self.msrs:SetLabel("ATIS") + self.msrs:SetGoogle(GoogleKey) + self.msrsQ = MSRSQUEUE:New("ATIS") if self.dTQueueCheck<=10 then self:SetQueueUpdateTime(90) end @@ -1185,32 +1241,34 @@ function ATIS:onafterStart( From, Event, To ) self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) ) -- Start radio queue. - self.radioqueue = RADIOQUEUE:New( self.frequency, self.modulation, string.format( "ATIS %s", self.airbasename ) ) - - -- Send coordinate is airbase coord. - self.radioqueue:SetSenderCoordinate( self.airbase:GetCoordinate() ) - - -- Set relay unit if we have one. - self.radioqueue:SetSenderUnitName( self.relayunitname ) - - -- Set radio power. - self.radioqueue:SetRadioPower( self.power ) - - -- Init numbers. - self.radioqueue:SetDigit( 0, ATIS.Sound.N0.filename, ATIS.Sound.N0.duration, self.soundpath ) - self.radioqueue:SetDigit( 1, ATIS.Sound.N1.filename, ATIS.Sound.N1.duration, self.soundpath ) - self.radioqueue:SetDigit( 2, ATIS.Sound.N2.filename, ATIS.Sound.N2.duration, self.soundpath ) - self.radioqueue:SetDigit( 3, ATIS.Sound.N3.filename, ATIS.Sound.N3.duration, self.soundpath ) - self.radioqueue:SetDigit( 4, ATIS.Sound.N4.filename, ATIS.Sound.N4.duration, self.soundpath ) - self.radioqueue:SetDigit( 5, ATIS.Sound.N5.filename, ATIS.Sound.N5.duration, self.soundpath ) - self.radioqueue:SetDigit( 6, ATIS.Sound.N6.filename, ATIS.Sound.N6.duration, self.soundpath ) - self.radioqueue:SetDigit( 7, ATIS.Sound.N7.filename, ATIS.Sound.N7.duration, self.soundpath ) - self.radioqueue:SetDigit( 8, ATIS.Sound.N8.filename, ATIS.Sound.N8.duration, self.soundpath ) - self.radioqueue:SetDigit( 9, ATIS.Sound.N9.filename, ATIS.Sound.N9.duration, self.soundpath ) - - -- Start radio queue. - self.radioqueue:Start( 1, 0.1 ) - + if not self.useSRS then + self.radioqueue = RADIOQUEUE:New( self.frequency, self.modulation, string.format( "ATIS %s", self.airbasename ) ) + + -- Send coordinate is airbase coord. + self.radioqueue:SetSenderCoordinate( self.airbase:GetCoordinate() ) + + -- Set relay unit if we have one. + self.radioqueue:SetSenderUnitName( self.relayunitname ) + + -- Set radio power. + self.radioqueue:SetRadioPower( self.power ) + + -- Init numbers. + self.radioqueue:SetDigit( 0, ATIS.Sound.N0.filename, ATIS.Sound.N0.duration, self.soundpath ) + self.radioqueue:SetDigit( 1, ATIS.Sound.N1.filename, ATIS.Sound.N1.duration, self.soundpath ) + self.radioqueue:SetDigit( 2, ATIS.Sound.N2.filename, ATIS.Sound.N2.duration, self.soundpath ) + self.radioqueue:SetDigit( 3, ATIS.Sound.N3.filename, ATIS.Sound.N3.duration, self.soundpath ) + self.radioqueue:SetDigit( 4, ATIS.Sound.N4.filename, ATIS.Sound.N4.duration, self.soundpath ) + self.radioqueue:SetDigit( 5, ATIS.Sound.N5.filename, ATIS.Sound.N5.duration, self.soundpath ) + self.radioqueue:SetDigit( 6, ATIS.Sound.N6.filename, ATIS.Sound.N6.duration, self.soundpath ) + self.radioqueue:SetDigit( 7, ATIS.Sound.N7.filename, ATIS.Sound.N7.duration, self.soundpath ) + self.radioqueue:SetDigit( 8, ATIS.Sound.N8.filename, ATIS.Sound.N8.duration, self.soundpath ) + self.radioqueue:SetDigit( 9, ATIS.Sound.N9.filename, ATIS.Sound.N9.duration, self.soundpath ) + + -- Start radio queue. + self.radioqueue:Start( 1, 0.1 ) + end + -- Handle airbase capture -- Handle events. self:HandleEvent( EVENTS.BaseCaptured ) @@ -1245,7 +1303,7 @@ function ATIS:onafterStatus( From, Event, To ) else text = text .. string.format( ", Relay unit=%s (alive=%s)", tostring( self.relayunitname ), relayunitstatus ) end - self:I( self.lid .. text ) + self:T( self.lid .. text ) self:__Status( -60 ) end @@ -1324,7 +1382,10 @@ function ATIS:onafterBroadcast( From, Event, To ) qnh = Q / 100 end - + + local mBarqnh = qnh + local mBarqfe = qfe + -- Convert to inHg. if self.PmmHg then qfe = UTILS.hPa2mmHg( qfe ) @@ -1775,7 +1836,9 @@ function ATIS:onafterBroadcast( From, Event, To ) end end alltext = alltext .. ";\n" .. subtitle - + --self:I("Line 1811") + --self:I(alltext) + -- Visibility if self.metric then subtitle = string.format( "Visibility %s km", VISIBILITY ) @@ -1792,7 +1855,10 @@ function ATIS:onafterBroadcast( From, Event, To ) end end alltext = alltext .. ";\n" .. subtitle - + --self:I("Line 1830") + --self:I(alltext) + + subtitle = "" -- Weather phenomena local wp = false local wpsub = "" @@ -1892,8 +1958,11 @@ function ATIS:onafterBroadcast( From, Event, To ) end end end - alltext = alltext .. ";\n" .. subtitle + --self:I("Line 1932") + alltext = alltext .. ";\n" .. subtitle + --self:I(alltext) + subtitle = "" -- Temperature if self.TDegF then if temperature < 0 then @@ -1921,8 +1990,10 @@ function ATIS:onafterBroadcast( From, Event, To ) self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 ) end end + --self:I("Line 1962") alltext = alltext .. ";\n" .. subtitle - + --self:I(alltext) + -- Dew point if self.TDegF then if dewpoint < 0 then @@ -1950,6 +2021,8 @@ function ATIS:onafterBroadcast( From, Event, To ) self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 ) end end + --self:I("Line 1992") + --self:I(alltext) alltext = alltext .. ";\n" .. subtitle -- Altimeter QNH/QFE. @@ -1974,6 +2047,15 @@ function ATIS:onafterBroadcast( From, Event, To ) end end end + + if self.ReportmBar and not self.metric then + if self.qnhonly then + subtitle = string.format( "%s;\nAltimeter %d hPa", subtitle, mBarqnh ) + else + subtitle = string.format( "%s;\nAltimeter: QNH %d, QFE %d hPa", subtitle, mBarqnh, mBarqfe) + end + end + local _ALTIMETER = subtitle if not self.useSRS then self:Transmission( ATIS.Sound.Altimeter, 1.0, subtitle ) @@ -2006,6 +2088,8 @@ function ATIS:onafterBroadcast( From, Event, To ) end end end + --self:I("Line 2049") + --self:I(alltext) alltext = alltext .. ";\n" .. subtitle -- Active runway. @@ -2133,7 +2217,9 @@ function ATIS:onafterBroadcast( From, Event, To ) end -- ILS + --self:I({ils=self.ils}) local ils=self:GetNavPoint(self.ils, runwayLanding, rwyLandingLeft) + --self:I({ils=ils,runwayLanding=runwayLanding, rwyLandingLeft=rwyLandingLeft}) if ils then subtitle = string.format( "ILS frequency %.2f MHz", ils.frequency ) if not self.useSRS then @@ -2148,6 +2234,7 @@ function ATIS:onafterBroadcast( From, Event, To ) self:Transmission( ATIS.Sound.MegaHertz, 0.2 ) end alltext = alltext .. ";\n" .. subtitle + --self:I(alltext) end -- Outer NDB @@ -2237,7 +2324,12 @@ function ATIS:onafterBroadcast( From, Event, To ) end alltext = alltext .. ";\n" .. subtitle end - + + -- additional info, if any + if self.useSRS and self.AdditionalInformation then + alltext = alltext .. ";\n"..self.AdditionalInformation + end + -- Advice on initial... subtitle = string.format( "Advise on initial contact, you have information %s", NATO ) if not self.useSRS then @@ -2285,8 +2377,10 @@ function ATIS:onafterReport( From, Event, To, Text ) -- Debug output. self:T( "SRS TTS: " .. text ) - -- Play text-to-speech report. - self.msrs:PlayText( text ) + -- Play text-to-speech report. + local duration = STTS.getSpeechTime(text,0.95) + self.msrsQ:NewTransmission(text,duration,self.msrs,nil,2) + --self.msrs:PlayText( text ) end @@ -2433,7 +2527,7 @@ end -- @return #string Runway heading without left or right, *e.g.* "31". function ATIS:GetRunwayWithoutLR( runway ) local rwywo = runway:gsub( "%D+", "" ) - -- self:I(string.format("FF runway=%s ==> rwywo=%s", runway, rwywo)) + -- self:T(string.format("FF runway=%s ==> rwywo=%s", runway, rwywo)) return rwywo end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 7afe36983..bfa135ab5 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -2766,9 +2766,9 @@ function AIRBOSS:SetRefuelAI( LowFuelThreshold ) return self end ---- Set max alitude to register flights in the initial zone. Aircraft above this altitude will not be registerered. +--- Set max altitude to register flights in the initial zone. Aircraft above this altitude will not be registerered. -- @param #AIRBOSS self --- @param #number MaxAltitude Max alitude in feet. Default 1300 ft. +-- @param #number MaxAltitude Max altitude in feet. Default 1300 ft. -- @return #AIRBOSS self function AIRBOSS:SetInitialMaxAlt( MaxAltitude ) self.initialmaxalt = UTILS.FeetToMeters( MaxAltitude or 1300 ) @@ -4268,7 +4268,7 @@ function AIRBOSS:_InitStennis() -- Carrier Parameters. self.carrierparam.sterndist = -153 - self.carrierparam.deckheight = 19.06 + self.carrierparam.deckheight = 18.30 -- Total size of the carrier (approx as rectangle). self.carrierparam.totlength = 310 -- Wiki says 332.8 meters overall length. @@ -5913,7 +5913,7 @@ function AIRBOSS:_WaitAI( flight, respawn ) -- Heading from carrier to flight group local hdgto = cv:HeadingTo( fc ) - -- Holding alitude between angels 6 and 10 (random). + -- Holding altitude between angels 6 and 10 (random). local angels = math.random( 6, 10 ) local altitude = UTILS.FeetToMeters( angels * 1000 ) @@ -8913,7 +8913,7 @@ function AIRBOSS:_Initial( playerData ) -- Relative heading to carrier direction. local relheading = self:_GetRelativeHeading( playerData.unit, false ) - -- Alitude of player in feet. + -- altitude of player in feet. local altitude = playerData.unit:GetAltitude() -- Check if player is in zone and flying roughly in the right direction. @@ -11112,7 +11112,7 @@ function AIRBOSS:_Lineup( unit, runway ) return lineup end ---- Get alitude of aircraft wrt carrier deck. Should give zero when the aircraft touched down. +--- Get altitude of aircraft wrt carrier deck. Should give zero when the aircraft touched down. -- @param #AIRBOSS self -- @param Wrapper.Unit#UNIT unit Aircraft unit. -- @return #number Altitude in meters wrt carrier height. @@ -14103,7 +14103,7 @@ end --- Convert altitude from meters to angels (thousands of feet). -- @param #AIRBOSS self --- @param alt Alitude in meters. +-- @param alt altitude in meters. -- @return #number Altitude in Anglels = thousands of feet using math.floor(). function AIRBOSS:_GetAngels( alt ) @@ -15415,7 +15415,7 @@ function AIRBOSS:_MarshalCallNewFinalBearing( FB ) end ---- Compile a radio call when Marshal tells a flight the holding alitude. +--- Compile a radio call when Marshal tells a flight the holding altitude. -- @param #AIRBOSS self -- @param #number hdg Heading in degrees. function AIRBOSS:_MarshalCallCarrierTurnTo( hdg ) @@ -15438,7 +15438,7 @@ function AIRBOSS:_MarshalCallCarrierTurnTo( hdg ) end ---- Compile a radio call when Marshal tells a flight the holding alitude. +--- Compile a radio call when Marshal tells a flight the holding altitude. -- @param #AIRBOSS self -- @param #string modex Tail number. -- @param #number nwaiting Number of flights already waiting. @@ -15464,7 +15464,7 @@ function AIRBOSS:_MarshalCallStackFull( modex, nwaiting ) self:RadioTransmission( self.MarshalRadio, call, nil, nil, nil, true ) end ---- Compile a radio call when Marshal tells a flight the holding alitude. +--- Compile a radio call when Marshal tells a flight the holding altitude. -- @param #AIRBOSS self function AIRBOSS:_MarshalCallRecoveryStart( case ) @@ -15504,12 +15504,12 @@ function AIRBOSS:_MarshalCallRecoveryStart( case ) end ---- Compile a radio call when Marshal tells a flight the holding alitude. +--- Compile a radio call when Marshal tells a flight the holding altitude. -- @param #AIRBOSS self -- @param #string modex Tail number. -- @param #number case Recovery case. -- @param #number brc Base recovery course. --- @param #number altitude Holding alitude. +-- @param #number altitude Holding altitude. -- @param #string charlie Charlie Time estimate. -- @param #number qfe Alitmeter inHg. function AIRBOSS:_MarshalCallArrived( modex, case, brc, altitude, charlie, qfe ) @@ -17362,7 +17362,7 @@ function AIRBOSS:_MarkMarshalZone( _unitName, flare ) -- Get Case I commence zone at three position. local zoneThree = self:_GetZoneCommence( case, stack ) - -- Pattern alitude. + -- Pattern altitude. local patternalt = self:_GetMarshalAltitude( stack, case ) -- Flare and smoke at the ground. diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index f4957f689..e1e9bea0b 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -91,6 +91,8 @@ do -- @field #boolean PlayerGuidance if true additional callouts to guide/warn players -- @field #boolean ModernEra if true we get more intel on targets, and EPLR on the AIC -- @field #boolean callsignshort if true use short (group) callsigns, e.g. "Ghost 1", else "Ghost 1 1" +-- @field #boolean keepnumber if true, use the full string after # for a player custom callsign +-- @field #table callsignTranslations optional translations for callsigns -- @field #number MeldDistance 25nm - distance for "Meld" Call , usually shortly before the actual engagement -- @field #number TacDistance 30nm - distance for "TAC" Call -- @field #number ThreatDistance 15nm - distance to declare untargeted (new) threats @@ -325,7 +327,7 @@ do -- * @{#AWACS.SetRadarBlur}() : Set the radar blur faktor in percent. -- * @{#AWACS.SetColdWar}() : Set to cold war - no fill-ins, no EPLRS, VID as standard. -- * @{#AWACS.SetModernEraDefensive}() : Set to modern, EPLRS, BVR/IFF engagement, fill-ins. --- * @{#AWACS.SetModernEraAgressive}() : Set to modern, EPLRS, BVR/IFF engagement, fill-ins. +-- * @{#AWACS.SetModernEraAggressive}() : Set to modern, EPLRS, BVR/IFF engagement, fill-ins. -- * @{#AWACS.SetPolicingModern}() : Set to modern, EPLRS, VID engagement, fill-ins. -- * @{#AWACS.SetPolicingColdWar}() : Set to cold war, no EPLRS, VID engagement, no fill-ins. -- * @{#AWACS.SetInterceptTimeline}() : Set distances for TAC, Meld and Threat range calls. @@ -495,7 +497,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.41", -- #string + version = "0.2.43", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -561,6 +563,8 @@ AWACS = { PlayerGuidance = true, ModernEra = true, callsignshort = true, + keepnumber = true, + callsignTranslations = nil, TacDistance = 45, MeldDistance = 35, ThreatDistance = 25, @@ -1710,7 +1714,7 @@ end --- [User] Set AWACS to Modern Era standards - ROE to BVR, ROT to return fire. Radar blur 15%. -- @param #AWACS self -- @return #AWACS self -function AWACS:SetModernEraAgressive() +function AWACS:SetModernEraAggressive() self.ModernEra = true self.AwacsROT = AWACS.ROT.RETURNFIRE self.AwacsROE = AWACS.ROE.BVR @@ -1896,6 +1900,7 @@ function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey self.Volume = Volume or 1.0 self.AwacsSRS = MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation,self.Volume) + self.AwacsSRS:SetCoalition(self.coalition) self.AwacsSRS:SetGender(self.Gender) self.AwacsSRS:SetCulture(self.Culture) self.AwacsSRS:SetVoice(self.Voice) @@ -2068,7 +2073,7 @@ function AWACS:_StartSettings(FlightGroup,Mission) --self.AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,self.PathToGoogleKey,"AWACS",self.Volume) - self.callsigntxt = string.format("%s",AWACS.CallSignClear[self.CallSign]) + self.callsigntxt = string.format("%s",self.CallSignClear[self.CallSign]) self:__CheckRadioQueue(10) @@ -2110,7 +2115,7 @@ function AWACS:_StartSettings(FlightGroup,Mission) --AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,nil,"AWACS") - self.callsigntxt = string.format("%s",AWACS.CallSignClear[self.CallSign]) + self.callsigntxt = string.format("%s",self.CallSignClear[self.CallSign]) local shifting = self.gettext:GetEntry("SHIFTCHANGE",self.locale) @@ -2222,39 +2227,29 @@ function AWACS:_GetCallSign(Group,GID, IsPlayer) local callsign = "Ghost 1" if Group and Group:IsAlive() then - local shortcallsign = Group:GetCallsign() or "unknown11"-- e.g.Uzi11, but we want Uzi 1 1 - local callsignroot = string.match(shortcallsign, '(%a+)') - self:I("CallSign = " .. callsignroot) - local groupname = Group:GetName() - local callnumber = string.match(shortcallsign, "(%d+)$" ) or "unknown11" - local callnumbermajor = string.char(string.byte(callnumber,1)) - local callnumberminor = string.char(string.byte(callnumber,2)) - local personalized = false - if IsPlayer and string.find(groupname,"#") then - -- personalized flight name in group naming - shortcallsign = string.match(groupname,"#([%a]+)") - personalized = true - end - if IsPlayer and string.find(Group:GetPlayerName(),"|") then - -- personalized flight name in group naming - shortcallsign = string.match(Group:GetPlayerName(),"| ([%a]+)") - personalized = true - end - if (not personalized) and self.callsignTranslations and self.callsignTranslations[callsignroot] then - shortcallsign = string.gsub(shortcallsign, callsignroot, self.callsignTranslations[callsignroot]) - end - - if self.callsignshort then - callsign = string.gsub(shortcallsign,callnumber,"").." "..callnumbermajor - else - callsign = string.gsub(shortcallsign,callnumber,"").." "..callnumbermajor.." "..callnumberminor - end - self:T("Generated Callsign for TTS = " .. callsign) - end - + callsign = Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations) + end return callsign end +--- [User] Set player callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns. +-- @param #AWACS self +-- @param #boolean ShortCallsign If true, only call out the major flight number +-- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name as-is, no amendments or numbers. +-- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized +-- callsigns from playername or group name. +-- @return #AWACS self +function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) + if not ShortCallsign or ShortCallsign == false then + self.callsignshort = false + else + self.callsignshort = true + end + self.keepnumber = Keepnumber or false + self.callsignTranslations = CallsignTranslations + return self +end + --- [Internal] Update contact from cluster data -- @param #AWACS self -- @param #number CID Contact ID @@ -2971,7 +2966,7 @@ function AWACS:_ShowAwacsInfo(Group) local report = REPORT:New("Info") report:Add("====================") report:Add(string.format("AWACS %s",self.callsigntxt)) - report:Add(string.format("Radio: %d %s",self.Frequency,UTILS.GetModulationName(self.Modulation))) + report:Add(string.format("Radio: %.3f %s",self.Frequency,UTILS.GetModulationName(self.Modulation))) report:Add(string.format("Bulls Alias: %s",self.AOName)) report:Add(string.format("Coordinate: %s",self.AOCoordinate:ToStringLLDDM())) report:Add("====================") @@ -4303,55 +4298,7 @@ function AWACS:_ReadAssignedGroupFromTID(TaskID) end return nil end - ---- [Internal] Create new idle task from contact to pick up later --- @param #AWACS self --- @param #string Description Task Type --- @param #table Object Object of TARGET --- @param Ops.Intelligence#INTEL.Contact Contact --- @return #AWACS self -function AWACS:_CreateIdleTaskForContact(Description,Object,Contact) - self:T(self.lid.."_CreateIdleTaskForContact "..Description) - local task = {} -- #AWACS.ManagedTask - self.ManagedTaskID = self.ManagedTaskID + 1 - task.TID = self.ManagedTaskID - task.AssignedGroupID = 0 - task.Status = AWACS.TaskStatus.IDLE - task.ToDo = Description - task.Target = TARGET:New(Object) - task.Contact = Contact - task.ScreenText = Description - if Description == AWACS.TaskDescription.ANCHOR or Description == AWACS.TaskDescription.REANCHOR then - task.Target.Type = TARGET.ObjectType.ZONE - end - self.ManagedTasks:Push(task,task.TID) - return self -end - ---- [Internal] Create new idle task from cluster to pick up later --- @param #AWACS self --- @param #string Description Task Type --- @param #table Object Object of TARGET --- @param Ops.Intelligence#INTEL.Cluster Cluster --- @return #AWACS self -function AWACS:_CreateIdleTaskForCluster(Description,Object,Cluster) - self:T(self.lid.."_CreateIdleTaskForCluster "..Description) - local task = {} -- #AWACS.ManagedTask - self.ManagedTaskID = self.ManagedTaskID + 1 - task.TID = self.ManagedTaskID - task.AssignedGroupID = 0 - task.Status = AWACS.TaskStatus.IDLE - task.ToDo = Description - task.Target = TARGET:New(self.intel:GetClusterCoordinate(Cluster)) - task.Cluster = Cluster - task.ScreenText = Description - if Description == AWACS.TaskDescription.ANCHOR or Description == AWACS.TaskDescription.REANCHOR then - task.Target.Type = TARGET.ObjectType.ZONE - end - self.ManagedTasks:Push(task,task.TID) - return self -end - + --- [Internal] Create radio entry to tell players that CAP is on station in Anchor -- @param #AWACS self -- @param #number GID Group ID @@ -5711,7 +5658,7 @@ function AWACS:onafterStart(From, Event, To) end --self.AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,self.PathToGoogleKey,"AWACS",self.Volume) - self.callsigntxt = string.format("%s",AWACS.CallSignClear[self.CallSign]) + self.callsigntxt = string.format("%s",self.CallSignClear[self.CallSign]) self:__CheckRadioQueue(-10) local sunrise = self.gettext:GetEntry("SUNRISE",self.locale) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 06cd10ae0..35fd9794e 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -39,6 +39,7 @@ -- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. +-- @field Core.Set#SET_GROUP allheligroupset Set of CSAR heli groups. -- @extends Core.Fsm#FSM --- *Combat search and rescue (CSAR) are search and rescue operations that are carried out during war that are within or near combat zones.* (Wikipedia) @@ -97,23 +98,22 @@ -- mycsar.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. -- mycsar.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! -- mycsar.verbose = 0 -- set to > 1 for stats output for debugging. --- -- (added 0.1.4) limit amount of downed pilots spawned by **ejection** events +-- -- limit amount of downed pilots spawned by **ejection** events -- mycsar.limitmaxdownedpilots = true -- mycsar.maxdownedpilots = 10 --- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors +-- -- allow to set far/near distance for approach and optionally pilot must open doors -- mycsar.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters -- mycsar.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters -- mycsar.pilotmustopendoors = false -- switch to true to enable check of open doors --- -- (added 0.1.9) -- mycsar.suppressmessages = false -- switch off all messaging if you want to do your own --- -- (added 0.1.11) -- mycsar.rescuehoverheight = 20 -- max height for a hovering rescue in meters -- mycsar.rescuehoverdistance = 10 -- max distance for a hovering rescue in meters --- -- (added 0.1.12) -- -- Country codes for spawned pilots -- mycsar.countryblue= country.id.USA -- mycsar.countryred = country.id.RUSSIA -- mycsar.countryneutral = country.id.UN_PEACEKEEPERS +-- mycsar.topmenuname = "CSAR" -- set the menu entry name +-- mycsar.ADFRadioPwr = 1000 -- ADF Beacons sending with 1KW as default -- -- ## 2.1 Experimental Features -- @@ -129,9 +129,11 @@ -- mycsar.SRSVoice = nil -- SRS voice, relevant for Google TTS -- mycsar.SRSGPathToCredentials = nil -- Path to your Google credentials json file, set this if you want to use Google TTS -- mycsar.SRSVolume = 1 -- Volume, between 0 and 1 +-- mycsar.SRSGender = "male" -- male or female voice -- -- -- mycsar.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection. Requires mycsar.enableForAI to be set to true. --shagrat -- mycsar.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases. +-- mycsar.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane -- -- ## 3. Results -- @@ -215,7 +217,7 @@ CSAR = { smokeMarkers = {}, -- tracks smoke markers for groups heliVisibleMessage = {}, -- tracks if the first message has been sent of the heli being visible heliCloseMessage = {}, -- tracks heli close message ie heli < 500m distance - max_units = 6, --number of pilots that can be carried + max_units = 6, -- number of pilots that can be carried hoverStatus = {}, -- tracks status of a helis hover above a downed pilot pilotDisabled = {}, -- tracks what aircraft a pilot is disabled for pilotLives = {}, -- tracks how many lives a pilot has @@ -228,6 +230,9 @@ CSAR = { rescuedpilots = 0, limitmaxdownedpilots = true, maxdownedpilots = 10, + allheligroupset = nil, + topmenuname = "CSAR", + ADFRadioPwr = 1000, } --- Downed pilots info. @@ -260,11 +265,12 @@ CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 CSAR.AircraftType["Bell-47"] = 2 CSAR.AircraftType["UH-60L"] = 10 -CSAR.AircraftType["AH-64D_BLK_II"] = 2 +CSAR.AircraftType["AH-64D_BLK_II"] = 2 +CSAR.AircraftType["Bronco-OV-10A"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.6" +CSAR.version="1.0.11" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -411,19 +417,25 @@ function CSAR:New(Coalition, Template, Alias) -- added 0.1.4 self.wetfeettemplate = nil self.usewetfeet = false + + -- added 0.1.8 + self.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane + + self.ADFRadioPwr = 1000 -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua -- needs SRS => 1.9.6 to work (works on the *server* side) self.useSRS = false -- Use FF\'s SRS integration - self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) + self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone" -- adjust your own path in your server(!) self.SRSchannel = 300 -- radio channel self.SRSModulation = radio.modulation.AM -- modulation self.SRSport = 5002 -- port self.SRSCulture = "en-GB" self.SRSVoice = nil self.SRSGPathToCredentials = nil - self.SRSVolume = 1 + self.SRSVolume = 1.0 -- volume 0.0 to 1.0 + self.SRSGender = "male" -- male or female ------------------------ --- Pseudo Functions --- @@ -925,7 +937,16 @@ function CSAR:_EventHandler(EventData) local _unit = _event.IniUnit local _group = _event.IniGroup - if _unit:IsHelicopter() or _group:IsHelicopter() then + + local function IsBronco(Group) + local grp = Group -- Wrapper.Group#GROUP + local typename = grp:GetTypeName() + self:T(typename) + if typename == "Bronco-OV-10A" then return true end + return false + end + + if _unit:IsHelicopter() or _group:IsHelicopter() or IsBronco(_group) then self:_AddMedevacMenuItem() end @@ -1580,21 +1601,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid end -- integrate SRS if _speak and self.useSRS then - local srstext = SOUNDTEXT:New(_text) - local path = self.SRSPath - local modulation = self.SRSModulation - local channel = self.SRSchannel - local msrs = MSRS:New(path,channel,modulation) - msrs:SetPort(self.SRSport) - msrs:SetLabel("CSAR") - msrs:SetCulture(self.SRSCulture) - msrs:SetCoalition(self.coalition) - msrs:SetVoice(self.SRSVoice) - if self.SRSGPathToCredentials then - msrs:SetGoogle(self.SRSGPathToCredentials) - end - msrs:SetVolume(self.SRSVolume) - msrs:PlaySoundText(srstext, 2) + self.SRSQueue:NewTransmission(_text,nil,self.msrs,nil,2) end return self end @@ -1894,7 +1901,7 @@ function CSAR:_AddMedevacMenuItem() self:T(self.lid .. " _AddMedevacMenuItem") local coalition = self.coalition - local allheligroupset = self.allheligroupset + local allheligroupset = self.allheligroupset -- Core.Set#SET_GROUP local _allHeliGroups = allheligroupset:GetSetObjects() -- rebuild units table @@ -1919,7 +1926,8 @@ function CSAR:_AddMedevacMenuItem() local groupname = _group:GetName() if self.addedTo[groupname] == nil then self.addedTo[groupname] = true - local _rootPath = MENU_GROUP:New(_group,"CSAR") + local menuname = self.topmenuname or "CSAR" + local _rootPath = MENU_GROUP:New(_group,menuname) local _rootMenu1 = MENU_GROUP_COMMAND:New(_group,"List Active CSAR",_rootPath, self._DisplayActiveSAR,self,_unitName) local _rootMenu2 = MENU_GROUP_COMMAND:New(_group,"Check Onboard",_rootPath, self._CheckOnboard,self,_unitName) local _rootMenu3 = MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, self._SignalFlare,self,_unitName) @@ -2025,10 +2033,13 @@ function CSAR:_AddBeaconToGroup(_group, _freq) end if _group:IsAlive() then - local _radioUnit = _group:GetUnit(1) - local Frequency = _freq -- Freq in Hertz - local Sound = "l10n/DEFAULT/"..self.radioSound - trigger.action.radioTransmission(Sound, _radioUnit:GetPositionVec3(), 0, false, Frequency, 1000) -- Beacon in MP only runs for exactly 30secs straight + local _radioUnit = _group:GetUnit(1) + if _radioUnit then + local Frequency = _freq -- Freq in Hertz + local Sound = "l10n/DEFAULT/"..self.radioSound + local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0} + trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000) -- Beacon in MP only runs for exactly 30secs straight + end end return self end @@ -2103,7 +2114,11 @@ function CSAR:onafterStart(From, Event, To) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PilotDead, self._EventHandler) - if self.useprefix then + + if self.allowbronco then + local prefixes = self.csarPrefix or {} + self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterStart() + elseif self.useprefix then local prefixes = self.csarPrefix or {} self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart() else @@ -2113,6 +2128,24 @@ function CSAR:onafterStart(From, Event, To) if self.wetfeettemplate then self.usewetfeet = true end + if self.useSRS then + local path = self.SRSPath + local modulation = self.SRSModulation + local channel = self.SRSchannel + self.msrs = MSRS:New(path,channel,modulation) + self.msrs:SetPort(self.SRSport) + self.msrs:SetLabel("CSAR") + self.msrs:SetCulture(self.SRSCulture) + self.msrs:SetCoalition(self.coalition) + self.msrs:SetVoice(self.SRSVoice) + self.msrs:SetGender(self.SRSGender) + if self.SRSGPathToCredentials then + self.msrs:SetGoogle(self.SRSGPathToCredentials) + end + self.msrs:SetVolume(self.SRSVolume) + self.msrs:SetLabel("CSAR") + self.SRSQueue = MSRSQUEUE:New("CSAR") + end self:__Status(-10) return self end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 522dd1efe..6ec93761c 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -23,6 +23,7 @@ -- @image OPS_CTLD.jpg -- Date: Feb 2022 +-- Last Update Sep 2022 do @@ -74,29 +75,13 @@ CTLD_ENGINEERING = { self.Name = Name or "Engineer Squad" -- #string self.Group = GROUP:FindByName(GroupName) -- Wrapper.Group#GROUP self.Unit = self.Group:GetUnit(1) -- Wrapper.Unit#UNIT - --self.C_Ops = C_Ops -- Ops.CTLD#CTLD self.HeliGroup = HeliGroup -- Wrapper.Group#GROUP self.HeliUnit = HeliUnit -- Wrapper.Unit#UNIT - --self.distance = Distance or UTILS.NMToMeters(1) self.currwpt = nil -- Core.Point#COORDINATE self.lid = string.format("%s (%s) | ",self.Name, self.Version) -- Start State. self.State = "Stopped" self.marktimer = 300 -- wait this many secs before trying a crate again - - --[[ Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("Stopped", "Start", "Running") -- Start FSM. - self:AddTransition("*", "Status", "*") - self:AddTransition("*", "Search", "Searching") - self:AddTransition("*", "Move", "Moving") - self:AddTransition("*", "Arrive", "Arrived") - self:AddTransition("*", "Build", "Building") - self:AddTransition("*", "Done", "Running") - self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - - self:__Start(5) - --]] self:Start() local parent = self:GetParent(self) return self @@ -200,10 +185,8 @@ CTLD_ENGINEERING = { -- have we tried this cargo recently? local tag = chalk.tag or "none" local timestamp = chalk.timestamp or 0 - --self:I({chalk}) -- enough time gone? local gone = timer.getAbsTime() - timestamp - --self:I({time=gone}) if gone >= self.marktimer then ok = true _cargo:WipeMark() @@ -287,7 +270,6 @@ CTLD_ENGINEERING = { if _point1 and _point2 then local distance1 = _point1:Get2DDistance(_point2) local distance2 = _point1:DistanceFromPointVec2(_point2) - --self:I({dist1=distance1, dist2=distance2}) if distance1 and type(distance1) == "number" then return distance1 elseif distance2 and type(distance2) == "number" then @@ -306,7 +288,8 @@ CTLD_ENGINEERING = { end -do +do + ------------------------------------------------------ --- **CTLD_CARGO** class, extends Core.Base#BASE -- @type CTLD_CARGO @@ -314,7 +297,7 @@ do -- @field #number ID ID of this cargo. -- @field #string Name Name for menu. -- @field #table Templates Table of #POSITIONABLE objects. --- @field #CTLD_CARGO.Enum CargoType Enumerator of Type. +-- @field #string CargoType Enumerator of Type. -- @field #boolean HasBeenMoved Flag for moving. -- @field #boolean LoadDirectly Flag for direct loading. -- @field #number CratesNeeded Crates needed to build. @@ -325,8 +308,9 @@ do -- @field #string Subcategory Sub-category name. -- @extends Core.Base#BASE + --- --- @field #CTLD_CARGO +-- @field CTLD_CARGO CTLD_CARGO = { ClassName = "CTLD_CARGO", ID = 0, @@ -343,9 +327,15 @@ CTLD_CARGO = { Mark = nil, } - --- --- Define cargo types. - -- @field Enum + -- @type CTLD_CARGO.Enum + -- @field #string VEHICLE + -- @field #string TROOPS + -- @field #string FOB + -- @field #string CRATE + -- @field #string REPAIR + -- @field #string ENGINEERS + -- @field #string STATIC CTLD_CARGO.Enum = { VEHICLE = "Vehicle", -- #string vehicles TROOPS = "Troops", -- #string troops @@ -542,7 +532,7 @@ CTLD_CARGO = { --- Query crate type for STATIC -- @param #CTLD_CARGO self - -- @param #boolean + -- @return #boolean function CTLD_CARGO:IsStatic() if self.CargoType == "Static" then return true @@ -551,19 +541,35 @@ CTLD_CARGO = { end end + --- Add mark + -- @param #CTLD_CARGO self + -- @return #CTLD_CARGO self function CTLD_CARGO:AddMark(Mark) self.Mark = Mark return self end + --- Get mark + -- @param #CTLD_CARGO self + -- @return #string Mark function CTLD_CARGO:GetMark(Mark) return self.Mark end + --- Wipe mark + -- @param #CTLD_CARGO self + -- @return #CTLD_CARGO self function CTLD_CARGO:WipeMark() self.Mark = nil return self end + + --- Get overall mass of a cargo object, i.e. crates needed x mass per crate + -- @param #CTLD_CARGO self + -- @return #number mass + function CTLD_CARGO:GetNetMass() + return self.CratesNeeded * self.PerCrateMass + end end @@ -1062,7 +1068,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.10" +CTLD.version="1.0.11" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1178,7 +1184,6 @@ function CTLD:New(Coalition, Prefixes, Alias) self.CrateDistance = 35 -- list/load crates in this radius self.ExtractFactor = 3.33 -- factor for troops extraction, i.e. CrateDistance * Extractfactor self.prefixes = Prefixes or {"Cargoheli"} - --self.I({prefixes = self.prefixes}) self.useprefix = true self.maximumHoverHeight = 15 @@ -1491,7 +1496,6 @@ function CTLD:_EventHandler(EventData) self:_RefreshF10Menus() end -- Herc support - --self:T_unit:GetTypeName()) if _unit:GetTypeName() == "Hercules" and self.enableHercules then local unitname = event.IniUnitName or "none" self.Loaded_Cargo[unitname] = nil @@ -1532,6 +1536,8 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) local instock = Cargotype:GetStock() local cgoname = Cargotype:GetName() local cgotype = Cargotype:GetType() + local cgonetmass = Cargotype:GetNetMass() + local maxloadable = self:_GetMaxLoadableMass(Unit) if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then -- nothing left over self:_SendMessage(string.format("Sorry, all %s are gone!", cgoname), 10, false, Group) @@ -1583,6 +1589,9 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) if troopsize + numberonboard > trooplimit then self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) return + elseif maxloadable < cgonetmass then + self:_SendMessage("Sorry, that\'s too heavy to load!", 10, false, Group) + return else self.CargoCounter = self.CargoCounter + 1 local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, cgotype, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) @@ -1610,7 +1619,6 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) local unit = v:GetUnit(1) -- Wrapper.Unit#UNIT local desc = unit:GetDesc() or nil - --self:I({desc = desc.attributes}) if distance < nearestDistance and distance ~= -1 and not desc.attributes.Infantry then nearestGroup = v nearestGroupIndex = k @@ -1647,7 +1655,6 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) -- walk through generics and find matching type local Cargotype = nil for k,v in pairs(self.Cargo_Crates) do - --self:I({groupname,v.Templates}) if matchstring(groupname,v.Templates) and matchstring(groupname,Repairtype) then Cargotype = v -- #CTLD_CARGO break @@ -1655,7 +1662,6 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) end if Cargotype == nil then - --self:_SendMessage("Can't find a matching group for " .. Repairtype, 10, false, Group) return nil, nil else return nearestGroup, Cargotype @@ -1674,17 +1680,14 @@ end function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering) self:T(self.lid .. " _RepairObjectFromCrates") local build = Build -- -- #CTLD.Buildable - --self:I({Build=Build}) local Repairtype = build.Template -- #string local NearestGroup, CargoType = self:_FindRepairNearby(Group,Unit,Repairtype) -- Wrapper.Group#GROUP, #CTLD_CARGO - --self:I({Repairtype=Repairtype, CargoType=CargoType, NearestGroup=NearestGroup}) if NearestGroup ~= nil then if self.repairtime < 2 then self.repairtime = 30 end -- noob catch if not Engineering then self:_SendMessage(string.format("Repair started using %s taking %d secs", build.Name, self.repairtime), 10, false, Group) end -- now we can build .... - --NearestGroup:Destroy(false) local name = CargoType:GetName() local required = CargoType:GetCratesNeeded() local template = CargoType:GetTemplates() @@ -1701,7 +1704,6 @@ function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering desttimer:Start(self.repairtime - 1) local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,object,true,NearestGroup:GetCoordinate()) buildtimer:Start(self.repairtime) - --self:_BuildObjectFromCrates(Group,Unit,object) else if not Engineering then self:_SendMessage("Can't repair this unit with " .. build.Name, 10, false, Group) @@ -1813,9 +1815,7 @@ end self:__TroopsExtracted(1,Group, Unit, nearestGroup) -- clean up: - --table.remove(self.DroppedTroops, nearestGroupIndex) if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then - --self:I("*****This CargoType has multiple templates: "..Cargotype.Name) for _,_key in pairs (Cargotype.Templates) do table.insert(secondarygroups,_key) end @@ -1966,7 +1966,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local width = width / 2 local Offy = math.random(-width,width) self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) - --:InitCoordinate(cratecoord) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) :InitLinkToUnit(Ship,dist,Offy,0) @@ -1976,7 +1975,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) :InitCoordinate(cratecoord) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) - --:InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle) :Spawn(270,cratealias) end local templ = cargotype:GetTemplates() @@ -2105,7 +2103,6 @@ function CTLD:_GetDistance(_point1, _point2) if _point1 and _point2 then local distance1 = _point1:Get2DDistance(_point2) local distance2 = _point1:DistanceFromPointVec2(_point2) - --self:I({dist1=distance1, dist2=distance2}) if distance1 and type(distance1) == "number" then return distance1 elseif distance2 and type(distance2) == "number" then @@ -2144,11 +2141,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) local maxmass = 2000 local maxloadable = 2000 if not _ignoreweight then - loadedmass = self:_GetUnitCargoMass(_unit) - unittype = _unit:GetTypeName() - capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities - maxmass = capabilities.cargoweightlimit or 2000 - maxloadable = maxmass - loadedmass + maxloadable = self:_GetMaxLoadableMass(_unit) end self:T(self.lid .. " Max loadable mass: " .. maxloadable) for _,_cargoobject in pairs (existingcrates) do @@ -2326,6 +2319,21 @@ function CTLD:_GetUnitCargoMass(Unit) return loadedmass end +--- (Internal) Function to calculate max loadable mass left over. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +-- @return #number maxloadable Max loadable mass in kg +function CTLD:_GetMaxLoadableMass(Unit) + self:T(self.lid .. " _GetMaxLoadableMass") + if not Unit then return 0 end + local loadable = 0 + local loadedmass = self:_GetUnitCargoMass(Unit) + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + local maxmass = capabilities.cargoweightlimit or 2000 -- max 2 tons + loadable = maxmass - loadedmass + return loadable +end + --- (Internal) Function to calculate and set Unit internal cargo mass -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit @@ -2333,9 +2341,6 @@ function CTLD:_UpdateUnitCargoMass(Unit) self:T(self.lid .. " _UpdateUnitCargoMass") local calculatedMass = self:_GetUnitCargoMass(Unit) Unit:SetUnitInternalCargo(calculatedMass) - --local report = REPORT:New("Loadmaster report") - --report:Add("Carrying " .. calculatedMass .. "Kg") - --self:_SendMessage(report:Text(),10,false,Unit:GetGroup()) return self end @@ -2353,6 +2358,7 @@ function CTLD:_ListCargo(Group, Unit) local cratelimit = capabilities.cratelimit -- #number local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo local loadedmass = self:_GetUnitCargoMass(Unit) -- #number + local maxloadable = self:_GetMaxLoadableMass(Unit) if self.Loaded_Cargo[unitname] then local no_troops = loadedcargo.Troopsloaded or 0 local no_crates = loadedcargo.Cratesloaded or 0 @@ -2387,11 +2393,11 @@ function CTLD:_ListCargo(Group, Unit) report:Add(" N O N E") end report:Add("------------------------------------------------------------") - report:Add("Total Mass: ".. loadedmass .. " kg") + report:Add("Total Mass: ".. loadedmass .. " kg. Loadable: "..maxloadable.." kg.") local text = report:Text() self:_SendMessage(text, 30, true, Group) else - self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit), 10, false, Group) + self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs",trooplimit,cratelimit,maxloadable), 10, false, Group) end return self end @@ -2567,7 +2573,6 @@ function CTLD:_UnloadTroops(Group, Unit) end -- template loop cargo:SetWasDropped(true) -- engineering group? - --self:I("Dropped Troop Type: "..type) if type == CTLD_CARGO.Enum.ENGINEERS then self.Engineers = self.Engineers + 1 local grpname = self.DroppedTroops[self.TroopCounter]:GetName() @@ -2943,7 +2948,6 @@ function CTLD:_MoveGroupToZone(Group) local groupcoord = Group:GetCoordinate() -- Get closest zone of type local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) - --self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) if (distance <= self.movetroopsdistance) and zone then -- yes, we can ;) local groupname = Group:GetName() @@ -3125,7 +3129,7 @@ function CTLD:_RefreshF10Menus() --- [Internal] Function to check if a template exists in the mission. -- @param #CTLD self -- @param #table temptable Table of string names --- @return #boolen outcome +-- @return #boolean outcome function CTLD:_CheckTemplates(temptable) self:T(self.lid .. " _CheckTemplates") local outcome = true @@ -3889,8 +3893,7 @@ end self:_SendMessage(text, 10, false, Group) return self end - - + --- (Internal) Check if a unit is in a load zone and is hovering in parameters. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit @@ -4160,9 +4163,6 @@ end self.Engineers = self.Engineers + 1 local grpname = self.DroppedTroops[self.TroopCounter]:GetName() self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname) - --self:I(string.format("%s Injected Engineers %s into action!",self.lid, name)) - else - --self:I(string.format("%s Injected Troops %s into action!",self.lid, name)) end if self.eventoninject then self:__TroopsDeployed(1,nil,nil,self.DroppedTroops[self.TroopCounter]) @@ -4533,7 +4533,6 @@ end local cargo = _cargo -- #CTLD_CARGO local object = cargo:GetPositionable() -- Wrapper.Static#STATIC if object and object:IsAlive() and cargo:WasDropped() then - self:I({_cargo}) statics[#statics+1] = cargo end end @@ -4550,7 +4549,6 @@ end template = { template } end for _,_name in pairs (template) do - --self:I(string.format("*** Saving CTLD: Matching %s with %s",name,_name)) if string.find(name,_name) and _cargo:GetType() ~= CTLD_CARGO.Enum.REPAIR then match = true cargo = thiscargo @@ -4745,7 +4743,6 @@ end local loadeddata = {} for line in file:lines() do - --self:I({line=type(line)}) loadeddata[#loadeddata+1] = line end file:close() @@ -4769,7 +4766,6 @@ end cargotemplates = UTILS.Split(cargotemplates,";") local size = tonumber(dataset[8]) local mass = tonumber(dataset[9]) - --self:I({groupname,vec3,cargoname,cargotemplates,cargotype,size,mass}) -- inject at Vec2 local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then @@ -4784,8 +4780,6 @@ end local size = tonumber(dataset[8]) local mass = tonumber(dataset[9]) local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) - -- STATIC,-84037,154,834021,Humvee,{Humvee;},Vehicle,1,100 - -- STATIC,-84036,154,834018,Ammunition-1,ammo_cargo,Static,1,500 local injectstatic = nil if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then cargotemplates = string.gsub(cargotemplates,"{","") @@ -4898,7 +4892,6 @@ CTLD_HERCULES.Types = { ["ART GVOZDIKA [34720lb]"] = {['name'] = "SAU Gvozdika", ['container'] = false}, ["APC MTLB Air [26400lb]"] = {['name'] = "MTLB", ['container'] = true}, ["APC MTLB Skid [26290lb]"] = {['name'] = "MTLB", ['container'] = false}, - --["Generic Crate [20000lb]"] = {['name'] = "Hercules_Container_Parachute", ['container'] = true} --nothing generic in Moose CTLD } --- Cargo Object @@ -5000,8 +4993,7 @@ function CTLD_HERCULES:New(Coalition, Alias, CtldObject) -- 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") - - --self:HandleEvent(EVENTS.Birth,self._HandleBirth) + self:HandleEvent(EVENTS.Shot, self._HandleShot) self:I(self.lid .. "Started") @@ -5115,13 +5107,8 @@ function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Positio local position = Cargo_Drop_Position:GetVec2() local Zone = ZONE_RADIUS:New("Cargo Static " .. math.random(1,10000),position,100) if not dead then - -- CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock) local injectstatic = CTLD_CARGO:New(nil,"Cargo Static Group "..math.random(1,10000),"iso_container",CTLD_CARGO.Enum.STATIC,true,false,1,nil,true,4500,1) self.CTLD:InjectStatics(Zone,injectstatic,true) - else - --local static = SPAWNSTATIC:NewFromType("iso_container","Cargos",Cargo_Country) - --static.InitDead = true - --static:SpawnFromZone(Zone,CargoHeading) end return self end @@ -5145,7 +5132,6 @@ function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direct self:T(self.lid .. 'Cargo_SpawnObjects') local CargoHeading = self.CargoHeading - --local Cargo_Drop_Position = {} if offload_cargo == true or ParatrooperGroupSpawn == true then if ParatrooperGroupSpawn == true then @@ -5157,14 +5143,7 @@ function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direct end else if all_cargo_gets_destroyed == true or Cargo_over_water == true then - if Container_Enclosed == true then - --self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) - if ParatrooperGroupSpawn == false then - --self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, true, Cargo_Country) - end - else - --self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) - end + else if all_cargo_survive_to_the_ground == true then if ParatrooperGroupSpawn == true then @@ -5209,14 +5188,12 @@ function CTLD_HERCULES:Calculate_Object_Height_AGL(group) return height - lheight else -- DCS object - --self:T({group}) if group:isExist() then local dcsposition = group:getPosition().p local dcsvec2 = {x = dcsposition.x, y = dcsposition.z} -- Vec2 local height = math.floor(group:getPosition().p.y - land.getHeight(dcsvec2)) self.ObjectTracker[group.id_] = dcsposition -- Vec3 self:T(self.lid .. "Height " .. height) - --self:T({group.id_,self.ObjectTracker[group.id_]}) return height else return 0 @@ -5437,27 +5414,6 @@ end function CTLD_HERCULES:_HandleBirth(event) -- not sure what this is needed for? I think this for setting generic crates "content" setting. self:T(self.lid .. "Birth Event ID:" .. event.id) - --[[ - if event.id == EVENTS.Birth then - local desc = event.initiator:getDesc() - if desc["displayName"] == "Hercules" then - local grpTab = {} - grpTab['object'] = event.IniGroup - grpTab['name'] = event.IniGroupName - grpTab['cargoType'] = 'Container red 1' - grpTab['cargoNum'] = 1 - grpTab['key'] = #self.carrierGroups + 1 - - table.insert(self.carrierGroups,grpTab) - - local hercCargoMenu = MENU_GROUP:New(event.IniGroup,"CargoTypes",nil) - local mlrs = MENU_GROUP_COMMAND:New(event.IniGroup,"MLRS",hercCargoMenu,self.SetType,self,grpTab['key'],'MLRS',1) - local mlrs = MENU_GROUP_COMMAND:New(event.IniGroup,"Mortar",hercCargoMenu,self.SetType,self,grpTab['key'],'2B11 mortar',8) - local mlrs = MENU_GROUP_COMMAND:New(event.IniGroup,"M-109",hercCargoMenu,self.SetType,self,grpTab['key'],'M-109',1) - local mlrs = MENU_GROUP_COMMAND:New(event.IniGroup,"FOB Crate",hercCargoMenu,self.SetType,self,grpTab['key'],'Container red 1',1) - end - end - --]] return self end diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index 840d2e3e2..54fe4ef31 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -327,7 +327,7 @@ FLIGHTCONTROL.FlightStatus={ --- FlightControl class version. -- @field #string version -FLIGHTCONTROL.version="0.7.2" +FLIGHTCONTROL.version="0.7.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -357,8 +357,10 @@ FLIGHTCONTROL.version="0.7.2" -- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a `#table` of multiple frequencies. -- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a `#table` of multiple modulations. -- @param #string PathToSRS Path to the directory, where SRS is located. +-- @param #number Port Port of SRS Server, defaults to 5002 +-- @param #string GoogleKey Path to the Google JSON-Key. -- @return #FLIGHTCONTROL self -function FLIGHTCONTROL:New(AirbaseName, Frequency, Modulation, PathToSRS) +function FLIGHTCONTROL:New(AirbaseName, Frequency, Modulation, PathToSRS, Port, GoogleKey) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #FLIGHTCONTROL @@ -406,15 +408,25 @@ function FLIGHTCONTROL:New(AirbaseName, Frequency, Modulation, PathToSRS) self:SetMarkHoldingPattern(true) self:SetRunwayRepairtime() + -- Set SRS Port + self:SetSRSPort(Port or 5002) + + -- Set Callsign Options + self:SetCallSignOptions(true,true) + -- Init msrs queue. self.msrsqueue=MSRSQUEUE:New(self.alias) -- SRS for Tower. self.msrsTower=MSRS:New(PathToSRS, Frequency, Modulation) + self.msrsTower:SetPort(self.Port) + self.msrsTower:SetGoogle(GoogleKey) self:SetSRSTower() -- SRS for Pilot. self.msrsPilot=MSRS:New(PathToSRS, Frequency, Modulation) + self.msrsPilot:SetPort(self.Port) + self.msrsPilot:SetGoogle(GoogleKey) self:SetSRSPilot() -- Wait at least 10 seconds after last radio message before calling the next status update. @@ -567,6 +579,15 @@ function FLIGHTCONTROL:SetFrequency(Frequency, Modulation) return self end +--- Set the SRS server port. +-- @param #FLIGHTCONTROL self +-- @param #number Port Port to be used. Defaults to 5002. +-- @return #FLIGHTCONTROL self +function FLIGHTCONTROL:SetSRSPort(Port) + self.Port = Port or 5002 + return self +end + --- Set SRS options for a given MSRS object. -- @param #FLIGHTCONTROL self -- @param Sound.SRS#MSRS msrs Moose SRS object. @@ -576,8 +597,9 @@ end -- @param #number Volume Volume. Default 1.0. -- @param #string Label Name under which SRS transmitts. -- @param #string PathToGoogleCredentials Path to google credentials json file. +-- @param #number Port Server port for SRS -- @return #FLIGHTCONTROL self -function FLIGHTCONTROL:_SetSRSOptions(msrs, Gender, Culture, Voice, Volume, Label, PathToGoogleCredentials) +function FLIGHTCONTROL:_SetSRSOptions(msrs, Gender, Culture, Voice, Volume, Label, PathToGoogleCredentials, Port) -- Defaults: Gender=Gender or "female" @@ -592,6 +614,7 @@ function FLIGHTCONTROL:_SetSRSOptions(msrs, Gender, Culture, Voice, Volume, Labe msrs:SetLabel(Label) msrs:SetGoogle(PathToGoogleCredentials) msrs:SetCoalition(self:GetCoalition()) + msrs:SetPort(Port or self.Port or 5002) end return self @@ -981,33 +1004,6 @@ end --- On Before Update status. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:onbeforeStatusUpdate() - - --[[ - if self.Tlastmessage then - local Tnow=timer.getAbsTime() - - -- Time interval between last radio message. - local dT=Tnow-self.Tlastmessage - - if dT%d sec ago. Status update allowed", dT, self.dTmessage)) - end - end - ]] local Tqueue=self.msrsqueue:CalcTransmisstionDuration() @@ -2633,8 +2629,18 @@ function FLIGHTCONTROL:_PlayerRadioCheck(groupname) local callsign=self:_GetCallsignName(flight) -- Pilot radio check. - local text=string.format("%s, %s, radio check %.3f", self.alias, callsign, self.frequency) + local text = "" + if type(self.frequency) == "table" then + local multifreq = "" + for _,_entry in pairs(self.frequency) do + multifreq = string.format("%s%.2f, ",multifreq,_entry) + end + multifreq = string.gsub(multifreq,", $","") + text=string.format("%s, %s, radio check %s", self.alias, callsign, multifreq) + else + text=string.format("%s, %s, radio check %.3f", self.alias, callsign, self.frequency) + end -- Radio message. self:TransmissionPilot(text, flight) @@ -2713,7 +2719,17 @@ function FLIGHTCONTROL:_PlayerInfoAirbase(groupname) local text=string.format("Airbase %s Info:", self.airbasename) text=text..string.format("\nATC Status: %s", self:GetState()) - text=text..string.format("\nFrequency: %.3f %s", self.frequency, UTILS.GetModulationName(self.modulation)) + + if type(self.frequency) == "table" then + local multifreq = "" + for i=1,#self.frequency do + multifreq=string.format("%s%.2f %s, ",multifreq,self.frequency[i],UTILS.GetModulationName(self.modulation[i] or 0)) + end + text=string.gsub(text,", $","") + text=text..string.format("\nFrequencies: %s", multifreq) + else + text=text..string.format("\nFrequency: %.3f %s", self.frequency, UTILS.GetModulationName(self.modulation)) + end text=text..string.format("\nRunway Landing: %s", self:GetActiveRunwayText()) text=text..string.format("\nRunway Takeoff: %s", self:GetActiveRunwayText(true)) @@ -4458,13 +4474,31 @@ function FLIGHTCONTROL:_IsFlightOnRunway(flight) return nil end +--- [User] Set callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns. +-- @param #FLIGHTCONTROL self +-- @param #boolean ShortCallsign If true, only call out the major flight number. Default = `true`. +-- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers. Default = `true`. +-- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized +-- callsigns from playername or group name. +-- @return #FLIGHTCONTROL self +function FLIGHTCONTROL:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) + if not ShortCallsign or ShortCallsign == false then + self.ShortCallsign = false + else + self.ShortCallsign = true + end + self.Keepnumber = Keepnumber or false + self.CallsignTranslations = CallsignTranslations + return self +end + --- Get callsign name of a given flight. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @return #string Callsign or "Ghostrider 1-1". function FLIGHTCONTROL:_GetCallsignName(flight) - local callsign=flight:GetCallsignName() + local callsign=flight:GetCallsignName(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) --local name=string.match(callsign, "%a+") --local number=string.match(callsign, "%d+") diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 342b34cb0..6c5d0bf2f 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -4495,8 +4495,10 @@ function FLIGHTGROUP:_PlayerSubtitles() -- Switch setting. playerData.subtitles=not playerData.subtitles + local onoff = playerData.subtitles == true and "ON" or "OFF" + -- Display message. - MESSAGE:New(string.format("%s, subtitles are now %s", playerData.name, tostring(playerData.subtitles)), 10, nil, true):ToGroup(self.group) + MESSAGE:New(string.format("%s, subtitles are now %s", playerData.name, onoff), 10, nil, true):ToGroup(self.group) else --TODO: Error diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index a8c556fd1..92fc2614e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -10123,7 +10123,7 @@ function OPSGROUP:_CheckDamage() for _,_element in pairs(self.elements) do local element=_element --Ops.OpsGroup#OPSGROUP.Element - if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then + if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then -- Current life points. local life=element.unit:GetLife() @@ -10135,8 +10135,8 @@ function OPSGROUP:_CheckDamage() self:ElementDamaged(element) damaged=true end - - end + + end end @@ -11748,8 +11748,11 @@ end --- Get callsign of the first element alive. -- @param #OPSGROUP self +-- @param #boolean ShortCallsign If true, append major flight number only +-- @param #boolean Keepnumber (Player only) If true, and using a customized callsign in the #GROUP name after an #-sign, use all of that information. +-- @param #table CallsignTranslations (optional) Translation table between callsigns -- @return #string Callsign name, e.g. Uzi11, or "Ghostrider11". -function OPSGROUP:GetCallsignName() +function OPSGROUP:GetCallsignName(ShortCallsign,Keepnumber,CallsignTranslations) local element=self:GetElementAlive() @@ -11757,6 +11760,9 @@ function OPSGROUP:GetCallsignName() self:T2(self.lid..string.format("Callsign %s", tostring(element.callsign))) local name=element.callsign or "Ghostrider11" name=name:gsub("-", "") + if self.group:IsPlayer() or CallsignTranslations then + name=self.group:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) + end return name end @@ -11778,7 +11784,7 @@ function OPSGROUP:_UpdatePosition() self.positionLast=self.position or self:GetVec3() self.headingLast=self.heading or self:GetHeading() self.orientXLast=self.orientX or self:GetOrientationX() - self.velocityLast=self.velocity or self.group:GetVelocityMPS() + self.velocityLast=self.velocity or self.group:GetVelocityMPS() -- Current state. self.position=self:GetVec3() @@ -12432,18 +12438,18 @@ function OPSGROUP:GetAmmoUnit(unit, display) if ammotable then local weapons=#ammotable - - --self:I(ammotable) + + --self:I(ammotable) -- Loop over all weapons. for w=1,weapons do -- Number of current weapon. local Nammo=ammotable[w]["count"] - - -- Range in meters. Seems only to exist for missiles (not shells). - local rmin=ammotable[w]["desc"]["rangeMin"] or 0 - local rmax=ammotable[w]["desc"]["rangeMaxAltMin"] or 0 + + -- Range in meters. Seems only to exist for missiles (not shells). + local rmin=ammotable[w]["desc"]["rangeMin"] or 0 + local rmax=ammotable[w]["desc"]["rangeMaxAltMin"] or 0 -- Type name of current weapon. local Tammo=ammotable[w]["desc"]["typeName"] @@ -12707,7 +12713,7 @@ function OPSGROUP:_AddElementByName(unitname) element.gid=element.DCSunit:getNumber() element.uid=element.DCSunit:getID() --element.group=unit:GetGroup() - element.controller=element.DCSunit:getController() + element.controller=element.DCSunit:getController() element.Nhit=0 element.opsgroup=self diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index fc22c647f..2202b73ee 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -80,7 +80,7 @@ PLAYERTASK = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.1" +PLAYERTASK.version="0.1.2" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -110,7 +110,7 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType) self.conditionSuccess = {} self.conditionFailure = {} self.TaskController = nil -- Ops.PlayerTask#PLAYERTASKCONTROLLER - self.timestamp = timer.getTime() + self.timestamp = timer.getAbsTime() self.TTSType = TTSType or "close air support" if Repeat then @@ -262,7 +262,7 @@ function PLAYERTASK:IsDone() return IsDone end ---- [User] Get clients assigned list as table +--- [User] Get client names assigned as table of #strings -- @param #PLAYERTASK self -- @return #table clients -- @return #number clientcount @@ -273,6 +273,17 @@ function PLAYERTASK:GetClients() return clientlist, count end +--- [User] Get #CLIENT objects assigned as table +-- @param #PLAYERTASK self +-- @return #table clients +-- @return #number clientcount +function PLAYERTASK:GetClientObjects() + self:T(self.lid.."GetClientObjects") + local clientlist = self.Clients:GetDataTable() or {} + local count = self.Clients:Count() + return clientlist, count +end + --- [User] Count clients -- @param #PLAYERTASK self -- @return #number clientcount @@ -548,6 +559,7 @@ end -- @return #PLAYERTASK self function PLAYERTASK:onafterPlanned(From, Event, To) self:T({From, Event, To}) + self.timestamp = timer.getAbsTime() return self end @@ -559,6 +571,7 @@ end -- @return #PLAYERTASK self function PLAYERTASK:onafterRequested(From, Event, To) self:T({From, Event, To}) + self.timestamp = timer.getAbsTime() return self end @@ -570,6 +583,7 @@ end -- @return #PLAYERTASK self function PLAYERTASK:onafterExecuting(From, Event, To) self:T({From, Event, To}) + self.timestamp = timer.getAbsTime() return self end @@ -581,6 +595,7 @@ end -- @return #PLAYERTASK self function PLAYERTASK:onafterStop(From, Event, To) self:T({From, Event, To}) + self.timestamp = timer.getAbsTime() return self end @@ -597,6 +612,7 @@ function PLAYERTASK:onafterClientAdded(From, Event, To, Client) local text = string.format("Player %s joined task %03d!",Client:GetPlayerName() or "Generic",self.PlayerTaskNr) self:I(self.lid..text) end + self.timestamp = timer.getAbsTime() return self end @@ -611,6 +627,7 @@ function PLAYERTASK:onafterDone(From, Event, To) if self.TaskController then self.TaskController:__TaskDone(-1,self) end + self.timestamp = timer.getAbsTime() self:__Stop(-1) return self end @@ -626,6 +643,7 @@ function PLAYERTASK:onafterCancel(From, Event, To) if self.TaskController then self.TaskController:__TaskCancelled(-1,self) end + self.timestamp = timer.getAbsTime() self:__Done(-1) return self end @@ -644,6 +662,7 @@ function PLAYERTASK:onafterSuccess(From, Event, To) if self.TargetMarker then self.TargetMarker:Remove() end + self.timestamp = timer.getAbsTime() self:__Done(-1) return self end @@ -673,6 +692,7 @@ function PLAYERTASK:onafterFailed(From, Event, To) end self:__Done(-1) end + self.timestamp = timer.getAbsTime() return self end ------------------------------------------------------------------------------------------------------------------- @@ -684,6 +704,11 @@ do ------------------------------------------------------------------------------------------------------------------- -- PLAYERTASKCONTROLLER -- TODO: PLAYERTASKCONTROLLER +-- DONE Playername customized +-- DONE Coalition-level screen info to SET based +-- DONE Flash directions +-- DONE less rebuilds menu, Task info menu available after join +-- DONE Limit menu entries ------------------------------------------------------------------------------------------------------------------- --- PLAYERTASKCONTROLLER class. @@ -715,8 +740,20 @@ do -- @field #boolean precisionbombing -- @field Ops.FlightGroup#FLIGHTGROUP LasingDrone -- @field Core.MarkerOps_BASE#MARKEROPS_BASE MarkerOps --- @field #boolean askinfomenu +-- @field #boolean taskinfomenu -- @field #boolean MarkerReadOnly +-- @field #table FlashPlayer List of player who switched Flashing Direction Info on +-- @field #boolean AllowFlash Flashing directions for players allowed +-- @field #number menuitemlimit +-- @field #boolean activehasinfomenu +-- @field #number holdmenutime +-- @field #table customcallsigns +-- @field #boolean ShortCallsign +-- @field #boolean Keepnumber +-- @field #table CallsignTranslations +-- @field #table PlayerFlashMenu +-- @field #table PlayerJoinMenu +-- @field #table PlayerInfoMenu -- @extends Core.Fsm#FSM --- @@ -893,6 +930,10 @@ do -- POINTEROVERTARGET = "%s, %s, pointer in reach for task %03d, lasing!", -- POINTERTARGETREPORT = "\nPointer in reach: %s\nLasing: %s", -- POINTERTARGETLASINGTTS = ". Pointer in reach and lasing.", +-- TARGET = "Target", +-- FLASHON = "%s - Flashing directions is now ON!", +-- FLASHOFF = "%s - Flashing directions is now OFF!", +-- FLASHMENU = "Flash Directions Switch", -- }, -- -- e.g. @@ -1004,8 +1045,16 @@ PLAYERTASKCONTROLLER = { gettext = nil, locale = "en", precisionbombing = false, - taskinfomenu = true, + taskinfomenu = false, + activehasinfomenu = false, MarkerReadOnly = false, + customcallsigns = {}, + ShortCallsign = true, + Keepnumber = false, + CallsignTranslations = nil, + PlayerFlashMenu = {}, + PlayerJoinMenu = {}, + PlayerInfoMenu = {}, } --- @@ -1030,9 +1079,9 @@ AUFTRAG.Type.PRECISIONBOMBING = "Precision Bombing" -- @field #number AAA GROUP.Attribute.GROUND_AAA -- @field #number EWR GROUP.Attribute.GROUND_EWR PLAYERTASKCONTROLLER.SeadAttributes = { - SAM = GROUP.Attribute.GROUND_SAM, - AAA = GROUP.Attribute.GROUND_AAA, - EWR = GROUP.Attribute.GROUND_EWR, + SAM = GROUP.Attribute.GROUND_SAM, + AAA = GROUP.Attribute.GROUND_AAA, + EWR = GROUP.Attribute.GROUND_EWR, } --- @@ -1094,6 +1143,10 @@ PLAYERTASKCONTROLLER.Messages = { POINTEROVERTARGET = "%s, %s, pointer in reach for task %03d, lasing!", POINTERTARGETREPORT = "\nPointer in reach: %s\nLasing: %s", POINTERTARGETLASINGTTS = ". Pointer in reach and lasing.", + TARGET = "Target", + FLASHON = "%s - Flashing directions is now ON!", + FLASHOFF = "%s - Flashing directions is now OFF!", + FLASHMENU = "Flash Directions Switch", }, DE = { TASKABORT = "Auftrag abgebrochen!", @@ -1151,12 +1204,16 @@ PLAYERTASKCONTROLLER.Messages = { POINTEROVERTARGET = "%s, %s, Marker im Zielbereich für %03d, Laser an!", POINTERTARGETREPORT = "\nMarker im Zielbereich: %s\nLaser an: %s", POINTERTARGETLASINGTTS = ". Marker im Zielbereich, Laser is an.", + TARGET = "Ziel", + FLASHON = "%s - Richtungsangaben einblenden ist EIN!", + FLASHOFF = "%s - Richtungsangaben einblenden ist AUS!", + FLASHMENU = "Richtungsangaben Schalter", }, } --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.30" +PLAYERTASKCONTROLLER.version="0.1.36" --- Constructor -- @param #PLAYERTASKCONTROLLER self @@ -1190,16 +1247,26 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self.TasksPerPlayer = FIFO:New() -- Utilities.FiFo#FIFO self.PrecisionTasks = FIFO:New() -- Utilities.FiFo#FIFO self.PlayerMenu = {} -- #table + self.FlashPlayer = {} -- #table + self.AllowFlash = false self.lasttaskcount = 0 self.taskinfomenu = false + self.activehasinfomenu = false self.MenuName = nil + self.menuitemlimit = 5 + self.holdmenutime = 30 self.MarkerReadOnly = false self.repeatonfailed = true self.repeattimes = 5 self.UseGroupNames = true + + self.customcallsigns = {} + self.ShortCallsign = true + self.Keepnumber = false + self.CallsignTranslations = nil if ClientFilter then self.ClientSet = SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterPrefixes(ClientFilter):FilterStart() @@ -1311,6 +1378,50 @@ function PLAYERTASKCONTROLLER:_InitLocalization() return self end +--- [User] Set flash directions option for player (player based info) +-- @param #PLAYERTASKCONTROLLER self +-- @param #boolean OnOff Set to `true` to switch on and `false` to switch off. Default is OFF. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetAllowFlashDirection(OnOff) + self:T(self.lid.."SetAllowFlashDirection") + self.AllowFlash = OnOff + return self +end + +--- [User] Set callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns. +-- @param #PLAYERTASKCONTROLLER self +-- @param #boolean ShortCallsign If true, only call out the major flight number +-- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers. +-- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized +-- callsigns from playername or group name. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) + if not ShortCallsign or ShortCallsign == false then + self.ShortCallsign = false + else + self.ShortCallsign = true + end + self.Keepnumber = Keepnumber or false + self.CallsignTranslations = CallsignTranslations + return self +end + +--- [Internal] Get text for text-to-speech. +-- Numbers are spaced out, e.g. "Heading 180" becomes "Heading 1 8 0 ". +-- @param #PLAYERTASKCONTROLLER self +-- @param #string text Original text. +-- @return #string Spoken text. +function PLAYERTASKCONTROLLER:_GetTextForSpeech(text) + + -- Space out numbers. + text=string.gsub(text,"%d","%1 ") + -- get rid of leading or trailing spaces + text=string.gsub(text,"^%s*","") + text=string.gsub(text,"%s*$","") + + return text +end + --- [User] Set repetition options for tasks -- @param #PLAYERTASKCONTROLLER self -- @param #boolean OnOff Set to `true` to switch on and `false` to switch off (defaults to true) @@ -1329,6 +1440,22 @@ function PLAYERTASKCONTROLLER:SetTaskRepetition(OnOff, Repeats) return self end +--- [Internal] Send message to SET_CLIENT of players +-- @param #PLAYERTASKCONTROLLER self +-- @param #string Text the text to be send +-- @param #number Seconds (optional) Seconds to show, default 10 +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:_SendMessageToClients(Text,Seconds) + self:T(self.lid.."_SendMessageToClients") + local seconds = Seconds or 10 + self.ClientSet:ForEachClient( + function (Client) + local m = MESSAGE:New(Text,seconds,"Tasking"):ToClient(Client) + end + ) + return self +end + --- [User] Allow precision laser-guided bombing on statics and "high-value" ground units (MBT etc) -- @param #PLAYERTASKCONTROLLER self -- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FlightGroup (e.g. drone) to be used for lasing (one unit in one group only). @@ -1416,14 +1543,15 @@ end function PLAYERTASKCONTROLLER:_GetPlayerName(Client) self:T(self.lid.."DisablePrecisionBombing") local playername = Client:GetPlayerName() - local ttsplayername = playername - if string.find(playername,"|") then - -- personalized flight name in player naming - ttsplayername = string.match(playername,"| ([%a]+)") - end - if string.find(playername,"#") then - -- personalized flight name in player naming - ttsplayername = string.match(playername,"# ([%a]+)") + local ttsplayername = nil + if not self.customcallsigns[playername] then + local playergroup = Client:GetGroup() + ttsplayername = playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local newplayername = self:_GetTextForSpeech(ttsplayername) + self.customcallsigns[playername] = newplayername + ttsplayername = newplayername + else + ttsplayername = self.customcallsigns[playername] end return playername, ttsplayername end @@ -1456,6 +1584,24 @@ function PLAYERTASKCONTROLLER:DisableTaskInfoMenu() return self end +--- [User] Set menu build fine-tuning options +-- @param #PLAYERTASKCONTROLLER self +-- @param #boolean InfoMenu If `true` this option will allow to show the Task Info-Menu also when a player has an active task. +-- Since the menu isn't refreshed if a player holds an active task, the info in there might be stale. +-- @param #number ItemLimit Number of items per task type to show, default 5. +-- @param #number HoldTime Minimum number of seconds between menu refreshes (called every 30 secs) if a player has **no active task**. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetMenuOptions(InfoMenu,ItemLimit,HoldTime) + self:T(self.lid.."SetMenuOptions") + self.activehasinfomenu = InfoMenu or false + if self.activehasinfomenu then + self:EnableTaskInfoMenu() + end + self.menuitemlimit = ItemLimit or 5 + self.holdmenutime = HoldTime or 30 + return self +end + --- [User] Forbid F10 markers to be deleted by pilots. Note: Marker will auto-delete when the undelying task is done. -- @param #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self @@ -1519,11 +1665,15 @@ function PLAYERTASKCONTROLLER:_EventHandler(EventData) modulation = UTILS.GetModulationName(modulation) local switchtext = self.gettext:GetEntry("BROADCAST",self.locale) - local playername = EventData.IniPlayerName - if string.find(playername,"|") then + local playername = EventData.IniPlayerName + if EventData.IniGroup then -- personalized flight name in player naming - playername = string.match(playername,"| ([%a]+)") + if self.customcallsigns[playername] then + self.customcallsigns[playername] = nil + end + playername = EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber) end + playername = self:_GetTextForSpeech(playername) --local text = string.format("%s, %s, switch to %s for task assignment!",EventData.IniPlayerName,self.MenuName or self.Name,freqtext) local text = string.format(switchtext,self.MenuName or self.Name,playername,freqtext) self.SRSQueue:NewTransmission(text,nil,self.SRS,timer.getAbsTime()+60,2,{EventData.IniGroup},text,30,self.BCFrequency,self.BCModulation) @@ -1532,11 +1682,6 @@ function PLAYERTASKCONTROLLER:_EventHandler(EventData) return self end -function PLAYERTASKCONTROLLER:_DummyMenu(group) - self:T(self.lid.."_DummyMenu") - return self -end - --- [User] Set locale for localization. Defaults to "en" -- @param #PLAYERTASKCONTROLLER self -- @param #string Locale The locale to use @@ -1634,12 +1779,13 @@ function PLAYERTASKCONTROLLER:_GetTasksPerType() for _,_task in pairs(datatable) do local task = _task -- Ops.PlayerTask#PLAYERTASK local threat = task.Target:GetThreatLevelMax() - threattable[#threattable+1]={task=task,threat=threat} + if not task:IsDone() then + threattable[#threattable+1]={task=task,threat=threat} + end end table.sort(threattable, function (k1, k2) return k1.threat > k2.threat end ) - for _id,_data in pairs(threattable) do local threat=_data.threat local task = _data.task -- Ops.PlayerTask#PLAYERTASK @@ -1684,8 +1830,11 @@ function PLAYERTASKCONTROLLER:_CheckTaskQueue() self:T("*****Removing player " .. _id) self.TasksPerPlayer:PullByID(_id) end - local task = self.TaskQueue:PullByID(_id) -- Ops.PlayerTask#PLAYERTASK - task = nil + local TNow = timer.getAbsTime() + if TNow - task.timestamp > 10 then + local task = self.TaskQueue:PullByID(_id) -- Ops.PlayerTask#PLAYERTASK + task = nil + end end end end @@ -1789,8 +1938,12 @@ function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() local text = "" for _,playername in pairs(clients) do local pointertext = self.gettext:GetEntry("POINTEROVERTARGET",self.locale) + local ttsplayername = playername + if self.customcallsigns[playername] then + ttsplayername = self.customcallsigns[playername] + end --text = string.format("%s, %s, pointer over target for task %03d, lasing!", playername, self.MenuName or self.Name, task.PlayerTaskNr) - text = string.format(pointertext, playername, self.MenuName or self.Name, task.PlayerTaskNr) + text = string.format(pointertext, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr) if not self.NoScreenOutput then local client = nil self.ClientSet:ForEachClient( @@ -1921,15 +2074,15 @@ end -- Default attribute types are: GROUP.Attribute.GROUND_SAM, GROUP.Attribute.GROUND_AAA, and GROUP.Attribute.GROUND_EWR. -- If you want to e.g. exclude AAA, so target groups with this attribute are assigned CAS or BAI tasks, and not SEAD, use this function as follows: -- --- `mycontroller:SetSEADAttributes({GROUP.Attribute.GROUND_SAM, GROUP.Attribute.GROUND_EWR})` +-- `mycontroller:SetSEADAttributes({GROUP.Attribute.GROUND_SAM, GROUP.Attribute.GROUND_EWR})` -- function PLAYERTASKCONTROLLER:SetSEADAttributes(Attributes) - self:T(self.lid.."SetSEADAttributes") - if type(Attributes) ~= "table" then - Attributes = {Attributes} - end - self.SeadAttributes = Attributes - return self + self:T(self.lid.."SetSEADAttributes") + if type(Attributes) ~= "table" then + Attributes = {Attributes} + end + self.SeadAttributes = Attributes + return self end --- [Internal] Function the check against SeadAttributes @@ -1937,15 +2090,15 @@ end -- @param #string Attribute -- @return #boolean IsSead function PLAYERTASKCONTROLLER:_IsAttributeSead(Attribute) - self:T(self.lid.."_IsAttributeSead?") - local IsSead = false - for _,_attribute in pairs(self.SeadAttributes) do - if Attribute == _attribute then - IsSead = true - break - end - end - return IsSead + self:T(self.lid.."_IsAttributeSead?") + local IsSead = false + for _,_attribute in pairs(self.SeadAttributes) do + if Attribute == _attribute then + IsSead = true + break + end + end + return IsSead end --- [Internal] Add a task to the task queue @@ -1975,22 +2128,22 @@ function PLAYERTASKCONTROLLER:_AddTask(Target) elseif targetobject:IsInstanceOf("GROUP") then self:T("SEAD Check GROUP") local attribute = targetobject:GetAttribute() - if self:_IsAttributeSead(attribute) then - type = AUFTRAG.Type.SEAD - --ttstype = "suppress air defense" - ttstype = self.gettext:GetEntry("SEADTTS",self.locale) - end + if self:_IsAttributeSead(attribute) then + type = AUFTRAG.Type.SEAD + --ttstype = "suppress air defense" + ttstype = self.gettext:GetEntry("SEADTTS",self.locale) + end elseif targetobject:IsInstanceOf("SET_GROUP") then self:T("SEAD Check SET_GROUP") targetobject:ForEachGroup( function (group) local attribute = group:GetAttribute() - if self:_IsAttributeSead(attribute) then - type = AUFTRAG.Type.SEAD - --ttstype = "suppress air defense" - ttstype = self.gettext:GetEntry("SEADTTS",self.locale) - end - end + if self:_IsAttributeSead(attribute) then + type = AUFTRAG.Type.SEAD + --ttstype = "suppress air defense" + ttstype = self.gettext:GetEntry("SEADTTS",self.locale) + end + end ) elseif targetobject:IsInstanceOf("SET_UNIT") then self:T("SEAD Check SET_UNIT") @@ -2135,7 +2288,7 @@ function PLAYERTASKCONTROLLER:_JoinTask(Group, Client, Task) -- Player already has a task if not self.NoScreenOutput then local text = self.gettext:GetEntry("HAVEACTIVETASK",self.locale) - local m=MESSAGE:New(text,"10","Tasking"):ToGroup(Group) + local m=MESSAGE:New(text,"10","Tasking"):ToClient(Client) end return self end @@ -2151,9 +2304,11 @@ function PLAYERTASKCONTROLLER:_JoinTask(Group, Client, Task) local text = string.format(joined,ttsplayername, self.MenuName or self.Name, Task.TTSType, Task.PlayerTaskNr) self:T(self.lid..text) if not self.NoScreenOutput then - local m=MESSAGE:New(text,"10","Tasking"):ToAll() + self:_SendMessageToClients(text) + --local m=MESSAGE:New(text,"10","Tasking"):ToAll() end if self.UseSRS then + self:I(self.lid..text) self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) end self.TasksPerPlayer:Push(Task,playername) @@ -2168,6 +2323,55 @@ function PLAYERTASKCONTROLLER:_JoinTask(Group, Client, Task) return self end +--- [Internal] Switch flashing info for a client +-- @param #PLAYERTASKCONTROLLER self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Client#CLIENT Client +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:_SwitchFlashing(Group, Client) + self:T(self.lid.."_SwitchFlashing") + local playername, ttsplayername = self:_GetPlayerName(Client) + if (not self.FlashPlayer[playername]) or (self.FlashPlayer[playername] == false) then + -- Switch on + self.FlashPlayer[playername] = Client + local flashtext = self.gettext:GetEntry("FLASHON",self.locale) + local text = string.format(flashtext,ttsplayername) + local m = MESSAGE:New(text,10,"Tasking"):ToClient(Client) + else + -- Switch off + self.FlashPlayer[playername] = false + local flashtext = self.gettext:GetEntry("FLASHOFF",self.locale) + local text = string.format(flashtext,ttsplayername) + local m = MESSAGE:New(text,10,"Tasking"):ToClient(Client) + end + return self +end + +--- [Internal] Flashing directional info for a client +-- @param #PLAYERTASKCONTROLLER self +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:_FlashInfo() + self:T(self.lid.."_FlashInfo") + for _playername,_client in pairs(self.FlashPlayer) do + if _client and _client:IsAlive() then + if self.TasksPerPlayer:HasUniqueID(_playername) then + local task = self.TasksPerPlayer:ReadByID(_playername) -- Ops.PlayerTask#PLAYERTASK + local Coordinate = task.Target:GetCoordinate() + local CoordText = "" + if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then + CoordText = Coordinate:ToStringA2G(_client) + else + CoordText = Coordinate:ToStringA2A(_client) + end + local targettxt = self.gettext:GetEntry("TARGET",self.locale) + local text = "Target: "..CoordText + local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client) + end + end + end + return self +end + --- [Internal] Show active task info -- @param #PLAYERTASKCONTROLLER self -- @param Wrapper.Group#GROUP Group @@ -2221,9 +2425,10 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task) local clienttxt = self.gettext:GetEntry("PILOTS",self.locale) if clientcount > 0 then for _,_name in pairs(clientlist) do - if string.find(_name,"|") then + if self.customcallsigns[_name] then -- personalized flight name in player naming - _name = string.match(_name,"| ([%a]+)") + --_name = string.match(_name,"| ([%a]+)") + _name = self.customcallsigns[_name] end clienttxt = clienttxt .. _name .. ", " end @@ -2252,7 +2457,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task) text = self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then - local m=MESSAGE:New(text,15,"Tasking"):ToGroup(Group) + local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) end return self end @@ -2281,7 +2486,7 @@ function PLAYERTASKCONTROLLER:_MarkTask(Group, Client) text = self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then - local m=MESSAGE:New(text,"10","Tasking"):ToGroup(Group) + local m=MESSAGE:New(text,"10","Tasking"):ToClient(Client) end return self end @@ -2309,7 +2514,7 @@ function PLAYERTASKCONTROLLER:_SmokeTask(Group, Client) text = self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then - local m=MESSAGE:New(text,15,"Tasking"):ToGroup(Group) + local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) end return self end @@ -2337,7 +2542,7 @@ function PLAYERTASKCONTROLLER:_FlareTask(Group, Client) text = self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then - local m=MESSAGE:New(text,15,"Tasking"):ToGroup(Group) + local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) end return self end @@ -2366,25 +2571,83 @@ function PLAYERTASKCONTROLLER:_AbortTask(Group, Client) text = self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then - local m=MESSAGE:New(text,15,"Tasking"):ToGroup(Group) + local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) end self:_BuildMenus(Client,true) return self end +--- [Internal] Build Task Info Menu +-- @param #PLAYERTASKCONTROLLER self +-- @param Wrapper.Group#GROUP group +-- @param Wrapper.Client#CLIENT client +-- @param #string playername +-- @param Core.Menu#MENU_BASE topmenu +-- @param #table tasktypes +-- @param #table taskpertype +-- @return #table taskinfomenu +function PLAYERTASKCONTROLLER:_BuildTaskInfoMenu(group,client,playername,topmenu,tasktypes,taskpertype) + self:T(self.lid.."_BuildTaskInfoMenu") + local taskinfomenu = nil + if self.taskinfomenu then + local menutaskinfo = self.gettext:GetEntry("MENUTASKINFO",self.locale) + local taskinfomenu = MENU_GROUP_DELAYED:New(group,menutaskinfo,topmenu) + local ittypes = {} + local itaskmenu = {} + + for _tasktype,_data in pairs(tasktypes) do + ittypes[_tasktype] = MENU_GROUP_DELAYED:New(group,_tasktype,taskinfomenu) + local tasks = taskpertype[_tasktype] or {} + local n = 0 + for _,_task in pairs(tasks) do + _task = _task -- Ops.PlayerTask#PLAYERTASK + local pilotcount = _task:CountClients() + local newtext = "]" + local tnow = timer.getTime() + -- marker for new tasks + if tnow - _task.timestamp < 60 then + newtext = "*]" + end + local menutaskno = self.gettext:GetEntry("MENUTASKNO",self.locale) + local text = string.format("%s %03d [%d%s",menutaskno,_task.PlayerTaskNr,pilotcount,newtext) + if self.UseGroupNames then + local name = _task.Target:GetName() + if name ~= "Unknown" then + text = string.format("%s (%03d) [%d%s",name,_task.PlayerTaskNr,pilotcount,newtext) + end + end + local taskentry = MENU_GROUP_COMMAND_DELAYED:New(group,text,ittypes[_tasktype],self._ActiveTaskInfo,self,group,client,_task) + --taskentry:SetTag(playername) + itaskmenu[#itaskmenu+1] = taskentry + -- keep max items limit + n = n + 1 + if n >= self.menuitemlimit then + break + end + end + end + end + return taskinfomenu +end + --- [Internal] Build client menus -- @param #PLAYERTASKCONTROLLER self -- @param Wrapper.Client#CLIENT Client (optional) build for this client name only -- @param #boolean enforced +-- @param #boolean fromsuccess -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced) +function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess) self:T(self.lid.."_BuildMenus") local clients = self.ClientSet:GetAliveSet() - + local joinorabort = false + local timedbuild = false + if Client then + -- client + enforced -- join task or abort clients = {Client} enforced = true + joinorabort = true end for _,_client in pairs(clients) do @@ -2394,11 +2657,6 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced) local unknown = self.gettext:GetEntry("UNKNOWN",self.locale) local playername = client:GetPlayerName() or unknown if group and client then - --- - -- Conditions for menu rebuild - -- 1) Player has no menu - -- 2) Player has no running task - -- 3) enforced --- -- TOPMENU --- @@ -2407,26 +2665,47 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced) local menuname = self.MenuName or longname local playerhastask = false - if self:_CheckPlayerHasTask(playername) then playerhastask = true end + if self:_CheckPlayerHasTask(playername) and not fromsuccess then playerhastask = true end local topmenu = nil - self:T("Playerhastask = "..tostring(playerhastask).." Enforced = "..tostring(enforced)) + self:T("Playerhastask = "..tostring(playerhastask).." Enforced = "..tostring(enforced).." Join or Abort = "..tostring(joinorabort)) + -- Cases to rebuild menu + -- 1) new player + -- 2) player joined a task, joinorabort = true + -- 3) player left a task, joinorabort = true + -- 4) player has no task, but number of tasks changed, and last build > 30 secs ago if self.PlayerMenu[playername] then - if enforced or not playerhastask then + -- NOT a new player + -- 2)+3) Join or abort? + if joinorabort then self.PlayerMenu[playername]:RemoveSubMenus() + self.PlayerMenu[playername]:SetTag(timer.getAbsTime()) + topmenu = self.PlayerMenu[playername] + elseif (not playerhastask) or enforced then + -- 4) last build > 30 secs? + local T0 = timer.getAbsTime() + local TDiff = T0-self.PlayerMenu[playername].MenuTag + self:T("TDiff = "..string.format("%.2d",TDiff)) + if TDiff >= self.holdmenutime then + self.PlayerMenu[playername]:RemoveSubMenus() + self.PlayerMenu[playername]:SetTag(timer.getAbsTime()) + timedbuild = true + end + topmenu = self.PlayerMenu[playername] end - topmenu = self.PlayerMenu[playername] else + -- 1) new player# topmenu = MENU_GROUP_DELAYED:New(group,menuname,nil) self.PlayerMenu[playername] = topmenu + self.PlayerMenu[playername]:SetTag(timer.getAbsTime()) end --- -- ACTIVE TASK MENU --- if playerhastask and enforced then - + --self:T("Building Active Task Menus for "..playername) local menuactive = self.gettext:GetEntry("MENUACTIVE",self.locale) local menuinfo = self.gettext:GetEntry("MENUINFO",self.locale) local menumark = self.gettext:GetEntry("MENUMARK",self.locale) @@ -2443,24 +2722,31 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced) local flare = MENU_GROUP_COMMAND_DELAYED:New(group,menuflare,active,self._FlareTask,self,group,client) end local abort = MENU_GROUP_COMMAND_DELAYED:New(group,menuabort,active,self._AbortTask,self,group,client) - - elseif (self.TaskQueue:Count() > 0 and enforced) or (not playerhastask) then + if self.activehasinfomenu and self.taskinfomenu then + --self:T("Building Active-Info Menus for "..playername) + local tasktypes = self:_GetAvailableTaskTypes() + local taskpertype = self:_GetTasksPerType() + if self.PlayerInfoMenu[playername] then + self.PlayerInfoMenu[playername]:RemoveSubMenus() + end + self.PlayerInfoMenu[playername] = self:_BuildTaskInfoMenu(group,client,playername,topmenu,tasktypes,taskpertype) + end + elseif (self.TaskQueue:Count() > 0 and enforced) or (not playerhastask and (timedbuild or joinorabort)) then + --self:T("Building Join Menus for "..playername) --- -- JOIN TASK MENU --- local tasktypes = self:_GetAvailableTaskTypes() local taskpertype = self:_GetTasksPerType() local menujoin = self.gettext:GetEntry("MENUJOIN",self.locale) - local menutaskinfo = self.gettext:GetEntry("MENUTASKINFO",self.locale) local joinmenu = MENU_GROUP_DELAYED:New(group,menujoin,topmenu) local ttypes = {} local taskmenu = {} - local ittypes = {} - local itaskmenu = {} for _tasktype,_data in pairs(tasktypes) do ttypes[_tasktype] = MENU_GROUP_DELAYED:New(group,_tasktype,joinmenu) local tasks = taskpertype[_tasktype] or {} + local n = 0 for _,_task in pairs(tasks) do _task = _task -- Ops.PlayerTask#PLAYERTASK local pilotcount = _task:CountClients() @@ -2478,45 +2764,21 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced) text = string.format("%s (%03d) [%d%s",name,_task.PlayerTaskNr,pilotcount,newtext) end end - --if _task:GetState() == "Planned" or (not _task:HasPlayerName(playername)) then local taskentry = MENU_GROUP_COMMAND_DELAYED:New(group,text,ttypes[_tasktype],self._JoinTask,self,group,client,_task) - taskentry:SetTag(playername) + --taskentry:SetTag(playername) taskmenu[#taskmenu+1] = taskentry - --end + n = n + 1 + if n >= self.menuitemlimit then + break + end end end - --joinmenu:Set() - if self.taskinfomenu then - local taskinfomenu = MENU_GROUP_DELAYED:New(group,menutaskinfo,topmenu) - for _tasktype,_data in pairs(tasktypes) do - ittypes[_tasktype] = MENU_GROUP_DELAYED:New(group,_tasktype,taskinfomenu) - local tasks = taskpertype[_tasktype] or {} - for _,_task in pairs(tasks) do - _task = _task -- Ops.PlayerTask#PLAYERTASK - local pilotcount = _task:CountClients() - local newtext = "]" - local tnow = timer.getTime() - -- marker for new tasks - if tnow - _task.timestamp < 60 then - newtext = "*]" - end - local menutaskno = self.gettext:GetEntry("MENUTASKNO",self.locale) - local text = string.format("%s %03d [%d%s",menutaskno,_task.PlayerTaskNr,pilotcount,newtext) - if self.UseGroupNames then - local name = _task.Target:GetName() - if name ~= "Unknown" then - text = string.format("%s (%03d) [%d%s",name,_task.PlayerTaskNr,pilotcount,newtext) - end - end - --if _task:GetState() == "Planned" or (not _task:HasPlayerName(playername)) then - local taskentry = MENU_GROUP_COMMAND_DELAYED:New(group,text,ittypes[_tasktype],self._ActiveTaskInfo,self,group,client,_task) - taskentry:SetTag(playername) - itaskmenu[#itaskmenu+1] = taskentry - --end - end + --self:T("Building Join-Info Menus for "..playername) + if self.PlayerInfoMenu[playername] then + self.PlayerInfoMenu[playername]:RemoveSubMenus() end - --taskinfomenu:Set() + self.PlayerInfoMenu[playername] = self:_BuildTaskInfoMenu(group,client,playername,topmenu,tasktypes,taskpertype) end elseif self.TaskQueue:Count() == 0 then -- no tasks (yet) @@ -2525,7 +2787,11 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced) end --- -- REFRESH MENU - --- + --- + if self.AllowFlash then + local flashtext = self.gettext:GetEntry("FLASHMENU",self.locale) + local flashmenu = MENU_GROUP_COMMAND_DELAYED:New(group,flashtext,self.PlayerMenu[playername],self._SwitchFlashing,self,group,client) + end self.PlayerMenu[playername]:Set() end end @@ -2769,11 +3035,14 @@ end -- @param #string To -- @return #PLAYERTASKCONTROLLER self function PLAYERTASKCONTROLLER:onafterStatus(From, Event, To) - self:I({From, Event, To}) + self:T({From, Event, To}) self:_CheckTargetQueue() self:_CheckTaskQueue() self:_CheckPrecisionTasks() + if self.AllowFlash then + self:_FlashInfo() + end local targetcount = self.TargetQueue:Count() local taskcount = self.TaskQueue:Count() @@ -2826,7 +3095,8 @@ function PLAYERTASKCONTROLLER:onafterTaskCancelled(From, Event, To, Task) local canceltxttts = self.gettext:GetEntry("TASKCANCELLEDTTS",self.locale) local taskname = string.format(canceltxt, Task.PlayerTaskNr, tostring(Task.Type)) if not self.NoScreenOutput then - local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) + self:_SendMessageToClients(taskname,15) + --local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) end if self.UseSRS then taskname = string.format(canceltxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType)) @@ -2849,12 +3119,17 @@ function PLAYERTASKCONTROLLER:onafterTaskSuccess(From, Event, To, Task) local succtxttts = self.gettext:GetEntry("TASKSUCCESSTTS",self.locale) local taskname = string.format(succtxt, Task.PlayerTaskNr, tostring(Task.Type)) if not self.NoScreenOutput then - local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) + self:_SendMessageToClients(taskname,15) + --local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) end if self.UseSRS then taskname = string.format(succtxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType)) self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2) end + local clients=Task:GetClientObjects() + for _,client in pairs(clients) do + self:_BuildMenus(client,true,true) + end return self end @@ -2872,7 +3147,8 @@ function PLAYERTASKCONTROLLER:onafterTaskFailed(From, Event, To, Task) local failtxttts = self.gettext:GetEntry("TASKFAILEDTTS",self.locale) local taskname = string.format(failtxt, Task.PlayerTaskNr, tostring(Task.Type)) if not self.NoScreenOutput then - local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) + self:_SendMessageToClients(taskname,15) + --local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) end if self.UseSRS then taskname = string.format(failtxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType)) @@ -2895,7 +3171,8 @@ function PLAYERTASKCONTROLLER:onafterTaskRepeatOnFailed(From, Event, To, Task) local repfailtxttts = self.gettext:GetEntry("TASKFAILEDREPLANTTS",self.locale) local taskname = string.format(repfailtxt, Task.PlayerTaskNr, tostring(Task.Type)) if not self.NoScreenOutput then - local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) + self:_SendMessageToClients(taskname,15) + --local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) end if self.UseSRS then taskname = string.format(repfailtxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType)) @@ -2917,7 +3194,8 @@ function PLAYERTASKCONTROLLER:onafterTaskAdded(From, Event, To, Task) local addtxt = self.gettext:GetEntry("TASKADDED",self.locale) local taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.Type)) if not self.NoScreenOutput then - local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) + self:_SendMessageToClients(taskname,15) + --local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) end if self.UseSRS then taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.TTSType)) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 0d91736c0..bc1bb67dc 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -143,6 +143,107 @@ MSRS = { -- @field #string version MSRS.version="0.1.0" +--- Voices +-- @type Voices +MSRS.Voices = { + Microsoft = { + ["Hedda"] = "Microsoft Hedda Desktop", -- de-DE + ["Hazel"] = "Microsoft Hazel Desktop", -- en-GB + ["David"] = "Microsoft David Desktop", -- en-US + ["Zira"] = "Microsoft Zira Desktop", -- en-US + ["Hortense"] = "Microsoft Hortense Desktop", --fr-FR + }, + Google = { + Standard = { + ["en_AU_Standard_A"] = 'en-AU-Standard-A', -- [1] FEMALE + ["en_AU_Standard_B"] = 'en-AU-Standard-B', -- [2] MALE + ["en_AU_Standard_C"] = 'en-AU-Standard-C', -- [3] FEMALE + ["en_AU_Standard_D"] = 'en-AU-Standard-D', -- [4] MALE + ["en_IN_Standard_A"] = 'en-IN-Standard-A', -- [5] FEMALE + ["en_IN_Standard_B"] = 'en-IN-Standard-B', -- [6] MALE + ["en_IN_Standard_C"] = 'en-IN-Standard-C', -- [7] MALE + ["en_IN_Standard_D"] = 'en-IN-Standard-D', -- [8] FEMALE + ["en_GB_Standard_A"] = 'en-GB-Standard-A', -- [9] FEMALE + ["en_GB_Standard_B"] = 'en-GB-Standard-B', -- [10] MALE + ["en_GB_Standard_C"] = 'en-GB-Standard-C', -- [11] FEMALE + ["en_GB_Standard_D"] = 'en-GB-Standard-D', -- [12] MALE + ["en_GB_Standard_F"] = 'en-GB-Standard-F', -- [13] FEMALE + ["en_US_Standard_A"] = 'en-US-Standard-A', -- [14] MALE + ["en_US_Standard_B"] = 'en-US-Standard-B', -- [15] MALE + ["en_US_Standard_C"] = 'en-US-Standard-C', -- [16] FEMALE + ["en_US_Standard_D"] = 'en-US-Standard-D', -- [17] MALE + ["en_US_Standard_E"] = 'en-US-Standard-E', -- [18] FEMALE + ["en_US_Standard_F"] = 'en-US-Standard-F', -- [19] FEMALE + ["en_US_Standard_G"] = 'en-US-Standard-G', -- [20] FEMALE + ["en_US_Standard_H"] = 'en-US-Standard-H', -- [21] FEMALE + ["en_US_Standard_I"] = 'en-US-Standard-I', -- [22] MALE + ["en_US_Standard_J"] = 'en-US-Standard-J', -- [23] MALE + ["fr_FR_Standard_A"] = "fr-FR-Standard-A", -- Female + ["fr_FR_Standard_B"] = "fr-FR-Standard-B", -- Male + ["fr_FR_Standard_C"] = "fr-FR-Standard-C", -- Female + ["fr_FR_Standard_D"] = "fr-FR-Standard-D", -- Male + ["fr_FR_Standard_E"] = "fr-FR-Standard-E", -- Female + ["de_DE_Standard_A"] = "de-DE-Standard-A", -- Female + ["de_DE_Standard_B"] = "de-DE-Standard-B", -- Male + ["de_DE_Standard_C"] = "de-DE-Standard-C", -- Female + ["de_DE_Standard_D"] = "de-DE-Standard-D", -- Male + ["de_DE_Standard_E"] = "de-DE-Standard-E", -- Male + ["de_DE_Standard_F"] = "de-DE-Standard-F", -- Female + ["es_ES_Standard_A"] = "es-ES-Standard-A", -- Female + ["es_ES_Standard_B"] = "es-ES-Standard-B", -- Male + ["es_ES_Standard_C"] = "es-ES-Standard-C", -- Female + ["es_ES_Standard_D"] = "es-ES-Standard-D", -- Female + ["it_IT_Standard_A"] = "it-IT-Standard-A", -- Female + ["it_IT_Standard_B"] = "it-IT-Standard-B", -- Female + ["it_IT_Standard_C"] = "it-IT-Standard-C", -- Male + ["it_IT_Standard_D"] = "it-IT-Standard-D", -- Male + }, + Wavenet = { + ["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- [1] FEMALE + ["en_AU_Wavenet_B"] = 'en-AU-Wavenet-B', -- [2] MALE + ["en_AU_Wavenet_C"] = 'en-AU-Wavenet-C', -- [3] FEMALE + ["en_AU_Wavenet_D"] = 'en-AU-Wavenet-D', -- [4] MALE + ["en_IN_Wavenet_A"] = 'en-IN-Wavenet-A', -- [5] FEMALE + ["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- [6] MALE + ["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- [7] MALE + ["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- [8] FEMALE + ["en_GB_Wavenet_A"] = 'en-GB-Wavenet-A', -- [9] FEMALE + ["en_GB_Wavenet_B"] = 'en-GB-Wavenet-B', -- [10] MALE + ["en_GB_Wavenet_C"] = 'en-GB-Wavenet-C', -- [11] FEMALE + ["en_GB_Wavenet_D"] = 'en-GB-Wavenet-D', -- [12] MALE + ["en_GB_Wavenet_F"] = 'en-GB-Wavenet-F', -- [13] FEMALE + ["en_US_Wavenet_A"] = 'en-US-Wavenet-A', -- [14] MALE + ["en_US_Wavenet_B"] = 'en-US-Wavenet-B', -- [15] MALE + ["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- [16] FEMALE + ["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- [17] MALE + ["en_US_Wavenet_E"] = 'en-US-Wavenet-E', -- [18] FEMALE + ["en_US_Wavenet_F"] = 'en-US-Wavenet-F', -- [19] FEMALE + ["en_US_Wavenet_G"] = 'en-US-Wavenet-G', -- [20] FEMALE + ["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- [21] FEMALE + ["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- [22] MALE + ["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- [23] MALE + ["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-A", -- Female + ["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-B", -- Male + ["fr_FR_Wavenet_C"] = "fr-FR-Wavenet-C", -- Female + ["fr_FR_Wavenet_D"] = "fr-FR-Wavenet-D", -- Male + ["fr_FR_Wavenet_E"] = "fr-FR-Wavenet-E", -- Female + ["de_DE_Wavenet_A"] = "de-DE-Wavenet-A", -- Female + ["de_DE_Wavenet_B"] = "de-DE-Wavenet-B", -- Male + ["de_DE_Wavenet_C"] = "de-DE-Wavenet-C", -- Female + ["de_DE_Wavenet_D"] = "de-DE-Wavenet-D", -- Male + ["de_DE_Wavenet_E"] = "de-DE-Wavenet-E", -- Male + ["de_DE_Wavenet_F"] = "de-DE-Wavenet-F", -- Female + ["es_ES_Wavenet_B"] = "es-ES-Wavenet-B", -- Male + ["es_ES_Wavenet_C"] = "es-ES-Wavenet-C", -- Female + ["es_ES_Wavenet_D"] = "es-ES-Wavenet-D", -- Female + ["it_IT_Wavenet_A"] = "it-IT-Wavenet-A", -- Female + ["it_IT_Wavenet_B"] = "it-IT-Wavenet-B", -- Female + ["it_IT_Wavenet_C"] = "it-IT-Wavenet-C", -- Male + ["it_IT_Wavenet_D"] = "it-IT-Wavenet-D", -- Male + } , + }, + } + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -705,16 +806,8 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp modus=modus:gsub("0", "AM") modus=modus:gsub("1", "FM") - -- This did not work well. Stopped if the transmission was a bit longer with no apparent error. - --local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed) - - -- Command from orig STTS script. Works better for some unknown reason! - --local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT") - - --local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT") - -- Command. - local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s" -v "%.1f"', path, exe, freqs, modus, coal, port, label,volume) + local command=string.format('"%s\\%s" -f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"', path, exe, freqs, modus, coal, port, label,volume) -- Set voice or gender/culture. if voice then @@ -1098,5 +1191,3 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 2eb8673ec..cf47f396c 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -489,7 +489,7 @@ UTILS.hPa2inHg = function( hPa ) return hPa * 0.0295299830714 end ---- Convert knots to alitude corrected KIAS, e.g. for tankers. +--- Convert knots to altitude corrected KIAS, e.g. for tankers. -- @param #number knots Speed in knots. -- @param #number altitude Altitude in feet -- @return #number Corrected KIAS @@ -1849,6 +1849,11 @@ function UTILS.IsLoadingDoorOpen( unit_name ) return true -- no doors on this one ;) end + if type_name == "Bronco-OV-10A" then + BASE:T(unit_name .. " front door(s) are open") + return true -- no doors on this one ;) + end + return false end -- nil @@ -2317,7 +2322,7 @@ end -- @param #string Filename The name of the file. -- @param #boolean Spawn If set to false, do not re-spawn the groups loaded in location and reduce to size. -- @return Core.Set#SET_GROUP Set of GROUP objects. --- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate }` +-- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate, template=template }` function UTILS.LoadSetOfGroups(Path,Filename,Spawn) local spawn = true if Spawn == false then spawn = false end @@ -2340,10 +2345,10 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn) local posz = tonumber(dataset[6]) local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) local group=nil - local data = { groupname=groupname, size=size, coordinate=coordinate } + local data = { groupname=groupname, size=size, coordinate=coordinate, template=template } table.insert(datatable,data) if spawn then - local group = SPAWN:New(groupname) + local group = SPAWN:New(template) :InitDelayOff() :OnSpawnGroup( function(spwndgrp) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 2036df9cc..4ef5d9731 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1455,14 +1455,14 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, --_spot:MarkToAll(string.format("Parking spot %d free=%s", parkingspot.TerminalID, tostring(not occupied))) if occupied then - self:I(string.format("%s: Parking spot id %d occupied.", airport, _termid)) + self:T(string.format("%s: Parking spot id %d occupied.", airport, _termid)) else - self:I(string.format("%s: Parking spot id %d free.", airport, _termid)) + self:T(string.format("%s: Parking spot id %d free.", airport, _termid)) if nvalid<_nspots then table.insert(validspots, {Coordinate=_spot, TerminalID=_termid}) end nvalid=nvalid+1 - self:I(string.format("%s: Parking spot id %d free. Nfree=%d/%d.", airport, _termid, nvalid,_nspots)) + self:T(string.format("%s: Parking spot id %d free. Nfree=%d/%d.", airport, _termid, nvalid,_nspots)) end end -- loop over units @@ -1980,7 +1980,7 @@ function AIRBASE:SetActiveRunwayLanding(Name, PreferLeft) end if runway then - self:I(string.format("%s: Setting active runway for landing as %s", self.AirbaseName, self:GetRunwayName(runway))) + self:T(string.format("%s: Setting active runway for landing as %s", self.AirbaseName, self:GetRunwayName(runway))) else self:E("ERROR: Could not set the runway for landing!") end @@ -2028,7 +2028,7 @@ function AIRBASE:SetActiveRunwayTakeoff(Name, PreferLeft) end if runway then - self:I(string.format("%s: Setting active runway for takeoff as %s", self.AirbaseName, self:GetRunwayName(runway))) + self:T(string.format("%s: Setting active runway for takeoff as %s", self.AirbaseName, self:GetRunwayName(runway))) else self:E("ERROR: Could not set the runway for takeoff!") end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 11cdab4af..e8e2e6fb9 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -673,24 +673,7 @@ end -- @param #GROUP self -- @return #boolean If true, group is associated with a client or player slot. function GROUP:IsPlayer() - - -- Get group. - -- local group=self:GetGroup() - - -- Units of template group. - local units=self:GetTemplate().units - - -- Get numbers. - for _,unit in pairs(units) do - - -- Check if unit name matach and skill is Client or Player. - if unit.name==self:GetName() and (unit.skill=="Client" or unit.skill=="Player") then - return true - end - - end - - return false + return self:GetUnit(1):IsPlayer() end --- Returns the UNIT wrapper class with number UnitNumber. @@ -725,29 +708,6 @@ function GROUP:GetUnit( UnitNumber ) end ---- Check if an (air) group is a client or player slot. Information is retrieved from the group template. --- @param #GROUP self --- @return #boolean If true, group is associated with a client or player slot. -function GROUP:IsPlayer() - - -- Get group. - -- local group=self:GetGroup() - - -- Units of template group. - local units=self:GetTemplate().units - - -- Get numbers. - for _,unit in pairs(units) do - - -- Check if unit name matach and skill is Client or Player. - if unit.name==self:GetName() and (unit.skill=="Client" or unit.skill=="Player") then - return true - end - - end - - return false -end --- Returns the DCS Unit with number UnitNumber. -- If the underlying DCS Unit does not exist, the method will return nil. . @@ -2767,94 +2727,85 @@ function GROUP:GetHighestThreat() return nil, nil end ---do -- Smoke +--- Get TTS friendly, optionally customized callsign mainly for **player groups**. A customized callsign is taken from the #GROUP name, after an optional '#' sign, e.g. "Aerial 1-1#Ghostrider" resulting in "Ghostrider 9", or, +-- if that isn't available, from the playername, as set in the mission editor main screen under Logbook, after an optional '|' sign (actually, more of a personal call sign), e.g. "Apple|Moose" results in "Moose 9 1". Options see below. +-- @param #GROUP self +-- @param #boolean ShortCallsign Return a shortened customized callsign, i.e. "Ghostrider 9" and not "Ghostrider 9 1" +-- @param #boolean Keepnumber (Player only) Return customized callsign, incl optional numbers at the end, e.g. "Aerial 1-1#Ghostrider 109" results in "Ghostrider 109", if you want to e.g. use historical US Navy Callsigns +-- @param #table CallsignTranslations Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized +-- callsigns from playername or group name. +-- @return #string Callsign +-- @usage +-- -- Set Custom CAP Flight Callsigns for use with TTS +-- mygroup:GetCustomCallSign(true,false,{ +-- Devil = 'Bengal', +-- Snake = 'Winder', +-- Colt = 'Camelot', +-- Enfield = 'Victory', +-- Uzi = 'Evil Eye' +-- }) -- ------ Signal a flare at the position of the GROUP. ----- @param #GROUP self ----- @param Utilities.Utils#FLARECOLOR FlareColor ---function GROUP:Flare( FlareColor ) --- self:F2() --- trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 ) ---end +-- results in this outcome if the group has Callsign "Enfield 9 1" on the 1st #UNIT of the group: -- ------ Signal a white flare at the position of the GROUP. ----- @param #GROUP self ---function GROUP:FlareWhite() --- self:F2() --- trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 ) ---end +-- 'Victory 9' -- ------ Signal a yellow flare at the position of the GROUP. ----- @param #GROUP self ---function GROUP:FlareYellow() --- self:F2() --- trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 ) ---end --- ------ Signal a green flare at the position of the GROUP. ----- @param #GROUP self ---function GROUP:FlareGreen() --- self:F2() --- trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 ) ---end --- ------ Signal a red flare at the position of the GROUP. ----- @param #GROUP self ---function GROUP:FlareRed() --- self:F2() --- local Vec3 = self:GetVec3() --- if Vec3 then --- trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) --- end ---end --- ------ Smoke the GROUP. ----- @param #GROUP self ---function GROUP:Smoke( SmokeColor, Range ) --- self:F2() --- if Range then --- trigger.action.smoke( self:GetRandomVec3( Range ), SmokeColor ) --- else --- trigger.action.smoke( self:GetVec3(), SmokeColor ) --- end --- ---end --- ------ Smoke the GROUP Green. ----- @param #GROUP self ---function GROUP:SmokeGreen() --- self:F2() --- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Green ) ---end --- ------ Smoke the GROUP Red. ----- @param #GROUP self ---function GROUP:SmokeRed() --- self:F2() --- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Red ) ---end --- ------ Smoke the GROUP White. ----- @param #GROUP self ---function GROUP:SmokeWhite() --- self:F2() --- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.White ) ---end --- ------ Smoke the GROUP Orange. ----- @param #GROUP self ---function GROUP:SmokeOrange() --- self:F2() --- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Orange ) ---end --- ------ Smoke the GROUP Blue. ----- @param #GROUP self ---function GROUP:SmokeBlue() --- self:F2() --- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue ) ---end --- --- --- ---end +-- +function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) + --self:I("GetCustomCallSign") + + local callsign = "Ghost 1" + if self:IsAlive() then + local IsPlayer = self:IsPlayer() + local shortcallsign = self:GetCallsign() or "unknown91" -- e.g.Uzi91, but we want Uzi 9 1 + local callsignroot = string.match(shortcallsign, '(%a+)') -- Uzi + --self:I("CallSign = " .. callsignroot) + local groupname = self:GetName() + local callnumber = string.match(shortcallsign, "(%d+)$" ) or "91" -- 91 + local callnumbermajor = string.char(string.byte(callnumber,1)) -- 9 + local callnumberminor = string.char(string.byte(callnumber,2)) -- 1 + local personalized = false + if IsPlayer and string.find(groupname,"#") then + -- personalized flight name in group naming + if Keepnumber then + shortcallsign = string.match(groupname,"#(.+)") or "Ghost 111" -- Ghostrider 219 + else + shortcallsign = string.match(groupname,"#%s*([%a]+)") or "Ghost" -- Ghostrider + end + personalized = true + elseif IsPlayer and string.find(self:GetPlayerName(),"|") then + -- personalized flight name in group naming + shortcallsign = string.match(self:GetPlayerName(),"|%s*([%a]+)") or string.match(self:GetPlayerName(),"|%s*([%d]+)") or "Ghost" -- Ghostrider + personalized = true + end + + if (not personalized) and CallsignTranslations and CallsignTranslations[callsignroot] then + callsignroot = CallsignTranslations[callsignroot] + end + + if personalized then + -- player personalized callsign + -- remove trailing/leading spaces + shortcallsign=string.gsub(shortcallsign,"^%s*","") + shortcallsign=string.gsub(shortcallsign,"%s*$","") + if Keepnumber then + return shortcallsign -- Ghostrider 219 + elseif ShortCallsign then + callsign = shortcallsign.." "..callnumbermajor -- Ghostrider 9 + else + callsign = shortcallsign.." "..callnumbermajor.." "..callnumberminor -- Ghostrider 9 1 + end + return callsign + end + + -- AI or not personalized + if ShortCallsign then + callsign = callsignroot.." "..callnumbermajor -- Uzi/Victory 9 + else + callsign = callsignroot.." "..callnumbermajor.." "..callnumberminor -- Uzi/Victory 9 1 + end + + --self:I("Generated Callsign = " .. callsign) + end + + return callsign +end