Merge branch 'FF/Ops' into FF/OpsDev

This commit is contained in:
Frank 2024-06-19 07:53:54 +02:00
commit fa8e1d2a56
29 changed files with 1396 additions and 638 deletions

View File

@ -47,6 +47,7 @@ jobs:
- name: Update apt-get (needed for act docker image)
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get -qq update
- name: Install tree

View File

@ -38,11 +38,13 @@
-- @type BEACON
-- @field #string ClassName Name of the class "BEACON".
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{Wrapper.Controllable#CONTROLLABLE} that will receive radio capabilities.
-- @field #number UniqueName Counter to make the unique naming work.
-- @extends Core.Base#BASE
BEACON = {
ClassName = "BEACON",
Positionable = nil,
name = nil,
UniqueName = 0,
}
--- Beacon types supported by DCS.
@ -385,6 +387,8 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
local IsValid = false
Modulation = Modulation or radio.modulation.AM
-- Check the filename
if type(FileName) == "string" then
if FileName:find(".ogg") or FileName:find(".wav") then
@ -395,7 +399,7 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
end
end
if not IsValid then
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
self:E({"File name invalid. Maybe something wrong with the extension? ", FileName})
end
-- Check the Frequency
@ -421,7 +425,9 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
if IsValid then
self:T2({"Activating Beacon on ", Frequency, Modulation})
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
BEACON.UniqueName = BEACON.UniqueName + 1
self.BeaconName = "MooseBeacon"..tostring(BEACON.UniqueName)
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, self.BeaconName)
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New( nil,
@ -430,6 +436,7 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
end, {}, BeaconDuration)
end
end
return self
end
--- Stops the Radio Beacon
@ -438,7 +445,7 @@ end
function BEACON:StopRadioBeacon()
self:F()
-- The unique name of the transmission is the class ID
trigger.action.stopRadioTransmission(tostring(self.ID))
trigger.action.stopRadioTransmission(self.BeaconName)
return self
end

View File

@ -20,7 +20,7 @@
--
-- @module Core.ClientMenu
-- @image Core_Menu.JPG
-- last change: Apr 2024
-- last change: May 2024
-- TODO
----------------------------------------------------------------------------------------------------------------
@ -691,7 +691,7 @@ function CLIENTMENUMANAGER:Propagate(Client)
local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then
local playerunit = client:GetName()
local playergroup = client:GetGroup()
--local playergroup = client:GetGroup()
local playername = client:GetPlayerName() or "none"
if not knownunits[playerunit] then
knownunits[playerunit] = true

View File

@ -1147,10 +1147,13 @@ end
-- @param #string GroupName Group name.
-- @return #table Group template table.
function DATABASE:GetGroupTemplate( GroupName )
local GroupTemplate = self.Templates.Groups[GroupName].Template
local GroupTemplate=nil
if self.Templates.Groups[GroupName] then
GroupTemplate = self.Templates.Groups[GroupName].Template
GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID
GroupTemplate.SpawnCategoryID = self.Templates.Groups[GroupName].CategoryID
GroupTemplate.SpawnCountryID = self.Templates.Groups[GroupName].CountryID
end
return GroupTemplate
end

View File

@ -1301,7 +1301,7 @@ function EVENT:onEvent( Event )
-- STATIC
---
Event.TgtDCSUnit = Event.target
if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object
if Event.target.isExist and Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object, check that isExist exists (Kiowa Hellfire issue, Special K)
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
-- Workaround for borked target info on cruise missiles
if Event.TgtDCSUnitName and Event.TgtDCSUnitName ~= "" then

View File

@ -2,7 +2,7 @@
--
-- ===
--
-- ### Features:
-- ## Features:
--
-- * Setup mission sub menus.
-- * Setup mission command menus.
@ -44,7 +44,11 @@
-- * @{Core.Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs.
--
-- ===
---
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Menu)
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
@ -166,7 +170,8 @@ function MENU_INDEX:Refresh( Group )
end
do -- MENU_BASE
--- @type MENU_BASE
---
-- @type MENU_BASE
-- @extends Core.Base#BASE
--- Defines the main MENU class where other MENU classes are derived from.
-- This is an abstract class, so don't use it.
@ -206,6 +211,7 @@ do -- MENU_BASE
return self
end
function MENU_BASE:SetParentMenu( MenuText, Menu )
if self.ParentMenu then
self.ParentMenu.Menus = self.ParentMenu.Menus or {}
@ -277,8 +283,10 @@ do -- MENU_BASE
end
end
do -- MENU_COMMAND_BASE
--- @type MENU_COMMAND_BASE
do
---
-- MENU_COMMAND_BASE
-- @type MENU_COMMAND_BASE
-- @field #function MenuCallHandler
-- @extends Core.Menu#MENU_BASE
@ -343,8 +351,10 @@ do -- MENU_COMMAND_BASE
end
end
do -- MENU_MISSION
--- @type MENU_MISSION
do
---
-- MENU_MISSION
-- @type MENU_MISSION
-- @extends Core.Menu#MENU_BASE
--- Manages the main menus for a complete mission.
--
@ -509,8 +519,9 @@ do -- MENU_MISSION_COMMAND
return self
end
end
do -- MENU_COALITION
--- @type MENU_COALITION
do
--- MENU_COALITION
-- @type MENU_COALITION
-- @extends Core.Menu#MENU_BASE
--- Manages the main menus for DCS.coalition.
@ -635,9 +646,10 @@ do -- MENU_COALITION
return self
end
end
do -- MENU_COALITION_COMMAND
do
--- @type MENU_COALITION_COMMAND
--- MENU_COALITION_COMMAND
-- @type MENU_COALITION_COMMAND
-- @extends Core.Menu#MENU_COMMAND_BASE
--- Manages the command menus for coalitions, which allow players to execute functions during mission execution.
@ -725,8 +737,11 @@ do
-- So every menu for a client created must be tracked so that program logic accidentally does not create.
-- the same menus twice during initialization logic.
-- These menu classes are handling this logic with this variable.
local _MENUGROUPS = {}
--- @type MENU_GROUP
---
-- @type MENU_GROUP
-- @extends Core.Menu#MENU_BASE
@ -757,7 +772,7 @@ do
-- MenuStatus[MenuGroupName]:Remove()
-- end
--
-- --- @param Wrapper.Group#GROUP MenuGroup
-- -- @param Wrapper.Group#GROUP MenuGroup
-- local function AddStatusMenu( MenuGroup )
-- local MenuGroupName = MenuGroup:GetName()
-- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object.
@ -899,8 +914,8 @@ do
return self
end
--- @type MENU_GROUP_COMMAND
---
-- @type MENU_GROUP_COMMAND
-- @extends Core.Menu#MENU_COMMAND_BASE
--- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution.
@ -983,7 +998,8 @@ do
end
--- MENU_GROUP_DELAYED
do
--- @type MENU_GROUP_DELAYED
---
-- @type MENU_GROUP_DELAYED
-- @extends Core.Menu#MENU_BASE
@ -1034,6 +1050,7 @@ do
-- @param #MENU_GROUP_DELAYED self
-- @return #MENU_GROUP_DELAYED
function MENU_GROUP_DELAYED:Set()
if not self.GroupID then return end
do
if not self.MenuSet then
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
@ -1106,8 +1123,8 @@ do
return self
end
--- @type MENU_GROUP_COMMAND_DELAYED
---
-- @type MENU_GROUP_COMMAND_DELAYED
-- @extends Core.Menu#MENU_COMMAND_BASE
--- Manages the command menus for coalitions, which allow players to execute functions during mission execution.

View File

@ -516,10 +516,10 @@ function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,G
end
_MESSAGESRS.label = Label or MSRS.Label or "MESSAGE"
_MESSAGESRS.MSRS:SetLabel(Label or "MESSAGE")
_MESSAGESRS.MSRS:SetLabel(_MESSAGESRS.label)
_MESSAGESRS.port = Port or MSRS.port or 5002
_MESSAGESRS.MSRS:SetPort(Port or 5002)
_MESSAGESRS.MSRS:SetPort(_MESSAGESRS.port)
_MESSAGESRS.volume = Volume or MSRS.volume or 1
_MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume)

View File

@ -1516,6 +1516,7 @@ do
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnDeadOrCrash )
if self.Filter.Zones then
self.ZoneTimer = TIMER:New(self._ContinousZoneFilter,self)
local timing = self.ZoneTimerInterval or 30
@ -3477,7 +3478,7 @@ do -- SET_STATIC
--- Add STATIC(s) to SET_STATIC.
-- @param #SET_STATIC self
-- @param #string AddStatic A single STATIC.
-- @param Wrapper.Static#STATIC AddStatic A single STATIC.
-- @return #SET_STATIC self
function SET_STATIC:AddStatic( AddStatic )
self:F2( AddStatic:GetName() )

View File

@ -202,19 +202,19 @@
--
-- ### Link-16 Datalink STN and SADL IDs (limited at the moment to F15/16/18/AWACS/Tanker/B1B, but not the F15E for clients, SADL A10CII only)
--
-- *{#SPAWN.InitSTN}(): Set the STN of the first unit in the group. All other units will have consecutive STNs, provided they have not been used yet.
-- *{#SPAWN.InitSADL}(): Set the SADL of the first unit in the group. All other units will have consecutive SADLs, provided they have not been used yet.
-- * @{#SPAWN.InitSTN}(): Set the STN of the first unit in the group. All other units will have consecutive STNs, provided they have not been used yet.
-- * @{#SPAWN.InitSADL}(): Set the SADL of the first unit in the group. All other units will have consecutive SADLs, provided they have not been used yet.
--
-- ### Callsigns
--
-- *{#SPAWN.InitRandomizeCallsign}(): Set a random callsign name per spawn.
-- *{#SPAWN.SpawnInitCallSign}(): Set a specific callsign for a spawned group.
-- * @{#SPAWN.InitRandomizeCallsign}(): Set a random callsign name per spawn.
-- * @{#SPAWN.SpawnInitCallSign}(): Set a specific callsign for a spawned group.
--
-- ### Speed
--
-- *{#SPAWN.InitSpeedMps}(): Set the initial speed on spawning in meters per second.
-- *{#SPAWN.InitSpeedKph}(): Set the initial speed on spawning in kilometers per hour.
-- *{#SPAWN.InitSpeedKnots}(): Set the initial speed on spawning in knots.
-- * @{#SPAWN.InitSpeedMps}(): Set the initial speed on spawning in meters per second.
-- * @{#SPAWN.InitSpeedKph}(): Set the initial speed on spawning in kilometers per hour.
-- * @{#SPAWN.InitSpeedKnots}(): Set the initial speed on spawning in knots.
--
-- ## SPAWN **Spawn** methods
--
@ -620,12 +620,14 @@ end
-- and any spaces before and after the resulting name are removed.
-- IMPORTANT! This method MUST be the first used after :New !!!
-- @param #SPAWN self
-- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided to make new unit names.
-- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided create new unit names.
-- @return #SPAWN self
function SPAWN:InitKeepUnitNames( KeepUnitNames )
self:F()
self.SpawnInitKeepUnitNames = KeepUnitNames or true
self.SpawnInitKeepUnitNames = false
if KeepUnitNames == true then self.SpawnInitKeepUnitNames = true end
return self
end
@ -1209,11 +1211,12 @@ end
-- @param #number Major Major number, i.e. the group number of this name, e.g. 1 - resulting in e.g. Texaco-2-1
-- @return #SPAWN self
function SPAWN:InitCallSign(ID,Name,Minor,Major)
local Name = Name or "Enfield"
self.SpawnInitCallSign = true
self.SpawnInitCallSignID = ID or 1
self.SpawnInitCallSignMinor = Minor or 1
self.SpawnInitCallSignMajor = Major or 1
self.SpawnInitCallSignName = string.lower(Name) or "enfield"
self.SpawnInitCallSignName=string.lower(Name):gsub("^%l", string.upper)
return self
end
@ -1609,8 +1612,8 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
numTries = numTries + 1
inZone = SpawnZone:IsVec2InZone(RandomVec2)
self:I("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!")
self:I(SpawnZone)
--self:I("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!")
--self:I(SpawnZone)
end
end
if (not inZone) then
@ -3437,7 +3440,9 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
if self.SpawnInitKeepUnitNames == false then
for UnitID = 1, #SpawnTemplate.units do
if not string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true) then --Razbam IFF hack for F15E etc
SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID )
end
SpawnTemplate.units[UnitID].unitId = nil
end
else
@ -3449,11 +3454,13 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
local UnitPrefix, Rest
if SpawnInitKeepUnitIFF == false then
UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" )
self:T( { UnitPrefix, Rest } )
else
UnitPrefix=SpawnTemplate.units[UnitID].name
end
SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
self:T( { UnitPrefix, Rest } )
--else
--UnitPrefix=SpawnTemplate.units[UnitID].name
end
--SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
SpawnTemplate.units[UnitID].unitId = nil
end
end

View File

@ -3090,10 +3090,26 @@ function ZONE_POLYGON:NewFromDrawing(DrawingName)
-- points for the drawings are saved in local space, so add the object's map x and y coordinates to get
-- world space points we can use
for _, point in UTILS.spairs(object["points"]) do
-- check if we want to skip adding a point
local skip = false
local p = {x = object["mapX"] + point["x"],
y = object["mapY"] + point["y"] }
-- Check if the same coordinates already exist in the list, skip if they do
-- This can happen when drawing a Polygon in Free mode, DCS adds points on
-- top of each other that are in the `mission` file, but not visible in the
-- Mission Editor
for _, pt in pairs(points) do
if pt.x == p.x and pt.y == p.y then
skip = true
end
end
-- if it's a unique point, add it
if not skip then
table.add(points, p)
end
end
elseif object["polygonMode"] == "rect" then
-- the points for a rect are saved as local coordinates with an angle. To get the world space points from this
-- we need to rotate the points around the center of the rects by an angle. UTILS.RotatePointAroundPivot was
@ -3110,6 +3126,7 @@ function ZONE_POLYGON:NewFromDrawing(DrawingName)
points = {p1, p2, p3, p4}
else
-- bring the Arrow code over from Shape/Polygon
-- something else that might be added in the future
end
end

View File

@ -74,7 +74,7 @@
-- @image Designation.JPG
--
-- Date: 24 Oct 2021
-- Last Update: Jan 2024
-- Last Update: May 2024
--
--- Class AUTOLASE
-- @type AUTOLASE
@ -88,6 +88,7 @@
-- @field #table LaserCodes
-- @field #table playermenus
-- @field #boolean smokemenu
-- @field #boolean threatmenu
-- @extends Ops.Intel#INTEL
---
@ -117,7 +118,7 @@ AUTOLASE = {
--- AUTOLASE class version.
-- @field #string version
AUTOLASE.version = "0.1.23"
AUTOLASE.version = "0.1.25"
-------------------------------------------------------------------
-- Begin Functional.Autolase.lua
@ -205,6 +206,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
self:SetLaserCodes( { 1688, 1130, 4785, 6547, 1465, 4578 } ) -- set self.LaserCodes
self.playermenus = {}
self.smokemenu = true
self.threatmenu = true
-- Set some string id for output to DCS.log file.
self.lid=string.format("AUTOLASE %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
@ -323,12 +325,15 @@ end
function AUTOLASE:SetPilotMenu()
if self.usepilotset then
local pilottable = self.pilotset:GetSetObjects() or {}
local grouptable = {}
for _,_unit in pairs (pilottable) do
local Unit = _unit -- Wrapper.Unit#UNIT
if Unit and Unit:IsAlive() then
local Group = Unit:GetGroup()
local GroupName = Group:GetName() or "none"
local unitname = Unit:GetName()
if self.playermenus[unitname] then self.playermenus[unitname]:Remove() end
if not grouptable[GroupName] == true then
if self.playermenus[unitname] then self.playermenus[unitname]:Remove() end -- menus
local lasetopm = MENU_GROUP:New(Group,"Autolase",nil)
self.playermenus[unitname] = lasetopm
local lasemenu = MENU_GROUP_COMMAND:New(Group,"Status",lasetopm,self.ShowStatus,self,Group,Unit)
@ -336,7 +341,14 @@ function AUTOLASE:SetPilotMenu()
local smoke = (self.smoketargets == true) and "off" or "on"
local smoketext = string.format("Switch smoke targets to %s",smoke)
local smokemenu = MENU_GROUP_COMMAND:New(Group,smoketext,lasetopm,self.SetSmokeTargets,self,(not self.smoketargets))
end
end -- smokement
if self.threatmenu then
local threatmenutop = MENU_GROUP:New(Group,"Set min lasing threat",lasetopm)
for i=0,10,2 do
local text = "Threatlevel "..tostring(i)
local threatmenu = MENU_GROUP_COMMAND:New(Group,text,threatmenutop,self.SetMinThreatLevel,self,i)
end -- threatlevel
end -- threatmenu
for _,_grp in pairs(self.RecceSet.Set) do
local grp = _grp -- Wrapper.Group#GROUP
local unit = grp:GetUnit(1)
@ -350,12 +362,14 @@ function AUTOLASE:SetPilotMenu()
local text = tostring(_code)
if _code == code then text = text.."(*)" end
local changemenu = MENU_GROUP_COMMAND:New(Group,text,unittop,self.SetRecceLaserCode,self,name,_code,true)
end
end
end
end -- Codes
end -- unit alive
end -- Recceset
grouptable[GroupName] = true
end -- grouptable[GroupName]
--lasemenu:Refresh()
end
end
end -- unit alive
end -- pilot loop
else
if not self.NoMenus then
self.Menu = MENU_COALITION_COMMAND:New(self.coalition,"Autolase",nil,self.ShowStatus,self)
@ -602,6 +616,21 @@ function AUTOLASE:DisableSmokeMenu()
return self
end
--- (User) Show the "Switch min threat lasing..." menu entry for pilots. On by default.
-- @param #AUTOLASE self
-- @return #AUTOLASE self
function AUTOLASE:EnableThreatLevelMenu()
self.threatmenu = true
return self
end
--- (User) Do not show the "Switch min threat lasing..." menu entry for pilots.
-- @param #AUTOLASE self
-- @return #AUTOLASE self
function AUTOLASE:DisableThreatLevelMenu()
self.threatmenu = false
return self
end
--- (Internal) Function to calculate line of sight.
-- @param #AUTOLASE self
@ -730,6 +759,7 @@ function AUTOLASE:ShowStatus(Group,Unit)
report:Add(string.format("Recce %s has code %d",name,code))
end
end
report:Add(string.format("Lasing min threat level %d",self.minthreatlevel))
local lines = 0
for _ind,_entry in pairs(self.CurrentLasing) do
local entry = _entry -- #AUTOLASE.LaserSpot

View File

@ -184,7 +184,7 @@
do -- DESIGNATE
--- @type DESIGNATE
-- @type DESIGNATE
-- @extends Core.Fsm#FSM_PROCESS
--- Manage the designation of detected targets.
@ -525,7 +525,7 @@ do -- DESIGNATE
self.AttackSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP AttackGroup
-- @param Wrapper.Group#GROUP AttackGroup
function( AttackGroup )
self.FlashStatusMenu[AttackGroup] = FlashMenu
end
@ -554,7 +554,7 @@ do -- DESIGNATE
self.AttackSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP AttackGroup
-- @param Wrapper.Group#GROUP AttackGroup
function( AttackGroup )
self.FlashDetectionMessage[AttackGroup] = FlashDetectionMessage
end
@ -826,7 +826,7 @@ do -- DESIGNATE
-- This Detection is obsolete, remove from the designate scope
self.Designating[DesignateIndex] = nil
self.AttackSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP AttackGroup
-- @param Wrapper.Group#GROUP AttackGroup
function( AttackGroup )
if AttackGroup:IsAlive() == true then
local DetectionText = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup ):Text( ", " )
@ -903,7 +903,7 @@ do -- DESIGNATE
self.AttackSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP GroupReport
-- @param Wrapper.Group#GROUP GroupReport
function( AttackGroup )
if self.FlashStatusMenu[AttackGroup] or ( MenuAttackGroup and ( AttackGroup:GetName() == MenuAttackGroup:GetName() ) ) then
@ -1060,7 +1060,7 @@ do -- DESIGNATE
self.AttackSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP GroupReport
-- @param Wrapper.Group#GROUP GroupReport
function( AttackGroup )
self:ScheduleOnce( Delay, self.SetMenu, self, AttackGroup )
@ -1198,7 +1198,7 @@ do -- DESIGNATE
--local ReportTypes = REPORT:New()
--local ReportLaserCodes = REPORT:New()
TargetSetUnit:Flush( self )
--TargetSetUnit:Flush( self )
--self:F( { Recces = self.Recces } )
for TargetUnit, RecceData in pairs( self.Recces ) do
@ -1229,10 +1229,12 @@ do -- DESIGNATE
end
end
if TargetSetUnit == nil then return end
if self.AutoLase or ( not self.AutoLase and ( self.LaseStart + Duration >= timer.getTime() ) ) then
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
--- @param Wrapper.Unit#UNIT SmokeUnit
-- @param Wrapper.Unit#UNIT SmokeUnit
function( TargetUnit )
self:F( { TargetUnit = TargetUnit:GetName() } )
@ -1253,7 +1255,7 @@ do -- DESIGNATE
local RecceUnit = UnitData -- Wrapper.Unit#UNIT
local RecceUnitDesc = RecceUnit:GetDesc()
--self:F( { RecceUnit = RecceUnit:GetName(), RecceDescription = RecceUnitDesc } )
--self:F( { RecceUnit = RecceUnit:GetName(), RecceDescription = RecceUnitDesc } )x
if RecceUnit:IsLasing() == false then
--self:F( { IsDetected = RecceUnit:IsDetected( TargetUnit ), IsLOS = RecceUnit:IsLOS( TargetUnit ) } )
@ -1275,9 +1277,10 @@ do -- DESIGNATE
local Spot = RecceUnit:LaseUnit( TargetUnit, LaserCode, Duration )
local AttackSet = self.AttackSet
local DesignateName = self.DesignateName
local typename = TargetUnit:GetTypeName()
function Spot:OnAfterDestroyed( From, Event, To )
self.Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() .. " destroyed. " .. TargetSetUnit:Count() .. " targets left.",
self.Recce:MessageToSetGroup( "Target " ..typename .. " destroyed. " .. TargetSetUnit:CountAlive() .. " targets left.",
5, AttackSet, self.DesignateName )
end
@ -1285,7 +1288,7 @@ do -- DESIGNATE
-- OK. We have assigned for the Recce a TargetUnit. We can exit the function.
MarkingCount = MarkingCount + 1
local TargetUnitType = TargetUnit:GetTypeName()
RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
RecceUnit:MessageToSetGroup( "Marking " .. TargetUnitType .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
10, self.AttackSet, DesignateName )
if not MarkedTypes[TargetUnitType] then
MarkedTypes[TargetUnitType] = true
@ -1392,7 +1395,7 @@ do -- DESIGNATE
local MarkedCount = 0
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
--- @param Wrapper.Unit#UNIT SmokeUnit
-- @param Wrapper.Unit#UNIT SmokeUnit
function( SmokeUnit )
if MarkedCount < self.MaximumMarkings then
@ -1457,10 +1460,11 @@ do -- DESIGNATE
-- @param #DESIGNATE self
-- @return #DESIGNATE
function DESIGNATE:onafterDoneSmoking( From, Event, To, Index )
if self.Designating[Index] ~= nil then
self.Designating[Index] = string.gsub( self.Designating[Index], "S", "" )
self:SetDesignateMenu()
end
end
--- DoneIlluminating
-- @param #DESIGNATE self
@ -1472,5 +1476,3 @@ do -- DESIGNATE
end
end

View File

@ -545,7 +545,7 @@ do -- DETECTION_BASE
-- @param #string To The To State string.
function DETECTION_BASE:onafterDetect( From, Event, To )
local DetectDelay = 0.1
local DetectDelay = 0.15
self.DetectionCount = 0
self.DetectionRun = 0
self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table
@ -604,7 +604,7 @@ do -- DETECTION_BASE
-- @param #number DetectionTimeStamp Time stamp of detection event.
function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp )
-- self:F( { DetectedObjects = self.DetectedObjects } )
self:I( { DetectedObjects = self.DetectedObjects } )
self.DetectionRun = self.DetectionRun + 1
@ -612,14 +612,14 @@ do -- DETECTION_BASE
if Detection and Detection:IsAlive() then
-- self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } )
self:I( { "DetectionGroup is Alive", Detection:GetName() } )
local DetectionGroupName = Detection:GetName()
local DetectionUnit = Detection:GetUnit( 1 )
local DetectedUnits = {}
local DetectedTargets = Detection:GetDetectedTargets(
local DetectedTargets = DetectionUnit:GetDetectedTargets(
self.DetectVisual,
self.DetectOptical,
self.DetectRadar,
@ -628,7 +628,9 @@ do -- DETECTION_BASE
self.DetectDLINK
)
self:F( { DetectedTargets = DetectedTargets } )
--self:I( { DetectedTargets = DetectedTargets } )
--self:I(UTILS.PrintTableToLog(DetectedTargets))
for DetectionObjectID, Detection in pairs( DetectedTargets ) do
local DetectedObject = Detection.object -- DCS#Object

View File

@ -22,7 +22,7 @@
-- @module Functional.Mantis
-- @image Functional.Mantis.jpg
--
-- Last Update: Feb 2024
-- Last Update: May 2024
-------------------------------------------------------------------------
--- **MANTIS** class, extends Core.Base#BASE
@ -58,6 +58,7 @@
-- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled
-- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range
-- @field #number ShoradActDistance Distance of an attacker in meters from a Mantis SAM site, on which Shorad will be switched on. Useful to not give away Shorad sites too early. Default 15km. Should be smaller than checkradius.
-- @field #boolean checkforfriendlies If true, do not activate a SAM installation if a friendly aircraft is in firing range.
-- @extends Core.Base#BASE
@ -187,29 +188,34 @@
-- -- This is effectively a 3-stage filter allowing for zone overlap. A coordinate is accepted first when
-- -- it is inside any AcceptZone. Then RejectZones are checked, which enforces both borders, but also overlaps of
-- -- Accept- and RejectZones. Last, if it is inside a conflict zone, it is accepted.
-- `mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones)`
-- mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones)
--
--
-- ### 2.1.2 Change the number of long-, mid- and short-range systems going live on a detected target:
--
-- -- parameters are numbers. Defaults are 1,2,2,6 respectively
-- `mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic)`
-- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic)
--
-- ### 2.1.3 SHORAD will automatically be added from SAM sites of type "short-range"
--
-- ### 2.1.4 Advanced features
--
-- -- switch off auto mode **before** you start MANTIS.
-- `mybluemantis.automode = false`
-- mybluemantis.automode = false
--
-- -- switch off auto shorad **before** you start MANTIS.
-- `mybluemantis.autoshorad = false`
-- mybluemantis.autoshorad = false
--
-- -- scale of the activation range, i.e. don't activate at the fringes of max range, defaults below.
-- -- also see engagerange below.
-- ` self.radiusscale[MANTIS.SamType.LONG] = 1.1`
-- ` self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2`
-- ` self.radiusscale[MANTIS.SamType.SHORT] = 1.3`
-- self.radiusscale[MANTIS.SamType.LONG] = 1.1
-- self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2
-- self.radiusscale[MANTIS.SamType.SHORT] = 1.3
--
-- ### 2.1.5 Friendlies check in firing range
--
-- -- For some scenarios, like Cold War, it might be useful not to activate SAMs if friendly aircraft are around to avoid death by friendly fire.
-- mybluemantis.checkforfriendlies = true
--
-- # 3. Default settings [both modes unless stated otherwise]
--
@ -321,6 +327,7 @@ MANTIS = {
automode = true,
autoshorad = true,
ShoradGroupSet = nil,
checkforfriendlies = false,
}
--- Advanced state enumerator
@ -503,6 +510,7 @@ do
-- DONE: Allow tables of prefixes for the setup
-- DONE: Auto-Mode with range setups for various known SAM types.
self.name = name or "mymantis"
self.SAM_Templates_Prefix = samprefix or "Red SAM"
self.EWR_Templates_Prefix = ewrprefix or "Red EWR"
self.HQ_Template_CC = hq or nil
@ -631,7 +639,7 @@ do
-- TODO Version
-- @field #string version
self.version="0.8.16"
self.version="0.8.18"
self:I(string.format("***** Starting MANTIS Version %s *****", self.version))
--- FSM Functions ---
@ -1255,6 +1263,10 @@ do
-- DEBUG
set = self:_PreFilterHeight(height)
end
local friendlyset -- Core.Set#SET_GROUP
if self.checkforfriendlies == true then
friendlyset = SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp) if grp and grp:InAir() then return true else return false end end):FilterOnce()
end
for _,_coord in pairs (set) do
local coord = _coord -- get current coord to check
-- output for cross-check
@ -1279,8 +1291,16 @@ do
local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug)
self:T(self.lid..text)
end
-- friendlies around?
local nofriendlies = true
if self.checkforfriendlies == true then
local closestfriend, distance = friendlyset:GetClosestGroup(samcoordinate)
if closestfriend and distance and distance < rad then
nofriendlies = false
end
end
-- end output to cross-check
if targetdistance <= rad and zonecheck then
if targetdistance <= rad and zonecheck == true and nofriendlies == true then
return true, targetdistance
end
end

View File

@ -7,7 +7,7 @@
-- Implementation is based on the [Simple Range Script](https://forums.eagle.ru/showthread.php?t=157991) by Ciribob, which itself was motivated
-- by a script by SNAFU [see here](https://forums.eagle.ru/showthread.php?t=109174).
--
-- [476th - Air Weapons Range Objects mod](http://www.476vfightergroup.com/downloads.php?do=file&id=287) is highly recommended for this class.
-- [476th - Air Weapons Range Objects mod](https://www.476vfightergroup.com/downloads.php?do=download&downloadid=482) is highly recommended for this class.
--
-- **Main Features:**
--
@ -1226,10 +1226,8 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
if PathToGoogleKey then
self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey)
self.controlmsrs:SetProvider(MSRS.Provider.GOOGLE)
self.instructmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey)
self.instructmsrs:SetProvider(MSRS.Provider.GOOGLE)
self.controlmsrs:SetGoogle(PathToGoogleKey)
self.instructmsrs:SetGoogle(PathToGoogleKey)
end
else

View File

@ -15,7 +15,7 @@
--
-- @module Functional.Stratego
-- @image Functional.Stratego.png
-- Last Update April 2024
-- Last Update May 2024
---
@ -42,6 +42,7 @@
-- @field #boolean usebudget
-- @field #number CaptureUnits
-- @field #number CaptureThreatlevel
-- @field #table CaptureObjectCategories
-- @field #boolean ExcludeShips
-- @field Core.Zone#ZONE StrategoZone
-- @extends Core.Base#BASE
@ -180,7 +181,7 @@ STRATEGO = {
debug = false,
drawzone = false,
markzone = false,
version = "0.2.7",
version = "0.3.1",
portweight = 3,
POIweight = 1,
maxrunways = 3,
@ -199,6 +200,7 @@ STRATEGO = {
usebudget = false,
CaptureUnits = 3,
CaptureThreatlevel = 1,
CaptureObjectCategories = {Object.Category.UNIT},
ExcludeShips = true,
}
@ -210,9 +212,10 @@ STRATEGO = {
-- @field #number coalition
-- @field #boolean port
-- @field Core.Zone#ZONE_RADIUS zone,
-- @field Core.Point#COORDINATRE coord
-- @field Core.Point#COORDINATE coord
-- @field #string type
-- @field Ops.OpsZone#OPSZONE opszone
-- @field #number connections
---
-- @type STRATEGO.DistData
@ -419,11 +422,13 @@ end
-- @param #STRATEGO self
-- @param #number CaptureUnits Number of units needed, defaults to three.
-- @param #number CaptureThreatlevel Threat level needed, can be 0..10, defaults to one.
-- @param #table CaptureCategories Table of object categories which can capture a node, defaults to `{Object.Category.UNIT}`.
-- @return #STRATEGO self
function STRATEGO:SetCaptureOptions(CaptureUnits,CaptureThreatlevel)
function STRATEGO:SetCaptureOptions(CaptureUnits,CaptureThreatlevel,CaptureCategories)
self:T(self.lid.."SetCaptureOptions")
self.CaptureUnits = CaptureUnits or 3
self.CaptureThreatlevel = CaptureThreatlevel or 1
self.CaptureObjectCategories = CaptureCategories or {Object.Category.UNIT}
return self
end
@ -486,6 +491,7 @@ function STRATEGO:AnalyseBases()
coord = coord,
type = abtype,
opszone = opszone,
connections = 0,
}
airbasetable[abname] = tbl
nonconnectedab[abname] = true
@ -526,6 +532,7 @@ function STRATEGO:GetNewOpsZone(Zone,Coalition)
local opszone = OPSZONE:New(Zone,Coalition or 0)
opszone:SetCaptureNunits(self.CaptureUnits)
opszone:SetCaptureThreatlevel(self.CaptureThreatlevel)
opszone:SetObjectCategories(self.CaptureObjectCategories)
opszone:SetDrawZone(self.drawzone)
opszone:SetMarkZone(self.markzone)
opszone:Start()
@ -571,10 +578,12 @@ function STRATEGO:AnalysePOIs(Set,Weight,Key)
coord = coord,
type = Key,
opszone = opszone,
connections = 0,
}
airbasetable[zone:GetName()] = tbl
nonconnectedab[zone:GetName()] = true
airbasetable[zname] = tbl
nonconnectedab[zname] = true
local name = string.gsub(zname,"[%p%s]",".")
--self:I({name=name,zone=zname})
easynames[name]=zname
end
)
@ -585,7 +594,7 @@ end
-- @param #STRATEGO self
-- @return #STRATEGO self
function STRATEGO:GetToFrom(StartPoint,EndPoint)
self:T(self.lid.."GetToFrom")
self:T(self.lid.."GetToFrom "..tostring(StartPoint).." "..tostring(EndPoint))
local pstart = string.gsub(StartPoint,"[%p%s]",".")
local pend = string.gsub(EndPoint,"[%p%s]",".")
local fromto = pstart..";"..pend
@ -593,11 +602,35 @@ function STRATEGO:GetToFrom(StartPoint,EndPoint)
return fromto, tofrom
end
--- [USER] Get available connecting nodes from one start node
-- @param #STRATEGO self
-- @param #string StartPoint The starting name
-- @return #boolean found
-- @return #table Nodes
function STRATEGO:GetRoutesFromNode(StartPoint)
self:T(self.lid.."GetRoutesFromNode")
local pstart = string.gsub(StartPoint,"[%p%s]",".")
local found = false
pstart=pstart..";"
local routes = {}
local listed = {}
for _,_data in pairs(self.routexists) do
if string.find(_data,pstart,1,true) and not listed[_data] then
local target = string.gsub(_data,pstart,"")
local fname = self.easynames[target]
table.insert(routes,fname)
found = true
listed[_data] = true
end
end
return found,routes
end
--- [USER] Manually add a route, for e.g. Island hopping or to connect isolated networks. Use **after** STRATEGO has been started!
-- @param #STRATEGO self
-- @param #string Startpoint Starting Point, e.g. AIRBASE.Syria.Hatay
-- @param #string Endpoint End Point, e.g. AIRBASE.Syria.H4
-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1,0,0} for red. Defaults to lila.
-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1,0,0} for red. Defaults to violet.
-- @param #number Linetype (Optional) Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 5.
-- @param #boolean Draw (Optional) If true, draw route on the F10 map. Defaukt false.
-- @return #STRATEGO self
@ -625,6 +658,8 @@ function STRATEGO:AddRoutesManually(Startpoint,Endpoint,Color,Linetype,Draw)
local factor = self.airbasetable[Startpoint].baseweight*self.routefactor
self.airbasetable[Startpoint].weight = self.airbasetable[Startpoint].weight+factor
self.airbasetable[Endpoint].weight = self.airbasetable[Endpoint].weight+factor
self.airbasetable[Endpoint].connections = self.airbasetable[Endpoint].connections + 2
self.airbasetable[Startpoint].connections = self.airbasetable[Startpoint].connections+2
if self.debug or Draw then
startcoordinate:LineToAll(targetcoordinate,-1,color,1,linetype,nil,string.format("%dkm",dist))
end
@ -643,7 +678,7 @@ function STRATEGO:AnalyseRoutes(tgtrwys,factor,color,linetype)
for _,_data in pairs(self.airbasetable) do
local fromto,tofrom = self:GetToFrom(startpoint,_data.name)
if _data.name == startpoint then
-- sam as we
-- same as we
elseif _data.baseweight == tgtrwys and not (self.routexists[fromto] or self.routexists[tofrom]) then
local tgtc = _data.coord
local dist = UTILS.Round(tgtc:Get2DDistance(startcoord),-2)/1000
@ -665,6 +700,8 @@ function STRATEGO:AnalyseRoutes(tgtrwys,factor,color,linetype)
self.nonconnectedab[startpoint] = false
self.airbasetable[startpoint].weight = self.airbasetable[startpoint].weight+factor
self.airbasetable[_data.name].weight = self.airbasetable[_data.name].weight+factor
self.airbasetable[startpoint].connections = self.airbasetable[startpoint].connections + 1
self.airbasetable[_data.name].connections = self.airbasetable[_data.name].connections + 1
if self.debug then
startcoord:LineToAll(tgtc,-1,color,1,linetype,nil,string.format("%dkm",dist))
end
@ -711,6 +748,8 @@ function STRATEGO:AnalyseUnconnected(Color)
end
self.airbasetable[startpoint].weight = self.airbasetable[startpoint].weight+1
self.airbasetable[closest].weight = self.airbasetable[closest].weight+1
self.airbasetable[startpoint].connections = self.airbasetable[startpoint].connections+2
self.airbasetable[closest].connections = self.airbasetable[closest].connections+2
local data = {
start = startpoint,
target = closest,
@ -727,14 +766,50 @@ function STRATEGO:AnalyseUnconnected(Color)
return self
end
--[[
function STRATEGO:PruneDeadEnds(abtable)
local found = false
local newtable = {}
for name, _data in pairs(abtable) do
local data = _data -- #STRATEGO.Data
if data.connections > 2 then
newtable[name] = data
else
-- dead end
found = true
local neighbors, nearest, distance = self:FindNeighborNodes(name)
--self:I("Pruning "..name)
if nearest then
for _name,_ in pairs(neighbors) do
local abname = self.easynames[_name] or _name
--self:I({easyname=_name,airbasename=abname})
if abtable[abname] then
abtable[abname].connections = abtable[abname].connections -1
end
end
end
if self.debug then
data.coord:CircleToAll(5000,-1,{1,1,1},1,{1,1,1},1,3,true,"Dead End")
end
end
end
abtable = nil
return found,newtable
end
--]]
--- [USER] Get a list of the nodes with the highest weight.
-- @param #STRATEGO self
-- @param #number Coalition (Optional) Find for this coalition only. E.g. coalition.side.BLUE.
-- @return #table Table of nodes.
-- @return #number Weight The consolidated weight associated with the nodes.
-- @return #number Highest Highest weight found.
-- @return #string Name of the node with the highest weight.
function STRATEGO:GetHighestWeightNodes(Coalition)
self:T(self.lid.."GetHighestWeightNodes")
local weight = 0
local highest = 0
local highname = nil
local airbases = {}
for _name,_data in pairs(self.airbasetable) do
local okay = true
@ -748,8 +823,12 @@ function STRATEGO:GetHighestWeightNodes(Coalition)
if not airbases[weight] then airbases[weight]={} end
table.insert(airbases[weight],_name)
end
if _data.weight > highest and okay then
highest = _data.weight
highname = _name
end
return airbases[weight],weight
end
return airbases[weight],weight,highest,highname
end
--- [USER] Get a list of the nodes a weight less than the given parameter.
@ -1136,14 +1215,18 @@ function STRATEGO:FindNeighborNodes(Name,Enemies,Friends)
self:T(self.lid.."FindNeighborNodes")
local neighbors = {}
local name = string.gsub(Name,"[%p%s]",".")
--self:I({Name=Name,name=name})
local shortestdist = 1000*1000
local nearest = nil
for _route,_data in pairs(self.disttable) do
if string.find(_route,name,1,true) then
local dist = self.disttable[_route] -- #STRATEGO.DistData
--self:I({route=_route,name=name})
local tname = string.gsub(_route,name,"")
local tname = string.gsub(tname,";","")
--self:I({tname=tname,cname=self.easynames[tname]})
local cname = self.easynames[tname] -- name of target
if cname then
local encoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
if Enemies == true then
if self.airbasetable[cname].coalition == encoa then
@ -1162,9 +1245,37 @@ function STRATEGO:FindNeighborNodes(Name,Enemies,Friends)
end
end
end
end
return neighbors, nearest, shortestdist
end
--- [INTERNAL] Route Finding - Find the next hop towards an end node from a start node
-- @param #STRATEGO self
-- @param #string Start The name of the start node.
-- @param #string End The name of the end node.
-- @param #table InRoute Table of node names making up the route so far.
-- @return #string Name of the next closest node
function STRATEGO:_GetNextClosest(Start,End,InRoute)
local ecoord = self.airbasetable[End].coord
local nodes,nearest = self:FindNeighborNodes(Start)
--self:I(tostring(nearest))
local closest = nil
local closedist = 1000*1000
for _name,_dist in pairs(nodes) do
local kcoord = self.airbasetable[_name].coord
local nnodes = self.airbasetable[_name].connections > 2 and true or false
if _name == End then nnodes = true end
if kcoord ~= nil and ecoord ~= nil and nnodes == true and InRoute[_name] ~= true then
local dist = math.floor((kcoord:Get2DDistance(ecoord)/1000)+0.5)
if (dist < closedist ) then
closedist = dist
closest = _name
end
end
end
return closest
end
--- [USER] Find a route between two nodes.
-- @param #STRATEGO self
-- @param #string Start The name of the start node.
@ -1173,15 +1284,19 @@ end
-- @param #boolean Draw If true, draw the route on the map.
-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1,0,0} for red. Defaults to black.
-- @param #number LineType (Optional) Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 6.
-- @param #boolean NoOptimize If set to true, do not optimize (shorten) the resulting route if possible.
-- @return #table Route Table of #string name entries of the route
-- @return #boolean Complete If true, the route was found end-to-end.
function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
-- @return #boolean Reverse If true, the route was found with a reverse search, the route table will be from sorted from end point to start point.
function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType,NoOptimize)
self:T(self.lid.."FindRoute")
--self:I({Start,End,Hops})
--local bases = UTILS.DeepCopy(self.airbasetable)
local Route = {}
local InRoute = {}
local hops = Hops or 4
local routecomplete = false
local reverse = false
local function Checker(neighbors)
for _name,_data in pairs(neighbors) do
@ -1193,25 +1308,6 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
return nil
end
local function NextClosest(Start,End)
local ecoord = self.airbasetable[End].coord
local nodes = self:FindNeighborNodes(Start)
local closest = nil
local closedist = 1000*1000
for _name,_dist in pairs(nodes) do
local kcoord = self.airbasetable[_name].coord
local dist = math.floor((kcoord:Get2DDistance(ecoord)/1000)+0.5)
if dist < closedist then
closedist = dist
closest = _name
end
end
if closest then
--MESSAGE:New(string.format("Start %s | End %s | Nextclosest %s",Start,End,closest),10,"STRATEGO"):ToLog():ToAll()
return closest
end
end
local function DrawRoute(Route)
for i=1,#Route-1 do
local p1=Route[i]
@ -1226,6 +1322,7 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
-- One hop
Route[#Route+1] = Start
InRoute[Start] = true
local nodes = self:FindNeighborNodes(Start)
local endpoint = Checker(nodes)
@ -1235,9 +1332,11 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
else
local spoint = Start
for i=1,hops do
local Next = NextClosest(spoint,End)
if Next then
--self:I("Start="..tostring(spoint))
local Next = self:_GetNextClosest(spoint,End,InRoute)
if Next ~= nil then
Route[#Route+1] = Next
InRoute[Next] = true
local nodes = self:FindNeighborNodes(Next)
local endpoint = Checker(nodes)
if endpoint then
@ -1247,11 +1346,59 @@ function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType)
else
spoint = Next
end
else
break
end
end
end
-- optimize route
local function OptimizeRoute(Route)
local foundcut = false
local largestcut = 0
local cut = {}
for i=1,#Route do
--self:I({Start=Route[i]})
local found,nodes = self:GetRoutesFromNode(Route[i])
for _,_name in pairs(nodes or {}) do
for j=i+2,#Route do
if _name == Route[j] then
--self:I({"Shortcut",Route[i],Route[j]})
if j-i > largestcut then
largestcut = j-i
cut = {i=i,j=j}
foundcut = true
end
end
end
end
end
if foundcut then
local newroute = {}
for i=1,#Route do
if i<= cut.i or i>=cut.j then
table.insert(newroute,Route[i])
end
end
return newroute
end
return Route, foundcut
end
if routecomplete == true and NoOptimize ~= true then
local foundcut = true
while foundcut ~= false do
Route, foundcut = OptimizeRoute(Route)
end
else
-- reverse search
Route, routecomplete = self:FindRoute(End,Start,Hops,Draw,Color,LineType)
reverse = true
end
if (self.debug or Draw) then DrawRoute(Route) end
return Route, routecomplete
return Route, routecomplete, reverse
end
--- [USER] Add budget points.
@ -1358,6 +1505,139 @@ function STRATEGO:FindAffordableConsolidationTarget()
end
end
--- [INTERNAL] Internal helper function to check for islands, aka Floodtest
-- @param #STRATEGO self
-- @param #string next Name of the start node
-- @param #table filled #table of visited nodes
-- @param #table unfilled #table if unvisited nodes
-- @return #STRATEGO self
function STRATEGO:_FloodNext(next,filled,unfilled)
local start = self:FindNeighborNodes(next)
for _name,_ in pairs (start) do
if filled[_name] ~= true then
self:T("Flooding ".._name)
filled[_name] = true
unfilled[_name] = nil
self:_FloodNext(_name,filled,unfilled)
end
end
return self
end
--- [INTERNAL] Internal helper function to check for islands, aka Floodtest
-- @param #STRATEGO self
-- @param #string Start Name of the start node
-- @param #table ABTable (Optional) #table of node names to check.
-- @return #STRATEGO self
function STRATEGO:_FloodFill(Start,ABTable)
self:T("Start = "..tostring(Start))
if Start == nil then return end
local filled = {}
local unfilled = {}
if ABTable then
unfilled = ABTable
else
for _name,_ in pairs(self.airbasetable) do
unfilled[_name] = true
end
end
filled[Start] = true
unfilled[Start] = nil
local start = self:FindNeighborNodes(Start)
for _name,_ in pairs (start) do
if filled[_name] ~= true then
self:T("Flooding ".._name)
filled[_name] = true
unfilled[_name] = nil
self:_FloodNext(_name,filled,unfilled)
end
end
return filled, unfilled
end
--- [INTERNAL] Internal helper function to check for islands, aka Floodtest
-- @param #STRATEGO self
-- @param #boolen connect If true, connect the two resulting islands at the shortest distance if necessary
-- @param #boolen draw If true, draw outer vertices of found node networks
-- @return #boolean Connected If true, all nodes are in one network
-- @return #table Network #table of node names in the network
-- @return #table Unconnected #table of node names **not** in the network
function STRATEGO:_FloodTest(connect,draw)
local function GetElastic(bases)
local vec2table = {}
for _name,_ in pairs(bases) do
local coord = self.airbasetable[_name].coord
local vec2 = coord:GetVec2()
table.insert(vec2table,vec2)
end
local zone = ZONE_ELASTIC:New("STRATEGO-Floodtest-"..math.random(1,10000),vec2table)
return zone
end
local function DrawElastic(filled,drawit)
local zone = GetElastic(filled)
if drawit then
zone:SetColor({1,1,1},1)
zone:SetDrawCoalition(-1)
zone:Update(1,true) -- draw zone
end
return zone
end
local _,_,weight,name = self:GetHighestWeightNodes()
local filled, unfilled = self:_FloodFill(name)
local allin = true
if table.length(unfilled) > 0 then
MESSAGE:New("There is at least one node island!",15,"STRATEGO"):ToAllIf(self.debug):ToLog()
allin = false
if self.debug == true then
local zone1 = DrawElastic(filled,draw)
local zone2 = DrawElastic(unfilled,draw)
local vertices1 = zone1:GetVerticiesVec2()
local vertices2 = zone2:GetVerticiesVec2()
-- get closest vertices
local corner1 = nil
local corner2 = nil
local mindist = math.huge
local found = false
for _,_edge in pairs(vertices1) do
for _,_edge2 in pairs(vertices2) do
local dist=UTILS.VecDist2D(_edge,_edge2)
if dist < mindist then
mindist = dist
corner1 = _edge
corner2 = _edge2
found = true
end
end
end
if found then
local Corner = COORDINATE:NewFromVec2(corner1)
local Corner2 = COORDINATE:NewFromVec2(corner2)
Corner:LineToAll(Corner2,-1,{1,1,1},1,1,true,"Island2Island")
local cornername
local cornername2
for _name,_data in pairs(self.airbasetable) do
local zone = _data.zone
if zone:IsVec2InZone(corner1) then
cornername = _name
self:T("Corner1 = ".._name)
end
if zone:IsVec2InZone(corner2) then
cornername2 = _name
self:T("Corner2 = ".._name)
end
if cornername and cornername2 and connect == true then
self:AddRoutesManually(cornername,cornername2,Color,Linetype,self.debug)
end
end
end
end
end
return allin, filled, unfilled
end
---------------------------------------------------------------------------------------------------------------
--
-- End

View File

@ -255,6 +255,7 @@
-- @field #boolean skipperUturn U-turn on/off via menu.
-- @field #number skipperOffset Holding offset angle in degrees for Case II/III manual recoveries.
-- @field #number skipperTime Recovery time in min for manual recovery.
-- @field #boolean intowindold If true, use old into wind calculation.
-- @extends Core.Fsm#FSM
--- Be the boss!
@ -2724,6 +2725,18 @@ function AIRBOSS:SetLSOCallInterval( TimeInterval )
return self
end
--- Set if old into wind calculation is used when carrier turns into the wind for a recovery.
-- @param #AIRBOSS self
-- @param #boolean SwitchOn If `true` or `nil`, use old into wind calculation.
-- @return #AIRBOSS self
function AIRBOSS:SetIntoWindLegacy( SwitchOn )
if SwitchOn==nil then
SwitchOn=true
end
self.intowindold=SwitchOn
return self
end
--- Airboss is a rather nice guy and not strictly following the rules. Fore example, he does allow you into the landing pattern if you are not coming from the Marshal stack.
-- @param #AIRBOSS self
-- @param #boolean Switch If true or nil, Airboss bends the rules a bit.
@ -3642,6 +3655,12 @@ function AIRBOSS:onafterStatus( From, Event, To )
local pos = self:GetCoordinate()
local speed = self.carrier:GetVelocityKNOTS()
-- Update magnetic variation if we can get it from DCS.
if require then
self.magvar=pos:GetMagneticDeclination()
--env.info(string.format("FF magvar=%.1f", self.magvar))
end
-- Check water is ahead.
local collision = false -- self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg))
@ -5201,6 +5220,7 @@ function AIRBOSS:_InitVoiceOvers()
TOMCAT = { file = "PILOT-Tomcat", suffix = "ogg", loud = false, subtitle = "", duration = 0.66, subduration = 5 },
HORNET = { file = "PILOT-Hornet", suffix = "ogg", loud = false, subtitle = "", duration = 0.56, subduration = 5 },
VIKING = { file = "PILOT-Viking", suffix = "ogg", loud = false, subtitle = "", duration = 0.61, subduration = 5 },
GREYHOUND = { file = "PILOT-Greyhound", suffix = "ogg", loud = false, subtitle = "", duration = 0.61, subduration = 5 },
BALL = { file = "PILOT-Ball", suffix = "ogg", loud = false, subtitle = "", duration = 0.50, subduration = 5 },
BINGOFUEL = { file = "PILOT-BingoFuel", suffix = "ogg", loud = false, subtitle = "", duration = 0.80 },
GASATDIVERT = { file = "PILOT-GasAtDivert", suffix = "ogg", loud = false, subtitle = "", duration = 1.80 },
@ -6475,7 +6495,7 @@ function AIRBOSS:_LandAI( flight )
or flight.actype == AIRBOSS.AircraftCarrier.RHINOF
or flight.actype == AIRBOSS.AircraftCarrier.GROWLER then
Speed = UTILS.KnotsToKmph( 200 )
elseif flight.actype == AIRBOSS.AircraftCarrier.E2D then
elseif flight.actype == AIRBOSS.AircraftCarrier.E2D or flight.actype == AIRBOSS.AircraftCarrier.C2A then
Speed = UTILS.KnotsToKmph( 150 )
elseif flight.actype == AIRBOSS.AircraftCarrier.F14A_AI or flight.actype == AIRBOSS.AircraftCarrier.F14A or flight.actype == AIRBOSS.AircraftCarrier.F14B then
Speed = UTILS.KnotsToKmph( 175 )
@ -11476,7 +11496,7 @@ end
--- Get wind direction and speed at carrier position.
-- @param #AIRBOSS self
-- @param #number alt Altitude ASL in meters. Default 15 m.
-- @param #number alt Altitude ASL in meters. Default 18 m.
-- @param #boolean magnetic Direction including magnetic declination.
-- @param Core.Point#COORDINATE coord (Optional) Coordinate at which to get the wind. Default is current carrier position.
-- @return #number Direction the wind is blowing **from** in degrees.
@ -11548,10 +11568,31 @@ end
--- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway.
-- @param #AIRBOSS self
-- @param #number vdeck Desired wind velocity over deck in knots.
-- @param #boolean magnetic If true, calculate magnetic heading. By default true heading is returned.
-- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position.
-- @return #number Carrier heading in degrees.
function AIRBOSS:GetHeadingIntoWind_old( magnetic, coord )
-- @return #number Carrier speed in knots to reach desired wind speed on deck.
function AIRBOSS:GetHeadingIntoWind(vdeck, magnetic, coord )
if self.intowindold then
--env.info("FF use OLD into wind")
return self:GetHeadingIntoWind_old(vdeck, magnetic, coord)
else
--env.info("FF use NEW into wind")
return self:GetHeadingIntoWind_new(vdeck, magnetic, coord)
end
end
--- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway.
-- @param #AIRBOSS self
-- @param #number vdeck Desired wind velocity over deck in knots.
-- @param #boolean magnetic If true, calculate magnetic heading. By default true heading is returned.
-- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position.
-- @return #number Carrier heading in degrees.
function AIRBOSS:GetHeadingIntoWind_old( vdeck, magnetic, coord )
local function adjustDegreesForWindSpeed(windSpeed)
local degreesAdjustment = 0
@ -11608,7 +11649,13 @@ function AIRBOSS:GetHeadingIntoWind_old( magnetic, coord )
intowind = intowind + 360
end
return intowind
-- Wind speed.
--local _, vwind = self:GetWind()
-- Speed of carrier in m/s but at least 4 knots.
local vtot = math.max(vdeck-UTILS.MpsToKnots(vwind), 4)
return intowind, vtot
end
--- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway.
@ -11619,7 +11666,7 @@ end
-- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position.
-- @return #number Carrier heading in degrees.
-- @return #number Carrier speed in knots to reach desired wind speed on deck.
function AIRBOSS:GetHeadingIntoWind( vdeck, magnetic, coord )
function AIRBOSS:GetHeadingIntoWind_new( vdeck, magnetic, coord )
-- Default offset angle.
local Offset=self.carrierparam.rwyangle or 0
@ -14280,6 +14327,8 @@ function AIRBOSS:_GetACNickname( actype )
nickname = "Harrier"
elseif actype == AIRBOSS.AircraftCarrier.E2D then
nickname = "Hawkeye"
elseif actype == AIRBOSS.AircraftCarrier.C2A then
nickname = "Greyhound"
elseif actype == AIRBOSS.AircraftCarrier.F14A_AI or actype == AIRBOSS.AircraftCarrier.F14A or actype == AIRBOSS.AircraftCarrier.F14B then
nickname = "Tomcat"
elseif actype == AIRBOSS.AircraftCarrier.FA18C or actype == AIRBOSS.AircraftCarrier.HORNET then
@ -14317,11 +14366,15 @@ function AIRBOSS:_GetOnboardNumbers( group, playeronly )
-- Debug text.
local text = string.format( "Onboard numbers of group %s:", groupname )
local template=group:GetTemplate()
local numbers = {}
if template then
-- Units of template group.
local units = group:GetTemplate().units
local units = template.units
-- Get numbers.
local numbers = {}
for _, unit in pairs( units ) do
-- Onboard number and unit name.
@ -14344,6 +14397,25 @@ function AIRBOSS:_GetOnboardNumbers( group, playeronly )
-- Debug info.
self:T2( self.lid .. text )
else
if playeronly then
return 101
else
local units=group:GetUnits()
for i,_unit in pairs(units) do
local name=_unit:GetName()
numbers[name]=100+i
end
end
end
return numbers
end

View File

@ -2109,7 +2109,7 @@ function ARMYGROUP:_InitGroup(Template, Delay)
return
end
self:I(self.lid.."FF Initializing Group")
self:T(self.lid.."FF Initializing Group")
-- Get template of group.
local template=Template or self:_GetTemplate()

View File

@ -291,10 +291,12 @@ CSAR.AircraftType["UH-60L"] = 10
CSAR.AircraftType["AH-64D_BLK_II"] = 2
CSAR.AircraftType["Bronco-OV-10A"] = 2
CSAR.AircraftType["MH-60R"] = 10
CSAR.AircraftType["OH-6A"] = 2
CSAR.AircraftType["OH58D"] = 2
--- CSAR class version.
-- @field #string version
CSAR.version="1.0.21"
CSAR.version="1.0.24"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@ -734,7 +736,7 @@ function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet)
:NewWithAlias(template,alias)
:InitCoalition(coalition)
:InitCountry(country)
:InitAIOnOff(pilotcacontrol)
--:InitAIOnOff(pilotcacontrol)
:InitDelayOff()
:SpawnFromCoordinate(point)
@ -1238,10 +1240,24 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage, _pla
if not _nomessage then
if _freq ~= 0 then --shagrat
local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute'
if self.coordtype ~= 2 then --not MGRS
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
else
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true)
local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true)
local _text = string.format("%s requests SAR at %s, beacon at %.2f kilo hertz", _groupName, coordtext, _freqk)
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false)
end
else --shagrat CASEVAC msg
local _text = string.format("Pickup Zone at %s.", _coordinatesText )
if self.coordtype ~= 2 then --not MGRS
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
else
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true)
local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true)
local _text = string.format("Pickup Zone at %s.", coordtext )
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false)
end
end
end
@ -1947,23 +1963,28 @@ end
-- @param #string _message Message to display.
-- @param #number _side Coalition of message.
-- @param #number _messagetime How long to show.
function CSAR:_DisplayToAllSAR(_message, _side, _messagetime)
-- @param #boolean ToSRS If true or nil, send to SRS TTS
-- @param #boolean ToScreen If true or nil, send to Screen
function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen)
self:T(self.lid .. " _DisplayToAllSAR")
local messagetime = _messagetime or self.messageTime
if self.msrs then
self:T({_message,ToSRS=ToSRS,ToScreen=ToScreen})
if self.msrs and (ToSRS == true or ToSRS == nil) then
local voice = self.CSARVoice or MSRS.Voices.Google.Standard.en_GB_Standard_F
if self.msrs:GetProvider() == MSRS.Provider.WINDOWS then
voice = self.CSARVoiceMS or MSRS.Voices.Microsoft.Hedda
end
self:I("Voice = "..voice)
self:F("Voice = "..voice)
self.SRSQueue:NewTransmission(_message,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,voice,volume,label,self.coordinate)
end
if ToScreen == true or ToScreen == nil then
for _, _unitName in pairs(self.csarUnits) do
local _unit = self:_GetSARHeli(_unitName)
if _unit and not self.suppressmessages then
self:_DisplayMessageToSAR(_unit, _message, _messagetime)
end
end
end
return self
end

View File

@ -1249,11 +1249,13 @@ CTLD.UnitTypeCapabilities = {
["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo
["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450},
["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550},
["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400},
}
--- CTLD class version.
-- @field #string version
CTLD.version="1.0.51"
CTLD.version="1.0.54"
--- Instantiate a new CTLD.
-- @param #CTLD self
@ -3607,7 +3609,7 @@ function CTLD:_MoveGroupToZone(Group)
local groupcoord = Group:GetCoordinate()
-- Get closest zone of type
local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE)
if (distance <= self.movetroopsdistance) and zone then
if (distance <= self.movetroopsdistance) and outcome == true and zone~= nil then
-- yes, we can ;)
local groupname = Group:GetName()
local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE
@ -4464,10 +4466,9 @@ function CTLD:IsUnitInZone(Unit,Zonetype)
zonewidth = zoneradius
end
local distance = self:_GetDistance(zonecoord,unitcoord)
if zone:IsVec2InZone(unitVec2) and active then
self:T("Distance Zone: "..distance)
if (zone:IsVec2InZone(unitVec2) or Zonetype == CTLD.CargoZoneType.MOVE) and active == true and maxdist > distance then
outcome = true
end
if maxdist > distance then
maxdist = distance
zoneret = zone
zonenameret = zonename

View File

@ -1078,6 +1078,13 @@ function CHIEF:SetStrategy(Strategy)
return self
end
--- Get current strategy.
-- @param #CHIEF self
-- @return #string Strategy.
function CHIEF:GetStrategy()
return self.strategy
end
--- Get defence condition.
-- @param #CHIEF self
-- @param #string Current Defence condition. See @{#CHIEF.DEFCON}, e.g. `CHIEF.DEFCON.RED`.
@ -1456,7 +1463,7 @@ end
--- Add a CAP zone. Flights will engage detected targets inside this zone.
-- @param #CHIEF self
-- @param Core.Zone#ZONE Zone CAP Zone. Has to be a circular zone.
-- @param #number Altitude Orbit altitude in feet. Default is 12,0000 feet.
-- @param #number Altitude Orbit altitude in feet. Default is 12,000 feet.
-- @param #number Speed Orbit speed in KIAS. Default 350 kts.
-- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West).
-- @param #number Leg Length of race-track in NM. Default 30 NM.
@ -1472,7 +1479,7 @@ end
--- Add a GCI CAP.
-- @param #CHIEF self
-- @param Core.Zone#ZONE Zone Zone, where the flight orbits.
-- @param #number Altitude Orbit altitude in feet. Default is 12,0000 feet.
-- @param #number Altitude Orbit altitude in feet. Default is 12,000 feet.
-- @param #number Speed Orbit speed in KIAS. Default 350 kts.
-- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West).
-- @param #number Leg Length of race-track in NM. Default 30 NM.
@ -1499,7 +1506,7 @@ end
--- Add an AWACS zone.
-- @param #CHIEF self
-- @param Core.Zone#ZONE Zone Zone.
-- @param #number Altitude Orbit altitude in feet. Default is 12,0000 feet.
-- @param #number Altitude Orbit altitude in feet. Default is 12,000 feet.
-- @param #number Speed Orbit speed in KIAS. Default 350 kts.
-- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West).
-- @param #number Leg Length of race-track in NM. Default 30 NM.
@ -1526,7 +1533,7 @@ end
--- Add a refuelling tanker zone.
-- @param #CHIEF self
-- @param Core.Zone#ZONE Zone Zone.
-- @param #number Altitude Orbit altitude in feet. Default is 12,0000 feet.
-- @param #number Altitude Orbit altitude in feet. Default is 12,000 feet.
-- @param #number Speed Orbit speed in KIAS. Default 350 kts.
-- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West).
-- @param #number Leg Length of race-track in NM. Default 30 NM.

View File

@ -663,7 +663,7 @@ end
--- Add a CAP zone.
-- @param #COMMANDER self
-- @param Core.Zone#ZONE Zone CapZone Zone.
-- @param #number Altitude Orbit altitude in feet. Default is 12,0000 feet.
-- @param #number Altitude Orbit altitude in feet. Default is 12,000 feet.
-- @param #number Speed Orbit speed in KIAS. Default 350 kts.
-- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West).
-- @param #number Leg Length of race-track in NM. Default 30 NM.
@ -689,7 +689,7 @@ end
--- Add a GCICAP zone.
-- @param #COMMANDER self
-- @param Core.Zone#ZONE Zone CapZone Zone.
-- @param #number Altitude Orbit altitude in feet. Default is 12,0000 feet.
-- @param #number Altitude Orbit altitude in feet. Default is 12,000 feet.
-- @param #number Speed Orbit speed in KIAS. Default 350 kts.
-- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West).
-- @param #number Leg Length of race-track in NM. Default 30 NM.
@ -735,7 +735,7 @@ end
--- Add an AWACS zone.
-- @param #COMMANDER self
-- @param Core.Zone#ZONE Zone Zone.
-- @param #number Altitude Orbit altitude in feet. Default is 12,0000 feet.
-- @param #number Altitude Orbit altitude in feet. Default is 12,000 feet.
-- @param #number Speed Orbit speed in KIAS. Default 350 kts.
-- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West).
-- @param #number Leg Length of race-track in NM. Default 30 NM.
@ -782,7 +782,7 @@ end
--- Add a refuelling tanker zone.
-- @param #COMMANDER self
-- @param Core.Zone#ZONE Zone Zone.
-- @param #number Altitude Orbit altitude in feet. Default is 12,0000 feet.
-- @param #number Altitude Orbit altitude in feet. Default is 12,000 feet.
-- @param #number Speed Orbit speed in KIAS. Default 350 kts.
-- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West).
-- @param #number Leg Length of race-track in NM. Default 30 NM.

View File

@ -62,6 +62,9 @@
-- @field #number runwayrepairtime Time in seconds until runway will be repaired after it was destroyed. Default is 3600 sec (one hour).
-- @field #boolean markerParking If `true`, occupied parking spots are marked.
-- @field #table warnings Warnings issued to flight groups.
-- @field #boolean nosubs If `true`, SRS TTS is without subtitles.
-- @field #number Nplayers Number of human players. Updated at each StatusUpdate call.
-- @field #boolean radioOnlyIfPlayers Activate to limit transmissions only if players are active at the airbase.
-- @extends Core.Fsm#FSM
--- **Ground Control**: Airliner X, Good news, you are clear to taxi to the active.
@ -273,6 +276,7 @@ FLIGHTCONTROL = {
hpcounter = 0,
warnings = {},
nosubs = false,
Nplayers = 0,
}
--- Holding point. Contains holding stacks.
@ -351,7 +355,7 @@ FLIGHTCONTROL.Violation={
--- FlightControl class version.
-- @field #string version
FLIGHTCONTROL.version="0.7.5"
FLIGHTCONTROL.version="0.7.7"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@ -452,6 +456,16 @@ function FLIGHTCONTROL:New(AirbaseName, Frequency, Modulation, PathToSRS, Port,
-- Init msrs queue.
self.msrsqueue=MSRSQUEUE:New(self.alias)
-- Set that transmission is only if alive players on the server.
self:SetTransmitOnlyWithPlayers(true)
-- Init msrs bases
local path = PathToSRS or MSRS.path
local port = Port or MSRS.port or 5002
-- Set SRS Port
self:SetSRSPort(port)
-- SRS for Tower.
self.msrsTower=MSRS:New(path, Frequency, Modulation)
self.msrsTower:SetPort(port)
@ -605,6 +619,31 @@ function FLIGHTCONTROL:SetVerbosity(VerbosityLevel)
return self
end
--- Limit radio transmissions only if human players are registered at the airbase.
-- This can be used to reduce TTS messages on heavy missions.
-- @param #FLIGHTCONTROL self
-- @param #boolean Switch If `true` or `nil` no transmission if there are no players. Use `false` enable TTS with no players.
-- @return #FLIGHTCONTROL self
function FLIGHTCONTROL:SetRadioOnlyIfPlayers(Switch)
if Switch==nil or Switch==true then
self.radioOnlyIfPlayers=true
else
self.radioOnlyIfPlayers=false
end
return self
end
--- Set whether to only transmit TTS messages if there are players on the server.
-- @param #FLIGHTCONTROL self
-- @param #boolean Switch If `true`, only send TTS messages if there are alive Players. If `false` or `nil`, transmission are done also if no players are on the server.
-- @return #FLIGHTCONTROL self
function FLIGHTCONTROL:SetTransmitOnlyWithPlayers(Switch)
self.msrsqueue:SetTransmitOnlyWithPlayers(Switch)
return self
end
--- Set subtitles to appear on SRS TTS messages.
-- @param #FLIGHTCONTROL self
-- @return #FLIGHTCONTROL self
@ -4243,6 +4282,72 @@ function FLIGHTCONTROL:_CheckFlights()
end
end
-- Count number of players
self.Nplayers=0
for _,_flight in pairs(self.flights) do
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
if not flight.isAI then
self.Nplayers=self.Nplayers+1
end
end
-- Check speeding.
if self.speedLimitTaxi then
for _,_flight in pairs(self.flights) do
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
if not flight.isAI then
-- Get player element.
local playerElement=flight:GetPlayerElement()
-- Current flight status.
local flightstatus=self:GetFlightStatus(flight)
if playerElement then
-- Check if speeding while taxiing.
if (flightstatus==FLIGHTCONTROL.FlightStatus.TAXIINB or flightstatus==FLIGHTCONTROL.FlightStatus.TAXIOUT) and self.speedLimitTaxi then
-- Current speed in m/s.
local speed=playerElement.unit:GetVelocityMPS()
-- Current position.
local coord=playerElement.unit:GetCoord()
-- We do not want to check speed on runways.
local onRunway=self:IsCoordinateRunway(coord)
-- Debug output.
self:T(self.lid..string.format("Player %s speed %.1f knots (max=%.1f) onRunway=%s", playerElement.playerName, UTILS.MpsToKnots(speed), UTILS.MpsToKnots(self.speedLimitTaxi), tostring(onRunway)))
if speed and speed>self.speedLimitTaxi and not onRunway then
-- Callsign.
local callsign=self:_GetCallsignName(flight)
-- Radio text.
local text=string.format("%s, slow down, you are taxiing too fast!", callsign)
-- Radio message to player.
self:TransmissionTower(text, flight)
-- Get player data.
local PlayerData=flight:_GetPlayerData()
-- Trigger FSM speeding event.
self:PlayerSpeeding(PlayerData)
end
end
end
end
end
end
-- Loop over all flights.
for _,_flight in pairs(self.flights) do
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
@ -4749,6 +4854,11 @@ end
-- @param #number Delay Delay in seconds before the text is transmitted. Default 0 sec.
function FLIGHTCONTROL:TransmissionTower(Text, Flight, Delay)
if self.radioOnlyIfPlayers==true and self.Nplayers==0 then
self:T(self.lid.."No players ==> skipping TOWER radio transmission")
return
end
-- Spoken text.
local text=self:_GetTextForSpeech(Text)
@ -4815,6 +4925,12 @@ end
-- @param #number Delay Delay in seconds before the text is transmitted. Default 0 sec.
function FLIGHTCONTROL:TransmissionPilot(Text, Flight, Delay)
if self.radioOnlyIfPlayers==true and self.Nplayers==0 then
self:T(self.lid.."No players ==> skipping PILOT radio transmission")
return
end
-- Get player data.
local playerData=Flight:_GetPlayerData()

View File

@ -2831,6 +2831,11 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime)
self:T(self.lid.."Engaging! Group NOT done...")
return
end
-- Check if group is going for fuel.
if self:IsGoing4Fuel() then
self:T(self.lid.."Going for FUEL! Group NOT done...")
return
end
-- Number of tasks remaining.
local nTasks=self:CountRemainingTasks()
@ -3447,6 +3452,9 @@ function FLIGHTGROUP:onafterRefuel(From, Event, To, Coordinate)
self:Route({wp0, wp9}, 1)
-- Set RTB on Bingo option. Currently DCS does not execute the refueling task if RTB_ON_BINGO is set to "NO RTB ON BINGO"
self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO, true)
end
--- On after "Refueled" event.
@ -3460,6 +3468,9 @@ function FLIGHTGROUP:onafterRefueled(From, Event, To)
local text=string.format("Flight group finished refuelling")
self:T(self.lid..text)
-- Set RTB on Bingo option to "NO RTB ON BINGO"
self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO, false)
-- Check if flight is done.
self:_CheckGroupDone(1)

View File

@ -21,7 +21,7 @@
-- ===
-- @module Ops.PlayerTask
-- @image OPS_PlayerTask.jpg
-- @date Last Update Feb 2024
-- @date Last Update May 2024
do
@ -1213,6 +1213,9 @@ do
-- AIRDEFENSE = "Airdefense",
-- SAM = "SAM",
-- GROUP = "Group",
-- ELEVATION = "\nTarget Elevation: %s %s",
-- METER = "meter",
-- FEET = "feet",
-- },
--
-- e.g.
@ -1367,7 +1370,7 @@ PLAYERTASKCONTROLLER.Type = {
AUFTRAG.Type.PRECISIONBOMBING = "Precision Bombing"
AUFTRAG.Type.CTLD = "Combat Transport"
AUFTRAG.Type.CSAR = "Combat Rescue"
AUFTRAG.Type.CONQUER = "Conquer"
---
-- @type Scores
PLAYERTASKCONTROLLER.Scores = {
@ -1381,6 +1384,7 @@ PLAYERTASKCONTROLLER.Scores = {
[AUFTRAG.Type.SEAD] = 100,
[AUFTRAG.Type.BOMBING] = 100,
[AUFTRAG.Type.BOMBRUNWAY] = 100,
[AUFTRAG.Type.CONQUER] = 100,
}
---
@ -1419,6 +1423,9 @@ PLAYERTASKCONTROLLER.Messages = {
THREATMEDIUM = "medium",
THREATLOW = "low",
THREATTEXT = "%s\nThreat: %s\nTargets left: %d\nCoord: %s",
ELEVATION = "\nTarget Elevation: %s %s",
METER = "meter",
FEET = "feet",
THREATTEXTTTS = "%s, %s. Target information for %s. Threat level %s. Targets left %d. Target location %s.",
MARKTASK = "%s, %s, copy, task %03d location marked on map!",
SMOKETASK = "%s, %s, copy, task %03d location smoked!",
@ -1499,6 +1506,9 @@ PLAYERTASKCONTROLLER.Messages = {
THREATMEDIUM = "mittel",
THREATLOW = "niedrig",
THREATTEXT = "%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s",
ELEVATION = "\nZiel Höhe: %s %s",
METER = "Meter",
FEET = "Fuss",
THREATTEXTTTS = "%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.",
MARKTASK = "%s, %s, verstanden, Zielposition %03d auf der Karte markiert!",
SMOKETASK = "%s, %s, verstanden, Zielposition %03d mit Rauch markiert!",
@ -1561,7 +1571,7 @@ PLAYERTASKCONTROLLER.Messages = {
--- PLAYERTASK class version.
-- @field #string version
PLAYERTASKCONTROLLER.version="0.1.65"
PLAYERTASKCONTROLLER.version="0.1.66"
--- Create and run a new TASKCONTROLLER instance.
-- @param #PLAYERTASKCONTROLLER self
@ -2113,10 +2123,12 @@ function PLAYERTASKCONTROLLER:_GetPlayerName(Client)
local ttsplayername = nil
if not self.customcallsigns[playername] then
local playergroup = Client:GetGroup()
if playergroup ~= nil then
ttsplayername = playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
local newplayername = self:_GetTextForSpeech(ttsplayername)
self.customcallsigns[playername] = newplayername
ttsplayername = newplayername
end
else
ttsplayername = self.customcallsigns[playername]
end
@ -3182,7 +3194,8 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
local ttsname = self.gettext:GetEntry("TASKNAMETTS",self.locale)
local taskname = string.format(tname,task.Type,task.PlayerTaskNr)
local ttstaskname = string.format(ttsname,task.TTSType,task.PlayerTaskNr)
local Coordinate = task.Target:GetCoordinate() or COORDINATE:New(0,0,0)
local Coordinate = task.Target:GetCoordinate() or COORDINATE:New(0,0,0) -- Core.Point#COORDINATE
local Elevation = Coordinate:GetLandHeight() or 0 -- meters
local CoordText = ""
local CoordTextLLDM = nil
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then
@ -3207,6 +3220,17 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
local ThreatGraph = "[" .. string.rep( "", ThreatLevel ) .. string.rep( "", 10 - ThreatLevel ) .. "]: "..ThreatLevel
local ThreatLocaleText = self.gettext:GetEntry("THREATTEXT",self.locale)
text = string.format(ThreatLocaleText, taskname, ThreatGraph, targets, CoordText)
local settings = _DATABASE:GetPlayerSettings(playername) or _SETTINGS -- Core.Settings#SETTINGS
local elevationmeasure = self.gettext:GetEntry("FEET",self.locale)
if settings:IsMetric() then
elevationmeasure = self.gettext:GetEntry("METER",self.locale)
--Elevation = math.floor(UTILS.MetersToFeet(Elevation))
else
Elevation = math.floor(UTILS.MetersToFeet(Elevation))
end
-- ELEVATION = "\nTarget Elevation: %s %s",
local elev = self.gettext:GetEntry("ELEVATION",self.locale)
text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure)
-- Prec bombing
if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
if self.LasingDrone and self.LasingDrone.playertask then

View File

@ -55,6 +55,7 @@ BIGSMOKEPRESET = {
-- @field #string MarianaIslands Mariana Islands map.
-- @field #string Falklands South Atlantic map.
-- @field #string Sinai Sinai map.
-- @field #string Kola Kola map.
DCSMAP = {
Caucasus="Caucasus",
NTTR="Nevada",
@ -64,7 +65,8 @@ DCSMAP = {
Syria="Syria",
MarianaIslands="MarianaIslands",
Falklands="Falklands",
Sinai="SinaiMap"
Sinai="SinaiMap",
Kola="Kola"
}
@ -1778,6 +1780,7 @@ end
-- * Mariana Islands +2 (East)
-- * Falklands +12 (East) - note there's a LOT of deviation across the map, as we're closer to the South Pole
-- * Sinai +4.8 (East)
-- * Kola +15 (East) - not there is a lot of deviation across the map (-1° to +24°), as we are close to the North pole
-- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre
-- @return #number Declination in degrees.
function UTILS.GetMagneticDeclination(map)
@ -1804,6 +1807,8 @@ function UTILS.GetMagneticDeclination(map)
declination=12
elseif map==DCSMAP.Sinai then
declination=4.8
elseif map==DCSMAP.Kola then
declination=15
else
declination=0
end
@ -2021,6 +2026,8 @@ function UTILS.GMTToLocalTimeDifference()
return -3 -- Fireland is UTC-3 hours.
elseif theatre==DCSMAP.Sinai then
return 2 -- Currently map is +2 but should be +3 (DCS bug?)
elseif theatre==DCSMAP.Kola then
return 3 -- Currently map is +2 but should be +3 (DCS bug?)
else
BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre)))
return 0
@ -2317,6 +2324,11 @@ function UTILS.IsLoadingDoorOpen( unit_name )
return true
end
if type_name == " OH-58D" and (unit:getDrawArgumentValue(35) > 0 or unit:getDrawArgumentValue(421) == -1) then
BASE:T(unit_name .. " cargo door is open")
return true
end
return false
end -- nil
@ -2547,7 +2559,7 @@ function UTILS.SaveToFile(Path,Filename,Data)
return true
end
--- Function to save an object to a file
--- Function to load an object from a file.
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @return #boolean outcome True if reading is possible and successful, else false.
@ -2724,6 +2736,9 @@ function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
if group and group:IsAlive() then
local name = group:GetName()
local template = string.gsub(name,"-(.+)$","")
if string.find(name,"AID") then
template = string.gsub(name,"(.AID.%d+$","")
end
if string.find(template,"#") then
template = string.gsub(name,"#(%d+)$","")
end
@ -4012,3 +4027,46 @@ function UTILS.ClockHeadingString(refHdg,tgtHdg)
local clockPos = math.ceil((relativeAngle % 360) / 30)
return clockPos.." o'clock"
end
--- Get a NATO abbreviated MGRS text for SRS use, optionally with prosody slow tag
-- @param #string Text The input string, e.g. "MGRS 4Q FJ 12345 67890"
-- @param #boolean Slow Optional - add slow tags
-- @return #string Output for (Slow) spelling in SRS TTS e.g. "MGRS;<prosody rate="slow">4;Quebec;Foxtrot;Juliett;1;2;3;4;5;6;7;8;niner;zero;</prosody>"
function UTILS.MGRSStringToSRSFriendly(Text,Slow)
local Text = string.gsub(Text,"MGRS ","")
Text = string.gsub(Text,"%s+","")
Text = string.gsub(Text,"([%a%d])","%1;") -- "0;5;1;"
Text = string.gsub(Text,"A","Alpha")
Text = string.gsub(Text,"B","Bravo")
Text = string.gsub(Text,"C","Charlie")
Text = string.gsub(Text,"D","Delta")
Text = string.gsub(Text,"E","Echo")
Text = string.gsub(Text,"F","Foxtrot")
Text = string.gsub(Text,"G","Golf")
Text = string.gsub(Text,"H","Hotel")
Text = string.gsub(Text,"I","India")
Text = string.gsub(Text,"J","Juliett")
Text = string.gsub(Text,"K","Kilo")
Text = string.gsub(Text,"L","Lima")
Text = string.gsub(Text,"M","Mike")
Text = string.gsub(Text,"N","November")
Text = string.gsub(Text,"O","Oscar")
Text = string.gsub(Text,"P","Papa")
Text = string.gsub(Text,"Q","Quebec")
Text = string.gsub(Text,"R","Romeo")
Text = string.gsub(Text,"S","Sierra")
Text = string.gsub(Text,"T","Tango")
Text = string.gsub(Text,"U","Uniform")
Text = string.gsub(Text,"V","Victor")
Text = string.gsub(Text,"W","Whiskey")
Text = string.gsub(Text,"X","Xray")
Text = string.gsub(Text,"Y","Yankee")
Text = string.gsub(Text,"Z","Zulu")
Text = string.gsub(Text,"0","zero")
Text = string.gsub(Text,"9","niner")
if Slow then
Text = '<prosody rate="slow">'..Text..'</prosody>'
end
Text = "MGRS;"..Text
return Text
end

View File

@ -722,6 +722,39 @@ AIRBASE.Sinai = {
["Wadi_al_Jandali"] = "Wadi al Jandali",
}
--- Airbases of the Kola map
--
-- * AIRBASE.Kola.Banak
-- * AIRBASE.Kola.Bas_100
-- * AIRBASE.Kola.Bodo
-- * AIRBASE.Kola.Jokkmokk
-- * AIRBASE.Kola.Kalixfors
-- * AIRBASE.Kola.Kemi_Tornio
-- * AIRBASE.Kola.Kiruna
-- * AIRBASE.Kola.Monchegorsk
-- * AIRBASE.Kola.Murmansk_International
-- * AIRBASE.Kola.Olenya
-- * AIRBASE.Kola.Rovaniemi
-- * AIRBASE.Kola.Severomorsk_1
-- * AIRBASE.Kola.Severomorsk_3
--
-- @field Kola
AIRBASE.Kola = {
["Banak"] = "Banak",
["Bas_100"] = "Bas 100",
["Bodo"] = "Bodo",
["Jokkmokk"] = "Jokkmokk",
["Kalixfors"] = "Kalixfors",
["Kemi_Tornio"] = "Kemi Tornio",
["Kiruna"] = "Kiruna",
["Monchegorsk"] = "Monchegorsk",
["Murmansk_International"] = "Murmansk International",
["Olenya"] = "Olenya",
["Rovaniemi"] = "Rovaniemi",
["Severomorsk_1"] = "Severomorsk-1",
["Severomorsk_3"] = "Severomorsk-3",
}
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
-- @type AIRBASE.ParkingSpot
-- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot.

View File

@ -367,7 +367,7 @@ function GROUP:GetDCSObject()
return DCSGroup
end
self:E(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.GroupName)))
self:T2(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.GroupName)))
return nil
end
@ -1228,6 +1228,7 @@ function GROUP:GetCoordinate()
-- no luck, try the API way
local DCSGroup = Group.getByName(self.GroupName)
if DCSGroup then
local DCSUnits = DCSGroup:getUnits() or {}
for _,_unit in pairs(DCSUnits) do
if Object.isExist(_unit) then
@ -1240,6 +1241,7 @@ function GROUP:GetCoordinate()
end
end
end
end
BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } )
@ -1794,10 +1796,14 @@ end
--- Returns the group template from the global _DATABASE object (an instance of @{Core.Database#DATABASE}).
-- @param #GROUP self
-- @return #table
-- @return #table Template table.
function GROUP:GetTemplate()
local GroupName = self:GetName()
return UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
local template=_DATABASE:GetGroupTemplate( GroupName )
if template then
return UTILS.DeepCopy( template )
end
return nil
end
--- Returns the group template route.points[] (the waypoints) from the global _DATABASE object (an instance of @{Core.Database#DATABASE}).

View File

@ -40,6 +40,7 @@
-- @field #number coalition Coalition ID.
-- @field #number country Country ID.
-- @field DCS#Desc desc Descriptor table.
-- @field DCS#Desc guidance Missile guidance descriptor.
-- @field DCS#Unit launcher Launcher DCS unit.
-- @field Wrapper.Unit#UNIT launcherUnit Launcher Unit.
-- @field #string launcherName Name of launcher unit.
@ -196,6 +197,9 @@ function WEAPON:New(WeaponObject)
if self:IsMissile() and self.desc.missileCategory then
self.categoryMissile=self.desc.missileCategory
if self.desc.guidance then
self.guidance = self.desc.guidance
end
end
-- Get type name.
@ -667,6 +671,26 @@ function WEAPON:IsTorpedo()
return self.category==Weapon.Category.TORPEDO
end
--- Check if weapon is a Fox One missile (Radar Semi-Active).
-- @param #WEAPON self
-- @return #boolean If `true`, is a Fox One.
function WEAPON:IsFoxOne()
return self.guidance==Weapon.GuidanceType.RADAR_SEMI_ACTIVE
end
--- Check if weapon is a Fox Two missile (IR guided).
-- @param #WEAPON self
-- @return #boolean If `true`, is a Fox Two.
function WEAPON:IsFoxTwo()
return self.guidance==Weapon.GuidanceType.IR
end
--- Check if weapon is a Fox Three missile (Radar Active).
-- @param #WEAPON self
-- @return #boolean If `true`, is a Fox Three.
function WEAPON:IsFoxThree()
return self.guidance==Weapon.GuidanceType.RADAR_ACTIVE
end
--- Destroy the weapon object.
-- @param #WEAPON self