Merge remote-tracking branch 'origin/master-ng' into develop

This commit is contained in:
Applevangelist
2026-05-24 13:01:22 +02:00
7 changed files with 1137 additions and 46 deletions
+1 -1
View File
@@ -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,
+8 -2
View File
@@ -2331,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
@@ -2392,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
+826 -13
View File
@@ -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.
@@ -376,7 +376,7 @@ MANTIS.radiusscale[MANTIS.SamType.POINT] = 3
-- @field #string Point Point defense capable
-- @field ARMCapacit ARMCapacity ie how many (H)ARMs the system can defend at the same time
MANTIS.SamData = {
["Hawk"] = { Range=45, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km
["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km
["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS", ARMCapacity=1 }, -- AIM 120B
["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot str" },
["Rapier"] = { Range=10, Blindspot=0, Height=3, Type="Short", Radar="rapier" },
@@ -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,157 @@ 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 — v8 curves {peak%, mu_nm, sigma_L, tail_dist, band, floor%}
-- floor: residual effectiveness inside burnthrough (noise injection)
-- 5 = legacy radar, no ECCM | 3 = moderate ECCM | 2 = advanced ECCM/AESA | 0 = optical/IR
-- @type MANTIS.JammerSAMParams
MANTIS.JammerSAMParams = {
["Nike"] ={peak=78,mu=35,sigma_L=14,tail_dist=80, band="S", floor=5},
["Hawk"] ={peak=30,mu=15,sigma_L=6, tail_dist=28, band="IJ", floor=3},
["SA-2"] ={peak=75,mu=40,sigma_L=16,tail_dist=85, band="S", floor=5},
["SA-3"] ={peak=45,mu=22,sigma_L=10,tail_dist=50, band="IJ", floor=5},
["SA-5"] ={peak=52,mu=60,sigma_L=22,tail_dist=110,band="S", floor=5},
["SA-6"] ={peak=33,mu=18,sigma_L=8, tail_dist=42, band="IJ", floor=3},
["SA-8"] ={peak=38,mu=10,sigma_L=4, tail_dist=22, 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=90, band="S", floor=3},
["SA-11"] ={peak=52,mu=28,sigma_L=12,tail_dist=55, 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=14,sigma_L=6, tail_dist=30, band="IJ", floor=3},
["SA-19"] ={peak=25,mu=18,sigma_L=7, tail_dist=38, band="IJ", floor=2},
["SA-10B"] ={peak=30,mu=52,sigma_L=20,tail_dist=95, band="S", floor=3},
["SA-17"] ={peak=24,mu=32,sigma_L=14,tail_dist=65, band="IJ", floor=2},
["SA-20A"] ={peak=22,mu=58,sigma_L=22,tail_dist=95, band="S", floor=2},
["SA-20B"] ={peak=20,mu=60,sigma_L=22,tail_dist=100,band="S", floor=2},
["S-300VM"] ={peak=16,mu=70,sigma_L=28,tail_dist=110,band="S", floor=2},
["S-300V4"] ={peak=14,mu=75,sigma_L=28,tail_dist=115,band="S", floor=2},
["S-400"] ={peak=18,mu=65,sigma_L=25,tail_dist=105,band="S", floor=2},
["SA-21"] ={peak=18,mu=65,sigma_L=25,tail_dist=105,band="S", floor=2},
-- NATO designation aliases (allow mission designers to use SA-NN naming)
["SA-22"] ={peak=10,mu=5, sigma_L=2, tail_dist=12, band="IJ", floor=0}, -- alias for Pantsir S1
["SA-23"] ={peak=16,mu=70,sigma_L=28,tail_dist=110,band="S", floor=2}, -- alias for S-300VM
["SA-23B"] ={peak=14,mu=75,sigma_L=28,tail_dist=115,band="S", floor=2}, -- alias for S-300V4
["SA-27"] ={peak=22,mu=38,sigma_L=16,tail_dist=72, band="IJ", floor=2}, -- alias for Buk-M3
["SA-28"] ={peak=18,mu=35,sigma_L=15,tail_dist=75, band="IJ", floor=2}, -- alias for S-350
["NASAMS"] ={peak=25,mu=28,sigma_L=12,tail_dist=55, band="IJ", floor=2},
["Patriot"] ={peak=32,mu=50,sigma_L=20,tail_dist=90, band="S", floor=3},
["Rapier"] ={peak=12,mu=8, sigma_L=3, tail_dist=18, band="IJ", floor=0},
["Gepard"] ={peak=18,mu=6, sigma_L=2, tail_dist=15, band="IJ", floor=0},
["Roland"] ={peak=35,mu=5, sigma_L=2, tail_dist=12, band="IJ", floor=3},
["HQ-7"] ={peak=38,mu=10,sigma_L=4, tail_dist=22, 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=5, sigma_L=2, tail_dist=12, band="IJ", floor=0},
["Tor M2"] ={peak=28,mu=14,sigma_L=6, tail_dist=30, band="IJ", floor=3},
["IRIS-T SLM"] ={peak=18,mu=18,sigma_L=8, tail_dist=40, band="IJ", floor=2},
["SON-9"] ={peak=48,mu=20,sigma_L=9, tail_dist=42, band="IJ", floor=5},
["TAMIR IDFA"] ={peak=19,mu=25,sigma_L=12,tail_dist=55,band="S", floor=2},
["STUNNER IDFA"]={peak=16,mu=45,sigma_L=18,tail_dist=80,band="S", floor=2},
["SA-2 HDS"] ={peak=75,mu=40,sigma_L=16,tail_dist=85, band="S", floor=5},
["SA-3 HDS"] ={peak=45,mu=22,sigma_L=10,tail_dist=50, band="IJ", floor=5},
["SA-10B HDS"] ={peak=30,mu=52,sigma_L=20,tail_dist=95, band="S", floor=3},
["SA-10C HDS"] ={peak=30,mu=50,sigma_L=20,tail_dist=92, band="S", floor=3},
["SA-17 HDS"] ={peak=24,mu=32,sigma_L=14,tail_dist=65, band="IJ", floor=2},
["SA-12 HDS"] ={peak=35,mu=42,sigma_L=16,tail_dist=75, band="S", floor=3},
["SA-23 HDS"] ={peak=16,mu=70,sigma_L=28,tail_dist=110,band="S", floor=2},
["HQ-2 HDS"] ={peak=70,mu=38,sigma_L=15,tail_dist=80, band="S", floor=5},
["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},
["RBS98M SMA"] ={peak=25,mu=12,sigma_L=5, tail_dist=25,band="IJ", floor=3},
["RBS70 SMA"] ={peak=5, mu=4, sigma_L=2, tail_dist=10,band="OPT",floor=0},
["RBS70M SMA"] ={peak=5, mu=4, sigma_L=2, tail_dist=10,band="OPT",floor=0},
["RBS90 SMA"] ={peak=5, mu=4, sigma_L=2, tail_dist=10,band="OPT",floor=0},
["RBS90M SMA"] ={peak=5, mu=4, sigma_L=2, tail_dist=10,band="OPT",floor=0},
["RBS103A SMA"] ={peak=20,mu=55,sigma_L=20,tail_dist=90,band="S", floor=2},
["RBS103B SMA"] ={peak=22,mu=45,sigma_L=18,tail_dist=80,band="S", floor=2},
["RBS103AM SMA"] ={peak=20,mu=55,sigma_L=20,tail_dist=90,band="S", floor=2},
["RBS103BM SMA"] ={peak=22,mu=45,sigma_L=18,tail_dist=80,band="S", floor=2},
["Lvkv9040M SMA"]={peak=15,mu=3, sigma_L=1, tail_dist=8, band="OPT",floor=0},
["2S38 CHM"] ={peak=8, mu=3, sigma_L=1, tail_dist=8, band="OPT",floor=0},
["PantsirS1 CHM"] ={peak=10,mu=5, sigma_L=2, tail_dist=12, band="IJ", floor=0},
["PantsirS2 CHM"] ={peak=10,mu=5, sigma_L=2, tail_dist=12, band="IJ", floor=0},
["PGL-625 CHM"] ={peak=12,mu=4, sigma_L=2, tail_dist=10, band="OPT",floor=0},
["HQ-17A CHM"] ={peak=30,mu=14,sigma_L=6, tail_dist=30, band="IJ", floor=3},
["M903PAC2 CHM"] ={peak=32,mu=50,sigma_L=20,tail_dist=90, band="S", floor=3},
["M903PAC3 CHM"] ={peak=15,mu=55,sigma_L=22,tail_dist=95, band="S", floor=2},
["M903PAC2KAT1 CHM"] ={peak=32,mu=50,sigma_L=20,tail_dist=90, band="S", floor=3},
["TorM2 CHM"] ={peak=28,mu=14,sigma_L=6, tail_dist=30, band="IJ", floor=3},
["TorM2K CHM"] ={peak=28,mu=14,sigma_L=6, tail_dist=30, band="IJ", floor=3},
["TorM2M CHM"] ={peak=26,mu=16,sigma_L=6, tail_dist=32, band="IJ", floor=3},
["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},
["C-RAM CHM"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
["PGZ-09 CHM"] ={peak=22,mu=5, sigma_L=2, tail_dist=12, band="IJ", floor=0},
["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=75, band="IJ", floor=2},
["S350-9M96D CHM"] ={peak=18,mu=35,sigma_L=15,tail_dist=75, band="IJ", floor=2},
["HQ-22 CHM"] ={peak=20,mu=58,sigma_L=22,tail_dist=100,band="S", floor=2},
["LD-3000 CHM"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
["LD-3000M CHM"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",floor=0},
["FlaRakRad CHM"] ={peak=35,mu=5, sigma_L=2, tail_dist=12, band="IJ", floor=3},
["IRIS-T SLM CHM"] ={peak=18,mu=18,sigma_L=8, tail_dist=40, band="IJ", floor=2},
["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=72, band="IJ", floor=2},
["BukM3-9M317MA CHM"] ={peak=22,mu=38,sigma_L=16,tail_dist=72, band="IJ", floor=2},
["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=80,sigma_L=35,tail_dist=120,band="IJ", floor=0},
["LAV-AD CHM"] ={peak=3, mu=3, sigma_L=1, tail_dist=5, band="OPT",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},
["RBS98M CHM"] ={peak=25,mu=12,sigma_L=5, tail_dist=25,band="IJ", floor=3},
["RBS70 CHM"] ={peak=5, mu=4, sigma_L=2, tail_dist=10,band="OPT",floor=0},
["RBS70M CHM"] ={peak=5, mu=4, sigma_L=2, tail_dist=10,band="OPT",floor=0},
["RBS90 CHM"] ={peak=5, mu=4, sigma_L=2, tail_dist=10,band="OPT",floor=0},
["RBS90M CHM"] ={peak=5, mu=4, sigma_L=2, tail_dist=10,band="OPT",floor=0},
["RBS103A CHM"] ={peak=20,mu=55,sigma_L=20,tail_dist=90,band="S", floor=2},
["RBS103B CHM"] ={peak=22,mu=45,sigma_L=18,tail_dist=80,band="S", floor=2},
["RBS103AM CHM"] ={peak=20,mu=55,sigma_L=20,tail_dist=90,band="S", floor=2},
["RBS103BM CHM"] ={peak=22,mu=45,sigma_L=18,tail_dist=80,band="S", floor=2},
["Lvkv9040M CHM"]={peak=15,mu=3, sigma_L=1, tail_dist=8, band="OPT",floor=0},
}
-----------------------------------------------------------------------
-- MANTIS System
-----------------------------------------------------------------------
@@ -908,7 +1057,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 +2282,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 +2856,657 @@ 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 (v8 engine).
-- Includes decoupled bt_mod/range_mod, ±10% jitter, and floor.
-- @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 (v8)
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
-- Stochastic jitter ±10% (v8)
local jitter = 1.0 + (math.random() * 2 - 1) * (self.JammerJitterPercent or 0.10)
raw = raw * jitter
-- Minimum effectiveness floor (residual noise injection, v8)
local eff_floor = (floor or 0) * bm
raw = math.max(eff_floor, raw)
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
+13 -8
View File
@@ -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
+7 -2
View File
@@ -204,7 +204,8 @@ do
-- my_ctld.C130DynamicCargoAutoBuildMergeSeconds = 0 -- Merge window in seconds for C-130 auto-build handoff; set to 0 to disable batching (default).
-- my_ctld.locale = "en" -- Language locale to use.
-- -- Supported: "en" English (default), "de" German, "fr" French, "es" Spanish,
-- -- "pt-br" Brazilian Portuguese, "ru" Russian, "tr" Turkish, "zh-cn" Simplified Chinese (Mainland China), "zh-tw" Traditional Chinese (Taiwan).
-- -- "it" Italian, "pt-br" Brazilian Portuguese, "ru" Russian, "tr" Turkish,
-- -- "zh-cn" Simplified Chinese (Mainland China), "zh-tw" Traditional Chinese (Taiwan).
--
-- ## 2.1 CH-47 Chinook support
--
@@ -1499,7 +1500,7 @@ function CTLD:_InitLocalization()
end
--- [User] Set a resolver used to select a CTLD locale for group-specific menus and messages.
-- Supported locales: "en", "de", "fr", "es", "pt-br", "ru", "tr", "zh-cn", "zh-tw".
-- Supported locales: "en", "de", "fr", "es", "it", "pt-br", "ru", "tr", "zh-cn", "zh-tw".
-- If unset, CTLD keeps using `self.locale` as before.
-- @param #CTLD self
-- @param #function Resolver Function called as `Resolver(self, Group)` and expected to return a locale string.
@@ -1542,6 +1543,10 @@ function CTLD:_GetMenuPluralSuffix(Count, Kind, Group)
return "ов"
end
if locale == "tr" then return "" end
if locale == "it" then
if Kind == "crate" then return count > 1 and "e" or "a" end
if Kind == "set" then return "" end
end
if locale == "de" and Kind == "crate" then return count > 1 and "n" or "" end
return count > 1 and "s" or ""
end
@@ -955,6 +955,265 @@ FR = {
BUILD_YES="SI",
BUILD_NO="NO",
},
IT = {
-- ============================================================
-- Crate / Cargo Loading
-- ============================================================
CRATE_LOADED_GROUNDCREW = "Cassa %s caricata dal personale di terra!",
CRATE_UNLOADED_GROUNDCREW = "Cassa %s scaricata dal personale di terra!",
CRATE_LOADED_ID = "ID cassa %d per %s caricato!",
LOADED_FULL = "Caricato %d %s.",
LOADED_SETS_LEFTOVER = "Caricato %d %s(s), con %d casse rimanenti.",
LOADED_SETS = "Caricato %d %s(s).",
LOADED_PARTIAL = "Caricato solo %d/%d casse di %s.",
LOADED_PARTIAL_LIMIT = "Caricato solo %d/%d casse di %s. Il limite di carico è stato raggiunto.!",
LOADED_BATCH = "Caricato %d %s.",
LOADED_BATCH_PARTIAL = "Alcuni set non sono stati caricati completamente.",
-- ============================================================
-- Dropping / Unloading
-- ============================================================
DROPPED_FULL = "Scaricati %d %s.",
DROPPED_SETS_LEFTOVER = "Sono stati scaricati %d %s(s), con %d casse rimanenti.",
DROPPED_SETS = "Scaricati %d %s(s).",
DROPPED_PARTIAL = "Scaricati %d/%d cassa(e) di %s.",
DROPPED_INTO_ACTION = "Scaricati %s in azione!",
DROPPED_BEACON = "Scaricati %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",
CRATES_POSITIONED = "%d Le casse per %s sono state posizionate vicino a te!",
CRATES_DROPPED = "%d casse per %s sono state rilasciate!",
-- ============================================================
-- Troops
-- ============================================================
BOARDED = "%s a bordo!",
BOARDING = "%s Imbarco!",
TROOPS_RETURNED = "Le truppe sono rientrate alla base!",
TROOPS_LABEL = "Ttuppe",
ENGINEERS_LABEL = "Ingenieri",
-- ============================================================
-- Deployment
-- ============================================================
DEPLOYED_NEAR_YOU = "%s sono stati dispiegati vicino a te!",
UNITS_REMOVED = "%s sono stati rimossi",
-- ============================================================
-- Build / Repair
-- ============================================================
BUILD_STARTED = "Costruzione avviata, pronta tra %d secondi!",
REPAIR_STARTED = "Riparazione avviata utilizzando %s e impiegando %d secondi",
NO_UNIT_TO_REPAIR = "Nessuna unità è abbastanza vicina per essere riparata!",
CANT_REPAIR_WITH = "Non è possibile riparare questa unità con %s",
CRATES_MOVE_BEFORE_BUILD = "*** Le casse devono essere spostate prima della costruzione.!",
-- ============================================================
-- Errors - Chopper / Weight / Capacity
-- ============================================================
CHOPPER_CANNOT_CARRY = "Spiacenti, questo elicottero non può trasportare casse!",
TOO_HEAVY = "Mi dispiace, è troppo pesante da caricare.!",
FULLY_LOADED = "Ci dispiace, abbiamo raggiunto la capacità massima!",
CRAMMED = "Ci dispiace, siamo già pieni.!",
NO_CAPACITY_NOW = "Impossibile caricare altro al momento!",
NO_MORE_CAPACITY = "Non c'è più capacità di carico per le casse!",
CANNOT_LOAD_NONE_OR_FULL = "Impossibile caricare le casse: non ne sono state trovate oppure non c'è più spazio disponibile.",
-- ============================================================
-- Errors - Position
-- ============================================================
NEED_TO_LAND_OR_HOVER_LOAD = "È necessario atterrare o rimanere in posizione per caricare!",
HOVER_OVER_CRATES = "Resta in hover sopra le casse per raccoglierle!",
LAND_OR_HOVER_OVER_CRATES = "Atterra o sorvola le casse per raccoglierle!",
MUST_LAND_OR_HOVER_CRATES = "Devi atterrare o rimanere in volo stazionario per caricare le casse!",
NEED_TO_LAND_BUILD = "Devi atterrare/fermarti per costruire qualcosa, pilota!",
NOT_CLOSE_ENOUGH_LOGISTICS = "Non sei abbastanza vicino a una zona logistica!",
NOT_CLOSE_ENOUGH_DROP = "Non sei abbastanza vicino a una zona di lancio!",
NOT_CLOSE_ENOUGH_ZONE_NM = "Negativo, è necessario essere più vicini di %dnm a una zona!",
CANNOT_BUILD_LOADING_AREA = "Non è possibile costruire in un'area di carico, Pilota!",
-- ============================================================
-- Errors - Doors
-- ============================================================
OPEN_DOORS_LOAD_CARGO = "È necessario aprire la/le porta/e per caricare la merce!",
OPEN_DOORS_LOAD_TROOPS = "È necessario aprire la/le porta/e per caricare le truppe!",
OPEN_DOORS_EXTRACT_TROOPS = "È necessario aprire la/le porta/e per estrarre le truppe!",
OPEN_DOORS_UNLOAD_TROOPS = "Devi aprire la/le porta/e per far scendere le truppe!",
OPEN_DOORS_DROP_CARGO = "È necessario aprire la/le porta/e per scaricare il carico!",
-- ============================================================
-- Errors - Stock / Availability
-- ============================================================
ALL_GONE = "Spiacenti, tutti i %s sono esauriti!",
RAN_OUT_OF = "Ci dispiace, abbiamo finito %s",
CARGO_NOT_AVAILABLE_ZONE = "Il carico richiesto non è disponibile in questa zona!",
ENOUGH_CRATES_NEARBY = "Ci sono già abbastanza casse qui vicino! Occupati prima di quelle!",
NO_CRATES_WITHIN = "Nessuna cassa (caricabile) entro %d metri!",
NO_CRATES_WITHIN_PLAIN = "Niente casse entro %d metri!",
NO_CRATES_IN_RANGE = "Nessuna cassa trovata nel raggio d'azione!",
NO_NAMED_CRATES_IN_RANGE = "Nessuna \"%s\" Casse trovate nel raggio d'azione!",
NO_LOADABLE_CRATES = "Spiacenti, nessuna cassa caricabile nelle vicinanze oppure peso massimo del carico raggiunto!",
NO_UNITS_TO_EXTRACT = "Nessuna unità è abbastanza vicina per l'estrazione!",
NO_UNIT_CONFIG = "Nessuna configurazione dell'unità trovata per %s",
CANT_ONBOARD = "Impossibile salire a bordo %s",
TOO_MANY_UNITS_NEARBY = "Hai già %d unità nelle vicinanze!",
NO_CRATE_GROUPS = "Nessun gruppo di casse trovato per questa unità!",
NO_CRATE_SET = "Nessun set di casse trovato o indice non valido!",
NO_CRATE_IN_SET = "Nessuna cassa trovata in quel set!",
NO_TROOP_CHUNK = "Nessun blocco di carico truppe trovato per l'ID %d!",
TROOP_CHUNK_EMPTY = "Il blocco truppe è vuoto per l'ID %d!",
-- ============================================================
-- Nothing loaded / in stock
-- ============================================================
NOTHING_LOADED = "Nessun caricamento effettuato!\nLimite truppe: %d | Limite casse: %d | Limite peso: %d kg",
NOTHING_LOADED_AIRDROP = "Nessun file caricato o non conforme ai parametri di AirDrop.!",
NOTHING_LOADED_HOVER = "Nessun caricamento effettuato o il puntatore del mouse non si trova all'interno dei parametri!",
NOTHING_IN_STOCK = "Nessun prodotto disponibile!",
NOTHING_TO_PACK = "Nothing to pack at this distance pilot!",
NOTHING_TO_REMOVE = "Niente da rimuovere a questa distanza pilota!",
-- ============================================================
-- Zone / Info
-- ============================================================
ROGER_ZONE = "Roger, %s Zona %s!",
-- ============================================================
-- Report: Hover / Flight Parameters
-- ============================================================
HOVER_PARAMS_METRIC = "Hover parametri (caricamento/rilascio automatico):\n - Altezza minima %dm \n - Altezza massima %dm \n - Velocità massima 2mps \n - Nel parametro: %s",
HOVER_PARAMS_IMPERIAL = "Hover parametri (caricamento/rilascio automatico):\n - Altezza minima %dft \n - Altezza massima %dft \n - Velocità massima 6ftps \n - Nel parametro: %s",
FLIGHT_PARAMS_IMPERIAL = "Flight parameters (airdrop):\n - Altezza minima %dft \n -Altezza massima %dft \n - Nel parametro: %s",
FLIGHT_PARAMS_METRIC = "Flight parameters (airdrop):\n - Altezza minima %dm \n - Altezza massima %dm \n - Nel parametro: %s",
-- ============================================================
-- Report Titles (REPORT:New())
-- ============================================================
REPORT_CRATES_FOUND = "Casse trovate nelle vicinanze:",
REPORT_REMOVING_CRATES = "Rimozione delle casse trovate nelle vicinanze:",
REPORT_TRANSPORT_CHECKOUT = "Documento di verifica del trasporto",
REPORT_INVENTORY = "Documento di inventario",
REPORT_BUILD_CHECKLIST = "Checklist Buildable Crates",
REPORT_REPAIR_CHECKLIST = "Lista di controllo per le riparazioni",
REPORT_BEACONS = "Segnalatori di zona attiva",
-- ============================================================
-- Report Section Headers (report:Add())
-- ============================================================
REPORT_SECTION_TROOPS = " -- TRUPPE --",
REPORT_SECTION_CRATES = " -- CASSE --",
REPORT_SECTION_CRATES_GC = " -- CASSE caricate tramite Ground Crew --",
REPORT_SECTION_NONE = " NESSUNO",
REPORT_SECTION_NONE_ALT = " --- Nessun risultato trovato! ---",
REPORT_SECTION_NONE_REPAIR = " --- Nessun risultato trovato ---",
REPORT_GC_LOADABLE_HINT = "Probabilmente caricabile dal personale di terra (F8)",
REPORT_TOTAL_MASS = "Massa totale: %s kg. Caricabile: %s kg.",
REPORT_TROOPS_CRATES_COUNT = "Truppe: %d(%d), Casse: %d(%d)",
REPORT_TROOPS_CRATETYPES_COUNT = "Truppe: %d, Tipi di casse: %d",
-- ============================================================
-- Report Row Templates (per-item lines in reports)
-- ============================================================
REPORT_ROW_TROOP = "Truppa: %s dimensione %d",
REPORT_ROW_CRATE = "Casse: %s %d/%d",
REPORT_ROW_CRATE_SIZE1 = "Cassa: %s dimensione 1",
REPORT_ROW_GC_CRATE = "GC Cassa caricata: %s dimensione 1",
REPORT_ROW_DROPPED_CRATE = "Cassa scaricata per %s, %dkg",
REPORT_ROW_CRATE_KG = "Cassa per %s, %dkg",
REPORT_ROW_CRATE_REMOVED = "Cassa per %s, %dkg rimossa",
REPORT_ROW_UNIT_STOCK = "Unità: %s | Soldati: %d | Stock: %s",
REPORT_ROW_TYPE_CRATE_STOCK = "Tipo: %s | Casse per set: %d | Stock: %s",
REPORT_ROW_TYPE_STOCK = "Tipo: %s | Stock: %s",
REPORT_ROW_BUILD_CHECK = "Tipo: %s | Richiesto %d | Trovato %d | puo' essere costruita %s",
REPORT_ROW_REPAIR_CHECK = "Tipo: %s | Richiesto %d | Trovato %d | Riparabile %s",
REPORT_ROW_BEACON = " %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",
-- ============================================================
-- Weight / Crate limit tokens
-- ============================================================
WEIGHT_LIMIT = "Limite di peso raggiunto",
CRATE_LIMIT = "Limite di casse raggiunto",
-- ============================================================
-- Menu labels - Top level
-- ============================================================
MENU_CTLD = "CTLD",
MENU_MANAGE_TROOPS = "Gestire le truppe",
MENU_MANAGE_CRATES = "Gestisci casse",
MENU_MANAGE_UNITS = "Gestisci unita'",
-- ============================================================
-- Menu labels - Troops
-- ============================================================
MENU_LOAD_TROOPS = "Carica truppe",
MENU_DROP_TROOPS = "Scarica truppe",
MENU_DROP_ALL_TROOPS = "Scarica tutte le truppe",
MENU_EXTRACT_TROOPS = "Estrai truppe",
MENU_DROP_N_TROOPS = "Scarica (%d) %s",
-- ============================================================
-- Menu labels - Crates: Get
-- ============================================================
MENU_GET_CRATES = "Prendi casse",
MENU_GET = "Prendi",
MENU_GET_AND_LOAD = "Prendi e carica",
MENU_GET_ANYWAY = "Prendi comunque",
MENU_PARTIALLY_LOAD = "Carico parziale",
MENU_OUT_OF_STOCK = "Esaurito",
MENU_TROOP_LIMIT = "Limite truppe raggiunto",
-- ============================================================
-- Menu labels - Crates: Load
-- ============================================================
MENU_LOAD_CRATES = "Carica casse",
MENU_LOAD_ALL = "Carica tutto",
MENU_SHOW_LOADABLE_CRATES = "Mostra casse caricate",
MENU_NO_CRATES_FOUND_RESCAN = "Nessuna cassa trovata! Esegui una nuova scansione.?",
MENU_USE_C130_LOAD = "Utilizzare il sistema di carico C-130",
MENU_LOAD_SINGLE = "Carica",
-- ============================================================
-- Menu labels - Crates: Drop
-- ============================================================
MENU_DROP_CRATES = "Scarica casse",
MENU_DROP_ALL_CRATES = "Scarica tutte le casse",
MENU_DROP = "Scarica",
MENU_DROP_AND_BUILD = "Scarica e costruisci",
MENU_DROP_N_SETS = "Scarica %d Set%s",
MENU_NO_CRATES_TO_DROP = "Nessuna cassa da scaricare!",
-- ============================================================
-- Menu labels - Crates: Build / Repair / Pack / Remove
-- ============================================================
MENU_BUILD_CRATES = "Sballa le casse",
MENU_REPAIR = "Ripara",
MENU_PACK_CRATES = "Casse da imballaggio",
MENU_PACK = "Imballaggio",
MENU_SCAN_PACKABLE_UNITS = "Scansiona le unità imballabili nelle vicinanze",
MENU_NO_PACKABLE_UNITS_FOUND_RESCAN = "Nessuna unità imballabile trovata! Esegui una nuova scansione.?",
MENU_PACK_ALL = "Pacchetto nelle vicinanze",
MENU_PACK_AND_LOAD = "Imballare e caricare",
MENU_PACK_AND_LOAD_ALL = "Imballaggio e carico nelle vicinanze",
MENU_PACK_AND_REMOVE = "Imballare e rimuovere",
MENU_PACK_AND_REMOVE_ALL = "Imballare e rimuovere nelle vicinanze",
MENU_REMOVE_CRATES = "Rimuovere le casse",
MENU_REMOVE_CRATES_NEARBY = "Rimuovere le casse nelle vicinanze",
MENU_LIST_CRATES_NEARBY = "Elenco delle casse nelle vicinanze",
MENU_CRATES_NEEDED = "%d cass%s %s (%dkg)",
MENU_CRATE_SINGLE = "%s (%dkg)",
-- ============================================================
-- Menu labels - Units (C-130)
-- ============================================================
MENU_GET_UNITS = "Ottieni unità",
MENU_REMOVE_UNITS_NEARBY = "Rimuovere le unità vicine",
-- ============================================================
-- Menu labels - Info / Cargo
-- ============================================================
MENU_LIST_BOARDED_CARGO = "Lista del carico a bordo",
MENU_INVENTORY = "Inventario",
MENU_LIST_ZONE_BEACONS = "Elenco dei beacon di zona attivi",
-- ============================================================
-- Menu labels - Smokes / Flares / Beacons
-- ============================================================
MENU_SMOKES_FLARES_BEACONS = "Fumogeni, razzi, segnalatori luminosi",
MENU_SMOKE_ZONES_NEARBY = "Fumogeni nelle vicinanze",
MENU_DROP_SMOKE_NOW = "Scarica fumogeni ora",
MENU_RED_SMOKE = "Fumogeno rosso",
MENU_BLUE_SMOKE = "Fumogeno blu",
MENU_GREEN_SMOKE = "Fumogeno verde",
MENU_ORANGE_SMOKE = "Fumogeno arancione",
MENU_WHITE_SMOKE = "Fumogeno bianco",
MENU_FLARE_ZONES_NEARBY = "Razzi di segnalazione nelle vicinanze",
MENU_FIRE_FLARE_NOW = "Razzi di segnalazione ora",
MENU_DROP_BEACON_NOW = "Scarica segnalatori luminosi",
-- ============================================================
-- Menu labels - Parameters
-- ============================================================
MENU_SHOW_FLIGHT_PARAMS = "Mostra i parametri di volo",
MENU_SHOW_HOVER_PARAMS = "Mostra parametri di hovering",
STOCK_NONE = "Nessuno",
STOCK_UNLIMITED = "Illimitato",
BUILD_YES = "SI",
BUILD_NO = "NO",
},
["PT-BR"] = {
-- ============================================================
-- Carregamento de caixa / carga
+23 -20
View File
@@ -118,21 +118,21 @@ TARS_SESSION.debugunitsearch = false
-- your mission before loading.
--
-- ## Player workflow
-- 1. Spawn into a recon-capable slot and open the **F10 Task TARS** radio menu.
-- 1. Spawn into a recon-capable slot and open the **F10 Task TARS** radio menu.
-- 2. Select **TARS validation** on the ground. The system checks your loadout and
-- reports the platform's altitude band, FOV, and available film.
-- The validation item then disappears once approved.
-- 3. Take off. **Start filming / STB & Resume / Stop filming** appear in the menu.
-- 4. Select **TARS mode : Start filming** to begin recording.
-- 5. Fly over enemy units within the sensor's altitude/attitude envelope.
-- Each detected unit is confirmed in the HUD (`+1 Captured target`).
-- 6. Use **TARS mode : STB & Resume** to pause (e.g. to refuel).
-- Film resumes automatically on the next takeoff if the loadout is still valid.
-- 7. Select **TARS mode : Stop filming** or let the film timer expire.
-- 8. Land at an allied airbase or FARP within `TARS.landingDistance` metres.
-- reports the platform's altitude band, FOV, and available film.
-- The validation item then disappears once approved.
-- 3. Take off. **Start filming / STB & Resume / Stop filming** appear in the menu.
-- 4. Select **TARS mode : Start filming** to begin recording.
-- 5. Fly over enemy units within the sensor's altitude/attitude envelope.
-- Each detected unit is confirmed in the HUD (`+1 Captured target`).
-- 6. Use **TARS mode : STB & Resume** to pause (e.g. to refuel).
-- Film resumes automatically on the next takeoff if the loadout is still valid.
-- 7. Select **TARS mode : Stop filming** or let the film timer expire.
-- 8. Land at an allied airbase or FARP within `TARS.landingDistance` metres.
-- The film controls disappear. After `landingDelay + debriefDelay` seconds the
-- intel marks appear on the F10 map and scoring credits are awarded.
-- After the debrief the **TARS validation** item reappears for the next sortie.
-- intel marks appear on the F10 map and scoring credits are awarded.
-- After the debrief the **TARS validation** item reappears for the next sortie.
--
-- ## Localization
--
@@ -274,7 +274,7 @@ TARS = {}
-- @field #string playerName Player display name.
--- Frozen target snapshot.
-- @type TARS.Snapshot
-- @type TARS.Snapshot #TARS.Snapshot
-- @field Wrapper.Unit#UNIT unit MOOSE UNIT wrapper or STATIC wrapper.
-- @field DCS#Object dcsObj Raw DCS object reference.
-- @field #number category `Object.Category.*` of the detected object.
@@ -294,7 +294,7 @@ TARS = {}
-------------------------------------------------
--- @field #string version
TARS.version = "v2.3.1"
TARS.version = "v2.3.2"
--- Active locale.
-- @field #string locale
@@ -1804,7 +1804,7 @@ function TARS:_OnEventBirth(EventData)
if not unit then return end
local instance = self:GetInstance(unit:GetName())
if instance then instance:Delete() end
local playerName = unit:GetPlayerName()
local playerName = EventData.IniPlayerName --unit:GetPlayerName()
if not playerName then return end
local pName = playerName
timer.scheduleFunction(function()
@@ -1818,6 +1818,7 @@ end
-- @param #TARS self
-- @param Core.Event#EVENTDATA EventData
function TARS:_OnEventEngineStartup(EventData)
if EventData.IniPlayerName == nil then return end
local unit = EventData.IniUnit
if not unit or not unit:GetPlayerName() then return end
local pName = unit:GetPlayerName()
@@ -1835,7 +1836,7 @@ function TARS:_OnEventDead(EventData)
local unit = EventData.IniUnit
if not unit then return end
local name = unit:GetName()
local playerName = unit:GetPlayerName() or unit:GetName()
local playerName = EventData.IniPlayerName --unit:GetPlayerName() or unit:GetName()
if TARS.groundMenus[playerName] then self:RemoveGroundMenu(playerName) end
if self.detectedTargets[name] then
local markID = self.marks.blue[name] or self.marks.red[name]
@@ -1852,7 +1853,7 @@ end
function TARS:_OnEventPlayerLeaveUnit(EventData)
local unit = EventData.IniUnit
if not unit then return end
local playerName = unit:GetPlayerName() or unit:GetName()
local playerName = EventData.IniPlayerName --unit:GetPlayerName() or unit:GetName()
if TARS.groundMenus[playerName] then self:RemoveGroundMenu(playerName) end
end
@@ -1863,11 +1864,12 @@ end
-- @param Core.Event#EVENTDATA EventData
function TARS:_OnEventTakeOff(EventData)
self:T(self.lid.."_OnEventTakeOff")
if EventData.IniPlayerName == nil then return end
local unit = EventData.IniUnit
if not unit then return end
local instance = self:GetInstance(unit:GetName())
local now = timer.getTime()
-- Branch 1: auto-resume after ground STB
if instance and instance.capturing then
if instance.lastTakeoffTime and (now - instance.lastTakeoffTime) < 5 then return end
@@ -1903,7 +1905,8 @@ function TARS:_OnEventTakeOff(EventData)
if instance and instance.lastTakeoffTime and (now - instance.lastTakeoffTime) < 5 then
return
end
local playerName = unit:GetPlayerName() or unit:GetName()
local playerName = EventData.IniPlayerName --unit:GetPlayerName() or unit:GetName()
if playerName == nil then return end
local groundData = TARS.groundMenus[playerName]
if not (groundData and groundData.approved) then return end