Merge branch 'develop' into FF/Ops

This commit is contained in:
Frank 2022-09-30 19:34:23 +02:00
commit efe7673c4c
20 changed files with 1289 additions and 728 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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 )

View File

@ -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()

View File

@ -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

View File

@ -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
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -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 <DCS root>\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

View File

@ -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

View File

@ -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<self.dTmessage then
-- Time
local dt=self.dTmessage-dT+1
-- Debug info.
local text=string.format("Last message sent %d sec ago. Will call status again in %d sec", dT, dt)
self:T(self.lid..text)
-- Call status again in dt seconds.
self:__StatusUpdate(-dt)
-- Deny transition.
return false
else
self:T2(self.lid..string.format("Last radio sent %d>%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+")

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -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)

View File

@ -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

View File

@ -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