Merge branch 'FF/Ops' into FF/OpsDev

This commit is contained in:
Frank
2024-01-07 21:17:17 +01:00
23 changed files with 546 additions and 199 deletions

View File

@@ -1344,8 +1344,16 @@ function DATABASE:_RegisterAirbase(airbase)
-- Unique ID. -- Unique ID.
local airbaseUID=airbase:GetID(true) local airbaseUID=airbase:GetID(true)
local typename = airbase:GetTypeName()
local category = airbase.category
if category == Airbase.Category.SHIP and typename == "FARP_SINGLE_01" then
category = Airbase.Category.HELIPAD
end
-- Debug output. -- Debug output.
local text=string.format("Register %s: %s (UID=%d), Runways=%d, Parking=%d [", AIRBASE.CategoryName[airbase.category], tostring(DCSAirbaseName), airbaseUID, #airbase.runways, airbase.NparkingTotal) local text=string.format("Register %s: %s (UID=%d), Runways=%d, Parking=%d [", AIRBASE.CategoryName[category], tostring(DCSAirbaseName), airbaseUID, #airbase.runways, airbase.NparkingTotal)
for _,terminalType in pairs(AIRBASE.TerminalType) do for _,terminalType in pairs(AIRBASE.TerminalType) do
if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType] then if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType] then
text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType]) text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType])

View File

@@ -459,14 +459,14 @@ end
_MESSAGESRS = {} _MESSAGESRS = {}
--- Set up MESSAGE generally to allow Text-To-Speech via SRS and TTS functions. --- Set up MESSAGE generally to allow Text-To-Speech via SRS and TTS functions. `SetMSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible.
-- @param #string PathToSRS Path to SRS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone". -- @param #string PathToSRS (optional) Path to SRS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" or your configuration file setting.
-- @param #number Port Port number of SRS, defaults to 5002. -- @param #number Port Port (optional) number of SRS, defaults to 5002 or your configuration file setting.
-- @param #string PathToCredentials (optional) Path to credentials file for e.g. Google. -- @param #string PathToCredentials (optional) Path to credentials file for Google.
-- @param #number Frequency Frequency in MHz. Can also be given as a #table of frequencies. -- @param #number Frequency Frequency in MHz. Can also be given as a #table of frequencies.
-- @param #number Modulation Modulation, i.e. radio.modulation.AM or radio.modulation.FM. Can also be given as a #table of modulations. -- @param #number Modulation Modulation, i.e. radio.modulation.AM or radio.modulation.FM. Can also be given as a #table of modulations.
-- @param #string Gender (optional) Gender, i.e. "male" or "female", defaults to "female". -- @param #string Gender (optional) Gender, i.e. "male" or "female", defaults to "female" or your configuration file setting.
-- @param #string Culture (optional) Culture, e.g. "en-US", defaults to "en-GB" -- @param #string Culture (optional) Culture, e.g. "en-US", defaults to "en-GB" or your configuration file setting.
-- @param #string Voice (optional) Voice. Will override gender and culture settings, e.g. MSRS.Voices.Microsoft.Hazel or MSRS.Voices.Google.Standard.de_DE_Standard_D. Hint on Microsoft voices - working voices are limited to Hedda, Hazel, David, Zira and Hortense. **Must** be installed on your Desktop or Server! -- @param #string Voice (optional) Voice. Will override gender and culture settings, e.g. MSRS.Voices.Microsoft.Hazel or MSRS.Voices.Google.Standard.de_DE_Standard_D. Hint on Microsoft voices - working voices are limited to Hedda, Hazel, David, Zira and Hortense. **Must** be installed on your Desktop or Server!
-- @param #number Coalition (optional) Coalition, can be coalition.side.RED, coalition.side.BLUE or coalition.side.NEUTRAL. Defaults to coalition.side.NEUTRAL. -- @param #number Coalition (optional) Coalition, can be coalition.side.RED, coalition.side.BLUE or coalition.side.NEUTRAL. Defaults to coalition.side.NEUTRAL.
-- @param #number Volume (optional) Volume, can be between 0.0 and 1.0 (loudest). -- @param #number Volume (optional) Volume, can be between 0.0 and 1.0 (loudest).
@@ -480,42 +480,51 @@ _MESSAGESRS = {}
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS() -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS()
-- --
function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate) function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate)
_MESSAGESRS.MSRS = MSRS:New(PathToSRS,Frequency or 243,Modulation or radio.modulation.AM,Volume)
_MESSAGESRS.frequency = Frequency _MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
_MESSAGESRS.modulation = Modulation or radio.modulation.AM
_MESSAGESRS.MSRS:SetCoalition(Coalition or coalition.side.NEUTRAL) _MESSAGESRS.frequency = Frequency or MSRS.frequencies or 243
_MESSAGESRS.coalition = Coalition or coalition.side.NEUTRAL _MESSAGESRS.modulation = Modulation or MSRS.modulations or radio.modulation.AM
_MESSAGESRS.MSRS = MSRS:New(_MESSAGESRS.PathToSRS,_MESSAGESRS.frequency, _MESSAGESRS.modulation)
_MESSAGESRS.coalition = Coalition or MSRS.coalition or coalition.side.NEUTRAL
_MESSAGESRS.MSRS:SetCoalition(_MESSAGESRS.coalition)
_MESSAGESRS.coordinate = Coordinate _MESSAGESRS.coordinate = Coordinate
_MESSAGESRS.MSRS:SetCoordinate(Coordinate)
if Coordinate then
_MESSAGESRS.MSRS:SetCoordinate(Coordinate)
end
_MESSAGESRS.Culture = Culture or MSRS.culture or "en-GB"
_MESSAGESRS.MSRS:SetCulture(Culture) _MESSAGESRS.MSRS:SetCulture(Culture)
_MESSAGESRS.Culture = Culture or "en-GB"
_MESSAGESRS.Gender = Gender or MSRS.gender or "female"
_MESSAGESRS.MSRS:SetGender(Gender) _MESSAGESRS.MSRS:SetGender(Gender)
_MESSAGESRS.Gender = Gender or "female"
_MESSAGESRS.MSRS:SetGoogle(PathToCredentials) if PathToCredentials then
_MESSAGESRS.MSRS:SetProviderOptionsGoogle(PathToCredentials)
_MESSAGESRS.MSRS:SetProvider(MSRS.Provider.GOOGLE)
end
_MESSAGESRS.label = Label or MSRS.Label or "MESSAGE"
_MESSAGESRS.MSRS:SetLabel(Label or "MESSAGE") _MESSAGESRS.MSRS:SetLabel(Label or "MESSAGE")
_MESSAGESRS.label = Label or "MESSAGE"
_MESSAGESRS.port = Port or MSRS.port or 5002
_MESSAGESRS.MSRS:SetPort(Port or 5002) _MESSAGESRS.MSRS:SetPort(Port or 5002)
_MESSAGESRS.port = Port or 5002
_MESSAGESRS.volume = Volume or 1 _MESSAGESRS.volume = Volume or MSRS.volume or 1
_MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume) _MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume)
if Voice then _MESSAGESRS.MSRS:SetVoice(Voice) end if Voice then _MESSAGESRS.MSRS:SetVoice(Voice) end
_MESSAGESRS.voice = Voice --or MSRS.Voices.Microsoft.Hedda _MESSAGESRS.voice = Voice or MSRS.voice --or MSRS.Voices.Microsoft.Hedda
_MESSAGESRS.SRSQ = MSRSQUEUE:New(Label or "MESSAGE") _MESSAGESRS.SRSQ = MSRSQUEUE:New(_MESSAGESRS.label)
end end
--- Sends a message via SRS. --- Sends a message via SRS. `ToSRS()` will try to use as many attributes configured with @{Core.Message#MESSAGE.SetMSRS}() and @{Sound.SRS#MSRS.LoadConfigFile}() as possible.
-- @param #MESSAGE self -- @param #MESSAGE self
-- @param #number frequency (optional) Frequency in MHz. Can also be given as a #table of frequencies. Only needed if you want to override defaults set with `MESSAGE.SetMSRS()` for this one setting. -- @param #number frequency (optional) Frequency in MHz. Can also be given as a #table of frequencies. Only needed if you want to override defaults set with `MESSAGE.SetMSRS()` for this one setting.
-- @param #number modulation (optional) Modulation, i.e. radio.modulation.AM or radio.modulation.FM. Can also be given as a #table of modulations. Only needed if you want to override defaults set with `MESSAGE.SetMSRS()` for this one setting. -- @param #number modulation (optional) Modulation, i.e. radio.modulation.AM or radio.modulation.FM. Can also be given as a #table of modulations. Only needed if you want to override defaults set with `MESSAGE.SetMSRS()` for this one setting.
@@ -543,7 +552,7 @@ function MESSAGE:ToSRS(frequency,modulation,gender,culture,voice,coalition,volum
_MESSAGESRS.MSRS:SetCoordinate(coordinate) _MESSAGESRS.MSRS:SetCoordinate(coordinate)
end end
local category = string.gsub(self.MessageCategory,":","") local category = string.gsub(self.MessageCategory,":","")
_MESSAGESRS.SRSQ:NewTransmission(self.MessageText,nil,_MESSAGESRS.MSRS,nil,nil,nil,nil,nil,frequency or _MESSAGESRS.frequency,modulation or _MESSAGESRS.modulation, gender or _MESSAGESRS.Gender,culture or _MESSAGESRS.Culture,nil,volume or _MESSAGESRS.volume,category,coordinate or _MESSAGESRS.coordinate) _MESSAGESRS.SRSQ:NewTransmission(self.MessageText,nil,_MESSAGESRS.MSRS,0.5,1,nil,nil,nil,frequency or _MESSAGESRS.frequency,modulation or _MESSAGESRS.modulation, gender or _MESSAGESRS.Gender,culture or _MESSAGESRS.Culture,nil,volume or _MESSAGESRS.volume,category,coordinate or _MESSAGESRS.coordinate)
end end
return self return self
end end

View File

@@ -1578,7 +1578,7 @@ do
function SET_GROUP:AddInDatabase( Event ) function SET_GROUP:AddInDatabase( Event )
self:F3( { Event } ) self:F3( { Event } )
if Event.IniObjectCategory == 1 then if Event.IniObjectCategory == Object.Category.UNIT then
if not self.Database[Event.IniDCSGroupName] then if not self.Database[Event.IniDCSGroupName] then
self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName ) self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName )
self:T3( self.Database[Event.IniDCSGroupName] ) self:T3( self.Database[Event.IniDCSGroupName] )
@@ -2641,7 +2641,7 @@ do -- SET_UNIT
function SET_UNIT:AddInDatabase( Event ) function SET_UNIT:AddInDatabase( Event )
self:F3( { Event } ) self:F3( { Event } )
if Event.IniObjectCategory == 1 then if Event.IniObjectCategory == Object.Category.UNIT then
if not self.Database[Event.IniDCSUnitName] then if not self.Database[Event.IniDCSUnitName] then
self.Database[Event.IniDCSUnitName] = UNIT:Register( Event.IniDCSUnitName ) self.Database[Event.IniDCSUnitName] = UNIT:Register( Event.IniDCSUnitName )
self:T3( self.Database[Event.IniDCSUnitName] ) self:T3( self.Database[Event.IniDCSUnitName] )
@@ -4518,7 +4518,7 @@ do -- SET_CLIENT
function SET_CLIENT:_EventPlayerEnterUnit(Event) function SET_CLIENT:_EventPlayerEnterUnit(Event)
self:I( "_EventPlayerEnterUnit" ) self:I( "_EventPlayerEnterUnit" )
if Event.IniDCSUnit then if Event.IniDCSUnit then
if Event.IniObjectCategory == 1 and Event.IniGroup and Event.IniGroup:IsGround() then if Event.IniObjectCategory == Object.Category.UNIT and Event.IniGroup and Event.IniGroup:IsGround() then
-- CA Slot entered -- CA Slot entered
local ObjectName, Object = self:AddInDatabase( Event ) local ObjectName, Object = self:AddInDatabase( Event )
self:I( ObjectName, UTILS.PrintTableToLog(Object) ) self:I( ObjectName, UTILS.PrintTableToLog(Object) )
@@ -4537,7 +4537,7 @@ do -- SET_CLIENT
function SET_CLIENT:_EventPlayerLeaveUnit(Event) function SET_CLIENT:_EventPlayerLeaveUnit(Event)
self:I( "_EventPlayerLeaveUnit" ) self:I( "_EventPlayerLeaveUnit" )
if Event.IniDCSUnit then if Event.IniDCSUnit then
if Event.IniObjectCategory == 1 and Event.IniGroup and Event.IniGroup:IsGround() then if Event.IniObjectCategory == Object.Category.UNIT and Event.IniGroup and Event.IniGroup:IsGround() then
-- CA Slot left -- CA Slot left
local ObjectName, Object = self:FindInDatabase( Event ) local ObjectName, Object = self:FindInDatabase( Event )
if ObjectName then if ObjectName then
@@ -7837,6 +7837,29 @@ do -- SET_OPSGROUP
return self return self
end end
--- Handles the OnBirth event for the Set.
-- @param #SET_OPSGROUP self
-- @param Core.Event#EVENTDATA Event Event data.
function SET_OPSGROUP:_EventOnBirth( Event )
self:F3( { Event } )
if Event.IniDCSUnit and Event.IniDCSGroup then
local DCSgroup=Event.IniDCSGroup --DCS#Group
if DCSgroup:getInitialSize() == DCSgroup:getSize() then -- This seems to be not a good check as even for the first birth event, getSize returns the total number of units in the group.
local groupname, group = self:AddInDatabase( Event )
if group and group:CountAliveUnits()==DCSgroup:getInitialSize() then
if group and self:IsIncludeObject( group ) then
self:Add( groupname, group )
end
end
end
end
end
--- Handles the OnDead or OnCrash event for alive groups set. --- Handles the OnDead or OnCrash event for alive groups set.
-- Note: The GROUP object in the SET_OPSGROUP collection will only be removed if the last unit is destroyed of the GROUP. -- Note: The GROUP object in the SET_OPSGROUP collection will only be removed if the last unit is destroyed of the GROUP.
-- @param #SET_OPSGROUP self -- @param #SET_OPSGROUP self
@@ -7857,12 +7880,12 @@ do -- SET_OPSGROUP
--- Handles the Database to check on an event (birth) that the Object was added in the Database. --- Handles the Database to check on an event (birth) that the Object was added in the Database.
-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
-- @param #SET_OPSGROUP self -- @param #SET_OPSGROUP self
-- @param Core.Event#EVENTDATA Event -- @param Core.Event#EVENTDATA Event Event data.
-- @return #string The name of the GROUP -- @return #string The name of the GROUP.
-- @return #table The GROUP -- @return Wrapper.Group#GROUP The GROUP object.
function SET_OPSGROUP:AddInDatabase( Event ) function SET_OPSGROUP:AddInDatabase( Event )
if Event.IniObjectCategory==1 then if Event.IniObjectCategory==Object.Category.UNIT then
if not self.Database[Event.IniDCSGroupName] then if not self.Database[Event.IniDCSGroupName] then
self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName ) self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName )
@@ -7877,8 +7900,8 @@ do -- SET_OPSGROUP
-- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa!
-- @param #SET_OPSGROUP self -- @param #SET_OPSGROUP self
-- @param Core.Event#EVENTDATA Event Event data table. -- @param Core.Event#EVENTDATA Event Event data table.
-- @return #string The name of the GROUP -- @return #string The name of the GROUP.
-- @return #table The GROUP -- @return Wrapper.Group#GROUP The GROUP object.
function SET_OPSGROUP:FindInDatabase(Event) function SET_OPSGROUP:FindInDatabase(Event)
return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName]
end end
@@ -8197,7 +8220,7 @@ do -- SET_SCENERY
end end
--- Get a table of alive objects. --- Get a table of alive objects.
-- @param #SET_GROUP self -- @param #SET_SCENERY self
-- @return #table Table of alive objects -- @return #table Table of alive objects
-- @return Core.Set#SET_SCENERY SET of alive objects -- @return Core.Set#SET_SCENERY SET of alive objects
function SET_SCENERY:GetAliveSet() function SET_SCENERY:GetAliveSet()

View File

@@ -1458,6 +1458,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
else else
local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate
local SpawnZone = self.SpawnGroups[self.SpawnIndex].SpawnZone
self:T( SpawnTemplate.name ) self:T( SpawnTemplate.name )
if SpawnTemplate then if SpawnTemplate then
@@ -1483,6 +1484,23 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
if self.SpawnRandomizeUnits then if self.SpawnRandomizeUnits then
for UnitID = 1, #SpawnTemplate.units do for UnitID = 1, #SpawnTemplate.units do
local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
if (SpawnZone) then
local inZone = SpawnZone:IsVec2InZone(RandomVec2)
local numTries = 1
while (not inZone) and (numTries < 20) do
if not inZone then
RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
numTries = numTries + 1
inZone = SpawnZone:IsVec2InZone(RandomVec2)
self:I("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!")
self:I(SpawnZone)
end
end
if (not inZone) then
self:I("Could not place unit within zone and within radius!")
RandomVec2 = SpawnZone:GetRandomVec2()
end
end
SpawnTemplate.units[UnitID].x = RandomVec2.x SpawnTemplate.units[UnitID].x = RandomVec2.x
SpawnTemplate.units[UnitID].y = RandomVec2.y SpawnTemplate.units[UnitID].y = RandomVec2.y
self:T( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) self:T( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y )
@@ -1534,12 +1552,14 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
for UnitID = 1, #SpawnTemplate.units do for UnitID = 1, #SpawnTemplate.units do
if UnitID > 1 then -- don't rotate position of unit #1 if not self.SpawnRandomizeUnits then
local unitXOff = SpawnTemplate.units[UnitID].x - pivotX -- rotate position offset around unit #1 if UnitID > 1 then -- don't rotate position of unit #1
local unitYOff = SpawnTemplate.units[UnitID].y - pivotY local unitXOff = SpawnTemplate.units[UnitID].x - pivotX -- rotate position offset around unit #1
local unitYOff = SpawnTemplate.units[UnitID].y - pivotY
SpawnTemplate.units[UnitID].x = pivotX + (unitXOff * cosHeading) - (unitYOff * sinHeading) SpawnTemplate.units[UnitID].x = pivotX + (unitXOff * cosHeading) - (unitYOff * sinHeading)
SpawnTemplate.units[UnitID].y = pivotY + (unitYOff * cosHeading) + (unitXOff * sinHeading) SpawnTemplate.units[UnitID].y = pivotY + (unitYOff * cosHeading) + (unitXOff * sinHeading)
end
end end
-- adjust heading of all units, including unit #1 -- adjust heading of all units, including unit #1
@@ -3591,6 +3611,7 @@ function SPAWN:_RandomizeZones( SpawnIndex )
self:T( { SpawnVec2 = SpawnVec2 } ) self:T( { SpawnVec2 = SpawnVec2 } )
local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate
self.SpawnGroups[SpawnIndex].SpawnZone = SpawnZone
self:T( { Route = SpawnTemplate.route } ) self:T( { Route = SpawnTemplate.route } )

View File

@@ -574,7 +574,7 @@ function AICSAR:SetSRSTTSRadio(OnOff,Path,Frequency,Modulation,Port,Voice,Cultur
self.SRSModulation = Modulation or radio.modulation.AM self.SRSModulation = Modulation or radio.modulation.AM
self.SRSPort = Port or 5002 self.SRSPort = Port or 5002
if OnOff then if OnOff then
self.SRS = MSRS:New(Path,Frequency,Modulation,1) self.SRS = MSRS:New(Path,Frequency,Modulation)
self.SRS:SetPort(self.SRSPort) self.SRS:SetPort(self.SRSPort)
self.SRS:SetCoalition(self.coalition) self.SRS:SetCoalition(self.coalition)
self.SRS:SetLabel("ACSR") self.SRS:SetLabel("ACSR")
@@ -600,7 +600,7 @@ end
function AICSAR:SetPilotTTSVoice(Voice,Culture,Gender) function AICSAR:SetPilotTTSVoice(Voice,Culture,Gender)
self:T(self.lid .. "SetPilotTTSVoice") self:T(self.lid .. "SetPilotTTSVoice")
self.SRSPilotVoice = true self.SRSPilotVoice = true
self.SRSPilot = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation,1) self.SRSPilot = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation)
self.SRSPilot:SetCoalition(self.coalition) self.SRSPilot:SetCoalition(self.coalition)
self.SRSPilot:SetVoice(Voice) self.SRSPilot:SetVoice(Voice)
self.SRSPilot:SetCulture(Culture or "en-US") self.SRSPilot:SetCulture(Culture or "en-US")
@@ -624,7 +624,7 @@ end
function AICSAR:SetOperatorTTSVoice(Voice,Culture,Gender) function AICSAR:SetOperatorTTSVoice(Voice,Culture,Gender)
self:T(self.lid .. "SetOperatorTTSVoice") self:T(self.lid .. "SetOperatorTTSVoice")
self.SRSOperatorVoice = true self.SRSOperatorVoice = true
self.SRSOperator = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation,1) self.SRSOperator = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation)
self.SRSOperator:SetCoalition(self.coalition) self.SRSOperator:SetCoalition(self.coalition)
self.SRSOperator:SetVoice(Voice) self.SRSOperator:SetVoice(Voice)
self.SRSOperator:SetCulture(Culture or "en-GB") self.SRSOperator:SetCulture(Culture or "en-GB")

View File

@@ -462,7 +462,7 @@ function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Cultu
self.Volume = Volume or 1.0 self.Volume = Volume or 1.0
self.Label = Label self.Label = Label
-- set up SRS -- set up SRS
self.SRS = MSRS:New(self.SRSPath,self.SRSFreq,self.SRSMod,self.Volume) self.SRS = MSRS:New(self.SRSPath,self.SRSFreq,self.SRSMod)
self.SRS:SetCoalition(self.coalition) self.SRS:SetCoalition(self.coalition)
self.SRS:SetLabel(self.MenuName or self.Name) self.SRS:SetLabel(self.MenuName or self.Name)
self.SRS:SetGender(self.Gender) self.SRS:SetGender(self.Gender)
@@ -470,6 +470,7 @@ function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Cultu
self.SRS:SetPort(self.Port) self.SRS:SetPort(self.Port)
self.SRS:SetVoice(self.Voice) self.SRS:SetVoice(self.Voice)
self.SRS:SetCoalition(self.coalition) self.SRS:SetCoalition(self.coalition)
self.SRS:SetVolume(Volume)
if self.PathToGoogleKey then if self.PathToGoogleKey then
self.SRS:SetGoogle(self.PathToGoogleKey) self.SRS:SetGoogle(self.PathToGoogleKey)
end end

View File

@@ -1211,16 +1211,18 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
self.useSRS=true self.useSRS=true
self.controlmsrs=MSRS:New(PathToSRS or MSRS.path, Frequency or 256, Modulation or radio.modulation.AM, Volume or 1.0) self.controlmsrs=MSRS:New(PathToSRS or MSRS.path, Frequency or 256, Modulation or radio.modulation.AM)
self.controlmsrs:SetPort(Port or MSRS.port) self.controlmsrs:SetPort(Port or MSRS.port)
self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE) self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE)
self.controlmsrs:SetLabel("RANGEC") self.controlmsrs:SetLabel("RANGEC")
self.controlmsrs:SetVolume(Volume or 1.0)
self.controlsrsQ = MSRSQUEUE:New("CONTROL") self.controlsrsQ = MSRSQUEUE:New("CONTROL")
self.instructmsrs=MSRS:New(PathToSRS or MSRS.path, Frequency or 305, Modulation or radio.modulation.AM, Volume or 1.0) self.instructmsrs=MSRS:New(PathToSRS or MSRS.path, Frequency or 305, Modulation or radio.modulation.AM)
self.instructmsrs:SetPort(Port or MSRS.port) self.instructmsrs:SetPort(Port or MSRS.port)
self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE) self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE)
self.instructmsrs:SetLabel("RANGEI") self.instructmsrs:SetLabel("RANGEI")
self.instructmsrs:SetVolume(Volume or 1.0)
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT") self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
if PathToGoogleKey then if PathToGoogleKey then

View File

@@ -276,9 +276,15 @@ function SCORING:New( GameName )
self:SetMessagesZone( true ) self:SetMessagesZone( true )
-- Scales -- Scales
self:SetScaleDestroyScore( 10 ) self:SetScaleDestroyScore( 10 )
self:SetScaleDestroyPenalty( 30 ) self:SetScaleDestroyPenalty( 30 )
-- Hitting a target multiple times before destoying it should not result in a higger score
-- Multiple hits is typically a results of bombs/missles missing their target but still inflict some spash damage
-- Making this configurable to anyone can enable this anyway if they want
self:SetScoreIncrementOnHit(0)
-- Default fratricide penalty level (maximum penalty that can be assigned to a player before he gets kicked). -- Default fratricide penalty level (maximum penalty that can be assigned to a player before he gets kicked).
self:SetFratricide( self.ScaleDestroyPenalty * 3 ) self:SetFratricide( self.ScaleDestroyPenalty * 3 )
self.penaltyonfratricide = true self.penaltyonfratricide = true
@@ -467,6 +473,16 @@ function SCORING:SetMessagesHit( OnOff )
return self return self
end end
--- Configure to increment score after a target has been hit.
-- @param #SCORING self
-- @param #number score amount of point to inclement score on each hit
-- @return #SCORING
function SCORING:SetScoreIncrementOnHit( score )
self.ScoreIncrementOnHit = score
return self
end
--- If to send messages after a target has been hit. --- If to send messages after a target has been hit.
-- @param #SCORING self -- @param #SCORING self
-- @return #boolean -- @return #boolean
@@ -885,6 +901,7 @@ function SCORING:OnEventBirth( Event )
Event.IniUnit.BirthTime = timer.getTime() Event.IniUnit.BirthTime = timer.getTime()
if PlayerName then if PlayerName then
self:_AddPlayerFromUnit( Event.IniUnit ) self:_AddPlayerFromUnit( Event.IniUnit )
self.Players[PlayerName].PlayerKills = 0
self:SetScoringMenu( Event.IniGroup ) self:SetScoringMenu( Event.IniGroup )
end end
end end
@@ -1015,7 +1032,7 @@ function SCORING:_EventOnHit( Event )
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth -- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth
if PlayerHit.UNIT.ThreatType == nil then if PlayerHit.UNIT.ThreatType == nil then
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
-- if this fails for some reason, set a good default value -- if this fails for some reason, set a good default value
if PlayerHit.ThreatType == nil then if PlayerHit.ThreatType == nil then
PlayerHit.ThreatLevel = 1 PlayerHit.ThreatLevel = 1
@@ -1060,10 +1077,8 @@ function SCORING:_EventOnHit( Event )
end end
self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else else
-- Hitting a target multiple times before destoying it should not result in a higger score Player.Score = Player.Score + self.ScoreIncrementOnHit
-- Multiple hits is typically a results of bombs/missles missing their target but still inflict some spash damage PlayerHit.Score = PlayerHit.Score + self.ScoreIncrementOnHit
-- Player.Score = Player.Score + 1
-- PlayerHit.Score = PlayerHit.Score + 1
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1 PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
if TargetPlayerName ~= nil then -- It is a player hitting another player ... if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
@@ -1128,7 +1143,7 @@ function SCORING:_EventOnHit( Event )
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth -- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth
if PlayerHit.UNIT.ThreatType == nil then if PlayerHit.UNIT.ThreatType == nil then
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
-- if this fails for some reason, set a good default value -- if this fails for some reason, set a good default value
if PlayerHit.ThreatType == nil then if PlayerHit.ThreatType == nil then
PlayerHit.ThreatLevel = 1 PlayerHit.ThreatLevel = 1
@@ -1163,10 +1178,8 @@ function SCORING:_EventOnHit( Event )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) :ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
self:ScoreCSV( Event.WeaponPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) self:ScoreCSV( Event.WeaponPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else else
-- Hitting a target multiple times before destoying it should not result in a higger score Player.Score = Player.Score + self.ScoreIncrementOnHit
-- Multiple hits is typically a results of bombs/missles missing their target but still inflict some spash damage PlayerHit.Score = PlayerHit.Score + self.ScoreIncrementOnHit
-- Player.Score = Player.Score + 1
-- PlayerHit.Score = PlayerHit.Score + 1
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1 PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
@@ -1274,13 +1287,18 @@ function SCORING:_EventOnDeadOrCrash( Event )
TargetDestroy.Penalty = TargetDestroy.Penalty + ThreatPenalty TargetDestroy.Penalty = TargetDestroy.Penalty + ThreatPenalty
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1 TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1
self:OnKillPvP(Player, TargetPlayerName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty)
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
self:OnKillPvP(Player, TargetPlayerName, true)
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty, "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else else
self:OnKillPvE(Player, TargetUnitName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty)
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty, "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
@@ -1303,12 +1321,19 @@ function SCORING:_EventOnDeadOrCrash( Event )
TargetDestroy.Score = TargetDestroy.Score + ThreatScore TargetDestroy.Score = TargetDestroy.Score + ThreatScore
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1 TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
if Player.PlayerKills ~= nil then
Player.PlayerKills = Player.PlayerKills + 1
else
Player.PlayerKills = 1
end
self:OnKillPvP(Player, TargetPlayerName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore)
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty, "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else else
self:OnKillPvE(Player, TargetUnitName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore)
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty, "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
@@ -1907,3 +1932,26 @@ function SCORING:SwitchAutoSave(OnOff)
self.AutoSave = OnOff self.AutoSave = OnOff
return self return self
end end
--- Handles the event when one player kill another player
-- @param #SCORING self
-- @param #PLAYER Player the ataching player
-- @param #string TargetPlayerName the name of the killed player
-- @param #bool IsTeamKill true if this kill was a team kill
-- @param #number TargetThreatLevel Thread level of the target
-- @param #number PlayerThreatLevelThread level of the player
-- @param #number Score The score based on both threat levels
function SCORING:OnKillPvP(Player, TargetPlayerName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
end
--- Handles the event when one player kill another player
-- @param #SCORING self
-- @param #PLAYER Player the ataching player
-- @param #string TargetUnitName the name of the killed unit
-- @param #bool IsTeamKill true if this kill was a team kill
-- @param #number TargetThreatLevel Thread level of the target
-- @param #number PlayerThreatLevelThread level of the player
-- @param #number Score The score based on both threat levels
function SCORING:OnKillPvE(Player, TargetUnitName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
end

View File

@@ -42,6 +42,7 @@
-- @field #number CaptureUnits -- @field #number CaptureUnits
-- @field #number CaptureThreatlevel -- @field #number CaptureThreatlevel
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
-- @extends Core.Fsm#FSM
--- *If you see what is right and fail to act on it, you lack courage* --- Confucius --- *If you see what is right and fail to act on it, you lack courage* --- Confucius
@@ -175,7 +176,7 @@ STRATEGO = {
debug = false, debug = false,
drawzone = false, drawzone = false,
markzone = false, markzone = false,
version = "0.2.1", version = "0.2.3",
portweight = 3, portweight = 3,
POIweight = 1, POIweight = 1,
maxrunways = 3, maxrunways = 3,
@@ -244,8 +245,8 @@ STRATEGO.Type = {
-- @param #number MaxDist Maximum distance of a single route in kilometers, defaults to 150. -- @param #number MaxDist Maximum distance of a single route in kilometers, defaults to 150.
-- @return #STRATEGO self -- @return #STRATEGO self
function STRATEGO:New(Name,Coalition,MaxDist) function STRATEGO:New(Name,Coalition,MaxDist)
-- Inherit everything from BASE class. -- Inherit everything from FSM class.
local self = BASE:Inherit(self, BASE:New()) -- #STRATEGO local self = BASE:Inherit(self, FSM:New()) -- #STRATEGO
self.coalition = Coalition self.coalition = Coalition
self.coalitiontext = UTILS.GetCoalitionName(Coalition) self.coalitiontext = UTILS.GetCoalitionName(Coalition)
@@ -268,13 +269,55 @@ function STRATEGO:New(Name,Coalition,MaxDist)
[4] = {1,0.65,0}, -- orange [4] = {1,0.65,0}, -- orange
} }
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Running") -- Start FSM.
self:AddTransition("*", "Update", "*") -- Start FSM.
self:AddTransition("*", "NodeEvent", "*") -- Start FSM.
self:AddTransition("Running", "Stop", "Stopped") -- Start FSM.
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Start". Starts the STRATEGO. Initializes parameters and starts event handlers.
-- @function [parent=#STRATEGO] Start
-- @param #STRATEGO self
--- Triggers the FSM event "Start" after a delay. Starts the STRATEGO. Initializes parameters and starts event handlers.
-- @function [parent=#STRATEGO] __Start
-- @param #STRATEGO self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop". Stops the STRATEGO and all its event handlers.
-- @function [parent=#STRATEGO] Stop
-- @param #STRATEGO self
--- Triggers the FSM event "Stop" after a delay. Stops the STRATEGO and all its event handlers.
-- @function [parent=#STRATEGO] __Stop
-- @param #STRATEGO self
-- @param #number delay Delay in seconds.
--- FSM Function OnAfterNodeEvent. A node changed coalition.
-- @function [parent=#STRATEGO] OnAfterNodeEvent
-- @param #STRATEGO self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Ops.OpsZone#OPSZONE OpsZone The OpsZone triggering the event.
-- @param #number Coalition The coalition of the new owner.
-- @return #STRATEGO self
return self return self
end end
--- [USER] Do initial setup and get ready. --- [INTERNAL] FSM function for initial setup and getting ready.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @return #STRATEGO self -- @return #STRATEGO self
function STRATEGO:Start() function STRATEGO:onafterStart(From,Event,To)
self:T(self.lid.."Start") self:T(self.lid.."Start")
self:AnalyseBases() self:AnalyseBases()
self:AnalysePOIs(self.ports,self.portweight,"PORT") self:AnalysePOIs(self.ports,self.portweight,"PORT")
@@ -282,12 +325,27 @@ function STRATEGO:Start()
for i=self.maxrunways,1,-1 do for i=self.maxrunways,1,-1 do
self:AnalyseRoutes(i,i*self.routefactor,self.colors[(i%3)+1],i) self:AnalyseRoutes(i,i*self.routefactor,self.colors[(i%3)+1],i)
--self:AnalyseRoutes(2,2*self.routefactor,self.colors[2],2)
--self:AnalyseRoutes(1,1*self.routefactor,self.colors[3],3)
end end
self:AnalyseUnconnected(self.colors[4]) self:AnalyseUnconnected(self.colors[4])
self:I(self.lid.."Advisory ready.") self:I(self.lid.."Advisory ready.")
self:__Update(180)
return self
end
--- [INTERNAL] Update knot association
-- @param #STRATEGO self
-- @return #STRATEGO self
function STRATEGO:onafterUpdate(From,Event,To)
self:T(self.lid.."Update")
self:UpdateNodeCoalitions()
if self:GetState() == "Running" then
self:__Update(180)
end
return self return self
end end
@@ -308,6 +366,7 @@ end
-- @param #boolean DrawZones If true, draw the OpsZones on the F10 map. -- @param #boolean DrawZones If true, draw the OpsZones on the F10 map.
-- @param #boolean MarkZones if true, mark the OpsZones on the F10 map (with further information). -- @param #boolean MarkZones if true, mark the OpsZones on the F10 map (with further information).
function STRATEGO:SetDebug(Debug,DrawZones,MarkZones) function STRATEGO:SetDebug(Debug,DrawZones,MarkZones)
self:T(self.lid.."SetDebug")
self.debug = Debug self.debug = Debug
self.drawzone = DrawZones self.drawzone = DrawZones
self.markzone = MarkZones self.markzone = MarkZones
@@ -322,6 +381,7 @@ end
-- @param #number RouteFactor Defines which weight each route between two defined nodes gets: Weight * RouteFactor. -- @param #number RouteFactor Defines which weight each route between two defined nodes gets: Weight * RouteFactor.
-- @return #STRATEGO self -- @return #STRATEGO self
function STRATEGO:SetWeights(MaxRunways,PortWeight,POIWeight,RouteFactor) function STRATEGO:SetWeights(MaxRunways,PortWeight,POIWeight,RouteFactor)
self:T(self.lid.."SetWeights")
self.portweight = PortWeight or 3 self.portweight = PortWeight or 3
self.POIweight = POIWeight or 1 self.POIweight = POIWeight or 1
self.maxrunways = MaxRunways or 3 self.maxrunways = MaxRunways or 3
@@ -334,6 +394,7 @@ end
-- @param #number NeutralBenefit Pointsm defaults to 100. -- @param #number NeutralBenefit Pointsm defaults to 100.
-- @return #STRATEGO self -- @return #STRATEGO self
function STRATEGO:SetNeutralBenefit(NeutralBenefit) function STRATEGO:SetNeutralBenefit(NeutralBenefit)
self:T(self.lid.."SetNeutralBenefit")
self.NeutralBenefit = NeutralBenefit or 100 self.NeutralBenefit = NeutralBenefit or 100
return self return self
end end
@@ -344,6 +405,7 @@ end
-- @param #number CaptureThreatlevel Threat level needed, can be 0..10, defaults to one. -- @param #number CaptureThreatlevel Threat level needed, can be 0..10, defaults to one.
-- @return #STRATEGO self -- @return #STRATEGO self
function STRATEGO:SetCaptureOptions(CaptureUnits,CaptureThreatlevel) function STRATEGO:SetCaptureOptions(CaptureUnits,CaptureThreatlevel)
self:T(self.lid.."SetCaptureOptions")
self.CaptureUnits = CaptureUnits or 3 self.CaptureUnits = CaptureUnits or 3
self.CaptureThreatlevel = CaptureThreatlevel or 1 self.CaptureThreatlevel = CaptureThreatlevel or 1
return self return self
@@ -369,6 +431,9 @@ function STRATEGO:AnalyseBases()
local numrwys = #runways local numrwys = #runways
if numrwys >= 1 then numrwys = numrwys * 0.5 end if numrwys >= 1 then numrwys = numrwys * 0.5 end
local abzone = ab:GetZone() local abzone = ab:GetZone()
if not abzone then
abzone = ZONE_RADIUS:New(abname,ab:GetVec2(),500)
end
local coa = ab:GetCoalition() + 1 local coa = ab:GetCoalition() + 1
local abtype = "AIRBASE" local abtype = "AIRBASE"
if ab:IsShip() then if ab:IsShip() then
@@ -379,7 +444,7 @@ function STRATEGO:AnalyseBases()
numrwys = 1 numrwys = 1
abtype = "FARP" abtype = "FARP"
end end
local coord = abzone:GetCoordinate() local coord = ab:GetCoordinate()
if debug then if debug then
abzone:DrawZone(-1,colors[coa],1,colors[coa],0.3,1) abzone:DrawZone(-1,colors[coa],1,colors[coa],0.3,1)
coord:TextToAll(tostring(numrwys),-1,{0,0,0},1,colors[coa],0.3,20) coord:TextToAll(tostring(numrwys),-1,{0,0,0},1,colors[coa],0.3,20)
@@ -394,6 +459,7 @@ function STRATEGO:AnalyseBases()
zone = abzone, zone = abzone,
coord = coord, coord = coord,
type = abtype, type = abtype,
opszone = opszone,
} }
airbasetable[abname] = tbl airbasetable[abname] = tbl
nonconnectedab[abname] = true nonconnectedab[abname] = true
@@ -437,6 +503,15 @@ function STRATEGO:GetNewOpsZone(Zone,Coalition)
opszone:SetDrawZone(self.drawzone) opszone:SetDrawZone(self.drawzone)
opszone:SetMarkZone(self.markzone) opszone:SetMarkZone(self.markzone)
opszone:Start() opszone:Start()
local function Captured(opszone,coalition)
self:__NodeEvent(1,opszone,coalition)
end
function opszone:OnBeforeCaptured(From,Event,To,Coalition)
Captured(opszone,Coalition)
end
return opszone return opszone
end end
@@ -494,13 +569,14 @@ end
--- [USER] Manually add a route, for e.g. Island hopping or to connect isolated networks. Use **after** STRATEGO has been started! --- [USER] Manually add a route, for e.g. Island hopping or to connect isolated networks. Use **after** STRATEGO has been started!
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Startpoint -- @param #string Startpoint Starting Point, e.g. AIRBASE.Syria.Hatay
-- @param #string Endpoint -- @param #string Endpoint End Point, e.g. AIRBASE.Syria.H4
-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1,0,0} for red. Defaults to lila. -- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1,0,0} for red. Defaults to lila.
-- @param #number LineType (Optional) Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 5. -- @param #number Linetype (Optional) Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 5.
-- @param #boolean Draw (Optional) If true, draw route on the F10 map. Defaukt false. -- @param #boolean Draw (Optional) If true, draw route on the F10 map. Defaukt false.
-- @return #STRATEGO self -- @return #STRATEGO self
function STRATEGO:AddRoutesManually(Startpoint,Endpoint,Color,Linetype,Draw) function STRATEGO:AddRoutesManually(Startpoint,Endpoint,Color,Linetype,Draw)
self:T(self.lid.."AddRoutesManually")
local fromto,tofrom = self:GetToFrom(Startpoint,Endpoint) local fromto,tofrom = self:GetToFrom(Startpoint,Endpoint)
local startcoordinate = self.airbasetable[Startpoint].coord local startcoordinate = self.airbasetable[Startpoint].coord
local targetcoordinate = self.airbasetable[Endpoint].coord local targetcoordinate = self.airbasetable[Endpoint].coord
@@ -630,7 +706,7 @@ end
-- @return #table Table of nodes. -- @return #table Table of nodes.
-- @return #number Weight The consolidated weight associated with the nodes. -- @return #number Weight The consolidated weight associated with the nodes.
function STRATEGO:GetHighestWeightNodes() function STRATEGO:GetHighestWeightNodes()
self:T(self.lid.."GetHighestWeightBases") self:T(self.lid.."GetHighestWeightNodes")
local weight = 0 local weight = 0
local airbases = {} local airbases = {}
for _name,_data in pairs(self.airbasetable) do for _name,_data in pairs(self.airbasetable) do
@@ -648,7 +724,7 @@ end
-- @return #table Table of nodes. -- @return #table Table of nodes.
-- @return #number Weight The consolidated weight associated with the nodes. -- @return #number Weight The consolidated weight associated with the nodes.
function STRATEGO:GetNextHighestWeightNodes(Weight) function STRATEGO:GetNextHighestWeightNodes(Weight)
self:T(self.lid.."GetNextHighestWeightBases") self:T(self.lid.."GetNextHighestWeightNodes")
local weight = 0 local weight = 0
local airbases = {} local airbases = {}
for _name,_data in pairs(self.airbasetable) do for _name,_data in pairs(self.airbasetable) do
@@ -666,6 +742,7 @@ end
-- @param #string Name. -- @param #string Name.
-- @return #number Weight The weight or 0 if not found. -- @return #number Weight The weight or 0 if not found.
function STRATEGO:GetNodeWeight(Name) function STRATEGO:GetNodeWeight(Name)
self:T(self.lid.."GetNodeWeight")
if Name and self.airbasetable[Name] then if Name and self.airbasetable[Name] then
return self.airbasetable[Name].weight or 0 return self.airbasetable[Name].weight or 0
else else
@@ -678,6 +755,7 @@ end
-- @param #string Name. -- @param #string Name.
-- @return #number Weight The base weight or 0 if not found. -- @return #number Weight The base weight or 0 if not found.
function STRATEGO:GetNodeBaseWeight(Name) function STRATEGO:GetNodeBaseWeight(Name)
self:T(self.lid.."GetNodeBaseWeight")
if Name and self.airbasetable[Name] then if Name and self.airbasetable[Name] then
return self.airbasetable[Name].baseweight or 0 return self.airbasetable[Name].baseweight or 0
else else
@@ -690,6 +768,7 @@ end
-- @param #string Name. -- @param #string Name.
-- @return #number Coalition The coalition. -- @return #number Coalition The coalition.
function STRATEGO:GetNodeCoalition(Name) function STRATEGO:GetNodeCoalition(Name)
self:T(self.lid.."GetNodeCoalition")
if Name and self.airbasetable[Name] then if Name and self.airbasetable[Name] then
return self.airbasetable[Name].coalition or coalition.side.NEUTRAL return self.airbasetable[Name].coalition or coalition.side.NEUTRAL
else else
@@ -702,6 +781,7 @@ end
-- @param #string Name. -- @param #string Name.
-- @return #string Type Type of the node, e.g. STRATEGO.Type.AIRBASE or nil if not found. -- @return #string Type Type of the node, e.g. STRATEGO.Type.AIRBASE or nil if not found.
function STRATEGO:GetNodeType(Name) function STRATEGO:GetNodeType(Name)
self:T(self.lid.."GetNodeType")
if Name and self.airbasetable[Name] then if Name and self.airbasetable[Name] then
return self.airbasetable[Name].type return self.airbasetable[Name].type
else else
@@ -714,6 +794,7 @@ end
-- @param #string Name. -- @param #string Name.
-- @return Core.Zone#ZONE Zone The Zone of the node or nil if not found. -- @return Core.Zone#ZONE Zone The Zone of the node or nil if not found.
function STRATEGO:GetNodeZone(Name) function STRATEGO:GetNodeZone(Name)
self:T(self.lid.."GetNodeZone")
if Name and self.airbasetable[Name] then if Name and self.airbasetable[Name] then
return self.airbasetable[Name].zone return self.airbasetable[Name].zone
else else
@@ -726,6 +807,7 @@ end
-- @param #string Name. -- @param #string Name.
-- @return Ops.OpsZone#OPSZONE OpsZone The OpsZone of the node or nil if not found. -- @return Ops.OpsZone#OPSZONE OpsZone The OpsZone of the node or nil if not found.
function STRATEGO:GetNodeOpsZone(Name) function STRATEGO:GetNodeOpsZone(Name)
self:T(self.lid.."GetNodeOpsZone")
if Name and self.airbasetable[Name] then if Name and self.airbasetable[Name] then
return self.airbasetable[Name].opszone return self.airbasetable[Name].opszone
else else
@@ -738,6 +820,7 @@ end
-- @param #string Name. -- @param #string Name.
-- @return Core.Point#COORDINATE Coordinate The Coordinate of the node or nil if not found. -- @return Core.Point#COORDINATE Coordinate The Coordinate of the node or nil if not found.
function STRATEGO:GetNodeCoordinate(Name) function STRATEGO:GetNodeCoordinate(Name)
self:T(self.lid.."GetNodeCoordinate")
if Name and self.airbasetable[Name] then if Name and self.airbasetable[Name] then
return self.airbasetable[Name].coord return self.airbasetable[Name].coord
else else
@@ -750,6 +833,7 @@ end
-- @param #string Name. -- @param #string Name.
-- @return #boolean Outcome -- @return #boolean Outcome
function STRATEGO:IsAirbase(Name) function STRATEGO:IsAirbase(Name)
self:T(self.lid.."IsAirbase")
if Name and self.airbasetable[Name] then if Name and self.airbasetable[Name] then
return self.airbasetable[Name].type == STRATEGO.Type.AIRBASE return self.airbasetable[Name].type == STRATEGO.Type.AIRBASE
else else
@@ -762,6 +846,7 @@ end
-- @param #string Name. -- @param #string Name.
-- @return #boolean Outcome -- @return #boolean Outcome
function STRATEGO:IsPort(Name) function STRATEGO:IsPort(Name)
self:T(self.lid.."IsPort")
if Name and self.airbasetable[Name] then if Name and self.airbasetable[Name] then
return self.airbasetable[Name].type == STRATEGO.Type.PORT return self.airbasetable[Name].type == STRATEGO.Type.PORT
else else
@@ -774,6 +859,7 @@ end
-- @param #string Name. -- @param #string Name.
-- @return #boolean Outcome -- @return #boolean Outcome
function STRATEGO:IsPOI(Name) function STRATEGO:IsPOI(Name)
self:T(self.lid.."IsPOI")
if Name and self.airbasetable[Name] then if Name and self.airbasetable[Name] then
return self.airbasetable[Name].type == STRATEGO.Type.POI return self.airbasetable[Name].type == STRATEGO.Type.POI
else else
@@ -786,6 +872,7 @@ end
-- @param #string Name. -- @param #string Name.
-- @return #boolean Outcome -- @return #boolean Outcome
function STRATEGO:IsFARP(Name) function STRATEGO:IsFARP(Name)
self:T(self.lid.."IsFARP")
if Name and self.airbasetable[Name] then if Name and self.airbasetable[Name] then
return self.airbasetable[Name].type == STRATEGO.Type.FARP return self.airbasetable[Name].type == STRATEGO.Type.FARP
else else
@@ -798,6 +885,7 @@ end
-- @param #string Name. -- @param #string Name.
-- @return #boolean Outcome -- @return #boolean Outcome
function STRATEGO:IsShip(Name) function STRATEGO:IsShip(Name)
self:T(self.lid.."IsShip")
if Name and self.airbasetable[Name] then if Name and self.airbasetable[Name] then
return self.airbasetable[Name].type == STRATEGO.Type.SHIP return self.airbasetable[Name].type == STRATEGO.Type.SHIP
else else
@@ -880,6 +968,7 @@ end
-- @param #STRATEGO self -- @param #STRATEGO self
-- @return #table of #STRATEGO.Target data points -- @return #table of #STRATEGO.Target data points
function STRATEGO:FindStrategicTargets() function STRATEGO:FindStrategicTargets()
self:T(self.lid.."FindStrategicTargets")
local targets = {} local targets = {}
for _,_data in pairs(self.airbasetable) do for _,_data in pairs(self.airbasetable) do
local data = _data -- #STRATEGO.Data local data = _data -- #STRATEGO.Data
@@ -923,6 +1012,7 @@ end
-- @param #STRATEGO self -- @param #STRATEGO self
-- @return #table of #STRATEGO.Target data points -- @return #table of #STRATEGO.Target data points
function STRATEGO:FindConsolidationTargets() function STRATEGO:FindConsolidationTargets()
self:T(self.lid.."FindConsolidationTargets")
local targets = {} local targets = {}
for _,_data in pairs(self.airbasetable) do for _,_data in pairs(self.airbasetable) do
local data = _data -- #STRATEGO.Data local data = _data -- #STRATEGO.Data
@@ -971,6 +1061,7 @@ end
-- @param #boolean Friends (optional) If true, find only friendly or neutral neighbors. -- @param #boolean Friends (optional) If true, find only friendly or neutral neighbors.
-- @return #table Neighbors Table of #STRATEGO.DistData entries indexed by neighbor node names. -- @return #table Neighbors Table of #STRATEGO.DistData entries indexed by neighbor node names.
function STRATEGO:FindNeighborNodes(Name,Enemies,Friends) function STRATEGO:FindNeighborNodes(Name,Enemies,Friends)
self:T(self.lid.."FindNeighborNodes")
local neighbors = {} local neighbors = {}
local name = string.gsub(Name,"[%p%s]",".") local name = string.gsub(Name,"[%p%s]",".")
for _route,_data in pairs(self.disttable) do for _route,_data in pairs(self.disttable) do
@@ -1002,10 +1093,13 @@ end
-- @param #string End The name of the end node. -- @param #string End The name of the end node.
-- @param #number Hops Max iterations to find a route. -- @param #number Hops Max iterations to find a route.
-- @param #boolean Draw If true, draw the route on the map. -- @param #boolean Draw If true, draw the route on the map.
-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1,0,0} for red. Defaults to black.
-- @param #number LineType (Optional) Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 6.
-- @return #table Route Table of #string name entries of the route -- @return #table Route Table of #string name entries of the route
-- @return #boolean Complete If true, the route was found end-to-end. -- @return #boolean Complete If true, the route was found end-to-end.
function STRATEGO:FindRoute(Start,End,Hops,Draw) function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
self:I({Start,End,Hops}) self:T(self.lid.."FindRoute")
--self:I({Start,End,Hops})
--local bases = UTILS.DeepCopy(self.airbasetable) --local bases = UTILS.DeepCopy(self.airbasetable)
local Route = {} local Route = {}
local hops = Hops or 4 local hops = Hops or 4
@@ -1046,7 +1140,9 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw)
local p2=Route[i+1] local p2=Route[i+1]
local c1 = self.airbasetable[p1].coord -- Core.Point#COORDINATE local c1 = self.airbasetable[p1].coord -- Core.Point#COORDINATE
local c2 = self.airbasetable[p2].coord -- Core.Point#COORDINATE local c2 = self.airbasetable[p2].coord -- Core.Point#COORDINATE
c1:LineToAll(c2,-1,{0,0,0},1,6) local line = LineType or 6
local color = Color or {0,0,0}
c1:LineToAll(c2,-1,color,1,line)
end end
end end
@@ -1076,7 +1172,7 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw)
end end
end end
end end
if self.debug or Draw then DrawRoute(Route) end if (self.debug or Draw) then DrawRoute(Route) end
return Route, routecomplete return Route, routecomplete
end end
@@ -1085,6 +1181,7 @@ end
-- @param #number Number of points to add. -- @param #number Number of points to add.
-- @return #STRATEGO self -- @return #STRATEGO self
function STRATEGO:AddBudget(Number) function STRATEGO:AddBudget(Number)
self:T(self.lid.."AddBudget")
self.Budget = self.Budget + Number self.Budget = self.Budget + Number
return self return self
end end
@@ -1094,6 +1191,7 @@ end
-- @param #number Number of points to subtract. -- @param #number Number of points to subtract.
-- @return #STRATEGO self -- @return #STRATEGO self
function STRATEGO:SubtractBudget(Number) function STRATEGO:SubtractBudget(Number)
self:T(self.lid.."SubtractBudget")
self.Budget = self.Budget - Number self.Budget = self.Budget - Number
return self return self
end end
@@ -1102,6 +1200,7 @@ end
-- @param #STRATEGO self -- @param #STRATEGO self
-- @return #number budget -- @return #number budget
function STRATEGO:GetBudget() function STRATEGO:GetBudget()
self:T(self.lid.."GetBudget")
return self.Budget return self.Budget
end end
@@ -1109,6 +1208,7 @@ end
-- @param #STRATEGO self -- @param #STRATEGO self
-- @return #table Target Table with #STRATEGO.Target data or nil if none found. -- @return #table Target Table with #STRATEGO.Target data or nil if none found.
function STRATEGO:FindAffordableStrategicTarget() function STRATEGO:FindAffordableStrategicTarget()
self:T(self.lid.."FindAffordableStrategicTarget")
local targets = self:FindStrategicTargets() -- #table of #STRATEGO.Target local targets = self:FindStrategicTargets() -- #table of #STRATEGO.Target
local budget = self.Budget local budget = self.Budget
--local leftover = self.Budget --local leftover = self.Budget
@@ -1140,6 +1240,7 @@ end
-- @param #STRATEGO self -- @param #STRATEGO self
-- @return #table Target Table with #STRATEGO.Target data or nil if none found. -- @return #table Target Table with #STRATEGO.Target data or nil if none found.
function STRATEGO:FindAffordableConsolidationTarget() function STRATEGO:FindAffordableConsolidationTarget()
self:T(self.lid.."FindAffordableConsolidationTarget")
local targets = self:FindConsolidationTargets() -- #table of #STRATEGO.Target local targets = self:FindConsolidationTargets() -- #table of #STRATEGO.Target
local budget = self.Budget local budget = self.Budget
--local leftover = self.Budget --local leftover = self.Budget

View File

@@ -1526,7 +1526,7 @@ function ATIS:MarkRunways( markall )
end end
end end
--- Use SRS Simple-Text-To-Speech for transmissions. No sound files necessary. --- Use SRS Simple-Text-To-Speech for transmissions. No sound files necessary.`SetSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible.
-- @param #ATIS self -- @param #ATIS self
-- @param #string PathToSRS Path to SRS directory (only necessary if SRS exe backend is used). -- @param #string PathToSRS Path to SRS directory (only necessary if SRS exe backend is used).
-- @param #string Gender Gender: "male" or "female" (default). -- @param #string Gender Gender: "male" or "female" (default).
@@ -1536,24 +1536,52 @@ end
-- @param #string GoogleKey Path to Google JSON-Key (SRS exe backend) or Google API key (DCS-gRPC backend). -- @param #string GoogleKey Path to Google JSON-Key (SRS exe backend) or Google API key (DCS-gRPC backend).
-- @return #ATIS self -- @return #ATIS self
function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port, GoogleKey) function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port, GoogleKey)
if PathToSRS or MSRS.path then --if PathToSRS or MSRS.path then
self.useSRS=true self.useSRS=true
self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation)
self.msrs:SetGender(Gender) local path = PathToSRS or MSRS.path
self.msrs:SetCulture(Culture) local gender = Gender or MSRS.gender
self.msrs:SetVoice(Voice) local culture = Culture or MSRS.culture
self.msrs:SetPort(Port) local voice = Voice or MSRS.voice
local port = Port or MSRS.port or 5002
self.msrs=MSRS:New(path, self.frequency, self.modulation)
self.msrs:SetGender(gender)
self.msrs:SetCulture(culture)
self.msrs:SetPort(port)
self.msrs:SetCoalition(self:GetCoalition()) self.msrs:SetCoalition(self:GetCoalition())
self.msrs:SetLabel("ATIS") self.msrs:SetLabel("ATIS")
self.msrs:SetGoogle(GoogleKey) if GoogleKey then
self.msrs:SetProviderOptionsGoogle(GoogleKey,GoogleKey)
self.msrs:SetProvider(MSRS.Provider.GOOGLE)
end
-- Pre-configured Google?
if (not GoogleKey) and self.msrs:GetProvider() == MSRS.Provider.GOOGLE then
voice = Voice or MSRS.poptions.gcloud.voice
end
self.msrs:SetVoice(voice)
self.msrs:SetCoordinate(self.airbase:GetCoordinate()) self.msrs:SetCoordinate(self.airbase:GetCoordinate())
self.msrsQ = MSRSQUEUE:New("ATIS") self.msrsQ = MSRSQUEUE:New("ATIS")
self.msrsQ:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers) self.msrsQ:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers)
if self.dTQueueCheck<=10 then if self.dTQueueCheck<=10 then
self:SetQueueUpdateTime(90) self:SetQueueUpdateTime(90)
end end
--else
--self:E(self.lid..string.format("ERROR: No SRS path specified!"))
--end
return self
end
--- Set an alternative provider to the one set in your MSRS configuration file.
-- @param #ATIS self
-- @param #string Provider The provider to use. Known providers are: `MSRS.Provider.WINDOWS` and `MSRS.Provider.GOOGLE`
-- @return #ATIS self
function ATIS:SetSRSProvider(Provider)
self:T(self.lid.."SetSRSProvider")
if self.msrs then
self.msrs:SetProvider(Provider)
else else
self:E(self.lid..string.format("ERROR: No SRS path specified!")) MESSAGE:New(self.lid.."Set up SRS first before trying to change the provider!",30,"ATIS"):ToAll():ToLog()
end end
return self return self
end end

View File

@@ -3072,6 +3072,7 @@ function AIRBOSS:EnableSRS(PathToSRS,Port,Culture,Gender,Voice,GoogleCreds,Volum
self.SRS:SetPort(Port or 5002) self.SRS:SetPort(Port or 5002)
self.SRS:SetLabel(self.AirbossRadio.alias or "AIRBOSS") self.SRS:SetLabel(self.AirbossRadio.alias or "AIRBOSS")
self.SRS:SetCoordinate(self.carrier:GetCoordinate()) self.SRS:SetCoordinate(self.carrier:GetCoordinate())
self.SRS:SetVolume(Volume)
--self.SRS:SetModulations(Modulations) --self.SRS:SetModulations(Modulations)
if GoogleCreds then if GoogleCreds then
self.SRS:SetGoogle(GoogleCreds) self.SRS:SetGoogle(GoogleCreds)

View File

@@ -17,7 +17,7 @@
-- === -- ===
-- --
-- ### Author: **applevangelist** -- ### Author: **applevangelist**
-- @date Last Update Nov 2023 -- @date Last Update Jan 2024
-- @module Ops.AWACS -- @module Ops.AWACS
-- @image OPS_AWACS.jpg -- @image OPS_AWACS.jpg
@@ -507,7 +507,7 @@ do
-- @field #AWACS -- @field #AWACS
AWACS = { AWACS = {
ClassName = "AWACS", -- #string ClassName = "AWACS", -- #string
version = "0.2.59", -- #string version = "0.2.61", -- #string
lid = "", -- #string lid = "", -- #string
coalition = coalition.side.BLUE, -- #number coalition = coalition.side.BLUE, -- #number
coalitiontxt = "blue", -- #string coalitiontxt = "blue", -- #string
@@ -1405,15 +1405,18 @@ function AWACS:SetTacticalRadios(BaseFreq,Increase,Modulation,Interval,Number)
self.TacticalFrequencies[freq] = freq self.TacticalFrequencies[freq] = freq
end end
if self.AwacsSRS then if self.AwacsSRS then
self.TacticalSRS = MSRS:New(self.PathToSRS,self.TacticalBaseFreq,self.TacticalModulation,self.Volume) self.TacticalSRS = MSRS:New(self.PathToSRS,self.TacticalBaseFreq,self.TacticalModulation)
self.TacticalSRS:SetCoalition(self.coalition) self.TacticalSRS:SetCoalition(self.coalition)
self.TacticalSRS:SetGender(self.Gender) self.TacticalSRS:SetGender(self.Gender)
self.TacticalSRS:SetCulture(self.Culture) self.TacticalSRS:SetCulture(self.Culture)
self.TacticalSRS:SetVoice(self.Voice) self.TacticalSRS:SetVoice(self.Voice)
self.TacticalSRS:SetPort(self.Port) self.TacticalSRS:SetPort(self.Port)
self.TacticalSRS:SetLabel("AWACS") self.TacticalSRS:SetLabel("AWACS")
self.TacticalSRS:SetVolume(self.Volume)
if self.PathToGoogleKey then if self.PathToGoogleKey then
self.TacticalSRS:SetGoogle(self.PathToGoogleKey) --self.TacticalSRS:SetGoogle(self.PathToGoogleKey)
self.TacticalSRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey)
self.TacticalSRS:SetProvider(MSRS.Provider.GOOGLE)
end end
self.TacticalSRSQ = MSRSQUEUE:New("Tactical AWACS") self.TacticalSRSQ = MSRSQUEUE:New("Tactical AWACS")
end end
@@ -2069,7 +2072,7 @@ function AWACS:AddGroupToDetection(Group)
return self return self
end end
--- [User] Set AWACS SRS TTS details - see @{Sound.SRS} for details --- [User] Set AWACS SRS TTS details - see @{Sound.SRS} for details. `SetSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible.
-- @param #AWACS self -- @param #AWACS self
-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone"
-- @param #string Gender Defaults to "male" -- @param #string Gender Defaults to "male"
@@ -2078,29 +2081,39 @@ end
-- @param #string Voice (Optional) Use a specifc voice with the @{Sound.SRS#SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. -- @param #string Voice (Optional) Use a specifc voice with the @{Sound.SRS#SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`.
-- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS. -- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS.
-- @param #number Volume Volume - between 0.0 (silent) and 1.0 (loudest) -- @param #number Volume Volume - between 0.0 (silent) and 1.0 (loudest)
-- @param #string PathToGoogleKey Path to your google key if you want to use google TTS -- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS; if you use a config file for MSRS, hand in nil here.
-- @param #string AccessKey (Optional) Your Google API access key. This is necessary if DCS-gRPC is used as backend; if you use a config file for MSRS, hand in nil here.
-- @return #AWACS self -- @return #AWACS self
function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey) function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey)
self:T(self.lid.."SetSRS") self:T(self.lid.."SetSRS")
self.PathToSRS = PathToSRS or "C:\\Program Files\\DCS-SimpleRadio-Standalone" self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.Gender = Gender or "male" self.Gender = Gender or MSRS.gender or "male"
self.Culture = Culture or "en-US" self.Culture = Culture or MSRS.culture or "en-US"
self.Port = Port or 5002 self.Port = Port or MSRS.port or 5002
self.Voice = Voice self.Voice = Voice or MSRS.voice
self.PathToGoogleKey = PathToGoogleKey self.PathToGoogleKey = PathToGoogleKey
self.AccessKey = AccessKey
self.Volume = Volume or 1.0 self.Volume = Volume or 1.0
self.AwacsSRS = MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation,self.Volume) self.AwacsSRS = MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation)
self.AwacsSRS:SetCoalition(self.coalition) self.AwacsSRS:SetCoalition(self.coalition)
self.AwacsSRS:SetGender(self.Gender) self.AwacsSRS:SetGender(self.Gender)
self.AwacsSRS:SetCulture(self.Culture) self.AwacsSRS:SetCulture(self.Culture)
self.AwacsSRS:SetVoice(self.Voice)
self.AwacsSRS:SetPort(self.Port) self.AwacsSRS:SetPort(self.Port)
self.AwacsSRS:SetLabel("AWACS") self.AwacsSRS:SetLabel("AWACS")
self.AwacsSRS:SetVolume(Volume)
if self.PathToGoogleKey then if self.PathToGoogleKey then
self.AwacsSRS:SetGoogle(self.PathToGoogleKey) --self.AwacsSRS:SetGoogle(self.PathToGoogleKey)
self.AwacsSRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey)
self.AwacsSRS:SetProvider(MSRS.Provider.GOOGLE)
end end
-- Pre-configured Google?
if (not PathToGoogleKey) and self.AwacsSRS:GetProvider() == MSRS.Provider.GOOGLE then
self.PathToGoogleKey = MSRS.poptions.gcloud.credentials
self.Voice = Voice or MSRS.poptions.gcloud.voice
self.AccessKey = AccessKey or MSRS.poptions.gcloud.key
end
self.AwacsSRS:SetVoice(self.Voice)
return self return self
end end
@@ -2935,7 +2948,7 @@ function AWACS:_Picture(Group,IsGeneral)
if not self.intel then if not self.intel then
-- no intel yet! -- no intel yet!
local picclean = self.gettext:GetEntry("PICCLEAN",self.locale) local picclean = self.gettext:GetEntry("PICCLEAN",self.locale)
text = string.format(picclean,self.callsigntxt, gcallsign) text = string.format(picclean,gcallsign,self.callsigntxt)
textScreen = text textScreen = text
self:_NewRadioEntry(text,text,GID,false,true,true,false) self:_NewRadioEntry(text,text,GID,false,true,true,false)
@@ -3663,7 +3676,7 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr)
CAPVoice = self.CapVoices[math.floor(math.random(1,10))] CAPVoice = self.CapVoices[math.floor(math.random(1,10))]
end end
FlightGroup:SetSRS(self.PathToSRS,self.CAPGender,self.CAPCulture,CAPVoice,self.Port,self.PathToGoogleKey,"FLIGHT") FlightGroup:SetSRS(self.PathToSRS,self.CAPGender,self.CAPCulture,CAPVoice,self.Port,self.PathToGoogleKey,"FLIGHT",1)
local checkai = self.gettext:GetEntry("CHECKINAI",self.locale) local checkai = self.gettext:GetEntry("CHECKINAI",self.locale)
text = string.format(checkai,self.callsigntxt, managedgroup.CallSign, self.CAPTimeOnStation, self.AOName) text = string.format(checkai,self.callsigntxt, managedgroup.CallSign, self.CAPTimeOnStation, self.AOName)
@@ -6595,7 +6608,7 @@ function AWACS:onafterCheckTacticalQueue(From,Event,To)
if self.PathToGoogleKey then if self.PathToGoogleKey then
gtext = string.format("<speak><prosody rate='medium'>%s</prosody></speak>",gtext) gtext = string.format("<speak><prosody rate='medium'>%s</prosody></speak>",gtext)
end end
self.TacticalSRSQ:NewTransmission(gtext,nil,self.TacticalSRS,nil,0.5,nil,nil,nil,frequency,self.TacticalModulation,nil,nil,nil,nil,nil) self.TacticalSRSQ:NewTransmission(gtext,nil,self.TacticalSRS,nil,0.5,nil,nil,nil,frequency,self.TacticalModulation)
self:T(RadioEntry.TextTTS) self:T(RadioEntry.TextTTS)

View File

@@ -453,9 +453,12 @@ function FLIGHTCONTROL:New(AirbaseName, Frequency, Modulation, PathToSRS, Port,
self.msrsqueue=MSRSQUEUE:New(self.alias) self.msrsqueue=MSRSQUEUE:New(self.alias)
-- SRS for Tower. -- SRS for Tower.
self.msrsTower=MSRS:New(PathToSRS, Frequency, Modulation) self.msrsTower=MSRS:New(path, Frequency, Modulation)
self.msrsTower:SetPort(self.Port) self.msrsTower:SetPort(port)
self.msrsTower:SetGoogle(GoogleKey) if GoogleKey then
self.msrsTower:SetProviderOptionsGoogle(GoogleKey,GoogleKey)
self.msrsTower:SetProvider(MSRS.Provider.GOOGLE)
end
self.msrsTower:SetCoordinate(self:GetCoordinate()) self.msrsTower:SetCoordinate(self:GetCoordinate())
if GoogleKey then if GoogleKey then
self.msrsTower:SetGoogle(GoogleKey) self.msrsTower:SetGoogle(GoogleKey)
@@ -465,7 +468,10 @@ function FLIGHTCONTROL:New(AirbaseName, Frequency, Modulation, PathToSRS, Port,
-- SRS for Pilot. -- SRS for Pilot.
self.msrsPilot=MSRS:New(PathToSRS, Frequency, Modulation) self.msrsPilot=MSRS:New(PathToSRS, Frequency, Modulation)
self.msrsPilot:SetPort(self.Port) self.msrsPilot:SetPort(self.Port)
self.msrsPilot:SetGoogle(GoogleKey) if GoogleKey then
self.msrsPilot:SetProviderOptionsGoogle(GoogleKey,GoogleKey)
self.msrsPilot:SetProvider(MSRS.Provider.GOOGLE)
end
self.msrsTower:SetCoordinate(self:GetCoordinate()) self.msrsTower:SetCoordinate(self:GetCoordinate())
if GoogleKey then if GoogleKey then
self.msrsPilot:SetGoogle(GoogleKey) self.msrsPilot:SetGoogle(GoogleKey)
@@ -688,12 +694,11 @@ end
-- @param #string Voice Specific voice. Overrides `Gender` and `Culture`. See [Google Voices](https://cloud.google.com/text-to-speech/docs/voices). -- @param #string Voice Specific voice. Overrides `Gender` and `Culture`. See [Google Voices](https://cloud.google.com/text-to-speech/docs/voices).
-- @param #number Volume Volume. Default 1.0. -- @param #number Volume Volume. Default 1.0.
-- @param #string Label Name under which SRS transmits. Default `self.alias`. -- @param #string Label Name under which SRS transmits. Default `self.alias`.
-- @param #string PathToGoogleCredentials Path to google credentials json file.
-- @return #FLIGHTCONTROL self -- @return #FLIGHTCONTROL self
function FLIGHTCONTROL:SetSRSTower(Gender, Culture, Voice, Volume, Label, PathToGoogleCredentials) function FLIGHTCONTROL:SetSRSTower(Gender, Culture, Voice, Volume, Label)
if self.msrsTower then if self.msrsTower then
self:_SetSRSOptions(self.msrsTower, Gender or "female", Culture or "en-GB", Voice, Volume, Label or self.alias, PathToGoogleCredentials) self:_SetSRSOptions(self.msrsTower, Gender or "female", Culture or "en-GB", Voice, Volume, Label or self.alias)
end end
return self return self
@@ -706,12 +711,11 @@ end
-- @param #string Voice Specific voice. Overrides `Gender` and `Culture`. -- @param #string Voice Specific voice. Overrides `Gender` and `Culture`.
-- @param #number Volume Volume. Default 1.0. -- @param #number Volume Volume. Default 1.0.
-- @param #string Label Name under which SRS transmits. Default "Pilot". -- @param #string Label Name under which SRS transmits. Default "Pilot".
-- @param #string PathToGoogleCredentials Path to google credentials json file.
-- @return #FLIGHTCONTROL self -- @return #FLIGHTCONTROL self
function FLIGHTCONTROL:SetSRSPilot(Gender, Culture, Voice, Volume, Label, PathToGoogleCredentials) function FLIGHTCONTROL:SetSRSPilot(Gender, Culture, Voice, Volume, Label)
if self.msrsPilot then if self.msrsPilot then
self:_SetSRSOptions(self.msrsPilot, Gender or "male", Culture or "en-US", Voice, Volume, Label or "Pilot", PathToGoogleCredentials) self:_SetSRSOptions(self.msrsPilot, Gender or "male", Culture or "en-US", Voice, Volume, Label or "Pilot")
end end
return self return self
@@ -949,7 +953,7 @@ end
--- Set ATIS. --- Set ATIS.
-- @param #FLIGHTCONTROL self -- @param #FLIGHTCONTROL self
-- @param Ops.ATIS#ATIS ATIS ATIS. -- @param Ops.ATIS#ATIS Atis ATIS.
-- @return #FLIGHTCONTROL self -- @return #FLIGHTCONTROL self
function FLIGHTCONTROL:SetATIS(Atis) function FLIGHTCONTROL:SetATIS(Atis)
self.atis=Atis self.atis=Atis

View File

@@ -3257,11 +3257,20 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand)
local TaskFinal = self.group:TaskFunction("FLIGHTGROUP._OnFinal", self) local TaskFinal = self.group:TaskFunction("FLIGHTGROUP._OnFinal", self)
-- Final approach waypoint. -- Final approach waypoint.
local papp=airbase:GetCoordinate():Translate(x1, runway.heading-180):SetAltitude(h1) local rheading
if runway then
rheading = runway.heading-180
else
-- AB HeloBase w/o runway eg Naqoura
local wind = airbase:GetCoordinate():GetWind()
rheading = -wind
end
local papp=airbase:GetCoordinate():Translate(x1, rheading):SetAltitude(h1)
wp[#wp+1]=papp:WaypointAirTurningPoint("BARO", UTILS.KnotsToKmph(SpeedLand), {TaskFinal}, "Final Approach") wp[#wp+1]=papp:WaypointAirTurningPoint("BARO", UTILS.KnotsToKmph(SpeedLand), {TaskFinal}, "Final Approach")
-- Okay, it looks like it's best to specify the coordinates not at the airbase but a bit away. This causes a more direct landing approach. -- Okay, it looks like it's best to specify the coordinates not at the airbase but a bit away. This causes a more direct landing approach.
local pland=airbase:GetCoordinate():Translate(x2, runway.heading-180):SetAltitude(h2) local pland=airbase:GetCoordinate():Translate(x2, rheading):SetAltitude(h2)
wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), airbase, {}, "Landing") wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), airbase, {}, "Landing")
elseif airbase:IsShip() or airbase:IsHelipad() then elseif airbase:IsShip() or airbase:IsHelipad() then

View File

@@ -2265,7 +2265,7 @@ function LEGION:RecruitAssetsForMission(Mission)
end end
end end
self:T(self.lid..string.format("Largest cargo bay available=%.1f", MaxWeight)) self:T(self.lid..string.format("Largest cargo bay available=%.1f", MaxWeight or 0))
end end
@@ -2745,7 +2745,7 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt,
end end
-- Now we have a long list with assets. -- Now we have a long list with assets.
LEGION._OptimizeAssetSelection(Assets, MissionTypeOpt, TargetVec2, false) LEGION._OptimizeAssetSelection(Assets, MissionTypeOpt, TargetVec2, false, TotalWeight)
-- Get payloads for air assets. -- Get payloads for air assets.
@@ -2770,7 +2770,7 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt,
end end
-- Now find the best asset for the given payloads. -- Now find the best asset for the given payloads.
LEGION._OptimizeAssetSelection(Assets, MissionTypeOpt, TargetVec2, true) LEGION._OptimizeAssetSelection(Assets, MissionTypeOpt, TargetVec2, true, TotalWeight)
-- Number of assets. At most NreqMax. -- Number of assets. At most NreqMax.
local Nassets=math.min(#Assets, NreqMax) local Nassets=math.min(#Assets, NreqMax)
@@ -3114,8 +3114,9 @@ end
-- @param #string MissionType Mission type for which the best assets are desired. -- @param #string MissionType Mission type for which the best assets are desired.
-- @param DCS#Vec2 TargetVec2 Target 2D vector. -- @param DCS#Vec2 TargetVec2 Target 2D vector.
-- @param #boolean IncludePayload If `true`, include the payload in the calulation if the asset has one attached. -- @param #boolean IncludePayload If `true`, include the payload in the calulation if the asset has one attached.
-- @param #number TotalWeight The total weight of the cargo to be transported, if applicable.
-- @return #number Mission score. -- @return #number Mission score.
function LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, IncludePayload) function LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, IncludePayload, TotalWeight)
-- Mission score. -- Mission score.
local score=0 local score=0
@@ -3209,8 +3210,18 @@ function LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, Inclu
-- TRANSPORT specific. -- TRANSPORT specific.
if MissionType==AUFTRAG.Type.OPSTRANSPORT then if MissionType==AUFTRAG.Type.OPSTRANSPORT then
-- Add 1 score point for each 10 kg of cargo bay. if TotalWeight then
score=score+UTILS.Round(asset.cargobaymax/10, 0) -- Add 1 score point for each 10 kg of cargo bay capacity up to the total cargo weight,
-- and then subtract 1 score point for each excess 10kg of cargo bay capacity.
if asset.cargobaymax < TotalWeight then
score=score+UTILS.Round(asset.cargobaymax/10, 0)
else
score=score+UTILS.Round(TotalWeight/10, 0)
end
else
-- Add 1 score point for each 10 kg of cargo bay.
score=score+UTILS.Round(asset.cargobaymax/10, 0)
end
end end
-- TODO: This could be vastly improved. Need to gather ideas during testing. -- TODO: This could be vastly improved. Need to gather ideas during testing.
@@ -3231,14 +3242,15 @@ end
-- @param #string MissionType Mission type. -- @param #string MissionType Mission type.
-- @param DCS#Vec2 TargetVec2 Target position as 2D vector. -- @param DCS#Vec2 TargetVec2 Target position as 2D vector.
-- @param #boolean IncludePayload If `true`, include the payload in the calulation if the asset has one attached. -- @param #boolean IncludePayload If `true`, include the payload in the calulation if the asset has one attached.
function LEGION._OptimizeAssetSelection(assets, MissionType, TargetVec2, IncludePayload) -- @param #number TotalWeight The total weight of the cargo to be transported, if applicable.
function LEGION._OptimizeAssetSelection(assets, MissionType, TargetVec2, IncludePayload, TotalWeight)
-- Calculate the mission score of all assets. -- Calculate the mission score of all assets.
for _,_asset in pairs(assets) do for _,_asset in pairs(assets) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
-- Calculate the asset score. -- Calculate the asset score.
asset.score=LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, IncludePayload) asset.score=LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, IncludePayload, TotalWeight)
if IncludePayload then if IncludePayload then

View File

@@ -2322,14 +2322,17 @@ end
-- @return #OPSGROUP self -- @return #OPSGROUP self
function OPSGROUP:SetSRS(PathToSRS, Gender, Culture, Voice, Port, PathToGoogleKey, Label, Volume) function OPSGROUP:SetSRS(PathToSRS, Gender, Culture, Voice, Port, PathToGoogleKey, Label, Volume)
self.useSRS=true self.useSRS=true
self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation) local path = PathToSRS or MSRS.path
local port = Port or MSRS.port
self.msrs=MSRS:New(path, self.frequency, self.modulation)
self.msrs:SetGender(Gender) self.msrs:SetGender(Gender)
self.msrs:SetCulture(Culture) self.msrs:SetCulture(Culture)
self.msrs:SetVoice(Voice) self.msrs:SetVoice(Voice)
self.msrs:SetPort(Port) self.msrs:SetPort(port)
self.msrs:SetLabel(Label) self.msrs:SetLabel(Label)
if PathToGoogleKey then if PathToGoogleKey then
self.msrs:SetGoogle(PathToGoogleKey) self.msrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey)
self.msrs:SetProvider(MSRS.Provider.GOOGLE)
end end
self.msrs:SetCoalition(self:GetCoalition()) self.msrs:SetCoalition(self:GetCoalition())
self.msrs:SetVolume(Volume) self.msrs:SetVolume(Volume)

View File

@@ -605,6 +605,16 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, TransportZoneCombo, DisembarkActi
self:AddCargoGroups(group, TransportZoneCombo, DisembarkActivation) self:AddCargoGroups(group, TransportZoneCombo, DisembarkActivation)
end end
-- Use FSM function to keep the SET up-to-date. Note that it overwrites the user FMS function, which cannot be used any more now.
local groupset=GroupSet --Core.Set#SET_OPSGROUP
function groupset.OnAfterAdded(groupset, From, Event, To, ObjectName, Object)
self:T(self.lid..string.format("Adding Cargo Group %s", tostring(ObjectName)))
self:AddCargoGroups(Object, TransportZoneCombo, DisembarkActivation, DisembarkZone, DisembarkCarriers)
end
end end
-- Debug info. -- Debug info.

View File

@@ -1508,15 +1508,22 @@ function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,V
self.Modulation = Modulation or {radio.modulation.FM,radio.modulation.AM} -- self.Modulation = Modulation or {radio.modulation.FM,radio.modulation.AM} --
self.BCModulation = self.Modulation self.BCModulation = self.Modulation
-- set up SRS -- set up SRS
self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation,self.Volume) self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation)
self.SRS:SetCoalition(self.Coalition) self.SRS:SetCoalition(self.Coalition)
self.SRS:SetLabel(self.MenuName or self.Name) self.SRS:SetLabel(self.MenuName or self.Name)
self.SRS:SetGender(self.Gender) self.SRS:SetGender(self.Gender)
self.SRS:SetCulture(self.Culture) self.SRS:SetCulture(self.Culture)
self.SRS:SetPort(self.Port) self.SRS:SetPort(self.Port)
self.SRS:SetVoice(self.Voice) self.SRS:SetVoice(self.Voice)
self.SRS:SetVolume(self.Volume)
if self.PathToGoogleKey then if self.PathToGoogleKey then
self.SRS:SetGoogle(self.PathToGoogleKey) self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey)
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
end
-- Pre-configured Google?
if (not PathToGoogleKey) and self.AwacsSRS:GetProvider() == MSRS.Provider.GOOGLE then
self.PathToGoogleKey = MSRS.poptions.gcloud.credentials
self.Voice = Voice or MSRS.poptions.gcloud.voice
end end
self.SRSQueue = MSRSQUEUE:New(self.MenuName or self.Name) self.SRSQueue = MSRSQUEUE:New(self.MenuName or self.Name)
self.SRSQueue:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers) self.SRSQueue:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers)

View File

@@ -21,7 +21,7 @@
-- === -- ===
-- @module Ops.PlayerTask -- @module Ops.PlayerTask
-- @image OPS_PlayerTask.jpg -- @image OPS_PlayerTask.jpg
-- @date Last Update Oct 2023 -- @date Last Update Jan 2024
do do
@@ -1552,7 +1552,7 @@ PLAYERTASKCONTROLLER.Messages = {
--- PLAYERTASK class version. --- PLAYERTASK class version.
-- @field #string version -- @field #string version
PLAYERTASKCONTROLLER.version="0.1.63" PLAYERTASKCONTROLLER.version="0.1.64"
--- Create and run a new TASKCONTROLLER instance. --- Create and run a new TASKCONTROLLER instance.
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
@@ -3993,7 +3993,7 @@ function PLAYERTASKCONTROLLER:SetupIntel(RecceName)
return self return self
end end
--- [User] Set SRS TTS details - see @{Sound.SRS} for details --- [User] Set SRS TTS details - see @{Sound.SRS} for details.`SetSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible.
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
-- @param #number Frequency Frequency to be used. Can also be given as a table of multiple frequencies, e.g. 271 or {127,251}. There needs to be exactly the same number of modulations! -- @param #number Frequency Frequency to be used. Can also be given as a table of multiple frequencies, e.g. 271 or {127,251}. There needs to be exactly the same number of modulations!
-- @param #number Modulation Modulation to be used. Can also be given as a table of multiple modulations, e.g. radio.modulation.AM or {radio.modulation.FM,radio.modulation.AM}. There needs to be exactly the same number of frequencies! -- @param #number Modulation Modulation to be used. Can also be given as a table of multiple modulations, e.g. radio.modulation.AM or {radio.modulation.FM,radio.modulation.AM}. There needs to be exactly the same number of frequencies!
@@ -4004,17 +4004,19 @@ end
-- @param #string Voice (Optional) Use a specifc voice with the @{Sound.SRS#SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. -- @param #string Voice (Optional) Use a specifc voice with the @{Sound.SRS#SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`.
-- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS. -- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS.
-- @param #number Volume (Optional) Volume - between 0.0 (silent) and 1.0 (loudest) -- @param #number Volume (Optional) Volume - between 0.0 (silent) and 1.0 (loudest)
-- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS -- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS; if you use a config file for MSRS, hand in nil here.
-- @param #string AccessKey (Optional) Your Google API access key. This is necessary if DCS-gRPC is used as backend; if you use a config file for MSRS, hand in nil here.
-- @param Core.Point#COORDINATE Coordinate Coordinate from which the controller radio is sending -- @param Core.Point#COORDINATE Coordinate Coordinate from which the controller radio is sending
-- @return #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Coordinate) function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate)
self:T(self.lid.."SetSRS") self:T(self.lid.."SetSRS")
self.PathToSRS = PathToSRS or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" --
self.Gender = Gender or "male" -- self.Gender = Gender or MSRS.gender or "male" --
self.Culture = Culture or "en-US" -- self.Culture = Culture or MSRS.culture or "en-US" --
self.Port = Port or 5002 -- self.Port = Port or MSRS.port or 5002 --
self.Voice = Voice -- self.Voice = Voice or MSRS.voice
self.PathToGoogleKey = PathToGoogleKey -- self.PathToGoogleKey = PathToGoogleKey --
self.AccessKey = AccessKey
self.Volume = Volume or 1.0 -- self.Volume = Volume or 1.0 --
self.UseSRS = true self.UseSRS = true
self.Frequency = Frequency or {127,251} -- self.Frequency = Frequency or {127,251} --
@@ -4022,19 +4024,28 @@ function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Cultu
self.Modulation = Modulation or {radio.modulation.FM,radio.modulation.AM} -- self.Modulation = Modulation or {radio.modulation.FM,radio.modulation.AM} --
self.BCModulation = self.Modulation self.BCModulation = self.Modulation
-- set up SRS -- set up SRS
self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation,self.Volume) self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation)
self.SRS:SetCoalition(self.Coalition) self.SRS:SetCoalition(self.Coalition)
self.SRS:SetLabel(self.MenuName or self.Name) self.SRS:SetLabel(self.MenuName or self.Name)
self.SRS:SetGender(self.Gender) self.SRS:SetGender(self.Gender)
self.SRS:SetCulture(self.Culture) self.SRS:SetCulture(self.Culture)
self.SRS:SetPort(self.Port) self.SRS:SetPort(self.Port)
self.SRS:SetVoice(self.Voice) self.SRS:SetVolume(self.Volume)
if self.PathToGoogleKey then if self.PathToGoogleKey then
self.SRS:SetGoogle(self.PathToGoogleKey) --self.SRS:SetGoogle(self.PathToGoogleKey)
self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey)
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
end
-- Pre-configured Google?
if (not PathToGoogleKey) and self.SRS:GetProvider() == MSRS.Provider.GOOGLE then
self.PathToGoogleKey = MSRS.poptions.gcloud.credentials
self.Voice = Voice or MSRS.poptions.gcloud.voice
self.AccessKey = AccessKey or MSRS.poptions.gcloud.key
end end
if Coordinate then if Coordinate then
self.SRS:SetCoordinate(Coordinate) self.SRS:SetCoordinate(Coordinate)
end end
self.SRS:SetVoice(self.Voice)
self.SRSQueue = MSRSQUEUE:New(self.MenuName or self.Name) self.SRSQueue = MSRSQUEUE:New(self.MenuName or self.Name)
self.SRSQueue:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers) self.SRSQueue:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers)
return self return self

View File

@@ -183,8 +183,10 @@ end
-- @param #number Port SRS port. Default 5002. -- @param #number Port SRS port. Default 5002.
-- @return #RADIOQUEUE self The RADIOQUEUE object. -- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:SetSRS(PathToSRS, Port) function RADIOQUEUE:SetSRS(PathToSRS, Port)
self.msrs=MSRS:New(PathToSRS, self.frequency/1000000, self.modulation) local path = PathToSRS or MSRS.path
self.msrs:SetPort(Port) local port = Port or MSRS.port
self.msrs=MSRS:New(path, self.frequency/1000000, self.modulation)
self.msrs:SetPort(port)
return self return self
end end

View File

@@ -272,6 +272,13 @@ MSRS.Voices = {
["Zira"] = "Microsoft Zira Desktop", -- en-US ["Zira"] = "Microsoft Zira Desktop", -- en-US
["Hortense"] = "Microsoft Hortense Desktop", --fr-FR ["Hortense"] = "Microsoft Hortense Desktop", --fr-FR
}, },
MicrosoftGRPC = {
["Hedda"] = "Hedda", -- de-DE
["Hazel"] = "Hazel", -- en-GB
["David"] = "David", -- en-US
["Zira"] = "Zira", -- en-US
["Hortense"] = "Hortense", --fr-FR
},
Google = { Google = {
Standard = { Standard = {
["en_AU_Standard_A"] = 'en-AU-Standard-A', -- [1] FEMALE ["en_AU_Standard_A"] = 'en-AU-Standard-A', -- [1] FEMALE
@@ -516,8 +523,20 @@ end
-- @return #MSRS self -- @return #MSRS self
function MSRS:SetBackend(Backend) function MSRS:SetBackend(Backend)
self:F( {Backend=Backend} ) self:F( {Backend=Backend} )
self.backend=Backend or MSRS.Backend.SRSEXE Backend = Backend or MSRS.Backend.SRSEXE -- avoid nil
local function Checker(back)
local ok = false
for _,_backend in pairs(MSRS.Backend) do
if tostring(back) == _backend then ok = true end
end
return ok
end
if Checker(Backend) then
self.backend=Backend
else
MESSAGE:New("ERROR: Backend "..tostring(Backend).." is not supported!",30,"MSRS",true):ToLog():ToAll()
end
return self return self
end end
@@ -906,12 +925,16 @@ end
-- @param #string Provider -- @param #string Provider
-- @return #MSRS self -- @return #MSRS self
function MSRS:SetProvider(Provider) function MSRS:SetProvider(Provider)
self:F( {Provider=Provider} ) BASE:F( {Provider=Provider} )
self.provider = Provider or MSRS.Provider.WINDOWS if self then
return self self.provider = Provider or MSRS.Provider.WINDOWS
return self
else
MSRS.provider = Provider or MSRS.Provider.WINDOWS
end
return
end end
--- Get provider. --- Get provider.
-- @param #MSRS self -- @param #MSRS self
-- @return #MSRS self -- @return #MSRS self
@@ -928,7 +951,7 @@ end
-- @param #string Region Region to use. -- @param #string Region Region to use.
-- @return #MSRS.ProviderOptions Provider optionas table. -- @return #MSRS.ProviderOptions Provider optionas table.
function MSRS:SetProviderOptions(Provider, CredentialsFile, AccessKey, SecretKey, Region) function MSRS:SetProviderOptions(Provider, CredentialsFile, AccessKey, SecretKey, Region)
self:F( {Provider, CredentialsFile, AccessKey, SecretKey, Region} ) BASE:F( {Provider, CredentialsFile, AccessKey, SecretKey, Region} )
local option=MSRS._CreateProviderOptions(Provider, CredentialsFile, AccessKey, SecretKey, Region) local option=MSRS._CreateProviderOptions(Provider, CredentialsFile, AccessKey, SecretKey, Region)
if self then if self then
@@ -1184,7 +1207,7 @@ end
-- @param Core.Point#COORDINATE Coordinate Coordinate. -- @param Core.Point#COORDINATE Coordinate Coordinate.
-- @return #MSRS self -- @return #MSRS self
function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate) function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate)
self:F( {Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate} ) self:T({Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate} )
if Delay and Delay>0 then if Delay and Delay>0 then
self:ScheduleOnce(Delay, MSRS.PlayTextExt, self, Text, 0, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate) self:ScheduleOnce(Delay, MSRS.PlayTextExt, self, Text, 0, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate)
@@ -1205,6 +1228,7 @@ function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture
self:_ExecCommand(command) self:_ExecCommand(command)
elseif self.backend==MSRS.Backend.GRPC then elseif self.backend==MSRS.Backend.GRPC then
--BASE:I("MSRS.Backend.GRPC")
self:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate) self:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate)
@@ -1593,7 +1617,7 @@ end
-- --
-- -- Moose MSRS default Config -- -- Moose MSRS default Config
-- MSRS_Config = { -- MSRS_Config = {
-- Path = C:\\Program Files\\DCS-SimpleRadio-Standalone, -- Path to SRS install directory. -- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone", -- Path to SRS install directory.
-- Port = 5002, -- Port of SRS server. Default 5002. -- Port = 5002, -- Port of SRS server. Default 5002.
-- Backend = "srsexe", -- Interface to SRS: "srsexe" or "grpc". -- Backend = "srsexe", -- Interface to SRS: "srsexe" or "grpc".
-- Frequency = {127, 243}, -- Default frequences. Must be a table 1..n entries! -- Frequency = {127, 243}, -- Default frequences. Must be a table 1..n entries!
@@ -1606,7 +1630,6 @@ end
-- Voice = "Microsoft Hazel Desktop", -- Voice that is used if no explicit provider voice is specified. -- Voice = "Microsoft Hazel Desktop", -- Voice that is used if no explicit provider voice is specified.
-- Label = "MSRS", -- Label = "MSRS",
-- Provider = "win", --Provider for generating TTS (win, gcloud, azure, aws). -- Provider = "win", --Provider for generating TTS (win, gcloud, azure, aws).
--
-- -- Windows -- -- Windows
-- win = { -- win = {
-- voice = "Microsoft Hazel Desktop", -- voice = "Microsoft Hazel Desktop",
@@ -1632,7 +1655,7 @@ end
-- }, -- },
-- } -- }
-- --
-- 3) The config file is automatically loaded when Moose starts. YOu can also load the config into the MSRS raw class manually before you do anything else: -- 3) The config file is automatically loaded when Moose starts. You can also load the config into the MSRS raw class manually before you do anything else:
-- --
-- MSRS.LoadConfigFile() -- Note the "." here -- MSRS.LoadConfigFile() -- Note the "." here
-- --
@@ -1648,8 +1671,7 @@ end
-- 4) Use the config in your code like so, variable names are basically the same as in the config file, but all lower case, examples: -- 4) Use the config in your code like so, variable names are basically the same as in the config file, but all lower case, examples:
-- --
-- -- Needed once only -- -- Needed once only
-- MESSAGE.SetMSRS(MSRS.path,nil,MSRS.google,243,radio.modulation.AM,nil,nil, -- MESSAGE.SetMSRS(MSRS.path,MSRS.port,nil,127,rado.modulation.FM,nil,nil,nil,nil,nil,"TALK")
-- MSRS.Voices.Google.Standard.de_DE_Standard_B,coalition.side.BLUE)
-- --
-- -- later on in your code -- -- later on in your code
-- --
@@ -1831,7 +1853,7 @@ end
-- @param #MSRSQUEUE self -- @param #MSRSQUEUE self
-- @return #MSRSQUEUE self The MSRSQUEUE object. -- @return #MSRSQUEUE self The MSRSQUEUE object.
function MSRSQUEUE:Clear() function MSRSQUEUE:Clear()
self:I(self.lid.."Clearing MSRSQUEUE") self:T(self.lid.."Clearing MSRSQUEUE")
self.queue={} self.queue={}
return self return self
end end
@@ -1842,7 +1864,6 @@ end
-- @param #MSRSQUEUE.Transmission transmission The transmission data table. -- @param #MSRSQUEUE.Transmission transmission The transmission data table.
-- @return #MSRSQUEUE self -- @return #MSRSQUEUE self
function MSRSQUEUE:AddTransmission(transmission) function MSRSQUEUE:AddTransmission(transmission)
-- Init. -- Init.
transmission.isplaying=false transmission.isplaying=false
transmission.Tstarted=nil transmission.Tstarted=nil
@@ -1921,20 +1942,20 @@ function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgr
transmission.Tplay=tstart or timer.getAbsTime() transmission.Tplay=tstart or timer.getAbsTime()
transmission.subtitle=subtitle transmission.subtitle=subtitle
transmission.interval=interval or 0 transmission.interval=interval or 0
transmission.frequency=frequency transmission.frequency=frequency or msrs.frequencies
transmission.modulation=modulation transmission.modulation=modulation or msrs.modulations
transmission.subgroups=subgroups transmission.subgroups=subgroups
if transmission.subtitle then if transmission.subtitle then
transmission.subduration=subduration or transmission.duration transmission.subduration=subduration or transmission.duration
else else
transmission.subduration=0 --nil transmission.subduration=0 --nil
end end
transmission.gender = gender transmission.gender = gender or msrs.gender
transmission.culture = culture transmission.culture = culture or msrs.culture
transmission.voice = voice transmission.voice = voice or msrs.voice
transmission.volume = volume transmission.volume = volume or msrs.volume
transmission.label = label transmission.label = label or msrs.Label
transmission.coordinate = coordinate transmission.coordinate = coordinate or msrs.coordinate
-- Add transmission to queue. -- Add transmission to queue.
self:AddTransmission(transmission) self:AddTransmission(transmission)
@@ -1946,6 +1967,7 @@ end
-- @param #MSRSQUEUE self -- @param #MSRSQUEUE self
-- @param #MSRSQUEUE.Transmission transmission The transmission. -- @param #MSRSQUEUE.Transmission transmission The transmission.
function MSRSQUEUE:Broadcast(transmission) function MSRSQUEUE:Broadcast(transmission)
self:T(self.lid.."Broadcast")
if transmission.frequency then if transmission.frequency then
transmission.msrs:PlayTextExt(transmission.text, nil, transmission.frequency, transmission.modulation, transmission.gender, transmission.culture, transmission.voice, transmission.volume, transmission.label, transmission.coordinate) transmission.msrs:PlayTextExt(transmission.text, nil, transmission.frequency, transmission.modulation, transmission.gender, transmission.culture, transmission.voice, transmission.volume, transmission.label, transmission.coordinate)

View File

@@ -691,7 +691,15 @@ function GROUP:GetUnits()
local DCSUnits = DCSGroup:getUnits() or {} local DCSUnits = DCSGroup:getUnits() or {}
local Units = {} local Units = {}
for Index, UnitData in pairs( DCSUnits ) do for Index, UnitData in pairs( DCSUnits ) do
Units[#Units+1] = UNIT:Find( UnitData )
local unit=UNIT:Find( UnitData )
if unit then
Units[#Units+1] = UNIT:Find( UnitData )
else
local UnitName=UnitData:getName()
unit=_DATABASE:AddUnit(UnitName)
Units[#Units+1]=unit
end
end end
self:T3( Units ) self:T3( Units )
return Units return Units

View File

@@ -5,37 +5,41 @@
-- This method is used by Moose developers and not mission builders. -- This method is used by Moose developers and not mission builders.
ModuleLoader = 'Scripts/Moose/Modules.lua' ModuleLoader = 'Scripts/Moose/Modules.lua'
local f=io.open(ModuleLoader,"r") if io then
if f~=nil then local f=io.open(ModuleLoader,"r")
io.close(f) if f~=nil then
io.close(f)
env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' )
local base = _G local base = _G
__Moose = {} __Moose = {}
__Moose.Include = function( IncludeFile ) __Moose.Include = function( IncludeFile )
if not __Moose.Includes[ IncludeFile ] then if not __Moose.Includes[ IncludeFile ] then
__Moose.Includes[IncludeFile] = IncludeFile __Moose.Includes[IncludeFile] = IncludeFile
local f = assert( base.loadfile( IncludeFile ) ) local f = assert( base.loadfile( IncludeFile ) )
if f == nil then if f == nil then
error ("Moose: Could not load Moose file " .. IncludeFile ) error ("Moose: Could not load Moose file " .. IncludeFile )
else else
env.info( "Moose: " .. IncludeFile .. " dynamically loaded." ) env.info( "Moose: " .. IncludeFile .. " dynamically loaded." )
return f() return f()
end
end end
end end
__Moose.Includes = {}
__Moose.Include( 'Scripts/Moose/Modules.lua' )
BASE:TraceOnOff( true )
env.info( '*** MOOSE INCLUDE END *** ' )
-- Skip the static part of this file completly
do return end
end end
else
__Moose.Includes = {} env.info( '*** MOOSE DYNAMIC INCLUDE NOT POSSIBLE (Desanitize io to use it) *** ' )
__Moose.Include( 'Scripts/Moose/Modules.lua' )
BASE:TraceOnOff( true )
env.info( '*** MOOSE INCLUDE END *** ' )
-- Skip the static part of this file completly
do return end
end end
-- Individual Moose files are not found. Use the static code below. -- Individual Moose files are not found. Use the static code below.