mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-07-16 22:22:25 +00:00
Compare commits
100 Commits
2.9.17
...
589e65963f
| Author | SHA1 | Date | |
|---|---|---|---|
| 589e65963f | |||
| af4f20af67 | |||
| 98ed7d50c1 | |||
| 626b12f486 | |||
| 8c60a8c678 | |||
| 867f919d5f | |||
| f769a0deb5 | |||
| 7e9dd9deff | |||
| b6ea6606fc | |||
| 5005203206 | |||
| a3455855b8 | |||
| d1e4dd3fe9 | |||
| 9326cfad1c | |||
| b8e114101b | |||
| f083b77390 | |||
| db3bc3742e | |||
| bf10196662 | |||
| a0420e1cb6 | |||
| 27bd27a2e5 | |||
| 8e219c5877 | |||
| 3d1dcd78a3 | |||
| 9cda1b88ea | |||
| 09f99b9a74 | |||
| ff5205e686 | |||
| ebd105f80d | |||
| cc8a4e0bad | |||
| 5f70e2d6b1 | |||
| 95f38fbf55 | |||
| aec3466f13 | |||
| 696630fe4d | |||
| 47aa524677 | |||
| 79f03c164c | |||
| ca7d3a48a7 | |||
| 2556dbe145 | |||
| af1e9546e5 | |||
| 3203a22668 | |||
| b69ac2f86f | |||
| 0d410f3e55 | |||
| cbe6756330 | |||
| 07045be32b | |||
| 52a2132463 | |||
| 9272b1f18c | |||
| b84880c0dd | |||
| a9be18b284 | |||
| 93178df67f | |||
| f34bf1e052 | |||
| e159eb83b7 | |||
| 2581836058 | |||
| d9651e1dea | |||
| 0347f185a5 | |||
| 5e2218de38 | |||
| 6db5441bb1 | |||
| 13b7e43729 | |||
| a72f7298d0 | |||
| 801cc105da | |||
| f186b4bd58 | |||
| 79f13fa6ae | |||
| d38fc4ec8f | |||
| ba1575d674 | |||
| 391182e23f | |||
| 6d5fb64fde | |||
| 5df1fb88f1 | |||
| 2e4d4f056a | |||
| a9ceda4edd | |||
| e76fd23396 | |||
| 7df1eb963f | |||
| aff1d3c7db | |||
| d15f6cdb44 | |||
| c59b668eec | |||
| 4620a83317 | |||
| 87d0c4e9d4 | |||
| 74361f6a3a | |||
| b82b882e02 | |||
| 347783d4b7 | |||
| 0b2f07ca6c | |||
| 7c5d5543e0 | |||
| 3d10716b1f | |||
| c48a7d4400 | |||
| 79130d4fe2 | |||
| aebb6ac0db | |||
| 3eb910b47e | |||
| 6223b8e1a7 | |||
| 533a4dfa6c | |||
| dbc752253b | |||
| 7f074781f4 | |||
| ab77fcbc1f | |||
| 56520092c3 | |||
| 252a1b0761 | |||
| 51e5f53965 | |||
| b0976d1613 | |||
| a737ca71e6 | |||
| 1071300a7f | |||
| 2490958090 | |||
| b78b76e579 | |||
| efff22fc18 | |||
| 4b27e92210 | |||
| f2063f41f6 | |||
| 12241e931a | |||
| 4cb6debef8 | |||
| efe4bf3912 |
@@ -855,7 +855,7 @@ end
|
||||
-- @param DCS#Time EventTime The time stamp of the event.
|
||||
-- @param DCS#Object Initiator The initiating object of the event.
|
||||
function BASE:CreateEventTakeoff( EventTime, Initiator )
|
||||
self:F( { EventTime, Initiator } )
|
||||
--self:F( { EventTime, Initiator } )
|
||||
|
||||
local Event = {
|
||||
id = world.event.S_EVENT_TAKEOFF,
|
||||
|
||||
@@ -1380,12 +1380,12 @@ function DATABASE:_RegisterDynamicGroup(Groupname)
|
||||
local DCSUnitName = DCSUnit:getName()
|
||||
|
||||
-- Add unit.
|
||||
self:I(string.format("Register Unit: %s", tostring(DCSUnitName)))
|
||||
self:T(string.format("Register Unit: %s", tostring(DCSUnitName)))
|
||||
self:AddUnit( tostring(DCSUnitName), true )
|
||||
|
||||
end
|
||||
else
|
||||
self:E({"Group does not exist: ", DCSGroup})
|
||||
self:T({"Group does not exist: ", DCSGroup})
|
||||
end
|
||||
return self
|
||||
end
|
||||
@@ -1488,27 +1488,28 @@ end
|
||||
-- @return #DATABASE self
|
||||
function DATABASE:_RegisterAirbase(airbase)
|
||||
|
||||
local IsSyria = UTILS.GetDCSMap() == "Syria" and true or false
|
||||
local countHSyria = 0
|
||||
--local IsSyria = UTILS.GetDCSMap() == "Syria" and true or false
|
||||
--local countHSyria = 0
|
||||
|
||||
if airbase then
|
||||
|
||||
-- Get the airbase name.
|
||||
local DCSAirbaseName = airbase:getName()
|
||||
|
||||
-- DCS 2.9.8.1107 added 143 helipads all named H with the same object ID ..
|
||||
--[[ DCS 2.9.8.1107 added 143 helipads all named H with the same object ID ..
|
||||
if IsSyria and DCSAirbaseName == "H" and countHSyria > 0 then
|
||||
--[[
|
||||
--
|
||||
local p = airbase:getPosition().p
|
||||
local mgrs = COORDINATE:New(p.x,p.z,p.y):ToStringMGRS()
|
||||
self:I("Airbase on Syria map named H @ "..mgrs)
|
||||
countHSyria = countHSyria + 1
|
||||
if countHSyria > 1 then return self end
|
||||
--]]
|
||||
--
|
||||
return self
|
||||
elseif IsSyria and DCSAirbaseName == "H" and countHSyria == 0 then
|
||||
countHSyria = countHSyria + 1
|
||||
end
|
||||
--]]
|
||||
|
||||
-- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now.
|
||||
local airbaseID=airbase:getID()
|
||||
|
||||
@@ -1453,7 +1453,12 @@ function EVENT:onEvent( Event )
|
||||
-- Weapon.
|
||||
if Event.weapon and type(Event.weapon) == "table" and Event.weapon.isExist and Event.weapon:isExist() then
|
||||
Event.Weapon = Event.weapon
|
||||
Event.WeaponName = Event.weapon:isExist() and Event.weapon:getTypeName() or "Unknown Weapon"
|
||||
if Event.weapon_name == "ZELL Booster" then
|
||||
Event.WeaponName = "ZELL Booster"
|
||||
else
|
||||
Event.WeaponName = Event.weapon:isExist() and Event.weapon.getTypeName and Event.weapon:getTypeName() or "Unknown Weapon"
|
||||
end
|
||||
if Event.weapon_name == "ZELL Booster" then Event.WeaponName = "ZELL Booster" end
|
||||
Event.WeaponUNIT = CLIENT:Find( Event.Weapon, '', true ) -- Sometimes, the weapon is a player unit!
|
||||
Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName()
|
||||
--Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName()
|
||||
|
||||
@@ -869,10 +869,21 @@ do
|
||||
local MenuTable = {}
|
||||
for MenuText, Menu in pairs( self.Menus or {} ) do
|
||||
local tag = Menu.MenuTag or math.random(1,10000)
|
||||
MenuTable[#MenuTable+1] = {Tag=tag, Enty=Menu}
|
||||
MenuTable[#MenuTable+1] = {Tag=tag, Entry=Menu}
|
||||
end
|
||||
table.sort(MenuTable, function (k1, k2) return k1.tag < k2.tag end )
|
||||
for _, Menu in pairs( MenuTable ) do
|
||||
local function SortTable(k1,k2)
|
||||
if not k1 then
|
||||
if not k2 then return true else return false end
|
||||
elseif not k2 then
|
||||
if not k1 then return true else return false end
|
||||
else
|
||||
return (k1.Tag or 15) <= (k2.Tag or 15)
|
||||
end
|
||||
return false
|
||||
end
|
||||
table.sort(MenuTable, SortTable)
|
||||
--table.sort(MenuTable, function (k1, k2) return (k1.tag or 15) <= (k2.tag or 15) end )
|
||||
for _, Menu in ipairs( MenuTable ) do
|
||||
Menu.Entry:Refresh()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -375,3 +375,13 @@ function SCHEDULEDISPATCHER:NoTrace( Scheduler )
|
||||
Scheduler.ShowTrace = false
|
||||
end
|
||||
|
||||
--- Helper for memory cleanup for self stopping schedulers
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
-- @param #string CallID (Optional) Scheduler Call ID.
|
||||
function SCHEDULEDISPATCHER:_Reclaim( Scheduler, CallID )
|
||||
self:Stop( Scheduler, CallID ) -- remove DCS timer, nil ScheduleID
|
||||
if self.Schedule[Scheduler] then self.Schedule[Scheduler][CallID] = nil end
|
||||
self.ObjectSchedulers[CallID] = nil
|
||||
self.PersistentSchedulers[CallID] = nil
|
||||
end
|
||||
|
||||
@@ -62,7 +62,8 @@ do -- SET_BASE
|
||||
-- @field #table List Unused table.
|
||||
-- @field Core.Scheduler#SCHEDULER CallScheduler
|
||||
-- @field #SET_BASE.Filters Filter Filters
|
||||
-- @field #booleaen filterNoRegex If true, FilterPrefix ignores special characters and evaluates plain string.
|
||||
-- @field #boolean filterNoRegex If true, FilterPrefix ignores special characters and evaluates plain string. Defaults to false.
|
||||
-- @field #boolean filterReplaceDash If true then if filterNoRegex is false, replace dashes with regex pattern friendly pattern. Defaults to true
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects.
|
||||
@@ -102,6 +103,7 @@ do -- SET_BASE
|
||||
},
|
||||
},
|
||||
filterNoRegex=false,
|
||||
filterReplaceDash=true,
|
||||
}
|
||||
|
||||
--- Filters
|
||||
@@ -256,7 +258,8 @@ do -- SET_BASE
|
||||
-- @return #boolean Returns `true`, if the pattern is contained in the name and false otherwise.
|
||||
function SET_BASE:_SearchPattern(Name, Pattern, NoRegex, ReplaceDash)
|
||||
NoRegex=NoRegex or self.filterNoRegex
|
||||
if ReplaceDash==true then
|
||||
ReplaceDash=ReplaceDash or self.filterReplaceDash
|
||||
if ReplaceDash==true and NoRegex ~= true then
|
||||
-- Not sure why "-" is replaced by "%-" ?! - So we can still match group names with a dash in them
|
||||
-- reason is that the string is interpreted as a pattern and "-" is a special character then. For interpreting it as a string, fourth parameter needs to be set to true.
|
||||
Pattern=Pattern:gsub("-", "%%-")
|
||||
@@ -265,6 +268,16 @@ do -- SET_BASE
|
||||
return contain
|
||||
end
|
||||
|
||||
---Set Regex Options for FilterPrefix function.
|
||||
-- @param #SET_BASE self
|
||||
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
|
||||
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
|
||||
-- @return #SET_BASE self
|
||||
function SET_BASE:FilterSetRegex(NoRegex,ReplaceDash)
|
||||
if NoRegex ~= nil then self.filterNoRegex = NoRegex end
|
||||
self.filterReplaceDash = ReplaceDash or true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the Set.
|
||||
-- @param #SET_BASE self
|
||||
@@ -1198,7 +1211,21 @@ do
|
||||
-- @param #SET_GROUP self
|
||||
-- @return #SET_GROUP self
|
||||
|
||||
|
||||
---Set Regex Options for FilterPrefix function.
|
||||
-- @function [parent=#SET_GROUP] FilterSetRegex
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
|
||||
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
|
||||
-- @return #SET_GROUP self
|
||||
|
||||
--- Builds a set of objects of same coalitions.
|
||||
-- Possible current coalitions are red, blue and neutral.
|
||||
-- @function [parent=#SET_GROUP] FilterCoalitions
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" and coalition.side.RED, coalition.side.BLUE,coalition.side.NEUTRAL
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_GROUP self
|
||||
|
||||
end
|
||||
|
||||
--- Get a *new* set table that only contains alive groups.
|
||||
@@ -2116,7 +2143,7 @@ do
|
||||
if self.Filter.GroupPrefixes and MGroupInclude then
|
||||
local MGroupPrefix = false
|
||||
for GroupPrefixId, GroupPrefix in pairs(self.Filter.GroupPrefixes) do
|
||||
if self:_SearchPattern(MGroup:GetName(), GroupPrefix, false, true) then
|
||||
if self:_SearchPattern(MGroup:GetName(), GroupPrefix, self.filterNoRegex, self.filterReplaceDash) then
|
||||
MGroupPrefix = true
|
||||
end
|
||||
end
|
||||
@@ -2337,6 +2364,26 @@ do -- SET_UNIT
|
||||
-- @param #SET_UNIT self
|
||||
-- @return #SET_UNIT self
|
||||
|
||||
--- Filter the set once
|
||||
-- @function [parent=#SET_UNIT] FilterOnce
|
||||
-- @param #SET_UNIT self
|
||||
-- @return #SET_UNIT self
|
||||
|
||||
---Set Regex Options for FilterPrefix function.
|
||||
-- @function [parent=#SET_UNIT] FilterSetRegex
|
||||
-- @param #SET_UNIT self
|
||||
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
|
||||
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
|
||||
-- @return #SET_UNIT self
|
||||
|
||||
--- Builds a set of objects of same coalitions.
|
||||
-- Possible current coalitions are red, blue and neutral.
|
||||
-- @function [parent=#SET_UNIT] FilterCoalitions
|
||||
-- @param #SET_UNIT self
|
||||
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" and coalition.side.RED, coalition.side.BLUE,coalition.side.NEUTRAL
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_UNIT self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -3348,7 +3395,7 @@ do -- SET_UNIT
|
||||
if self.Filter.UnitPrefixes and MUnitInclude then
|
||||
local MUnitPrefix = false
|
||||
for UnitPrefixId, UnitPrefix in pairs(self.Filter.UnitPrefixes) do
|
||||
if self:_SearchPattern(MUnit:GetName(), UnitPrefix, false, true) then
|
||||
if self:_SearchPattern(MUnit:GetName(), UnitPrefix, self.filterNoRegex, self.filterReplaceDash) then
|
||||
MUnitPrefix = true
|
||||
end
|
||||
end
|
||||
@@ -3544,7 +3591,27 @@ do -- SET_STATIC
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.STATICS)) -- Core.Set#SET_STATIC
|
||||
|
||||
|
||||
--- Filter the set once
|
||||
-- @function [parent=#SET_STATIC] FilterOnce
|
||||
-- @param #SET_STATIC self
|
||||
-- @return #SET_STATIC self
|
||||
|
||||
---Set Regex Options for FilterPrefix function.
|
||||
-- @function [parent=#SET_STATIC] FilterSetRegex
|
||||
-- @param #SET_STATIC self
|
||||
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
|
||||
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
|
||||
-- @return #SET_STATIC self
|
||||
|
||||
--- Builds a set of objects of same coalitions.
|
||||
-- Possible current coalitions are red, blue and neutral.
|
||||
-- @function [parent=#SET_STATIC] FilterCoalitions
|
||||
-- @param #SET_STATIC self
|
||||
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" and coalition.side.RED, coalition.side.BLUE,coalition.side.NEUTRAL
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_STATIC self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -4119,7 +4186,7 @@ do -- SET_STATIC
|
||||
if self.Filter.StaticPrefixes then
|
||||
local MStaticPrefix = false
|
||||
for StaticPrefixId, StaticPrefix in pairs(self.Filter.StaticPrefixes) do
|
||||
if self:_SearchPattern(MStatic:GetName(), StaticPrefix, false, true) then
|
||||
if self:_SearchPattern(MStatic:GetName(), StaticPrefix, self.filterNoRegex, self.filterReplaceDash) then
|
||||
MStaticPrefix = true
|
||||
end
|
||||
end
|
||||
@@ -4303,7 +4370,27 @@ do -- SET_CLIENT
|
||||
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.CLIENTS)) -- #SET_CLIENT
|
||||
|
||||
self:FilterActive(false)
|
||||
|
||||
|
||||
--- Filter the set once
|
||||
-- @function [parent=#SET_CLIENT] FilterOnce
|
||||
-- @param #SET_CLIENT self
|
||||
-- @return #SET_CLIENT self
|
||||
|
||||
---Set Regex Options for FilterPrefix function.
|
||||
-- @function [parent=#SET_CLIENT] FilterSetRegex
|
||||
-- @param #SET_CLIENT self
|
||||
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
|
||||
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
|
||||
-- @return #SET_CLIENT self
|
||||
|
||||
--- Builds a set of objects of same coalitions.
|
||||
-- Possible current coalitions are red, blue and neutral.
|
||||
-- @function [parent=#SET_CLIENT] FilterCoalitions
|
||||
-- @param #SET_CLIENT self
|
||||
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" and coalition.side.RED, coalition.side.BLUE,coalition.side.NEUTRAL
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_CLIENT self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -4902,7 +4989,7 @@ do -- SET_CLIENT
|
||||
if self.Filter.ClientPrefixes and MClientInclude then
|
||||
local MClientPrefix = false
|
||||
for ClientPrefixId, ClientPrefix in pairs(self.Filter.ClientPrefixes) do
|
||||
if self:_SearchPattern(MClient.UnitName, ClientPrefix) then
|
||||
if self:_SearchPattern(MClient.UnitName, ClientPrefix, self.filterNoRegex, self.filterReplaceDash) then
|
||||
MClientPrefix = true
|
||||
end
|
||||
end
|
||||
@@ -4926,7 +5013,7 @@ do -- SET_CLIENT
|
||||
local playername = MClient:GetPlayerName() or "Unknown"
|
||||
--self:T(playername)
|
||||
for _,_Playername in pairs(self.Filter.Playernames) do
|
||||
if playername and self:_SearchPattern(playername,_Playername) then
|
||||
if playername and self:_SearchPattern(playername,_Playername,self.filterNoRegex, self.filterReplaceDash) then
|
||||
MClientPlayername = true
|
||||
end
|
||||
end
|
||||
@@ -4939,7 +5026,7 @@ do -- SET_CLIENT
|
||||
local callsign = MClient:GetCallsign()
|
||||
--self:I(callsign)
|
||||
for _,_Callsign in pairs(self.Filter.Callsigns) do
|
||||
if callsign and self:_SearchPattern(callsign,_Callsign, true) then
|
||||
if callsign and self:_SearchPattern(callsign,_Callsign, self.filterNoRegex, self.filterReplaceDash) then
|
||||
MClientCallsigns = true
|
||||
end
|
||||
end
|
||||
@@ -5360,7 +5447,7 @@ do -- SET_PLAYER
|
||||
if self.Filter.ClientPrefixes then
|
||||
local MClientPrefix = false
|
||||
for ClientPrefixId, ClientPrefix in pairs(self.Filter.ClientPrefixes) do
|
||||
if self:_SearchPattern(MClient.UnitName,ClientPrefix) then
|
||||
if self:_SearchPattern(MClient.UnitName,ClientPrefix,self.filterNoRegex, self.filterReplaceDash) then
|
||||
MClientPrefix = true
|
||||
end
|
||||
end
|
||||
@@ -5830,7 +5917,19 @@ do -- SET_ZONE
|
||||
function SET_ZONE:New()
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.ZONES))
|
||||
|
||||
|
||||
--- Filter the set once
|
||||
-- @function [parent=#SET_ZONE] FilterOnce
|
||||
-- @param #SET_ZONE self
|
||||
-- @return #SET_ZONE self
|
||||
|
||||
---Set Regex Options for FilterPrefix function.
|
||||
-- @function [parent=#SET_ZONE] FilterSetRegex
|
||||
-- @param #SET_ZONE self
|
||||
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
|
||||
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
|
||||
-- @return #SET_ZONE self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -6068,7 +6167,7 @@ do -- SET_ZONE
|
||||
if self.Filter.Prefixes then
|
||||
local MZonePrefix = false
|
||||
for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do
|
||||
if self:_SearchPattern(MZoneName, ZonePrefix, false, true) then
|
||||
if self:_SearchPattern(MZoneName, ZonePrefix, self.filterNoRegex, self.filterReplaceDash) then
|
||||
MZonePrefix = true
|
||||
end
|
||||
end
|
||||
@@ -6577,7 +6676,7 @@ do -- SET_ZONE_GOAL
|
||||
if self.Filter.Prefixes then
|
||||
local MZonePrefix = false
|
||||
for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do
|
||||
if self:_SearchPattern(MZoneName, ZonePrefix, false, true) then
|
||||
if self:_SearchPattern(MZoneName, ZonePrefix, self.filterNoRegex, self.filterReplaceDash) then
|
||||
MZonePrefix = true
|
||||
end
|
||||
end
|
||||
@@ -6713,7 +6812,19 @@ do -- SET_OPSZONE
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.OPSZONES))
|
||||
|
||||
|
||||
--- Filter the set once
|
||||
-- @function [parent=#SET_OPSZONE] FilterOnce
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
|
||||
---Set Regex Options for FilterPrefix function.
|
||||
-- @function [parent=#SET_OPSZONE] FilterSetRegex
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
|
||||
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
|
||||
-- @return #SET_OPSZONE self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -6938,7 +7049,7 @@ do -- SET_OPSZONE
|
||||
for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do
|
||||
|
||||
-- Prefix
|
||||
if self:_SearchPattern(MZoneName, ZonePrefix, false, true) then
|
||||
if self:_SearchPattern(MZoneName, ZonePrefix, self.filterNoRegex, self.filterReplaceDash) then
|
||||
MZonePrefix = true
|
||||
break --Break the loop as we found the prefix.
|
||||
end
|
||||
@@ -7732,7 +7843,7 @@ function SET_OPSGROUP:_EventOnBirth(Event)
|
||||
local MGroupPrefix = false
|
||||
|
||||
for GroupPrefixId, GroupPrefix in pairs(self.Filter.GroupPrefixes) do
|
||||
if self:_SearchPattern(MGroup:GetName(), GroupPrefix, false, true) then
|
||||
if self:_SearchPattern(MGroup:GetName(), GroupPrefix, self.filterNoRegex, self.filterReplaceDash) then
|
||||
MGroupPrefix = true
|
||||
end
|
||||
end
|
||||
@@ -8066,7 +8177,7 @@ do -- SET_SCENERY
|
||||
if self.Filter.Prefixes then
|
||||
local MSceneryPrefix = false
|
||||
for ZonePrefixId, ZonePrefix in pairs(self.Filter.Prefixes) do
|
||||
if self:_SearchPattern(MSceneryName, ZonePrefix, false, true) then
|
||||
if self:_SearchPattern(MSceneryName, ZonePrefix, self.filterNoRegex, self.filterReplaceDash) then
|
||||
MSceneryPrefix = true
|
||||
end
|
||||
end
|
||||
@@ -8289,7 +8400,19 @@ do -- SET_DYNAMICCARGO
|
||||
|
||||
--- Inherits from BASE
|
||||
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.DYNAMICCARGO)) -- Core.Set#SET_DYNAMICCARGO
|
||||
|
||||
|
||||
--- Filter the set once
|
||||
-- @function [parent=#SET_DYNAMICCARGO] FilterOnce
|
||||
-- @param #SET_DYNAMICCARGO self
|
||||
-- @return #SET_DYNAMICCARGO self
|
||||
|
||||
---Set Regex Options for FilterPrefix function.
|
||||
-- @function [parent=#SET_DYNAMICCARGO] FilterSetRegex
|
||||
-- @param #SET_DYNAMICCARGO self
|
||||
-- @param #boolean NoRegex If true, switch off Regex pattern matching for FilterPrefixes.
|
||||
-- @param #boolean ReplaceDash If false, switch off dash "-" replacement in strings for FilterPrefixes.
|
||||
-- @return #SET_DYNAMICCARGO self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -8337,7 +8460,7 @@ do -- SET_DYNAMICCARGO
|
||||
if self.Filter.StaticPrefixes then
|
||||
local DCargoPrefix = false
|
||||
for StaticPrefixId, StaticPrefix in pairs(self.Filter.StaticPrefixes) do
|
||||
if self:_SearchPattern(DCargo:GetName(), StaticPrefix, false, true) then
|
||||
if self:_SearchPattern(DCargo:GetName(), StaticPrefix, self.filterNoRegex, self.filterReplaceDash) then
|
||||
DCargoPrefix = true
|
||||
end
|
||||
end
|
||||
@@ -8505,7 +8628,7 @@ do -- SET_DYNAMICCARGO
|
||||
function SET_DYNAMICCARGO:FilterCurrentOwner(PlayerName)
|
||||
self:FilterFunction(
|
||||
function(cargo)
|
||||
if cargo and cargo.Owner and self:_SearchPattern(cargo.Owner, PlayerName, true) then
|
||||
if cargo and cargo.Owner and self:_SearchPattern(cargo.Owner, PlayerName, self.filterNoRegex, self.filterReplaceDash) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
@@ -1184,6 +1184,7 @@ end
|
||||
|
||||
--- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types.
|
||||
-- @param #SPAWN self
|
||||
-- @param #table SpawnZoneTable A table with @{Core.Zone} objects. If nil or empty, the method returns self without effect.
|
||||
-- @param #table SpawnZoneTable A table with @{Core.Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Core.Zone}s objects.
|
||||
-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone.
|
||||
-- @return #SPAWN self
|
||||
@@ -1201,6 +1202,9 @@ end
|
||||
function SPAWN:InitRandomizeZones( SpawnZoneTable, RandomizePositionInZone )
|
||||
--self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } )
|
||||
|
||||
if not SpawnZoneTable then
|
||||
return self
|
||||
end
|
||||
local temptable = {}
|
||||
for _,_temp in pairs(SpawnZoneTable) do
|
||||
temptable[#temptable+1] = _temp
|
||||
@@ -2149,6 +2153,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
local parkingspots = {}
|
||||
local parkingindex = {}
|
||||
local spots
|
||||
local useexplicitspots = false
|
||||
|
||||
-- Spawn happens on ground, i.e. at an airbase, a FARP or a ship.
|
||||
if spawnonground and not SpawnTemplate.parked then
|
||||
@@ -2178,15 +2183,41 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
local scanscenery = false
|
||||
local verysafe = false
|
||||
|
||||
-- Use exact parking data when provided, otherwise let helicopters on ships/FARPs
|
||||
-- use the smarter parking search before falling back to the procedural queue path.
|
||||
-- Number of free parking spots at the airbase.
|
||||
if autoparking then
|
||||
if Parkingdata~=nil then
|
||||
-- Parking data explicitly set by user as input parameter.
|
||||
nfree = #Parkingdata
|
||||
spots = Parkingdata
|
||||
useexplicitspots = true
|
||||
elseif autoparking and AirbaseCategory == Airbase.Category.HELIPAD and ishelo then
|
||||
if termtype == nil then
|
||||
-- Helo is spawned. Try exclusive helo spots first.
|
||||
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, nil )
|
||||
nfree = #spots
|
||||
if nfree < nunits then
|
||||
-- Not enough helo ports. Let's try also other terminal types.
|
||||
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, nil )
|
||||
nfree = #spots
|
||||
end
|
||||
else
|
||||
-- Terminal type explicitly given.
|
||||
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, nil )
|
||||
nfree = #spots
|
||||
end
|
||||
|
||||
if nfree >= nunits then
|
||||
useexplicitspots = true
|
||||
else
|
||||
-- These places work procedural and have some kind of build in queue ==> Less effort.
|
||||
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true )
|
||||
spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true )
|
||||
end
|
||||
elseif autoparking then
|
||||
-- These places work procedural and have some kind of build in queue ==> Less effort.
|
||||
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true )
|
||||
spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true )
|
||||
elseif Parkingdata~=nil then
|
||||
-- Parking data explicitly set by user as input parameter. (This was commented out for some unknown reason. But I need it this way.)
|
||||
nfree=#Parkingdata
|
||||
spots=Parkingdata
|
||||
else
|
||||
if ishelo then
|
||||
if termtype == nil then
|
||||
@@ -2238,7 +2269,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
local _notenough = false
|
||||
|
||||
-- Need to differentiate some cases again.
|
||||
if autoparking then
|
||||
if autoparking and not useexplicitspots then
|
||||
|
||||
-- On free spot required in these cases.
|
||||
if nfree >= 1 then
|
||||
@@ -2271,6 +2302,10 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
end
|
||||
end
|
||||
|
||||
if useexplicitspots and parkingspots[1] then
|
||||
PointVec3 = parkingspots[1]
|
||||
end
|
||||
|
||||
-- Not enough spots ==> Prepare airstart.
|
||||
if _notenough then
|
||||
|
||||
@@ -2296,9 +2331,15 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
|
||||
Takeoff = GROUP.Takeoff.Air
|
||||
spawnonground = false
|
||||
else
|
||||
else
|
||||
if not Takeoff == GROUP.Takeoff.Runway then
|
||||
self:E( string.format( "WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
|
||||
return nil
|
||||
else
|
||||
Takeoff = GROUP.Takeoff.Runway
|
||||
spawnonground = false
|
||||
self:E( string.format( "WARNING: Group %s set to runway spawning at %s, this only works in Single Player!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2339,7 +2380,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
if spawnonground then
|
||||
|
||||
-- Ships and FARPS seem to have a build in queue.
|
||||
if autoparking then
|
||||
if autoparking and not useexplicitspots then
|
||||
|
||||
-- Spawn on ship. We take only the position of the ship.
|
||||
SpawnTemplate.units[UnitID].x = PointVec3.x -- TX
|
||||
@@ -2357,7 +2398,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
|
||||
else
|
||||
|
||||
-- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set.
|
||||
-- Spawn in air as requested initially. Original template orientation is preserved, altitude is already correctly set.
|
||||
SpawnTemplate.units[UnitID].x = TX
|
||||
SpawnTemplate.units[UnitID].y = TY
|
||||
SpawnTemplate.units[UnitID].alt = PointVec3.y
|
||||
@@ -2390,7 +2431,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
if Takeoff == GROUP.Takeoff.Air then
|
||||
for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do
|
||||
--SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) --No need to create a new SCHEDULER instance every time!
|
||||
self:ScheduleOnce(5, BASE.CreateEventTakeoff, {GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject()})
|
||||
self:ScheduleOnce(5, BASE.CreateEventTakeoff, GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject())
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2582,21 +2623,51 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex
|
||||
local scanscenery = false
|
||||
local verysafe = false
|
||||
|
||||
-- Number of free parking spots at the airbase.
|
||||
if spawnonship or spawnonfarp or spawnonrunway then
|
||||
-- These places work procedural and have some kind of build in queue ==> Less effort.
|
||||
--self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
|
||||
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true )
|
||||
spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true )
|
||||
--[[
|
||||
elseif Parkingdata~=nil then
|
||||
-- Parking data explicitly set by user as input parameter.
|
||||
nfree=#Parkingdata
|
||||
spots=Parkingdata
|
||||
]]
|
||||
else
|
||||
if ishelo then
|
||||
-- Use exact parking data when provided, otherwise let helicopters on FARPs
|
||||
-- use the smarter parking search before falling back to the procedural queue path.
|
||||
local useexplicitspots = false
|
||||
|
||||
-- Number of free parking spots at the airbase.
|
||||
if Parkingdata~=nil then
|
||||
-- Parking data explicitly set by user as input parameter.
|
||||
nfree = #Parkingdata
|
||||
spots = Parkingdata
|
||||
useexplicitspots = true
|
||||
elseif spawnonfarp and ishelo then
|
||||
if termtype == nil then
|
||||
-- Helo is spawned. Try exclusive helo spots first.
|
||||
--self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) )
|
||||
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, nil )
|
||||
nfree = #spots
|
||||
if nfree < nunits then
|
||||
-- Not enough helo ports. Let's try also other terminal types.
|
||||
--self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) )
|
||||
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, nil )
|
||||
nfree = #spots
|
||||
end
|
||||
else
|
||||
-- Terminal type explicitly given.
|
||||
--self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) )
|
||||
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, nil )
|
||||
nfree = #spots
|
||||
end
|
||||
|
||||
if nfree >= nunits then
|
||||
useexplicitspots = true
|
||||
else
|
||||
-- These places work procedural and have some kind of build in queue ==> Less effort.
|
||||
--self:T2( string.format( "Group %s is spawned on farp %s using procedural spots.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
|
||||
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true )
|
||||
spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true )
|
||||
end
|
||||
elseif spawnonship or spawnonfarp or spawnonrunway then
|
||||
-- These places work procedural and have some kind of build in queue ==> Less effort.
|
||||
--self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) )
|
||||
nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true )
|
||||
spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true )
|
||||
else
|
||||
if ishelo then
|
||||
if termtype == nil then
|
||||
-- Helo is spawned. Try exclusive helo spots first.
|
||||
--self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) )
|
||||
spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata )
|
||||
@@ -2659,7 +2730,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex
|
||||
local _notenough = false
|
||||
|
||||
-- Need to differentiate some cases again.
|
||||
if spawnonship or spawnonfarp or spawnonrunway then
|
||||
if (spawnonship or spawnonfarp or spawnonrunway) and not useexplicitspots then
|
||||
|
||||
-- On free spot required in these cases.
|
||||
if nfree >= 1 then
|
||||
|
||||
@@ -1848,7 +1848,7 @@ do -- DETECTION_BASE
|
||||
return self
|
||||
end
|
||||
|
||||
--- Validate if the detected item is locked.
|
||||
--- Validate if the detected item is locked (not in the radar sense, though!).
|
||||
-- @param #DETECTION_BASE self
|
||||
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
|
||||
-- @return #boolean
|
||||
@@ -1858,7 +1858,7 @@ do -- DETECTION_BASE
|
||||
|
||||
end
|
||||
|
||||
--- Lock a detected item.
|
||||
--- Lock a detected item (not in the radar sense, though!).
|
||||
-- @param #DETECTION_BASE self
|
||||
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
|
||||
-- @return #DETECTION_BASE
|
||||
@@ -1869,7 +1869,7 @@ do -- DETECTION_BASE
|
||||
return self
|
||||
end
|
||||
|
||||
--- Unlock a detected item.
|
||||
--- Unlock a detected item (not in the radar sense, though!).
|
||||
-- @param #DETECTION_BASE self
|
||||
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
|
||||
-- @return #DETECTION_BASE
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
-- * TOR M2
|
||||
-- * C-RAM
|
||||
-- * 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-21, S-300VM, S-300V4, S-400
|
||||
-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19, S-300VM, S-300V4, S-400 (SA-21)
|
||||
-- * 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, SAMP/T Block 1, SAMP/T Block 1INT, SAMP/T Block2
|
||||
-- * Other Mods: Nike
|
||||
@@ -135,8 +135,8 @@
|
||||
-- * 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" with 5P85CE launcher)
|
||||
-- * 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 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 2" for the 9A82ME variant and "Red SAM SA-23 HDS 1" for the 9A83ME variant)
|
||||
-- * SA-12 (e.g. "Red SAM SA-12 HDS")
|
||||
-- * SA-23 (e.g. "Red SAM SA-23 HDS")
|
||||
-- * 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.
|
||||
@@ -404,10 +404,9 @@ MANTIS.SamData = {
|
||||
["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=4 },
|
||||
["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1", ARMCapacity=16},
|
||||
["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2", ARMCapacity=18},
|
||||
["SA-21"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="92N6E"},
|
||||
["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4},
|
||||
["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4},
|
||||
["S-400"] = { Range=250, Blindspot=5, Height=27, Type="Long" , Radar="92N6E", ARMCapacity=4},
|
||||
["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32ME", ARMCapacity=4}, -- SA-23 Gladiator/Giant
|
||||
["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M-1E", ARMCapacity=4}, -- SA-23B
|
||||
["S-400"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="92N6E", ARMCapacity=4}, -- SA-21 Growler
|
||||
["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" },
|
||||
["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" },
|
||||
@@ -417,6 +416,7 @@ MANTIS.SamData = {
|
||||
["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true", ARMCapacity=3 },
|
||||
["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true", ARMCapacity=4 },
|
||||
["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM", ARMCapacity=12 }, -- 4 per starter, usually 3 starters in a battery
|
||||
["SON-9"] = { Range=20, Blindspot=0, Height=14, Type="Point", Radar="SON_9", Point="true" }, -- Fire Can FCR for S-60/KS-19 AAA
|
||||
}
|
||||
|
||||
--- SAM data HDS
|
||||
@@ -435,10 +435,8 @@ MANTIS.SamDataHDS = {
|
||||
["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln", ARMCapacity=8}, -- V55RUD
|
||||
["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln", ARMCapacity=3}, -- V55RUD
|
||||
["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=4 },
|
||||
["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l", ARMCapacity=12},
|
||||
["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l", ARMCapacity=12},
|
||||
["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME", ARMCapacity=14 },
|
||||
["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME", ARMCapacity=14 },
|
||||
["SA-12 HDS"] = { Range=100, Blindspot=6, Height=30, Type="Long" , Radar="S-300V 9A82 l", ARMCapacity=12},
|
||||
["SA-23 HDS"] = { Range=200, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A82ME", ARMCapacity=14 },
|
||||
["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
|
||||
@@ -526,6 +524,187 @@ MANTIS.SamDataCH = {
|
||||
["Lvkv9040M CHM"] = { Range=2, Blindspot=0.1, Height=1.2, Type="Point", Radar="LvKv9040",Point="true" },
|
||||
}
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- MANTIS Jammer Extension v2.0.0
|
||||
-- Standoff Jamming (SOJ) aircraft support for MANTIS IADS networks
|
||||
-- Physics-based jamming: asymmetric Gaussian rise + exponential decay
|
||||
-- Compatible with v7 jammer curves
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
--- Jammer Loadout Configurations (v8: Max 3 Pods)
|
||||
-- @type MANTIS.JammerLoadouts
|
||||
MANTIS.JammerLoadouts = {
|
||||
["1xALQ99"]={name="1x AN/ALQ-99",description="Baseline single pod. Full 64MHz-20GHz spectrum.",mult_LOW=1.12,mult_S=1.10,mult_IJ=1.12,mult_OPT=1.00,bt_mod=1.00,range_mod=1.00,tier="ALQ99"},
|
||||
["2xALQ99"]={name="2x AN/ALQ-99",description="Two pods. Improved ERP with log stacking.",mult_LOW=1.40,mult_S=1.36,mult_IJ=1.40,mult_OPT=1.02,bt_mod=1.00,range_mod=1.03,tier="ALQ99"},
|
||||
["3xALQ99"]={name="3x AN/ALQ-99 (Maximum Legacy)",description="Max legacy barrage. Highest broadband ERP.",mult_LOW=1.58,mult_S=1.52,mult_IJ=1.58,mult_OPT=1.04,bt_mod=1.00,range_mod=1.06,tier="ALQ99"},
|
||||
["1xALQ249"]={name="1x AN/ALQ-249 (AESA)",description="AESA 2-18GHz. High peak+wider window. Blind <2GHz.",mult_LOW=0.15,mult_S=1.82,mult_IJ=1.70,mult_OPT=1.00,bt_mod=1.28,range_mod=1.16,tier="ALQ249"},
|
||||
["2xALQ249"]={name="2x AN/ALQ-249",description="Two AESA pods. Strong S/IJ dominance.",mult_LOW=0.15,mult_S=2.30,mult_IJ=2.15,mult_OPT=1.00,bt_mod=1.38,range_mod=1.26,tier="ALQ249"},
|
||||
["3xALQ249"]={name="3x AN/ALQ-249 (Maximum AESA)",description="Max AESA ERP. Extreme S/IJ. No LOW coverage.",mult_LOW=0.15,mult_S=2.75,mult_IJ=2.58,mult_OPT=1.00,bt_mod=1.48,range_mod=1.35,tier="ALQ249"},
|
||||
["1xALQ99_1xALQ249"]={name="1x ALQ-99 + 1x ALQ-249",description="Balanced coverage. AESA S/IJ + ALQ-99 LOW-band.",mult_LOW=1.14,mult_S=1.90,mult_IJ=1.80,mult_OPT=1.01,bt_mod=1.22,range_mod=1.12,tier="Mixed"},
|
||||
["1xALQ99_2xALQ249"]={name="1x ALQ-99 + 2x ALQ-249 [Recommended]",description="Best all-around. High AESA ERP + LOW coverage.",mult_LOW=1.10,mult_S=2.38,mult_IJ=2.22,mult_OPT=1.01,bt_mod=1.35,range_mod=1.24,tier="Mixed"},
|
||||
["2xALQ99_1xALQ249"]={name="2x ALQ-99 + 1x ALQ-249",description="Strong LOW-band + AESA S/IJ boost.",mult_LOW=1.42,mult_S=2.10,mult_IJ=2.04,mult_OPT=1.03,bt_mod=1.25,range_mod=1.15,tier="Mixed"},
|
||||
}
|
||||
|
||||
--- Tiered loadout keys for F10 menu (v8: 3/3/3)
|
||||
-- @type MANTIS.JammerLoadoutTiers
|
||||
MANTIS.JammerLoadoutTiers = {
|
||||
ALQ99 = {"1xALQ99","2xALQ99","3xALQ99"},
|
||||
ALQ249 = {"1xALQ249","2xALQ249","3xALQ249"},
|
||||
Mixed = {"1xALQ99_1xALQ249","1xALQ99_2xALQ249","2xALQ99_1xALQ249"},
|
||||
}
|
||||
|
||||
--- Jitter configuration: ±10% per evaluation (EW Fundamentals Ch.9)
|
||||
-- @type MANTIS.JammerJitterPercent
|
||||
MANTIS.JammerJitterPercent = 0.10
|
||||
|
||||
--- Jammer SAM Parameters — v9 curves (v8.1) {peak%, mu_nm, sigma_L, tail_dist, band, floor%}
|
||||
-- v9 changes from v8:
|
||||
-- * Width rework: curves now track radar detection/track envelope (not just missile range)
|
||||
-- * Long-range systems widened (SA-5, SA-21, SA-23, Nike, PAC-2, S-300/400 family)
|
||||
-- * Short-range systems tightened (SA-19, SON-9, HQ-7, Rapier)
|
||||
-- * HAWK widened to match CW illuminator envelope (~90nm)
|
||||
-- floor: residual effectiveness inside burnthrough (now tapers past mu with R^2 falloff)
|
||||
-- 5 = legacy radar, no ECCM | 3 = moderate ECCM | 2 = advanced ECCM/AESA | 0 = optical/IR
|
||||
-- @type MANTIS.JammerSAMParams
|
||||
MANTIS.JammerSAMParams = {
|
||||
-- Standard SamData (canonical entries — aliases below point to these)
|
||||
["Nike"] ={peak=78,mu=55,sigma_L=22,tail_dist=110,band="S", floor=5},
|
||||
["Hawk"] ={peak=30,mu=22,sigma_L=9, tail_dist=55, band="IJ", floor=3},
|
||||
["SA-2"] ={peak=75,mu=40,sigma_L=16,tail_dist=90, band="S", floor=5},
|
||||
["SA-3"] ={peak=45,mu=22,sigma_L=10,tail_dist=52, band="IJ", floor=5},
|
||||
["SA-5"] ={peak=52,mu=75,sigma_L=26,tail_dist=145,band="S", floor=5},
|
||||
["SA-6"] ={peak=33,mu=18,sigma_L=8, tail_dist=48, band="IJ", floor=3},
|
||||
["SA-8"] ={peak=38,mu=10,sigma_L=4, tail_dist=24, band="IJ", floor=3},
|
||||
["SA-9"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
|
||||
["SA-10"] ={peak=32,mu=50,sigma_L=20,tail_dist=95, band="S", floor=3},
|
||||
["SA-10B"] ={peak=30,mu=52,sigma_L=20,tail_dist=100,band="S", floor=3},
|
||||
["SA-11"] ={peak=52,mu=28,sigma_L=12,tail_dist=58, band="IJ", floor=3},
|
||||
["SA-13"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
|
||||
["SA-15"] ={peak=30,mu=11,sigma_L=5, tail_dist=22, band="IJ", floor=3},
|
||||
["SA-17"] ={peak=24,mu=32,sigma_L=14,tail_dist=68, band="IJ", floor=2},
|
||||
["SA-19"] ={peak=25,mu=8, sigma_L=3.5,tail_dist=16,band="IJ", floor=2},
|
||||
["SA-20A"] ={peak=22,mu=65,sigma_L=24,tail_dist=115,band="S", floor=2},
|
||||
["SA-20B"] ={peak=20,mu=68,sigma_L=24,tail_dist=120,band="S", floor=2},
|
||||
["S-300VM"] ={peak=16,mu=80,sigma_L=30,tail_dist=135,band="S", floor=2}, -- SA-23 canonical
|
||||
["S-300V4"] ={peak=14,mu=85,sigma_L=30,tail_dist=145,band="S", floor=2}, -- SA-23B canonical
|
||||
["S-400"] ={peak=18,mu=80,sigma_L=30,tail_dist=150,band="S", floor=2}, -- SA-21 canonical
|
||||
["NASAMS"] ={peak=25,mu=28,sigma_L=12,tail_dist=52, band="IJ", floor=2},
|
||||
["Patriot"] ={peak=32,mu=62,sigma_L=24,tail_dist=110,band="S", floor=3},
|
||||
["Rapier"] ={peak=12,mu=6, sigma_L=2.5,tail_dist=14,band="IJ", floor=0},
|
||||
["Gepard"] ={peak=18,mu=6, sigma_L=2, tail_dist=18, band="IJ", floor=0},
|
||||
["Roland"] ={peak=35,mu=5, sigma_L=2, tail_dist=12, band="IJ", floor=3},
|
||||
["HQ-7"] ={peak=38,mu=8, sigma_L=3.5,tail_dist=18,band="IJ", floor=3},
|
||||
["HQ-2"] ={peak=70,mu=38,sigma_L=15,tail_dist=80, band="S", floor=5},
|
||||
["C-RAM"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
|
||||
["Avenger"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
|
||||
["Chaparral"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
|
||||
["Linebacker"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
|
||||
["Silkworm"] ={peak=35,mu=20,sigma_L=8, tail_dist=40, band="IJ", floor=3},
|
||||
["Dog Ear"] ={peak=40,mu=10,sigma_L=4, tail_dist=20, band="IJ", floor=5},
|
||||
["Pantsir S1"] ={peak=10,mu=9, sigma_L=4, tail_dist=20, band="IJ", floor=0}, -- SA-22 canonical
|
||||
["Tor M2"] ={peak=28,mu=14,sigma_L=6, tail_dist=30, band="IJ", floor=3},
|
||||
["IRIS-T SLM"] ={peak=18,mu=22,sigma_L=10,tail_dist=50, band="IJ", floor=2},
|
||||
["SON-9"] ={peak=48,mu=14,sigma_L=6, tail_dist=30, band="IJ", floor=5},
|
||||
-- IDF Mod
|
||||
["TAMIR IDFA"] ={peak=19,mu=25,sigma_L=12,tail_dist=52,band="S", floor=2},
|
||||
["STUNNER IDFA"]={peak=16,mu=45,sigma_L=18,tail_dist=80,band="S", floor=2},
|
||||
-- HDS Mod (canonical for keys with no base equivalent)
|
||||
["SA-10C HDS"] ={peak=30,mu=50,sigma_L=20,tail_dist=92, band="S", floor=3},
|
||||
["SA-12 HDS"] ={peak=35,mu=50,sigma_L=18,tail_dist=90, band="S", floor=3},
|
||||
["SAMPT Block 1 HDS"] ={peak=28,mu=45,sigma_L=18,tail_dist=85, band="S", floor=3},
|
||||
["SAMPT Block 1INT HDS"]={peak=26,mu=48,sigma_L=18,tail_dist=88,band="S", floor=3},
|
||||
["SAMPT Block 2 HDS"] ={peak=22,mu=52,sigma_L=20,tail_dist=92, band="S", floor=2},
|
||||
-- SMA Mod (canonical for SMA-only keys)
|
||||
["RBS70 SMA"] ={peak=5, mu=4, sigma_L=2, tail_dist=10,band="OPT",floor=0}, -- canonical for RBS70 family
|
||||
["RBS90 SMA"] ={peak=5, mu=4, sigma_L=2, tail_dist=10,band="OPT",floor=0}, -- canonical for RBS90 family
|
||||
["RBS98M SMA"] ={peak=25,mu=12,sigma_L=5, tail_dist=25,band="IJ", floor=3}, -- canonical
|
||||
["RBS103A SMA"] ={peak=20,mu=55,sigma_L=20,tail_dist=90,band="S", floor=2}, -- canonical
|
||||
["RBS103B SMA"] ={peak=22,mu=45,sigma_L=18,tail_dist=80,band="S", floor=2}, -- canonical
|
||||
["Lvkv9040M SMA"]={peak=15,mu=3, sigma_L=1, tail_dist=8, band="OPT",floor=0}, -- canonical
|
||||
-- CH Mod (canonical for CHM-only keys)
|
||||
["2S38 CHM"] ={peak=8, mu=3, sigma_L=1, tail_dist=8, band="OPT",floor=0},
|
||||
["PGL-625 CHM"] ={peak=12,mu=6, sigma_L=2.5,tail_dist=14,band="OPT",floor=0}, -- Type-08
|
||||
["HQ-17A CHM"] ={peak=30,mu=11,sigma_L=5, tail_dist=24, band="IJ", floor=3},
|
||||
["M903PAC3 CHM"] ={peak=15,mu=58,sigma_L=22,tail_dist=105,band="S", floor=2},
|
||||
["TorM2M CHM"] ={peak=26,mu=16,sigma_L=6, tail_dist=32, band="IJ", floor=3}, -- variant
|
||||
["NASAMS3-AMRAAMER CHM"] ={peak=20,mu=35,sigma_L=14,tail_dist=65, band="IJ", floor=2},
|
||||
["NASAMS3-AIM9X2 CHM"] ={peak=10,mu=15,sigma_L=6, tail_dist=30, band="IJ", floor=0},
|
||||
["PGZ-09 CHM"] ={peak=22,mu=5, sigma_L=2, tail_dist=14, band="IJ", floor=0}, -- Type-09
|
||||
["PGZ-95 CHM"] ={peak=15,mu=4, sigma_L=2, tail_dist=10, band="OPT",floor=0},
|
||||
["S350-9M100 CHM"] ={peak=18,mu=35,sigma_L=15,tail_dist=78, band="IJ", floor=2}, -- SA-28 canonical
|
||||
["HQ-22 CHM"] ={peak=20,mu=70,sigma_L=26,tail_dist=120,band="S", floor=2},
|
||||
["IRIS-T SLM CHM"] ={peak=18,mu=22,sigma_L=10,tail_dist=50, band="IJ", floor=2}, -- will be aliased
|
||||
["Skynex CHM"] ={peak=12,mu=4, sigma_L=2, tail_dist=10, band="OPT",floor=0},
|
||||
["Skyshield CHM"] ={peak=12,mu=4, sigma_L=2, tail_dist=10, band="OPT",floor=0},
|
||||
["BukM3-9M317M CHM"] ={peak=22,mu=38,sigma_L=16,tail_dist=75, band="IJ", floor=2}, -- SA-27 canonical
|
||||
["SkySabre CHM"] ={peak=18,mu=18,sigma_L=7, tail_dist=40, band="IJ", floor=2},
|
||||
["Stormer CHM"] ={peak=10,mu=5, sigma_L=2, tail_dist=12, band="OPT",floor=0},
|
||||
["THAAD CHM"] ={peak=10,mu=90,sigma_L=38,tail_dist=140,band="IJ", floor=0},
|
||||
["WieselOzelot CHM"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
|
||||
["USInfantryFIM92K CHM"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
|
||||
}
|
||||
|
||||
-- Alias entries: point alias keys to the canonical entry's table.
|
||||
-- Modifying any canonical entry above automatically updates all its aliases.
|
||||
-- This prevents parameter drift when updating values.
|
||||
do
|
||||
local p = MANTIS.JammerSAMParams
|
||||
-- NATO designation aliases
|
||||
p["SA-21"] = p["S-400"] -- SA-21 Growler
|
||||
p["SA-22"] = p["Pantsir S1"] -- SA-22 Greyhound
|
||||
p["SA-23"] = p["S-300VM"] -- SA-23 Gladiator
|
||||
p["SA-23B"] = p["S-300V4"] -- SA-23B Antey-2500 (improved)
|
||||
p["SA-27"] = p["BukM3-9M317M CHM"] -- SA-27 Gollum (Buk-M3)
|
||||
p["SA-28"] = p["S350-9M100 CHM"] -- SA-28 Vityaz (S-350)
|
||||
-- Buk-M3 variants (same radar)
|
||||
p["BukM3-9M317MA CHM"] = p["BukM3-9M317M CHM"]
|
||||
-- S-350 variants (same radar)
|
||||
p["S350-9M96D CHM"] = p["S350-9M100 CHM"]
|
||||
-- Pantsir variants
|
||||
p["PantsirS1 CHM"] = p["Pantsir S1"]
|
||||
p["PantsirS2 CHM"] = p["Pantsir S1"]
|
||||
-- Tor-M2 variants
|
||||
p["TorM2 CHM"] = p["Tor M2"]
|
||||
p["TorM2K CHM"] = p["Tor M2"]
|
||||
-- Patriot PAC-2 (same MPQ-65 radar)
|
||||
p["M903PAC2 CHM"] = p["Patriot"]
|
||||
p["M903PAC2KAT1 CHM"] = p["Patriot"]
|
||||
-- IRIS-T (base and CHM are same system)
|
||||
p["IRIS-T SLM CHM"] = p["IRIS-T SLM"]
|
||||
-- HDS variants (same radar as base entry)
|
||||
p["SA-2 HDS"] = p["SA-2"]
|
||||
p["SA-3 HDS"] = p["SA-3"]
|
||||
p["SA-10B HDS"] = p["SA-10B"]
|
||||
p["SA-17 HDS"] = p["SA-17"]
|
||||
p["SA-23 HDS"] = p["S-300VM"]
|
||||
p["HQ-2 HDS"] = p["HQ-2"]
|
||||
-- Roland / FlaRakRad (same system)
|
||||
p["FlaRakRad CHM"] = p["Roland"]
|
||||
-- Phalanx-class CIWS (Ka-band, essentially unjammable)
|
||||
p["C-RAM CHM"] = p["C-RAM"]
|
||||
p["LD-3000 CHM"] = p["C-RAM"]
|
||||
p["LD-3000M CHM"] = p["C-RAM"]
|
||||
-- SMA / CHM Swedish duplicates (CH mod re-implements SMA assets)
|
||||
p["RBS70 CHM"] = p["RBS70 SMA"]
|
||||
p["RBS70M SMA"] = p["RBS70 SMA"]
|
||||
p["RBS70M CHM"] = p["RBS70 SMA"]
|
||||
p["RBS90 CHM"] = p["RBS90 SMA"]
|
||||
p["RBS90M SMA"] = p["RBS90 SMA"]
|
||||
p["RBS90M CHM"] = p["RBS90 SMA"]
|
||||
p["RBS98M CHM"] = p["RBS98M SMA"]
|
||||
p["RBS103A CHM"] = p["RBS103A SMA"]
|
||||
p["RBS103AM SMA"] = p["RBS103A SMA"]
|
||||
p["RBS103AM CHM"] = p["RBS103A SMA"]
|
||||
p["RBS103B CHM"] = p["RBS103B SMA"]
|
||||
p["RBS103BM SMA"] = p["RBS103B SMA"]
|
||||
p["RBS103BM CHM"] = p["RBS103B SMA"]
|
||||
p["Lvkv9040M CHM"]= p["Lvkv9040M SMA"]
|
||||
-- IR MANPADS (all near-zero, different systems but share params)
|
||||
p["LAV-AD CHM"] = p["SA-9"]
|
||||
-- NOTE: SA-13, Avenger, Chaparral, Linebacker kept separate (different real systems)
|
||||
end
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- MANTIS System
|
||||
-----------------------------------------------------------------------
|
||||
@@ -908,7 +1087,8 @@ do
|
||||
|
||||
local function SwitchSAMOn(Name,Group)
|
||||
local suppressed = self.SuppressedGroups[Name] or false
|
||||
if not suppressed and self.SamStateTracker[Name] == "GREEN" then
|
||||
local jammed = self._jammerEnabled and self._jammedSAMs and self._jammedSAMs[Name] or false
|
||||
if not suppressed and not jammed and self.SamStateTracker[Name] == "GREEN" then
|
||||
self.SamStateTracker[Name] = "RED"
|
||||
if self.UseEmOnOff then
|
||||
-- DONE: add emissions on/off
|
||||
@@ -2132,6 +2312,18 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression REQUEST | target=%s | weapon=%s | tti=%s | delay=%s",tostring(targetName),
|
||||
tostring(weaponName),tostring(tti),tostring(delay)))
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- JAMMER CHECK: if SAM is currently jammed, deny SEAD suppression.
|
||||
-- The jammer is already keeping the SAM off. SEAD suppression would
|
||||
-- cause SuppressionStop to briefly re-enable the radar and relocate
|
||||
-- the SAM (potentially out of jammer range). Let the jammer handle it.
|
||||
----------------------------------------------------------------
|
||||
if self._jammerEnabled and self._jammedSAMs and self._jammedSAMs[targetName] then
|
||||
self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> DENIED (JAMMED %.0f%%) | target=%s",
|
||||
self._jammedSAMs[targetName] * 100, tostring(targetName)))
|
||||
return false
|
||||
end
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- LOOK UP ARM CAPACITY FOR THIS SAM
|
||||
----------------------------------------------------------------
|
||||
@@ -2694,6 +2886,664 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea
|
||||
return self
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- MANTIS Jammer Extension v2.0.0 - Functions
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
--- Add client jammer aircraft support. Call BEFORE :Start().
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Set#SET_GROUP clientSet SET_GROUP of client jammer aircraft, or a prefix string
|
||||
-- @param #string defaultLoadout Default loadout key (default "1xALQ99_2xALQ249")
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:AddJammer(clientSet, defaultLoadout)
|
||||
self:T(self.lid .. "AddJammer")
|
||||
self._jammerEnabled = true
|
||||
-- Allow string prefix shorthand
|
||||
if type(clientSet) == "string" then
|
||||
clientSet = SET_GROUP:New():FilterPrefixes(clientSet):FilterActive(true):FilterStart()
|
||||
end
|
||||
self._jammerClientSet = clientSet
|
||||
self._jammerDefaultLoadout = defaultLoadout or "1xALQ99_2xALQ249"
|
||||
self._jammerAircraft = self._jammerAircraft or {}
|
||||
self._jammerAISets = self._jammerAISets or {}
|
||||
self._jammerSnapshot = self._jammerSnapshot or {}
|
||||
self._jammedSAMs = self._jammedSAMs or {}
|
||||
self._jammerMenusBuilt = self._jammerMenusBuilt or {}
|
||||
if not self._jammerHasTransitions then
|
||||
self:AddTransition("*", "JammerSuppression", "*")
|
||||
self:AddTransition("*", "JammerActivated", "*")
|
||||
self:AddTransition("*", "JammerDeactivated", "*")
|
||||
self._jammerHasTransitions = true
|
||||
end
|
||||
-- Start fast client menu scheduler (5s interval) so menus appear quickly
|
||||
-- instead of waiting for the next MANTIS Status cycle (~30s)
|
||||
if not self._jammerMenuScheduler then
|
||||
self._jammerMenuScheduler = SCHEDULER:New(nil, function()
|
||||
if not self._jammerEnabled then return end
|
||||
if self._jammerClientSet then
|
||||
self:_IterateJammerSet(self._jammerClientSet, function(unit, group)
|
||||
local unitName = unit:GetName()
|
||||
if not self._jammerAircraft[unitName] then
|
||||
self._jammerAircraft[unitName] = {
|
||||
loadout = self._jammerDefaultLoadout,
|
||||
active = false,
|
||||
isClient = unit:IsPlayer(),
|
||||
hasLoadout = false,
|
||||
}
|
||||
elseif not self._jammerAircraft[unitName].isClient and unit:IsPlayer() then
|
||||
self._jammerAircraft[unitName].isClient = true
|
||||
end
|
||||
if self._jammerAircraft[unitName].isClient and not self._jammerMenusBuilt[unitName] then
|
||||
self:_SetupJammerMenu(unit, group)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end, {}, 2, 5) -- start in 2s, repeat every 5s
|
||||
end
|
||||
self:I(string.format("%sJammer configured | default=%s", self.lid, self._jammerDefaultLoadout))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add AI jammer aircraft. Call BEFORE :Start(). Can be called multiple times.
|
||||
-- AI jammers start OFF; use SetJammerActive(unitName, true) to activate via script.
|
||||
-- @param #MANTIS self
|
||||
-- @param Core.Set#SET_GROUP aiSet SET_GROUP or prefix string for AI jammer aircraft
|
||||
-- @param #string loadout Loadout key for these AI aircraft (default "1xALQ99_2xALQ249")
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:AddJammerAI(aiSet, loadout)
|
||||
self:T(self.lid .. "AddJammerAI")
|
||||
self._jammerEnabled = true
|
||||
self._jammerAircraft = self._jammerAircraft or {}
|
||||
self._jammerAISets = self._jammerAISets or {}
|
||||
self._jammerSnapshot = self._jammerSnapshot or {}
|
||||
self._jammedSAMs = self._jammedSAMs or {}
|
||||
self._jammerMenusBuilt = self._jammerMenusBuilt or {}
|
||||
local actualSet = aiSet
|
||||
if type(aiSet) == "string" then
|
||||
actualSet = SET_GROUP:New():FilterPrefixes(aiSet):FilterActive(true):FilterStart()
|
||||
end
|
||||
table.insert(self._jammerAISets, { set = actualSet, loadout = loadout or "1xALQ99_2xALQ249" })
|
||||
if not self._jammerHasTransitions then
|
||||
self:AddTransition("*", "JammerSuppression", "*")
|
||||
self:AddTransition("*", "JammerActivated", "*")
|
||||
self:AddTransition("*", "JammerDeactivated", "*")
|
||||
self._jammerHasTransitions = true
|
||||
end
|
||||
self:I(string.format("%sJammer AI configured | loadout=%s", self.lid, loadout or "1xALQ99_2xALQ249"))
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Core jamming probability curve (v9 / v8.1 engine).
|
||||
-- Features: decoupled bt_mod/range_mod, tapering residual floor, ±10% jitter.
|
||||
-- @param #MANTIS self
|
||||
-- @param #number d Distance in nautical miles
|
||||
-- @param #table params {peak, mu, sigma_L, tail_dist, band, floor}
|
||||
-- @param #string loadoutKey Key into JammerLoadouts
|
||||
-- @return #number Jamming probability as fraction (0.0-0.95)
|
||||
function MANTIS:_JamGaussianExp(d, params, loadoutKey)
|
||||
if d < 0 or d > 200 then return 0 end
|
||||
if not params then return 0 end
|
||||
local cfg = self.JammerLoadouts[loadoutKey]
|
||||
if not cfg then return 0 end
|
||||
local peak, mu, sigma_L, tail_dist, band = params.peak, params.mu, params.sigma_L, params.tail_dist, params.band
|
||||
local floor = params.floor or 0
|
||||
local bm
|
||||
if band == "LOW" then bm = cfg.mult_LOW
|
||||
elseif band == "S" then bm = cfg.mult_S
|
||||
elseif band == "IJ" then bm = cfg.mult_IJ
|
||||
else bm = cfg.mult_OPT end
|
||||
-- Decoupled window modifiers
|
||||
local eff_sigma_L = sigma_L / cfg.bt_mod
|
||||
local eff_tail_dist = tail_dist * cfg.range_mod
|
||||
local eff_peak = math.min(95, peak * bm)
|
||||
local raw
|
||||
if d < mu then
|
||||
raw = eff_peak * math.exp(-0.5 * ((d - mu) / eff_sigma_L) ^ 2)
|
||||
else
|
||||
local lambda = math.log(100.0) / eff_tail_dist
|
||||
raw = eff_peak * math.exp(-lambda * (d - mu))
|
||||
end
|
||||
-- Tapering residual floor (v9): floor full inside engagement band (d <= mu),
|
||||
-- attenuates with R^2-like falloff past mu over 2.5x the main decay distance.
|
||||
-- Prevents artificial hard cliff at 200nm cap; matches real radar/jammer physics.
|
||||
local eff_floor = floor * bm
|
||||
if eff_floor > 0 and d > mu then
|
||||
local floorLambda = math.log(100.0) / (eff_tail_dist * 2.5)
|
||||
eff_floor = eff_floor * math.exp(-floorLambda * (d - mu))
|
||||
end
|
||||
raw = math.max(eff_floor, raw)
|
||||
-- Stochastic jitter ±10% applied AFTER floor (v9), so residual also varies.
|
||||
-- Prevents gameable flat long-range value. Reflects S/N, RCS, atmospheric fluctuation.
|
||||
local jitter = 1.0 + (math.random() * 2 - 1) * (self.JammerJitterPercent or 0.10)
|
||||
raw = raw * jitter
|
||||
return math.max(0, math.min(95, raw)) / 100.0
|
||||
end
|
||||
|
||||
--- [Internal] Build cached sorted key lists for the resolver.
|
||||
-- Called once on first use. Avoids re-scanning/re-sorting JammerSAMParams every cycle.
|
||||
-- @param #MANTIS self
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:_BuildJammerKeyCache()
|
||||
self._jammerSortedHDS = {}
|
||||
self._jammerSortedSMA = {}
|
||||
self._jammerSortedCHM = {}
|
||||
self._jammerSortedBase = {}
|
||||
for key, _ in pairs(self.JammerSAMParams) do
|
||||
if string.find(key, "HDS", 1, true) then table.insert(self._jammerSortedHDS, key)
|
||||
elseif string.find(key, "SMA", 1, true) then table.insert(self._jammerSortedSMA, key)
|
||||
elseif string.find(key, "CHM", 1, true) then table.insert(self._jammerSortedCHM, key)
|
||||
else table.insert(self._jammerSortedBase, key) end
|
||||
end
|
||||
local byLenDesc = function(a, b) return #a > #b end
|
||||
table.sort(self._jammerSortedHDS, byLenDesc)
|
||||
table.sort(self._jammerSortedSMA, byLenDesc)
|
||||
table.sort(self._jammerSortedCHM, byLenDesc)
|
||||
table.sort(self._jammerSortedBase, byLenDesc)
|
||||
self._jammerResolverCache = {}
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Resolve a SAM group name to its jammer parameters.
|
||||
-- Uses cached sorted key lists and per-name memoization for performance.
|
||||
-- Substring matching with digit-boundary protection prevents false matches
|
||||
-- (e.g. "SA-22 Pantsir" no longer matches "SA-2").
|
||||
-- @param #MANTIS self
|
||||
-- @param #string grpname The SAM group name
|
||||
-- @return #table params or nil
|
||||
function MANTIS:_ResolveJammerParams(grpname)
|
||||
if not grpname then return nil end
|
||||
-- Lazy-build cache on first use
|
||||
if not self._jammerSortedBase then self:_BuildJammerKeyCache() end
|
||||
-- Memoization: same group name resolves to same params (params don't change at runtime)
|
||||
local cached = self._jammerResolverCache[grpname]
|
||||
if cached ~= nil then
|
||||
if cached == false then return nil end -- false sentinel = "no match"
|
||||
return cached
|
||||
end
|
||||
-- Helper: check key match with digit-boundary protection
|
||||
local function safeMatch(name, key)
|
||||
local startPos, endPos = string.find(name, key, 1, true)
|
||||
if not startPos then return false end
|
||||
local nextChar = string.sub(name, endPos + 1, endPos + 1)
|
||||
if nextChar == "" then return true end
|
||||
if string.match(nextChar, "%d") then return false end
|
||||
return true
|
||||
end
|
||||
-- Detect mod type and select pre-sorted key list
|
||||
local keyList
|
||||
if string.find(grpname, "HDS", 1, true) then keyList = self._jammerSortedHDS
|
||||
elseif string.find(grpname, "SMA", 1, true) then keyList = self._jammerSortedSMA
|
||||
elseif string.find(grpname, "CHM", 1, true) then keyList = self._jammerSortedCHM end
|
||||
-- First pass: mod-specific keys
|
||||
if keyList then
|
||||
for i = 1, #keyList do
|
||||
if safeMatch(grpname, keyList[i]) then
|
||||
local params = self.JammerSAMParams[keyList[i]]
|
||||
self._jammerResolverCache[grpname] = params
|
||||
return params
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Second pass: base keys
|
||||
local baseKeys = self._jammerSortedBase
|
||||
for i = 1, #baseKeys do
|
||||
if safeMatch(grpname, baseKeys[i]) then
|
||||
local params = self.JammerSAMParams[baseKeys[i]]
|
||||
self._jammerResolverCache[grpname] = params
|
||||
return params
|
||||
end
|
||||
end
|
||||
self._jammerResolverCache[grpname] = false -- cache "no match" too
|
||||
return nil
|
||||
end
|
||||
|
||||
--- [Internal] Count table entries
|
||||
function MANTIS:_CountTable(t)
|
||||
local c = 0
|
||||
for _ in pairs(t) do c = c + 1 end
|
||||
return c
|
||||
end
|
||||
|
||||
--- [Internal] Iterate any supported SET type, calling fn(unit, group) for each alive unit.
|
||||
-- Supports SET_GROUP (via ForEachGroupAlive) and SET_CLIENT/SET_PLAYER (via ForEachClient).
|
||||
-- Note: callback receives ALL alive units — caller must check InAir() for snapshot logic.
|
||||
-- Menu setup happens for ground units too so clients see the menu before takeoff.
|
||||
-- @param #MANTIS self
|
||||
-- @param set The SET object (SET_GROUP, SET_CLIENT, or SET_PLAYER)
|
||||
-- @param #function fn Callback receiving (unit, group)
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:_IterateJammerSet(set, fn)
|
||||
if not set then return self end
|
||||
if type(set.ForEachClient) == "function" then
|
||||
set:ForEachClient(function(client)
|
||||
if not client then return end
|
||||
local unit = client:GetClientGroupUnit()
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
local group = unit:GetGroup()
|
||||
if not group then return end
|
||||
fn(unit, group)
|
||||
end)
|
||||
elseif type(set.ForEachGroupAlive) == "function" then
|
||||
set:ForEachGroupAlive(function(group)
|
||||
if not group then return end
|
||||
local units = group:GetUnits()
|
||||
if not units then return end
|
||||
for _, unit in pairs(units) do
|
||||
if unit and unit:IsAlive() then
|
||||
fn(unit, group)
|
||||
end
|
||||
end
|
||||
end)
|
||||
else
|
||||
self:E(self.lid .. "ERROR: jammer set is not SET_GROUP, SET_CLIENT, or SET_PLAYER (no ForEachGroupAlive or ForEachClient method).")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Update jammer aircraft states and build snapshot.
|
||||
-- @param #MANTIS self
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:_UpdateJammers()
|
||||
if not self._jammerEnabled then return self end
|
||||
self._jammerSnapshot = {}
|
||||
-- Process client aircraft
|
||||
if self._jammerClientSet then
|
||||
self:_IterateJammerSet(self._jammerClientSet, function(unit, group)
|
||||
local unitName = unit:GetName()
|
||||
if not self._jammerAircraft[unitName] then
|
||||
self._jammerAircraft[unitName] = {
|
||||
loadout = self._jammerDefaultLoadout,
|
||||
active = false,
|
||||
isClient = unit:IsPlayer(),
|
||||
hasLoadout = false,
|
||||
}
|
||||
else
|
||||
-- Re-check IsPlayer in case a human took the slot after first detection
|
||||
if not self._jammerAircraft[unitName].isClient and unit:IsPlayer() then
|
||||
self._jammerAircraft[unitName].isClient = true
|
||||
end
|
||||
end
|
||||
-- Setup menu for clients (works on ground or airborne)
|
||||
if self._jammerAircraft[unitName].isClient and not self._jammerMenusBuilt[unitName] then
|
||||
self:_SetupJammerMenu(unit, group)
|
||||
end
|
||||
-- Snapshot only includes airborne armed jammers
|
||||
local state = self._jammerAircraft[unitName]
|
||||
if state.active and unit:InAir() then
|
||||
local coord = unit:GetCoordinate()
|
||||
if coord then
|
||||
table.insert(self._jammerSnapshot, { coord = coord, loadout = state.loadout, name = unitName })
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
-- Process AI aircraft
|
||||
for _, aiEntry in ipairs(self._jammerAISets or {}) do
|
||||
self:_IterateJammerSet(aiEntry.set, function(unit, group)
|
||||
local unitName = unit:GetName()
|
||||
if not self._jammerAircraft[unitName] then
|
||||
self._jammerAircraft[unitName] = {
|
||||
loadout = aiEntry.loadout,
|
||||
active = false,
|
||||
isClient = false,
|
||||
hasLoadout = true,
|
||||
}
|
||||
end
|
||||
-- Snapshot only includes airborne armed jammers
|
||||
local state = self._jammerAircraft[unitName]
|
||||
if state.active and unit:InAir() then
|
||||
local coord = unit:GetCoordinate()
|
||||
if coord then
|
||||
table.insert(self._jammerSnapshot, { coord = coord, loadout = state.loadout, name = unitName })
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
-- Clean up dead units only (keep ground-bound clients so menus persist)
|
||||
-- Respawn detection: when client respawns, slot may briefly disappear then reappear
|
||||
local toRemove = {}
|
||||
for unitName, state in pairs(self._jammerAircraft) do
|
||||
local unit = UNIT:FindByName(unitName)
|
||||
if not unit or not unit:IsAlive() then
|
||||
table.insert(toRemove, unitName)
|
||||
end
|
||||
end
|
||||
for _, unitName in ipairs(toRemove) do
|
||||
if self._jammerAircraft[unitName] and self._jammerAircraft[unitName].active then
|
||||
self:__JammerDeactivated(1, unitName)
|
||||
end
|
||||
self._jammerAircraft[unitName] = nil
|
||||
self._jammerMenusBuilt[unitName] = nil
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Compute which SAMs are jammed this cycle.
|
||||
-- @param #MANTIS self
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:_ComputeJammedSAMs()
|
||||
self._jammedSAMs = {}
|
||||
if not self._jammerEnabled then return self end
|
||||
if #self._jammerSnapshot == 0 then return self end
|
||||
local M_TO_NM = 1.0 / 1852.0
|
||||
local allSAMs = {}
|
||||
local tables = self.automode
|
||||
and { self.SAM_Table_Long, self.SAM_Table_Medium, self.SAM_Table_Short, self.SAM_Table_PointDef }
|
||||
or { self.SAM_Table }
|
||||
for _, samTable in ipairs(tables) do
|
||||
for _, _data in pairs(samTable) do
|
||||
if not allSAMs[_data[1]] then allSAMs[_data[1]] = _data[2] end
|
||||
end
|
||||
end
|
||||
for samName, samCoord in pairs(allSAMs) do
|
||||
local params = self:_ResolveJammerParams(samName)
|
||||
if params then
|
||||
local survival = 1.0
|
||||
for _, jammer in ipairs(self._jammerSnapshot) do
|
||||
local distNM = samCoord:Get2DDistance(jammer.coord) * M_TO_NM
|
||||
local pJam = self:_JamGaussianExp(distNM, params, jammer.loadout)
|
||||
if pJam > 0 then survival = survival * (1.0 - pJam) end
|
||||
end
|
||||
local combinedProb = 1.0 - survival
|
||||
if combinedProb > 0 and math.random() < combinedProb then
|
||||
self._jammedSAMs[samName] = combinedProb
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Cycle debug logging (mirrors JammerDebug output, gated on MANTIS debug/verbose flags)
|
||||
if self.debug or self.verbose then
|
||||
local activeCount = #(self._jammerSnapshot or {})
|
||||
local jamCount = self:_CountTable(self._jammedSAMs)
|
||||
-- Only log if there's something interesting to report
|
||||
if activeCount > 0 or jamCount > 0 then
|
||||
local lines = {}
|
||||
table.insert(lines, string.format("%sJammer cycle: %d active aircraft, %d SAMs jammed",
|
||||
self.lid, activeCount, jamCount))
|
||||
-- List active jammers
|
||||
for _, jammer in ipairs(self._jammerSnapshot) do
|
||||
local cfg = self.JammerLoadouts[jammer.loadout]
|
||||
table.insert(lines, string.format(" ACTIVE: %s | loadout=%s",
|
||||
jammer.name, cfg and cfg.name or jammer.loadout))
|
||||
end
|
||||
-- List jammed SAMs
|
||||
for samName, prob in pairs(self._jammedSAMs) do
|
||||
table.insert(lines, string.format(" JAMMED: %s @ %.0f%%", samName, prob * 100))
|
||||
end
|
||||
local text = table.concat(lines, "\n")
|
||||
self:I(text)
|
||||
if self.debug then
|
||||
MESSAGE:New(text, 10, "MANTIS"):ToAll()
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Set up tiered F10 jammer menu for a player unit.
|
||||
-- @param #MANTIS self
|
||||
-- @param Wrapper.Unit#UNIT unit
|
||||
-- @param Wrapper.Group#GROUP group
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:_SetupJammerMenu(unit, group)
|
||||
local unitName = unit:GetName()
|
||||
local groupName = group:GetName()
|
||||
if self._jammerMenusBuilt[unitName] then return self end
|
||||
self._jammerMenusBuilt[unitName] = true
|
||||
-- If this group already has a menu tree (e.g. respawn), remove it first
|
||||
if self._jammerGroupMenus and self._jammerGroupMenus[groupName] then
|
||||
self._jammerGroupMenus[groupName]:Remove()
|
||||
self._jammerGroupMenus[groupName] = nil
|
||||
end
|
||||
self._jammerGroupMenus = self._jammerGroupMenus or {}
|
||||
local rootMenu = MENU_GROUP:New(group, "Jammer Controls")
|
||||
self._jammerGroupMenus[groupName] = rootMenu
|
||||
-- Tiered loadout submenus
|
||||
local alq99Menu = MENU_GROUP:New(group, "ALQ-99 Loadouts", rootMenu)
|
||||
local alq249Menu = MENU_GROUP:New(group, "ALQ-249 Loadouts", rootMenu)
|
||||
local mixedMenu = MENU_GROUP:New(group, "Mixed Loadouts", rootMenu)
|
||||
local tierMenus = { ALQ99 = alq99Menu, ALQ249 = alq249Menu, Mixed = mixedMenu }
|
||||
for tierName, keys in pairs(self.JammerLoadoutTiers) do
|
||||
local parentMenu = tierMenus[tierName]
|
||||
for _, loadoutKey in ipairs(keys) do
|
||||
local cfg = self.JammerLoadouts[loadoutKey]
|
||||
if cfg then
|
||||
MENU_GROUP_COMMAND:New(group, cfg.name, parentMenu,
|
||||
self._JammerMenuSetLoadout, self, unitName, loadoutKey, group, rootMenu)
|
||||
end
|
||||
end
|
||||
end
|
||||
MESSAGE:New("JAMMER ONLINE\nSelect a loadout from the Jammer Controls menu.", 15, "JAMMER"):ToGroup(group)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] F10 callback: Set loadout and show Music toggle
|
||||
function MANTIS:_JammerMenuSetLoadout(unitName, loadoutKey, group, rootMenu)
|
||||
local state = self._jammerAircraft[unitName]
|
||||
if not state then return end
|
||||
state.loadout = loadoutKey
|
||||
state.hasLoadout = true
|
||||
local cfg = self.JammerLoadouts[loadoutKey]
|
||||
MESSAGE:New(string.format("LOADOUT SELECTED: %s\n%s", cfg and cfg.name or loadoutKey, cfg and cfg.description or ""), 12, "JAMMER"):ToGroup(group)
|
||||
-- Add Music toggle if not already present
|
||||
if not state._musicMenuAdded then
|
||||
state._musicMenuAdded = true
|
||||
state._musicOnMenu = nil
|
||||
state._musicOffMenu = nil
|
||||
self:_ShowMusicOn(unitName, group, rootMenu)
|
||||
end
|
||||
end
|
||||
|
||||
--- [Internal] Show "Music On" menu entry (jammer is OFF, click to turn ON)
|
||||
function MANTIS:_ShowMusicOn(unitName, group, rootMenu)
|
||||
local state = self._jammerAircraft[unitName]
|
||||
if not state then return end
|
||||
if state._musicOffMenu then state._musicOffMenu:Remove() state._musicOffMenu = nil end
|
||||
state._musicOnMenu = MENU_GROUP_COMMAND:New(group, "Music On", rootMenu,
|
||||
function()
|
||||
local s = self._jammerAircraft[unitName]
|
||||
if not s then return end
|
||||
s.active = true
|
||||
MESSAGE:New(string.format("JAMMER ACTIVE\n%s", self.JammerLoadouts[s.loadout] and self.JammerLoadouts[s.loadout].name or s.loadout), 10, "JAMMER"):ToGroup(group)
|
||||
self:__JammerActivated(1, unitName, s.loadout)
|
||||
self:_ShowMusicOff(unitName, group, rootMenu)
|
||||
end)
|
||||
end
|
||||
|
||||
--- [Internal] Show "Music Off" menu entry (jammer is ON, click to turn OFF)
|
||||
function MANTIS:_ShowMusicOff(unitName, group, rootMenu)
|
||||
local state = self._jammerAircraft[unitName]
|
||||
if not state then return end
|
||||
if state._musicOnMenu then state._musicOnMenu:Remove() state._musicOnMenu = nil end
|
||||
state._musicOffMenu = MENU_GROUP_COMMAND:New(group, "Music Off", rootMenu,
|
||||
function()
|
||||
local s = self._jammerAircraft[unitName]
|
||||
if not s then return end
|
||||
s.active = false
|
||||
MESSAGE:New("JAMMER SAFE", 10, "JAMMER"):ToGroup(group)
|
||||
self:__JammerDeactivated(1, unitName)
|
||||
self:_ShowMusicOn(unitName, group, rootMenu)
|
||||
end)
|
||||
end
|
||||
|
||||
--- On After "JammerSuppression" event
|
||||
function MANTIS:onafterJammerSuppression(From, Event, To, Group, Name, Probability)
|
||||
self:T({From, Event, To, Name, Probability})
|
||||
return self
|
||||
end
|
||||
|
||||
--- On After "JammerActivated" event
|
||||
function MANTIS:onafterJammerActivated(From, Event, To, UnitName, Loadout)
|
||||
self:T({From, Event, To, UnitName, Loadout})
|
||||
return self
|
||||
end
|
||||
|
||||
--- On After "JammerDeactivated" event
|
||||
function MANTIS:onafterJammerDeactivated(From, Event, To, UnitName)
|
||||
self:T({From, Event, To, UnitName})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get table of currently jammed SAM group names.
|
||||
-- @param #MANTIS self
|
||||
-- @return #table {samName = probability}
|
||||
function MANTIS:GetJammedSAMs()
|
||||
return self._jammedSAMs or {}
|
||||
end
|
||||
|
||||
--- Get number of active jammer aircraft.
|
||||
-- @param #MANTIS self
|
||||
-- @return #number
|
||||
function MANTIS:GetActiveJammerCount()
|
||||
return self._jammerSnapshot and #self._jammerSnapshot or 0
|
||||
end
|
||||
|
||||
--- Print a debug report of the jammer system to dcs.log and screen.
|
||||
-- Lists registered aircraft, their states, snapshot count, and currently
|
||||
-- jammed SAMs with their probabilities. Use this to diagnose menu issues,
|
||||
-- missing aircraft, or unexpected SAM matching.
|
||||
-- @param #MANTIS self
|
||||
-- @param #boolean toScreen If true, also display report on screen (default false)
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:JammerDebug(toScreen)
|
||||
local lines = {}
|
||||
table.insert(lines, "=== MANTIS JAMMER DEBUG REPORT ===")
|
||||
table.insert(lines, string.format("Enabled: %s | Default loadout: %s",
|
||||
tostring(self._jammerEnabled), tostring(self._jammerDefaultLoadout)))
|
||||
table.insert(lines, string.format("Has client set: %s | AI sets: %d",
|
||||
tostring(self._jammerClientSet ~= nil), #(self._jammerAISets or {})))
|
||||
-- Aircraft state
|
||||
local count = 0
|
||||
for unitName, state in pairs(self._jammerAircraft or {}) do
|
||||
count = count + 1
|
||||
table.insert(lines, string.format(" [%s] active=%s isClient=%s loadout=%s hasLoadout=%s",
|
||||
unitName, tostring(state.active), tostring(state.isClient),
|
||||
tostring(state.loadout), tostring(state.hasLoadout)))
|
||||
end
|
||||
table.insert(lines, string.format("Total tracked aircraft: %d", count))
|
||||
table.insert(lines, string.format("Active jammer snapshot: %d aircraft airborne+armed", #(self._jammerSnapshot or {})))
|
||||
-- Jammed SAMs
|
||||
local jcount = 0
|
||||
for samName, prob in pairs(self._jammedSAMs or {}) do
|
||||
jcount = jcount + 1
|
||||
table.insert(lines, string.format(" JAMMED: %s @ %.0f%%", samName, prob * 100))
|
||||
end
|
||||
table.insert(lines, string.format("Currently jammed SAMs: %d", jcount))
|
||||
table.insert(lines, "=== END REPORT ===")
|
||||
local report = table.concat(lines, "\n")
|
||||
self:I(report)
|
||||
if toScreen then
|
||||
MESSAGE:New(report, 30, "JAMMER DEBUG"):ToAll()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Test the SAM resolver against a hypothetical group name.
|
||||
-- Useful for verifying naming convention before mission start.
|
||||
-- @param #MANTIS self
|
||||
-- @param #string testName The hypothetical SAM group name to test
|
||||
-- @return #string Matched JammerSAMParams key, or "NO MATCH"
|
||||
function MANTIS:JammerTestResolver(testName)
|
||||
local params = self:_ResolveJammerParams(testName)
|
||||
if not params then
|
||||
self:I(string.format("[JammerTestResolver] '%s' -> NO MATCH", testName))
|
||||
return "NO MATCH"
|
||||
end
|
||||
-- Find which key matched
|
||||
for key, p in pairs(self.JammerSAMParams) do
|
||||
if p == params then
|
||||
self:I(string.format("[JammerTestResolver] '%s' -> '%s' {peak=%d, mu=%d, band=%s, floor=%d}",
|
||||
testName, key, params.peak, params.mu, params.band, params.floor or 0))
|
||||
return key
|
||||
end
|
||||
end
|
||||
return "MATCHED (key unknown)"
|
||||
end
|
||||
|
||||
--- Manually set a jammer aircraft loadout (AI scripted control).
|
||||
-- @param #MANTIS self
|
||||
-- @param #string unitName DCS unit name
|
||||
-- @param #string loadoutKey Key into JammerLoadouts
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetJammerLoadout(unitName, loadoutKey)
|
||||
if self._jammerAircraft and self._jammerAircraft[unitName] then
|
||||
self._jammerAircraft[unitName].loadout = loadoutKey
|
||||
self._jammerAircraft[unitName].hasLoadout = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Manually activate/deactivate a jammer aircraft (AI scripted control).
|
||||
-- @param #MANTIS self
|
||||
-- @param #string unitName DCS unit name
|
||||
-- @param #boolean active true=on, false=off
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetJammerActive(unitName, active)
|
||||
if self._jammerAircraft and self._jammerAircraft[unitName] then
|
||||
local wasActive = self._jammerAircraft[unitName].active
|
||||
self._jammerAircraft[unitName].active = active
|
||||
if active and not wasActive then
|
||||
self:__JammerActivated(1, unitName, self._jammerAircraft[unitName].loadout)
|
||||
elseif not active and wasActive then
|
||||
self:__JammerDeactivated(1, unitName)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- MANTIS Jammer Extension - Hook Overrides
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
-- Guard: only save originals once (prevents infinite recursion if file loaded twice)
|
||||
if not MANTIS._CheckLoopOriginal then
|
||||
MANTIS._CheckLoopOriginal = MANTIS._CheckLoop
|
||||
end
|
||||
if not MANTIS._onbeforeStatusOriginal then
|
||||
MANTIS._onbeforeStatusOriginal = MANTIS.onbeforeStatus
|
||||
end
|
||||
|
||||
--- [Internal] Override: _CheckLoop with jammer suppression.
|
||||
function MANTIS:_CheckLoop(samset, detset, dlink, limit)
|
||||
local r, g, s = self:_CheckLoopOriginal(samset, detset, dlink, limit)
|
||||
if self._jammerEnabled and self._jammedSAMs then
|
||||
for _, _data in pairs(samset) do
|
||||
local name = _data[1]
|
||||
if self._jammedSAMs[name] and self.SamStateTracker[name] == "RED" then
|
||||
local samgroup = GROUP:FindByName(name)
|
||||
if samgroup and samgroup:IsAlive() then
|
||||
if self.UseEmOnOff then
|
||||
samgroup:EnableEmission(false)
|
||||
else
|
||||
samgroup:OptionAlarmStateGreen()
|
||||
end
|
||||
self.SamStateTracker[name] = "GREEN"
|
||||
self:__JammerSuppression(1, samgroup, name, self._jammedSAMs[name])
|
||||
if self.ShoradLink then
|
||||
local Shorad = self.Shorad
|
||||
local shoradradius = self.checkradius
|
||||
local ontime = self.ShoradTime
|
||||
Shorad:WakeUpShorad(name, shoradradius, ontime, nil, true)
|
||||
self:__ShoradActivated(1, name, shoradradius, ontime)
|
||||
end
|
||||
if self.debug or self.verbose then
|
||||
self:T(string.format("%sJAMMED: %s forced GREEN (%.1f%%)", self.lid, name, self._jammedSAMs[name] * 100))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return r, g, s
|
||||
end
|
||||
|
||||
--- [Internal] Override: onbeforeStatus with jammer update.
|
||||
function MANTIS:onbeforeStatus(From, Event, To)
|
||||
if self._jammerEnabled then
|
||||
self:_UpdateJammers()
|
||||
self:_ComputeJammedSAMs()
|
||||
end
|
||||
return self:_onbeforeStatusOriginal(From, Event, To)
|
||||
end
|
||||
|
||||
end
|
||||
-----------------------------------------------------------------------
|
||||
-- MANTIS end
|
||||
|
||||
@@ -795,15 +795,20 @@ do
|
||||
local tgtgrp1 = self.Samset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtcoord1 = tgtgrp1:GetCoordinate()
|
||||
local tgtgrp2 = self.Groupset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtcoord2 = tgtgrp2:GetCoordinate()
|
||||
local dist1 = tgtcoord:Get2DDistance(tgtcoord1)
|
||||
local dist2 = tgtcoord:Get2DDistance(tgtcoord2)
|
||||
|
||||
if dist1 < dist2 then
|
||||
targetunit = tgtgrp1
|
||||
targetcat = Object.Category.UNIT
|
||||
if tgtgrp2 then
|
||||
local tgtcoord2 = tgtgrp2:GetCoordinate()
|
||||
local dist1 = tgtcoord:Get2DDistance(tgtcoord1)
|
||||
local dist2 = tgtcoord:Get2DDistance(tgtcoord2)
|
||||
|
||||
if dist1 < dist2 then
|
||||
targetunit = tgtgrp1
|
||||
targetcat = Object.Category.UNIT
|
||||
else
|
||||
targetunit = tgtgrp2
|
||||
targetcat = Object.Category.UNIT
|
||||
end
|
||||
else
|
||||
targetunit = tgtgrp2
|
||||
targetunit = tgtgrp1
|
||||
targetcat = Object.Category.UNIT
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1485,7 +1485,7 @@ function SUPPRESSION:onafterStop(Controllable, From, Event, To)
|
||||
|
||||
local text=string.format("Stopping SUPPRESSION for group %s", self.Controllable:GetName())
|
||||
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
||||
sefl:T(self.lid..text)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Clear all pending schedules
|
||||
self.CallScheduler:Clear()
|
||||
|
||||
@@ -4097,7 +4097,7 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay,
|
||||
-- Get name of template group.
|
||||
local templategroupname=group:GetName()
|
||||
local unit = group:GetUnit(1)
|
||||
local Descriptors= (unit and unit:IsAlive()) and unit:GetDesc() or {}
|
||||
local Descriptors= (unit and unit:IsAlive()~=nil) and unit:GetDesc() or {}
|
||||
local Category=group:GetCategory()
|
||||
local TypeName=group:GetTypeName() or "none"
|
||||
local SpeedMax=group:GetSpeedMax()
|
||||
@@ -4115,7 +4115,7 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay,
|
||||
for _i,_unit in pairs(group:GetUnits()) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
local Desc=unit:GetDesc()
|
||||
|
||||
|
||||
-- Weight. We sum up all units in the group.
|
||||
local unitweight=forceweight or Desc.massEmpty
|
||||
if unitweight then
|
||||
|
||||
@@ -115,6 +115,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Squadron.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Target.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyGCICAP.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyA2G.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/TARS.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/ShapeBase.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Circle.lua' )
|
||||
|
||||
@@ -2717,17 +2717,27 @@ function AIRBOSS:DeleteRecoveryWindow( Window, Delay )
|
||||
self:ScheduleOnce( Delay, self.DeleteRecoveryWindow, self, Window )
|
||||
else
|
||||
|
||||
for i, _recovery in pairs( self.recoverytimes ) do
|
||||
local recovery = _recovery -- #AIRBOSS.Recovery
|
||||
if not Window then
|
||||
return
|
||||
end
|
||||
|
||||
if Window and Window.ID == recovery.ID then
|
||||
if Window.OPEN then
|
||||
-- Window is currently open.
|
||||
self:RecoveryStop()
|
||||
else
|
||||
table.remove( self.recoverytimes, i )
|
||||
end
|
||||
-- If this window is currently open, stop recovery first. Mark it OVER so the
|
||||
-- recovery time check cannot re-open it before/while we remove it.
|
||||
if Window.OPEN then
|
||||
Window.OPEN = false
|
||||
Window.OVER = true
|
||||
if self:IsRecovering() then
|
||||
self:RecoveryStop()
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove the window from the queue by its unique ID. Iterate over a numerically
|
||||
-- indexed copy of the keys and remove via ipairs-safe reverse loop so that the
|
||||
-- removal does not corrupt traversal (the window may appear once).
|
||||
for i = #self.recoverytimes, 1, -1 do
|
||||
local recovery = self.recoverytimes[i] -- #AIRBOSS.Recovery
|
||||
if recovery and recovery.ID == Window.ID then
|
||||
table.remove( self.recoverytimes, i )
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4078,15 +4088,23 @@ function AIRBOSS:_CheckRecoveryTimes()
|
||||
if self:IsRecovering() then
|
||||
-- Carrier is already recovering.
|
||||
state = "in progress"
|
||||
else
|
||||
-- Start recovery.
|
||||
elseif not recovery.OVER then
|
||||
-- Start recovery. Only if the window has not already been closed/cancelled.
|
||||
-- The OVER guard prevents a window that was stopped manually (e.g. via the
|
||||
-- Skipper "Stop Recovery" menu) from being immediately re-opened on the next
|
||||
-- status tick while its [START,STOP) range is still active.
|
||||
self:RecoveryStart( recovery.CASE, recovery.OFFSET )
|
||||
state = "starting now"
|
||||
recovery.OPEN = true
|
||||
else
|
||||
-- Window was already closed/cancelled within its active time range.
|
||||
state = "cancelled"
|
||||
end
|
||||
|
||||
-- Set current recovery window.
|
||||
currwindow = recovery
|
||||
-- Set current recovery window (unless this window has been cancelled).
|
||||
if not recovery.OVER then
|
||||
currwindow = recovery
|
||||
end
|
||||
|
||||
else -- Stop time HAS passed.
|
||||
|
||||
@@ -4183,13 +4201,16 @@ function AIRBOSS:_CheckRecoveryTimes()
|
||||
-- Time into the wind 1 day or if longer recovery time + the 5 min early.
|
||||
local t = math.max( nextwindow.STOP - nextwindow.START + self.dTturn, 60 * 60 * 24 )
|
||||
|
||||
-- Recovery wind on deck in knots.
|
||||
-- Recovery wind on deck in knots.
|
||||
-- NOTE: Do NOT clamp the desired wind-over-deck (WOD) to the carrier's max hull
|
||||
-- speed here. WOD = carrier speed + headwind, so a WOD target above the hull's
|
||||
-- top speed is achievable whenever there is wind. CarrierTurnIntoWind ->
|
||||
-- GetHeadingIntoWind already converts the WOD target into the required hull
|
||||
-- speed and caps THAT at the carrier's max speed (Vmax) internally. Clamping the
|
||||
-- WOD target itself capped achievable WOD at ~30 kts (the supercarrier's max
|
||||
-- hull speed) even in strong wind, which made high-WOD recovery windows fall
|
||||
-- short of their requested value.
|
||||
local v = UTILS.KnotsToMps( nextwindow.SPEED )
|
||||
|
||||
-- Check that we do not go above max possible speed.
|
||||
local vmax = self.carrier:GetSpeedMax() / 3.6 -- convert to m/s
|
||||
v = math.min( v, vmax )
|
||||
|
||||
-- Route carrier into the wind. Sets self.turnintowind=true
|
||||
self:CarrierTurnIntoWind( t, v, uturn )
|
||||
|
||||
@@ -4361,11 +4382,20 @@ function AIRBOSS:onafterRecoveryStop( From, Event, To )
|
||||
self:CarrierResumeRoute( coord )
|
||||
end
|
||||
|
||||
-- Delete current recovery window if open.
|
||||
if self.recoverywindow and self.recoverywindow.OPEN == true then
|
||||
-- Mark the current recovery window closed and cancelled, then remove it from the
|
||||
-- queue. We do NOT gate this on Window.OPEN: that flag is only set by
|
||||
-- _CheckRecoveryTimes (not by RecoveryStart), and the recovery time check nils and
|
||||
-- rebuilds self.recoverywindow every status tick, so OPEN is not a reliable signal
|
||||
-- here. Setting OVER=true is what actually prevents the window from being re-opened
|
||||
-- on the next tick while its [START,STOP) range is still active.
|
||||
--
|
||||
-- The removal is deferred by one tick (Delay>0) so it does not mutate the
|
||||
-- recoverytimes table while _CheckRecoveryTimes may be iterating over it (the natural
|
||||
-- close path calls RecoveryStop() from inside that loop).
|
||||
if self.recoverywindow then
|
||||
self.recoverywindow.OPEN = false
|
||||
self.recoverywindow.OVER = true
|
||||
self:DeleteRecoveryWindow( self.recoverywindow )
|
||||
self:DeleteRecoveryWindow( self.recoverywindow, 1 )
|
||||
end
|
||||
|
||||
-- Check recovery windows. This sets self.recoverywindow to the next window.
|
||||
@@ -12903,6 +12933,14 @@ function AIRBOSS:_LSOgrade( playerData )
|
||||
-- Circuit edit only take points awary from a 1 wire if there are more than 4 other deviations
|
||||
if playerData.wire == 1 and points >= 3 and N > 4 then
|
||||
points = points -1
|
||||
-- We also need to change the grade based on the new points.
|
||||
if points == 4 then
|
||||
grade = "OK"
|
||||
elseif points == 3 then
|
||||
grade = "(OK)"
|
||||
elseif points == 2 then
|
||||
grade = "--"
|
||||
end
|
||||
end
|
||||
|
||||
env.info("Returning: " .. grade .. " " .. points .. " " .. G)
|
||||
|
||||
@@ -6610,7 +6610,7 @@ function AUFTRAG:GetDCSMissionTask(MissionGroup)
|
||||
local SeadUnitSet = SET_UNIT:New()
|
||||
for _,_unit in pairs (ScanUnitSet.Set) do
|
||||
local unit = _unit -- Wrapper.Unit#UNTI
|
||||
if unit and unit:IsAlive() and unit:HasSEAD() then
|
||||
if unit and unit:IsAlive() and unit.HasSEAD and unit:HasSEAD() then
|
||||
self:T("Adding UNIT for SEAD: "..unit:GetName())
|
||||
local task = CONTROLLABLE.TaskAttackUnit(nil,unit,GroupAttack,AI.Task.WeaponExpend.ALL,1,Direction,self.engageAltitude,2956984318)
|
||||
table.insert(DCStasks, task)
|
||||
|
||||
@@ -504,6 +504,9 @@ CSAR.AircraftType["OH58D"] = 2
|
||||
CSAR.AircraftType["CH-47Fbl1"] = 31
|
||||
CSAR.AircraftType["AH-6J"] = 2
|
||||
CSAR.AircraftType["MH-6J"] = 2
|
||||
CSAR.AircraftType["Ka-50_3"] = 0
|
||||
CSAR.AircraftType["Ka-50"] = 0
|
||||
CSAR.AircraftType["AV8BNA"] = 0
|
||||
|
||||
--- CSAR class version.
|
||||
-- @field #string version
|
||||
@@ -2538,8 +2541,11 @@ function CSAR:_AddMedevacMenuItem()
|
||||
if _unit then
|
||||
--self:T("Unitname ".._unit:GetName().." IsAlive "..tostring(_unit:IsAlive()).." IsPlayer "..tostring(_unit:IsPlayer()))
|
||||
if _unit:IsAlive() and _unit:IsPlayer() then
|
||||
local unitName = _unit:GetName()
|
||||
_UnitList[unitName] = unitName
|
||||
local _maxUnits = self.AircraftType[_unit:GetTypeName()]
|
||||
if _maxUnits == nil or _maxUnits > 0 then
|
||||
local unitName = _unit:GetName()
|
||||
_UnitList[unitName] = unitName
|
||||
end
|
||||
end -- end isAlive
|
||||
end -- end if _unit
|
||||
end -- end for
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@
|
||||
--
|
||||
-------------------------------------------------------------------------
|
||||
-- Date: September 2023
|
||||
-- Last Update: Mar 2026
|
||||
-- Last Update: June 2026
|
||||
-------------------------------------------------------------------------
|
||||
--
|
||||
--- **Ops** - Easy GCI & CAP Manager
|
||||
@@ -135,6 +135,7 @@
|
||||
-- local mywing = EASYGCICAP:New("Blue CAP Operations",AIRBASE.Caucasus.Kutaisi,"blue","Blue EWR")
|
||||
--
|
||||
-- -- Add a CAP patrol point belonging to our airbase, we'll be at 30k ft doing 400 kn, initial direction 90 degrees (East), leg 20NM
|
||||
-- -- NOTE - Skip this function and do not create CAP Points if you want GCI behaviour only.
|
||||
-- mywing:AddPatrolPointCAP(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone 1"):GetCoordinate(),30000,400,90,20)
|
||||
--
|
||||
-- -- Add a Squadron with template "Blue Sq1 M2000c", 20 airframes, skill good, Modex starting with 102 and skin "Vendee Jeanne"
|
||||
@@ -190,9 +191,10 @@
|
||||
-- * @{#EASYGCICAP.SetDefaultResurrection}: Set how many seconds the AirWing stays inoperable after the AirWing STATIC HQ ist destroyed, default 900 secs.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPSpeed}: Set how many knots the CAP flights should do (will be altitude corrected), default 300 kn.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPAlt}: Set at which altitude (ASL) the CAP planes will fly, default 25,000 ft.
|
||||
-- * @{#EASYGCICAP.SetDefaultINTERCEPTAlt}: Set at which altitude (ASL) the Intercept planes will fly, default 25,000 ft.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPDirection}: Set the initial direction from the CAP point the planes will fly in degrees, default is 90°.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPLeg}: Set the length of the CAP leg, default is 15 NM.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 2.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPGrouping}: Set how many planes will be spawned per mission (CAP/GCI), defaults to 2.
|
||||
-- * @{#EASYGCICAP.SetDefaultMissionRange}: Set how many NM the planes can go from the home base, defaults to 100.
|
||||
-- * @{#EASYGCICAP.SetDefaultNumberAlert5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2.
|
||||
-- * @{#EASYGCICAP.SetDefaultEngageRange}: Set max engage range for CAP flights if they detect intruders, defaults to 50.
|
||||
@@ -286,7 +288,7 @@ EASYGCICAP = {
|
||||
|
||||
--- EASYGCICAP class version.
|
||||
-- @field #string version
|
||||
EASYGCICAP.version="0.1.35"
|
||||
EASYGCICAP.version="0.1.38"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
--
|
||||
@@ -587,6 +589,17 @@ function EASYGCICAP:SetDefaultCAPAlt(Altitude)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set default INTERCEPT Altitude in feet
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Altitude (Optional) Altitude defaults to 25000
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetDefaultINTERCEPTAlt(Altitude)
|
||||
self:T(self.lid.."SetDefaultINTERCEPTAlt")
|
||||
self.interceptalt = Altitude or 25000
|
||||
return self
|
||||
end
|
||||
--
|
||||
|
||||
--- Set default CAP lieg initial direction in degrees
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Direction (Optional) Direction defaults to 90 (East)
|
||||
@@ -754,6 +767,15 @@ end
|
||||
function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
self:T(self.lid.."_AddAirwing "..Airbasename)
|
||||
|
||||
-- Gather Some Stats
|
||||
local function counttable(tbl)
|
||||
local count = 0
|
||||
for _,_data in pairs(tbl) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
local CapFormation = self.CapFormation
|
||||
local DespawnAfterLanding = self.DespawnAfterLanding
|
||||
local DespawnAfterHolding = self.DespawnAfterHolding
|
||||
@@ -772,7 +794,11 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
CAP_Wing:SetMarker(false)
|
||||
CAP_Wing:SetAirbase(AIRBASE:FindByName(Airbasename))
|
||||
CAP_Wing:SetRespawnAfterDestroyed()
|
||||
CAP_Wing:SetNumberCAP(self.capgrouping)
|
||||
|
||||
--- #DONE avoid wings with no CAP points starting CAP anyhow; AirWing uses this to start CAP and creates points when there are none.
|
||||
if counttable(self.ManagedCP) >0 then
|
||||
CAP_Wing:SetNumberCAP(self.capgrouping)
|
||||
end
|
||||
CAP_Wing:SetCapCloseRaceTrack(true)
|
||||
|
||||
if self.showpatrolpointmarks then
|
||||
@@ -854,16 +880,18 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
end
|
||||
|
||||
if self.noalert5 > 0 then
|
||||
local alert
|
||||
if self.ClassName == "EASYGCICAP" then
|
||||
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
|
||||
elseif self.ClassName == "EASYA2G" then
|
||||
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.BAI)
|
||||
for i=1,self.noalert5 do
|
||||
local alert
|
||||
if self.ClassName == "EASYGCICAP" then
|
||||
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
|
||||
elseif self.ClassName == "EASYA2G" then
|
||||
alert = AUFTRAG:NewALERT5(AUFTRAG.Type.BAI)
|
||||
end
|
||||
alert:SetRequiredAssets(self.capgrouping)
|
||||
alert:SetRepeat(99)
|
||||
CAP_Wing:AddMission(alert)
|
||||
table.insert(self.ListOfAuftrag,alert)
|
||||
end
|
||||
alert:SetRequiredAssets(self.noalert5)
|
||||
alert:SetRepeat(99)
|
||||
CAP_Wing:AddMission(alert)
|
||||
table.insert(self.ListOfAuftrag,alert)
|
||||
end
|
||||
|
||||
self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename }
|
||||
@@ -1037,7 +1065,7 @@ function EASYGCICAP:_SetCAPPatrolPoints()
|
||||
MESSAGE:New(self.lid.."You are trying to create a CAP point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog()
|
||||
return
|
||||
end
|
||||
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
|
||||
local Wing = self.wings[data.AirbaseName][1] -- Ops.AirWing#AIRWING
|
||||
local Coordinate = data.Coordinate
|
||||
local Altitude = data.Altitude
|
||||
local Speed = data.Speed
|
||||
@@ -1503,6 +1531,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
local overhead = self.overhead
|
||||
local capspeed = self.capspeed + 100
|
||||
local capalt = self.capalt
|
||||
local interalt = self.interceptalt or self.capalt
|
||||
local maxsize = self.maxinterceptsize
|
||||
local repeatsonfailure = self.repeatsonfailure
|
||||
|
||||
@@ -1590,6 +1619,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
:SetRepeatOnFailure(repeats)
|
||||
:SetMissionSpeed(UTILS.KnotsToAltKIAS(capspeed,capalt))
|
||||
:SetMissionAltitude(capalt)
|
||||
:SetEngageAltitude(interalt)
|
||||
|
||||
if nogozoneset:Count() > 0 then
|
||||
InterceptAuftrag:AddConditionSuccess(
|
||||
@@ -1835,9 +1865,10 @@ function EASYGCICAP:onafterRestart(From,Event,To)
|
||||
-- self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename }
|
||||
for _,_wing in pairs(self.wings or {}) do
|
||||
for _,_aw in pairs(_wing) do
|
||||
_wing[1]:Start()
|
||||
_wing[1]:Restart()
|
||||
end
|
||||
end
|
||||
end
|
||||
self:__Status(5)
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1270,6 +1270,15 @@ function INTEL:_UpdateContact(Contact)
|
||||
else
|
||||
Contact.maneuvering = false
|
||||
end
|
||||
local typename = Contact.group:GetTypeName()
|
||||
local base_rcs = INTEL.RCS_Table[typename]
|
||||
|
||||
if not base_rcs then
|
||||
-- Fallback: category default
|
||||
local cat = Contact.group:GetCategory()
|
||||
base_rcs = (cat and INTEL.RCS_CategoryDefault[cat]) or INTEL.RCS_Reference
|
||||
end
|
||||
Contact.rcs = base_rcs
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2581,7 +2581,7 @@ end
|
||||
-- @return #boolean Returns `true` if given cohort can meet all requirements.
|
||||
function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properties, WeaponTypes, TargetVec2, RangeMax, RefuelSystem, CargoWeight, MaxWeight, RangeMin)
|
||||
|
||||
RangeMin = RangeMin or 0
|
||||
RangeMin = RangeMin or -1
|
||||
|
||||
--- Function to check category.
|
||||
local function CheckCategory(_cohort)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -61,24 +61,21 @@
|
||||
--
|
||||
-- # The MSRS Concept
|
||||
--
|
||||
-- This class allows to broadcast sound files or text via Simple Radio Standalone (SRS).
|
||||
-- This class allows to broadcast sound files or text to speech via Simple Radio Standalone (SRS).
|
||||
--
|
||||
-- ## Prerequisites
|
||||
--
|
||||
-- * This script needs SRS version >= 1.9.6
|
||||
-- * You need to de-sanitize os, io and lfs in the missionscripting.lua
|
||||
-- * Optional: DCS-gRPC as backend to communicate with SRS (via infra)
|
||||
-- * Optional: HOUNDTTS as backend to communicate with SRS (via infra)
|
||||
-- * Optional but recommended: HOUND TTS as backend to communicate with SRS (via plugin)
|
||||
-- * Optional: DCS-gRPC as backend to communicate with SRS (via plugin)
|
||||
--
|
||||
-- ## Known Issues
|
||||
--
|
||||
-- ## Knwon Issues
|
||||
--
|
||||
-- ### Pop-up Window
|
||||
-- ### Pop-up Window (with backend windows)
|
||||
--
|
||||
-- The text-to-speech conversion of SRS is done via an external exe file. When this file is called, a windows `cmd` window is briefly opended. That puts DCS out of focus, which is annoying,
|
||||
-- expecially in VR but unavoidable (if you have a solution, please feel free to share!).
|
||||
-- expecially in VR, but unavoidable unless you use HOUD or gRPC as backends.
|
||||
--
|
||||
-- NOTE that this is not an issue if the mission is running on a server.
|
||||
-- Also NOTE that using DCS-gRPC and Hound-TTS as backend create no pop-up window.
|
||||
--
|
||||
-- # Play Sound Files
|
||||
--
|
||||
@@ -137,16 +134,17 @@
|
||||
--
|
||||
-- See @{#MSRS.LoadConfigFile} for details on how to set this up.
|
||||
--
|
||||
-- ## TTS Providers
|
||||
--
|
||||
-- The default provider for generating speech from text is the native Windows TTS service. Note that you need to install the voices you want to use.
|
||||
-- ## TTS (text-to-speech) Providers
|
||||
--
|
||||
-- The **currently recommended** backend is HOUND TTS (no pop-ups), which has a wide variety of TTS providers.
|
||||
-- The *default* provider for generating speech from text is the native Windows TTS service. Note that you need to install the voices you want to use.
|
||||
--
|
||||
-- **Pro-Tip** - use the command line with power shell to call `DCS-SR-ExternalAudio.exe` - it will tell you what is missing
|
||||
-- and also the Google Console error, in case you have missed a step in setting up your Google TTS.
|
||||
-- For example, `.\DCS-SR-ExternalAudio.exe -t "Text Message" -f 255 -m AM -c 2 -s 2 -z -G "Path_To_You_Google.Json"`
|
||||
-- plays a message on 255 MHz AM for the blue coalition in-game.
|
||||
--
|
||||
-- ### Google
|
||||
-- ### Google (best speech synthesis)
|
||||
--
|
||||
-- In order to use Google Cloud for TTS you need to use @{#MSRS.SetProvider} and @{#MSRS.SetProviderOptionsGoogle} functions:
|
||||
--
|
||||
@@ -162,7 +160,7 @@
|
||||
-- For more information on setting up a cloud account, visit: https://cloud.google.com/text-to-speech
|
||||
-- Google's supported SSML reference: https://cloud.google.com/text-to-speech/docs/ssml
|
||||
--
|
||||
-- ### Amazon Web Service [Only DCS-gRPC backend]
|
||||
-- ### Amazon Web Service [Only HOUDN and DCS-gRPC backend]
|
||||
--
|
||||
-- In order to use Amazon Web Service (AWS) for TTS you need to use @{#MSRS.SetProvider} and @{#MSRS.SetProviderOptionsAmazon} functions:
|
||||
--
|
||||
@@ -173,7 +171,7 @@
|
||||
--
|
||||
-- You can set the voice to use with AWS via @{#MSRS.SetVoiceAmazon}.
|
||||
--
|
||||
-- ### Microsoft Azure [Only DCS-gRPC backend]
|
||||
-- ### Microsoft Azure [Only HOUND and DCS-gRPC backend]
|
||||
--
|
||||
-- In order to use Microsoft Azure for TTS you need to use @{#MSRS.SetProvider} and @{#MSRS.SetProviderOptionsAzure} functions:
|
||||
--
|
||||
@@ -184,6 +182,12 @@
|
||||
--
|
||||
-- You can set the voice to use with Azure via @{#MSRS.SetVoiceAzure}.
|
||||
--
|
||||
-- ### Piper, OpenAI and further provider options [Only HOUND backend]
|
||||
--
|
||||
-- You can use further providers with HOUND TTS as a backend, see [Hound-TTS](https://github.com/uriba107/HoundTTS/releases) for setup.
|
||||
--
|
||||
-- msrs:SetProvider(MSRS.Provider.PIPER) -- e.g. for piper
|
||||
--
|
||||
-- ## Backend
|
||||
--
|
||||
-- The default interface to SRS is via calling the 'DCS-SR-ExternalAudio.exe'. As noted above, this has the unavoidable drawback that a pop-up briefly appears
|
||||
@@ -191,13 +195,13 @@
|
||||
--
|
||||
-- ## Hound TTS as an alternative to 'DCS-SR-ExternalAudio.exe' for TTS
|
||||
--
|
||||
-- An alternative interface to SRS is [Hound-TTS](https://github.com/uriba107/HoundTTS/releases). This does not call an exe file and therefore avoids the annoying pop-up window.
|
||||
-- the recommended alternative interface to SRS is [Hound-TTS](https://github.com/uriba107/HoundTTS/releases). This does not call an exe file and therefore avoids the annoying pop-up window.
|
||||
-- In addition to Windows and Google cloud, it also offers Piper local voice creation and others as providers for TTS.
|
||||
--
|
||||
-- Use @{#MSRS.SetDefaultBackendHound} to enable [Hound-TTS](https://github.com/uriba107/HoundTTS/releases) as an alternate backend.
|
||||
-- This can be useful if the popup should be avoided or to use Piper or others for TTS. Please note, only text-to-speech is supported and it it cannot be used to transmit audio files.
|
||||
--
|
||||
-- Hound TTS must be installed and configured per the [Hound TTS](https://github.com/uriba107/HoundTTS#installation) prior to use.
|
||||
-- Hound TTS must be installed and configured per the [Hound TTS](https://github.com/uriba107/HoundTTS#installation) GitHub prior to use.
|
||||
-- If a cloud TTS provider is being used, the API key(s) must be set as per the documentation.
|
||||
-- Hound TTS can be used both with DCS dedicated server and regular DCS installations.
|
||||
--
|
||||
@@ -280,7 +284,7 @@ MSRS = {
|
||||
|
||||
--- MSRS class version.
|
||||
-- @field #string version
|
||||
MSRS.version="0.3.5"
|
||||
MSRS.version="0.3.7"
|
||||
|
||||
--- Voices
|
||||
-- @type MSRS.Voices
|
||||
@@ -731,14 +735,16 @@ MSRS.Backend = {
|
||||
-- @field #string AZURE Microsoft Azure (`azure`). Only possible with DCS-gRPC backend.
|
||||
-- @field #string AMAZON Amazon Web Service (`aws`). Only possible with DCS-gRPC backend.
|
||||
-- @field #string PIPER Piper local voice service. Only possible with Hound-TTS backend.
|
||||
-- @field #string KITTEN Kitten voice server. Only possible with Hound-TTS backend.
|
||||
-- @field #string KITTEN Kitten voice server. Only possible with Hound-TTS backend. Superseded by OpenAI
|
||||
-- @field #string OPENAI OpenAI voice server. Only possible with Hound-TTS backend.
|
||||
MSRS.Provider = {
|
||||
WINDOWS = "win",
|
||||
GOOGLE = "gcloud",
|
||||
AZURE = "azure",
|
||||
AMAZON = "aws",
|
||||
PIPER = "piper",
|
||||
KITTEN = "kitten",
|
||||
KITTEN = "openai",
|
||||
OPENAI = "openai",
|
||||
}
|
||||
|
||||
--- Function for UUID.
|
||||
@@ -1471,6 +1477,15 @@ function MSRS:SetTTSProviderKitten()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Use OpenAI to provide text-to-speech. Only supported if used in combination with Hound-TTS as backend.
|
||||
-- @param #MSRS self
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetTTSProviderOpenAI()
|
||||
self:F()
|
||||
self:SetProvider(MSRS.Provider.OPENAI)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Print SRS help to DCS log file.
|
||||
-- @param #MSRS self
|
||||
-- @return #MSRS self
|
||||
@@ -1605,7 +1620,7 @@ function MSRS:PlayText(Text, Delay, Coordinate, Speed, Speaker)
|
||||
else
|
||||
|
||||
local speaker = Speaker or self.Speaker
|
||||
|
||||
|
||||
if self.backend==MSRS.Backend.GRPC then
|
||||
self:T(self.lid.."Transmitting")
|
||||
self:_DCSgRPCtts(Text, nil, nil , nil, nil, nil, nil, Coordinate)
|
||||
@@ -2088,19 +2103,16 @@ function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Co
|
||||
return
|
||||
end
|
||||
|
||||
Frequencies = UTILS.EnsureTable(Frequencies)
|
||||
Modulations = UTILS.EnsureTable(Modulations)
|
||||
Frequencies = UTILS.EnsureTable(Frequencies or self.frequencies)
|
||||
Modulations = UTILS.EnsureTable(Modulations or self.modulations)
|
||||
|
||||
local ffs = {}
|
||||
for _,_f in pairs(Frequencies or self.frequencies) do
|
||||
table.insert(ffs,string.format("%.1f",_f))
|
||||
for _,_f in pairs(Frequencies) do
|
||||
table.insert(ffs,string.format("%.3f",_f))
|
||||
end
|
||||
|
||||
local freqs = table.concat(ffs, ",")
|
||||
|
||||
|
||||
|
||||
local modus = table.concat(Modulations or self.modulations, ",")
|
||||
local modus = table.concat(Modulations, ",")
|
||||
|
||||
local coal=Coalition or self.coalition
|
||||
local gender=Gender or self.gender
|
||||
@@ -2144,7 +2156,7 @@ function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Co
|
||||
gender = gender,
|
||||
speaker = Speaker or self.Speaker,
|
||||
}
|
||||
|
||||
|
||||
local speechtime = HoundTTS.Transmit(Message, TransmissionP, ProviderP)
|
||||
|
||||
return speechtime
|
||||
@@ -2202,7 +2214,7 @@ function MSRS:_HoundTestTone(Frequencies, Modulations, Coalition)
|
||||
|
||||
local ffs = {}
|
||||
for _,_f in pairs(Frequencies or self.frequencies) do
|
||||
table.insert(ffs,string.format("%.1f",_f))
|
||||
table.insert(ffs,string.format("%.3f",_f))
|
||||
end
|
||||
|
||||
local freqs = table.concat(ffs, ",")
|
||||
@@ -2258,6 +2270,71 @@ function MSRS._HoundTranslate(Message,Parameters,CallbackFunction)
|
||||
return
|
||||
end
|
||||
|
||||
--- Create and run a radio jammer for a number of seconds on given frequencies. Requires HOUND backend v0.2.0-beta5 or better.
|
||||
-- @param #MSRS self
|
||||
-- @param #table Frequencies The table of frequencies to use.
|
||||
-- @param #table Modulations The table of modulations to use.
|
||||
-- @param #number Coalition (Optional) The coalition to use. Defaults to previously set coalition on this instance.
|
||||
-- @param #string Noisetype (Optional) One of "white" (default) | "chirp" | "harsh" | "jam".
|
||||
-- @param #number Volume (Optional) Volume 0.0–1.0 (default 1.0).
|
||||
-- @param #number Seconds (Optional) How long in seconds this jammer runs. Defaults to 30 seconds.
|
||||
-- @param #string Label (Optional) The label to use, defaults to "MSRS".
|
||||
-- @param DCS#Vec3 Vec3 (Optional) Vec3 of the sender's position.
|
||||
-- @param #boolean Encrypt (Optional) If true, use the SRS encrypt option.
|
||||
-- @param #number EncKey (Optional) If encrypt is true, use this encrypt key.
|
||||
-- @return #number ID The Jammer ID to switch the jamming off again with `MSRS.RadioJammerOff()`
|
||||
function MSRS:RadioJammerOn(Frequencies, Modulations, Coalition, Noisetype, Volume, Seconds, Label, Vec3, Encrypt, EncKey)
|
||||
self:T(self.lid.."RadioJammerOn")
|
||||
|
||||
Frequencies = UTILS.EnsureTable(Frequencies)
|
||||
Modulations = UTILS.EnsureTable(Modulations)
|
||||
|
||||
local ffs = {}
|
||||
for _,_f in pairs(Frequencies or self.frequencies) do
|
||||
table.insert(ffs,string.format("%.3f",_f))
|
||||
end
|
||||
|
||||
local freqs = table.concat(ffs, ",")
|
||||
local modus = table.concat(Modulations or self.modulations, ",")
|
||||
-- Replace modulation
|
||||
modus=modus:gsub("0", "AM")
|
||||
modus=modus:gsub("1", "FM")
|
||||
|
||||
local coal=Coalition or self.coalition or coalition.side.RED
|
||||
local secs = Seconds or 30
|
||||
|
||||
local TransmissionP = {}
|
||||
local ProviderP = {}
|
||||
|
||||
TransmissionP.transmitter = "srs"
|
||||
TransmissionP.freqs = freqs
|
||||
TransmissionP.modulations = modus
|
||||
TransmissionP.coalition = coal or self.coalition -- number 0=spectator, 1=red, 2=blue
|
||||
TransmissionP.name = Label or self.Label -- string client name shown in SRS
|
||||
TransmissionP.point = Vec3 -- DCS Vec3 (optional) — initial transmitter position
|
||||
TransmissionP.encrypt = Encrypt -- bool
|
||||
TransmissionP.encKey = EncKey -- number 0–255
|
||||
|
||||
ProviderP.noiseType = Noisetype or "white"
|
||||
ProviderP.volume = Volume or 1
|
||||
|
||||
local ID = HoundTTS.TransmitNoise(TransmissionP, ProviderP)
|
||||
|
||||
self.NoiseID = ID
|
||||
|
||||
self:ScheduleOnce(secs,MSRS.RadioJammerOff,self,ID)
|
||||
|
||||
return ID
|
||||
|
||||
end
|
||||
|
||||
--- Switch off a running Jammer using its ID.
|
||||
-- @param #MSRS self
|
||||
-- @param #number ID
|
||||
function MSRS:RadioJammerOff(ID)
|
||||
return HoundTTS.KillSession(ID or self.NoiseID)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Config File
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -523,7 +523,7 @@ ENUMS.ReportingName =
|
||||
Tornado = "Tornado",
|
||||
-- Transport / Bomber / Others
|
||||
Atlas = "A400",
|
||||
Lancer = "B1-B",
|
||||
Lancer = "B-1B",
|
||||
Stratofortress = "B-52H",
|
||||
Herc = "C-130",
|
||||
Hercules = "C-130J-30",
|
||||
@@ -568,7 +568,7 @@ ENUMS.ReportingName =
|
||||
Apache = "AH-64",
|
||||
Chinook = "CH-47",
|
||||
Sea_Stallion = "CH-53",
|
||||
Kiowa = "OH-58",
|
||||
Kiowa = "OH58D",
|
||||
Seahawk = "SH-60",
|
||||
Blackhawk = "UH-60",
|
||||
Sea_King = "S-61",
|
||||
|
||||
@@ -2378,6 +2378,11 @@ function UTILS.IsLoadingDoorOpen( unit_name )
|
||||
BASE:T(unit_name .. " front door(s) are open or doors removed")
|
||||
return true
|
||||
end
|
||||
|
||||
if type_name == "Ka-50_3" and unit:getDrawArgumentValue(38) == 1 then
|
||||
BASE:T(unit_name .. " cockpit door is open")
|
||||
return true
|
||||
end
|
||||
|
||||
if type_name=="C-130J-30" and (unit:getDrawArgumentValue(86)==1)then
|
||||
BASE:T(unit_name.." rear doors are open")
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
-- @field #boolean isAirdrome Airbase is an airdrome.
|
||||
-- @field #boolean isHelipad Airbase is a helipad.
|
||||
-- @field #boolean isShip Airbase is a ship.
|
||||
-- @field #boolean isZell Airbase is a ZELL Booster.
|
||||
-- @field #table parking Parking spot data.
|
||||
-- @field #table parkingByID Parking spot data table with ID as key.
|
||||
-- @field #table parkingWhitelist List of parking spot terminal IDs considered for spawning.
|
||||
@@ -458,115 +459,420 @@ AIRBASE.TheChannel = {
|
||||
|
||||
--- Airbases of the Syria map
|
||||
--
|
||||
-- * `AIRBASE.Syria.Abu_al_Duhur` Abu al-Duhur
|
||||
-- * `AIRBASE.Syria.Adana_Sakirpasa` Adana Sakirpasa
|
||||
-- * `AIRBASE.Syria.Akrotiri` Akrotiri
|
||||
-- * `AIRBASE.Syria.Al_Qusayr` Al Qusayr
|
||||
-- * `AIRBASE.Syria.Al_Dumayr` Al-Dumayr
|
||||
-- * `AIRBASE.Syria.Aleppo` Aleppo
|
||||
-- * `AIRBASE.Syria.An_Nasiriyah` An Nasiriyah
|
||||
-- * `AIRBASE.Syria.At_Tanf` At Tanf
|
||||
-- * `AIRBASE.Syria.Bassel_Al_Assad` Bassel Al-Assad
|
||||
-- * `AIRBASE.Syria.Beirut_Rafic_Hariri` Beirut-Rafic Hariri
|
||||
-- * `AIRBASE.Syria.Ben_Gurion` Ben Gurion
|
||||
-- * `AIRBASE.Syria.Damascus` Damascus
|
||||
-- * `AIRBASE.Syria.Deir_ez_Zor` Deir ez-Zor
|
||||
-- * `AIRBASE.Syria.Ercan` Ercan
|
||||
-- * `AIRBASE.Syria.Eyn_Shemer` Eyn Shemer
|
||||
-- * `AIRBASE.Syria.Gaziantep` Gaziantep
|
||||
-- * `AIRBASE.Syria.Gazipasa` Gazipasa
|
||||
-- * `AIRBASE.Syria.Gecitkale` Gecitkale
|
||||
-- * `AIRBASE.Syria.H` H
|
||||
-- * `AIRBASE.Syria.H3` H3
|
||||
-- * `AIRBASE.Syria.H3_Northwest` H3 Northwest
|
||||
-- * `AIRBASE.Syria.H3_Southwest` H3 Southwest
|
||||
-- * `AIRBASE.Syria.H4` H4
|
||||
-- * `AIRBASE.Syria.Haifa` Haifa
|
||||
-- * `AIRBASE.Syria.Hama` Hama
|
||||
-- * `AIRBASE.Syria.Hatay` Hatay
|
||||
-- * `AIRBASE.Syria.Hatzor` Hatzor
|
||||
-- * `AIRBASE.Syria.Herzliya` Herzliya
|
||||
-- * `AIRBASE.Syria.Incirlik` Incirlik
|
||||
-- * `AIRBASE.Syria.Jirah` Jirah
|
||||
-- * `AIRBASE.Syria.Khalkhalah` Khalkhalah
|
||||
-- * `AIRBASE.Syria.Kharab_Ishk` Kharab Ishk
|
||||
-- * `AIRBASE.Syria.King_Abdullah_II` King Abdullah II
|
||||
-- * `AIRBASE.Syria.King_Hussein_Air_College` King Hussein Air College
|
||||
-- * `AIRBASE.Syria.Kingsfield` Kingsfield
|
||||
-- * `AIRBASE.Syria.Kiryat_Shmona` Kiryat Shmona
|
||||
-- * `AIRBASE.Syria.Kuweires` Kuweires
|
||||
-- * `AIRBASE.Syria.Lakatamia` Lakatamia
|
||||
-- * `AIRBASE.Syria.Larnaca` Larnaca
|
||||
-- * `AIRBASE.Syria.Marj_Ruhayyil` Marj Ruhayyil
|
||||
-- * `AIRBASE.Syria.Marj_as_Sultan_North` Marj as Sultan North
|
||||
-- * `AIRBASE.Syria.Marj_as_Sultan_South` Marj as Sultan South
|
||||
-- * `AIRBASE.Syria.Marka` Marka
|
||||
-- * `AIRBASE.Syria.Megiddo` Megiddo
|
||||
-- * `AIRBASE.Syria.Mezzeh` Mezzeh
|
||||
-- * `AIRBASE.Syria.Minakh` Minakh
|
||||
-- * `AIRBASE.Syria.Muwaffaq_Salti` Muwaffaq Salti
|
||||
-- * `AIRBASE.Syria.Naqoura` Naqoura
|
||||
-- * `AIRBASE.Syria.Nicosia` Nicosia
|
||||
-- * `AIRBASE.Syria.Palmachim` Palmachim
|
||||
-- * `AIRBASE.Syria.Palmyra` Palmyra
|
||||
-- * `AIRBASE.Syria.Paphos` Paphos
|
||||
-- * `AIRBASE.Syria.Pinarbashi` Pinarbashi
|
||||
-- * `AIRBASE.Syria.Prince_Hassan` Prince Hassan
|
||||
-- * `AIRBASE.Syria.Qabr_as_Sitt` Qabr as Sitt
|
||||
-- * `AIRBASE.Syria.Ramat_David` Ramat David
|
||||
-- * `AIRBASE.Syria.Rayak` Rayak
|
||||
-- * `AIRBASE.Syria.Rene_Mouawad` Rene Mouawad
|
||||
-- * `AIRBASE.Syria.Rosh_Pina` Rosh Pina
|
||||
-- * `AIRBASE.Syria.Ruwayshid` Ruwayshid
|
||||
-- * `AIRBASE.Syria.Sanliurfa` Sanliurfa
|
||||
-- * `AIRBASE.Syria.Sayqal` Sayqal
|
||||
-- * `AIRBASE.Syria.Shayrat` Shayrat
|
||||
-- * `AIRBASE.Syria.Tabqa` Tabqa
|
||||
-- * `AIRBASE.Syria.Taftanaz` Taftanaz
|
||||
-- * `AIRBASE.Syria.Tal_Siman` Tal Siman
|
||||
-- * `AIRBASE.Syria.Tel_Nof` Tel Nof
|
||||
-- * `AIRBASE.Syria.Tha_lah` Tha'lah
|
||||
-- * `AIRBASE.Syria.Tiyas` Tiyas
|
||||
-- * `AIRBASE.Syria.Wujah_Al_Hajar` Wujah Al Hajar
|
||||
-- * AIRBASE.Syria.Abu_al_Duhur
|
||||
-- * AIRBASE.Syria.Adana_Sakirpasa
|
||||
-- * AIRBASE.Syria.Adiyaman
|
||||
-- * AIRBASE.Syria.Akrotiri
|
||||
-- * AIRBASE.Syria.Al_Dumayr
|
||||
-- * AIRBASE.Syria.Al_Qusayr
|
||||
-- * AIRBASE.Syria.Aleppo
|
||||
-- * AIRBASE.Syria.An_Nasiriyah
|
||||
-- * AIRBASE.Syria.At_Tanf
|
||||
-- * AIRBASE.Syria.Bassel_Al_Assad
|
||||
-- * AIRBASE.Syria.Beirut_Rafic_Hariri
|
||||
-- * AIRBASE.Syria.Ben_Gurion
|
||||
-- * AIRBASE.Syria.Chukurova
|
||||
-- * AIRBASE.Syria.Damascus
|
||||
-- * AIRBASE.Syria.Deir_ez_Zor
|
||||
-- * AIRBASE.Syria.Diyarbakir
|
||||
-- * AIRBASE.Syria.Ercan
|
||||
-- * AIRBASE.Syria.Eyn_Shemer
|
||||
-- * AIRBASE.Syria.Gaziantep
|
||||
-- * AIRBASE.Syria.Gazipasa
|
||||
-- * AIRBASE.Syria.Gecitkale
|
||||
-- * AIRBASE.Syria.Gulechoba
|
||||
-- * AIRBASE.Syria.H3
|
||||
-- * AIRBASE.Syria.H3_Northwest
|
||||
-- * AIRBASE.Syria.H3_Southwest
|
||||
-- * AIRBASE.Syria.H4
|
||||
-- * AIRBASE.Syria.H4_Emergency
|
||||
-- * AIRBASE.Syria.HC01
|
||||
-- * AIRBASE.Syria.HC02
|
||||
-- * AIRBASE.Syria.HC03
|
||||
-- * AIRBASE.Syria.HC04
|
||||
-- * AIRBASE.Syria.HC05
|
||||
-- * AIRBASE.Syria.HC06
|
||||
-- * AIRBASE.Syria.HI01
|
||||
-- * AIRBASE.Syria.HI02
|
||||
-- * AIRBASE.Syria.HI03
|
||||
-- * AIRBASE.Syria.HI05
|
||||
-- * AIRBASE.Syria.HI06
|
||||
-- * AIRBASE.Syria.HI07
|
||||
-- * AIRBASE.Syria.HI08
|
||||
-- * AIRBASE.Syria.HI09
|
||||
-- * AIRBASE.Syria.HI11
|
||||
-- * AIRBASE.Syria.HI12
|
||||
-- * AIRBASE.Syria.HI13
|
||||
-- * AIRBASE.Syria.HI14
|
||||
-- * AIRBASE.Syria.HI15
|
||||
-- * AIRBASE.Syria.HI16
|
||||
-- * AIRBASE.Syria.HI17
|
||||
-- * AIRBASE.Syria.HI18
|
||||
-- * AIRBASE.Syria.HI20
|
||||
-- * AIRBASE.Syria.HI21
|
||||
-- * AIRBASE.Syria.HI22
|
||||
-- * AIRBASE.Syria.HI23
|
||||
-- * AIRBASE.Syria.HI24
|
||||
-- * AIRBASE.Syria.HI25
|
||||
-- * AIRBASE.Syria.HI26
|
||||
-- * AIRBASE.Syria.HJ01
|
||||
-- * AIRBASE.Syria.HJ02
|
||||
-- * AIRBASE.Syria.HJ03
|
||||
-- * AIRBASE.Syria.HJ04
|
||||
-- * AIRBASE.Syria.HL01
|
||||
-- * AIRBASE.Syria.HL02
|
||||
-- * AIRBASE.Syria.HL03
|
||||
-- * AIRBASE.Syria.HL04
|
||||
-- * AIRBASE.Syria.HL05
|
||||
-- * AIRBASE.Syria.HL06
|
||||
-- * AIRBASE.Syria.HL07
|
||||
-- * AIRBASE.Syria.HL08
|
||||
-- * AIRBASE.Syria.HL09
|
||||
-- * AIRBASE.Syria.HL10
|
||||
-- * AIRBASE.Syria.HL11
|
||||
-- * AIRBASE.Syria.HL12
|
||||
-- * AIRBASE.Syria.HL13
|
||||
-- * AIRBASE.Syria.HMed00
|
||||
-- * AIRBASE.Syria.HMed01
|
||||
-- * AIRBASE.Syria.HMed02
|
||||
-- * AIRBASE.Syria.HMed03
|
||||
-- * AIRBASE.Syria.HMed04
|
||||
-- * AIRBASE.Syria.HMed05
|
||||
-- * AIRBASE.Syria.HMed06
|
||||
-- * AIRBASE.Syria.HMed07
|
||||
-- * AIRBASE.Syria.HMed08
|
||||
-- * AIRBASE.Syria.HMed09
|
||||
-- * AIRBASE.Syria.HMed10
|
||||
-- * AIRBASE.Syria.HMed11
|
||||
-- * AIRBASE.Syria.HMed12
|
||||
-- * AIRBASE.Syria.HMed13
|
||||
-- * AIRBASE.Syria.HMed14
|
||||
-- * AIRBASE.Syria.HMed15
|
||||
-- * AIRBASE.Syria.HMed16
|
||||
-- * AIRBASE.Syria.HMed17
|
||||
-- * AIRBASE.Syria.HMed18
|
||||
-- * AIRBASE.Syria.HMed19
|
||||
-- * AIRBASE.Syria.HMed20
|
||||
-- * AIRBASE.Syria.HMed21
|
||||
-- * AIRBASE.Syria.HMed22
|
||||
-- * AIRBASE.Syria.HMed23
|
||||
-- * AIRBASE.Syria.HMed24
|
||||
-- * AIRBASE.Syria.HMed25
|
||||
-- * AIRBASE.Syria.HMed26
|
||||
-- * AIRBASE.Syria.HMed27
|
||||
-- * AIRBASE.Syria.HMed28
|
||||
-- * AIRBASE.Syria.HMed29
|
||||
-- * AIRBASE.Syria.HMed30
|
||||
-- * AIRBASE.Syria.HOil01
|
||||
-- * AIRBASE.Syria.HOil02
|
||||
-- * AIRBASE.Syria.HOil03
|
||||
-- * AIRBASE.Syria.HOil04
|
||||
-- * AIRBASE.Syria.HOil05
|
||||
-- * AIRBASE.Syria.HOil06
|
||||
-- * AIRBASE.Syria.HS02
|
||||
-- * AIRBASE.Syria.HS03
|
||||
-- * AIRBASE.Syria.HS04
|
||||
-- * AIRBASE.Syria.HS05
|
||||
-- * AIRBASE.Syria.HS06
|
||||
-- * AIRBASE.Syria.HS07
|
||||
-- * AIRBASE.Syria.HS08
|
||||
-- * AIRBASE.Syria.HS09
|
||||
-- * AIRBASE.Syria.HS10
|
||||
-- * AIRBASE.Syria.HS11
|
||||
-- * AIRBASE.Syria.HS12
|
||||
-- * AIRBASE.Syria.HS13
|
||||
-- * AIRBASE.Syria.HS14
|
||||
-- * AIRBASE.Syria.HS15
|
||||
-- * AIRBASE.Syria.HS16
|
||||
-- * AIRBASE.Syria.HS17
|
||||
-- * AIRBASE.Syria.HS18
|
||||
-- * AIRBASE.Syria.HS19
|
||||
-- * AIRBASE.Syria.HS20
|
||||
-- * AIRBASE.Syria.HS21
|
||||
-- * AIRBASE.Syria.HS22
|
||||
-- * AIRBASE.Syria.HS23
|
||||
-- * AIRBASE.Syria.HS24
|
||||
-- * AIRBASE.Syria.HS25
|
||||
-- * AIRBASE.Syria.HS26
|
||||
-- * AIRBASE.Syria.HS27
|
||||
-- * AIRBASE.Syria.HS28
|
||||
-- * AIRBASE.Syria.HS29
|
||||
-- * AIRBASE.Syria.HS30
|
||||
-- * AIRBASE.Syria.HS31
|
||||
-- * AIRBASE.Syria.HS32
|
||||
-- * AIRBASE.Syria.HS33
|
||||
-- * AIRBASE.Syria.HS34
|
||||
-- * AIRBASE.Syria.HS35
|
||||
-- * AIRBASE.Syria.HS36
|
||||
-- * AIRBASE.Syria.HS37
|
||||
-- * AIRBASE.Syria.HS38
|
||||
-- * AIRBASE.Syria.HS39
|
||||
-- * AIRBASE.Syria.HS40
|
||||
-- * AIRBASE.Syria.HS41
|
||||
-- * AIRBASE.Syria.HS42
|
||||
-- * AIRBASE.Syria.HStad01
|
||||
-- * AIRBASE.Syria.HStad02
|
||||
-- * AIRBASE.Syria.HStad03
|
||||
-- * AIRBASE.Syria.HStad04
|
||||
-- * AIRBASE.Syria.HStad05
|
||||
-- * AIRBASE.Syria.HStad06
|
||||
-- * AIRBASE.Syria.HT01
|
||||
-- * AIRBASE.Syria.HT02
|
||||
-- * AIRBASE.Syria.H_med_orig_01
|
||||
-- * AIRBASE.Syria.H_med_orig_02
|
||||
-- * AIRBASE.Syria.H_med_orig_03
|
||||
-- * AIRBASE.Syria.H_med_orig_04
|
||||
-- * AIRBASE.Syria.H_med_orig_05
|
||||
-- * AIRBASE.Syria.H_med_orig_06
|
||||
-- * AIRBASE.Syria.H_med_orig_07
|
||||
-- * AIRBASE.Syria.H_med_orig_08
|
||||
-- * AIRBASE.Syria.H_med_orig_09
|
||||
-- * AIRBASE.Syria.Haifa
|
||||
-- * AIRBASE.Syria.Hama
|
||||
-- * AIRBASE.Syria.Hatay
|
||||
-- * AIRBASE.Syria.Hatzerim
|
||||
-- * AIRBASE.Syria.Hatzor
|
||||
-- * AIRBASE.Syria.Herzliya
|
||||
-- * AIRBASE.Syria.Incirlik
|
||||
-- * AIRBASE.Syria.Jirah
|
||||
-- * AIRBASE.Syria.Kahramanmaras
|
||||
-- * AIRBASE.Syria.Kedem
|
||||
-- * AIRBASE.Syria.Khalkhalah
|
||||
-- * AIRBASE.Syria.Kharab_Ishk
|
||||
-- * AIRBASE.Syria.King_Abdullah_II
|
||||
-- * AIRBASE.Syria.King_Hussein_Air_College
|
||||
-- * AIRBASE.Syria.Kingsfield
|
||||
-- * AIRBASE.Syria.Kiryat_Shmona
|
||||
-- * AIRBASE.Syria.Konya
|
||||
-- * AIRBASE.Syria.Kuweires
|
||||
-- * AIRBASE.Syria.Lakatamia
|
||||
-- * AIRBASE.Syria.Larnaca
|
||||
-- * AIRBASE.Syria.Marj_Ruhayyil
|
||||
-- * AIRBASE.Syria.Marj_as_Sultan_North
|
||||
-- * AIRBASE.Syria.Marj_as_Sultan_South
|
||||
-- * AIRBASE.Syria.Marka
|
||||
-- * AIRBASE.Syria.Megiddo
|
||||
-- * AIRBASE.Syria.Mezzeh
|
||||
-- * AIRBASE.Syria.Minakh
|
||||
-- * AIRBASE.Syria.Muwaffaq_Salti
|
||||
-- * AIRBASE.Syria.Naqoura
|
||||
-- * AIRBASE.Syria.Nevatim
|
||||
-- * AIRBASE.Syria.Nicosia
|
||||
-- * AIRBASE.Syria.Palmachim
|
||||
-- * AIRBASE.Syria.Palmyra
|
||||
-- * AIRBASE.Syria.Paphos
|
||||
-- * AIRBASE.Syria.Pinarbashi
|
||||
-- * AIRBASE.Syria.Prince_Hassan
|
||||
-- * AIRBASE.Syria.Qabr_as_Sitt
|
||||
-- * AIRBASE.Syria.Ramat_David
|
||||
-- * AIRBASE.Syria.Rayak
|
||||
-- * AIRBASE.Syria.Rene_Mouawad
|
||||
-- * AIRBASE.Syria.Rosh_Pina
|
||||
-- * AIRBASE.Syria.Ruwayshid
|
||||
-- * AIRBASE.Syria.Sanliurfa
|
||||
-- * AIRBASE.Syria.Sanliurfa_Heliport
|
||||
-- * AIRBASE.Syria.Sayqal
|
||||
-- * AIRBASE.Syria.Shayrat
|
||||
-- * AIRBASE.Syria.T2
|
||||
-- * AIRBASE.Syria.T3
|
||||
-- * AIRBASE.Syria.Tabqa
|
||||
-- * AIRBASE.Syria.Taftanaz
|
||||
-- * AIRBASE.Syria.Tal_Siman
|
||||
-- * AIRBASE.Syria.Tel_Nof
|
||||
-- * AIRBASE.Syria.Teyman
|
||||
-- * AIRBASE.Syria.Tha_lah
|
||||
-- * AIRBASE.Syria.Tiyas
|
||||
-- * AIRBASE.Syria.Wujah_Al_Hajar
|
||||
-- * AIRBASE.Syria.Zarqa
|
||||
|
||||
--
|
||||
-- @field Syria
|
||||
AIRBASE.Syria = {
|
||||
["Abu_al_Duhur"] = "Abu al-Duhur",
|
||||
["Adana_Sakirpasa"] = "Adana Sakirpasa",
|
||||
["Adiyaman"] = "Adiyaman",
|
||||
["Akrotiri"] = "Akrotiri",
|
||||
["Al_Qusayr"] = "Al Qusayr",
|
||||
["Al_Dumayr"] = "Al-Dumayr",
|
||||
["Al_Qusayr"] = "Al Qusayr",
|
||||
["Aleppo"] = "Aleppo",
|
||||
["An_Nasiriyah"] = "An Nasiriyah",
|
||||
["At_Tanf"] = "At Tanf",
|
||||
["Bassel_Al_Assad"] = "Bassel Al-Assad",
|
||||
["Beirut_Rafic_Hariri"] = "Beirut-Rafic Hariri",
|
||||
["Ben_Gurion"] = "Ben Gurion",
|
||||
["Chukurova"] = "Chukurova",
|
||||
["Damascus"] = "Damascus",
|
||||
["Deir_ez_Zor"] = "Deir ez-Zor",
|
||||
["Diyarbakir"] = "Diyarbakir",
|
||||
["Ercan"] = "Ercan",
|
||||
["Eyn_Shemer"] = "Eyn Shemer",
|
||||
["Gaziantep"] = "Gaziantep",
|
||||
["Gazipasa"] = "Gazipasa",
|
||||
["Gecitkale"] = "Gecitkale",
|
||||
["H"] = "H",
|
||||
["Gulechoba"] = "Gulechoba",
|
||||
["H3"] = "H3",
|
||||
["H3_Northwest"] = "H3 Northwest",
|
||||
["H3_Southwest"] = "H3 Southwest",
|
||||
["H4"] = "H4",
|
||||
["H4_Emergency"] = "H4 Emergency",
|
||||
["HC01"] = "HC01",
|
||||
["HC02"] = "HC02",
|
||||
["HC03"] = "HC03",
|
||||
["HC04"] = "HC04",
|
||||
["HC05"] = "HC05",
|
||||
["HC06"] = "HC06",
|
||||
["HI01"] = "HI01",
|
||||
["HI02"] = "HI02",
|
||||
["HI03"] = "HI03",
|
||||
["HI05"] = "HI05",
|
||||
["HI06"] = "HI06",
|
||||
["HI07"] = "HI07",
|
||||
["HI08"] = "HI08",
|
||||
["HI09"] = "HI09",
|
||||
["HI11"] = "HI11",
|
||||
["HI12"] = "HI12",
|
||||
["HI13"] = "HI13",
|
||||
["HI14"] = "HI14",
|
||||
["HI15"] = "HI15",
|
||||
["HI16"] = "HI16",
|
||||
["HI17"] = "HI17",
|
||||
["HI18"] = "HI18",
|
||||
["HI20"] = "HI20",
|
||||
["HI21"] = "HI21",
|
||||
["HI22"] = "HI22",
|
||||
["HI23"] = "HI23",
|
||||
["HI24"] = "HI24",
|
||||
["HI25"] = "HI25",
|
||||
["HI26"] = "HI26",
|
||||
["HJ01"] = "HJ01",
|
||||
["HJ02"] = "HJ02",
|
||||
["HJ03"] = "HJ03",
|
||||
["HJ04"] = "HJ04",
|
||||
["HL01"] = "HL01",
|
||||
["HL02"] = "HL02",
|
||||
["HL03"] = "HL03",
|
||||
["HL04"] = "HL04",
|
||||
["HL05"] = "HL05",
|
||||
["HL06"] = "HL06",
|
||||
["HL07"] = "HL07",
|
||||
["HL08"] = "HL08",
|
||||
["HL09"] = "HL09",
|
||||
["HL10"] = "HL10",
|
||||
["HL11"] = "HL11",
|
||||
["HL12"] = "HL12",
|
||||
["HL13"] = "HL13",
|
||||
["HMed00"] = "HMed00",
|
||||
["HMed01"] = "HMed01",
|
||||
["HMed02"] = "HMed02",
|
||||
["HMed03"] = "HMed03",
|
||||
["HMed04"] = "HMed04",
|
||||
["HMed05"] = "HMed05",
|
||||
["HMed06"] = "HMed06",
|
||||
["HMed07"] = "HMed07",
|
||||
["HMed08"] = "HMed08",
|
||||
["HMed09"] = "HMed09",
|
||||
["HMed10"] = "HMed10",
|
||||
["HMed11"] = "HMed11",
|
||||
["HMed12"] = "HMed12",
|
||||
["HMed13"] = "HMed13",
|
||||
["HMed14"] = "HMed14",
|
||||
["HMed15"] = "HMed15",
|
||||
["HMed16"] = "HMed16",
|
||||
["HMed17"] = "HMed17",
|
||||
["HMed18"] = "HMed18",
|
||||
["HMed19"] = "HMed19",
|
||||
["HMed20"] = "HMed20",
|
||||
["HMed21"] = "HMed21",
|
||||
["HMed22"] = "HMed22",
|
||||
["HMed23"] = "HMed23",
|
||||
["HMed24"] = "HMed24",
|
||||
["HMed25"] = "HMed25",
|
||||
["HMed26"] = "HMed26",
|
||||
["HMed27"] = "HMed27",
|
||||
["HMed28"] = "HMed28",
|
||||
["HMed29"] = "HMed29",
|
||||
["HMed30"] = "HMed30",
|
||||
["HOil01"] = "HOil01",
|
||||
["HOil02"] = "HOil02",
|
||||
["HOil03"] = "HOil03",
|
||||
["HOil04"] = "HOil04",
|
||||
["HOil05"] = "HOil05",
|
||||
["HOil06"] = "HOil06",
|
||||
["HS02"] = "HS02",
|
||||
["HS03"] = "HS03",
|
||||
["HS04"] = "HS04",
|
||||
["HS05"] = "HS05",
|
||||
["HS06"] = "HS06",
|
||||
["HS07"] = "HS07",
|
||||
["HS08"] = "HS08",
|
||||
["HS09"] = "HS09",
|
||||
["HS10"] = "HS10",
|
||||
["HS11"] = "HS11",
|
||||
["HS12"] = "HS12",
|
||||
["HS13"] = "HS13",
|
||||
["HS14"] = "HS14",
|
||||
["HS15"] = "HS15",
|
||||
["HS16"] = "HS16",
|
||||
["HS17"] = "HS17",
|
||||
["HS18"] = "HS18",
|
||||
["HS19"] = "HS19",
|
||||
["HS20"] = "HS20",
|
||||
["HS21"] = "HS21",
|
||||
["HS22"] = "HS22",
|
||||
["HS23"] = "HS23",
|
||||
["HS24"] = "HS24",
|
||||
["HS25"] = "HS25",
|
||||
["HS26"] = "HS26",
|
||||
["HS27"] = "HS27",
|
||||
["HS28"] = "HS28",
|
||||
["HS29"] = "HS29",
|
||||
["HS30"] = "HS30",
|
||||
["HS31"] = "HS31",
|
||||
["HS32"] = "HS32",
|
||||
["HS33"] = "HS33",
|
||||
["HS34"] = "HS34",
|
||||
["HS35"] = "HS35",
|
||||
["HS36"] = "HS36",
|
||||
["HS37"] = "HS37",
|
||||
["HS38"] = "HS38",
|
||||
["HS39"] = "HS39",
|
||||
["HS40"] = "HS40",
|
||||
["HS41"] = "HS41",
|
||||
["HS42"] = "HS42",
|
||||
["HStad01"] = "HStad01",
|
||||
["HStad02"] = "HStad02",
|
||||
["HStad03"] = "HStad03",
|
||||
["HStad04"] = "HStad04",
|
||||
["HStad05"] = "HStad05",
|
||||
["HStad06"] = "HStad06",
|
||||
["HT01"] = "HT01",
|
||||
["HT02"] = "HT02",
|
||||
["H_med_orig_01"] = "H_med_orig_01",
|
||||
["H_med_orig_02"] = "H_med_orig_02",
|
||||
["H_med_orig_03"] = "H_med_orig_03",
|
||||
["H_med_orig_04"] = "H_med_orig_04",
|
||||
["H_med_orig_05"] = "H_med_orig_05",
|
||||
["H_med_orig_06"] = "H_med_orig_06",
|
||||
["H_med_orig_07"] = "H_med_orig_07",
|
||||
["H_med_orig_08"] = "H_med_orig_08",
|
||||
["H_med_orig_09"] = "H_med_orig_09",
|
||||
["Haifa"] = "Haifa",
|
||||
["Hama"] = "Hama",
|
||||
["Hatay"] = "Hatay",
|
||||
["Hatzerim"] = "Hatzerim",
|
||||
["Hatzor"] = "Hatzor",
|
||||
["Herzliya"] = "Herzliya",
|
||||
["Incirlik"] = "Incirlik",
|
||||
["Jirah"] = "Jirah",
|
||||
["Kahramanmaras"] = "Kahramanmaras",
|
||||
["Kedem"] = "Kedem",
|
||||
["Khalkhalah"] = "Khalkhalah",
|
||||
["Kharab_Ishk"] = "Kharab Ishk",
|
||||
["King_Abdullah_II"] = "King Abdullah II",
|
||||
["King_Hussein_Air_College"] = "King Hussein Air College",
|
||||
["Kingsfield"] = "Kingsfield",
|
||||
["Kiryat_Shmona"] = "Kiryat Shmona",
|
||||
["Konya"] = "Konya",
|
||||
["Kuweires"] = "Kuweires",
|
||||
["Lakatamia"] = "Lakatamia",
|
||||
["Larnaca"] = "Larnaca",
|
||||
@@ -579,6 +885,7 @@ AIRBASE.Syria = {
|
||||
["Minakh"] = "Minakh",
|
||||
["Muwaffaq_Salti"] = "Muwaffaq Salti",
|
||||
["Naqoura"] = "Naqoura",
|
||||
["Nevatim"] = "Nevatim",
|
||||
["Nicosia"] = "Nicosia",
|
||||
["Palmachim"] = "Palmachim",
|
||||
["Palmyra"] = "Palmyra",
|
||||
@@ -592,15 +899,20 @@ AIRBASE.Syria = {
|
||||
["Rosh_Pina"] = "Rosh Pina",
|
||||
["Ruwayshid"] = "Ruwayshid",
|
||||
["Sanliurfa"] = "Sanliurfa",
|
||||
["Sanliurfa_Heliport"] = "Sanliurfa Heliport",
|
||||
["Sayqal"] = "Sayqal",
|
||||
["Shayrat"] = "Shayrat",
|
||||
["T2"] = "T2",
|
||||
["T3"] = "T3",
|
||||
["Tabqa"] = "Tabqa",
|
||||
["Taftanaz"] = "Taftanaz",
|
||||
["Tal_Siman"] = "Tal Siman",
|
||||
["Tel_Nof"] = "Tel Nof",
|
||||
["Teyman"] = "Teyman",
|
||||
["Tha_lah"] = "Tha'lah",
|
||||
["Tiyas"] = "Tiyas",
|
||||
["Wujah_Al_Hajar"] = "Wujah Al Hajar",
|
||||
["Zarqa"] = "Zarqa",
|
||||
}
|
||||
|
||||
--- Airbases of the Mariana Islands map
|
||||
@@ -1673,6 +1985,7 @@ elseif self.category==Airbase.Category.SHIP then
|
||||
self.category=Airbase.Category.HELIPAD
|
||||
_DATABASE:AddStatic(AirbaseName)
|
||||
end
|
||||
if self:GetTypeName() == "Zell" then self.isZell = true end
|
||||
else
|
||||
self:E("ERROR: Unknown airbase category!")
|
||||
end
|
||||
@@ -2122,6 +2435,13 @@ function AIRBASE:IsShip()
|
||||
return self.isShip
|
||||
end
|
||||
|
||||
--- Check if airbase is a ZELL booster.
|
||||
-- @param #AIRBASE self
|
||||
-- @return #boolean If true, airbase is a ZELL booster.
|
||||
function AIRBASE:IsZell()
|
||||
return self.isZell
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Parking
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -112,6 +112,7 @@ Ops/OpsZone.lua
|
||||
Ops/ArmyGroup.lua
|
||||
Ops/OpsTransport.lua
|
||||
Ops/Target.lua
|
||||
Ops/TARS.lua
|
||||
|
||||
Sound/UserSound.lua
|
||||
Sound/SoundOutput.lua
|
||||
|
||||
Reference in New Issue
Block a user