Merge branch 'develop' into FF/Ops

This commit is contained in:
Frank 2025-08-25 20:53:40 +02:00
commit 1ddb2aaec0
40 changed files with 3993 additions and 1725 deletions

View File

@ -1112,7 +1112,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name) self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
else else
self.STNS[stn] = UnitTemplate.name self.STNS[stn] = UnitTemplate.name
self:I("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name) self:T("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
end end
end end
if UnitTemplate.AddPropAircraft.SADL_TN then if UnitTemplate.AddPropAircraft.SADL_TN then
@ -1121,7 +1121,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name) self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
else else
self.SADL[sadl] = UnitTemplate.name self.SADL[sadl] = UnitTemplate.name
self:I("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name) self:T("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
end end
end end
end end
@ -1382,7 +1382,7 @@ function DATABASE:GetCoalitionFromClientTemplate( ClientName )
if self.Templates.ClientsByName[ClientName] then if self.Templates.ClientsByName[ClientName] then
return self.Templates.ClientsByName[ClientName].CoalitionID return self.Templates.ClientsByName[ClientName].CoalitionID
end end
self:E("WARNING: Template does not exist for client "..tostring(ClientName)) self:T("WARNING: Template does not exist for client "..tostring(ClientName))
return nil return nil
end end
@ -1394,7 +1394,7 @@ function DATABASE:GetCategoryFromClientTemplate( ClientName )
if self.Templates.ClientsByName[ClientName] then if self.Templates.ClientsByName[ClientName] then
return self.Templates.ClientsByName[ClientName].CategoryID return self.Templates.ClientsByName[ClientName].CategoryID
end end
self:E("WARNING: Template does not exist for client "..tostring(ClientName)) self:T("WARNING: Template does not exist for client "..tostring(ClientName))
return nil return nil
end end
@ -1406,7 +1406,7 @@ function DATABASE:GetCountryFromClientTemplate( ClientName )
if self.Templates.ClientsByName[ClientName] then if self.Templates.ClientsByName[ClientName] then
return self.Templates.ClientsByName[ClientName].CountryID return self.Templates.ClientsByName[ClientName].CountryID
end end
self:E("WARNING: Template does not exist for client "..tostring(ClientName)) self:T("WARNING: Template does not exist for client "..tostring(ClientName))
return nil return nil
end end

View File

@ -50,7 +50,7 @@ MARKEROPS_BASE = {
ClassName = "MARKEROPS", ClassName = "MARKEROPS",
Tag = "mytag", Tag = "mytag",
Keywords = {}, Keywords = {},
version = "0.1.3", version = "0.1.4",
debug = false, debug = false,
Casesensitive = true, Casesensitive = true,
} }
@ -154,14 +154,7 @@ function MARKEROPS_BASE:OnEventMark(Event)
self:E("Skipping onEvent. Event or Event.idx unknown.") self:E("Skipping onEvent. Event or Event.idx unknown.")
return true return true
end end
--position
local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z}
local coord=COORDINATE:NewFromVec3(vec3)
if self.debug then
local coordtext = coord:ToStringLLDDM()
local text = tostring(Event.text)
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
end
local coalition = Event.MarkCoalition local coalition = Event.MarkCoalition
-- decision -- decision
if Event.id==world.event.S_EVENT_MARK_ADDED then if Event.id==world.event.S_EVENT_MARK_ADDED then
@ -170,8 +163,14 @@ function MARKEROPS_BASE:OnEventMark(Event)
local Eventtext = tostring(Event.text) local Eventtext = tostring(Event.text)
if Eventtext~=nil then if Eventtext~=nil then
if self:_MatchTag(Eventtext) then if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext) local coord=COORDINATE:NewFromVec3({y=Event.pos.y, x=Event.pos.x, z=Event.pos.z})
self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event) if self.debug then
local coordtext = coord:ToStringLLDDM()
local text = tostring(Event.text)
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
end
local matchtable = self:_MatchKeywords(Eventtext)
self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
end end
end end
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
@ -180,8 +179,14 @@ function MARKEROPS_BASE:OnEventMark(Event)
local Eventtext = tostring(Event.text) local Eventtext = tostring(Event.text)
if Eventtext~=nil then if Eventtext~=nil then
if self:_MatchTag(Eventtext) then if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext) local coord=COORDINATE:NewFromVec3({y=Event.pos.y, x=Event.pos.x, z=Event.pos.z})
self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event) if self.debug then
local coordtext = coord:ToStringLLDDM()
local text = tostring(Event.text)
local m = MESSAGE:New(string.format("Mark changed at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
end
local matchtable = self:_MatchKeywords(Eventtext)
self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
end end
end end
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then elseif Event.id==world.event.S_EVENT_MARK_REMOVED then

View File

@ -452,7 +452,7 @@ end
_MESSAGESRS = {} _MESSAGESRS = {}
--- 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. --- 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 (optional) Path to SRS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" or your configuration file setting. -- @param #string PathToSRS (optional) Path to SRS TTS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\ExternalAudio" or your configuration file setting.
-- @param #number Port Port (optional) number of SRS, defaults to 5002 or your configuration file setting. -- @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 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.
@ -468,13 +468,13 @@ _MESSAGESRS = {}
-- @usage -- @usage
-- -- Mind the dot here, not using the colon this time around! -- -- Mind the dot here, not using the colon this time around!
-- -- Needed once only -- -- Needed once only
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) -- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
-- -- later on in your code -- -- later on in your code
-- 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,Backend) function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend)
_MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" _MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
_MESSAGESRS.frequency = Frequency or MSRS.frequencies or 243 _MESSAGESRS.frequency = Frequency or MSRS.frequencies or 243
_MESSAGESRS.modulation = Modulation or MSRS.modulations or radio.modulation.AM _MESSAGESRS.modulation = Modulation or MSRS.modulations or radio.modulation.AM
@ -535,7 +535,7 @@ end
-- @usage -- @usage
-- -- Mind the dot here, not using the colon this time around! -- -- Mind the dot here, not using the colon this time around!
-- -- Needed once only -- -- Needed once only
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) -- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
-- -- later on in your code -- -- later on in your code
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS() -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS()
-- --
@ -567,7 +567,7 @@ end
-- @usage -- @usage
-- -- Mind the dot here, not using the colon this time around! -- -- Mind the dot here, not using the colon this time around!
-- -- Needed once only -- -- Needed once only
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) -- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
-- -- later on in your code -- -- later on in your code
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSBlue() -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSBlue()
-- --
@ -589,7 +589,7 @@ end
-- @usage -- @usage
-- -- Mind the dot here, not using the colon this time around! -- -- Mind the dot here, not using the colon this time around!
-- -- Needed once only -- -- Needed once only
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.RED) -- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.RED)
-- -- later on in your code -- -- later on in your code
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSRed() -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSRed()
-- --
@ -611,7 +611,7 @@ end
-- @usage -- @usage
-- -- Mind the dot here, not using the colon this time around! -- -- Mind the dot here, not using the colon this time around!
-- -- Needed once only -- -- Needed once only
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.NEUTRAL) -- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.NEUTRAL)
-- -- later on in your code -- -- later on in your code
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSAll() -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSAll()
-- --

View File

@ -777,7 +777,9 @@ do -- COORDINATE
-- @return DCS#Vec2 Vec2 -- @return DCS#Vec2 Vec2
function COORDINATE:GetRandomVec2InRadius( OuterRadius, InnerRadius ) function COORDINATE:GetRandomVec2InRadius( OuterRadius, InnerRadius )
self:F2( { OuterRadius, InnerRadius } ) self:F2( { OuterRadius, InnerRadius } )
math.random()
math.random()
math.random()
local Theta = 2 * math.pi * math.random() local Theta = 2 * math.pi * math.random()
local Radials = math.random() + math.random() local Radials = math.random() + math.random()
if Radials > 1 then if Radials > 1 then
@ -837,6 +839,26 @@ do -- COORDINATE
return land.getHeight( Vec2 ) return land.getHeight( Vec2 )
end end
--- Returns a table of DCS#Vec3 points representing the terrain profile between two points.
-- @param #COORDINATE self
-- @param Destination DCS#Vec3 Ending point of the profile.
-- @return #table DCS#Vec3 table of the profile
function COORDINATE:GetLandProfileVec3(Destination)
return land.profile(self:GetVec3(), Destination)
end
--- Returns a table of #COORDINATE representing the terrain profile between two points.
-- @param #COORDINATE self
-- @param Destination #COORDINATE Ending coordinate of the profile.
-- @return #table #COORDINATE table of the profile
function COORDINATE:GetLandProfileCoordinates(Destination)
local points = self:GetLandProfileVec3(Destination:GetVec3())
local coords = {}
for _, point in ipairs(points) do
table.insert(coords, COORDINATE:NewFromVec3(point))
end
return coords
end
--- Set the heading of the coordinate, if applicable. --- Set the heading of the coordinate, if applicable.
-- @param #COORDINATE self -- @param #COORDINATE self
@ -3797,7 +3819,26 @@ do -- COORDINATE
function COORDINATE:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) function COORDINATE:GetRandomPointVec3InRadius( OuterRadius, InnerRadius )
return COORDINATE:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) return COORDINATE:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) )
end end
--- Search for clear zones in a given area. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param #number SearchRadius Radius of the search area.
-- @param #number PosRadius Required clear radius around each position.
-- @param #number NumPositions Number of positions to find.
-- @return #table A table of Core.Point#COORDINATE that are clear of map objects within the given PosRadius. nil if no positions are found.
function COORDINATE:GetSimpleZones(SearchRadius, PosRadius, NumPositions)
local clearPositions = UTILS.GetSimpleZones(self:GetVec3(), SearchRadius, PosRadius, NumPositions)
if clearPositions and #clearPositions > 0 then
local coords = {}
for _, pos in pairs(clearPositions) do
local coord = COORDINATE:NewFromVec2(pos)
table.insert(coords, coord)
end
return coords
end
return nil
end
end end
do do

View File

@ -175,7 +175,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
local Name = Info.name or "?" local Name = Info.name or "?"
local ErrorHandler = function( errmsg ) local ErrorHandler = function( errmsg )
env.info( "Error in timer function: " .. errmsg ) env.info( "Error in timer function: " .. errmsg or "" )
if BASE.Debug ~= nil then if BASE.Debug ~= nil then
env.info( BASE.Debug.traceback() ) env.info( BASE.Debug.traceback() )
end end

View File

@ -958,7 +958,26 @@ do -- SET_BASE
return ObjectNames return ObjectNames
end end
--- Get a *new* set table that only contains alive objects.
-- @param #SET_BASE self
-- @return #table Set table of alive objects.
function SET_BASE:GetAliveSet()
--self:F2()
local AliveSet = {}
-- Clean the Set before returning with only the alive Objects.
for ObjectName, Object in pairs( self.Set ) do
if Object then
if Object:IsAlive() then
AliveSet[#AliveSet+1] = Object
end
end
end
return AliveSet or {}
end
end end
do do
@ -1125,25 +1144,25 @@ do
end end
--- Get a *new* set that only contains alive groups. --- Get a *new* set table that only contains alive groups.
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @return #SET_GROUP Set of alive groups. -- @return #table Set of alive groups.
function SET_GROUP:GetAliveSet() function SET_GROUP:GetAliveSet()
--self:F2() --self:F2()
local AliveSet = SET_GROUP:New() --local AliveSet = SET_GROUP:New()
local AliveSet = {}
-- Clean the Set before returning with only the alive Groups. -- Clean the Set before returning with only the alive Groups.
for GroupName, GroupObject in pairs( self.Set ) do for GroupName, GroupObject in pairs( self.Set ) do
local GroupObject = GroupObject -- Wrapper.Group#GROUP local GroupObject = GroupObject -- Wrapper.Group#GROUP
if GroupObject then if GroupObject then
if GroupObject:IsAlive() then if GroupObject:IsAlive() then
AliveSet:Add( GroupName, GroupObject ) AliveSet[GroupName] = GroupObject
end end
end end
end end
return AliveSet.Set or {} return AliveSet or {}
end end
--- Returns a report of of unit types. --- Returns a report of of unit types.
@ -2595,18 +2614,16 @@ do -- SET_UNIT
--- Gets the alive set. --- Gets the alive set.
-- @param #SET_UNIT self -- @param #SET_UNIT self
-- @return #table Table of SET objects -- @return #table Table of alive UNIT objects
-- @return #SET_UNIT AliveSet -- @return #SET_UNIT AliveSet
function SET_UNIT:GetAliveSet() function SET_UNIT:GetAliveSet()
local AliveSet = SET_UNIT:New() local AliveSet = SET_UNIT:New()
-- Clean the Set before returning with only the alive Groups. -- Clean the Set before returning with only the alive Groups.
for GroupName, GroupObject in pairs(self.Set) do for GroupName, GroupObject in pairs(self.Set) do
local GroupObject=GroupObject --Wrapper.Client#CLIENT
if GroupObject and GroupObject:IsAlive() then if GroupObject and GroupObject:IsAlive() then
AliveSet:Add(GroupName, GroupObject) AliveSet[GroupName] = GroupObject
end end
end end
@ -4784,18 +4801,16 @@ do -- SET_CLIENT
-- @return #table Table of SET objects -- @return #table Table of SET objects
function SET_CLIENT:GetAliveSet() function SET_CLIENT:GetAliveSet()
local AliveSet = SET_CLIENT:New() local AliveSet = {}
-- Clean the Set before returning with only the alive Groups. -- Clean the Set before returning with only the alive Groups.
for GroupName, GroupObject in pairs(self.Set) do for GroupName, GroupObject in pairs(self.Set) do
local GroupObject=GroupObject --Wrapper.Client#CLIENT
if GroupObject and GroupObject:IsAlive() then if GroupObject and GroupObject:IsAlive() then
AliveSet:Add(GroupName, GroupObject) AliveSet[GroupName] = GroupObject
end end
end end
return AliveSet.Set or {} return AliveSet or {}
end end
--- [User] Add a custom condition function. --- [User] Add a custom condition function.
@ -6676,6 +6691,8 @@ do -- SET_ZONE
-- --
-- -- Stop watching after 1 hour -- -- Stop watching after 1 hour
-- zoneset:__TriggerStop(3600) -- zoneset:__TriggerStop(3600)
-- -- Call :SetPartlyInside() on any zone (or SET_ZONE) if you want GROUPs to count as inside when any of their units enters even if they are far apart.
-- -- Make sure to call :SetPartlyInside() before :Trigger()!.
function SET_ZONE:Trigger(Objects) function SET_ZONE:Trigger(Objects)
--self:I("Added Set_Zone Trigger") --self:I("Added Set_Zone Trigger")
self:AddTransition("*","TriggerStart","TriggerRunning") self:AddTransition("*","TriggerStart","TriggerRunning")
@ -6726,6 +6743,20 @@ do -- SET_ZONE
-- @param Core.Zone#ZONE_BASE Zone The zone left. -- @param Core.Zone#ZONE_BASE Zone The zone left.
end end
--- Toggle “partly-inside” handling for every zone in the set when those zones are used with :Trigger().
-- * Call with no argument or **true** → enable for all.
-- * Call with **false** → disable again (handy if it was enabled before).
-- @param #SET_ZONE self
-- @return #SET_ZONE self
function SET_ZONE:SetPartlyInside(state)
for _,Zone in pairs(self.Set) do
if Zone.SetPartlyInside then
Zone:SetPartlyInside(state)
end
end
return self
end
--- (Internal) Check the assigned objects for being in/out of the zone --- (Internal) Check the assigned objects for being in/out of the zone
-- @param #SET_ZONE self -- @param #SET_ZONE self
-- @param #boolean fromstart If true, do the init of the objects -- @param #boolean fromstart If true, do the init of the objects
@ -6761,8 +6792,13 @@ do -- SET_ZONE
-- has not been tagged previously - wasn't in set! -- has not been tagged previously - wasn't in set!
obj.TriggerInZone[_zone.ZoneName] = false obj.TriggerInZone[_zone.ZoneName] = false
end end
-- is obj in zone? -- is obj in this zone?
local inzone = _zone:IsCoordinateInZone(obj:GetCoordinate()) local inzone
if _zone.PartlyInside and obj.ClassName == "GROUP" then
inzone = obj:IsAnyInZone(_zone) -- TRUE as soon as any unit is inside
else
inzone = _zone:IsCoordinateInZone(obj:GetCoordinate()) -- original centroid test
end
--self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone)) --self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone))
if inzone and not obj.TriggerInZone[_zone.ZoneName] then if inzone and not obj.TriggerInZone[_zone.ZoneName] then
-- wasn't in zone before -- wasn't in zone before

View File

@ -1049,6 +1049,22 @@ function SPAWN:InitSetUnitAbsolutePositions(Positions)
return self return self
end end
--- Uses Disposition and other fallback logic to find better ground positions for ground units.
--- NOTE: This is not a spawn randomizer.
--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area.
--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit.
-- @param #boolean OnOff Enable/disable the feature.
-- @param #number MaxRadius (Optional) Max radius to search for valid ground locations in meters. Default is double the max radius of the units.
-- @param #number Spacing (Optional) Minimum spacing between units in meters. Default is 5% of the search radius or 5 meters, whichever is larger.
-- @return #SPAWN
function SPAWN:InitValidateAndRepositionGroundUnits(OnOff, MaxRadius, Spacing)
self.SpawnValidateAndRepositionGroundUnits = OnOff
self.SpawnValidateAndRepositionGroundUnitsRadius = MaxRadius
self.SpawnValidateAndRepositionGroundUnitsSpacing = Spacing
return self
end
--- This method is rather complicated to understand. But I'll try to explain. --- This method is rather complicated to understand. But I'll try to explain.
-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor,
-- but they will all follow the same Template route and have the same prefix name. -- but they will all follow the same Template route and have the same prefix name.
@ -1829,7 +1845,13 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
if self.SpawnHiddenOnMap then if self.SpawnHiddenOnMap then
SpawnTemplate.hidden=self.SpawnHiddenOnMap SpawnTemplate.hidden=self.SpawnHiddenOnMap
end end
if self.SpawnValidateAndRepositionGroundUnits then
local units = SpawnTemplate.units
local gPos = { x = SpawnTemplate.x, y = SpawnTemplate.y }
UTILS.ValidateAndRepositionGroundUnits(units, gPos, self.SpawnValidateAndRepositionGroundUnitsRadius, self.SpawnValidateAndRepositionGroundUnitsSpacing)
end
-- Set country, coalition and category. -- Set country, coalition and category.
SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID
SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID

View File

@ -149,6 +149,7 @@ function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
self.CategoryID = CategoryID self.CategoryID = CategoryID
self.CoalitionID = CoalitionID self.CoalitionID = CoalitionID
self.SpawnIndex = 0 self.SpawnIndex = 0
self.StaticCopyFrom = SpawnTemplateName
else else
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" ) error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
end end
@ -302,12 +303,16 @@ end
-- @param #number CallsignID Callsign ID. Default 1 (="London"). -- @param #number CallsignID Callsign ID. Default 1 (="London").
-- @param #number Frequency Frequency in MHz. Default 127.5 MHz. -- @param #number Frequency Frequency in MHz. Default 127.5 MHz.
-- @param #number Modulation Modulation 0=AM, 1=FM. -- @param #number Modulation Modulation 0=AM, 1=FM.
-- @param #boolean DynamicSpawns If true, allow Dynamic Spawns
-- @param #boolean DynamicHotStarts If true, and DynamicSpawns is true, then allow Dynamic Spawns with hot starts.
-- @return #SPAWNSTATIC self -- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation) function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation, DynamicSpawns,DynamicHotStarts)
self.InitFarp=true self.InitFarp=true
self.InitFarpCallsignID=CallsignID or 1 self.InitFarpCallsignID=CallsignID or 1
self.InitFarpFreq=Frequency or 127.5 self.InitFarpFreq=Frequency or 127.5
self.InitFarpModu=Modulation or 0 self.InitFarpModu=Modulation or 0
self.InitFarpDynamicSpawns = DynamicSpawns
self.InitFarpDynamicHotStarts = (DynamicSpawns == true and DynamicHotStarts == true) and true or nil
return self return self
end end
@ -550,6 +555,13 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
TemplateGroup.x=Template.x TemplateGroup.x=Template.x
TemplateGroup.y=Template.y TemplateGroup.y=Template.y
TemplateGroup.name=Template.name TemplateGroup.name=Template.name
if self.InitFarpDynamicSpawns == true then
TemplateGroup.units[1].dynamicSpawn = true
if self.InitFarpDynamicHotStarts == true then
TemplateGroup.units[1].allowHotStart = true
end
end
self:T("Spawning FARP") self:T("Spawning FARP")
self:T({Template=Template}) self:T({Template=Template})
@ -557,7 +569,8 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
-- ED's dirty way to spawn FARPS. -- ED's dirty way to spawn FARPS.
Static=coalition.addGroup(CountryID, -1, TemplateGroup) Static=coalition.addGroup(CountryID, -1, TemplateGroup)
--Static=coalition.addStaticObject(CountryID, Template)
-- Currently DCS 2.8 does not trigger birth events if FARPS are spawned! -- Currently DCS 2.8 does not trigger birth events if FARPS are spawned!
-- We create such an event. The airbase is registered in Core.Event -- We create such an event. The airbase is registered in Core.Event
local Event = { local Event = {
@ -595,6 +608,19 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
-- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group. -- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group.
self:ScheduleOnce(0.3, self.SpawnFunctionHook, mystatic, unpack(self.SpawnFunctionArguments)) self:ScheduleOnce(0.3, self.SpawnFunctionHook, mystatic, unpack(self.SpawnFunctionArguments))
end end
if self.StaticCopyFrom ~= nil then
mystatic.StaticCopyFrom = self.StaticCopyFrom
if not _DATABASE.Templates.Statics[Template.name] then
local TemplateGroup={}
TemplateGroup.units={}
TemplateGroup.units[1]=Template
TemplateGroup.x=Template.x
TemplateGroup.y=Template.y
TemplateGroup.name=Template.name
_DATABASE:_RegisterStaticTemplate( TemplateGroup, self.CoalitionID, self.CategoryID, CountryID )
end
end
return mystatic return mystatic
end end

View File

@ -70,6 +70,7 @@
-- @field #table Table of any trigger zone properties from the ME. The key is the Name of the property, and the value is the property's Value. -- @field #table Table of any trigger zone properties from the ME. The key is the Name of the property, and the value is the property's Value.
-- @field #number Surface Type of surface. Only determined at the center of the zone! -- @field #number Surface Type of surface. Only determined at the center of the zone!
-- @field #number Checktime Check every Checktime seconds, used for ZONE:Trigger() -- @field #number Checktime Check every Checktime seconds, used for ZONE:Trigger()
-- @field #boolean PartlyInside When called, a GROUP is considered inside as soon as any of its units enters the zone even if they are far apart.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@ -534,6 +535,19 @@ function ZONE_BASE:GetZoneProbability()
return self.ZoneProbability return self.ZoneProbability
end end
--- Get the coordinate on the radius of the zone nearest to Outsidecoordinate. Useto e.g. find an ingress point.
-- @param #ZONE_BASE self
-- @param Core.Point#COORDINATE Outsidecoordinate The coordinate outside of the zone from where to look.
-- @return Core.Point#COORDINATE CoordinateOnRadius
function ZONE_BASE:FindNearestCoordinateOnRadius(Outsidecoordinate)
local Vec1 = self:GetVec2()
local Radius = self:GetRadius()
local Vec2 = Outsidecoordinate:GetVec2()
local Point = UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2)
local rc = COORDINATE:NewFromVec2(Point)
return rc
end
--- Get the zone taking into account the randomization probability of a zone to be selected. --- Get the zone taking into account the randomization probability of a zone to be selected.
-- @param #ZONE_BASE self -- @param #ZONE_BASE self
-- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor. -- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor.
@ -599,6 +613,8 @@ end
-- --
-- -- Stop watching the zone after 1 hour -- -- Stop watching the zone after 1 hour
-- triggerzone:__TriggerStop(3600) -- triggerzone:__TriggerStop(3600)
-- -- Call :SetPartlyInside() if you use SET_GROUP to count as inside when any of their units enters even when they are far apart.
-- -- Make sure to call :SetPartlyInside() before :Trigger()!
function ZONE_BASE:Trigger(Objects) function ZONE_BASE:Trigger(Objects)
--self:I("Added Zone Trigger") --self:I("Added Zone Trigger")
self:SetStartState("TriggerStopped") self:SetStartState("TriggerStopped")
@ -667,6 +683,16 @@ function ZONE_BASE:Trigger(Objects)
end end
--- Toggle “partly-inside” handling for this zone. To be used before :Trigger().
-- * Default:* flag is **false** until you call the method.
-- * Call with no argument or with **true** → enable.
-- * Call with **false** → disable again (handy if it was enabled before).
-- @param #ZONE_BASE self
-- @return #ZONE_BASE self
function ZONE_BASE:SetPartlyInside(state)
self.PartlyInside = state or not ( state == false )
return self
end
--- (Internal) Check the assigned objects for being in/out of the zone --- (Internal) Check the assigned objects for being in/out of the zone
-- @param #ZONE_BASE self -- @param #ZONE_BASE self
-- @param #boolean fromstart If true, do the init of the objects -- @param #boolean fromstart If true, do the init of the objects
@ -705,7 +731,12 @@ function ZONE_BASE:_TriggerCheck(fromstart)
obj.TriggerInZone[self.ZoneName] = false obj.TriggerInZone[self.ZoneName] = false
end end
-- is obj in zone? -- is obj in zone?
local inzone = self:IsCoordinateInZone(obj:GetCoordinate()) local inzone
if self.PartlyInside and obj.ClassName == "GROUP" then
inzone = obj:IsAnyInZone(self) -- TRUE if any unit is inside
else
inzone = self:IsCoordinateInZone(obj:GetCoordinate()) -- original barycentre test
end
--self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone)) --self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone))
if inzone and obj.TriggerInZone[self.ZoneName] then if inzone and obj.TriggerInZone[self.ZoneName] then
-- just count -- just count
@ -1509,6 +1540,26 @@ function ZONE_RADIUS:IsVec3InZone( Vec3 )
return InZone return InZone
end end
--- Search for clear ground spawn zones within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param #ZONE_RADIUS self
-- @param #number PosRadius Required clear radius around each position.
-- @param #number NumPositions Number of positions to find.
-- @return #table A table of DCS#Vec2 positions that are clear of map objects within the given PosRadius. nil if no clear positions are found.
function ZONE_RADIUS:GetClearZonePositions(PosRadius, NumPositions)
return UTILS.GetClearZonePositions(self, PosRadius, NumPositions)
end
--- Search for a random clear ground spawn coordinate within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param #ZONE_RADIUS self
-- @param #number PosRadius (Optional) Required clear radius around each position. (Default is math.min(Radius/10, 200))
-- @param #number NumPositions (Optional) Number of positions to find. (Default 50)
-- @return Core.Point#COORDINATE A random coordinate for a clear zone. nil if no clear positions are found.
-- @return #number Assigned radius for the found zones. nil if no clear positions are found.
function ZONE_RADIUS:GetRandomClearZoneCoordinate(PosRadius, NumPositions)
return UTILS.GetRandomClearZoneCoordinate(self, PosRadius, NumPositions)
end
--- Returns a random Vec2 location within the zone. --- Returns a random Vec2 location within the zone.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. -- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0.
@ -1520,6 +1571,10 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
local Vec2 = self:GetVec2() local Vec2 = self:GetVec2()
local _inner = inner or 0 local _inner = inner or 0
local _outer = outer or self:GetRadius() local _outer = outer or self:GetRadius()
math.random()
math.random()
math.random()
if surfacetypes and type(surfacetypes)~="table" then if surfacetypes and type(surfacetypes)~="table" then
surfacetypes={surfacetypes} surfacetypes={surfacetypes}
@ -2487,6 +2542,26 @@ function ZONE_POLYGON_BASE:Flush()
return self return self
end end
--- Search for clear ground spawn zones within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param #ZONE_POLYGON_BASE self
-- @param #number PosRadius Required clear radius around each position.
-- @param #number NumPositions Number of positions to find.
-- @return #table A table of DCS#Vec2 positions that are clear of map objects within the given PosRadius. nil if no clear positions are found.
function ZONE_POLYGON_BASE:GetClearZonePositions(PosRadius, NumPositions)
return UTILS.GetClearZonePositions(self, PosRadius, NumPositions)
end
--- Search for a random clear ground spawn coordinate within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param #ZONE_POLYGON_BASE self
-- @param #number PosRadius (Optional) Required clear radius around each position. (Default is math.min(Radius/10, 200))
-- @param #number NumPositions (Optional) Number of positions to find. (Default 50)
-- @return Core.Point#COORDINATE A random coordinate for a clear zone. nil if no clear positions are found.
-- @return #number Assigned radius for the found zones. nil if no clear positions are found.
function ZONE_POLYGON_BASE:GetRandomClearZoneCoordinate(PosRadius, NumPositions)
return UTILS.GetRandomClearZoneCoordinate(self, PosRadius, NumPositions)
end
--- Smokes the zone boundaries in a color. --- Smokes the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE self
-- @param #boolean UnBound If true, the tyres will be destroyed. -- @param #boolean UnBound If true, the tyres will be destroyed.
@ -2865,6 +2940,11 @@ end
function ZONE_POLYGON_BASE:GetRandomVec2() function ZONE_POLYGON_BASE:GetRandomVec2()
-- make sure we assign weights to the triangles based on their surface area, otherwise -- make sure we assign weights to the triangles based on their surface area, otherwise
-- we'll be more likely to generate random points in smaller triangles -- we'll be more likely to generate random points in smaller triangles
math.random()
math.random()
math.random()
local weights = {} local weights = {}
for _, triangle in pairs(self._Triangles) do for _, triangle in pairs(self._Triangles) do
weights[triangle] = triangle.SurfaceArea / self.SurfaceArea weights[triangle] = triangle.SurfaceArea / self.SurfaceArea
@ -3204,12 +3284,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories )
local vectors = self:GetBoundingSquare() local vectors = self:GetBoundingSquare()
local minVec3 = {x=vectors.x1, y=0, z=vectors.y1} local ZoneRadius = UTILS.VecDist2D({x=vectors.x1, y=vectors.y1}, {x=vectors.x2, y=vectors.y2})/2
local maxVec3 = {x=vectors.x2, y=0, z=vectors.y2}
local minmarkcoord = COORDINATE:NewFromVec3(minVec3)
local maxmarkcoord = COORDINATE:NewFromVec3(maxVec3)
local ZoneRadius = minmarkcoord:Get2DDistance(maxmarkcoord)/2
-- self:I("Scan Radius:" ..ZoneRadius) -- self:I("Scan Radius:" ..ZoneRadius)
local CenterVec3 = self:GetCoordinate():GetVec3() local CenterVec3 = self:GetCoordinate():GetVec3()

View File

@ -22,7 +22,7 @@
-- === -- ===
-- --
-- ### Author: **Applevangelist** -- ### Author: **Applevangelist**
-- Last Update Sept 2023 -- Last Update July 2025
-- --
-- === -- ===
-- @module Functional.AICSAR -- @module Functional.AICSAR
@ -57,6 +57,8 @@
-- @field #number Speed Default speed setting for the helicopter FLIGHTGROUP is 100kn. -- @field #number Speed Default speed setting for the helicopter FLIGHTGROUP is 100kn.
-- @field #boolean UseEventEject In case Event LandingAfterEjection isn't working, use set this to true. -- @field #boolean UseEventEject In case Event LandingAfterEjection isn't working, use set this to true.
-- @field #number Delay In case of UseEventEject wait this long until we spawn a landed pilot. -- @field #number Delay In case of UseEventEject wait this long until we spawn a landed pilot.
-- @field #boolean UseRescueZone If true, use a rescue zone and not the max distance to FARP/MASH
-- @field Core.Zone#ZONE_RADIUS RescueZone Use this zone as operational area for the AICSAR instance.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@ -153,10 +155,10 @@
-- To set up AICSAR for SRS TTS output, add e.g. the following to your script: -- To set up AICSAR for SRS TTS output, add e.g. the following to your script:
-- --
-- -- setup for google TTS, radio 243 AM, SRS server port 5002 with a google standard-quality voice (google cloud account required) -- -- setup for google TTS, radio 243 AM, SRS server port 5002 with a google standard-quality voice (google cloud account required)
-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\google.json") -- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\google.json")
-- --
-- -- alternatively for MS Desktop TTS (voices need to be installed locally first!) -- -- alternatively for MS Desktop TTS (voices need to be installed locally first!)
-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female") -- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female")
-- --
-- -- define a different voice for the downed pilot(s) -- -- define a different voice for the downed pilot(s)
-- my_aicsar:SetPilotTTSVoice(MSRS.Voices.Google.Standard.en_AU_Standard_D,"en-AU","male") -- my_aicsar:SetPilotTTSVoice(MSRS.Voices.Google.Standard.en_AU_Standard_D,"en-AU","male")
@ -177,7 +179,7 @@
-- --
-- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so: -- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so:
-- --
-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,nil,5002) -- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",270,radio.modulation.AM,nil,5002)
-- --
-- or -- or
-- --
@ -191,7 +193,7 @@
-- @field #AICSAR -- @field #AICSAR
AICSAR = { AICSAR = {
ClassName = "AICSAR", ClassName = "AICSAR",
version = "0.1.16", version = "0.1.18",
lid = "", lid = "",
coalition = coalition.side.BLUE, coalition = coalition.side.BLUE,
template = "", template = "",
@ -236,6 +238,8 @@ AICSAR = {
Altitude = 1500, Altitude = 1500,
UseEventEject = false, UseEventEject = false,
Delay = 100, Delay = 100,
UseRescueZone = false,
RescueZone = nil,
} }
-- TODO Messages -- TODO Messages
@ -304,8 +308,9 @@ AICSAR.RadioLength = {
-- @param #string Helotemplate Helicopter template name. -- @param #string Helotemplate Helicopter template name.
-- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start. -- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start.
-- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue. -- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue.
-- @param #number Helonumber Max number of alive Ai Helos at the same time. Defaults to three.
-- @return #AICSAR self -- @return #AICSAR self
function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Helonumber)
-- Inherit everything from FSM class. -- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) local self=BASE:Inherit(self, FSM:New())
@ -373,7 +378,7 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
-- limit number of available helos at the same time -- limit number of available helos at the same time
self.limithelos = true self.limithelos = true
self.helonumber = 3 self.helonumber = Helonumber or 3
-- localization -- localization
self:InitLocalization() self:InitLocalization()
@ -524,10 +529,20 @@ function AICSAR:InitLocalization()
return self return self
end end
--- [User] Use a defined zone as area of operation and not the distance to FARP.
-- @param #AICSAR self
-- @param Core.Zone#ZONE Zone The operational zone to use. Downed pilots in this area will be rescued. Can be any known #ZONE type.
-- @return #AICSAR self
function AICSAR:SetUsingRescueZone(Zone)
self.UseRescueZone = true
self.RescueZone = Zone
return self
end
--- [User] Switch sound output on and use SRS output for sound files. --- [User] Switch sound output on and use SRS output for sound files.
-- @param #AICSAR self -- @param #AICSAR self
-- @param #boolean OnOff Switch on (true) or off (false). -- @param #boolean OnOff Switch on (true) or off (false).
-- @param #string Path Path to your SRS Server Component, e.g. "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" -- @param #string Path Path to your SRS Server External Audio Component, e.g. "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\\\ExternalAudio"
-- @param #number Frequency Defaults to 243 (guard) -- @param #number Frequency Defaults to 243 (guard)
-- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM -- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM
-- @param #string SoundPath Where to find the audio files. Defaults to nil, i.e. add messages via "Sound to..." in the Mission Editor. -- @param #string SoundPath Where to find the audio files. Defaults to nil, i.e. add messages via "Sound to..." in the Mission Editor.
@ -538,7 +553,7 @@ function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port)
self.SRSRadio = OnOff and true self.SRSRadio = OnOff and true
self.SRSTTSRadio = false self.SRSTTSRadio = false
self.SRSFrequency = Frequency or 243 self.SRSFrequency = Frequency or 243
self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.SRS:SetLabel("ACSR") self.SRS:SetLabel("ACSR")
self.SRS:SetCoalition(self.coalition) self.SRS:SetCoalition(self.coalition)
self.SRSModulation = Modulation or radio.modulation.AM self.SRSModulation = Modulation or radio.modulation.AM
@ -556,7 +571,7 @@ end
-- See `AICSAR:SetPilotTTSVoice()` and `AICSAR:SetOperatorTTSVoice()` -- See `AICSAR:SetPilotTTSVoice()` and `AICSAR:SetOperatorTTSVoice()`
-- @param #AICSAR self -- @param #AICSAR self
-- @param #boolean OnOff Switch on (true) or off (false). -- @param #boolean OnOff Switch on (true) or off (false).
-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" -- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\ExternalAudio"
-- @param #number Frequency (Optional) Defaults to 243 (guard) -- @param #number Frequency (Optional) Defaults to 243 (guard)
-- @param #number Modulation (Optional) Radio modulation. Defaults to radio.modulation.AM -- @param #number Modulation (Optional) Radio modulation. Defaults to radio.modulation.AM
-- @param #number Port (Optional) Port of the SRS, defaults to 5002. -- @param #number Port (Optional) Port of the SRS, defaults to 5002.
@ -570,7 +585,7 @@ function AICSAR:SetSRSTTSRadio(OnOff,Path,Frequency,Modulation,Port,Voice,Cultur
self.SRSTTSRadio = OnOff and true self.SRSTTSRadio = OnOff and true
self.SRSRadio = false self.SRSRadio = false
self.SRSFrequency = Frequency or 243 self.SRSFrequency = Frequency or 243
self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.SRSModulation = Modulation or radio.modulation.AM self.SRSModulation = Modulation or radio.modulation.AM
self.SRSPort = Port or MSRS.port or 5002 self.SRSPort = Port or MSRS.port or 5002
if OnOff then if OnOff then
@ -693,7 +708,7 @@ function AICSAR:_EjectEventHandler(EventData)
local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
local _country = _event.initiator:getCountry() local _country = _event.initiator:getCountry()
local _coalition = coalition.getCountryCoalition( _country ) local _coalition = coalition.getCountryCoalition( _country )
local data = UTILS.DeepCopy(EventData) --local data = UTILS.DeepCopy(EventData)
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
self:ScheduleOnce(self.Delay,self._DelayedSpawnPilot,self,_LandingPos,_coalition) self:ScheduleOnce(self.Delay,self._DelayedSpawnPilot,self,_LandingPos,_coalition)
end end
@ -708,7 +723,14 @@ end
-- @return #AICSAR self -- @return #AICSAR self
function AICSAR:_DelayedSpawnPilot(_LandingPos,_coalition) function AICSAR:_DelayedSpawnPilot(_LandingPos,_coalition)
local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate()) local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate())
if self.UseRescueZone == true and self.RescueZone ~= nil then
if self.RescueZone:IsCoordinateInZone(_LandingPos) then
distancetofarp = self.maxdistance - 10
else
distancetofarp = self.maxdistance + 10
end
end
-- Mayday Message -- Mayday Message
local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale) local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale)
local text = "" local text = ""
@ -795,7 +817,13 @@ function AICSAR:_EventHandler(EventData, FromEject)
-- DONE: add distance check -- DONE: add distance check
local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate()) local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate())
if self.UseRescueZone == true and self.RescueZone ~= nil then
if self.RescueZone:IsCoordinateInZone(_LandingPos) then
distancetofarp = self.maxdistance - 10
else
distancetofarp = self.maxdistance + 10
end
end
-- Mayday Message -- Mayday Message
local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale) local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale)
local text = "" local text = ""
@ -817,7 +845,6 @@ function AICSAR:_EventHandler(EventData, FromEject)
if _coalition == self.coalition then if _coalition == self.coalition then
if self.verbose then if self.verbose then
MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition) MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition)
-- MESSAGE:New(msgtxt,15,"AICSAR"):ToLog()
end end
if self.SRSRadio then if self.SRSRadio then
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
@ -869,6 +896,7 @@ function AICSAR:_GetFlight()
:InitUnControlled(true) :InitUnControlled(true)
:OnSpawnGroup( :OnSpawnGroup(
function(Group) function(Group)
Group:OptionPreferVerticalLanding()
self:__HeloOnDuty(1,Group) self:__HeloOnDuty(1,Group)
end end
) )
@ -892,7 +920,7 @@ function AICSAR:_InitMission(Pilot,Index)
--local pilotset = SET_GROUP:New() --local pilotset = SET_GROUP:New()
--pilotset:AddGroup(Pilot) --pilotset:AddGroup(Pilot)
-- Cargo transport assignment. -- Cargo transport assignment.
local opstransport=OPSTRANSPORT:New(Pilot, pickupzone, self.farpzone) local opstransport=OPSTRANSPORT:New(Pilot, pickupzone, self.farpzone)
--opstransport:SetVerbosity(3) --opstransport:SetVerbosity(3)
@ -934,6 +962,10 @@ function AICSAR:_InitMission(Pilot,Index)
helo:__UnloadingDone(5) helo:__UnloadingDone(5)
end end
function helo:OnAfterLandAtAirbase(From,Event,To,airbase)
helo:Despawn(2)
end
self.helos[Index] = helo self.helos[Index] = helo
return self return self
@ -984,7 +1016,9 @@ function AICSAR:_CheckHelos()
local name = helo:GetName() local name = helo:GetName()
self:T("Helo group "..name.." in state "..state) self:T("Helo group "..name.." in state "..state)
if state == "Arrived" then if state == "Arrived" then
helo:__Stop(5) --helo:__Stop(5)
helo.OnAfterDead = nil
helo:Despawn(35)
self.helos[_index] = nil self.helos[_index] = nil
end end
else else
@ -1025,7 +1059,7 @@ function AICSAR:_CheckQueue(OpsGroup)
if self:_CheckInMashZone(_pilot) then if self:_CheckInMashZone(_pilot) then
self:T("Pilot" .. _pilot.GroupName .. " rescued!") self:T("Pilot" .. _pilot.GroupName .. " rescued!")
if OpsGroup then if OpsGroup then
OpsGroup:Despawn(10) --OpsGroup:Despawn(10)
else else
_pilot:Destroy(true,10) _pilot:Destroy(true,10)
end end

View File

@ -105,7 +105,7 @@ AUTOLASE = {
debug = false, debug = false,
smokemenu = true, smokemenu = true,
RoundingPrecision = 0, RoundingPrecision = 0,
increasegroundawareness = true, increasegroundawareness = false,
MonitorFrequency = 30, MonitorFrequency = 30,
} }
@ -216,7 +216,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
self.smokemenu = true self.smokemenu = true
self.threatmenu = true self.threatmenu = true
self.RoundingPrecision = 0 self.RoundingPrecision = 0
self.increasegroundawareness = true self.increasegroundawareness = false
self.MonitorFrequency = 30 self.MonitorFrequency = 30
self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)})
@ -493,7 +493,7 @@ end
--- (User) Function enable sending messages via SRS. --- (User) Function enable sending messages via SRS.
-- @param #AUTOLASE self -- @param #AUTOLASE self
-- @param #boolean OnOff Switch usage on and off -- @param #boolean OnOff Switch usage on and off
-- @param #string Path Path to SRS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalone -- @param #string Path Path to SRS TTS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio
-- @param #number Frequency Frequency to send, e.g. 243 -- @param #number Frequency Frequency to send, e.g. 243
-- @param #number Modulation Modulation i.e. radio.modulation.AM or radio.modulation.FM -- @param #number Modulation Modulation i.e. radio.modulation.AM or radio.modulation.FM
-- @param #string Label (Optional) Short label to be used on the SRS Client Overlay -- @param #string Label (Optional) Short label to be used on the SRS Client Overlay
@ -508,7 +508,7 @@ end
function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Culture,Port,Voice,Volume,PathToGoogleKey) function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Culture,Port,Voice,Volume,PathToGoogleKey)
if OnOff then if OnOff then
self.useSRS = true self.useSRS = true
self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.SRSFreq = Frequency or 271 self.SRSFreq = Frequency or 271
self.SRSMod = Modulation or radio.modulation.AM self.SRSMod = Modulation or radio.modulation.AM
self.Gender = Gender or MSRS.gender or "male" self.Gender = Gender or MSRS.gender or "male"
@ -1020,7 +1020,7 @@ function AUTOLASE:_Prescient()
self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName()) self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName())
for _,_static in pairs(Statics) do -- DCS static object here for _,_static in pairs(Statics) do -- DCS static object here
local static = STATIC:Find(_static) local static = STATIC:Find(_static)
if static and static:GetCoalition() ~= self.coalition then if static and static:GetCoalition() ~= self.coalition and static:GetCoordinate() then
local IsLOS = position:IsLOS(static:GetCoordinate()) local IsLOS = position:IsLOS(static:GetCoordinate())
if IsLOS then if IsLOS then
unit:KnowUnit(static,true,true) unit:KnowUnit(static,true,true)

View File

@ -22,7 +22,7 @@
-- @module Functional.Mantis -- @module Functional.Mantis
-- @image Functional.Mantis.jpg -- @image Functional.Mantis.jpg
-- --
-- Last Update: May 2025 -- Last Update: August 2025
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **MANTIS** class, extends Core.Base#BASE --- **MANTIS** class, extends Core.Base#BASE
@ -108,10 +108,15 @@
-- * Patriot -- * Patriot
-- * Rapier -- * Rapier
-- * Roland -- * Roland
-- * IRIS-T SLM
-- * Pantsir S1
-- * TOR M2
-- * C-RAM
-- * Silkworm (though strictly speaking this is a surface to ship missile) -- * Silkworm (though strictly speaking this is a surface to ship missile)
-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19 -- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19
-- * From IDF mod: STUNNER IDFA, TAMIR IDFA (Note all caps!) -- * From IDF mod: STUNNER IDFA, TAMIR IDFA (Note all caps!)
-- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2 -- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2, SAMP/T Block 1, SAMP/T Block 1INT, SAMP/T Block2
-- * Other Mods: Nike
-- --
-- * From SMA: RBS98M, RBS70, RBS90, RBS90M, RBS103A, RBS103B, RBS103AM, RBS103BM, Lvkv9040M -- * From SMA: RBS98M, RBS70, RBS90, RBS90M, RBS103A, RBS103B, RBS103AM, RBS103BM, Lvkv9040M
-- **NOTE** If you are using the Swedish Military Assets (SMA), please note that the **group name** for RBS-SAM types also needs to contain the keyword "SMA" -- **NOTE** If you are using the Swedish Military Assets (SMA), please note that the **group name** for RBS-SAM types also needs to contain the keyword "SMA"
@ -125,10 +130,11 @@
-- * SA-2 (with V759 missile, e.g. "Red SAM SA-2 HDS") -- * SA-2 (with V759 missile, e.g. "Red SAM SA-2 HDS")
-- * SA-2 (with HQ-2 launcher, use HQ-2 in the group name, e.g. "Red SAM HQ-2" ) -- * SA-2 (with HQ-2 launcher, use HQ-2 in the group name, e.g. "Red SAM HQ-2" )
-- * SA-3 (with V601P missile, e.g. "Red SAM SA-3 HDS") -- * SA-3 (with V601P missile, e.g. "Red SAM SA-3 HDS")
-- * SA-10B (overlap with other SA-10 types, e.g. "Red SAM SA-10B HDS") -- * SA-10B (overlap with other SA-10 types, e.g. "Red SAM SA-10B HDS" with 5P85CE launcher)
-- * SA-10C (overlap with other SA-10 types, e.g. "Red SAM SA-10C HDS") -- * SA-10C (overlap with other SA-10 types, e.g. "Red SAM SA-10C HDS" with 5P85SE launcher)
-- * SA-12 (launcher dependent range, e.g. "Red SAM SA-12 HDS") -- * SA-12 (launcher dependent range, e.g. "Red SAM SA-12 HDS 2" for the 9A82 variant and "Red SAM SA-12 HDS 1" for the 9A83 variant)
-- * SA-23 (launcher dependent range, e.g. "Red SAM SA-23 HDS") -- * SA-23 (launcher dependent range, e.g. "Red SAM SA-23 HDS 2" for the 9A82ME variant and "Red SAM SA-23 HDS 1" for the 9A83ME variant)
-- * SAMP/T (launcher dependent range, e.g. "Blue SAM SAMPT Block 1 HDS" for Block 1, "Blue SAM SAMPT Block 1INT HDS", "Blue SAM SAMPT Block 2 HDS")
-- --
-- The other HDS types work like the rest of the known SAM systems. -- The other HDS types work like the rest of the known SAM systems.
-- --
@ -274,6 +280,7 @@
MANTIS = { MANTIS = {
ClassName = "MANTIS", ClassName = "MANTIS",
name = "mymantis", name = "mymantis",
version = "0.9.34",
SAM_Templates_Prefix = "", SAM_Templates_Prefix = "",
SAM_Group = nil, SAM_Group = nil,
EWR_Templates_Prefix = "", EWR_Templates_Prefix = "",
@ -382,15 +389,21 @@ MANTIS.SamData = {
["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" }, ["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" },
["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Linebacker", Point="true" }, ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Linebacker", Point="true" },
["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" }, ["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" },
["HEMTT_C-RAM_Phalanx"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" }, ["C-RAM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" },
-- units from HDS Mod, multi launcher options is tricky -- units from HDS Mod, multi launcher options is tricky
["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"}, ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"},
["SA-17"] = { Range=50, Blindspot=3, Height=30, Type="Medium", Radar="SA-17" }, ["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17" },
["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"},
["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"},
["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" }, ["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" },
["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" },
["NIKE"] = { Range=155, Blindspot=6, Height=30, Type="Long", Radar="HIPAR" },
["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" },
-- CH Added to DCS core 2.9.19.x
["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true" },
["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" },
["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" },
} }
--- SAM data HDS --- SAM data HDS
@ -406,13 +419,17 @@ MANTIS.SamDataHDS = {
-- group name MUST contain HDS to ID launcher type correctly! -- group name MUST contain HDS to ID launcher type correctly!
["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" }, ["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" },
["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" }, ["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" },
["SA-10C HDS 2"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85DE ln"}, -- V55RUD ["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD
["SA-10C HDS 1"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD ["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln"}, -- V55RUD
["SA-12 HDS 2"] = { Range=100, Blindspot=10, Height=25, Type="Long" , Radar="S-300V 9A82 l"}, ["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17 " },
["SA-12 HDS 1"] = { Range=75, Blindspot=1, Height=25, Type="Long" , Radar="S-300V 9A83 l"}, ["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l"},
["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l"},
["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME" }, ["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME" },
["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME" }, ["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME" },
["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, ["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
["SAMPT Block 1 HDS"] = { Range=120, Blindspot=1, Height=20, Type="long", Radar="SAMPT_MLT_Blk1" }, -- Block 1 Launcher
["SAMPT Block 1INT HDS"] = { Range=150, Blindspot=1, Height=25, Type="long", Radar="SAMPT_MLT_Blk1NT" }, -- Block 1-INT Launcher
["SAMPT Block 2 HDS"] = { Range=200, Blindspot=10, Height=70, Type="long", Radar="SAMPT_MLT_Blk2" }, -- Block 2 Launcher
} }
--- SAM data SMA --- SAM data SMA
@ -452,15 +469,15 @@ MANTIS.SamDataCH = {
-- https://www.currenthill.com/ -- https://www.currenthill.com/
-- group name MUST contain CHM to ID launcher type correctly! -- group name MUST contain CHM to ID launcher type correctly!
["2S38 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" }, ["2S38 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" },
["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" }, ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true" },
["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, ["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" },
["PGL-625 CHM"] = { Range=10, Blindspot=1, Height=5, Type="Short", Radar="PGL_625" }, ["PGL-625 CHM"] = { Range=10, Blindspot=1, Height=5, Type="Short", Radar="PGL_625" },
["HQ-17A CHM"] = { Range=15, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" }, ["HQ-17A CHM"] = { Range=15, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" },
["M903PAC2 CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" }, ["M903PAC2 CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" },
["M903PAC3 CHM"] = { Range=160, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" }, ["M903PAC3 CHM"] = { Range=160, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" },
["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" }, ["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" },
["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" }, ["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2K", Point="true" },
["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" }, ["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Point", Radar="TorM2M", Point="true" },
["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" }, ["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" },
["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" }, ["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" },
["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" }, ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" },
@ -680,9 +697,6 @@ do
-- counter for SAM table updates -- counter for SAM table updates
self.checkcounter = 1 self.checkcounter = 1
-- TODO Version
-- @field #string version
self.version="0.9.30"
self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) self:I(string.format("***** Starting MANTIS Version %s *****", self.version))
--- FSM Functions --- --- FSM Functions ---
@ -879,7 +893,11 @@ do
self.AcceptZones = AcceptZones or {} self.AcceptZones = AcceptZones or {}
self.RejectZones = RejectZones or {} self.RejectZones = RejectZones or {}
self.ConflictZones = ConflictZones or {} self.ConflictZones = ConflictZones or {}
if #self.AcceptZones > 0 or #self.RejectZones > 0 or #self.ConflictZones > 0 then self.AcceptZonesNo = UTILS.TableLength(self.AcceptZones)
self.RejectZonesNo = UTILS.TableLength(self.RejectZones)
self.ConflictZonesNo = UTILS.TableLength(self.ConflictZones)
self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo))
if self.AcceptZonesNo > 0 or self.RejectZonesNo > 0 or self.ConflictZonesNo > 0 then
self.usezones = true self.usezones = true
end end
return self return self
@ -1271,7 +1289,8 @@ do
self:T(self.lid.."_CheckCoordinateInZones") self:T(self.lid.."_CheckCoordinateInZones")
local inzone = false local inzone = false
-- acceptzones -- acceptzones
if #self.AcceptZones > 0 then self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo))
if self.AcceptZonesNo > 0 then
for _,_zone in pairs(self.AcceptZones) do for _,_zone in pairs(self.AcceptZones) do
local zone = _zone -- Core.Zone#ZONE local zone = _zone -- Core.Zone#ZONE
if zone:IsCoordinateInZone(coord) then if zone:IsCoordinateInZone(coord) then
@ -1282,7 +1301,7 @@ do
end end
end end
-- rejectzones -- rejectzones
if #self.RejectZones > 0 and inzone then -- maybe in accept zone, but check the overlaps if self.RejectZonesNo > 0 then
for _,_zone in pairs(self.RejectZones) do for _,_zone in pairs(self.RejectZones) do
local zone = _zone -- Core.Zone#ZONE local zone = _zone -- Core.Zone#ZONE
if zone:IsCoordinateInZone(coord) then if zone:IsCoordinateInZone(coord) then
@ -1293,7 +1312,7 @@ do
end end
end end
-- conflictzones -- conflictzones
if #self.ConflictZones > 0 and not inzone then -- if not already accepted, might be in conflict zones if self.ConflictZonesNo > 0 then
for _,_zone in pairs(self.ConflictZones) do for _,_zone in pairs(self.ConflictZones) do
local zone = _zone -- Core.Zone#ZONE local zone = _zone -- Core.Zone#ZONE
if zone:IsCoordinateInZone(coord) then if zone:IsCoordinateInZone(coord) then
@ -1359,6 +1378,7 @@ do
end end
-- check accept/reject zones -- check accept/reject zones
local zonecheck = true local zonecheck = true
self:T("self.usezones = "..tostring(self.usezones))
if self.usezones then if self.usezones then
-- DONE -- DONE
zonecheck = self:_CheckCoordinateInZones(coord) zonecheck = self:_CheckCoordinateInZones(coord)
@ -1798,7 +1818,7 @@ do
if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then
activeshorad = true activeshorad = true
end end
if IsInZone and not suppressed and not activeshorad then --check any target in zone and not currently managed by SEAD if IsInZone and (not suppressed) and (not activeshorad) then --check any target in zone and not currently managed by SEAD
if samgroup:IsAlive() then if samgroup:IsAlive() then
-- switch on SAM -- switch on SAM
local switch = false local switch = false
@ -1830,7 +1850,7 @@ do
-- link in to SHORAD if available -- link in to SHORAD if available
-- DONE: Test integration fully -- DONE: Test integration fully
if self.ShoradLink and (Distance < self.ShoradActDistance or Distance < blind ) then -- don't give SHORAD position away too early if self.ShoradLink and (Distance < self.ShoradActDistance or Distance < blind ) then -- don't give SHORAD position away too early
local Shorad = self.Shorad local Shorad = self.Shorad --Functional.Shorad#SHORAD
local radius = self.checkradius local radius = self.checkradius
local ontime = self.ShoradTime local ontime = self.ShoradTime
Shorad:WakeUpShorad(name, radius, ontime) Shorad:WakeUpShorad(name, radius, ontime)
@ -2110,7 +2130,7 @@ do
if self.debug and self.verbose then if self.debug and self.verbose then
self:I(self.lid .. "Status Report") self:I(self.lid .. "Status Report")
for _name,_state in pairs(self.SamStateTracker) do for _name,_state in pairs(self.SamStateTracker) do
self:I(string.format("Site %s\tStatus %s",_name,_state)) self:I(string.format("Site %s | Status %s",_name,_state))
end end
end end
local interval = self.detectinterval * -1 local interval = self.detectinterval * -1

View File

@ -985,6 +985,7 @@ function SCORING:_EventOnHit( Event )
local TargetUnitCoalition = nil local TargetUnitCoalition = nil
local TargetUnitCategory = nil local TargetUnitCategory = nil
local TargetUnitType = nil local TargetUnitType = nil
local TargetIsScenery = false
if Event.IniDCSUnit then if Event.IniDCSUnit then
@ -1025,6 +1026,12 @@ function SCORING:_EventOnHit( Event )
TargetCategory = Event.TgtCategory TargetCategory = Event.TgtCategory
TargetType = Event.TgtTypeName TargetType = Event.TgtTypeName
-- Scenery hit
if (not TargetCategory) and TargetUNIT ~= nil and TargetUnit:IsInstanceOf("SCENERY") then
TargetCategory = Unit.Category.STRUCTURE
TargetIsScenery = true
end
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
TargetUnitCategory = _SCORINGCategory[TargetCategory] TargetUnitCategory = _SCORINGCategory[TargetCategory]
TargetUnitType = TargetType TargetUnitType = TargetType
@ -1117,17 +1124,22 @@ function SCORING:_EventOnHit( Event )
MESSAGE.Type.Update ) MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
else elseif TargetIsScenery ~= true then
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update ) MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
elseif TargetIsScenery == true then
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object." .. " Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
end end
self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
end end
else -- A scenery object was hit. else -- A scenery object was hit.
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object.", MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit nothing special.",
MESSAGE.Type.Update ) MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )

File diff suppressed because it is too large Load Diff

View File

@ -3153,7 +3153,7 @@ end
-- @param #WAREHOUSE self -- @param #WAREHOUSE self
-- @return Core.Point#COORDINATE The coordinate of the warehouse. -- @return Core.Point#COORDINATE The coordinate of the warehouse.
function WAREHOUSE:GetCoordinate() function WAREHOUSE:GetCoordinate()
return self.warehouse:GetCoordinate() return self.warehouse:GetCoord()
end end
--- Get 3D vector of warehouse static. --- Get 3D vector of warehouse static.
@ -4247,6 +4247,16 @@ function WAREHOUSE:_AssetItemInfo(asset)
self:T3({Template=asset.template}) self:T3({Template=asset.template})
end end
--- This function uses Disposition and other fallback logic to find better ground positions for ground units.
--- NOTE: This is not a spawn randomizer.
--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table.
--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit.
--- Uses UTILS.ValidateAndRepositionGroundUnits.
-- @param #boolean Enabled Enable/disable the feature.
function WAREHOUSE:SetValidateAndRepositionGroundUnits(Enabled)
self.ValidateAndRepositionGroundUnits = Enabled
end
--- On after "NewAsset" event. A new asset has been added to the warehouse stock. --- On after "NewAsset" event. A new asset has been added to the warehouse stock.
-- @param #WAREHOUSE self -- @param #WAREHOUSE self
-- @param #string From From state. -- @param #string From From state.
@ -5965,6 +5975,10 @@ function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, late
template.y = coord.z template.y = coord.z
template.alt = coord.y template.alt = coord.y
if self.ValidateAndRepositionGroundUnits then
UTILS.ValidateAndRepositionGroundUnits(template.units)
end
-- Spawn group. -- Spawn group.
local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP
@ -6893,7 +6907,7 @@ function WAREHOUSE:_CheckConquered()
for _,_unit in pairs(units) do for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT local unit=_unit --Wrapper.Unit#UNIT
local distance=coord:Get2DDistance(unit:GetCoordinate()) local distance=coord:Get2DDistance(unit:GetCoord())
-- Filter only alive groud units. Also check distance again, because the scan routine might give some larger distances. -- Filter only alive groud units. Also check distance again, because the scan routine might give some larger distances.
if unit:IsGround() and unit:IsAlive() and distance <= radius then if unit:IsGround() and unit:IsAlive() and distance <= radius then

View File

@ -116,7 +116,6 @@ __Moose.Include( 'Ops\\Operation.lua' )
__Moose.Include( 'Ops\\FlightControl.lua' ) __Moose.Include( 'Ops\\FlightControl.lua' )
__Moose.Include( 'Ops\\PlayerRecce.lua' ) __Moose.Include( 'Ops\\PlayerRecce.lua' )
__Moose.Include( 'Ops\\EasyGCICAP.lua' ) __Moose.Include( 'Ops\\EasyGCICAP.lua' )
__Moose.Include( 'Ops\\EasyA2G.lua' )
__Moose.Include( 'AI\\AI_Balancer.lua' ) __Moose.Include( 'AI\\AI_Balancer.lua' )
__Moose.Include( 'AI\\AI_Air.lua' ) __Moose.Include( 'AI\\AI_Air.lua' )

View File

@ -159,6 +159,8 @@ AIRWING = {
-- @field #number refuelsystem Refueling system type: `0=Unit.RefuelingSystem.BOOM_AND_RECEPTACLE`, `1=Unit.RefuelingSystem.PROBE_AND_DROGUE`. -- @field #number refuelsystem Refueling system type: `0=Unit.RefuelingSystem.BOOM_AND_RECEPTACLE`, `1=Unit.RefuelingSystem.PROBE_AND_DROGUE`.
-- @field #number noccupied Number of flights on this patrol point. -- @field #number noccupied Number of flights on this patrol point.
-- @field Wrapper.Marker#MARKER marker F10 marker. -- @field Wrapper.Marker#MARKER marker F10 marker.
-- @field #boolean IsZonePoint flag for using a (moving) zone as point for patrol etc.
-- @field Core.Zone#ZONE_BASE patrolzone in case Patrol coordinate was handed as zone, store here.
--- Patrol zone. --- Patrol zone.
-- @type AIRWING.PatrolZone -- @type AIRWING.PatrolZone
@ -187,13 +189,14 @@ AIRWING = {
--- AIRWING class version. --- AIRWING class version.
-- @field #string version -- @field #string version
AIRWING.version="0.9.6" AIRWING.version="0.9.7"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Check that airbase has enough parking spots if a request is BIG. -- TODO: Check that airbase has enough parking spots if a request is BIG.
-- DONE: Allow (moving) zones as base for patrol points.
-- DONE: Spawn in air ==> Needs WAREHOUSE update. -- DONE: Spawn in air ==> Needs WAREHOUSE update.
-- DONE: Spawn hot. -- DONE: Spawn hot.
-- DONE: Make special request to transfer squadrons to anther airwing (or warehouse). -- DONE: Make special request to transfer squadrons to anther airwing (or warehouse).
@ -807,13 +810,22 @@ function AIRWING:_PatrolPointMarkerText(point)
end end
--- Update marker of the patrol point. --- Update marker of the patrol point.
-- @param #AIRWING self
-- @param #AIRWING.PatrolData point Patrol point table. -- @param #AIRWING.PatrolData point Patrol point table.
function AIRWING:UpdatePatrolPointMarker(point) function AIRWING:UpdatePatrolPointMarker(point)
if self.markpoints then -- sometimes there's a direct call from #OPSGROUP
if self and self.markpoints then -- sometimes there's a direct call from #OPSGROUP
local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts",
point.type, point.noccupied, point.heading, point.leg, point.altitude, point.speed) point.type, point.noccupied, point.heading, point.leg, point.altitude, point.speed)
point.marker:UpdateText(text, 1) if point.IsZonePoint and point.IsZonePoint == true and point.patrolzone then
-- update position
local Coordinate = point.patrolzone:GetCoordinate()
point.marker:UpdateCoordinate(Coordinate)
point.marker:UpdateText(text, 1.5)
else
point.marker:UpdateText(text, 1)
end
end end
end end
@ -821,7 +833,7 @@ end
--- Create a new generic patrol point. --- Create a new generic patrol point.
-- @param #AIRWING self -- @param #AIRWING self
-- @param #string Type Patrol point type, e.g. "CAP" or "AWACS". Default "Unknown". -- @param #string Type Patrol point type, e.g. "CAP" or "AWACS". Default "Unknown".
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Default 10-15 NM away from the location of the airwing. -- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Default 10-15 NM away from the location of the airwing. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Orbit altitude in feet. Default random between Angels 10 and 20. -- @param #number Altitude Orbit altitude in feet. Default random between Angels 10 and 20.
-- @param #number Heading Heading in degrees. Default random (0, 360] degrees. -- @param #number Heading Heading in degrees. Default random (0, 360] degrees.
-- @param #number LegLength Length of race-track orbit in NM. Default 15 NM. -- @param #number LegLength Length of race-track orbit in NM. Default 15 NM.
@ -830,14 +842,16 @@ end
-- @return #AIRWING.PatrolData Patrol point table. -- @return #AIRWING.PatrolData Patrol point table.
function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem)
-- Check if a zone was passed instead of a coordinate.
if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE") then
Coordinate=Coordinate:GetCoordinate()
end
local patrolpoint={} --#AIRWING.PatrolData local patrolpoint={} --#AIRWING.PatrolData
patrolpoint.type=Type or "Unknown" patrolpoint.type=Type or "Unknown"
patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10, 15)), math.random(360)) patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10, 15)), math.random(360))
if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE") then
patrolpoint.IsZonePoint = true
patrolpoint.patrolzone = Coordinate
patrolpoint.coord = patrolpoint.patrolzone:GetCoordinate()
else
patrolpoint.IsZonePoint = false
end
patrolpoint.heading=Heading or math.random(360) patrolpoint.heading=Heading or math.random(360)
patrolpoint.leg=LegLength or 15 patrolpoint.leg=LegLength or 15
patrolpoint.altitude=Altitude or math.random(10,20)*1000 patrolpoint.altitude=Altitude or math.random(10,20)*1000
@ -847,7 +861,7 @@ function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegL
if self.markpoints then if self.markpoints then
patrolpoint.marker=MARKER:New(Coordinate, "New Patrol Point"):ToAll() patrolpoint.marker=MARKER:New(Coordinate, "New Patrol Point"):ToAll()
AIRWING.UpdatePatrolPointMarker(patrolpoint) self:UpdatePatrolPointMarker(patrolpoint)
end end
return patrolpoint return patrolpoint
@ -855,7 +869,7 @@ end
--- Add a patrol Point for CAP missions. --- Add a patrol Point for CAP missions.
-- @param #AIRWING self -- @param #AIRWING self
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. -- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Orbit altitude in feet. -- @param #number Altitude Orbit altitude in feet.
-- @param #number Speed Orbit speed in knots. -- @param #number Speed Orbit speed in knots.
-- @param #number Heading Heading in degrees. -- @param #number Heading Heading in degrees.
@ -872,7 +886,7 @@ end
--- Add a patrol Point for RECON missions. --- Add a patrol Point for RECON missions.
-- @param #AIRWING self -- @param #AIRWING self
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. -- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Orbit altitude in feet. -- @param #number Altitude Orbit altitude in feet.
-- @param #number Speed Orbit speed in knots. -- @param #number Speed Orbit speed in knots.
-- @param #number Heading Heading in degrees. -- @param #number Heading Heading in degrees.
@ -889,7 +903,7 @@ end
--- Add a patrol Point for TANKER missions. --- Add a patrol Point for TANKER missions.
-- @param #AIRWING self -- @param #AIRWING self
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. -- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Orbit altitude in feet. -- @param #number Altitude Orbit altitude in feet.
-- @param #number Speed Orbit speed in knots. -- @param #number Speed Orbit speed in knots.
-- @param #number Heading Heading in degrees. -- @param #number Heading Heading in degrees.
@ -907,7 +921,7 @@ end
--- Add a patrol Point for AWACS missions. --- Add a patrol Point for AWACS missions.
-- @param #AIRWING self -- @param #AIRWING self
-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. -- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Orbit altitude in feet. -- @param #number Altitude Orbit altitude in feet.
-- @param #number Speed Orbit speed in knots. -- @param #number Speed Orbit speed in knots.
-- @param #number Heading Heading in degrees. -- @param #number Heading Heading in degrees.
@ -974,6 +988,46 @@ function AIRWING:SetTakeoffAir()
return self return self
end end
--- Set the aircraft of the AirWing to land straight in.
-- @param #AIRWING self
-- @return #FLIGHTGROUP self
function AIRWING:SetLandingStraightIn()
self.OptionLandingStraightIn = true
return self
end
--- Set the aircraft of the AirWing to land in pairs for groups > 1 aircraft.
-- @param #AIRWING self
-- @return #AIRWING self
function AIRWING:SetLandingForcePair()
self.OptionLandingForcePair = true
return self
end
--- Set the aircraft of the AirWing to NOT land in pairs.
-- @param #AIRWING self
-- @return #AIRWING self
function AIRWING:SetLandingRestrictPair()
self.OptionLandingRestrictPair = true
return self
end
--- Set the aircraft of the AirWing to land after overhead break.
-- @param #AIRWING self
-- @return #AIRWING self
function AIRWING:SetLandingOverheadBreak()
self.OptionLandingOverheadBreak = true
return self
end
--- [Helicopter] Set the aircraft of the AirWing to prefer vertical takeoff and landing.
-- @param #AIRWING self
-- @return #AIRWING self
function AIRWING:SetOptionPreferVerticalLanding()
self.OptionPreferVerticalLanding = true
return self
end
--- Set despawn after landing. Aircraft will be despawned after the landing event. --- Set despawn after landing. Aircraft will be despawned after the landing event.
-- Can help to avoid DCS AI taxiing issues. -- Can help to avoid DCS AI taxiing issues.
-- @param #AIRWING self -- @param #AIRWING self
@ -1136,6 +1190,10 @@ function AIRWING:_GetPatrolData(PatrolPoints, RefuelSystem)
for _,_patrolpoint in pairs(PatrolPoints) do for _,_patrolpoint in pairs(PatrolPoints) do
local patrolpoint=_patrolpoint --#AIRWING.PatrolData local patrolpoint=_patrolpoint --#AIRWING.PatrolData
if patrolpoint.IsZonePoint and patrolpoint.IsZonePoint == true and patrolpoint.patrolzone then
-- update
patrolpoint.coord = patrolpoint.patrolzone:GetCoordinate()
end
if (RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem) or RefuelSystem==nil or patrolpoint.refuelsystem==nil then if (RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem) or RefuelSystem==nil or patrolpoint.refuelsystem==nil then
return patrolpoint return patrolpoint
end end
@ -1195,7 +1253,7 @@ function AIRWING:CheckCAP()
patrol.noccupied=patrol.noccupied+1 patrol.noccupied=patrol.noccupied+1
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
self:AddMission(missionCAP) self:AddMission(missionCAP)
@ -1247,7 +1305,7 @@ function AIRWING:CheckRECON()
patrol.noccupied=patrol.noccupied+1 patrol.noccupied=patrol.noccupied+1
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
self:AddMission(missionRECON) self:AddMission(missionRECON)
@ -1292,7 +1350,7 @@ function AIRWING:CheckTANKER()
patrol.noccupied=patrol.noccupied+1 patrol.noccupied=patrol.noccupied+1
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
self:AddMission(mission) self:AddMission(mission)
@ -1311,7 +1369,7 @@ function AIRWING:CheckTANKER()
patrol.noccupied=patrol.noccupied+1 patrol.noccupied=patrol.noccupied+1
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
self:AddMission(mission) self:AddMission(mission)
@ -1349,7 +1407,7 @@ function AIRWING:CheckAWACS()
patrol.noccupied=patrol.noccupied+1 patrol.noccupied=patrol.noccupied+1
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end if self.markpoints then self:UpdatePatrolPointMarker(patrol) end
self:AddMission(mission) self:AddMission(mission)
@ -1464,7 +1522,21 @@ function AIRWING:onafterFlightOnMission(From, Event, To, FlightGroup, Mission)
self:T(self.lid..string.format("Group %s on %s mission %s", FlightGroup:GetName(), Mission:GetType(), Mission:GetName())) self:T(self.lid..string.format("Group %s on %s mission %s", FlightGroup:GetName(), Mission:GetType(), Mission:GetName()))
if self.UseConnectedOpsAwacs and self.ConnectedOpsAwacs then if self.UseConnectedOpsAwacs and self.ConnectedOpsAwacs then
self.ConnectedOpsAwacs:__FlightOnMission(2,FlightGroup,Mission) self.ConnectedOpsAwacs:__FlightOnMission(2,FlightGroup,Mission)
end end
-- Landing Options
if self.OptionLandingForcePair then
FlightGroup:SetOptionLandingForcePair()
elseif self.OptionLandingOverheadBreak then
FlightGroup:SetOptionLandingOverheadBreak()
elseif self.OptionLandingRestrictPair then
FlightGroup:SetOptionLandingRestrictPair()
elseif self.OptionLandingStraightIn then
FlightGroup:SetOptionLandingStraightIn()
end
-- Landing Options Helo
if self.OptionPreferVerticalLanding then
FlightGroup:SetOptionPreferVertical()
end
end end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@ -397,6 +397,7 @@ AUFTRAG = {
conditionPush = {}, conditionPush = {},
conditionSuccessSet = false, conditionSuccessSet = false,
conditionFailureSet = false, conditionFailureSet = false,
repeatDelay = 1,
} }
--- Global mission counter. --- Global mission counter.
@ -1715,6 +1716,42 @@ function AUFTRAG:NewSEAD(Target, Altitude)
return mission return mission
end end
--- **[AIR]** Create a SEAD in Zone mission.
-- @param #AUFTRAG self
-- @param Core.Zone#ZONE TargetZone The target zone to attack.
-- @param #number Altitude Engage altitude in feet. Default 25000 ft.
-- @param #table TargetTypes Table of string of DCS known target types, defaults to {"Air Defence"}. See [DCS Target Attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes)
-- @param #number Duration Engage this much time when the AUFTRAG starts executing.
-- @return #AUFTRAG self
function AUFTRAG:NewSEADInZone(TargetZone, Altitude, TargetTypes, Duration)
local mission=AUFTRAG:New(AUFTRAG.Type.SEAD)
--mission:_TargetFromObject(TargetZone)
-- DCS Task options:
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000)
mission.engageZone = TargetZone
mission.engageTargetTypes = TargetTypes or {"Air Defence"}
-- Mission options:
mission.missionTask=ENUMS.MissionTask.SEAD
mission.missionAltitude=mission.engageAltitude
mission.missionFraction=0.2
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.EvadeFire
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
mission:SetDuration(Duration or 1800)
return mission
end
--- **[AIR]** Create a STRIKE mission. Flight will attack the closest map object to the specified coordinate. --- **[AIR]** Create a STRIKE mission. Flight will attack the closest map object to the specified coordinate.
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC, SET_GROUP, SET_UNIT, SET_STATIC or TARGET object. -- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC, SET_GROUP, SET_UNIT, SET_STATIC or TARGET object.
@ -1752,8 +1789,9 @@ end
-- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC, SET_GROUP, SET_UNIT, SET_STATIC or TARGET object. -- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC, SET_GROUP, SET_UNIT, SET_STATIC or TARGET object.
-- @param #number Altitude Engage altitude in feet. Default 25000 ft. -- @param #number Altitude Engage altitude in feet. Default 25000 ft.
-- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options. -- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options.
-- @param #boolean Divebomb If true, use a dive bombing attack approach.
-- @return #AUFTRAG self -- @return #AUFTRAG self
function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType) function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType, Divebomb)
local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING) local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING)
@ -1770,6 +1808,7 @@ function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType)
mission.missionFraction=0.5 mission.missionFraction=0.5
mission.optionROE=ENUMS.ROE.OpenFire mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.NoReaction -- No reaction is better. mission.optionROT=ENUMS.ROT.NoReaction -- No reaction is better.
mission.optionDivebomb = Divebomb or nil
-- Evaluate result after 5 min. We might need time until the bombs have dropped and targets have been detroyed. -- Evaluate result after 5 min. We might need time until the bombs have dropped and targets have been detroyed.
mission.dTevaluate=5*60 mission.dTevaluate=5*60
@ -2966,6 +3005,16 @@ function AUFTRAG:SetRepeat(Nrepeat)
return self return self
end end
--- **[LEGION, COMMANDER, CHIEF]** Set the repeat delay in seconds after a mission is successful/failed. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, FLEET) or higher level.
-- @param #AUFTRAG self
-- @param #number Nrepeat Repeat delay in seconds. Default 1.
-- @return #AUFTRAG self
function AUFTRAG:SetRepeatDelay(RepeatDelay)
self.repeatDelay = RepeatDelay
return self
end
--- **[LEGION, COMMANDER, CHIEF]** Set how many times the mission is repeated if it fails. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, FLEET) or higher level. --- **[LEGION, COMMANDER, CHIEF]** Set how many times the mission is repeated if it fails. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, FLEET) or higher level.
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @param #number Nrepeat Number of repeats. Default 0. -- @param #number Nrepeat Number of repeats. Default 0.
@ -4765,6 +4814,8 @@ end
-- @return #boolean If `true`, all groups are done with the mission. -- @return #boolean If `true`, all groups are done with the mission.
function AUFTRAG:CheckGroupsDone() function AUFTRAG:CheckGroupsDone()
local fsmState = self:GetState()
-- Check status of all OPS groups. -- Check status of all OPS groups.
for groupname,data in pairs(self.groupdata) do for groupname,data in pairs(self.groupdata) do
local groupdata=data --#AUFTRAG.GroupData local groupdata=data --#AUFTRAG.GroupData
@ -4823,9 +4874,9 @@ function AUFTRAG:CheckGroupsDone()
return true return true
end end
if (self:IsStarted() or self:IsExecuting()) and self:CountOpsGroups()>0 then if (self:IsStarted() or self:IsExecuting()) and (fsmState == AUFTRAG.Status.STARTED or fsmState == AUFTRAG.Status.EXECUTING) and self:CountOpsGroups()>0 then
self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!", self.status, self:GetState())) self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!", self.status, self:GetState()))
return true return false
end end
return true return true
@ -5165,7 +5216,7 @@ function AUFTRAG:onafterSuccess(From, Event, To)
-- Repeat mission. -- Repeat mission.
self:T(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, N)) self:T(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, N))
self:Repeat() self:__Repeat(self.repeatDelay)
else else
@ -5207,7 +5258,7 @@ function AUFTRAG:onafterFailed(From, Event, To)
-- Repeat mission. -- Repeat mission.
self:T(self.lid..string.format("Mission FAILED! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, N)) self:T(self.lid..string.format("Mission FAILED! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, N))
self:Repeat() self:__Repeat(self.repeatDelay)
else else
@ -6115,7 +6166,7 @@ function AUFTRAG:GetDCSMissionTask()
local coords = self.engageTarget:GetCoordinates() local coords = self.engageTarget:GetCoordinates()
for _, coord in pairs(coords) do for _, coord in pairs(coords) do
local DCStask = CONTROLLABLE.TaskBombing(nil, coord:GetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType) local DCStask = CONTROLLABLE.TaskBombing(nil, coord:GetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType, self.optionDivebomb)
table.insert(DCStasks, DCStask) table.insert(DCStasks, DCStask)
end end
@ -6330,7 +6381,7 @@ function AUFTRAG:GetDCSMissionTask()
local unit = _unit -- Wrapper.Unit#UNTI local unit = _unit -- Wrapper.Unit#UNTI
if unit and unit:IsAlive() and unit:HasSEAD() then if unit and unit:IsAlive() and unit:HasSEAD() then
self:T("Adding UNIT for SEAD: "..unit:GetName()) self:T("Adding UNIT for SEAD: "..unit:GetName())
local task = CONTROLLABLE.TaskAttackUnit(nil,unit,GroupAttack,AI.Task.WeaponExpend.ALL,1,Direction,self.engageAltitude,4161536) local task = CONTROLLABLE.TaskAttackUnit(nil,unit,GroupAttack,AI.Task.WeaponExpend.ALL,1,Direction,self.engageAltitude,2956984318)
table.insert(DCStasks, task) table.insert(DCStasks, task)
SeadUnitSet:AddUnit(unit) SeadUnitSet:AddUnit(unit)
end end

View File

@ -17,7 +17,7 @@
-- === -- ===
-- --
-- ### Author: **applevangelist** -- ### Author: **applevangelist**
-- @date Last Update Jan 2025 -- @date Last Update July 2025
-- @module Ops.AWACS -- @module Ops.AWACS
-- @image OPS_AWACS.jpg -- @image OPS_AWACS.jpg
@ -237,7 +237,7 @@ do
-- -- Callsign will be "Focus". We'll be a Angels 30, doing 300 knots, orbit leg to 88deg with a length of 25nm. -- -- Callsign will be "Focus". We'll be a Angels 30, doing 300 knots, orbit leg to 88deg with a length of 25nm.
-- testawacs:SetAwacsDetails(CALLSIGN.AWACS.Focus,1,30,300,88,25) -- testawacs:SetAwacsDetails(CALLSIGN.AWACS.Focus,1,30,300,88,25)
-- -- Set up SRS on port 5010 - change the below to your path and port -- -- Set up SRS on port 5010 - change the below to your path and port
-- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010) -- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio","female","en-GB",5010)
-- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "Red Border#ZONE_POLYGON" -- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "Red Border#ZONE_POLYGON"
-- testawacs:SetRejectionZone(ZONE:FindByName("Red Border")) -- testawacs:SetRejectionZone(ZONE:FindByName("Red Border"))
-- -- Our CAP flight will have the callsign "Ford", we want 4 AI planes, Time-On-Station is four hours, doing 300 kn IAS. -- -- Our CAP flight will have the callsign "Ford", we want 4 AI planes, Time-On-Station is four hours, doing 300 kn IAS.
@ -255,7 +255,7 @@ do
-- -- The CAP station zone is called "Fremont". We will be on 255 AM. Note the Orbit Zone is given as *nil* in the `New()`-Statement -- -- The CAP station zone is called "Fremont". We will be on 255 AM. Note the Orbit Zone is given as *nil* in the `New()`-Statement
-- local testawacs = AWACS:New("GCI Senaki",AwacsAW,"blue",AIRBASE.Caucasus.Senaki_Kolkhi,nil,ZONE:FindByName("Rock"),"Fremont",255,radio.modulation.AM ) -- local testawacs = AWACS:New("GCI Senaki",AwacsAW,"blue",AIRBASE.Caucasus.Senaki_Kolkhi,nil,ZONE:FindByName("Rock"),"Fremont",255,radio.modulation.AM )
-- -- Set up SRS on port 5010 - change the below to your path and port -- -- Set up SRS on port 5010 - change the below to your path and port
-- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010) -- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio","female","en-GB",5010)
-- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "Red Border#ZONE_POLYGON" -- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "Red Border#ZONE_POLYGON"
-- testawacs:SetRejectionZone(ZONE:FindByName("Red Border")) -- testawacs:SetRejectionZone(ZONE:FindByName("Red Border"))
-- -- Our CAP flight will have the callsign "Ford", we want 4 AI planes, Time-On-Station is four hours, doing 300 kn IAS. -- -- Our CAP flight will have the callsign "Ford", we want 4 AI planes, Time-On-Station is four hours, doing 300 kn IAS.
@ -509,7 +509,7 @@ do
-- @field #AWACS -- @field #AWACS
AWACS = { AWACS = {
ClassName = "AWACS", -- #string ClassName = "AWACS", -- #string
version = "0.2.71", -- #string version = "0.2.72", -- #string
lid = "", -- #string lid = "", -- #string
coalition = coalition.side.BLUE, -- #number coalition = coalition.side.BLUE, -- #number
coalitiontxt = "blue", -- #string coalitiontxt = "blue", -- #string
@ -1123,7 +1123,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station
self.EscortMissionReplacement = {} self.EscortMissionReplacement = {}
-- SRS -- SRS
self.PathToSRS = "C:\\Program Files\\DCS-SimpleRadio-Standalone" self.PathToSRS = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.Gender = "female" self.Gender = "female"
self.Culture = "en-GB" self.Culture = "en-GB"
self.Voice = nil self.Voice = nil
@ -1242,6 +1242,8 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station
self:AddTransition("*", "Intercept", "*") self:AddTransition("*", "Intercept", "*")
self:AddTransition("*", "InterceptSuccess", "*") self:AddTransition("*", "InterceptSuccess", "*")
self:AddTransition("*", "InterceptFailure", "*") self:AddTransition("*", "InterceptFailure", "*")
self:AddTransition("*", "VIDSuccess", "*")
self:AddTransition("*", "VIDFailure", "*")
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
@ -1365,18 +1367,38 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station
-- @param #string To To state. -- @param #string To To state.
--- On After "InterceptSuccess" event. Intercept successful. --- On After "InterceptSuccess" event. Intercept successful.
-- @function [parent=#AWACS] OnAfterIntercept -- @function [parent=#AWACS] OnAfterInterceptSuccess
-- @param #AWACS self -- @param #AWACS self
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
--- On After "InterceptFailure" event. Intercept failure. --- On After "InterceptFailure" event. Intercept failure.
-- @function [parent=#AWACS] OnAfterIntercept -- @function [parent=#AWACS] OnAfterInterceptFailure
-- @param #AWACS self -- @param #AWACS self
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
--- On After "VIDSuccess" event. Intercept successful.
-- @function [parent=#AWACS] OnAfterVIDSuccess
-- @param #AWACS self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number GID Managed group ID (Player)
-- @param Wrapper.Group#GROUP Group (Player) Group done the VID
-- @param #AWACS.ManagedContact Contact The contact that was VID'd
--- On After "VIDFailure" event. Intercept failure.
-- @function [parent=#AWACS] OnAfterVIDFailure
-- @param #AWACS self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number GID Managed group ID (Player)
-- @param Wrapper.Group#GROUP Group (Player) Group done the VID
-- @param #AWACS.ManagedContact Contact The contact that was VID'd
return self return self
end end
@ -2091,7 +2113,7 @@ end
--- [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. --- [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\\ExternalAudio"
-- @param #string Gender Defaults to "male" -- @param #string Gender Defaults to "male"
-- @param #string Culture Defaults to "en-US" -- @param #string Culture Defaults to "en-US"
-- @param #number Port Defaults to 5002 -- @param #number Port Defaults to 5002
@ -2104,7 +2126,7 @@ end
-- @return #AWACS self -- @return #AWACS self
function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Backend) function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Backend)
self:T(self.lid.."SetSRS") self:T(self.lid.."SetSRS")
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
self.Gender = Gender or MSRS.gender or "male" self.Gender = Gender or MSRS.gender or "male"
self.Culture = Culture or MSRS.culture or "en-US" self.Culture = Culture or MSRS.culture or "en-US"
self.Port = Port or MSRS.port or 5002 self.Port = Port or MSRS.port or 5002
@ -3263,12 +3285,14 @@ function AWACS:_VID(Group,Declaration)
local vidpos = self.gettext:GetEntry("VIDPOS",self.locale) local vidpos = self.gettext:GetEntry("VIDPOS",self.locale)
text = string.format(vidpos,Callsign,self.callsigntxt, Declaration) text = string.format(vidpos,Callsign,self.callsigntxt, Declaration)
self:T(text) self:T(text)
self:__VIDSuccess(3,GID,group,cluster)
else else
-- too far away -- too far away
self:T("Contact VID not close enough") self:T("Contact VID not close enough")
local vidneg = self.gettext:GetEntry("VIDNEG",self.locale) local vidneg = self.gettext:GetEntry("VIDNEG",self.locale)
text = string.format(vidneg,Callsign,self.callsigntxt) text = string.format(vidneg,Callsign,self.callsigntxt)
self:T(text) self:T(text)
self:__VIDFailure(3,GID,group,cluster)
end end
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true)
end end

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@
-- @module Ops.CTLD -- @module Ops.CTLD
-- @image OPS_CTLD.jpg -- @image OPS_CTLD.jpg
-- Last Update May 2025 -- Last Update July 2025
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -867,7 +867,9 @@ do
-- my_ctld.TroopUnloadDistHoverHook = 5 -- When hovering, unload troops this far behind the Chinook -- my_ctld.TroopUnloadDistHoverHook = 5 -- When hovering, unload troops this far behind the Chinook
-- my_ctld.showstockinmenuitems = false -- When set to true, the menu lines will also show the remaining items in stock (that is, if you set any), downside is that the menu for all will be build every 30 seconds anew. -- my_ctld.showstockinmenuitems = false -- When set to true, the menu lines will also show the remaining items in stock (that is, if you set any), downside is that the menu for all will be build every 30 seconds anew.
-- my_ctld.onestepmenu = false -- When set to true, the menu will create Drop and build, Get and load, Pack and remove, Pack and load, Pack. it will be a 1 step solution. -- my_ctld.onestepmenu = false -- When set to true, the menu will create Drop and build, Get and load, Pack and remove, Pack and load, Pack. it will be a 1 step solution.
-- -- my_ctld.VehicleMoveFormation = AI.Task.VehicleFormation.VEE -- When a group moves to a MOVE zone, then it takes this formation. Can be a table of formations, which are then randomly chosen. Defaults to "Vee".
-- my_ctld.validateAndRepositionUnits = false -- Uses Disposition and other logic to find better ground positions for ground units avoiding trees, water, roads, runways, map scenery, statics and other units in the area. (Default is false)
--
-- ## 2.1 CH-47 Chinook support -- ## 2.1 CH-47 Chinook support
-- --
-- The Chinook comes with the option to use the ground crew menu to load and unload cargo into the Helicopter itself for better immersion. As well, it can sling-load cargo from ground. The cargo you can actually **create** -- The Chinook comes with the option to use the ground crew menu to load and unload cargo into the Helicopter itself for better immersion. As well, it can sling-load cargo from ground. The cargo you can actually **create**
@ -1294,6 +1296,7 @@ CTLD = {
LoadedGroupsTable = {}, LoadedGroupsTable = {},
keeploadtable = true, keeploadtable = true,
allowCATransport = false, allowCATransport = false,
VehicleMoveFormation = AI.Task.VehicleFormation.VEE,
} }
------------------------------ ------------------------------
@ -1414,7 +1417,7 @@ CTLD.FixedWingTypes = {
--- CTLD class version. --- CTLD class version.
-- @field #string version -- @field #string version
CTLD.version="1.3.35" CTLD.version="1.3.37"
--- Instantiate a new CTLD. --- Instantiate a new CTLD.
-- @param #CTLD self -- @param #CTLD self
@ -1481,6 +1484,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event.
self:AddTransition("*", "CratesBuildStarted", "*") -- CTLD build event. self:AddTransition("*", "CratesBuildStarted", "*") -- CTLD build event.
self:AddTransition("*", "CratesRepairStarted", "*") -- CTLD repair event. self:AddTransition("*", "CratesRepairStarted", "*") -- CTLD repair event.
self:AddTransition("*", "CratesPacked", "*") -- CTLD repack event.
self:AddTransition("*", "HelicopterLost", "*") -- CTLD lost event. self:AddTransition("*", "HelicopterLost", "*") -- CTLD lost event.
self:AddTransition("*", "Load", "*") -- CTLD load event. self:AddTransition("*", "Load", "*") -- CTLD load event.
self:AddTransition("*", "Loaded", "*") -- CTLD load event. self:AddTransition("*", "Loaded", "*") -- CTLD load event.
@ -1553,13 +1557,17 @@ function CTLD:New(Coalition, Prefixes, Alias)
self.movetroopsdistance = 5000 self.movetroopsdistance = 5000
self.troopdropzoneradius = 100 self.troopdropzoneradius = 100
self.VehicleMoveFormation = AI.Task.VehicleFormation.VEE
-- added support Hercules Mod -- added support Hercules Mod
self.enableHercules = false -- deprecated self.enableHercules = false -- deprecated
self.enableFixedWing = false self.enableFixedWing = false
self.FixedMinAngels = 165 -- for troop/cargo drop via chute self.FixedMinAngels = 165 -- for troop/cargo drop via chute
self.FixedMaxAngels = 2000 -- for troop/cargo drop via chute self.FixedMaxAngels = 2000 -- for troop/cargo drop via chute
self.FixedMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps self.FixedMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps
self.validateAndRepositionUnits = false -- 280 kph or 150kn eq 77 mps
-- message suppression -- message suppression
self.suppressmessages = false self.suppressmessages = false
@ -1759,6 +1767,17 @@ function CTLD:New(Coalition, Prefixes, Alias)
-- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Unit#UNIT Unit Unit Object.
-- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired. -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired.
-- @return #CTLD self -- @return #CTLD self
--- FSM Function OnBeforeCratesPacked.
-- @function [parent=#CTLD] OnBeforeCratesPacked
-- @param #CTLD self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Group#GROUP Group Group Object.
-- @param Wrapper.Unit#UNIT Unit Unit Object.
-- @param #CTLD_CARGO Cargo Cargo crate that was repacked.
-- @return #CTLD self
--- FSM Function OnBeforeTroopsRTB. --- FSM Function OnBeforeTroopsRTB.
-- @function [parent=#CTLD] OnBeforeTroopsRTB -- @function [parent=#CTLD] OnBeforeTroopsRTB
@ -1846,6 +1865,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
-- @param #string To State. -- @param #string To State.
-- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Group#GROUP Group Group Object.
-- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Unit#UNIT Unit Unit Object.
-- @param CargoName The name of the cargo being built.
-- @return #CTLD self -- @return #CTLD self
--- FSM Function OnAfterCratesRepairStarted. Info event that a repair has been started. --- FSM Function OnAfterCratesRepairStarted. Info event that a repair has been started.
@ -1888,6 +1908,17 @@ function CTLD:New(Coalition, Prefixes, Alias)
-- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Unit#UNIT Unit Unit Object.
-- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired. -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired.
-- @return #CTLD self -- @return #CTLD self
--- FSM Function OnAfterCratesPacked.
-- @function [parent=#CTLD] OnAfterCratesPacked
-- @param #CTLD self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Group#GROUP Group Group Object.
-- @param Wrapper.Unit#UNIT Unit Unit Object.
-- @param #CTLD_CARGO Cargo Cargo crate that was repacked.
-- @return #CTLD self
--- FSM Function OnAfterTroopsRTB. --- FSM Function OnAfterTroopsRTB.
-- @function [parent=#CTLD] OnAfterTroopsRTB -- @function [parent=#CTLD] OnAfterTroopsRTB
@ -2827,8 +2858,12 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack)
if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end
-- altered heading logic -- altered heading logic
-- DONE: right standard deviation? -- DONE: right standard deviation?
rheading = UTILS.RandomGaussian(0,30,-90,90,100) if self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) then
rheading = math.fmod((heading + rheading), 360) rheading = math.random(20,60)
else
rheading = UTILS.RandomGaussian(0, 30, -90, 90, 100)
end
rheading=math.fmod((heading+rheading),360)
cratecoord = position:Translate(cratedistance,rheading) cratecoord = position:Translate(cratedistance,rheading)
else else
cratedistance = (row-1)*6 cratedistance = (row-1)*6
@ -3703,6 +3738,7 @@ function CTLD:_UnloadTroops(Group, Unit)
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
:InitDelayOff() :InitDelayOff()
:InitSetUnitAbsolutePositions(Positions) :InitSetUnitAbsolutePositions(Positions)
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
:SpawnFromVec2(randomcoord:GetVec2()) :SpawnFromVec2(randomcoord:GetVec2())
self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter],type) self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter],type)
@ -3965,7 +4001,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop)
local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop)
buildtimer:Start(self.buildtime) buildtimer:Start(self.buildtime)
self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group)
self:__CratesBuildStarted(1,Group,Unit) self:__CratesBuildStarted(1,Group,Unit,build.Name)
self:_RefreshDropTroopsMenu(Group,Unit) self:_RefreshDropTroopsMenu(Group,Unit)
else else
self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop) self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop)
@ -4007,6 +4043,7 @@ function CTLD:_PackCratesNearby(Group, Unit)
_Group:Destroy() -- if a match is found destroy the Wrapper.Group#GROUP near the player _Group:Destroy() -- if a match is found destroy the Wrapper.Group#GROUP near the player
self:_GetCrates(Group, Unit, _entry, nil, false, true) -- spawn the appropriate crates near the player self:_GetCrates(Group, Unit, _entry, nil, false, true) -- spawn the appropriate crates near the player
self:_RefreshLoadCratesMenu(Group,Unit) -- call the refresher to show the crates in the menu self:_RefreshLoadCratesMenu(Group,Unit) -- call the refresher to show the crates in the menu
self:__CratesPacked(1,Group,Unit,_entry)
return true return true
end end
end end
@ -4148,11 +4185,13 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,Mult
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
--:InitRandomizeUnits(true,20,2) --:InitRandomizeUnits(true,20,2)
:InitDelayOff() :InitDelayOff()
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
:SpawnFromVec2(randomcoord) :SpawnFromVec2(randomcoord)
else -- don't random position of e.g. SAM units build as FOB else -- don't random position of e.g. SAM units build as FOB
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
:InitDelayOff() :InitDelayOff()
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
:SpawnFromVec2(randomcoord) :SpawnFromVec2(randomcoord)
end end
@ -4168,6 +4207,17 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,Mult
return self return self
end end
--- (Internal) Function to get a vehicle formation for a moving group
-- @param #CTLD self
-- @return #string Formation
function CTLD:_GetVehicleFormation()
local VehicleMoveFormation = self.VehicleMoveFormation or AI.Task.VehicleFormation.VEE
if type(self.VehicleMoveFormation)=="table" then
VehicleMoveFormation = self.VehicleMoveFormation[math.random(1,#self.VehicleMoveFormation)]
end
return VehicleMoveFormation
end
--- (Internal) Function to move group to WP zone. --- (Internal) Function to move group to WP zone.
-- @param #CTLD self -- @param #CTLD self
-- @param Wrapper.Group#GROUP Group The Group to move. -- @param Wrapper.Group#GROUP Group The Group to move.
@ -4182,18 +4232,20 @@ function CTLD:_MoveGroupToZone(Group)
-- yes, we can ;) -- yes, we can ;)
local groupname = Group:GetName() local groupname = Group:GetName()
local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE
local coordinate = zonecoord:GetVec2() local formation = self:_GetVehicleFormation()
--local coordinate = zonecoord:GetVec2()
Group:SetAIOn() Group:SetAIOn()
Group:OptionAlarmStateAuto() Group:OptionAlarmStateAuto()
Group:OptionDisperseOnAttack(30) Group:OptionDisperseOnAttack(30)
Group:OptionROEOpenFirePossible() Group:OptionROEOpenFire()
Group:RouteToVec2(coordinate,5) Group:RouteGroundTo(zonecoord,25,formation)
end end
return self return self
end end
--- (Internal) Housekeeping - Cleanup crates when build --- (Internal) Housekeeping - Cleanup crates when build
-- @param #CTLD self -- @param #CTLD self
--
-- @param #table Crates Table of #CTLD_CARGO objects near the unit. -- @param #table Crates Table of #CTLD_CARGO objects near the unit.
-- @param #CTLD.Buildable Build Table build object. -- @param #CTLD.Buildable Build Table build object.
-- @param #number Number Number of objects in Crates (found) to limit search. -- @param #number Number Number of objects in Crates (found) to limit search.
@ -5165,6 +5217,7 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID)
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias)
:InitDelayOff() :InitDelayOff()
:InitSetUnitAbsolutePositions(Positions) :InitSetUnitAbsolutePositions(Positions)
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
:SpawnFromVec2(randomcoord:GetVec2()) :SpawnFromVec2(randomcoord:GetVec2())
self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter], cType) self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter], cType)
@ -7105,6 +7158,16 @@ end
local filepath = self.filepath local filepath = self.filepath
self:__Save(interval,filepath,filename) self:__Save(interval,filepath,filename)
end end
if type(self.VehicleMoveFormation) == "table" then
local Formations = {}
for _,_formation in pairs(self.VehicleMoveFormation) do
table.insert(Formations,_formation)
end
self.VehicleMoveFormation = nil
self.VehicleMoveFormation = Formations
end
return self return self
end end

View File

@ -136,6 +136,7 @@ COMMANDER = {
awacsZones = {}, awacsZones = {},
tankerZones = {}, tankerZones = {},
limitMission = {}, limitMission = {},
maxMissionsAssignPerCycle = 1,
} }
--- COMMANDER class version. --- COMMANDER class version.
@ -1535,6 +1536,8 @@ function COMMANDER:CheckMissionQueue()
end end
end end
local missionsAssigned = 0
-- Loop over missions in queue. -- Loop over missions in queue.
for _,_mission in pairs(self.missionqueue) do for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG local mission=_mission --Ops.Auftrag#AUFTRAG
@ -1594,9 +1597,12 @@ function COMMANDER:CheckMissionQueue()
-- Recruited assets but no requested escort available. Unrecruit assets! -- Recruited assets but no requested escort available. Unrecruit assets!
LEGION.UnRecruitAssets(assets, mission) LEGION.UnRecruitAssets(assets, mission)
end end
-- Only ONE mission is assigned. missionsAssigned = missionsAssigned + 1
return if missionsAssigned >= (self.maxMissionsAssignPerCycle or 1) then
return
end
end end
else else
@ -1611,6 +1617,16 @@ function COMMANDER:CheckMissionQueue()
end end
--- Set how many missions can be assigned in a single status iteration. (eg. This is useful for persistent missions where you need to load all AUFTRAGs on mission start and then change it back to default)
--- Warning: Increasing this value will increase the number of missions started per iteration and thus may lead to performance issues if too many missions are started at once.
-- @param #COMMANDER self
-- @param #number Number of missions assigned per status iteration. Default is 1.
-- @return #COMMANDER self.
function COMMANDER:SetMaxMissionsAssignPerCycle(MaxMissionsAssignPerCycle)
self.maxMissionsAssignPerCycle = MaxMissionsAssignPerCycle or 1
return self
end
--- Get cohorts. --- Get cohorts.
-- @param #COMMANDER self -- @param #COMMANDER self
-- @param #table Legions Special legions. -- @param #table Legions Special legions.
@ -1670,9 +1686,12 @@ function COMMANDER:_GetCohorts(Legions, Cohorts, Operation)
for _,_legion in pairs(Legions or {}) do for _,_legion in pairs(Legions or {}) do
local legion=_legion --Ops.Legion#LEGION local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational. -- Check that runway is operational.
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true local Runway=true
if legion:IsAirwing() then
Runway=legion:IsRunwayOperational() and legion.airbase and legion.airbase:GetCoalition() == legion:GetCoalition()
end
-- Legion has to be running. -- Legion has to be running.
if legion:IsRunning() and Runway then if legion:IsRunning() and Runway then
@ -1703,9 +1722,12 @@ function COMMANDER:_GetCohorts(Legions, Cohorts, Operation)
for _,_legion in pairs(self.legions) do for _,_legion in pairs(self.legions) do
local legion=_legion --Ops.Legion#LEGION local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational. -- Check that runway is operational.
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true local Runway=true
if legion:IsAirwing() then
Runway=legion:IsRunwayOperational() and legion.airbase and legion.airbase:GetCoalition() == legion:GetCoalition()
end
-- Legion has to be running. -- Legion has to be running.
if legion:IsRunning() and Runway then if legion:IsRunning() and Runway then

View File

@ -1,13 +1,18 @@
------------------------------------------------------------------------- -------------------------------------------------------------------------
-- Easy CAP/GCI Class, based on OPS classes -- Easy CAP/GCI Class, based on OPS classes
------------------------------------------------------------------------- -------------------------------------------------------------------------
-- Documentation --
-- ## Documentation:
-- --
-- https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Ops.EasyGCICAP.html -- https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Ops.EasyGCICAP.html
-- --
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/EasyGCICAP).
--
------------------------------------------------------------------------- -------------------------------------------------------------------------
-- Date: September 2023 -- Date: September 2023
-- Last Update: July 2024 -- Last Update: Aug 2025
------------------------------------------------------------------------- -------------------------------------------------------------------------
-- --
--- **Ops** - Easy GCI & CAP Manager --- **Ops** - Easy GCI & CAP Manager
@ -71,6 +76,9 @@
-- @field #boolean DespawnAfterHolding -- @field #boolean DespawnAfterHolding
-- @field #list<Ops.Auftrag#AUFTRAG> ListOfAuftrag -- @field #list<Ops.Auftrag#AUFTRAG> ListOfAuftrag
-- @field #string defaulttakeofftype Take off type -- @field #string defaulttakeofftype Take off type
-- @field #number FuelLowThreshold
-- @field #number FuelCriticalThreshold
-- @field #boolean showpatrolpointmarks
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown. --- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown.
@ -226,6 +234,9 @@ EASYGCICAP = {
DespawnAfterHolding = true, DespawnAfterHolding = true,
ListOfAuftrag = {}, ListOfAuftrag = {},
defaulttakeofftype = "hot", defaulttakeofftype = "hot",
FuelLowThreshold = 25,
FuelCriticalThreshold = 10,
showpatrolpointmarks = false,
} }
--- Internal Squadron data type --- Internal Squadron data type
@ -258,10 +269,11 @@ EASYGCICAP = {
-- @field #number Speed -- @field #number Speed
-- @field #number Heading -- @field #number Heading
-- @field #number LegLength -- @field #number LegLength
-- @field Core.Zone#ZONE_BASE Zone
--- EASYGCICAP class version. --- EASYGCICAP class version.
-- @field #string version -- @field #string version
EASYGCICAP.version="0.1.23" EASYGCICAP.version="0.1.27"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@ -315,6 +327,9 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
self.DespawnAfterHolding = true self.DespawnAfterHolding = true
self.ListOfAuftrag = {} self.ListOfAuftrag = {}
self.defaulttakeofftype = "hot" self.defaulttakeofftype = "hot"
self.FuelLowThreshold = 25
self.FuelCriticalThreshold = 10
self.showpatrolpointmarks = false
-- Set some string id for output to DCS.log file. -- Set some string id for output to DCS.log file.
self.lid=string.format("EASYGCICAP %s | ", self.alias) self.lid=string.format("EASYGCICAP %s | ", self.alias)
@ -339,6 +354,63 @@ end
-- Functions -- Functions
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- Get a specific managed AirWing by name
-- @param #EASYGCICAP self
-- @param #string AirbaseName Airbase name of the home of this wing.
-- @return Ops.AirWing#AIRWING Airwing or nil if not found
function EASYGCICAP:GetAirwing(AirbaseName)
self:T(self.lid.."GetAirwing")
if self.wings[AirbaseName] then
return self.wings[AirbaseName][1]
end
return nil
end
--- Get a table of all managed AirWings
-- @param #EASYGCICAP self
-- @return #table Table of Ops.AirWing#AIRWING Airwings
function EASYGCICAP:GetAirwingTable()
self:T(self.lid.."GetAirwingTable")
local Wingtable = {}
for _,_object in pairs(self.wings or {}) do
table.insert(Wingtable,_object[1])
end
return Wingtable
end
--- Set "fuel low" threshold for CAP and INTERCEPT flights.
-- @param #EASYGCICAP self
-- @param #number Percent RTB if fuel at this percent. Values: 1..100, defaults to 25.
-- @return #EASYGCICAP self
function EASYGCICAP:SetFuelLow(Percent)
self:T(self.lid.."SetFuelLow")
self.FuelLowThreshold = Percent or 25
return self
end
--- Set markers on the map for Patrol Points.
-- @param #EASYGCICAP self
-- @param #boolean onoff Set to true to switch markers on.
-- @return #EASYGCICAP self
function EASYGCICAP:ShowPatrolPointMarkers(onoff)
if onoff then
self.showpatrolpointmarks = true
else
self.showpatrolpointmarks = false
end
return self
end
--- Set "fuel critical" threshold for CAP and INTERCEPT flights.
-- @param #EASYGCICAP self
-- @param #number Percent RTB if fuel at this percent. Values: 1..100, defaults to 10.
-- @return #EASYGCICAP self
function EASYGCICAP:SetFuelCritical(Percent)
self:T(self.lid.."SetFuelCritical")
self.FuelCriticalThreshold = Percent or 10
return self
end
--- Set CAP formation. --- Set CAP formation.
-- @param #EASYGCICAP self -- @param #EASYGCICAP self
-- @param #number Formation Formation to fly, defaults to ENUMS.Formation.FixedWing.FingerFour.Group -- @param #number Formation Formation to fly, defaults to ENUMS.Formation.FixedWing.FingerFour.Group
@ -359,7 +431,7 @@ function EASYGCICAP:SetTankerAndAWACSInvisible(Switch)
return self return self
end end
--- Count alive missions in our internal stack. --- (internal) Count alive missions in our internal stack.
-- @param #EASYGCICAP self -- @param #EASYGCICAP self
-- @return #number count -- @return #number count
function EASYGCICAP:_CountAliveAuftrags() function EASYGCICAP:_CountAliveAuftrags()
@ -583,7 +655,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
local DespawnAfterHolding = self.DespawnAfterHolding local DespawnAfterHolding = self.DespawnAfterHolding
-- Check STATIC name -- Check STATIC name
local check = STATIC:FindByName(Airbasename,false) local check = STATIC:FindByName(Airbasename,false) or UNIT:FindByName(Airbasename)
if check == nil then if check == nil then
MESSAGE:New(self.lid.."There's no warehouse static on the map (wrong naming?) for airbase "..tostring(Airbasename).."!",30,"CHECK"):ToAllIf(self.debug):ToLog() MESSAGE:New(self.lid.."There's no warehouse static on the map (wrong naming?) for airbase "..tostring(Airbasename).."!",30,"CHECK"):ToAllIf(self.debug):ToLog()
return return
@ -598,6 +670,10 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
CAP_Wing:SetRespawnAfterDestroyed() CAP_Wing:SetRespawnAfterDestroyed()
CAP_Wing:SetNumberCAP(self.capgrouping) CAP_Wing:SetNumberCAP(self.capgrouping)
CAP_Wing:SetCapCloseRaceTrack(true) CAP_Wing:SetCapCloseRaceTrack(true)
if self.showpatrolpointmarks then
CAP_Wing:ShowPatrolPointMarkers(true)
end
if self.capOptionVaryStartTime then if self.capOptionVaryStartTime then
CAP_Wing:SetCapStartTimeVariation(self.capOptionVaryStartTime,self.capOptionVaryEndTime) CAP_Wing:SetCapStartTimeVariation(self.capOptionVaryStartTime,self.capOptionVaryEndTime)
@ -628,6 +704,8 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
local engagerange = self.engagerange local engagerange = self.engagerange
local GoZoneSet = self.GoZoneSet local GoZoneSet = self.GoZoneSet
local NoGoZoneSet = self.NoGoZoneSet local NoGoZoneSet = self.NoGoZoneSet
local FuelLow = self.FuelLowThreshold or 25
local FuelCritical = self.FuelCriticalThreshold or 10
function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission) function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission)
local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP
@ -639,10 +717,15 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
flightgroup:SetDestinationbase(AIRBASE:FindByName(Airbasename)) flightgroup:SetDestinationbase(AIRBASE:FindByName(Airbasename))
flightgroup:GetGroup():CommandEPLRS(true,5) flightgroup:GetGroup():CommandEPLRS(true,5)
flightgroup:GetGroup():SetOptionRadarUsingForContinousSearch() flightgroup:GetGroup():SetOptionRadarUsingForContinousSearch()
flightgroup:GetGroup():SetOptionLandingOverheadBreak()
if Mission.type ~= AUFTRAG.Type.TANKER and Mission.type ~= AUFTRAG.Type.AWACS and Mission.type ~= AUFTRAG.Type.RECON then if Mission.type ~= AUFTRAG.Type.TANKER and Mission.type ~= AUFTRAG.Type.AWACS and Mission.type ~= AUFTRAG.Type.RECON then
flightgroup:SetDetection(true) flightgroup:SetDetection(true)
flightgroup:SetEngageDetectedOn(engagerange,{"Air"},GoZoneSet,NoGoZoneSet) flightgroup:SetEngageDetectedOn(engagerange,{"Air"},GoZoneSet,NoGoZoneSet)
flightgroup:SetOutOfAAMRTB() flightgroup:SetOutOfAAMRTB()
flightgroup:SetFuelLowRTB(true)
flightgroup:SetFuelLowThreshold(FuelLow)
flightgroup:SetFuelCriticalRTB(true)
flightgroup:SetFuelCriticalThreshold(FuelCritical)
if CapFormation then if CapFormation then
flightgroup:GetGroup():SetOption(AI.Option.Air.id.FORMATION,CapFormation) flightgroup:GetGroup():SetOption(AI.Option.Air.id.FORMATION,CapFormation)
end end
@ -681,24 +764,30 @@ end
--- Add a CAP patrol point to a Wing --- Add a CAP patrol point to a Wing
-- @param #EASYGCICAP self -- @param #EASYGCICAP self
-- @param #string AirbaseName Name of the Wing's airbase -- @param #string AirbaseName Name of the Wing's airbase
-- @param Core.Point#COORDINATE Coordinate. -- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Defaults to 25000 feet ASL. -- @param #number Altitude Defaults to 25000 feet ASL.
-- @param #number Speed Defaults to 300 knots TAS. -- @param #number Speed Defaults to 300 knots TAS.
-- @param #number Heading Defaults to 90 degrees (East). -- @param #number Heading Defaults to 90 degrees (East).
-- @param #number LegLength Defaults to 15 NM. -- @param #number LegLength Defaults to 15 NM.
-- @return #EASYGCICAP self -- @return #EASYGCICAP self
function EASYGCICAP:AddPatrolPointCAP(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) function EASYGCICAP:AddPatrolPointCAP(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
self:T(self.lid.."AddPatrolPointCAP "..Coordinate:ToStringLLDDM()) self:T(self.lid.."AddPatrolPointCAP")--..Coordinate:ToStringLLDDM())
local EntryCAP = {} -- #EASYGCICAP.CapPoint local coordinate = Coordinate
local EntryCAP = {} -- #EASYGCICAP.CapPoint
if Coordinate:IsInstanceOf("ZONE_BASE") then
-- adjust coordinate and get the coordinate from the zone
coordinate = Coordinate:GetCoordinate()
EntryCAP.Zone = Coordinate
end
EntryCAP.AirbaseName = AirbaseName EntryCAP.AirbaseName = AirbaseName
EntryCAP.Coordinate = Coordinate EntryCAP.Coordinate = coordinate
EntryCAP.Altitude = Altitude or 25000 EntryCAP.Altitude = Altitude or 25000
EntryCAP.Speed = Speed or 300 EntryCAP.Speed = Speed or 300
EntryCAP.Heading = Heading or 90 EntryCAP.Heading = Heading or 90
EntryCAP.LegLength = LegLength or 15 EntryCAP.LegLength = LegLength or 15
self.ManagedCP[#self.ManagedCP+1] = EntryCAP self.ManagedCP[#self.ManagedCP+1] = EntryCAP
if self.debug then if self.debug then
local mark = MARKER:New(Coordinate,self.lid.."Patrol Point"):ToAll() local mark = MARKER:New(coordinate,self.lid.."Patrol Point"):ToAll()
end end
return self return self
end end
@ -706,7 +795,7 @@ end
--- Add a RECON patrol point to a Wing --- Add a RECON patrol point to a Wing
-- @param #EASYGCICAP self -- @param #EASYGCICAP self
-- @param #string AirbaseName Name of the Wing's airbase -- @param #string AirbaseName Name of the Wing's airbase
-- @param Core.Point#COORDINATE Coordinate. -- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Defaults to 25000 feet. -- @param #number Altitude Defaults to 25000 feet.
-- @param #number Speed Defaults to 300 knots. -- @param #number Speed Defaults to 300 knots.
-- @param #number Heading Defaults to 90 degrees (East). -- @param #number Heading Defaults to 90 degrees (East).
@ -731,7 +820,7 @@ end
--- Add a TANKER patrol point to a Wing --- Add a TANKER patrol point to a Wing
-- @param #EASYGCICAP self -- @param #EASYGCICAP self
-- @param #string AirbaseName Name of the Wing's airbase -- @param #string AirbaseName Name of the Wing's airbase
-- @param Core.Point#COORDINATE Coordinate. -- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Defaults to 25000 feet. -- @param #number Altitude Defaults to 25000 feet.
-- @param #number Speed Defaults to 300 knots. -- @param #number Speed Defaults to 300 knots.
-- @param #number Heading Defaults to 90 degrees (East). -- @param #number Heading Defaults to 90 degrees (East).
@ -756,7 +845,7 @@ end
--- Add an AWACS patrol point to a Wing --- Add an AWACS patrol point to a Wing
-- @param #EASYGCICAP self -- @param #EASYGCICAP self
-- @param #string AirbaseName Name of the Wing's airbase -- @param #string AirbaseName Name of the Wing's airbase
-- @param Core.Point#COORDINATE Coordinate. -- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone).
-- @param #number Altitude Defaults to 25000 feet. -- @param #number Altitude Defaults to 25000 feet.
-- @param #number Speed Defaults to 300 knots. -- @param #number Speed Defaults to 300 knots.
-- @param #number Heading Defaults to 90 degrees (East). -- @param #number Heading Defaults to 90 degrees (East).
@ -844,7 +933,12 @@ function EASYGCICAP:_SetCAPPatrolPoints()
local Speed = data.Speed local Speed = data.Speed
local Heading = data.Heading local Heading = data.Heading
local LegLength = data.LegLength local LegLength = data.LegLength
Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength) local Zone = _data.Zone
if Zone then
Wing:AddPatrolPointCAP(Zone,Altitude,Speed,Heading,LegLength)
else
Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength)
end
end end
return self return self
@ -910,7 +1004,7 @@ end
-- @param #string SquadName Squadron name - must be unique! -- @param #string SquadName Squadron name - must be unique!
-- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi -- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi
-- @param #number AirFrames Number of available airframes, e.g. 20. -- @param #number AirFrames Number of available airframes, e.g. 20.
-- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE -- @param #string Skill (optional) Skill level, e.g. AI.Skill.AVERAGE
-- @param #string Modex (optional) Modex to be used,e.g. 402. -- @param #string Modex (optional) Modex to be used,e.g. 402.
-- @param #string Livery (optional) Livery name to be used. -- @param #string Livery (optional) Livery name to be used.
-- @return #EASYGCICAP self -- @return #EASYGCICAP self
@ -1199,19 +1293,19 @@ end
-- @return #boolean assigned -- @return #boolean assigned
-- @return #number leftover -- @return #number leftover
function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize) function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize)
self:I("_TryAssignIntercept for size "..WingSize or 1) self:T("_TryAssignIntercept for size "..WingSize or 1)
local assigned = false local assigned = false
local wingsize = WingSize or 1 local wingsize = WingSize or 1
local mindist = 0 local mindist = 0
local disttable = {} local disttable = {}
if Group and Group:IsAlive() then if Group and Group:IsAlive() then
local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0) local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0)
self:I(self.lid..string.format("Assignment for %s",Group:GetName())) self:T(self.lid..string.format("Assignment for %s",Group:GetName()))
for _name,_FG in pairs(ReadyFlightGroups or {}) do for _name,_FG in pairs(ReadyFlightGroups or {}) do
local FG = _FG -- Ops.FlightGroup#FLIGHTGROUP local FG = _FG -- Ops.FlightGroup#FLIGHTGROUP
local fcoord = FG:GetCoordinate() local fcoord = FG:GetCoordinate()
local dist = math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1)) local dist = math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1))
self:I(self.lid..string.format("FG %s Distance %dkm",_name,dist)) self:T(self.lid..string.format("FG %s Distance %dkm",_name,dist))
disttable[#disttable+1] = { FG=FG, dist=dist} disttable[#disttable+1] = { FG=FG, dist=dist}
if dist>mindist then mindist=dist end if dist>mindist then mindist=dist end
end end
@ -1228,7 +1322,7 @@ function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group
local cm = FG:GetMissionCurrent() local cm = FG:GetMissionCurrent()
if cm then cm:Cancel() end if cm then cm:Cancel() end
wingsize = wingsize - 1 wingsize = wingsize - 1
self:I(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist)) self:T(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist))
if wingsize == 0 then if wingsize == 0 then
assigned = true assigned = true
break break
@ -1258,7 +1352,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
local conflictzoneset = self.ConflictZoneSet local conflictzoneset = self.ConflictZoneSet
local ReadyFlightGroups = self.ReadyFlightGroups local ReadyFlightGroups = self.ReadyFlightGroups
-- Aircraft? -- Aircraft?
if Cluster.ctype ~= INTEL.Ctype.AIRCRAFT then return end if Cluster.ctype ~= INTEL.Ctype.AIRCRAFT then return end
-- Threatlevel 0..10 -- Threatlevel 0..10
local contact = self.Intel:GetHighestThreatContact(Cluster) local contact = self.Intel:GetHighestThreatContact(Cluster)
@ -1303,6 +1397,10 @@ function EASYGCICAP:_AssignIntercept(Cluster)
local data = _data -- #EASYGCICAP.CapPoint local data = _data -- #EASYGCICAP.CapPoint
local name = data.AirbaseName local name = data.AirbaseName
local zonecoord = data.Coordinate local zonecoord = data.Coordinate
if data.Zone then
-- refresh coordinate in case we have a (moving) zone
zonecoord = data.Zone:GetCoordinate()
end
local airwing = wings[name][1] local airwing = wings[name][1]
local coa = AIRBASE:FindByName(name):GetCoalition() local coa = AIRBASE:FindByName(name):GetCoalition()
local samecoalitionab = coa == self.coalition and true or false local samecoalitionab = coa == self.coalition and true or false
@ -1404,7 +1502,7 @@ function EASYGCICAP:_StartIntel()
end end
------------------------------------------------------------------------- -------------------------------------------------------------------------
-- FSM Functions -- TODO FSM Functions
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- (Internal) FSM Function onafterStart --- (Internal) FSM Function onafterStart
@ -1500,7 +1598,7 @@ function EASYGCICAP:onafterStatus(From,Event,To)
local engage = FG:IsEngaging() local engage = FG:IsEngaging()
local hasmissiles = FG:IsOutOfMissiles() == nil and true or false local hasmissiles = FG:IsOutOfMissiles() == nil and true or false
local ready = hasmissiles and FG:IsFuelGood() and FG:IsAirborne() local ready = hasmissiles and FG:IsFuelGood() and FG:IsAirborne()
--self:I(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready))) --self:T(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready)))
if ready then if ready then
self.ReadyFlightGroups[name] = FG self.ReadyFlightGroups[name] = FG
end end
@ -1535,5 +1633,8 @@ end
function EASYGCICAP:onafterStop(From,Event,To) function EASYGCICAP:onafterStop(From,Event,To)
self:T({From,Event,To}) self:T({From,Event,To})
self.Intel:Stop() self.Intel:Stop()
for _,_wing in pairs(self.wings or {}) do
_wing:Stop()
end
return self return self
end end

View File

@ -2464,7 +2464,7 @@ end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Payer Menu -- Player Menu
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create player menu. --- Create player menu.

View File

@ -779,6 +779,61 @@ function FLIGHTGROUP:SetJettisonWeapons(Switch)
return self return self
end end
--- Set the aircraft to land straight in.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetOptionLandingStraightIn()
self.OptionLandingStraightIn = true
if self:GetGroup():IsAlive() then
self:GetGroup():SetOptionLandingStraightIn()
end
return self
end
--- Set the aircraft to land in pairs.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetOptionLandingForcePair()
self.OptionLandingForcePair = true
if self:GetGroup():IsAlive() then
self:GetGroup():SetOptionLandingForcePair()
end
return self
end
--- Set the aircraft to NOT land in pairs.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetOptionLandingRestrictPair()
self.OptionLandingRestrictPair = true
if self:GetGroup():IsAlive() then
self:GetGroup():SetOptionLandingRestrictPair()
end
return self
end
--- Set the aircraft to land after overhead break.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetOptionLandingOverheadBreak()
self.OptionLandingOverheadBreak = true
if self:GetGroup():IsAlive() then
self:GetGroup():SetOptionLandingOverheadBreak()
end
return self
end
--- [HELICOPTER] Set the aircraft to prefer takeoff and landing vertically.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetOptionPreferVertical()
self.OptionPreferVertical = true
if self:GetGroup():IsAlive() then
self:GetGroup():OptionPreferVerticalLanding()
end
return self
end
--- Set if group is ready for taxi/takeoff if controlled by a `FLIGHTCONTROL`. --- Set if group is ready for taxi/takeoff if controlled by a `FLIGHTCONTROL`.
-- @param #FLIGHTGROUP self -- @param #FLIGHTGROUP self
-- @param #boolean ReadyTO If `true`, flight is ready for takeoff. -- @param #boolean ReadyTO If `true`, flight is ready for takeoff.
@ -3079,7 +3134,7 @@ function FLIGHTGROUP:onbeforeLandAtAirbase(From, Event, To, airbase)
local Tsuspend=nil local Tsuspend=nil
if airbase==nil then if airbase==nil then
self:T(self.lid.."ERROR: Airbase is nil in LandAtAirase() call!") self:T(self.lid.."ERROR: Airbase is nil in LandAtAirbase() call!")
allowed=false allowed=false
end end
@ -4497,6 +4552,11 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase)
-- Airbase. -- Airbase.
airbase=airbase or self:GetClosestAirbase() airbase=airbase or self:GetClosestAirbase()
if airbase == nil then
self:T(self.lid.."No airbase found for element "..element.name)
return nil
end
-- Parking table of airbase. -- Parking table of airbase.
local parking=airbase.parking --:GetParkingSpotsTable() local parking=airbase.parking --:GetParkingSpotsTable()
@ -4607,10 +4667,12 @@ function FLIGHTGROUP:GetParking(airbase)
local coords={} local coords={}
for clientname, client in pairs(clients) do for clientname, client in pairs(clients) do
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname) local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
local units=template.units if template then
for i,unit in pairs(units) do local units=template.units
local coord=COORDINATE:New(unit.x, unit.alt, unit.y) for i,unit in pairs(units) do
coords[unit.name]=coord local coord=COORDINATE:New(unit.x, unit.alt, unit.y)
coords[unit.name]=coord
end
end end
end end
return coords return coords

View File

@ -2513,9 +2513,12 @@ function LEGION._GetCohorts(Legions, Cohorts, Operation, OpsQueue)
for _,_legion in pairs(Legions or {}) do for _,_legion in pairs(Legions or {}) do
local legion=_legion --Ops.Legion#LEGION local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational. -- Check that runway is operational.
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true local Runway=true
if legion:IsAirwing() then
Runway=legion:IsRunwayOperational() and legion.airbase and legion.airbase:GetCoalition() == legion:GetCoalition()
end
-- Legion has to be running. -- Legion has to be running.
if legion:IsRunning() and Runway then if legion:IsRunning() and Runway then

View File

@ -5730,7 +5730,7 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission)
-- Decrease patrol data. -- Decrease patrol data.
if Mission.patroldata then if Mission.patroldata then
Mission.patroldata.noccupied=Mission.patroldata.noccupied-1 Mission.patroldata.noccupied=Mission.patroldata.noccupied-1
AIRWING.UpdatePatrolPointMarker(Mission.patroldata) AIRWING.UpdatePatrolPointMarker(self,Mission.patroldata)
end end
-- Switch auto engage detected off. This IGNORES that engage detected had been activated for the group! -- Switch auto engage detected off. This IGNORES that engage detected had been activated for the group!

View File

@ -1544,7 +1544,7 @@ end
-- @param #PLAYERRECCE self -- @param #PLAYERRECCE 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!
-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
-- @param #string Gender (Optional) Defaults to "male" -- @param #string Gender (Optional) Defaults to "male"
-- @param #string Culture (Optional) Defaults to "en-US" -- @param #string Culture (Optional) Defaults to "en-US"
-- @param #number Port (Optional) Defaults to 5002 -- @param #number Port (Optional) Defaults to 5002
@ -1556,7 +1556,7 @@ end
-- @return #PLAYERRECCE self -- @return #PLAYERRECCE self
function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend) function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend)
self:T(self.lid.."SetSRS") self:T(self.lid.."SetSRS")
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" --
self.Gender = Gender or MSRS.gender or "male" -- self.Gender = Gender or MSRS.gender or "male" --
self.Culture = Culture or MSRS.culture or "en-US" -- self.Culture = Culture or MSRS.culture or "en-US" --
self.Port = Port or MSRS.port or 5002 -- self.Port = Port or MSRS.port or 5002 --

View File

@ -1440,9 +1440,9 @@ do
-- taskmanager:AddRejectZone(ZONE:FindByName("RejectZone")) -- taskmanager:AddRejectZone(ZONE:FindByName("RejectZone"))
-- --
-- -- Set up using SRS for messaging -- -- Set up using SRS for messaging
-- local hereSRSPath = "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- local hereSRSPath = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
-- local hereSRSPort = 5002 -- local hereSRSPort = 5002
-- -- local hereSRSGoogle = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourkey.json" -- -- local hereSRSGoogle = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\yourkey.json"
-- taskmanager:SetSRS({130,255},{radio.modulation.AM,radio.modulation.AM},hereSRSPath,"female","en-GB",hereSRSPort,"Microsoft Hazel Desktop",0.7,hereSRSGoogle) -- taskmanager:SetSRS({130,255},{radio.modulation.AM,radio.modulation.AM},hereSRSPath,"female","en-GB",hereSRSPort,"Microsoft Hazel Desktop",0.7,hereSRSGoogle)
-- --
-- -- Controller will announce itself under these broadcast frequencies, handy to use cold-start frequencies here of your aircraft -- -- Controller will announce itself under these broadcast frequencies, handy to use cold-start frequencies here of your aircraft
@ -4606,7 +4606,7 @@ end
-- @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!
-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
-- @param #string Gender (Optional) Defaults to "male" -- @param #string Gender (Optional) Defaults to "male"
-- @param #string Culture (Optional) Defaults to "en-US" -- @param #string Culture (Optional) Defaults to "en-US"
-- @param #number Port (Optional) Defaults to 5002 -- @param #number Port (Optional) Defaults to 5002
@ -4620,7 +4620,7 @@ end
-- @return #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate,Backend) function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate,Backend)
self:T(self.lid.."SetSRS") self:T(self.lid.."SetSRS")
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" --
self.Gender = Gender or MSRS.gender or "male" -- self.Gender = Gender or MSRS.gender or "male" --
self.Culture = Culture or MSRS.culture or "en-US" -- self.Culture = Culture or MSRS.culture or "en-US" --
self.Port = Port or MSRS.port or 5002 -- self.Port = Port or MSRS.port or 5002 --

View File

@ -72,7 +72,7 @@ end
--- Checks if a point is contained within the circle. --- Checks if a point is contained within the circle.
-- @param #table point The point to check -- @param #table point The point to check
-- @return #bool True if the point is contained, false otherwise -- @return #boolean True if the point is contained, false otherwise
function CIRCLE:ContainsPoint(point) function CIRCLE:ContainsPoint(point)
if ((point.x - self.CenterVec2.x) ^ 2 + (point.y - self.CenterVec2.y) ^ 2) ^ 0.5 <= self.Radius then if ((point.x - self.CenterVec2.x) ^ 2 + (point.y - self.CenterVec2.y) ^ 2) ^ 0.5 <= self.Radius then
return true return true
@ -226,6 +226,11 @@ end
--- Returns a random Vec2 within the circle. --- Returns a random Vec2 within the circle.
-- @return #table The random Vec2 -- @return #table The random Vec2
function CIRCLE:GetRandomVec2() function CIRCLE:GetRandomVec2()
math.random()
math.random()
math.random()
local angle = math.random() * 2 * math.pi local angle = math.random() * 2 * math.pi
local rx = math.random(0, self.Radius) * math.cos(angle) + self.CenterVec2.x local rx = math.random(0, self.Radius) * math.cos(angle) + self.CenterVec2.x
@ -237,6 +242,11 @@ end
--- Returns a random Vec2 on the border of the circle. --- Returns a random Vec2 on the border of the circle.
-- @return #table The random Vec2 -- @return #table The random Vec2
function CIRCLE:GetRandomVec2OnBorder() function CIRCLE:GetRandomVec2OnBorder()
math.random()
math.random()
math.random()
local angle = math.random() * 2 * math.pi local angle = math.random() * 2 * math.pi
local rx = self.Radius * math.cos(angle) + self.CenterVec2.x local rx = self.Radius * math.cos(angle) + self.CenterVec2.x

View File

@ -352,6 +352,7 @@ end
--- Returns a random Vec2 within the polygon. The Vec2 is weighted by the areas of the triangles that make up the polygon. --- Returns a random Vec2 within the polygon. The Vec2 is weighted by the areas of the triangles that make up the polygon.
-- @return #table The random Vec2 -- @return #table The random Vec2
function POLYGON:GetRandomVec2() function POLYGON:GetRandomVec2()
local weights = {} local weights = {}
for _, triangle in pairs(self.Triangles) do for _, triangle in pairs(self.Triangles) do
weights[triangle] = triangle.SurfaceArea / self.SurfaceArea weights[triangle] = triangle.SurfaceArea / self.SurfaceArea

View File

@ -73,6 +73,11 @@ end
-- @param #table points The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it -- @param #table points The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it
-- @return #table The random Vec2 -- @return #table The random Vec2
function TRIANGLE:GetRandomVec2(points) function TRIANGLE:GetRandomVec2(points)
math.random()
math.random()
math.random()
points = points or self.Points points = points or self.Points
local pt = {math.random(), math.random()} local pt = {math.random(), math.random()}
table.sort(pt) table.sort(pt)

View File

@ -443,28 +443,32 @@ MSRS.Voices = {
["en_AU_Standard_B"] = 'en-AU-Standard-B', -- [2] MALE ["en_AU_Standard_B"] = 'en-AU-Standard-B', -- [2] MALE
["en_AU_Standard_C"] = 'en-AU-Standard-C', -- [3] FEMALE ["en_AU_Standard_C"] = 'en-AU-Standard-C', -- [3] FEMALE
["en_AU_Standard_D"] = 'en-AU-Standard-D', -- [4] MALE ["en_AU_Standard_D"] = 'en-AU-Standard-D', -- [4] MALE
["en_IN_Standard_A"] = 'en-IN-Standard-A', -- [5] FEMALE -- IN
["en_IN_Standard_B"] = 'en-IN-Standard-B', -- [6] MALE ["en_IN_Standard_A"] = 'en-IN-Standard-A', -- Female
["en_IN_Standard_C"] = 'en-IN-Standard-C', -- [7] MALE ["en_IN_Standard_B"] = 'en-IN-Standard-B', -- Male
["en_IN_Standard_D"] = 'en-IN-Standard-D', -- [8] FEMALE ["en_IN_Standard_C"] = 'en-IN-Standard-C', -- Male
["en_IN_Standard_D"] = 'en-IN-Standard-D', -- Female
["en_IN_Standard_E"] = 'en-IN-Standard-E', -- Female
["en_IN_Standard_F"] = 'en-IN-Standard-F', -- Male
-- 2025 changes -- 2025 changes
["en_GB_Standard_A"] = 'en-GB-Standard-N', -- [9] FEMALE ["en_GB_Standard_A"] = 'en-GB-Standard-A', -- Female
["en_GB_Standard_B"] = 'en-GB-Standard-O', -- [10] MALE ["en_GB_Standard_B"] = 'en-GB-Standard-B', -- Male
["en_GB_Standard_C"] = 'en-GB-Standard-N', -- [11] FEMALE ["en_GB_Standard_C"] = 'en-GB-Standard-C', -- Female
["en_GB_Standard_D"] = 'en-GB-Standard-O', -- [12] MALE ["en_GB_Standard_D"] = 'en-GB-Standard-D', -- Male
["en_GB_Standard_F"] = 'en-GB-Standard-N', -- [13] FEMALE ["en_GB_Standard_F"] = 'en-GB-Standard-F', -- Female
["en_GB_Standard_O"] = 'en-GB-Standard-O', -- [12] MALE ["en_GB_Standard_N"] = 'en-GB-Standard-N', -- Female
["en_GB_Standard_N"] = 'en-GB-Standard-N', -- [13] FEMALE ["en_GB_Standard_O"] = 'en-GB-Standard-O', -- Male
["en_US_Standard_A"] = 'en-US-Standard-A', -- [14] MALE -- US
["en_US_Standard_B"] = 'en-US-Standard-B', -- [15] MALE ["en_US_Standard_A"] = 'en-US-Standard-A', -- Male
["en_US_Standard_C"] = 'en-US-Standard-C', -- [16] FEMALE ["en_US_Standard_B"] = 'en-US-Standard-B', -- Male
["en_US_Standard_D"] = 'en-US-Standard-D', -- [17] MALE ["en_US_Standard_C"] = 'en-US-Standard-C', -- Female
["en_US_Standard_E"] = 'en-US-Standard-E', -- [18] FEMALE ["en_US_Standard_D"] = 'en-US-Standard-D', -- Male
["en_US_Standard_F"] = 'en-US-Standard-F', -- [19] FEMALE ["en_US_Standard_E"] = 'en-US-Standard-E', -- Female
["en_US_Standard_G"] = 'en-US-Standard-G', -- [20] FEMALE ["en_US_Standard_F"] = 'en-US-Standard-F', -- Female
["en_US_Standard_H"] = 'en-US-Standard-H', -- [21] FEMALE ["en_US_Standard_G"] = 'en-US-Standard-G', -- Female
["en_US_Standard_I"] = 'en-US-Standard-I', -- [22] MALE ["en_US_Standard_H"] = 'en-US-Standard-H', -- Female
["en_US_Standard_J"] = 'en-US-Standard-J', -- [23] MALE ["en_US_Standard_I"] = 'en-US-Standard-I', -- Male
["en_US_Standard_J"] = 'en-US-Standard-J', -- Male
-- 2025 catalog changes -- 2025 catalog changes
["fr_FR_Standard_A"] = "fr-FR-Standard-F", -- Female ["fr_FR_Standard_A"] = "fr-FR-Standard-F", -- Female
["fr_FR_Standard_B"] = "fr-FR-Standard-G", -- Male ["fr_FR_Standard_B"] = "fr-FR-Standard-G", -- Male
@ -474,14 +478,15 @@ MSRS.Voices = {
["fr_FR_Standard_G"] = "fr-FR-Standard-G", -- Male ["fr_FR_Standard_G"] = "fr-FR-Standard-G", -- Male
["fr_FR_Standard_F"] = "fr-FR-Standard-F", -- Female ["fr_FR_Standard_F"] = "fr-FR-Standard-F", -- Female
-- 2025 catalog changes -- 2025 catalog changes
["de_DE_Standard_A"] = "de-DE-Standard-G", -- Female ["de_DE_Standard_A"] = 'de-DE-Standard-A', -- Female
["de_DE_Standard_B"] = "de-DE-Standard-H", -- Male ["de_DE_Standard_B"] = 'de-DE-Standard-B', -- Male
["de_DE_Standard_C"] = "de-DE-Standard-G", -- Female ["de_DE_Standard_C"] = 'de-DE-Standard-C', -- Female
["de_DE_Standard_D"] = "de-DE-Standard-H", -- Male ["de_DE_Standard_D"] = 'de-DE-Standard-D', -- Male
["de_DE_Standard_E"] = "de-DE-Standard-H", -- Male ["de_DE_Standard_E"] = 'de-DE-Standard-E', -- Male
["de_DE_Standard_F"] = "de-DE-Standard-G", -- Female ["de_DE_Standard_F"] = 'de-DE-Standard-F', -- Female
["de_DE_Standard_H"] = "de-DE-Standard-H", -- Male ["de_DE_Standard_G"] = 'de-DE-Standard-G', -- Female
["de_DE_Standard_G"] = "de-DE-Standard-G", -- Female ["de_DE_Standard_H"] = 'de-DE-Standard-H', -- Male
-- ES
["es_ES_Standard_A"] = "es-ES-Standard-E", -- Female ["es_ES_Standard_A"] = "es-ES-Standard-E", -- Female
["es_ES_Standard_B"] = "es-ES-Standard-F", -- Male ["es_ES_Standard_B"] = "es-ES-Standard-F", -- Male
["es_ES_Standard_C"] = "es-ES-Standard-E", -- Female ["es_ES_Standard_C"] = "es-ES-Standard-E", -- Female
@ -497,32 +502,36 @@ MSRS.Voices = {
["it_IT_Standard_F"] = "it-IT-Standard-F", -- Male ["it_IT_Standard_F"] = "it-IT-Standard-F", -- Male
}, },
Wavenet = { Wavenet = {
["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- [1] FEMALE ["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- Female
["en_AU_Wavenet_B"] = 'en-AU-Wavenet-B', -- [2] MALE ["en_AU_Wavenet_B"] = 'en-AU-Wavenet-B', -- Male
["en_AU_Wavenet_C"] = 'en-AU-Wavenet-C', -- [3] FEMALE ["en_AU_Wavenet_C"] = 'en-AU-Wavenet-C', -- Female
["en_AU_Wavenet_D"] = 'en-AU-Wavenet-D', -- [4] MALE ["en_AU_Wavenet_D"] = 'en-AU-Wavenet-D', -- Male
["en_IN_Wavenet_A"] = 'en-IN-Wavenet-A', -- [5] FEMALE -- IN
["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- [6] MALE ["en_IN_Wavenet_A"] = 'en-IN-Wavenet-A', -- Female
["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- [7] MALE ["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- Male
["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- [8] FEMALE ["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- Male
["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- Female
["en_IN_Wavenet_E"] = 'en-IN-Wavenet-E', -- Female
["en_IN_Wavenet_F"] = 'en-IN-Wavenet-F', -- Male
-- 2025 changes -- 2025 changes
["en_GB_Wavenet_A"] = 'en-GB-Wavenet-N', -- [9] FEMALE ["en_GB_Wavenet_A"] = 'en-GB-Wavenet-A', -- [9] FEMALE
["en_GB_Wavenet_B"] = 'en-GB-Wavenet-O', -- [10] MALE ["en_GB_Wavenet_B"] = 'en-GB-Wavenet-B', -- [10] MALE
["en_GB_Wavenet_C"] = 'en-GB-Wavenet-N', -- [11] FEMALE ["en_GB_Wavenet_C"] = 'en-GB-Wavenet-C', -- [11] FEMALE
["en_GB_Wavenet_D"] = 'en-GB-Wavenet-O', -- [12] MALE ["en_GB_Wavenet_D"] = 'en-GB-Wavenet-D', -- [12] MALE
["en_GB_Wavenet_F"] = 'en-GB-Wavenet-N', -- [13] FEMALE ["en_GB_Wavenet_F"] = 'en-GB-Wavenet-F', -- [13] FEMALE
["en_GB_Wavenet_O"] = 'en-GB-Wavenet-O', -- [12] MALE ["en_GB_Wavenet_O"] = 'en-GB-Wavenet-O', -- [12] MALE
["en_GB_Wavenet_N"] = 'en-GB-Wavenet-N', -- [13] FEMALE ["en_GB_Wavenet_N"] = 'en-GB-Wavenet-N', -- [13] FEMALE
["en_US_Wavenet_A"] = 'en-US-Wavenet-A', -- [14] MALE -- US
["en_US_Wavenet_B"] = 'en-US-Wavenet-B', -- [15] MALE ["en_US_Wavenet_A"] = 'en-US-Wavenet-A', -- Male
["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- [16] FEMALE ["en_US_Wavenet_B"] = 'en-US-Wavenet-B', -- Male
["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- [17] MALE ["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- Female
["en_US_Wavenet_E"] = 'en-US-Wavenet-E', -- [18] FEMALE ["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- Male
["en_US_Wavenet_F"] = 'en-US-Wavenet-F', -- [19] FEMALE ["en_US_Wavenet_E"] = 'en-US-Wavenet-E', -- Female
["en_US_Wavenet_G"] = 'en-US-Wavenet-G', -- [20] FEMALE ["en_US_Wavenet_F"] = 'en-US-Wavenet-F', -- Female
["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- [21] FEMALE ["en_US_Wavenet_G"] = 'en-US-Wavenet-G', -- Female
["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- [22] MALE ["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- Female
["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- [23] MALE ["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- Male
["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- Male
-- 2025 catalog changes -- 2025 catalog changes
["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-F", -- Female ["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-F", -- Female
["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-G", -- Male ["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-G", -- Male
@ -532,14 +541,15 @@ MSRS.Voices = {
["fr_FR_Wavenet_G"] = "fr-FR-Wavenet-G", -- Male ["fr_FR_Wavenet_G"] = "fr-FR-Wavenet-G", -- Male
["fr_FR_Wavenet_F"] = "fr-FR-Wavenet-F", -- Female ["fr_FR_Wavenet_F"] = "fr-FR-Wavenet-F", -- Female
-- 2025 catalog changes -- 2025 catalog changes
["de_DE_Wavenet_A"] = "de-DE-Wavenet-G", -- Female ["de_DE_Wavenet_A"] = 'de-DE-Wavenet-A', -- Female
["de_DE_Wavenet_B"] = "de-DE-Wavenet-H", -- Male ["de_DE_Wavenet_B"] = 'de-DE-Wavenet-B', -- Male
["de_DE_Wavenet_C"] = "de-DE-Wavenet-G", -- Female ["de_DE_Wavenet_C"] = 'de-DE-Wavenet-C', -- Female
["de_DE_Wavenet_D"] = "de-DE-Wavenet-H", -- Male ["de_DE_Wavenet_D"] = 'de-DE-Wavenet-D', -- Male
["de_DE_Wavenet_E"] = "de-DE-Wavenet-H", -- Male ["de_DE_Wavenet_E"] = 'de-DE-Wavenet-E', -- Male
["de_DE_Wavenet_F"] = "de-DE-Wavenet-G", -- Female ["de_DE_Wavenet_F"] = 'de-DE-Wavenet-F', -- Female
["de_DE_Wavenet_H"] = "de-DE-Wavenet-H", -- Male ["de_DE_Wavenet_G"] = 'de-DE-Wavenet-G', -- Female
["de_DE_Wavenet_G"] = "de-DE-Wavenet-G", -- Female ["de_DE_Wavenet_H"] = 'de-DE-Wavenet-H', -- Male
-- ES
["es_ES_Wavenet_B"] = "es-ES-Wavenet-E", -- Male ["es_ES_Wavenet_B"] = "es-ES-Wavenet-E", -- Male
["es_ES_Wavenet_C"] = "es-ES-Wavenet-F", -- Female ["es_ES_Wavenet_C"] = "es-ES-Wavenet-F", -- Female
["es_ES_Wavenet_D"] = "es-ES-Wavenet-E", -- Female ["es_ES_Wavenet_D"] = "es-ES-Wavenet-E", -- Female
@ -553,6 +563,134 @@ MSRS.Voices = {
["it_IT_Wavenet_E"] = "it-IT-Wavenet-E", -- Female ["it_IT_Wavenet_E"] = "it-IT-Wavenet-E", -- Female
["it_IT_Wavenet_F"] = "it-IT-Wavenet-F", -- Male ["it_IT_Wavenet_F"] = "it-IT-Wavenet-F", -- Male
} , } ,
Chirp3HD = {
["en_GB_Chirp3_HD_Aoede"] = 'en-GB-Chirp3-HD-Aoede', -- Female
["en_GB_Chirp3_HD_Charon"] = 'en-GB-Chirp3-HD-Charon', -- Male
["en_GB_Chirp3_HD_Fenrir"] = 'en-GB-Chirp3-HD-Fenrir', -- Male
["en_GB_Chirp3_HD_Kore"] = 'en-GB-Chirp3-HD-Kore', -- Female
["en_GB_Chirp3_HD_Leda"] = 'en-GB-Chirp3-HD-Leda', -- Female
["en_GB_Chirp3_HD_Orus"] = 'en-GB-Chirp3-HD-Orus', -- Male
["en_GB_Chirp3_HD_Puck"] = 'en-GB-Chirp3-HD-Puck', -- Male
["en_GB_Chirp3_HD_Zephyr"] = 'en-GB-Chirp3-HD-Zephyr', -- Female
--["de_DE_Chirp3_HD_Aoede"] = 'de-DE-Chirp3-HD-Aoede', -- Female (Datenfehler im Original)
["en_US_Chirp3_HD_Charon"] = 'en-US-Chirp3-HD-Charon', -- Male
["en_US_Chirp3_HD_Fenrir"] = 'en-US-Chirp3-HD-Fenrir', -- Male
["en_US_Chirp3_HD_Kore"] = 'en-US-Chirp3-HD-Kore', -- Female
["en_US_Chirp3_HD_Leda"] = 'en-US-Chirp3-HD-Leda', -- Female
["en_US_Chirp3_HD_Orus"] = 'en-US-Chirp3-HD-Orus', -- Male
["en_US_Chirp3_HD_Puck"] = 'en-US-Chirp3-HD-Puck', -- Male
--["de_DE_Chirp3_HD_Zephyr"] = 'de-DE-Chirp3-HD-Zephyr', -- Female (Datenfehler im Original)
-- DE
["de_DE_Chirp3_HD_Aoede"] = 'de-DE-Chirp3-HD-Aoede', -- Female
["de_DE_Chirp3_HD_Charon"] = 'de-DE-Chirp3-HD-Charon', -- Male
["de_DE_Chirp3_HD_Fenrir"] = 'de-DE-Chirp3-HD-Fenrir', -- Male
["de_DE_Chirp3_HD_Kore"] = 'de-DE-Chirp3-HD-Kore', -- Female
["de_DE_Chirp3_HD_Leda"] = 'de-DE-Chirp3-HD-Leda', -- Female
["de_DE_Chirp3_HD_Orus"] = 'de-DE-Chirp3-HD-Orus', -- Male
["de_DE_Chirp3_HD_Puck"] = 'de-DE-Chirp3-HD-Puck', -- Male
["de_DE_Chirp3_HD_Zephyr"] = 'de-DE-Chirp3-HD-Zephyr', -- Female
-- AU
["en_AU_Chirp3_HD_Aoede"] = 'en-AU-Chirp3-HD-Aoede', -- Female
["en_AU_Chirp3_HD_Charon"] = 'en-AU-Chirp3-HD-Charon', -- Male
["en_AU_Chirp3_HD_Fenrir"] = 'en-AU-Chirp3-HD-Fenrir', -- Male
["en_AU_Chirp3_HD_Kore"] = 'en-AU-Chirp3-HD-Kore', -- Female
["en_AU_Chirp3_HD_Leda"] = 'en-AU-Chirp3-HD-Leda', -- Female
["en_AU_Chirp3_HD_Orus"] = 'en-AU-Chirp3-HD-Orus', -- Male
["en_AU_Chirp3_HD_Puck"] = 'en-AU-Chirp3-HD-Puck', -- Male
["en_AU_Chirp3_HD_Zephyr"] = 'en-AU-Chirp3-HD-Zephyr', -- Female
-- IN
["en_IN_Chirp3_HD_Aoede"] = 'en-IN-Chirp3-HD-Aoede', -- Female
["en_IN_Chirp3_HD_Charon"] = 'en-IN-Chirp3-HD-Charon', -- Male
["en_IN_Chirp3_HD_Fenrir"] = 'en-IN-Chirp3-HD-Fenrir', -- Male
["en_IN_Chirp3_HD_Kore"] = 'en-IN-Chirp3-HD-Kore', -- Female
["en_IN_Chirp3_HD_Leda"] = 'en-IN-Chirp3-HD-Leda', -- Female
["en_IN_Chirp3_HD_Orus"] = 'en-IN-Chirp3-HD-Orus', -- Male
},
ChirpHD = {
["en_US_Chirp_HD_D"] = 'en-US-Chirp-HD-D', -- Male
["en_US_Chirp_HD_F"] = 'en-US-Chirp-HD-F', -- Female
["en_US_Chirp_HD_O"] = 'en-US-Chirp-HD-O', -- Female
-- DE
["de_DE_Chirp_HD_D"] = 'de-DE-Chirp-HD-D', -- Male
["de_DE_Chirp_HD_F"] = 'de-DE-Chirp-HD-F', -- Female
["de_DE_Chirp_HD_O"] = 'de-DE-Chirp-HD-O', -- Female
-- AU
["en_AU_Chirp_HD_D"] = 'en-AU-Chirp-HD-D', -- Male
["en_AU_Chirp_HD_F"] = 'en-AU-Chirp-HD-F', -- Female
["en_AU_Chirp_HD_O"] = 'en-AU-Chirp-HD-O', -- Female
-- IN
["en_IN_Chirp_HD_D"] = 'en-IN-Chirp-HD-D', -- Male
["en_IN_Chirp_HD_F"] = 'en-IN-Chirp-HD-F', -- Female
["en_IN_Chirp_HD_O"] = 'en-IN-Chirp-HD-O', -- Female
},
},
Neural2 = {
["en_GB_Neural2_A"] = 'en-GB-Neural2-A', -- Female
["en_GB_Neural2_B"] = 'en-GB-Neural2-B', -- Male
["en_GB_Neural2_C"] = 'en-GB-Neural2-C', -- Female
["en_GB_Neural2_D"] = 'en-GB-Neural2-D', -- Male
["en_GB_Neural2_F"] = 'en-GB-Neural2-F', -- Female
["en_GB_Neural2_N"] = 'en-GB-Neural2-N', -- Female
["en_GB_Neural2_O"] = 'en-GB-Neural2-O', -- Male
-- US
["en_US_Neural2_A"] = 'en-US-Neural2-A', -- Male
["en_US_Neural2_C"] = 'en-US-Neural2-C', -- Female
["en_US_Neural2_D"] = 'en-US-Neural2-D', -- Male
["en_US_Neural2_E"] = 'en-US-Neural2-E', -- Female
["en_US_Neural2_F"] = 'en-US-Neural2-F', -- Female
["en_US_Neural2_G"] = 'en-US-Neural2-G', -- Female
["en_US_Neural2_H"] = 'en-US-Neural2-H', -- Female
["en_US_Neural2_I"] = 'en-US-Neural2-I', -- Male
["en_US_Neural2_J"] = 'en-US-Neural2-J', -- Male
-- DE
["de_DE_Neural2_G"] = 'de-DE-Neural2-G', -- Female
["de_DE_Neural2_H"] = 'de-DE-Neural2-H', -- Male
-- AU
["en_AU_Neural2_A"] = 'en-AU-Neural2-A', -- Female
["en_AU_Neural2_B"] = 'en-AU-Neural2-B', -- Male
["en_AU_Neural2_C"] = 'en-AU-Neural2-C', -- Female
["en_AU_Neural2_D"] = 'en-AU-Neural2-D', -- Male
-- IN
["en_IN_Neural2_A"] = 'en-IN-Neural2-A', -- Female
["en_IN_Neural2_B"] = 'en-IN-Neural2-B', -- Male
["en_IN_Neural2_C"] = 'en-IN-Neural2-C', -- Male
["en_IN_Neural2_D"] = 'en-IN-Neural2-D', -- Female
},
News = {
["en_GB_News_G"] = 'en-GB-News-G', -- Female
["en_GB_News_H"] = 'en-GB-News-H', -- Female
["en_GB_News_I"] = 'en-GB-News-I', -- Female
["en_GB_News_J"] = 'en-GB-News-J', -- Male
["en_GB_News_K"] = 'en-GB-News-K', -- Male
["en_GB_News_L"] = 'en-GB-News-L', -- Male
["en_GB_News_M"] = 'en-GB-News-M', -- Male
-- US
["en_US_News_K"] = 'en-US-News-K', -- Female
["en_US_News_L"] = 'en-US-News-L', -- Female
["en_US_News_N"] = 'en-US-News-N', -- Male
-- AU
["en_AU_News_E"] = 'en-AU-News-E', -- Female
["en_AU_News_F"] = 'en-AU-News-F', -- Female
["en_AU_News_G"] = 'en-AU-News-G', -- Male
},
Casual = {
["en_US_Casual_K"] = 'en-US-Casual-K', -- Male
},
Polyglot = {
["en_US_Polyglot_1"] = 'en-US-Polyglot-1', -- Male
["de_DE_Polyglot_1"] = 'de-DE-Polyglot-1', -- Male
["en_AU_Polyglot_1"] = 'en-AU-Polyglot-1', -- Male
},
Studio = {
-- Englisch (UK) - Studio
["en_GB_Studio_B"] = 'en-GB-Studio-B', -- Male
["en_GB_Studio_C"] = 'en-GB-Studio-C', -- Female
-- Englisch (USA) - Studio
["en_US_Studio_O"] = 'en-US-Studio-O', -- Female
["en_US_Studio_Q"] = 'en-US-Studio-Q', -- Male
-- DE
["de_DE_Studio_B"] = 'de-DE-Studio-B', -- Male
["de_DE_Studio_C"] = 'de-DE-Studio-C', -- Female
}, },
} }
@ -632,7 +770,7 @@ end
-- set the path to the exe file via @{#MSRS.SetPath}. -- set the path to the exe file via @{#MSRS.SetPath}.
-- --
-- @param #MSRS self -- @param #MSRS self
-- @param #string Path Path to SRS directory. Default `C:\\Program Files\\DCS-SimpleRadio-Standalone`. -- @param #string Path Path to SRS directory. Default `C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio`.
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies. -- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies.
-- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations. -- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations.
-- @param #string Backend Backend used: `MSRS.Backend.SRSEXE` (default) or `MSRS.Backend.GRPC`. -- @param #string Backend Backend used: `MSRS.Backend.SRSEXE` (default) or `MSRS.Backend.GRPC`.
@ -767,13 +905,13 @@ end
--- Set path to SRS install directory. More precisely, path to where the `DCS-SR-ExternalAudio.exe` is located. --- Set path to SRS install directory. More precisely, path to where the `DCS-SR-ExternalAudio.exe` is located.
-- @param #MSRS self -- @param #MSRS self
-- @param #string Path Path to the directory, where the sound file is located. Default is `C:\\Program Files\\DCS-SimpleRadio-Standalone`. -- @param #string Path Path to the directory, where the sound file is located. Default is `C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio`.
-- @return #MSRS self -- @return #MSRS self
function MSRS:SetPath(Path) function MSRS:SetPath(Path)
self:F( {Path=Path} ) self:F( {Path=Path} )
-- Set path. -- Set path.
self.path=Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" self.path=Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
-- Remove (back)slashes. -- Remove (back)slashes.
local n=1 ; local nmax=1000 local n=1 ; local nmax=1000
@ -1817,7 +1955,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\\ExternalAudio", -- 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!
@ -1837,7 +1975,7 @@ end
-- -- Google Cloud -- -- Google Cloud
-- gcloud = { -- gcloud = {
-- voice = "en-GB-Standard-A", -- The Google Cloud voice to use (see https://cloud.google.com/text-to-speech/docs/voices). -- voice = "en-GB-Standard-A", -- The Google Cloud voice to use (see https://cloud.google.com/text-to-speech/docs/voices).
-- credentials="C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourfilename.json", -- Full path to credentials JSON file (only for SRS-TTS.exe backend) -- credentials="C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\yourfilename.json", -- Full path to credentials JSON file (only for SRS-TTS.exe backend)
-- key="Your access Key", -- Google API access key (only for DCS-gRPC backend) -- key="Your access Key", -- Google API access key (only for DCS-gRPC backend)
-- }, -- },
-- -- Amazon Web Service -- -- Amazon Web Service
@ -1905,7 +2043,7 @@ function MSRS:LoadConfigFile(Path,Filename)
local Self = self or MSRS --#MSRS local Self = self or MSRS --#MSRS
Self.path = MSRS_Config.Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" Self.path = MSRS_Config.Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
Self.port = MSRS_Config.Port or 5002 Self.port = MSRS_Config.Port or 5002
Self.backend = MSRS_Config.Backend or MSRS.Backend.SRSEXE Self.backend = MSRS_Config.Backend or MSRS.Backend.SRSEXE
Self.frequencies = MSRS_Config.Frequency or {127,243} Self.frequencies = MSRS_Config.Frequency or {127,243}

View File

@ -4143,9 +4143,14 @@ end
-- @param #string VehicleTemplate, template name for additional vehicles. Can be nil for no additional vehicles. -- @param #string VehicleTemplate, template name for additional vehicles. Can be nil for no additional vehicles.
-- @param #number Liquids Tons of fuel to be added initially to the FARP. Defaults to 10 (tons). Set to 0 for no fill. -- @param #number Liquids Tons of fuel to be added initially to the FARP. Defaults to 10 (tons). Set to 0 for no fill.
-- @param #number Equipment Number of equipment items per known item to be added initially to the FARP. Defaults to 10 (items). Set to 0 for no fill. -- @param #number Equipment Number of equipment items per known item to be added initially to the FARP. Defaults to 10 (items). Set to 0 for no fill.
-- @param #number Airframes Number of helicopter airframes per known type in Ops.CSAR#CSAR.AircraftType to be added initially to the FARP. Set to 0 for no airframes.
-- @param #string F10Text Text to display on F10 map if given. Handy to post things like the ADF beacon Frequency, Callsign and ATC Frequency.
-- @param #boolean DynamicSpawns If true, allow Dynamic Spawns from this FARP.
-- @param #boolean HotStart If true and DynamicSpawns is true, allow hot starts for Dynamic Spawns from this FARP.
-- @return #list<Wrapper.Static#STATIC> Table of spawned objects and vehicle object (if given). -- @return #list<Wrapper.Static#STATIC> Table of spawned objects and vehicle object (if given).
-- @return #string ADFBeaconName Name of the ADF beacon, to be able to remove/stop it later. -- @return #string ADFBeaconName Name of the ADF beacon, to be able to remove/stop it later.
function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment) -- @return #number MarkerID ID of the F10 Text, to be able to remove it later.
function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart)
-- Set Defaults -- Set Defaults
local farplocation = Coordinate local farplocation = Coordinate
@ -4159,6 +4164,7 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
local liquids = Liquids or 10 local liquids = Liquids or 10
liquids = liquids * 1000 -- tons to kg liquids = liquids * 1000 -- tons to kg
local equip = Equipment or 10 local equip = Equipment or 10
local airframes = Airframes or 10
local statictypes = ENUMS.FARPObjectTypeNamesAndShape[farptype] or {TypeName="FARP", ShapeName="FARPS"} local statictypes = ENUMS.FARPObjectTypeNamesAndShape[farptype] or {TypeName="FARP", ShapeName="FARPS"}
local STypeName = statictypes.TypeName local STypeName = statictypes.TypeName
local SShapeName = statictypes.ShapeName local SShapeName = statictypes.ShapeName
@ -4168,7 +4174,7 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
-- Spawn FARP -- Spawn FARP
local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP"
newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS" newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS"
newfarp:InitFARP(callsign,freq,mod) newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart)
local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name) local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name)
table.insert(ReturnObjects,spawnedfarp) table.insert(ReturnObjects,spawnedfarp)
-- Spawn Objects -- Spawn Objects
@ -4221,6 +4227,12 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
end end
end end
if airframes and airframes > 0 then
for typename in pairs (CSAR.AircraftType) do
newWH:SetItem(typename,airframes)
end
end
local ADFName local ADFName
if ADF and type(ADF) == "number" then if ADF and type(ADF) == "number" then
local ADFFreq = ADF*1000 -- KHz to Hz local ADFFreq = ADF*1000 -- KHz to Hz
@ -4231,7 +4243,150 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,
trigger.action.radioTransmission(Sound, vec3, 0, true, ADFFreq, 250, ADFName) trigger.action.radioTransmission(Sound, vec3, 0, true, ADFFreq, 250, ADFName)
end end
return ReturnObjects, ADFName local MarkerID = nil
if F10Text then
local Color = {0,0,1}
if Coalition == coalition.side.RED then
Color = {1,0,0}
elseif Coalition == coalition.side.NEUTRAL then
Color = {0,1,0}
end
local Alpha = 0.75
local coordinate = Coordinate:Translate(600,0)
MarkerID = coordinate:TextToAll(F10Text,Coalition,Color,1,{1,1,1},Alpha,14,true)
end
return ReturnObjects, ADFName, MarkerID
end
--- Spawn a MASH at a given coordinate, optionally, add an ADF Beacon.
-- @param #string Name Unique Name of the Mash.
-- @param Core.Point#COORDINATE Coordinate Coordinate where to spawn the MASH. Can be given as a Core.Zone#ZONE object, in this case we take the center coordinate.
-- @param #number Country Country ID the MASH belongs to, e.g. country.id.USA or country.id.RUSSIA.
-- @param #number ADF (Optional) ADF Frequency in kHz (Kilohertz), if given activate an ADF Beacon at the location of the MASH.
-- @param #string Livery (Optional) The livery of the static CH-47, defaults to dark green.
-- @param #boolean DeployHelo (Optional) If true, deploy the helicopter static.
-- @param #number MASHRadio MASH Radio Frequency, defaults to 127.5.
-- @param #number MASHRadioModulation MASH Radio Modulation, defaults to radio.modulation.AM.
-- @param #number MASHCallsign Defaults to CALLSIGN.FARP.Berlin.
-- @param #table Templates (Optional) You can hand in your own template table of numbered(!) entries. Each entry consist of a relative(!) x,y position and data of a
-- static, shape_name is optional. Also, livery_id is optional, but is applied to the helicopter static only.
-- @return #table Table of Wrapper.Static#STATIC objects that were spawned.
-- @return #string ADFName Name of the ADF Beacon to remove it later.
-- @usage
-- -- MASH Template example, this one is the built in one used in the function:
-- MASHTemplates = {
-- [1]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.000000,y=0.000000,},
-- [2]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.313533,y=8.778935,},
-- [3]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=16.303737,y=20.379671,},
-- [4]={category='Helicopters',type='CH-47Fbl1',shape_name='none',heading=0,x=-20.047735,y=-63.166179,livery_id = "us army dark green",},
-- [5]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=26.650339,y=20.066138,},
-- [6]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.432292,y=9.077099,},
-- [7]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=-3.216114,},
-- [8]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.439281,y=-3.216114,},
-- [9]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=9.155603,},
-- [10]={category='Fortifications',type='TACAN_beacon',shape_name='none',heading=0,x=-2.329847,y=-16.579903,},
-- [11]={category='Fortifications',type='FARP Fuel Depot',shape_name='GSM Rus',heading=0,x=2.222011,y=4.487030,},
-- [12]={category='Fortifications',type='APFC fuel',shape_name='M92_APFCfuel',heading=0,x=3.614927,y=0.367838,},
-- [13]={category='Fortifications',type='Camouflage03',shape_name='M92_Camouflage03',heading=0,x=21.544148,y=21.998879,},
-- [14]={category='Fortifications',type='Container_generator',shape_name='M92_Container_generator',heading=0,x=20.989192,y=37.314334,},
-- [15]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=3.988003,y=8.362333,},
-- [16]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=-3.953195,y=12.945844,},
-- [17]={category='Fortifications',type='Windsock',shape_name='H-Windsock_RW',heading=0,x=-18.944173,y=-33.042196,},
-- [18]={category='Fortifications',type='Tent04',shape_name='M92_Tent04',heading=0,x=21.220671,y=30.247529,},
-- }
--
function UTILS.SpawnMASHStatics(Name,Coordinate,Country,ADF,Livery,DeployHelo,MASHRadio,MASHRadioModulation,MASHCallsign,Templates)
-- Basic objects table
local MASHTemplates = {
[1]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.000000,y=0.000000,},
[2]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.313533,y=8.778935,},
[3]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=16.303737,y=20.379671,},
[4]={category='Helicopters',type='CH-47Fbl1',shape_name='none',heading=0,x=-20.047735,y=-63.166179,livery_id = "us army dark green",},
[5]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=26.650339,y=20.066138,},
[6]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.432292,y=9.077099,},
[7]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=-3.216114,},
[8]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.439281,y=-3.216114,},
[9]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=9.155603,},
[10]={category='Fortifications',type='TACAN_beacon',shape_name='none',heading=0,x=-2.329847,y=-16.579903,},
[11]={category='Fortifications',type='FARP Fuel Depot',shape_name='GSM Rus',heading=0,x=2.222011,y=4.487030,},
[12]={category='Fortifications',type='APFC fuel',shape_name='M92_APFCfuel',heading=0,x=3.614927,y=0.367838,},
[13]={category='Fortifications',type='Camouflage03',shape_name='M92_Camouflage03',heading=0,x=21.544148,y=21.998879,},
[14]={category='Fortifications',type='Container_generator',shape_name='M92_Container_generator',heading=0,x=20.989192,y=37.314334,},
[15]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=3.988003,y=8.362333,},
[16]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=-3.953195,y=12.945844,},
[17]={category='Fortifications',type='Windsock',shape_name='H-Windsock_RW',heading=0,x=-18.944173,y=-33.042196,},
[18]={category='Fortifications',type='Tent04',shape_name='M92_Tent04',heading=0,x=21.220671,y=30.247529,},
}
if Templates then MASHTemplates=Templates end
-- locals
local name = Name or "Florence Nightingale"
local positionVec2
local positionVec3
local ReturnStatics = {}
local CountryID = Country or country.id.USA
local livery = "us army dark green"
local MASHRadio = MASHRadio or 127.5
local MASHRadioModulation = MASHRadioModulation or radio.modulation.AM
local MASHCallsign = MASHCallsign or CALLSIGN.FARP.Berlin
-- check for coordinate or zone
if type(Coordinate) == "table" then
if Coordinate:IsInstanceOf("COORDINATE") or Coordinate:IsInstanceOf("ZONE_BASE") then
positionVec2 = Coordinate:GetVec2()
positionVec3 = Coordinate:GetVec3()
end
else
BASE:E("Spawn MASH - no ZONE or COORDINATE handed!")
return
end
-- position
local BaseX = positionVec2.x
local BaseY = positionVec2.y
-- Statics
for id,object in pairs(MASHTemplates) do
local NewName = string.format("%s#%3d",name,id)
local vec2 = {x=BaseX+object.x,y=BaseY+object.y}
local Coordinate=COORDINATE:NewFromVec2(vec2)
local static = SPAWNSTATIC:NewFromType(object.type,object.category,CountryID)
if object.shape_name and object.shape_name ~= "none" then
static:InitShape(object.shape_name)
end
if object.category == "Helicopters" and DeployHelo == true then
if object.livery_id ~= nil then
livery = object.livery_id
end
static:InitLivery(livery)
local newstatic = static:SpawnFromCoordinate(Coordinate,object.heading,NewName)
table.insert(ReturnStatics,newstatic)
elseif object.category == "Heliports" then
static:InitFARP(MASHCallsign,MASHRadio,MASHRadioModulation,false,false)
local newstatic = static:SpawnFromCoordinate(Coordinate,object.heading,NewName)
table.insert(ReturnStatics,newstatic)
elseif object.category ~= "Helicopters" and object.category ~= "Heliports" then
local newstatic = static:SpawnFromCoordinate(Coordinate,object.heading,NewName)
table.insert(ReturnStatics,newstatic)
end
end
-- Beacon
local ADFName
if ADF and type(ADF) == "number" then
local ADFFreq = ADF*1000 -- KHz to Hz
local Sound = "l10n/DEFAULT/beacon.ogg"
ADFName = Name .. " ADF "..tostring(ADF).."KHz"
--BASE:I(string.format("Adding MASH Beacon %d KHz Name %s",ADF,ADFName))
trigger.action.radioTransmission(Sound, positionVec3, 0, true, ADFFreq, 250, ADFName)
end
return ReturnStatics, ADFName
end end
--- Converts a Vec2 to a Vec3. --- Converts a Vec2 to a Vec3.
@ -4428,3 +4583,401 @@ end
function UTILS.Weather.StopFogAnimation() function UTILS.Weather.StopFogAnimation()
return world.weather.setFogAnimation({}) return world.weather.setFogAnimation({})
end end
--- Find a ME created zone by its name
function UTILS.GetEnvZone(name)
for _,v in ipairs(env.mission.triggers.zones) do
if v.name == name then
return v
end
end
end
--- net.dostring_in
function UTILS.DoStringIn(State,DoString)
return net.dostring_in(State,DoString)
end
--- Show a picture on the screen to all
-- @param #string FileName File name of the picture
-- @param #number Duration Duration in seconds, defaults to 10
-- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false
-- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0
-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right
-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom
-- @param #number Size Size of the picture in percent, defaults to 100
-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size
function UTILS.ShowPictureToAll(FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)
ClearView = ClearView or false
StartDelay = StartDelay or 0
HorizontalAlign = HorizontalAlign or 1
VerticalAlign = VerticalAlign or 1
Size = Size or 100
SizeUnits = SizeUnits or 0
if ClearView then ClearView = "true" else ClearView = "false" end
net.dostring_in("mission", string.format("a_out_picture(\"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits))
end
--- Show a picture on the screen to Coalition
-- @param #number Coalition Coalition ID, can be coalition.side.BLUE, coalition.side.RED or coalition.side.NEUTRAL
-- @param #string FileName File name of the picture
-- @param #number Duration Duration in seconds, defaults to 10
-- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false
-- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0
-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right
-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom
-- @param #number Size Size of the picture in percent, defaults to 100
-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size
function UTILS.ShowPictureToCoalition(Coalition, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)
ClearView = ClearView or false
StartDelay = StartDelay or 0
HorizontalAlign = HorizontalAlign or 1
VerticalAlign = VerticalAlign or 1
Size = Size or 100
SizeUnits = SizeUnits or 0
if ClearView then ClearView = "true" else ClearView = "false" end
local coalName = string.lower(UTILS.GetCoalitionName(Coalition))
net.dostring_in("mission", string.format("a_out_picture_s(\"%s\", \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", coalName, FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits))
end
--- Show a picture on the screen to Country
-- @param #number Country Country ID, can be country.id.USA, country.id.RUSSIA, etc.
-- @param #string FileName File name of the picture
-- @param #number Duration Duration in seconds, defaults to 10
-- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false
-- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0
-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right
-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom
-- @param #number Size Size of the picture in percent, defaults to 100
-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size
function UTILS.ShowPictureToCountry(Country, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)
ClearView = ClearView or false
StartDelay = StartDelay or 0
HorizontalAlign = HorizontalAlign or 1
VerticalAlign = VerticalAlign or 1
Size = Size or 100
SizeUnits = SizeUnits or 0
if ClearView then ClearView = "true" else ClearView = "false" end
net.dostring_in("mission", string.format("a_out_picture_c(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", Country, FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits))
end
--- Show a picture on the screen to Group
-- @param Wrapper.Group#GROUP Group Group to show the picture to
-- @param #string FileName File name of the picture
-- @param #number Duration Duration in seconds, defaults to 10
-- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false
-- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0
-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right
-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom
-- @param #number Size Size of the picture in percent, defaults to 100
-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size
function UTILS.ShowPictureToGroup(Group, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)
ClearView = ClearView or false
StartDelay = StartDelay or 0
HorizontalAlign = HorizontalAlign or 1
VerticalAlign = VerticalAlign or 1
Size = Size or 100
SizeUnits = SizeUnits or 0
if ClearView then ClearView = "true" else ClearView = "false" end
net.dostring_in("mission", string.format("a_out_picture_g(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", Group:GetID(), FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits))
end
--- Show a picture on the screen to Unit
-- @param Wrapper.Unit#UNIT Unit Unit to show the picture to
-- @param #string FileName File name of the picture
-- @param #number Duration Duration in seconds, defaults to 10
-- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false
-- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0
-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right
-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom
-- @param #number Size Size of the picture in percent, defaults to 100
-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size
function UTILS.ShowPictureToUnit(Unit, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)
ClearView = ClearView or false
StartDelay = StartDelay or 0
HorizontalAlign = HorizontalAlign or 1
VerticalAlign = VerticalAlign or 1
Size = Size or 100
SizeUnits = SizeUnits or 0
if ClearView then ClearView = "true" else ClearView = "false" end
net.dostring_in("mission", string.format("a_out_picture_u(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", Unit:GetID(), FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits))
end
--- Load a mission file. This will replace the current mission with the one given carrying along the online clients.
-- @param #string FileName Mission filename
function UTILS.LoadMission(FileName)
net.dostring_in("mission", string.format("a_load_mission(\"%s\")", FileName))
end
--- Set the mission briefing for a coalition.
-- @param #number Coalition Briefing coalition ID, can be coalition.side.BLUE, coalition.side.RED or coalition.side.NEUTRAL
-- @param #string Text Briefing text, can contain newlines, will be converted formatted properly for DCS
-- @param #string Picture Picture file path, can be a file in the DEFAULT folder inside the .miz
function UTILS.SetMissionBriefing(Coalition, Text, Picture)
Text = Text or ""
Text = Text:gsub("\n", "\\n")
Picture = Picture or ""
local coalName = string.lower(UTILS.GetCoalitionName(Coalition))
net.dostring_in("mission", string.format("a_set_briefing(\"%s\", \"%s\", \"%s\")", coalName, Picture, Text))
end
--- Show a helper gate at a DCS#Vec3 position
-- @param DCS#Vec3 pos The position
-- @param #number heading Heading in degrees, can be 0..359 degrees
function UTILS.ShowHelperGate(pos, heading)
net.dostring_in("mission",string.format("a_show_helper_gate(%s, %s, %s, %f)", pos.x, pos.y, pos.z, math.rad(heading)))
end
--- Show a helper gate for a unit.
-- @param Wrapper.Unit#UNIT Unit The unit to show the gate for
-- @param #number Flag Helper gate flag
function UTILS.ShowHelperGateForUnit(Unit, Flag)
net.dostring_in("mission",string.format("a_show_route_gates_for_unit(%d, \"%d\")", Unit:GetID(), Flag))
end
--- Set the carrier illumination mode. -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY
-- @param #number UnitID Carrier unit ID ( UNIT:GetID() )
-- @param #number Mode Illumination mode, can be -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY
function UTILS.SetCarrierIlluminationMode(UnitID, Mode)
net.dostring_in("mission",string.format("a_set_carrier_illumination_mode(%d, %d)", UnitID, Mode))
end
--- Shell a zone, zone must ME created
-- @param #string name The name of the ME created zone
-- @param #number power Equals kg of TNT, e.g. 75
-- @param #count Number of shells simulated
function UTILS.ShellZone(name, power, count)
local z = UTILS.GetEnvZone(name)
if z then
net.dostring_in("mission",string.format("a_shelling_zone(%d, %d, %d)", z.zoneId, power, count))
end
end
--- Remove objects from a zone, zone must ME created
-- @param #string name The name of the ME created zone
-- @param #number type Type of objects to remove can be 0:all, 1: trees, 2:objects
function UTILS.RemoveObjects(name, type)
local z = UTILS.GetEnvZone(name)
if z then
net.dostring_in("mission",string.format("a_remove_scene_objects(%d, %d)", z.zoneId, type))
end
end
--- Remove scenery objects from a zone, zone must ME created
-- @param #string name The name of the ME created zone
-- @param #number level Level of removal
function UTILS.DestroyScenery(name, level)
local z = UTILS.GetEnvZone(name)
if z then
net.dostring_in("mission",string.format("a_scenery_destruction_zone(%d, %d)", z.zoneId, level))
end
end
--- Search for clear zones in a given area. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param DCS##Vec3 Center position vector for the search area.
-- @param #number SearchRadius Radius of the search area.
-- @param #number PosRadius Required clear radius around each position.
-- @param #number NumPositions Number of positions to find.
-- @return #table A table of DCS#Vec2 positions that are clear of map objects within the given PosRadius.
function UTILS.GetSimpleZones(Vec3, SearchRadius, PosRadius, NumPositions)
return Disposition.getSimpleZones(Vec3, SearchRadius, PosRadius, NumPositions)
end
--- Search for clear ground spawn zones within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param Core.Zone#ZONE Zone to search.
-- @param #number (Optional) PosRadius Required clear radius around each position. (Default is math.min(Radius/10, 200))
-- @param #number (Optional) NumPositions Number of positions to find. (Default 50)
-- @return #table A table of DCS#Vec2 positions that are clear of map objects within the given PosRadius. nil if no clear positions are found.
function UTILS.GetClearZonePositions(Zone, PosRadius, NumPositions)
local radius = PosRadius or math.min(Zone:GetRadius()/10, 200)
local clearPositions = UTILS.GetSimpleZones(Zone:GetVec3(), Zone:GetRadius(), radius, NumPositions or 50)
if clearPositions and #clearPositions > 0 then
local validZones = {}
for _, vec2 in pairs(clearPositions) do
if Zone:IsVec2InZone(vec2) then
table.insert(validZones, vec2)
end
end
if #validZones > 0 then
return validZones, radius
end
end
return nil
end
--- Search for a random clear ground spawn coordinate within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery.
-- @param Core.Zone#ZONE Zone to search.
-- @param #number PosRadius (Optional) Required clear radius around each position. (Default is math.min(Radius/10, 200))
-- @param #number NumPositions (Optional) Number of positions to find. (Default 50)
-- @return Core.Point#COORDINATE A random coordinate for a clear zone. nil if no clear positions are found.
-- @return #number Assigned radius for the found zones. nil if no clear positions are found.
function UTILS.GetRandomClearZoneCoordinate(Zone, PosRadius, NumPositions)
local clearPositions = UTILS.GetClearZonePositions(Zone, PosRadius, NumPositions)
if clearPositions and #clearPositions > 0 then
local randomPosition, radius = clearPositions[math.random(1, #clearPositions)]
return COORDINATE:NewFromVec2(randomPosition), radius
end
return nil
end
--- Find the point on the radius of a circle closest to a point outside of the radius.
-- @param DCS#Vec2 Vec1 Simple Vec2 marking the middle of the circle.
-- @param #number Radius The radius of the circle.
-- @param DCS#Vec2 Vec2 Simple Vec2 marking the point outside of the circle.
-- @return DCS#Vec2 Vec2 point on the radius.
function UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2)
local r = Radius
local cx = Vec1.x or 1
local cy = Vec1.y or 1
local px = Vec2.x or 1
local py = Vec2.y or 1
-- Berechne den Vektor vom Mittelpunkt zum externen Punkt
local dx = px - cx
local dy = py - cy
-- Berechne die Länge des Vektors
local dist = math.sqrt(dx * dx + dy * dy)
-- Wenn der Punkt im Mittelpunkt liegt, wähle einen Punkt auf der X-Achse
if dist == 0 then
return {x=cx + r, y=cy}
end
-- Normalisiere den Vektor (richtungsweise Vektor mit Länge 1)
local norm_dx = dx / dist
local norm_dy = dy / dist
-- Berechne den Punkt auf dem Rand des Kreises
local qx = cx + r * norm_dx
local qy = cy + r * norm_dy
local shift_factor = 1
qx = qx + shift_factor * norm_dx
qy = qy + shift_factor * norm_dy
return {x=qx, y=qy}
end
--- This function uses Disposition and other fallback logic to find better ground positions for ground units.
--- NOTE: This is not a spawn randomizer.
--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table.
--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit.
-- @param #table Positions A table of DCS#Vec2 or DCS#Vec3, can be a units table from the group template.
-- @param DCS#Vec2 Anchor (Optional) DCS#Vec2 or DCS#Vec3 as anchor point to calculate offset of the units.
-- @param #number MaxRadius (Optional) Max radius to search for valid ground locations in meters. Default is double the max radius of the units.
-- @param #number Spacing (Optional) Minimum spacing between units in meters. Default is 5% of the search radius or 5 meters, whichever is larger.
function UTILS.ValidateAndRepositionGroundUnits(Positions, Anchor, MaxRadius, Spacing)
local units = Positions
Anchor = Anchor or UTILS.GetCenterPoint(units)
local gPos = { x = Anchor.x, y = Anchor.z or Anchor.y }
local maxRadius = 0
local unitCount = 0
for _, unit in pairs(units) do
local pos = { x = unit.x, y = unit.z or unit.y }
local dist = UTILS.VecDist2D(pos, gPos)
if dist > maxRadius then
maxRadius = dist
end
unitCount = unitCount + 1
end
maxRadius = MaxRadius or math.max(maxRadius * 2, 10)
local spacing = Spacing or math.max(maxRadius * 0.05, 5)
if unitCount > 0 and maxRadius > 5 then
local spots = UTILS.GetSimpleZones(UTILS.Vec2toVec3(gPos), maxRadius, spacing, 1000)
if spots and #spots > 0 then
local validSpots = {}
for _, spot in pairs(spots) do -- Disposition sometimes returns points on roads, hence this filter.
if land.getSurfaceType(spot) == land.SurfaceType.LAND then
table.insert(validSpots, spot)
end
end
spots = validSpots
end
local step = spacing
for _, unit in pairs(units) do
local pos = { x = unit.x, y = unit.z or unit.y }
local isOnLand = land.getSurfaceType(pos) == land.SurfaceType.LAND
local isValid = false
if spots and #spots > 0 then
local si = 1
local sid = 0
local closestDist = 100000000
local closestSpot
for _, spot in pairs(spots) do
local dist = UTILS.VecDist2D(pos, spot)
if dist < closestDist then
closestDist = dist
closestSpot = spot
sid = si
end
si = si + 1
end
if closestSpot then
if closestDist >= spacing then
pos = closestSpot
end
isValid = true
table.remove(spots, sid)
end
end
-- Failsafe calculation
if not isValid and not isOnLand then
local h = UTILS.HdgTo(pos, gPos)
local retries = 0
while not isValid and retries < 500 do
local dist = UTILS.VecDist2D(pos, gPos)
pos = UTILS.Vec2Translate(pos, step, h)
local skip = false
for _, unit2 in pairs(units) do
if unit ~= unit2 then
local pos2 = { x = unit2.x, y = unit2.z or unit2.y }
local dist2 = UTILS.VecDist2D(pos, pos2)
if dist2 < 12 then
isValid = false
skip = true
break
end
end
end
if not skip and dist > step and land.getSurfaceType(pos) == land.SurfaceType.LAND then
isValid = true
break
elseif dist <= step then
break
end
retries = retries + 1
end
end
if isValid then
unit.x = pos.x
if unit.z then
unit.z = pos.y
else
unit.y = pos.y
end
end
end
end
end

View File

@ -611,6 +611,35 @@ AIRBASE.MarianaIslands = {
["Tinian_Intl"] = "Tinian Intl", ["Tinian_Intl"] = "Tinian Intl",
} }
--- Airbase of the Marianas WWII map
--
-- * AIRBASE.MarianaIslandsWWII.Agana
-- * AIRBASE.MarianaIslandsWWII.Airfield_3
-- * AIRBASE.MarianaIslandsWWII.Charon_Kanoa
-- * AIRBASE.MarianaIslandsWWII.Gurguan_Point
-- * AIRBASE.MarianaIslandsWWII.Isley
-- * AIRBASE.MarianaIslandsWWII.Kagman
-- * AIRBASE.MarianaIslandsWWII.Marpi
-- * AIRBASE.MarianaIslandsWWII.Orote
-- * AIRBASE.MarianaIslandsWWII.Pagan
-- * AIRBASE.MarianaIslandsWWII.Rota
-- * AIRBASE.MarianaIslandsWWII.Ushi
-- @field AIRBASE.MarianaIslandsWWII
AIRBASE.MarianaIslandsWWII =
{
["Agana"] = "Agana",
["Airfield_3"] = "Airfield 3",
["Charon_Kanoa"] = "Charon Kanoa",
["Gurguan_Point"] = "Gurguan Point",
["Isley"] = "Isley",
["Kagman"] = "Kagman",
["Marpi"] = "Marpi",
["Orote"] = "Orote",
["Pagan"] = "Pagan",
["Rota"] = "Rota",
["Ushi"] = "Ushi",
}
--- Airbases of the South Atlantic map: --- Airbases of the South Atlantic map:
-- --
-- * AIRBASE.SouthAtlantic.Almirante_Schroeders -- * AIRBASE.SouthAtlantic.Almirante_Schroeders
@ -707,15 +736,19 @@ AIRBASE.SouthAtlantic={
-- * AIRBASE.Sinai.Kibrit_Air_Base -- * AIRBASE.Sinai.Kibrit_Air_Base
-- * AIRBASE.Sinai.Kom_Awshim -- * AIRBASE.Sinai.Kom_Awshim
-- * AIRBASE.Sinai.Melez -- * AIRBASE.Sinai.Melez
-- * AIRBASE.Sinai.Mezzeh_Air_Base
-- * AIRBASE.Sinai.Nevatim -- * AIRBASE.Sinai.Nevatim
-- * AIRBASE.Sinai.Ovda -- * AIRBASE.Sinai.Ovda
-- * AIRBASE.Sinai.Palmachim -- * AIRBASE.Sinai.Palmachim
-- * AIRBASE.Sinai.Quwaysina -- * AIRBASE.Sinai.Quwaysina
-- * AIRBASE.Sinai.Rafic_Hariri_Intl
-- * AIRBASE.Sinai.Ramat_David
-- * AIRBASE.Sinai.Ramon_Airbase -- * AIRBASE.Sinai.Ramon_Airbase
-- * AIRBASE.Sinai.Ramon_International_Airport -- * AIRBASE.Sinai.Ramon_International_Airport
-- * AIRBASE.Sinai.Sde_Dov -- * AIRBASE.Sinai.Sde_Dov
-- * AIRBASE.Sinai.Sharm_El_Sheikh_International_Airport -- * AIRBASE.Sinai.Sharm_El_Sheikh_International_Airport
-- * AIRBASE.Sinai.St_Catherine -- * AIRBASE.Sinai.St_Catherine
-- * AIRBASE.Sinai.Tabuk
-- * AIRBASE.Sinai.Tel_Nof -- * AIRBASE.Sinai.Tel_Nof
-- * AIRBASE.Sinai.Wadi_Abu_Rish -- * AIRBASE.Sinai.Wadi_Abu_Rish
-- * AIRBASE.Sinai.Wadi_al_Jandali -- * AIRBASE.Sinai.Wadi_al_Jandali
@ -755,15 +788,19 @@ AIRBASE.Sinai = {
["Kibrit_Air_Base"] = "Kibrit Air Base", ["Kibrit_Air_Base"] = "Kibrit Air Base",
["Kom_Awshim"] = "Kom Awshim", ["Kom_Awshim"] = "Kom Awshim",
["Melez"] = "Melez", ["Melez"] = "Melez",
["Mezzeh_Air_Base"] = "Mezzeh Air Base",
["Nevatim"] = "Nevatim", ["Nevatim"] = "Nevatim",
["Ovda"] = "Ovda", ["Ovda"] = "Ovda",
["Palmachim"] = "Palmachim", ["Palmachim"] = "Palmachim",
["Quwaysina"] = "Quwaysina", ["Quwaysina"] = "Quwaysina",
["Rafic_Hariri_Intl"] = "Rafic Hariri Intl",
["Ramat_David"] = "Ramat David",
["Ramon_Airbase"] = "Ramon Airbase", ["Ramon_Airbase"] = "Ramon Airbase",
["Ramon_International_Airport"] = "Ramon International Airport", ["Ramon_International_Airport"] = "Ramon International Airport",
["Sde_Dov"] = "Sde Dov", ["Sde_Dov"] = "Sde Dov",
["Sharm_El_Sheikh_International_Airport"] = "Sharm El Sheikh International Airport", ["Sharm_El_Sheikh_International_Airport"] = "Sharm El Sheikh International Airport",
["St_Catherine"] = "St Catherine", ["St_Catherine"] = "St Catherine",
["Tabuk"] = "Tabuk",
["Tel_Nof"] = "Tel Nof", ["Tel_Nof"] = "Tel Nof",
["Wadi_Abu_Rish"] = "Wadi Abu Rish", ["Wadi_Abu_Rish"] = "Wadi Abu Rish",
["Wadi_al_Jandali"] = "Wadi al Jandali", ["Wadi_al_Jandali"] = "Wadi al Jandali",
@ -1438,7 +1475,7 @@ function AIRBASE:Register(AirbaseName)
self.descriptors=self:GetDesc() self.descriptors=self:GetDesc()
-- Debug info. -- Debug info.
--self:I({airbase=AirbaseName, descriptors=self.descriptors}) --self:T({airbase=AirbaseName, descriptors=self.descriptors})
-- Category. -- Category.
self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME
@ -2605,6 +2642,7 @@ function AIRBASE:_InitRunways(IncludeInverse)
--runway.name=string.format("%02d", tonumber(name)) --runway.name=string.format("%02d", tonumber(name))
runway.magheading=tonumber(runway.name)*10 runway.magheading=tonumber(runway.name)*10
runway.idx=runway.magheading
runway.heading=heading runway.heading=heading
runway.width=width or 0 runway.width=width or 0
runway.length=length or 0 runway.length=length or 0
@ -2917,6 +2955,7 @@ function AIRBASE:GetRunwayData(magvar, mark)
local runway={} --#AIRBASE.Runway local runway={} --#AIRBASE.Runway
runway.heading=hdg runway.heading=hdg
runway.idx=idx runway.idx=idx
runway.magheading=idx
runway.length=c1:Get2DDistance(c2) runway.length=c1:Get2DDistance(c2)
runway.position=c1 runway.position=c1
runway.endpoint=c2 runway.endpoint=c2
@ -2932,6 +2971,57 @@ function AIRBASE:GetRunwayData(magvar, mark)
-- Add runway. -- Add runway.
table.insert(runways, runway) table.insert(runways, runway)
end
-- Look for identical (parallel) runways, e.g. 03L and 03R at Nellis.
local rpairs={}
for i,_ri in pairs(runways) do
local ri=_ri --#AIRBASE.Runway
for j,_rj in pairs(runways) do
local rj=_rj --#AIRBASE.Runway
if i<j then
if ri.name==rj.name then
rpairs[i]=j
end
end
end
end
local function isLeft(a, b, c)
--return ((b.x - a.x)*(c.z - a.z) - (b.z - a.z)*(c.x - a.x)) > 0
return ((b.z - a.z)*(c.x - a.x) - (b.x - a.x)*(c.z - a.z)) > 0
end
for i,j in pairs(rpairs) do
local ri=runways[i] --#AIRBASE.Runway
local rj=runways[j] --#AIRBASE.Runway
-- Draw arrow.
--ri.center:ArrowToAll(rj.center)
local c0=ri.position
-- Vector in the direction of the runway.
local a=UTILS.VecTranslate(c0, 1000, ri.heading)
-- Vector from runway i to runway j.
local b=UTILS.VecSubstract(rj.position, ri.position)
b=UTILS.VecAdd(ri.position, b)
-- Check if rj is left of ri.
local left=isLeft(c0, a, b)
--env.info(string.format("Found pair %s: i=%d, j=%d, left==%s", ri.name, i, j, tostring(left)))
if left then
ri.isLeft=false
rj.isLeft=true
else
ri.isLeft=true
rj.isLeft=false
end
--break
end end
return runways return runways

View File

@ -168,16 +168,25 @@
-- * @{#CONTROLLABLE.OptionAlarmStateGreen} -- * @{#CONTROLLABLE.OptionAlarmStateGreen}
-- * @{#CONTROLLABLE.OptionAlarmStateRed} -- * @{#CONTROLLABLE.OptionAlarmStateRed}
-- --
-- ## 5.4) Jettison weapons: -- ## 5.4) [AIR] Jettison weapons:
-- --
-- * @{#CONTROLLABLE.OptionAllowJettisonWeaponsOnThreat} -- * @{#CONTROLLABLE.OptionAllowJettisonWeaponsOnThreat}
-- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat} -- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat}
-- --
-- ## 5.5) Air-2-Air missile attack range: -- ## 5.5) [AIR] Air-2-Air missile attack range:
-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets. -- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets.
-- --
-- # 6) [GROUND] IR Maker Beacons for GROUPs and UNITs -- # 6) [GROUND] IR Maker Beacons for GROUPs and UNITs
-- * @{#CONTROLLABLE:NewIRMarker}(): Create a blinking IR Marker on a GROUP or UNIT. -- * @{#CONTROLLABLE:NewIRMarker}(): Create a blinking IR Marker on a GROUP or UNIT.
--
-- # 7) [HELICOPTER] Units prefer vertical landing and takeoffs:
-- * @{#CONTROLLABLE.OptionPreferVerticalLanding}(): Set aircraft to prefer vertical landing and takeoff.
--
-- # 8) [AIRCRAFT] Landing approach options
-- * @{#CONTROLLABLE.SetOptionLandingStraightIn}(): Landing approach straight in.
-- * @{#CONTROLLABLE.SetOptionLandingForcePair}(): Landing approach in pairs for groups > 1 unit.
-- * @{#CONTROLLABLE.SetOptionLandingRestrictPair}(): Landing approach single.
-- * @{#CONTROLLABLE.SetOptionLandingOverheadBreak}(): Landing approach overhead break.
-- --
-- @field #CONTROLLABLE -- @field #CONTROLLABLE
CONTROLLABLE = { CONTROLLABLE = {
@ -1432,7 +1441,7 @@ end
-- @param #number Speed The speed [m/s] flying when holding the position. -- @param #number Speed The speed [m/s] flying when holding the position.
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed )
self:F2( { self.ControllableName, Point, Altitude, Speed } ) --self:F2( { self.ControllableName, Point, Altitude, Speed } )
local DCSTask = { local DCSTask = {
id = 'Orbit', id = 'Orbit',
@ -3629,6 +3638,26 @@ function CONTROLLABLE:OptionROTPassiveDefense()
return nil return nil
end end
--- Helicopter - prefer vertical landing.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionPreferVerticalLanding()
self:F2( { self.ControllableName } )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if self:IsAir() then
Controller:setOption( AI.Option.Air.id.PREFER_VERTICAL, true )
end
return self
end
return nil
end
--- Can the CONTROLLABLE evade on enemy fire? --- Can the CONTROLLABLE evade on enemy fire?
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @return #boolean -- @return #boolean
@ -4183,6 +4212,50 @@ function CONTROLLABLE:OptionEngageRange( EngageRange )
return nil return nil
end end
--- [AIR] Set how the AI lands on an airfield. Here: Straight in.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetOptionLandingStraightIn()
self:F2( { self.ControllableName } )
if self:IsAir() then
self:SetOption("36","0")
end
return self
end
--- [AIR] Set how the AI lands on an airfield. Here: In pairs (if > 1 aircraft in group)
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetOptionLandingForcePair()
self:F2( { self.ControllableName } )
if self:IsAir() then
self:SetOption("36","1")
end
return self
end
--- [AIR] Set how the AI lands on an airfield. Here: No landing in pairs.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetOptionLandingRestrictPair()
self:F2( { self.ControllableName } )
if self:IsAir() then
self:SetOption("36","2")
end
return self
end
--- [AIR] Set how the AI lands on an airfield. Here: Overhead break.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetOptionLandingOverheadBreak()
self:F2( { self.ControllableName } )
if self:IsAir() then
self:SetOption("36","3")
end
return self
end
--- [AIR] Set how the AI uses the onboard radar. --- [AIR] Set how the AI uses the onboard radar.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param #number Option Options are: `NEVER = 0, FOR_ATTACK_ONLY = 1,FOR_SEARCH_IF_REQUIRED = 2, FOR_CONTINUOUS_SEARCH = 3` -- @param #number Option Options are: `NEVER = 0, FOR_ATTACK_ONLY = 1,FOR_SEARCH_IF_REQUIRED = 2, FOR_CONTINUOUS_SEARCH = 3`

View File

@ -230,6 +230,7 @@ GROUP.Attribute = {
GROUND_EWR="Ground_EWR", GROUND_EWR="Ground_EWR",
GROUND_AAA="Ground_AAA", GROUND_AAA="Ground_AAA",
GROUND_SAM="Ground_SAM", GROUND_SAM="Ground_SAM",
GROUND_SHORAD="Ground_SHORAD",
GROUND_OTHER="Ground_OtherGround", GROUND_OTHER="Ground_OtherGround",
NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier", NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier",
NAVAL_WARSHIP="Naval_WarShip", NAVAL_WARSHIP="Naval_WarShip",

View File

@ -246,18 +246,20 @@ end
function POSITIONABLE:GetVec3() function POSITIONABLE:GetVec3()
local DCSPositionable = self:GetDCSObject() local DCSPositionable = self:GetDCSObject()
if DCSPositionable then if DCSPositionable then
--local status, vec3 = pcall(
-- function()
-- local vec3 = DCSPositionable:getPoint()
-- return vec3
--end
--)
local vec3 = DCSPositionable:getPoint() local vec3 = DCSPositionable:getPoint()
--if status then
return vec3 if not vec3 then
--else local pos = DCSPositionable:getPosition()
--self:E( { "Cannot get Vec3 from DCS Object", Positionable = self, Alive = self:IsAlive() } ) if pos and pos.p then
--end vec3 = pos.p
else
self:E( { "Cannot get the position from DCS Object for GetVec3", Positionable = self, Alive = self:IsAlive() } )
end
end
return vec3
end end
-- ERROR! -- ERROR!
self:E( { "Cannot get the Positionable DCS Object for GetVec3", Positionable = self, Alive = self:IsAlive() } ) self:E( { "Cannot get the Positionable DCS Object for GetVec3", Positionable = self, Alive = self:IsAlive() } )
@ -388,13 +390,13 @@ function POSITIONABLE:GetCoordinate()
-- Get the current position. -- Get the current position.
local PositionableVec3 = self:GetVec3() local PositionableVec3 = self:GetVec3()
if PositionableVec3 then
local coord=COORDINATE:NewFromVec3(PositionableVec3) local coord=COORDINATE:NewFromVec3(PositionableVec3)
local heading = self:GetHeading() local heading = self:GetHeading()
coord.Heading = heading coord.Heading = heading
-- Return a new coordiante object. -- Return a new coordiante object.
return coord return coord
end
end end
-- Error message. -- Error message.

View File

@ -1924,3 +1924,17 @@ function UNIT:IsAAA()
end end
return false return false
end end
--- Set the relative life points of a UNIT object
-- @param #UNIT self
-- @param #number Percent Percent to set, can be 0..100.
function UNIT:SetLife(Percent)
net.dostring_in("mission",string.format("a_unit_set_life_percentage(%d, %f)", self:GetID(), Percent))
end
--- Set the carrier illumination mode. -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY
-- @param #UNIT self
-- @param #number Mode Illumination mode, can be -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY
function UNIT:SetCarrierIlluminationMode(Mode)
UTILS.SetCarrierIlluminationMode(self:GetID(), Mode)
end