Merge branch 'develop' into FF/Ops

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

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