diff --git a/DCS_Afgainistan/.buildpath b/DCS_Afgainistan/.buildpath
deleted file mode 100644
index d2d7e4d..0000000
--- a/DCS_Afgainistan/.buildpath
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/DCS_Afgainistan/.project b/DCS_Afgainistan/.project
deleted file mode 100644
index 22c09a2..0000000
--- a/DCS_Afgainistan/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- DCS_Afgainistan
-
-
-
-
-
- org.eclipse.dltk.core.scriptbuilder
-
-
-
-
-
- org.eclipse.ldt.nature
-
-
diff --git a/DCS_Afgainistan/.settings/org.eclipse.ldt.prefs b/DCS_Afgainistan/.settings/org.eclipse.ldt.prefs
deleted file mode 100644
index 3b1203d..0000000
--- a/DCS_Afgainistan/.settings/org.eclipse.ldt.prefs
+++ /dev/null
@@ -1,2 +0,0 @@
-Grammar__default_id=lua-5.1
-eclipse.preferences.version=1
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_CTLD.lua b/DCS_Afgainistan/Insurgent_Sandstorm/Archive/Moose_CTLD.lua
similarity index 100%
rename from DCS_Afgainistan/Insurgent_Sandstorm/Moose_CTLD.lua
rename to DCS_Afgainistan/Insurgent_Sandstorm/Archive/Moose_CTLD.lua
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_DroneOps.lua b/DCS_Afgainistan/Insurgent_Sandstorm/Archive/Moose_DroneOps.lua
similarity index 100%
rename from DCS_Afgainistan/Insurgent_Sandstorm/Moose_DroneOps.lua
rename to DCS_Afgainistan/Insurgent_Sandstorm/Archive/Moose_DroneOps.lua
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_DynamicGroundBattle.lua b/DCS_Afgainistan/Insurgent_Sandstorm/Archive/Moose_DynamicGroundBattle.lua
similarity index 100%
rename from DCS_Afgainistan/Insurgent_Sandstorm/Moose_DynamicGroundBattle.lua
rename to DCS_Afgainistan/Insurgent_Sandstorm/Archive/Moose_DynamicGroundBattle.lua
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_InsurgentSandstorm.lua b/DCS_Afgainistan/Insurgent_Sandstorm/Archive/Moose_InsurgentSandstorm.lua
similarity index 100%
rename from DCS_Afgainistan/Insurgent_Sandstorm/Moose_InsurgentSandstorm.lua
rename to DCS_Afgainistan/Insurgent_Sandstorm/Archive/Moose_InsurgentSandstorm.lua
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_InsurgentSandstorm_Awacs.lua b/DCS_Afgainistan/Insurgent_Sandstorm/Archive/Moose_InsurgentSandstorm_Awacs.lua
similarity index 100%
rename from DCS_Afgainistan/Insurgent_Sandstorm/Moose_InsurgentSandstorm_Awacs.lua
rename to DCS_Afgainistan/Insurgent_Sandstorm/Archive/Moose_InsurgentSandstorm_Awacs.lua
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_Intel.lua b/DCS_Afgainistan/Insurgent_Sandstorm/Archive/Moose_Intel.lua
similarity index 100%
rename from DCS_Afgainistan/Insurgent_Sandstorm/Moose_Intel.lua
rename to DCS_Afgainistan/Insurgent_Sandstorm/Archive/Moose_Intel.lua
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/F99th-Insurgent_Sandstorm_2.0.0.miz b/DCS_Afgainistan/Insurgent_Sandstorm/F99th-Insurgent_Sandstorm_2.0.0.miz
index 5fcb710..541616f 100644
Binary files a/DCS_Afgainistan/Insurgent_Sandstorm/F99th-Insurgent_Sandstorm_2.0.0.miz and b/DCS_Afgainistan/Insurgent_Sandstorm/F99th-Insurgent_Sandstorm_2.0.0.miz differ
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_CTLD_Init_DualCoalitions.lua b/DCS_Afgainistan/Insurgent_Sandstorm/Moose_CTLD_Init_DualCoalitions.lua
index c873ad2..a5058b0 100644
--- a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_CTLD_Init_DualCoalitions.lua
+++ b/DCS_Afgainistan/Insurgent_Sandstorm/Moose_CTLD_Init_DualCoalitions.lua
@@ -63,9 +63,11 @@ local redCfg = {
},
Zones = {
- PickupZones = { { name = 'ReadLoadZone1', flag = 9101, activeWhen = 0 },
- { name = "ReadLoadZone2", flag = 9104, activeWhen = 0 },
- { name = "ReadLoadZone3", flag = 9105, activeWhen = 0 } },
+ PickupZones = { { name = 'RedLoadZone1', flag = 9101, activeWhen = 0 },
+ { name = "RedLoadZone2", flag = 9104, activeWhen = 0 },
+ { name = "RedLoadZone3", flag = 9105, activeWhen = 0 },
+ { name = "RedLoadZone4", flag = 9106, activeWhen = 0 },
+ { name = "RedLoadZone5", flag = 9107, activeWhen = 0 } },
--DropZones = { { name = 'ECHO', flag = 9102, activeWhen = 0 } },
--FOBZones = { { name = 'FOXTROT', flag = 9103, activeWhen = 0 } },
--MASHZones = { { name = 'MASH Bravo', freq = '252.0 AM', radius = 500, flag = 9111, activeWhen = 0 } },
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_DualCoalitionZoneCapture.lua b/DCS_Afgainistan/Insurgent_Sandstorm/Moose_DualCoalitionZoneCapture.lua
index 5f7ee65..663862f 100644
--- a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_DualCoalitionZoneCapture.lua
+++ b/DCS_Afgainistan/Insurgent_Sandstorm/Moose_DualCoalitionZoneCapture.lua
@@ -212,8 +212,8 @@ end
-- ==========================================
-- Storage for all zone capture objects and metadata
-local zoneCaptureObjects = {}
-local zoneNames = {}
+zoneCaptureObjects = {}
+zoneNames = {}
local zoneMetadata = {} -- Stores coalition ownership info
-- Function to initialize all zones from configuration
@@ -1065,7 +1065,9 @@ end
local function SetupZoneStatusCommands()
-- Add F10 radio menu commands for BLUE coalition
if US_CC then
- local USMenu = MENU_COALITION:New( coalition.side.BLUE, "Zone Control" )
+ -- Use MenuManager to create zone control menu under Mission Options
+ local USMenu = MenuManager and MenuManager.CreateCoalitionMenu(coalition.side.BLUE, "Zone Control")
+ or MENU_COALITION:New( coalition.side.BLUE, "Zone Control" )
MENU_COALITION_COMMAND:New( coalition.side.BLUE, "Get Zone Status Report", USMenu, BroadcastZoneStatus )
MENU_COALITION_COMMAND:New( coalition.side.BLUE, "Check Victory Progress", USMenu, function()
@@ -1097,7 +1099,9 @@ local function SetupZoneStatusCommands()
-- Add F10 radio menu commands for RED coalition
if RU_CC then
- local RUMenu = MENU_COALITION:New( coalition.side.RED, "Zone Control" )
+ -- Use MenuManager to create zone control menu under Mission Options
+ local RUMenu = MenuManager and MenuManager.CreateCoalitionMenu(coalition.side.RED, "Zone Control")
+ or MENU_COALITION:New( coalition.side.RED, "Zone Control" )
MENU_COALITION_COMMAND:New( coalition.side.RED, "Get Zone Status Report", RUMenu, BroadcastZoneStatus )
MENU_COALITION_COMMAND:New( coalition.side.RED, "Check Victory Progress", RUMenu, function()
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_InsurgentSandstorm2.lua b/DCS_Afgainistan/Insurgent_Sandstorm/Moose_InsurgentSandstorm2.lua
index 387ea22..26337a4 100644
--- a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_InsurgentSandstorm2.lua
+++ b/DCS_Afgainistan/Insurgent_Sandstorm/Moose_InsurgentSandstorm2.lua
@@ -1,34 +1,36 @@
+-- Disable MOOSE's automatic F10 menus
+_SETTINGS:SetPlayerMenuOff() -- Disables the "Settings" F10 menu
+-- Note: MOOSE does not add scoring menus by default unless SCORING objects are created
+
local ENABLE_SAMS = true -- used for testing purposes. Set to true to enable SAMs, false to disable.
-local TAC_DISPLAY = false -- Set to false to disable Tacview display for AI flights (default = false)
--- How many red/blue aircraft are in the air by default.
-local RedA2ADefaultOverhead = 1.5
-local RedDefaultCAP = 1
-local BlueA2ADefaultOverhead = 1.5
-local BlueDefaultCAP = 1
+-- Build Command Center and Mission for Blue Coalition
+local blueHQ = GROUP:FindByName("BLUEHQ")
+if blueHQ then
+ US_CC = COMMANDCENTER:New(blueHQ, "USA HQ")
+ US_Mission = MISSION:New(US_CC, "Insurgent Sandstorm", "Primary", "", coalition.side.BLUE)
+ US_Mission:GetCommandCenter():SetMenu() -- Disable mission F10 menu
+ --US_Score = SCORING:New("Operation Polar Hammer") -- Commented out to prevent Scoring F10 menu
+ --US_Mission:AddScoring(US_Score)
+ --US_Mission:Start()
+ env.info("Blue Coalition Command Center and Mission started successfully")
+else
+ env.info("ERROR: BLUEHQ group not found! Blue mission will not start.")
+end
--- Create the main mission menu.
-missionMenu = MENU_MISSION:New("Mission Menu")
-
---Build Command Center and Mission for Blue
-US_CC = COMMANDCENTER:New( GROUP:FindByName( "BLUEHQ" ), "USA HQ" )
-US_Mission = MISSION:New( US_CC, "Insurgent Sandstorm", "Primary", "Clear the front lines of enemy activity.", coalition.side.BLUE)
-US_Score = SCORING:New( "Insurgent Sandstorm - Blue" )
-US_Mission:AddScoring( US_Score )
-US_Mission:Start()
-US_Score:SetMessagesHit(false)
-US_Score:SetMessagesDestroy(false)
-US_Score:SetMessagesScore(false)
-
--Build Command Center and Mission Red
-RU_CC = COMMANDCENTER:New( GROUP:FindByName( "REDHQ" ), "Russia HQ" )
-RU_Mission = MISSION:New (RU_CC, "Insurgent Sandstorm", "Primary", "Destroy U.S. and NATO forces.", coalition.side.RED)
-RU_Score = SCORING:New("Insurgent Sandstorm - Red")
-RU_Mission:AddScoring( RU_Score)
-RU_Mission:Start()
-RU_Score:SetMessagesHit(false)
-RU_Score:SetMessagesDestroy(false)
-RU_Score:SetMessagesScore(false)
+local redHQ = GROUP:FindByName("REDHQ")
+if redHQ then
+ RU_CC = COMMANDCENTER:New(redHQ, "Russia HQ")
+ RU_Mission = MISSION:New(RU_CC, "Insurgent Sandstorm", "Primary", "Hold what we have, take what we don't.", coalition.side.RED)
+ RU_Mission:GetCommandCenter():SetMenu() -- Disable mission F10 menu
+ --RU_Score = SCORING:New("Operation Polar Shield") -- Commented out to prevent Scoring F10 menu
+ --RU_Mission:AddScoring(RU_Score)
+ RU_Mission:Start()
+ env.info("Red Coalition Command Center and Mission started successfully")
+else
+ env.info("ERROR: REDHQ group not found! Red mission will not start.")
+end
------------------------------------------------------------------------------------------------------------------------------------------------------
-- Setup SAM Systems
@@ -116,59 +118,7 @@ end
------------------------------------------------------------------------------------------------------------------------------------------------------
-- Setup Air Dispatchers for RED and BLUE
------------------------------------------------------------------------------------------------------------------------------------------------------
-
-BLUEBorderZone = ZONE_POLYGON:New( "BLUE BORDER", GROUP:FindByName( "BLUE BORDER" ) )
-BLUEA2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "BLUE EWR" }, { "FIGHTER SWEEP BLUE" }, 'BLUE BORDER', 'BLUE BORDER', BlueDefaultCAP, 10000, 50000, 75000, 100)
-BLUEA2ADispatcher:SetDefaultLandingAtRunway()
-BLUEA2ADispatcher:SetDefaultTakeoffInAir()
-BLUEA2ADispatcher:SetTacticalDisplay(TAC_DISPLAY)
-BLUEA2ADispatcher:SetDefaultFuelThreshold( 0.20 )
-BLUEA2ADispatcher:SetRefreshTimeInterval( 300 )
-BLUEA2ADispatcher:SetDefaultOverhead(BlueA2ADefaultOverhead)
-BLUEA2ADispatcher:SetDisengageRadius( 100000 )
-BLUEA2ADispatcher:SetEngageRadius( 50000 )
-BLUEA2ADispatcher:SetGciRadius( 75000 )
-
-CCCPBorderZone = ZONE_POLYGON:New( "RED BORDER", GROUP:FindByName( "RED BORDER" ) )
-RedA2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "RED EWR" }, { "FIGHTER SWEEP RED" }, "RED BORDER", "RED BORDER", RedDefaultCAP, 10000, 50000, 75000, 100)
-RedA2ADispatcher:SetDefaultLandingAtRunway()
-RedA2ADispatcher:SetDefaultTakeoffInAir()
-RedA2ADispatcher:SetTacticalDisplay(TAC_DISPLAY)
-RedA2ADispatcher:SetDefaultFuelThreshold( 0.20 )
-RedA2ADispatcher:SetRefreshTimeInterval( 300 )
-RedA2ADispatcher:SetDefaultOverhead(RedA2ADefaultOverhead)
-RedA2ADispatcher:SetDisengageRadius( 100000 )
-RedA2ADispatcher:SetEngageRadius( 50000 )
-RedA2ADispatcher:SetGciRadius( 75000 )
-
-
-
-DwyerBorderZone = ZONE_POLYGON:New( "DwyerBorderZone", GROUP:FindByName( "DwyerBorderZone" ) )
-DwyerDispatcher = AI_A2A_GCICAP:NewWithBorder( { "RED EWR" }, { "DwyerBorderCAP" }, "DwyerBorderZone", "DwyerBorderZone", RedDefaultCAP, 10000, 50000, 75000, 100)
-DwyerDispatcher:SetDefaultLandingAtRunway()
-DwyerDispatcher:SetDefaultTakeoffInAir()
-DwyerDispatcher:SetBorderZone( DwyerBorderZone )
-DwyerDispatcher:SetTacticalDisplay(TAC_DISPLAY)
-DwyerDispatcher:SetDefaultFuelThreshold( 0.20 )
-DwyerDispatcher:SetRefreshTimeInterval( 300 )
-DwyerDispatcher:SetDefaultOverhead(RedA2ADefaultOverhead)
-DwyerDispatcher:SetDisengageRadius( 100000 )
-DwyerDispatcher:SetEngageRadius( 50000 )
-DwyerDispatcher:SetGciRadius( 75000 )
-
-
-BostZone = ZONE_POLYGON:New( "BostBorderZone", GROUP:FindByName( "BostBorderZone" ) )
-BostDispatcher = AI_A2A_GCICAP:NewWithBorder( { "RED EWR" }, { "BostBorderCAP" }, "BostBorderZone", "BostBorderZone", RedDefaultCAP, 10000, 50000, 75000, 100)
-BostDispatcher:SetDefaultLandingAtRunway()
-BostDispatcher:SetDefaultTakeoffInAir()
-BostDispatcher:SetBorderZone(BostZone)
-BostDispatcher:SetTacticalDisplay(TAC_DISPLAY)
-BostDispatcher:SetDefaultFuelThreshold( 0.20 )
-BostDispatcher:SetRefreshTimeInterval( 300 )
-BostDispatcher:SetDefaultOverhead(RedA2ADefaultOverhead)
-BostDispatcher:SetDisengageRadius( 100000 )
-BostDispatcher:SetEngageRadius( 50000 )
-BostDispatcher:SetGciRadius( 75000 )
+--now handled by TADC system.
------------------------------------------------------------------------------------------------------------------------------------------------------
-- Clean up the airbases of debris and stuck aircraft.
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_TADC_CargoDispatcher.lua b/DCS_Afgainistan/Insurgent_Sandstorm/Moose_TADC_CargoDispatcher.lua
index 96a149c..334960a 100644
--- a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_TADC_CargoDispatcher.lua
+++ b/DCS_Afgainistan/Insurgent_Sandstorm/Moose_TADC_CargoDispatcher.lua
@@ -56,12 +56,12 @@ end
local CARGO_SUPPLY_CONFIG = {
red = {
supplyAirfields = { "Farah", "Nimroz", "Herat", "Shindand" }, -- replace with your RED supply airbase names
- cargoTemplate = "CARGO_RED_AN26", -- replace with your RED cargo aircraft template name
+ cargoTemplate = "CARGO_IL-76MD", -- replace with your RED cargo aircraft template name
threshold = 0.90 -- ratio below which to trigger resupply (testing)
},
blue = {
supplyAirfields = { "Sharana", "Tarinkot" }, -- replace with your BLUE supply airbase names
- cargoTemplate = "CARGO_BLUE_C130", -- replace with your BLUE cargo aircraft template name
+ cargoTemplate = "CARGO_C-130", -- replace with your BLUE cargo aircraft template name
threshold = 0.90 -- ratio below which to trigger resupply (testing)
}
}
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_TADC_SquadronConfigs_Load1st.lua b/DCS_Afgainistan/Insurgent_Sandstorm/Moose_TADC_SquadronConfigs_Load1st.lua
index e07df13..03761cd 100644
--- a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_TADC_SquadronConfigs_Load1st.lua
+++ b/DCS_Afgainistan/Insurgent_Sandstorm/Moose_TADC_SquadronConfigs_Load1st.lua
@@ -90,8 +90,8 @@ RED_SQUADRON_CONFIG = {
type = "FIGHTER",
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
- primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
- secondaryZone = nil, -- Secondary coverage area (zone name)
+ primaryZone = "BostBorderZone", -- Main responsibility area (zone name from mission editor)
+ secondaryZone = "DwyerBorderZone", -- Secondary coverage area (zone name)
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
@@ -118,8 +118,8 @@ RED_SQUADRON_CONFIG = {
type = "FIGHTER",
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
- primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
- secondaryZone = nil, -- Secondary coverage area (zone name)
+ primaryZone = "DwyerBorderZone", -- Main responsibility area (zone name from mission editor)
+ secondaryZone = "BostBorderZone", -- Secondary coverage area (zone name)
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
@@ -146,8 +146,8 @@ RED_SQUADRON_CONFIG = {
type = "FIGHTER",
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
- primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
- secondaryZone = nil, -- Secondary coverage area (zone name)
+ primaryZone = "DwyerBorderZone", -- Main responsibility area (zone name from mission editor)
+ secondaryZone = "BostBorderZone", -- Secondary coverage area (zone name)
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
@@ -173,8 +173,8 @@ RED_SQUADRON_CONFIG = {
type = "FIGHTER",
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
- primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
- secondaryZone = nil, -- Secondary coverage area (zone name)
+ primaryZone = "DwyerBorderZone", -- Main responsibility area (zone name from mission editor)
+ secondaryZone = "BostBorderZone", -- Secondary coverage area (zone name)
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
@@ -199,9 +199,9 @@ RED_SQUADRON_CONFIG = {
type = "FIGHTER",
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
- primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
- secondaryZone = nil, -- Secondary coverage area (zone name)
- tertiaryZone = nil, -- Emergency/fallback zone (zone name)
+ primaryZone = "BATTLE GROUND", -- Main responsibility area (zone name from mission editor)
+ secondaryZone = "DwyerBorderZone", -- Secondary coverage area (zone name)
+ tertiaryZone = "BostBorderZone", -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
zoneConfig = {
@@ -225,9 +225,9 @@ RED_SQUADRON_CONFIG = {
type = "FIGHTER",
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
- primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
- secondaryZone = nil, -- Secondary coverage area (zone name)
- tertiaryZone = nil, -- Emergency/fallback zone (zone name)
+ primaryZone = "BATTLE GROUND", -- Main responsibility area (zone name from mission editor)
+ secondaryZone = "DwyerBorderZone", -- Secondary coverage area (zone name)
+ tertiaryZone = "BostBorderZone", -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
zoneConfig = {
@@ -251,9 +251,9 @@ RED_SQUADRON_CONFIG = {
type = "FIGHTER",
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
- primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
- secondaryZone = nil, -- Secondary coverage area (zone name)
- tertiaryZone = nil, -- Emergency/fallback zone (zone name)
+ primaryZone = "BATTLE GROUND", -- Main responsibility area (zone name from mission editor)
+ secondaryZone = "DwyerBorderZone", -- Secondary coverage area (zone name)
+ tertiaryZone = "BostBorderZone", -- Emergency/fallback zone (zone name)
-- Zone behavior settings (optional - uses defaults if not specified)
zoneConfig = {
@@ -266,6 +266,112 @@ RED_SQUADRON_CONFIG = {
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
}
},
+ { templateName = "Bost MiG-21", -- Change to your RED template name
+ displayName = "Bost MiG-21", -- Change to your preferred name
+ airbaseName = "Bost", -- Change to your RED airbase
+ aircraft = 14, -- Adjust aircraft count
+ skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT
+ altitude = 20000, -- Patrol altitude (feet)
+ speed = 350, -- Patrol speed (knots)
+ patrolTime = 25, -- Time on station (minutes)
+ type = "FIGHTER",
+
+ -- Zone-based Areas of Responsibility (optional - leave nil for global response)
+ primaryZone = "BATTLE GROUND", -- Main responsibility area (zone name from mission editor)
+ secondaryZone = "BostBorderZone", -- Secondary coverage area (zone name)
+ tertiaryZone = "DwyerBorderZone", -- Emergency/fallback zone (zone name)
+
+ -- Zone behavior settings (optional - uses defaults if not specified)
+ zoneConfig = {
+ primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
+ secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
+ tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
+ maxRange = 200, -- Maximum engagement range from airbase (nm)
+ enableFallback = false, -- Auto-switch to tertiary when base threatened
+ priorityThreshold = 4, -- Min aircraft count for "major threat"
+ ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
+ }
+ },
+ { templateName = "Bost Su-27", -- Change to your RED template name
+ displayName = "Bost Su-27", -- Change to your preferred name
+ airbaseName = "Bost", -- Change to your RED airbase
+ aircraft = 12, -- Adjust aircraft count
+ skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT
+ altitude = 20000, -- Patrol altitude (feet)
+ speed = 350, -- Patrol speed (knots)
+ patrolTime = 25, -- Time on station (minutes)
+ type = "FIGHTER",
+
+ -- Zone-based Areas of Responsibility (optional - leave nil for global response)
+ primaryZone = "BATTLE GROUND", -- Main responsibility area (zone name from mission editor)
+ secondaryZone = "BostBorderZone", -- Secondary coverage area (zone name)
+ tertiaryZone = "DwyerBorderZone", -- Emergency/fallback zone (zone name)
+
+ -- Zone behavior settings (optional - uses defaults if not specified)
+ zoneConfig = {
+ primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
+ secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
+ tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
+ maxRange = 200, -- Maximum engagement range from airbase (nm)
+ enableFallback = false, -- Auto-switch to tertiary when base threatened
+ priorityThreshold = 4, -- Min aircraft count for "major threat"
+ ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
+ }
+ },
+ { templateName = "Dwyer MiG-23MLD", -- Change to your RED template name
+ displayName = "Dwyer MiG-23MLD", -- Change to your preferred name
+ airbaseName = "Dwyer", -- Change to your RED airbase
+ aircraft = 12, -- Adjust aircraft count
+ skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT
+ altitude = 20000, -- Patrol altitude (feet)
+ speed = 350, -- Patrol speed (knots)
+ patrolTime = 25, -- Time on station (minutes)
+ type = "FIGHTER",
+
+ -- Zone-based Areas of Responsibility (optional - leave nil for global response)
+ primaryZone = "BATTLE GROUND", -- Main responsibility area (zone name from mission editor)
+ secondaryZone = "BostBorderZone", -- Secondary coverage area (zone name)
+ tertiaryZone = "DwyerBorderZone", -- Emergency/fallback zone (zone name)
+
+ -- Zone behavior settings (optional - uses defaults if not specified)
+ zoneConfig = {
+ primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
+ secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
+ tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
+ maxRange = 200, -- Maximum engagement range from airbase (nm)
+ enableFallback = false, -- Auto-switch to tertiary when base threatened
+ priorityThreshold = 4, -- Min aircraft count for "major threat"
+ ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
+ }
+
+ },
+ {
+ templateName = "Dwyer Su-27", -- Change to your RED template name
+ displayName = "Dwyer Su-27", -- Change to your preferred name
+ airbaseName = "Dwyer", -- Change to your RED airbase
+ aircraft = 12, -- Adjust aircraft count
+ skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT
+ altitude = 20000, -- Patrol altitude (feet)
+ speed = 350, -- Patrol speed (knots)
+ patrolTime = 25, -- Time on station (minutes)
+ type = "FIGHTER",
+
+ -- Zone-based Areas of Responsibility (optional - leave nil for global response)
+ primaryZone = "BATTLE GROUND", -- Main responsibility area (zone name from mission editor)
+ secondaryZone = "BostBorderZone", -- Secondary coverage area (zone name)
+ tertiaryZone = "DwyerBorderZone", -- Emergency/fallback zone (zone name)
+
+ -- Zone behavior settings (optional - uses defaults if not specified)
+ zoneConfig = {
+ primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
+ secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
+ tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
+ maxRange = 200, -- Maximum engagement range from airbase (nm)
+ enableFallback = false, -- Auto-switch to tertiary when base threatened
+ priorityThreshold = 4, -- Min aircraft count for "major threat"
+ ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
+ }
+ },
}
-- ═══════════════════════════════════════════════════════════════════════════
@@ -369,6 +475,6 @@ BLUE_SQUADRON_CONFIG = {
priorityThreshold = 4, -- Min aircraft count for "major threat"
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
}
+ },
}
-
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_Tanker.lua b/DCS_Afgainistan/Insurgent_Sandstorm/Moose_Tanker.lua
index c7fd286..85baf7f 100644
--- a/DCS_Afgainistan/Insurgent_Sandstorm/Moose_Tanker.lua
+++ b/DCS_Afgainistan/Insurgent_Sandstorm/Moose_Tanker.lua
@@ -1,55 +1,2493 @@
--- Define the event handler class for Blue TANKER
+-- ============================================================================
+-- MOOSE TANKER MANAGEMENT SYSTEM
+-- Comprehensive tanker lifecycle management with auto-respawn, fuel monitoring,
+-- TACAN/frequency announcements, and menu controls
+-- ============================================================================
+
+-- ============================================================================
+-- USER CONFIGURATION
+-- ============================================================================
+
+-- Tanker Configuration
+local TANKER_CONFIG = {
+ KC135 = {
+ groupName = "TANKER 135",
+ unitName = "TANKER 135-1",
+ displayName = "TANKER KC-135",
+ aircraftType = "KC-135", -- DCS aircraft type name
+ livery = nil, -- nil for default, or livery_id string
+ callsign = "SHELL", -- Map marker prefix for custom routes
+ tacan = "50X", -- Set to match ME or nil if none
+ frequency = "251.000", -- Set to match ME or nil if none
+ respawnDelay = 180, -- seconds before auto-respawn after destruction
+ emergencyRespawnDelay = 60, -- Emergency spawn delay
+ fuelWarningPercent = 25, -- Warn when fuel drops below this %
+ fuelBingoPercent = 15, -- RTB fuel level
+ defaultAltitude = 22000, -- Default altitude in feet (FL220)
+ defaultSpeed = 330, -- Default speed in knots
+ },
+ KC135_MPRS = {
+ groupName = "TANKER 135 MPRS",
+ unitName = "TANKER 135 MPRS-1",
+ displayName = "TANKER KC-135 MPRS",
+ aircraftType = "KC135MPRS", -- DCS aircraft type name
+ livery = nil,
+ callsign = "ARCO", -- Map marker prefix for custom routes
+ tacan = "51X",
+ frequency = "252.000",
+ respawnDelay = 180,
+ emergencyRespawnDelay = 60,
+ fuelWarningPercent = 25,
+ fuelBingoPercent = 15,
+ defaultAltitude = 22000,
+ defaultSpeed = 330,
+ }
+}
+
+-- Custom Route Configuration
+local ROUTE_CONFIG = {
+ minWaypoints = 2, -- Minimum waypoints required
+ maxWaypoints = 10, -- Maximum waypoints allowed
+ deleteMarkersAfterUse = true, -- Delete markers after route creation
+ waypointPrefix = { -- Recognized marker prefixes
+ SHELL = "KC135", -- SHELL1, SHELL2, etc. → KC-135
+ ARCO = "KC135_MPRS", -- ARCO1, ARCO2, etc. → KC-135 MPRS
+ }
+}
+
+-- Monitoring Configuration
+local FUEL_CHECK_INTERVAL = 60 -- Check fuel every 60 seconds
+local DAMAGE_RTB_THRESHOLD = 50 -- RTB if hull damage exceeds this %
+
+-- Default Spawn Location (for non-custom route spawns)
+-- Note: Using lat/lon with SetAltitude to ensure proper altitude MSL
+local DEFAULT_SPAWN_COORD = COORDINATE:NewFromLLDD(34.564, 69.212):SetAltitude(22000 * 0.3048, true) -- Kabul area, FL220
+
+-- ============================================================================
+-- GLOBAL STATE TRACKING
+-- ============================================================================
+
+TANKER_STATE = {
+ KC135 = {
+ active = false,
+ group = nil,
+ fuelWarned = false,
+ bingoWarned = false,
+ respawnScheduler = nil,
+ fuelMonitor = nil,
+ },
+ KC135_MPRS = {
+ active = false,
+ group = nil,
+ fuelWarned = false,
+ bingoWarned = false,
+ respawnScheduler = nil,
+ fuelMonitor = nil,
+ }
+}
+
+-- ============================================================================
+-- MENU REFERENCES (for enable/disable)
+-- ============================================================================
+
+local MENU_TANKER_ROOT = nil
+local MENU_KC135_LAUNCH = nil
+local MENU_KC135_MPRS_LAUNCH = nil
+
+-- ============================================================================
+-- MESSAGE POOLS FOR VARIETY
+-- Randomized messages provide immersive variety across tanker operations.
+-- Each category contains 100 variations selected randomly via GetRandomMessage()
+-- ============================================================================
+
+local TANKER_MESSAGES = {
+ -- Spawn Confirmation (success)
+ SPAWN_SUCCESS = {
+ "%s is airborne and ready for refueling operations.",
+ "%s has launched and is standing by for fuel.",
+ "%s is now on station and ready to pump gas.",
+ "%s has departed and is available for refueling.",
+ "%s is up and ready to service aircraft.",
+ "%s is airborne. Refueling services now available.",
+ "%s has checked in on station.",
+ "%s is overhead and ready for business.",
+ "%s is now available for aerial refueling.",
+ "%s has arrived on station. Ready to refuel.",
+ "%s is up! Time to get your drink on.",
+ "%s has joined the party. Bring your cups!",
+ "%s reporting. The bar is now open.",
+ "%s is flying. Get in line for your juice.",
+ "%s on station. Don't be shy, we got plenty.",
+ "%s airborne. Unlike Mo's last attempt at flying.",
+ "%s has successfully launched. No thanks to Mo.",
+ "%s is ready. Mo said he could do this but we know better.",
+ "%s in position. Fuel truck of the sky is open for business!",
+ "%s has arrived fashionably late but ready to pump.",
+ "%s checking in. Your gas station with wings is here.",
+ "%s is up there doing tanker things.",
+ "%s launched without hitting anything. Good start!",
+ "%s airborne and hasn't broken anything yet.",
+ "%s is ready to make it rain... JP-8.",
+ "%s in the pattern. Come get some dinosaur juice!",
+ "%s reporting for duty. Time to feed some thirsty birds.",
+ "%s has spawned successfully. Mo's jealous.",
+ "%s is flying high and ready to share the wealth.",
+ "%s on station. Dispensary is OPEN.",
+ "%s has graced you with its presence. You're welcome.",
+ "%s is here to save your ass from flameout.",
+ "%s launched. The sky gas station is open 24/7.",
+ "%s airborne. Better than Mo's last tanker spawn attempt.",
+ "%s ready to refuel. Unlike your love life, this actually works.",
+ "%s has arrived to keep you from embarrassing yourself.",
+ "%s on station and totally not judging your fuel planning.",
+ "%s is up. Try not to break the boom this time.",
+ "%s launched successfully. Mo couldn't get his off the ground.",
+ "%s airborne. Your aerial bartender has arrived!",
+ "%s ready for action. The juice is loose!",
+ "%s has spawned. Time to get wet... with fuel.",
+ "%s on station. We promise not to tell anyone you needed us.",
+ "%s reporting. Because someone forgot to fuel before takeoff.",
+ "%s is here! The flying fuel truck has arrived!",
+ "%s airborne and ready to fill your tanks. That's what she said.",
+ "%s launched. Even Mo could refuel from this... maybe.",
+ "%s on station. Your poor planning is our opportunity!",
+ "%s has arrived. The aerial milk truck is ready.",
+ "%s ready to pump. Get your minds out of the gutter.",
+ "%s airborne because you can't manage fuel apparently.",
+ "%s is up there waiting. Don't keep us hovering forever.",
+ "%s has joined the fight. By 'fight' we mean 'hovering lazily.'",
+ "%s on station. Premium unleaded is on tap!",
+ "%s launched and looking sexy up here.",
+ "%s ready to refuel. Try not to scratch the paint this time.",
+ "%s has arrived. Mo said this was impossible but here we are.",
+ "%s airborne. The sky's full service station is open!",
+ "%s on station ready to save your bacon.",
+ "%s has launched into the wild blue yonder!",
+ "%s reporting for gas pumping duty.",
+ "%s is up and Mo isn't. Winner: us.",
+ "%s airborne. Fuel flows like wine at a wedding!",
+ "%s on station. Unlike Mo, we actually showed up.",
+ "%s ready to top you off. No phrasing.",
+ "%s has successfully taken off. Mo's still taxiing.",
+ "%s airborne and operational. The real MVP.",
+ "%s on station doing the lord's work.",
+ "%s has arrived to prevent your walk of shame.",
+ "%s ready for refueling ops. Try to connect this time.",
+ "%s launched because someone has to be the adult here.",
+ "%s airborne. Probably more reliable than your ex.",
+ "%s on station ready to give you the good stuff.",
+ "%s has spawned. Mo's tanker is still in the hangar.",
+ "%s reporting. Your airborne gas station awaits!",
+ "%s is up! Time for some hot refueling action.",
+ "%s airborne and Mo's not invited to this party.",
+ "%s on station. We have fuel, you have need. Let's dance.",
+ "%s ready to dispense freedom molecules!",
+ "%s has arrived to fix your fuel management issues.",
+ "%s launched. The flying gas can is ready for customers.",
+ "%s airborne because apparently nobody can calculate bingo.",
+ "%s on station. Come get your fix!",
+ "%s ready to pump premium into your thirsty bird.",
+ "%s has spawned successfully. Suck it, Mo.",
+ "%s reporting. Your aerial enabler is on station.",
+ "%s is up there waiting like a patient parent.",
+ "%s airborne and ready to make your fuel gauge happy.",
+ "%s on station. Mo said we couldn't do it. We did it.",
+ "%s launched with more grace than Mo's last landing.",
+ "%s ready for business. The boom is ready to boom.",
+ "%s has arrived fashionably and ready to serve.",
+ "%s airborne. Your fuel problems are about to be solved!",
+ "%s on station doing God's work up here.",
+ "%s ready to refuel. We got the good stuff.",
+ "%s has spawned. Time to feed the hungry jets!",
+ "%s reporting for duty with full tanks!",
+ "%s launched successfully without Mo's help, thank God.",
+ "%s airborne. The flying filling station is OPEN!",
+ },
+
+ -- Already Active Warning
+ ALREADY_ACTIVE = {
+ "%s is already airborne!",
+ "%s is currently active.",
+ "%s is already on station.",
+ "%s is already flying. Check status for details.",
+ "%s is already up - can't spawn another.",
+ "%s is currently operating.",
+ "Cannot spawn - %s already active.",
+ "%s is already out there!",
+ "%s already flying. One at a time, please.",
+ "%s is already working the pattern.",
+ "%s is already up there, genius.",
+ "Dude, %s is ALREADY flying. Pay attention.",
+ "%s is currently active. Are you even looking?",
+ "Hey Einstein, %s is already airborne!",
+ "%s is up there right now. Use your eyes.",
+ "What part of '%s is active' don't you understand?",
+ "%s is already flying. Did Mo program this button?",
+ "Seriously? %s is already up. Check your radar.",
+ "%s is currently operational. Nice try though.",
+ "Negative. %s is already in the air.",
+ "%s is already active. Mo would have known that.",
+ "Can't spawn two, buttercup. %s is already flying.",
+ "%s is already out there doing tanker things.",
+ "Nice try. %s is already airborne, hotshot.",
+ "%s is currently flying. One's enough.",
+ "Hold your horses! %s is already active.",
+ "%s is already up. We're not running a bus service here.",
+ "Bruh. %s is already flying.",
+ "%s is currently active. Maybe learn to read?",
+ "Negative ghostrider. %s is already up.",
+ "%s is already airborne. Unlike your awareness.",
+ "Um, %s is ALREADY flying. Hello?",
+ "%s is currently on station. Wake up.",
+ "You can't spawn %s twice. Physics doesn't work that way.",
+ "%s is already active. Not sure what you expected.",
+ "Denied! %s is already in the pattern.",
+ "%s is currently flying around. Look outside.",
+ "News flash: %s is already airborne!",
+ "%s is already up there. Mo makes better decisions than this.",
+ "Request denied. %s is already active, chief.",
+ "%s is currently operational. Check your instruments.",
+ "Already got one! %s is flying right now.",
+ "%s is already airborne. Reading is fundamental.",
+ "Uh, no. %s is currently active.",
+ "%s is already out there. Situational awareness: zero.",
+ "Can't spawn %s again. Not a video game, buddy.",
+ "%s is currently flying. We only get one.",
+ "That's a negative. %s is already up.",
+ "%s is already on station. Did you even check?",
+ "Seriously? %s has been flying for 20 minutes.",
+ "%s is already active. This isn't rocket science.",
+ "Request rejected. %s is currently airborne.",
+ "%s is already up there pumping gas. Pay attention!",
+ "Nope. %s is already flying. Check the status board.",
+ "%s is currently active. Even Mo knew this.",
+ "Cannot comply. %s is already operational.",
+ "%s is already airborne. Try the status menu next time.",
+ "That's a no-go. %s is currently flying.",
+ "%s is already up. Did you think we had two?",
+ "Denied. %s is already on station doing its thing.",
+ "%s is currently active. Surprised you didn't notice.",
+ "Can't do it. %s is already flying around up there.",
+ "%s is already operational. One tanker at a time, pal.",
+ "Negative. %s has been active for a while now.",
+ "%s is already up there. Spawn button isn't a toy.",
+ "Request denied. %s is currently on station.",
+ "%s is already flying. Not cloning aircraft today.",
+ "Can't spawn another. %s is already airborne.",
+ "%s is currently active. Stop button mashing.",
+ "That's not happening. %s is already up.",
+ "%s is already operational. One's all you get.",
+ "No can do. %s is already in the pattern.",
+ "%s is currently flying. Check before clicking, maybe?",
+ "Request rejected. %s is already on duty.",
+ "%s is already airborne. Unlike your attention span.",
+ "Nope! %s is currently active and doing fine.",
+ "%s is already up there. Stop spamming the spawn button.",
+ "Cannot spawn duplicate. %s is already flying.",
+ "%s is currently operational. Mo's spawn would work better.",
+ "Denied! %s is already on station, genius.",
+ "%s is already flying. Check your tanker status!",
+ "That's a negative. %s is currently active.",
+ "%s is already airborne. One tanker per customer.",
+ "Can't do that. %s is already up and working.",
+ "%s is currently on station. Read the room.",
+ "Request denied. %s is already operational, chief.",
+ "%s is already active. Try paying attention.",
+ "No dice. %s is already flying the pattern.",
+ "%s is currently airborne. Spawn limit: 1.",
+ "Negative. %s is already up there doing tanker stuff.",
+ "%s is already active. Maybe check the status screen?",
+ "Can't spawn %s again. We're not made of tankers here.",
+ "%s is currently flying. One at a time, hotshot.",
+ "Request rejected. %s is already on station.",
+ "%s is already operational. Even Mo knows you only get one.",
+ "That's not possible. %s is currently airborne.",
+ "%s is already up there. Better situational awareness needed.",
+ "Denied. %s is currently active and wondering why you asked.",
+ "%s is already flying. The spawn button isn't for spam.",
+ "Cannot comply. %s is already operational, Einstein.",
+ },
+
+ -- Spawn Failure
+ SPAWN_FAILURE = {
+ "Failed to spawn %s!",
+ "Unable to launch %s. Try again.",
+ "%s spawn aborted!",
+ "Cannot spawn %s at this time.",
+ "%s failed to launch!",
+ "Error spawning %s. Contact support.",
+ "%s launch unsuccessful.",
+ "Unable to activate %s. Retry required.",
+ "%s spawn failed. Check logs.",
+ "Launch failure for %s!",
+ "%s spawn went sideways. Oops.",
+ "Well that didn't work. %s failed to spawn.",
+ "%s couldn't get off the ground. Awkward.",
+ "Houston, we have a problem. %s didn't spawn.",
+ "%s spawn failed harder than Mo's last landing.",
+ "Oof. %s spawn went to hell.",
+ "%s launch aborted. This is embarrassing.",
+ "Yeah, %s didn't spawn. Our bad.",
+ "Spawn failed for %s. Not our finest moment.",
+ "%s couldn't launch. Try again, genius.",
+ "That's a big negative on %s spawn.",
+ "%s failed to spawn. Did Mo write this code?",
+ "Error: %s spawn went boom. The bad kind.",
+ "%s launch unsuccessful. Better luck next time.",
+ "Spawn failed. %s is still in the hangar.",
+ "%s didn't want to fly today apparently.",
+ "Well crap. %s spawn totally failed.",
+ "%s launch aborted. Something broke.",
+ "That didn't work. %s spawn failed miserably.",
+ "%s couldn't spawn. Technical difficulties.",
+ "Negative spawn for %s. Try again maybe?",
+ "%s spawn went tits up. Sorry.",
+ "Launch failure! %s is grounded.",
+ "%s spawn crashed and burned. Not literally.",
+ "Unable to spawn %s. Computer says no.",
+ "%s launch failed. Mo could have done better.",
+ "Spawn error for %s. This is awkward.",
+ "%s didn't spawn. The universe said no.",
+ "Failed to launch %s. Not our day.",
+ "%s spawn aborted. Probably for the best.",
+ "Yeah... %s spawn didn't happen.",
+ "%s failed to spawn. Check your setup.",
+ "Spawn unsuccessful for %s. Womp womp.",
+ "%s launch went south. Way south.",
+ "That's a no-go. %s failed to spawn.",
+ "%s spawn error. Better call tech support.",
+ "Launch failure! %s stayed on the ground.",
+ "%s couldn't spawn. Even we're confused.",
+ "Spawn failed for %s. Mo's laughing right now.",
+ "%s launch aborted. Something went wrong.",
+ "Unable to activate %s. Try turning it off and on again.",
+ "%s spawn went nowhere fast.",
+ "Failed spawn alert: %s is still parked.",
+ "%s launch unsuccessful. This is fine. Everything's fine.",
+ "Spawn error for %s. Not ideal.",
+ "%s couldn't get airborne. Rough.",
+ "Launch aborted. %s is taking a day off.",
+ "%s spawn failed spectacularly.",
+ "Cannot spawn %s. System said 'nah.'",
+ "%s launch went sideways. Try again.",
+ "Spawn failure! %s is grounded indefinitely.",
+ "%s didn't spawn. Murphy's Law in effect.",
+ "Failed to launch %s. Mo's spawn worked better.",
+ "%s spawn unsuccessful. Check the logs.",
+ "Error spawning %s. This shouldn't happen.",
+ "%s launch aborted. Technical difficulties ahead.",
+ "Spawn failed. %s is staying home today.",
+ "%s couldn't spawn. Better luck next time, champ.",
+ "Launch failure for %s. Not sure why.",
+ "%s spawn went wrong. Very wrong.",
+ "Unable to spawn %s. Try again later.",
+ "%s launch failed. Mo would be disappointed.",
+ "Spawn error: %s didn't make it.",
+ "%s failed to spawn. Computer threw a tantrum.",
+ "Launch aborted for %s. Sorry about that.",
+ "%s spawn unsuccessful. Try again maybe?",
+ "Cannot activate %s. Spawn failed.",
+ "%s launch went nowhere. Like Mo's career.",
+ "Spawn failure! %s is MIA.",
+ "%s couldn't spawn. System error.",
+ "Failed to launch %s. This is awkward.",
+ "%s spawn aborted. Not today, apparently.",
+ "Launch error for %s. Check your setup.",
+ "%s didn't spawn. Computer says no way.",
+ "Spawn unsuccessful. %s is grounded.",
+ "%s launch failed. Mo's code was better.",
+ "Cannot spawn %s. Technical issues.",
+ "%s failed to activate. Try again.",
+ "Launch aborted. %s spawn went south.",
+ "%s spawn error. This isn't good.",
+ "Failed to spawn %s. Maybe next time.",
+ "%s launch unsuccessful. Something broke.",
+ "Spawn failure for %s. Not our best work.",
+ "%s couldn't get off the ground. Awkward moment.",
+ "Launch error! %s is still parked.",
+ "%s spawn went wrong. Very, very wrong.",
+ "Unable to spawn %s. System malfunction.",
+ "%s launch failed harder than expected.",
+ "Spawn aborted. %s is taking a sick day.",
+ },
+
+ -- Custom Route Accepted
+ ROUTE_ACCEPTED = {
+ "%s accepting custom route with %d waypoints.%s",
+ "%s has your route. %d waypoints loaded.%s",
+ "%s acknowledges custom flight plan. %d waypoints.%s",
+ "%s route confirmed. %d waypoints programmed.%s",
+ "%s copy your route. %d waypoints accepted.%s",
+ "%s roger. %d waypoint route loaded.%s",
+ "%s has the route. %d points confirmed.%s",
+ "%s flight plan accepted. %d waypoints.%s",
+ "%s confirms route. %d waypoints in the box.%s",
+ "%s routing confirmed with %d waypoints.%s",
+ "%s has your custom route. %d waypoints loaded.%s",
+ "%s accepts your flight plan. %d points confirmed.%s",
+ "%s copies custom route with %d waypoints.%s",
+ "%s acknowledges %d waypoint route.%s",
+ "%s route programmed. %d waypoints locked in.%s",
+ "%s flight plan confirmed with %d points.%s",
+ "%s roger your route. %d waypoints loaded.%s",
+ "%s accepts %d waypoint custom plan.%s",
+ "%s has your %d waypoint route locked in.%s",
+ "%s confirms %d waypoint flight plan.%s",
+ "%s copy that. %d waypoint route programmed.%s",
+ "%s routing accepted. %d points confirmed.%s",
+ "%s has the route. %d waypoints ready.%s",
+ "%s acknowledges %d point route.%s",
+ "%s flight plan loaded with %d waypoints.%s",
+ "%s custom route confirmed. %d points.%s",
+ "%s accepts your %d waypoint plan.%s",
+ "%s roger. %d waypoints programmed.%s",
+ "%s has your %d waypoint custom route.%s",
+ "%s routing confirmed with %d points.%s",
+ "%s copies %d waypoint route. Unlike Mo's attempt.%s",
+ "%s accepts your custom %d waypoint plan.%s",
+ "%s has loaded %d waypoint route.%s",
+ "%s confirms %d waypoint routing.%s",
+ "%s flight plan locked in. %d waypoints.%s",
+ "%s roger your %d waypoint route.%s",
+ "%s accepts custom route with %d points.%s",
+ "%s has programmed %d waypoint plan.%s",
+ "%s acknowledges %d waypoint custom route.%s",
+ "%s routing confirmed. %d waypoints ready.%s",
+ "%s copies %d waypoint flight plan.%s",
+ "%s accepts your %d point custom route.%s",
+ "%s has %d waypoint route confirmed.%s",
+ "%s roger custom plan with %d waypoints.%s",
+ "%s routing accepted. %d points programmed.%s",
+ "%s flight plan confirmed. %d waypoints loaded.%s",
+ "%s has your custom %d waypoint routing.%s",
+ "%s accepts %d waypoint plan.%s",
+ "%s confirms custom route. %d waypoints.%s",
+ "%s roger that. %d waypoint route accepted.%s",
+ "%s has %d waypoint custom plan loaded.%s",
+ "%s acknowledges %d point custom route.%s",
+ "%s routing programmed. %d waypoints confirmed.%s",
+ "%s flight plan accepted with %d points.%s",
+ "%s copies your %d waypoint custom route.%s",
+ "%s has %d waypoint route ready.%s",
+ "%s accepts custom plan. %d waypoints.%s",
+ "%s confirms %d point flight plan.%s",
+ "%s roger custom %d waypoint route.%s",
+ "%s routing locked in. %d waypoints.%s",
+ "%s has %d waypoint plan confirmed.%s",
+ "%s flight plan loaded. %d waypoints accepted.%s",
+ "%s acknowledges custom %d waypoint route.%s",
+ "%s accepts %d waypoint routing.%s",
+ "%s copies custom %d waypoint plan.%s",
+ "%s has %d waypoint route programmed.%s",
+ "%s confirms your %d waypoint custom route.%s",
+ "%s roger. %d waypoint custom plan loaded.%s",
+ "%s routing accepted with %d points.%s",
+ "%s flight plan programmed. %d waypoints.%s",
+ "%s has custom route with %d waypoints.%s",
+ "%s accepts %d waypoint custom plan.%s",
+ "%s acknowledges %d waypoint routing.%s",
+ "%s copies %d waypoint custom route.%s",
+ "%s has %d waypoint flight plan confirmed.%s",
+ "%s roger custom route. %d waypoints.%s",
+ "%s routing confirmed with %d waypoints.%s",
+ "%s flight plan accepted. %d points loaded.%s",
+ "%s has your %d waypoint custom plan.%s",
+ "%s accepts custom %d waypoint route.%s",
+ "%s confirms %d waypoint custom plan.%s",
+ "%s acknowledges %d waypoint flight plan.%s",
+ "%s copies %d waypoint route confirmed.%s",
+ "%s has custom %d waypoint routing ready.%s",
+ "%s roger. %d waypoints accepted and locked.%s",
+ "%s routing programmed with %d waypoints.%s",
+ "%s flight plan confirmed with %d points.%s",
+ "%s has %d waypoint custom route loaded.%s",
+ "%s accepts your custom %d waypoint routing.%s",
+ "%s confirms %d waypoint plan confirmed.%s",
+ "%s acknowledges custom route with %d points.%s",
+ "%s copies %d waypoint custom flight plan.%s",
+ "%s has %d waypoint route locked and loaded.%s",
+ "%s roger that. %d waypoint custom route ready.%s",
+ "%s routing accepted. %d waypoints programmed.%s",
+ "%s flight plan loaded with %d waypoints.%s",
+ },
+
+ -- Emergency Spawn
+ EMERGENCY_SPAWN = {
+ "EMERGENCY: %s launching immediately!",
+ "PRIORITY LAUNCH: %s is scrambling now!",
+ "EMERGENCY TANKER: %s departing expedited!",
+ "URGENT: %s is launching on priority status!",
+ "EMERGENCY RESPONSE: %s airborne ASAP!",
+ "PRIORITY: %s scrambling for emergency fuel!",
+ "EMERGENCY: %s launching hot!",
+ "URGENT LAUNCH: %s is wheels up now!",
+ "EMERGENCY TANKER: %s responding immediately!",
+ "PRIORITY STATUS: %s emergency launch in progress!",
+ "EMERGENCY! %s wheels up NOW!",
+ "SCRAMBLE SCRAMBLE: %s launching immediately!",
+ "PRIORITY LAUNCH: %s getting airborne right now!",
+ "EMERGENCY TANKER: %s departing hot and fast!",
+ "URGENT: %s scrambling for emergency refuel!",
+ "PRIORITY: %s launching on expedited status!",
+ "EMERGENCY RESPONSE: %s airborne immediately!",
+ "URGENT LAUNCH: %s departing NOW!",
+ "EMERGENCY: %s getting up there ASAP!",
+ "PRIORITY STATUS: %s scrambling right now!",
+ "EMERGENCY TANKER: %s wheels up immediately!",
+ "URGENT: %s launching on priority!",
+ "SCRAMBLE: %s departing expedited!",
+ "EMERGENCY: %s getting airborne fast!",
+ "PRIORITY LAUNCH: %s launching NOW!",
+ "URGENT TANKER: %s scrambling immediately!",
+ "EMERGENCY: %s departing hot!",
+ "PRIORITY: %s wheels up ASAP!",
+ "URGENT LAUNCH: %s airborne right now!",
+ "EMERGENCY TANKER: %s launching immediately!",
+ "SCRAMBLE SCRAMBLE: %s getting up there now!",
+ "PRIORITY: %s launching on emergency status!",
+ "URGENT: %s departing immediately!",
+ "EMERGENCY: %s scrambling for urgent refuel!",
+ "PRIORITY LAUNCH: %s wheels up hot!",
+ "URGENT TANKER: %s airborne ASAP!",
+ "EMERGENCY: %s launching right now!",
+ "PRIORITY: %s scrambling expedited!",
+ "URGENT LAUNCH: %s departing NOW NOW NOW!",
+ "EMERGENCY TANKER: %s getting airborne fast!",
+ "PRIORITY STATUS: %s launching immediately!",
+ "URGENT: %s wheels up on priority!",
+ "EMERGENCY: %s scrambling now!",
+ "PRIORITY LAUNCH: %s departing fast!",
+ "URGENT TANKER: %s launching ASAP!",
+ "EMERGENCY: %s airborne immediately!",
+ "PRIORITY: %s scrambling hot!",
+ "URGENT LAUNCH: %s wheels up right now!",
+ "EMERGENCY TANKER: %s departing expedited!",
+ "PRIORITY: %s launching on urgent status!",
+ "URGENT: %s getting airborne now!",
+ "EMERGENCY SCRAMBLE: %s departing immediately!",
+ "PRIORITY TANKER: %s wheels up fast!",
+ "URGENT: %s launching right now!",
+ "EMERGENCY: %s airborne ASAP!",
+ "PRIORITY LAUNCH: %s scrambling now!",
+ "URGENT TANKER: %s departing hot!",
+ "EMERGENCY: %s wheels up immediately!",
+ "PRIORITY: %s getting airborne fast!",
+ "URGENT LAUNCH: %s scrambling ASAP!",
+ "EMERGENCY TANKER: %s launching on priority!",
+ "PRIORITY: %s departing right now!",
+ "URGENT: %s airborne expedited!",
+ "EMERGENCY: %s scrambling immediately!",
+ "PRIORITY LAUNCH: %s wheels up NOW!",
+ "URGENT TANKER: %s launching fast!",
+ "EMERGENCY: %s departing ASAP!",
+ "PRIORITY: %s airborne right now!",
+ "URGENT LAUNCH: %s scrambling hot!",
+ "EMERGENCY TANKER: %s wheels up expedited!",
+ "PRIORITY: %s launching immediately!",
+ "URGENT: %s getting airborne ASAP!",
+ "EMERGENCY: %s scrambling fast!",
+ "PRIORITY LAUNCH: %s departing NOW!",
+ "URGENT TANKER: %s wheels up right now!",
+ "EMERGENCY: %s airborne hot!",
+ "PRIORITY: %s scrambling ASAP!",
+ "URGENT LAUNCH: %s launching immediately!",
+ "EMERGENCY TANKER: %s departing fast!",
+ "PRIORITY: %s wheels up expedited!",
+ "URGENT: %s airborne NOW!",
+ "EMERGENCY: %s launching hot and fast!",
+ "PRIORITY LAUNCH: %s scrambling expedited!",
+ "URGENT TANKER: %s departing immediately!",
+ "EMERGENCY: %s wheels up ASAP!",
+ "PRIORITY: %s getting airborne now!",
+ "URGENT LAUNCH: %s airborne fast!",
+ "EMERGENCY TANKER: %s scrambling NOW!",
+ "PRIORITY: %s launching expedited!",
+ "URGENT: %s departing hot!",
+ "EMERGENCY: %s airborne immediately unlike Mo!",
+ "PRIORITY LAUNCH: %s wheels up faster than Mo!",
+ "URGENT TANKER: %s scrambling (Mo couldn't do this)!",
+ "EMERGENCY: %s launching while Mo watches!",
+ "PRIORITY: %s departing - Mo take notes!",
+ "URGENT LAUNCH: %s airborne (unlike Mo's attempts)!",
+ "EMERGENCY TANKER: %s scrambling successfully!",
+ "PRIORITY: %s wheels up for real!",
+ "URGENT: %s launching like professionals do!",
+ },
+
+ -- Low Fuel Warning
+ LOW_FUEL = {
+ "%s reports fuel at %d%%. Recommend expedite refueling.",
+ "%s low on fuel - %d%% remaining. RTB soon.",
+ "%s fuel state: %d%%. Time is limited.",
+ "%s down to %d%% fuel. Get your gas quick.",
+ "%s running low - %d%% remaining.",
+ "%s fuel advisory: %d%% left. Don't delay.",
+ "%s reports %d%% fuel state. Limited time remaining.",
+ "%s low fuel warning at %d%%. RTB imminent.",
+ "%s fuel: %d%%. Better hurry up.",
+ "%s getting thirsty at %d%% fuel remaining.",
+ "%s fuel down to %d%%. Time's ticking.",
+ "%s running on fumes at %d%%. Get moving.",
+ "%s reports %d%% fuel. Clock is running.",
+ "%s fuel state critical at %d%%.",
+ "%s getting low at %d%%. Don't dawdle.",
+ "%s fuel: %d%%. Window is closing.",
+ "%s reports %d%% remaining. Hurry it up.",
+ "%s fuel advisory: %d%%. Time's short.",
+ "%s down to %d%%. Better move fast.",
+ "%s fuel at %d%%. RTB soon or refuel now.",
+ "%s running thin at %d%%. Expedite.",
+ "%s reports %d%% fuel. Not much time left.",
+ "%s fuel state %d%%. Don't mess around.",
+ "%s getting low - %d%% and dropping.",
+ "%s fuel: %d%%. Better get some quick.",
+ "%s reports %d%%. Running out of time.",
+ "%s fuel down to %d%%. Tick tock.",
+ "%s low on gas at %d%%. Move it.",
+ "%s reports %d%% fuel state. Limited window.",
+ "%s fuel: %d%%. Don't be slow about it.",
+ "%s getting thirsty - %d%% remaining.",
+ "%s reports %d%%. Better hurry your ass up.",
+ "%s fuel at %d%%. Time ain't on your side.",
+ "%s running low - %d%%. Get in here.",
+ "%s fuel state: %d%%. Mo could refuel faster.",
+ "%s reports %d%%. Don't be a hero, get fuel.",
+ "%s fuel down to %d%%. Unlike Mo we're warning you.",
+ "%s getting low at %d%%. Stop screwing around.",
+ "%s reports %d%% fuel. This isn't a drill.",
+ "%s fuel: %d%%. Better not screw this up.",
+ "%s running thin - %d%% remaining.",
+ "%s reports %d%%. Time to get your ass over here.",
+ "%s fuel state %d%%. Seriously, hurry up.",
+ "%s getting thirsty at %d%%. Don't be stupid.",
+ "%s fuel: %d%%. We're leaving soon.",
+ "%s reports %d%%. Better expedite refueling.",
+ "%s fuel down to %d%%. Window closing fast.",
+ "%s low on juice - %d%% remaining.",
+ "%s reports %d%% fuel. Get moving or RTB.",
+ "%s fuel state: %d%%. Don't drag ass.",
+ "%s getting low at %d%%. Time's running out.",
+ "%s reports %d%%. Stop dicking around.",
+ "%s fuel: %d%%. Get in the basket.",
+ "%s running thin at %d%%. Move faster.",
+ "%s reports %d%% remaining. Chop chop.",
+ "%s fuel down to %d%%. Unlike Mo's planning.",
+ "%s getting thirsty - %d%%. Don't be slow.",
+ "%s reports %d%% fuel state. Hurry.",
+ "%s fuel: %d%%. Better not flame out.",
+ "%s low on gas at %d%%. Get over here.",
+ "%s reports %d%%. Time to move it.",
+ "%s fuel state %d%%. We don't have all day.",
+ "%s getting low - %d%% and dropping fast.",
+ "%s reports %d%%. Stop being a pussy.",
+ "%s fuel: %d%%. Refuel or die trying.",
+ "%s running thin - %d%%. Better hurry.",
+ "%s reports %d%% fuel. Move your ass.",
+ "%s fuel down to %d%%. Not kidding here.",
+ "%s getting thirsty at %d%%. Expedite.",
+ "%s reports %d%%. Don't be like Mo.",
+ "%s fuel state: %d%%. Get fuel or get bent.",
+ "%s low at %d%%. Time's wasting.",
+ "%s reports %d%% remaining. Hurry up.",
+ "%s fuel: %d%%. Stop fucking around.",
+ "%s running low - %d%%. Get here now.",
+ "%s reports %d%%. We're not waiting forever.",
+ "%s fuel down to %d%%. Better get moving.",
+ "%s getting low at %d%%. Tick tock motherfucker.",
+ "%s reports %d%% fuel state. Move it.",
+ "%s fuel: %d%%. Don't be a jackass.",
+ "%s running thin at %d%%. Expedite refuel.",
+ "%s reports %d%%. Time's running short.",
+ "%s fuel state %d%%. Get in the pattern.",
+ "%s getting thirsty - %d%%. Don't delay.",
+ "%s reports %d%%. Unlike Mo we're still here.",
+ "%s fuel: %d%%. Better not screw this up.",
+ "%s low on gas - %d%% remaining.",
+ "%s reports %d%% fuel. Window closing.",
+ "%s fuel down to %d%%. Get your shit together.",
+ "%s getting low at %d%%. Seriously move.",
+ "%s reports %d%%. Don't make us leave.",
+ "%s fuel state: %d%%. Better expedite.",
+ "%s running thin - %d%%. Time's up soon.",
+ "%s reports %d%% remaining. Get here.",
+ "%s fuel: %d%%. Stop dragging ass.",
+ "%s getting thirsty at %d%%. Hurry.",
+ "%s reports %d%%. Mo would have flamed out by now.",
+ },
+
+ -- Bingo Fuel (RTB)
+ BINGO_FUEL = {
+ "%s is BINGO fuel. Returning to base immediately!",
+ "%s has reached BINGO. RTB in progress!",
+ "%s calling BINGO fuel. Departing the pattern now!",
+ "%s is at BINGO state. Returning to base!",
+ "%s BINGO fuel - heading home now!",
+ "%s has hit BINGO. No more refueling available!",
+ "%s fuel critical - RTB initiated!",
+ "%s at BINGO state. Breaking off now!",
+ "%s calling BINGO. Pattern is clear!",
+ "%s BINGO fuel declared. Returning to base!",
+ "%s is BINGO. Getting the hell out!",
+ "%s calling BINGO fuel. We're done here!",
+ "%s has reached BINGO state. Leaving NOW!",
+ "%s BINGO declared. RTB in progress!",
+ "%s at BINGO fuel. Heading home!",
+ "%s calling BINGO. Pattern clear!",
+ "%s has hit BINGO. See ya!",
+ "%s BINGO fuel state. Departing!",
+ "%s is at BINGO. RTB immediately!",
+ "%s calling BINGO. We're out!",
+ "%s has reached BINGO. Breaking off!",
+ "%s BINGO fuel declared. Leaving!",
+ "%s at BINGO state. Going home!",
+ "%s calling BINGO. Adios!",
+ "%s has hit BINGO fuel. Departing now!",
+ "%s BINGO declared. RTB active!",
+ "%s is at BINGO. Bye bye!",
+ "%s calling BINGO fuel. Out of here!",
+ "%s has reached BINGO state. Later!",
+ "%s BINGO fuel. Heading back!",
+ "%s at BINGO. Returning immediately!",
+ "%s calling BINGO. Pattern's yours!",
+ "%s has hit BINGO. Going home!",
+ "%s BINGO declared. Leaving the AO!",
+ "%s is at BINGO fuel. RTB now!",
+ "%s calling BINGO. Peace out!",
+ "%s has reached BINGO. Departing!",
+ "%s BINGO fuel state. We're done!",
+ "%s at BINGO. Heading to base!",
+ "%s calling BINGO. Catch you later!",
+ "%s has hit BINGO fuel. RTB!",
+ "%s BINGO declared. Getting out!",
+ "%s is at BINGO state. Later gator!",
+ "%s calling BINGO. We out!",
+ "%s has reached BINGO. Returning!",
+ "%s BINGO fuel. Leaving now!",
+ "%s at BINGO. Going home finally!",
+ "%s calling BINGO. Done pumping gas!",
+ "%s has hit BINGO. RTB initiated!",
+ "%s BINGO declared. Out of here!",
+ "%s is at BINGO fuel. Bye!",
+ "%s calling BINGO. Pattern clear!",
+ "%s has reached BINGO state. Departing!",
+ "%s BINGO fuel. Heading back!",
+ "%s at BINGO. RTB in progress!",
+ "%s calling BINGO. See ya later!",
+ "%s has hit BINGO fuel. Leaving!",
+ "%s BINGO declared. Going home!",
+ "%s is at BINGO. Out!",
+ "%s calling BINGO fuel. We're outta here!",
+ "%s has reached BINGO. RTB now!",
+ "%s BINGO fuel state. Later!",
+ "%s at BINGO. Returning to base!",
+ "%s calling BINGO. Adios amigos!",
+ "%s has hit BINGO. Departing!",
+ "%s BINGO declared. Heading home!",
+ "%s is at BINGO fuel. Peace!",
+ "%s calling BINGO. We done!",
+ "%s has reached BINGO state. Leaving!",
+ "%s BINGO fuel. RTB active!",
+ "%s at BINGO. Going back!",
+ "%s calling BINGO. That's it folks!",
+ "%s has hit BINGO fuel. Out of here!",
+ "%s BINGO declared. Returning!",
+ "%s is at BINGO. Later suckers!",
+ "%s calling BINGO fuel. Bye!",
+ "%s has reached BINGO. Heading home!",
+ "%s BINGO fuel state. Departing!",
+ "%s at BINGO. RTB initiated!",
+ "%s calling BINGO. We're gone!",
+ "%s has hit BINGO. Leaving now!",
+ "%s BINGO declared. Getting out of dodge!",
+ "%s is at BINGO fuel. Later!",
+ "%s calling BINGO. Don't wait up!",
+ "%s has reached BINGO state. Out!",
+ "%s BINGO fuel. Going home!",
+ "%s at BINGO. Returning immediately!",
+ "%s calling BINGO. Unlike Mo we planned this!",
+ "%s has hit BINGO fuel. Peace out!",
+ "%s BINGO declared. Heading back!",
+ "%s is at BINGO. Bye felicia!",
+ "%s calling BINGO fuel. That's a wrap!",
+ "%s has reached BINGO. We're out!",
+ "%s BINGO fuel state. Later gator!",
+ "%s at BINGO. RTB right now!",
+ "%s calling BINGO. Smell ya later!",
+ "%s has hit BINGO. Departing!",
+ "%s BINGO declared. Mo would've flamed out!",
+ "%s is at BINGO fuel. Catch you on the flip side!",
+ },
+
+ -- Tanker Destroyed
+ DESTROYED = {
+ "%s has been destroyed!",
+ "%s is down! Aircraft lost!",
+ "%s has been shot down!",
+ "%s destroyed in combat!",
+ "%s is gone - aircraft destroyed!",
+ "%s has been lost!",
+ "We've lost %s!",
+ "%s destroyed! No survivors!",
+ "%s is down and out!",
+ "%s has been eliminated!",
+ "%s has been blown to hell!",
+ "%s is toast! Aircraft destroyed!",
+ "%s went down in flames!",
+ "%s has been obliterated!",
+ "%s is scrap metal now!",
+ "%s got smoked!",
+ "RIP %s. Aircraft destroyed!",
+ "%s has been vaporized!",
+ "%s is no more!",
+ "%s went down hard!",
+ "%s has been wasted!",
+ "%s is KIA! Aircraft lost!",
+ "%s got shot the fuck down!",
+ "%s has been annihilated!",
+ "%s is sleeping with the fishes!",
+ "%s went boom!",
+ "%s has been terminated!",
+ "%s is dead! No survivors!",
+ "%s got fucked up!",
+ "%s has ceased to exist!",
+ "%s went down like Mo's career!",
+ "%s is destroyed! Total loss!",
+ "%s got hammered!",
+ "%s has been neutralized!",
+ "%s is history!",
+ "%s went down in a ball of fire!",
+ "%s has been taken out!",
+ "%s is gone forever!",
+ "%s got massacred!",
+ "%s has been deleted!",
+ "%s is pushing up daisies!",
+ "%s went down screaming!",
+ "%s has been liquidated!",
+ "%s is scattered across the landscape!",
+ "%s got wrecked!",
+ "%s has been erased!",
+ "%s is no longer operational!",
+ "%s went down like a brick!",
+ "%s has been dispatched!",
+ "%s is gone to the great hangar in the sky!",
+ "%s got absolutely demolished!",
+ "%s has been removed from existence!",
+ "%s is dead as fuck!",
+ "%s went down faster than Mo!",
+ "%s has been exterminated!",
+ "%s is now a smoking crater!",
+ "%s got absolutely destroyed!",
+ "%s has been converted to debris!",
+ "%s is no longer with us!",
+ "%s went down in a spectacular fashion!",
+ "%s has been sent to hell!",
+ "%s is totally fucked!",
+ "%s got blown out of the sky!",
+ "%s has been utterly destroyed!",
+ "%s is burning on the ground!",
+ "%s went down like a sack of shit!",
+ "%s has been wiped out!",
+ "%s is permanently grounded!",
+ "%s got turned into confetti!",
+ "%s has been removed from service!",
+ "%s is now spare parts!",
+ "%s went down hard and fast!",
+ "%s has been completely destroyed!",
+ "%s is toast and then some!",
+ "%s got absolutely annihilated!",
+ "%s has been blown to smithereens!",
+ "%s is no longer flying!",
+ "%s went down like the Hindenburg!",
+ "%s has been totally wrecked!",
+ "%s is deader than dead!",
+ "%s got straight up murdered!",
+ "%s has been completely obliterated!",
+ "%s is scattered across three counties!",
+ "%s went down in flames like Mo's reputation!",
+ "%s has been catastrophically destroyed!",
+ "%s is now a fireball!",
+ "%s got absolutely smoked!",
+ "%s has been reduced to atoms!",
+ "%s is gone gone gone!",
+ "%s went down and ain't coming back!",
+ "%s has been utterly annihilated!",
+ "%s is now a lawn dart!",
+ "%s got completely fucked!",
+ "%s has been sent to the shadow realm!",
+ "%s is now in aircraft heaven!",
+ "%s went down faster than your hopes and dreams!",
+ "%s has been totally destroyed!",
+ "%s is no longer a thing!",
+ },
+
+ -- Hostile Fire
+ TAKING_FIRE = {
+ "%s is taking fire!",
+ "%s under attack!",
+ "%s receiving hostile fire!",
+ "%s taking hits!",
+ "%s is being engaged!",
+ "%s under hostile fire!",
+ "Hostile fire on %s!",
+ "%s taking enemy fire!",
+ "%s is under attack!",
+ "%s being fired upon!",
+ "%s is getting shot at!",
+ "%s under hostile fire!",
+ "%s taking incoming!",
+ "%s is being lit up!",
+ "%s receiving enemy fire!",
+ "%s getting hammered!",
+ "%s under attack right now!",
+ "%s taking fire from hostiles!",
+ "%s is being engaged by enemy!",
+ "%s getting shot to shit!",
+ "%s under hostile attack!",
+ "%s taking heavy fire!",
+ "%s is being targeted!",
+ "%s receiving hostile rounds!",
+ "%s getting fucked up!",
+ "%s under enemy fire!",
+ "%s taking hits from hostiles!",
+ "%s is being shot at!",
+ "%s receiving incoming fire!",
+ "%s getting attacked!",
+ "%s under hostile engagement!",
+ "%s taking enemy rounds!",
+ "%s is being hit!",
+ "%s receiving fire!",
+ "%s getting lit up!",
+ "%s under attack from enemy!",
+ "%s taking hostile fire!",
+ "%s is being engaged!",
+ "%s receiving enemy rounds!",
+ "%s getting shot!",
+ "%s under fire right now!",
+ "%s taking incoming rounds!",
+ "%s is being attacked!",
+ "%s receiving hostile fire!",
+ "%s getting hammered by hostiles!",
+ "%s under enemy attack!",
+ "%s taking fire from below!",
+ "%s is being targeted by enemy!",
+ "%s receiving heavy fire!",
+ "%s getting shot at hard!",
+ "%s under hostile fire!",
+ "%s taking enemy fire now!",
+ "%s is being engaged by hostiles!",
+ "%s receiving incoming!",
+ "%s getting attacked by enemy!",
+ "%s under fire from hostiles!",
+ "%s taking hits!",
+ "%s is being shot up!",
+ "%s receiving hostile rounds!",
+ "%s getting fucked up by enemy!",
+ "%s under hostile engagement!",
+ "%s taking fire!",
+ "%s is being hammered!",
+ "%s receiving enemy fire!",
+ "%s getting shot at!",
+ "%s under enemy fire!",
+ "%s taking hostile rounds!",
+ "%s is being lit up!",
+ "%s receiving fire from hostiles!",
+ "%s getting attacked hard!",
+ "%s under hostile attack!",
+ "%s taking incoming fire!",
+ "%s is being engaged!",
+ "%s receiving hostile fire!",
+ "%s getting shot to hell!",
+ "%s under fire!",
+ "%s taking enemy rounds!",
+ "%s is being targeted!",
+ "%s receiving fire!",
+ "%s getting hammered!",
+ "%s under attack by hostiles!",
+ "%s taking fire from enemy!",
+ "%s is being shot at!",
+ "%s receiving incoming rounds!",
+ "%s getting attacked!",
+ "%s under hostile fire right now!",
+ "%s taking hits from enemy!",
+ "%s is being engaged by hostiles!",
+ "%s receiving hostile fire!",
+ "%s getting lit up by enemy!",
+ "%s under fire from below!",
+ "%s taking enemy fire!",
+ "%s is being attacked by hostiles!",
+ "%s receiving fire from enemy!",
+ "%s getting shot at hard!",
+ "%s under enemy attack!",
+ "%s taking hostile fire unlike Mo who'd be dead!",
+ "%s is being hammered by hostiles!",
+ "%s receiving enemy rounds!",
+ },
+
+ -- Invalid Waypoint Count (too few)
+ TOO_FEW_WAYPOINTS = {
+ "Custom route requires at least %d waypoints!\nPlace markers: %s1, %s2, etc.",
+ "Not enough waypoints! Need at least %d.\nUse markers: %s1, %s2, etc.",
+ "Insufficient waypoints - need %d minimum.\nCreate markers: %s1, %s2, etc.",
+ "Route rejected: need %d waypoints minimum.\nPlace %s1, %s2, etc.",
+ "At least %d waypoints required!\nDrop markers: %s1, %s2, etc.",
+ "Need more waypoints - minimum is %d.\nUse: %s1, %s2, etc.",
+ "Route incomplete. Need %d waypoints.\nCreate: %s1, %s2, etc.",
+ "Waypoint count too low - need %d.\nPlace: %s1, %s2, etc.",
+ "Minimum %d waypoints required!\nMark: %s1, %s2, etc.",
+ "Can't route with less than %d points.\nAdd markers: %s1, %s2, etc.",
+ },
+
+ -- Too Many Waypoints
+ TOO_MANY_WAYPOINTS = {
+ "Too many waypoints! Maximum is %d",
+ "Waypoint limit exceeded. Max: %d",
+ "Can't route with more than %d waypoints!",
+ "Route rejected - too many points. Max: %d",
+ "Waypoint overflow! Maximum is %d",
+ "Too complex - max %d waypoints allowed!",
+ "Exceeded waypoint limit of %d!",
+ "Route too long! Maximum: %d waypoints",
+ "Cannot accept more than %d waypoints!",
+ "Waypoint maximum is %d. Route rejected.",
+ },
+
+ -- No RTB Airbase Found
+ NO_RTB_AIRBASE = {
+ "No friendly airbase found for RTB!",
+ "Cannot locate RTB destination!",
+ "No suitable airbase available for recovery!",
+ "Unable to find friendly base for RTB!",
+ "No recovery airfield located!",
+ "RTB destination unavailable!",
+ "Cannot identify friendly airbase for return!",
+ "No airbase in range for RTB!",
+ "Recovery base not found!",
+ "Unable to locate RTB airfield!",
+ },
+ -- EMERGENCY SPAWN MESSAGES (100 total)
+ EMERGENCY_SPAWN = {
+ "EMERGENCY: %s launching immediately!",
+ "PRIORITY LAUNCH: %s is scrambling now!",
+ "EMERGENCY TANKER: %s departing expedited!",
+ "URGENT: %s is launching on priority status!",
+ "EMERGENCY RESPONSE: %s airborne ASAP!",
+ "PRIORITY: %s scrambling for emergency fuel!",
+ "EMERGENCY: %s launching hot!",
+ "URGENT LAUNCH: %s is wheels up now!",
+ "EMERGENCY TANKER: %s responding immediately!",
+ "PRIORITY STATUS: %s emergency launch in progress!",
+ "EMERGENCY! %s wheels up NOW!",
+ "SCRAMBLE SCRAMBLE: %s launching immediately!",
+ "PRIORITY LAUNCH: %s getting airborne right now!",
+ "EMERGENCY TANKER: %s departing hot and fast!",
+ "URGENT: %s scrambling for emergency refuel!",
+ "PRIORITY: %s launching on expedited status!",
+ "EMERGENCY RESPONSE: %s airborne immediately!",
+ "URGENT LAUNCH: %s departing NOW!",
+ "EMERGENCY: %s getting up there ASAP!",
+ "PRIORITY STATUS: %s scrambling right now!",
+ "EMERGENCY TANKER: %s wheels up immediately!",
+ "URGENT: %s launching on priority!",
+ "SCRAMBLE: %s departing expedited!",
+ "EMERGENCY: %s getting airborne fast!",
+ "PRIORITY LAUNCH: %s launching NOW!",
+ "URGENT TANKER: %s scrambling immediately!",
+ "EMERGENCY: %s departing hot!",
+ "PRIORITY: %s wheels up ASAP!",
+ "URGENT LAUNCH: %s airborne right now!",
+ "EMERGENCY TANKER: %s launching immediately!",
+ "SCRAMBLE SCRAMBLE: %s getting up there now!",
+ "PRIORITY: %s launching on emergency status!",
+ "URGENT: %s departing immediately!",
+ "EMERGENCY: %s scrambling for urgent refuel!",
+ "PRIORITY LAUNCH: %s wheels up hot!",
+ "URGENT TANKER: %s airborne ASAP!",
+ "EMERGENCY: %s launching right now!",
+ "PRIORITY: %s scrambling expedited!",
+ "URGENT LAUNCH: %s departing NOW NOW NOW!",
+ "EMERGENCY TANKER: %s getting airborne fast!",
+ "PRIORITY STATUS: %s launching immediately!",
+ "URGENT: %s wheels up on priority!",
+ "EMERGENCY: %s scrambling now!",
+ "PRIORITY LAUNCH: %s departing fast!",
+ "URGENT TANKER: %s launching ASAP!",
+ "EMERGENCY: %s airborne immediately!",
+ "PRIORITY: %s scrambling hot!",
+ "URGENT LAUNCH: %s wheels up right now!",
+ "EMERGENCY TANKER: %s departing expedited!",
+ "PRIORITY: %s launching on urgent status!",
+ "URGENT: %s getting airborne now!",
+ "EMERGENCY SCRAMBLE: %s departing immediately!",
+ "PRIORITY TANKER: %s wheels up fast!",
+ "URGENT: %s launching right now!",
+ "EMERGENCY: %s airborne ASAP!",
+ "PRIORITY LAUNCH: %s scrambling now!",
+ "URGENT TANKER: %s departing hot!",
+ "EMERGENCY: %s wheels up immediately!",
+ "PRIORITY: %s getting airborne fast!",
+ "URGENT LAUNCH: %s scrambling ASAP!",
+ "EMERGENCY TANKER: %s launching on priority!",
+ "PRIORITY: %s departing right now!",
+ "URGENT: %s airborne expedited!",
+ "EMERGENCY: %s scrambling immediately!",
+ "PRIORITY LAUNCH: %s wheels up NOW!",
+ "URGENT TANKER: %s launching fast!",
+ "EMERGENCY: %s departing ASAP!",
+ "PRIORITY: %s airborne right now!",
+ "URGENT LAUNCH: %s scrambling hot!",
+ "EMERGENCY TANKER: %s wheels up expedited!",
+ "PRIORITY: %s launching immediately!",
+ "URGENT: %s getting airborne ASAP!",
+ "EMERGENCY: %s scrambling fast!",
+ "PRIORITY LAUNCH: %s departing NOW!",
+ "URGENT TANKER: %s wheels up right now!",
+ "EMERGENCY: %s airborne hot!",
+ "PRIORITY: %s scrambling ASAP!",
+ "URGENT LAUNCH: %s launching immediately!",
+ "EMERGENCY TANKER: %s departing fast!",
+ "PRIORITY: %s wheels up expedited!",
+ "URGENT: %s airborne NOW!",
+ "EMERGENCY: %s launching hot and fast!",
+ "PRIORITY LAUNCH: %s scrambling expedited!",
+ "URGENT TANKER: %s departing immediately!",
+ "EMERGENCY: %s wheels up ASAP!",
+ "PRIORITY: %s getting airborne now!",
+ "URGENT LAUNCH: %s airborne fast!",
+ "EMERGENCY TANKER: %s scrambling NOW!",
+ "PRIORITY: %s launching expedited!",
+ "URGENT: %s departing hot!",
+ "EMERGENCY: %s airborne immediately unlike Mo!",
+ "PRIORITY LAUNCH: %s wheels up faster than Mo!",
+ "URGENT TANKER: %s scrambling (Mo couldn't do this)!",
+ "EMERGENCY: %s launching while Mo watches!",
+ "PRIORITY: %s departing - Mo take notes!",
+ "URGENT LAUNCH: %s airborne (unlike Mo's attempts)!",
+ "EMERGENCY TANKER: %s scrambling successfully!",
+ "PRIORITY: %s wheels up for real!",
+ "URGENT: %s launching like professionals do!",
+ },
+}
+
+--- Get a random message from a category
+--- @param category string Message category key
+--- @param ... any Format arguments for string.format
+--- @return string Formatted message
+local function GetRandomMessage(category, ...)
+ local pool = TANKER_MESSAGES[category]
+ if not pool or #pool == 0 then
+ return "Message unavailable"
+ end
+
+ local template = pool[math.random(1, #pool)]
+
+ if select("#", ...) > 0 then
+ return string.format(template, ...)
+ else
+ return template
+ end
+end
+
+-- ============================================================================
+-- UTILITY FUNCTIONS
+-- ============================================================================
+
+--- Update menu state based on tanker availability
+local function UpdateTankerMenus()
+ if MENU_KC135_LAUNCH then
+ if TANKER_STATE.KC135.active then
+ MENU_KC135_LAUNCH:Remove()
+ MENU_KC135_LAUNCH = nil
+ elseif not MENU_KC135_LAUNCH then
+ MENU_KC135_LAUNCH = MENU_COALITION_COMMAND:New(
+ coalition.side.BLUE,
+ "Launch " .. TANKER_CONFIG.KC135.displayName,
+ MENU_TANKER_ROOT,
+ SpawnTanker
+ )
+ end
+ end
+
+ if MENU_KC135_MPRS_LAUNCH then
+ if TANKER_STATE.KC135_MPRS.active then
+ MENU_KC135_MPRS_LAUNCH:Remove()
+ MENU_KC135_MPRS_LAUNCH = nil
+ elseif not MENU_KC135_MPRS_LAUNCH then
+ MENU_KC135_MPRS_LAUNCH = MENU_COALITION_COMMAND:New(
+ coalition.side.BLUE,
+ "Launch " .. TANKER_CONFIG.KC135_MPRS.displayName,
+ MENU_TANKER_ROOT,
+ SpawnTankerMPRS
+ )
+ end
+ end
+end
+
+--- Announce tanker information to coalition
+local function AnnounceTankerInfo(config, spawned)
+ local msg = GetRandomMessage("SPAWN_SUCCESS", config.displayName) .. "\n"
+
+ if config.tacan then
+ msg = msg .. string.format("TACAN: %s\n", config.tacan)
+ end
+
+ if config.frequency then
+ msg = msg .. string.format("Radio: %s MHz", config.frequency)
+ end
+
+ MESSAGE:New(msg, 20):ToBlue()
+ env.info(string.format("[TANKER] %s spawned successfully", config.displayName))
+end
+
+--- Monitor tanker fuel levels
+local function MonitorTankerFuel(stateKey, config)
+ return function()
+ local state = TANKER_STATE[stateKey]
+
+ if not state.active or not state.group then
+ return
+ end
+
+ -- Check if group still exists
+ if not state.group:IsAlive() then
+ return
+ end
+
+ local fuelPercent = state.group:GetFuel() * 100
+
+ -- Bingo fuel check
+ if fuelPercent <= config.fuelBingoPercent and not state.bingoWarned then
+ MESSAGE:New(GetRandomMessage("BINGO_FUEL", config.displayName), 15, "WARNING"):ToBlue()
+ state.bingoWarned = true
+ env.info(string.format("[TANKER] %s bingo fuel: %.1f%%", config.displayName, fuelPercent))
+
+ -- Low fuel warning
+ elseif fuelPercent <= config.fuelWarningPercent and not state.fuelWarned then
+ MESSAGE:New(GetRandomMessage("LOW_FUEL", config.displayName, math.floor(fuelPercent)), 15):ToBlue()
+ state.fuelWarned = true
+ env.info(string.format("[TANKER] %s low fuel warning: %.1f%%", config.displayName, fuelPercent))
+ end
+ end
+end
+
+--- Start (or restart) the fuel monitor scheduler for a tanker
+local function StartFuelMonitor(stateKey, config)
+ local state = TANKER_STATE[stateKey]
+ if not state then
+ return
+ end
+
+ if state.fuelMonitor then
+ state.fuelMonitor:Stop()
+ state.fuelMonitor = nil
+ end
+
+ state.fuelMonitor = SCHEDULER:New(
+ nil,
+ MonitorTankerFuel(stateKey, config),
+ {},
+ FUEL_CHECK_INTERVAL,
+ FUEL_CHECK_INTERVAL
+ )
+end
+
+--- Schedule auto-respawn after tanker loss
+local function ScheduleRespawn(stateKey, config, spawnFunc)
+ local state = TANKER_STATE[stateKey]
+
+ -- Cancel existing respawn if any
+ if state.respawnScheduler then
+ state.respawnScheduler:Stop()
+ end
+
+ local countdown = config.respawnDelay
+
+ MESSAGE:New(string.format("%s will respawn in %d seconds",
+ config.displayName, countdown), 10):ToBlue()
+
+ -- Respawn scheduler
+ state.respawnScheduler = SCHEDULER:New(nil, function()
+ env.info(string.format("[TANKER] Auto-respawning %s", config.displayName))
+ spawnFunc()
+ end, {}, config.respawnDelay)
+end
+
+--- Clean up tanker state
+local function CleanupTankerState(stateKey)
+ local state = TANKER_STATE[stateKey]
+
+ state.active = false
+ state.group = nil
+ state.fuelWarned = false
+ state.bingoWarned = false
+
+ if state.fuelMonitor then
+ state.fuelMonitor:Stop()
+ state.fuelMonitor = nil
+ end
+
+ if state.respawnScheduler then
+ state.respawnScheduler:Stop()
+ state.respawnScheduler = nil
+ end
+end
+
+-- ============================================================================
+-- CUSTOM ROUTE FUNCTIONS
+-- ============================================================================
+
+--- Parse waypoint marker text for altitude and speed overrides
+--- Supports formats: SHELL1, SHELL1:FL220, SHELL1:FL220:SP330, SHELL1::SP300, SHELL1:RTB
+--- @param markerText string The text from the map marker
+--- @param defaultAlt number Default altitude in feet
+--- @param defaultSpeed number Default speed in knots
+--- @return table Parsed waypoint data {altitude, speed, rtb, isValid}
+local function ParseWaypointMarker(markerText, defaultAlt, defaultSpeed)
+ local result = {
+ altitude = defaultAlt,
+ speed = defaultSpeed,
+ rtb = false,
+ isValid = true,
+ originalText = markerText
+ }
+
+ -- Split by colon
+ local parts = {}
+ for part in string.gmatch(markerText, "[^:]+") do
+ table.insert(parts, part)
+ end
+
+ -- Check for RTB command
+ for _, part in ipairs(parts) do
+ if string.upper(part) == "RTB" then
+ result.rtb = true
+ return result
+ end
+ end
+
+ -- Parse FL (Flight Level)
+ for _, part in ipairs(parts) do
+ local fl = string.match(part, "FL(%d+)")
+ if fl then
+ result.altitude = tonumber(fl) * 100 -- Convert FL to feet
+ end
+ end
+
+ -- Parse SP (Speed)
+ for _, part in ipairs(parts) do
+ local sp = string.match(part, "SP(%d+)")
+ if sp then
+ result.speed = tonumber(sp)
+ end
+ end
+
+ return result
+end
+
+--- Scan map for waypoint markers matching callsign pattern
+--- @param callsign string The callsign prefix to search for (e.g., "SHELL", "ARCO")
+--- @return table Array of waypoint data sorted by sequence number
+local function ScanForWaypointMarkers(callsign)
+ local waypoints = {}
+ local markerIds = {}
+
+ -- Iterate through all possible marker IDs (DCS markers are numbered)
+ -- We'll scan up to 1000 markers (should be more than enough)
+ for i = 1, 1000 do
+ local markerData = world.getMarkPanels()
+ if markerData and markerData[i] then
+ local marker = markerData[i]
+ local markerText = marker.text
+
+ if markerText then
+ -- Check if marker matches pattern: CALLSIGN + number
+ local upperText = string.upper(markerText)
+ local upperCallsign = string.upper(callsign)
+ local sequence = string.match(upperText, "^" .. upperCallsign .. "(%d+)")
+
+ if sequence then
+ local seqNum = tonumber(sequence)
+ local pos = marker.pos
+
+ table.insert(waypoints, {
+ sequence = seqNum,
+ coordinate = COORDINATE:NewFromVec3(pos),
+ markerId = marker.idx,
+ markerText = markerText
+ })
+
+ table.insert(markerIds, marker.idx)
+
+ env.info(string.format("[TANKER] Found waypoint marker: %s at seq %d (ID: %d)",
+ markerText, seqNum, marker.idx))
+ end
+ end
+ end
+ end
+
+ -- Sort by sequence number
+ table.sort(waypoints, function(a, b) return a.sequence < b.sequence end)
+
+ return waypoints, markerIds
+end
+
+-- ============================================================================
+
+--- Spawn a tanker directly from config (no Mission Editor template required)
+--- @param config table Tanker configuration
+--- @param coord COORDINATE Where to spawn
+--- @param heading number Initial heading in degrees
+--- @return GROUP The spawned tanker group
+local function SpawnTankerFromConfig(config, coord, heading)
+ -- Generate unique group/unit IDs
+ local groupId = math.random(10000, 99999)
+ local unitId = math.random(10000, 99999)
+
+ -- Ensure we have valid altitude (coord.y is altitude in meters MSL)
+ local spawnAlt = coord.y
+ env.info(string.format("[TANKER] Spawn altitude: %.1f meters (FL%03d)", spawnAlt, spawnAlt * 3.28084 / 100))
+
+ -- Create group data structure
+ local groupData = {
+ ["visible"] = false,
+ ["taskSelected"] = true,
+ ["route"] = {
+ ["points"] = {
+ [1] = {
+ ["alt"] = spawnAlt,
+ ["type"] = "Turning Point",
+ ["action"] = "Turning Point",
+ ["alt_type"] = "BARO",
+ ["speed"] = config.defaultSpeed * 0.514444,
+ ["task"] = {
+ ["id"] = "ComboTask",
+ ["params"] = {
+ ["tasks"] = {
+ [1] = {
+ ["id"] = "Tanker",
+ ["params"] = {}
+ }
+ }
+ }
+ },
+ ["x"] = coord.x,
+ ["y"] = coord.z,
+ }
+ }
+ },
+ ["hidden"] = false,
+ ["units"] = {
+ [1] = {
+ ["alt"] = spawnAlt,
+ ["alt_type"] = "BARO",
+ ["livery_id"] = config.livery,
+ ["skill"] = "High",
+ ["speed"] = config.defaultSpeed * 0.514444,
+ ["type"] = config.aircraftType,
+ ["unitId"] = unitId,
+ ["psi"] = -heading, -- Negative for correct heading
+ ["unitName"] = config.unitName,
+ ["x"] = coord.x,
+ ["y"] = coord.z,
+ ["heading"] = math.rad(heading),
+ ["onboard_num"] = "010",
+ },
+ },
+ ["groupId"] = groupId,
+ ["y"] = coord.z,
+ ["x"] = coord.x,
+ ["name"] = config.groupName,
+ ["task"] = "Refueling",
+ }
+
+ -- Spawn the group using coalition.addGroup
+ local spawnedGroup = coalition.addGroup(country.id.USA, Group.Category.AIRPLANE, groupData)
+
+ if spawnedGroup then
+ env.info(string.format("[TANKER] Spawned %s (ID: %d)", config.groupName, groupId))
+ return GROUP:Find(spawnedGroup:getName())
+ else
+ env.error(string.format("[TANKER] Failed to spawn %s", config.groupName))
+ return nil
+ end
+end
+
+--- Ensure default spawns immediately enter a holding pattern so they do not RTB
+--- @param group GROUP The spawned tanker group
+--- @param coord COORDINATE Center point for the orbit
+--- @param config table Tanker configuration for speed/altitude
+local function ApplyDefaultOrbitRoute(group, coord, config)
+ if not group or not coord or not config then
+ return
+ end
+
+ local orbitCenter = coord:SetAltitude(config.defaultAltitude * 0.3048, true)
+ local orbitWP = orbitCenter:WaypointAirTurningPoint(
+ COORDINATE.WaypointAltType.BARO,
+ config.defaultSpeed * 0.514444,
+ config.defaultAltitude * 0.3048,
+ {},
+ "DEFAULT-ORBIT"
+ )
+
+ orbitWP.task = {
+ id = "ComboTask",
+ params = {
+ tasks = {
+ {
+ id = "Tanker",
+ params = {}
+ },
+ {
+ id = "Orbit",
+ params = {
+ pattern = "Circle",
+ speed = config.defaultSpeed * 0.514444,
+ altitude = config.defaultAltitude * 0.3048,
+ point = {
+ x = orbitCenter.x,
+ y = orbitCenter.z
+ }
+ }
+ }
+ }
+ }
+ }
+
+ group:Route({ orbitWP })
+ env.info(string.format("[TANKER] Applied default orbit for %s", config.displayName))
+end
+
+--- Create custom route tanker spawn
+--- @param callsign string Callsign prefix used for markers
+--- @param config table Tanker configuration
+--- @param stateKey string State key for tracking
+--- @param isEmergency boolean Whether this is an emergency spawn
+--- @return boolean Success status
+local function SpawnCustomRouteTanker(callsign, config, stateKey, isEmergency)
+ local state = TANKER_STATE[stateKey]
+
+ -- Check if already active
+ if state.active then
+ MESSAGE:New(GetRandomMessage("ALREADY_ACTIVE", config.displayName), 10):ToBlue()
+ return false
+ end
+
+ -- Scan for waypoint markers
+ local waypoints, markerIds = ScanForWaypointMarkers(callsign)
+
+ -- Validate waypoint count
+ if #waypoints < ROUTE_CONFIG.minWaypoints then
+ MESSAGE:New(GetRandomMessage("TOO_FEW_WAYPOINTS",
+ ROUTE_CONFIG.minWaypoints, callsign, callsign), 15, "ERROR"):ToBlue()
+ return false
+ end
+
+ if #waypoints > ROUTE_CONFIG.maxWaypoints then
+ MESSAGE:New(GetRandomMessage("TOO_MANY_WAYPOINTS",
+ ROUTE_CONFIG.maxWaypoints), 15, "ERROR"):ToBlue()
+ return false
+ end
+
+ -- Build route description and validate waypoints
+ local routeDesc = ""
+ local routePoints = {}
+ local hasRTB = false
+
+ for i, wp in ipairs(waypoints) do
+ local parsed = ParseWaypointMarker(wp.markerText, config.defaultAltitude, config.defaultSpeed)
+
+ if parsed.rtb then
+ hasRTB = true
+ -- Find nearest friendly airbase from last waypoint position
+ local lastPos = #routePoints > 0 and routePoints[#routePoints].coord or wp.coordinate
+ local nearestAirbase = lastPos:GetClosestAirbase(Airbase.Category.AIRDROME, coalition.side.BLUE)
+
+ if nearestAirbase then
+ local airbaseName = nearestAirbase:GetName()
+ local airbaseCoord = nearestAirbase:GetCoordinate()
+ routeDesc = routeDesc .. string.format("\n WP%d: RTB to %s", i, airbaseName)
+
+ table.insert(routePoints, {
+ coord = airbaseCoord,
+ altitude = 0, -- Will land
+ speed = parsed.speed,
+ rtb = true,
+ airbase = nearestAirbase,
+ airbaseName = airbaseName
+ })
+
+ env.info(string.format("[TANKER] RTB destination: %s", airbaseName))
+ else
+ routeDesc = routeDesc .. string.format("\n WP%d: RTB (no airbase found)", i)
+ env.warning("[TANKER] No friendly airbase found for RTB")
+ end
+ break -- RTB is terminal command
+ else
+ routeDesc = routeDesc .. string.format("\n WP%d: FL%03d @ %d kts",
+ i, math.floor(parsed.altitude / 100), parsed.speed)
+
+ table.insert(routePoints, {
+ coord = wp.coordinate,
+ altitude = parsed.altitude,
+ speed = parsed.speed,
+ rtb = false
+ })
+ end
+ end
+
+ -- Confirm route to player
+ local emergencyText = isEmergency and " [EMERGENCY]" or ""
+ local routeMsg = GetRandomMessage("ROUTE_ACCEPTED", config.displayName, #routePoints, routeDesc)
+ if isEmergency then
+ routeMsg = GetRandomMessage("EMERGENCY_SPAWN", config.displayName) .. "\n" .. routeMsg
+ end
+ MESSAGE:New(routeMsg, 20):ToBlue()
+
+ env.info(string.format("[TANKER] Spawning %s with custom route: %d waypoints",
+ config.displayName, #routePoints))
+
+ -- Debug: log route point data
+ for i, rp in ipairs(routePoints) do
+ env.info(string.format("[TANKER] RoutePoint %d: coord=%s, alt=%.0f, spd=%.0f, rtb=%s",
+ i, tostring(rp.coord), rp.altitude, rp.speed, tostring(rp.rtb)))
+ end
+
+ -- Delete markers if configured
+ if ROUTE_CONFIG.deleteMarkersAfterUse then
+ for _, markerId in ipairs(markerIds) do
+ trigger.action.removeMark(markerId)
+ end
+ env.info(string.format("[TANKER] Deleted %d waypoint markers", #markerIds))
+ end
+
+ -- Spawn tanker with custom route
+ -- Calculate initial heading
+ local headingCoord
+ if routePoints[2] and routePoints[2].coord then
+ headingCoord = routePoints[2].coord
+ else
+ headingCoord = routePoints[1].coord
+ end
+
+ local initialHeading = routePoints[1].coord:HeadingTo(headingCoord)
+
+ -- Set the spawn coordinate with correct altitude (convert feet to meters)
+ local spawnCoord = routePoints[1].coord:SetAltitude(routePoints[1].altitude * 0.3048)
+
+ local spawnedGroup = SpawnTankerFromConfig(
+ config,
+ spawnCoord,
+ initialHeading
+ )
+
+ if not spawnedGroup then
+ MESSAGE:New(GetRandomMessage("SPAWN_FAILURE", config.displayName), 10, "ERROR"):ToBlue()
+ return false
+ end
+
+ -- Route the group through all waypoints
+ local taskRoute = {}
+ for i, rp in ipairs(routePoints) do
+ local wp
+
+ -- RTB waypoint - land at airbase
+ if rp.rtb and rp.airbase then
+ wp = rp.coord:WaypointAirLanding(
+ rp.speed * 0.514444,
+ rp.airbase:GetDCSObject(),
+ {},
+ "RTB"
+ )
+ else
+ -- Normal waypoint
+ wp = rp.coord:WaypointAirFlyOverPoint(
+ COORDINATE.WaypointAltType.BARO,
+ rp.speed * 0.514444, -- Convert knots to m/s
+ rp.altitude * 0.3048, -- Convert feet to meters
+ {},
+ "WP" .. i
+ )
+
+ -- Add tanker task to all waypoints
+ wp.task = {
+ id = "ComboTask",
+ params = {
+ tasks = {
+ {
+ id = "Tanker",
+ params = {}
+ }
+ }
+ }
+ }
+ end
+
+ table.insert(taskRoute, wp)
+ end
+
+ -- If last waypoint is not RTB, loop back to first waypoint to create continuous patrol
+ if not hasRTB and #routePoints > 1 then
+ local firstPoint = routePoints[1]
+ local loopWP = firstPoint.coord:WaypointAirFlyOverPoint(
+ COORDINATE.WaypointAltType.BARO,
+ firstPoint.speed * 0.514444,
+ firstPoint.altitude * 0.3048,
+ {},
+ "LOOP-WP1"
+ )
+
+ -- Add tanker task to loop waypoint
+ loopWP.task = {
+ id = "ComboTask",
+ params = {
+ tasks = {
+ {
+ id = "Tanker",
+ params = {}
+ }
+ }
+ }
+ }
+
+ table.insert(taskRoute, loopWP)
+ env.info(string.format("[TANKER] Added loop waypoint back to WP1 for continuous patrol"))
+ elseif not hasRTB and #routePoints == 1 then
+ -- Single waypoint - add circular orbit pattern
+ local singlePoint = routePoints[1]
+ local orbitWP = singlePoint.coord:WaypointAirTurningPoint(
+ COORDINATE.WaypointAltType.BARO,
+ singlePoint.speed * 0.514444,
+ singlePoint.altitude * 0.3048,
+ {},
+ "ORBIT"
+ )
+ orbitWP.task = {
+ id = "ComboTask",
+ params = {
+ tasks = {
+ {
+ id = "Tanker",
+ params = {}
+ },
+ {
+ id = "Orbit",
+ params = {
+ pattern = "Circle",
+ speed = singlePoint.speed * 0.514444,
+ altitude = singlePoint.altitude * 0.3048
+ }
+ }
+ }
+ }
+ }
+ table.insert(taskRoute, orbitWP)
+ env.info(string.format("[TANKER] Single waypoint - added circular orbit pattern"))
+ end
+
+ -- Apply route to group
+ spawnedGroup:Route(taskRoute)
+
+ -- Update state
+ local state = TANKER_STATE[stateKey]
+ state.active = true
+ state.group = spawnedGroup
+ state.fuelWarned = false
+ state.bingoWarned = false
+
+ -- Announce spawn with details
+ AnnounceTankerInfo(config, true)
+
+ -- Start fuel monitoring
+ StartFuelMonitor(stateKey, config)
+
+ -- Update menus
+ UpdateTankerMenus()
+
+ return true
+end
+
+-- ============================================================================
+-- EVENT HANDLER
+-- ============================================================================
+
BlueTankerEventHandler = EVENTHANDLER:New()
--- Handle the Birth event for Blue TANKER
function BlueTankerEventHandler:OnEventBirth(EventData)
- if EventData.IniDCSGroupName == "TANKER 135" then
- MESSAGE:New("TANKER has spawned!", 15):ToBlue()
- elseif EventData.IniDCSGroupName == "TANKER 135 MPRS" then
- MESSAGE:New("TANKER 135 MPRS has spawned!", 15):ToBlue()
+ local groupName = EventData.IniDCSGroupName
+
+ if groupName and string.find(groupName, "TANKER 135") then
+ env.info(string.format("[TANKER] Birth event: %s", groupName))
+
+ -- Determine which tanker spawned
+ local stateKey, config
+ if string.find(groupName, "MPRS") then
+ stateKey = "KC135_MPRS"
+ config = TANKER_CONFIG.KC135_MPRS
+ else
+ stateKey = "KC135"
+ config = TANKER_CONFIG.KC135
+ end
+
+ -- Update state
+ local state = TANKER_STATE[stateKey]
+ state.active = true
+ state.group = GROUP:FindByName(groupName)
+ state.fuelWarned = false
+ state.bingoWarned = false
+
+ -- Announce spawn with details
+ AnnounceTankerInfo(config, true)
+
+ -- Start fuel monitoring
+ StartFuelMonitor(stateKey, config)
+
+ -- Update menus
+ UpdateTankerMenus()
end
end
--- Handle the Dead event for Blue TANKER
function BlueTankerEventHandler:OnEventDead(EventData)
- if EventData.IniDCSGroupName == "TANKER 135" then
- MESSAGE:New("TANKER has been destroyed!", 15):ToBlue()
- elseif EventData.IniDCSGroupName == "TANKER 135 MPRS" then
- MESSAGE:New("TANKER 135 MPRS has been destroyed!", 15):ToBlue()
+ local groupName = EventData.IniDCSGroupName
+
+ if groupName and string.find(groupName, "TANKER 135") then
+ env.info(string.format("[TANKER] Dead event: %s", groupName))
+
+ -- Determine which tanker died
+ local stateKey, config, spawnFunc
+ if string.find(groupName, "MPRS") then
+ stateKey = "KC135_MPRS"
+ config = TANKER_CONFIG.KC135_MPRS
+ spawnFunc = SpawnTankerMPRS
+ else
+ stateKey = "KC135"
+ config = TANKER_CONFIG.KC135
+ spawnFunc = SpawnTanker
+ end
+
+ MESSAGE:New(GetRandomMessage("DESTROYED", config.displayName),
+ 15, "ALERT"):ToBlue()
+
+ -- Clean up and schedule respawn
+ CleanupTankerState(stateKey)
+ ScheduleRespawn(stateKey, config, spawnFunc)
+
+ -- Update menus
+ UpdateTankerMenus()
+ end
+end
+
+function BlueTankerEventHandler:OnEventCrash(EventData)
+ -- Treat crash same as dead
+ self:OnEventDead(EventData)
+end
+
+function BlueTankerEventHandler:OnEventEngineShutdown(EventData)
+ local groupName = EventData.IniDCSGroupName
+
+ if groupName and string.find(groupName, "TANKER 135") then
+ env.info(string.format("[TANKER] Engine shutdown event: %s", groupName))
+
+ -- Determine which tanker
+ local stateKey, config, spawnFunc
+ if string.find(groupName, "MPRS") then
+ stateKey = "KC135_MPRS"
+ config = TANKER_CONFIG.KC135_MPRS
+ spawnFunc = SpawnTankerMPRS
+ else
+ stateKey = "KC135"
+ config = TANKER_CONFIG.KC135
+ spawnFunc = SpawnTanker
+ end
+
+ MESSAGE:New(string.format("%s has returned to base", config.displayName),
+ 10):ToBlue()
+
+ -- Clean up and schedule respawn
+ CleanupTankerState(stateKey)
+ ScheduleRespawn(stateKey, config, spawnFunc)
+
+ -- Update menus
+ UpdateTankerMenus()
end
end
--- Handle the Hit event for Blue TANKER
function BlueTankerEventHandler:OnEventHit(EventData)
- if EventData.IniDCSGroupName == "TANKER 135" then
- MESSAGE:New("TANKER is under attack!", 15):ToBlue()
- elseif EventData.IniDCSGroupName == "TANKER 135 MPRS" then
- MESSAGE:New("TANKER 135 MPRS is under attack!", 15):ToBlue()
+ local groupName = EventData.IniDCSGroupName
+
+ if groupName and string.find(groupName, "TANKER 135") then
+ local config = string.find(groupName, "MPRS") and TANKER_CONFIG.KC135_MPRS or TANKER_CONFIG.KC135
+
+ MESSAGE:New(GetRandomMessage("TAKING_FIRE", config.displayName),
+ 15, "WARNING"):ToBlue()
+
+ env.info(string.format("[TANKER] %s hit by hostile fire", config.displayName))
end
end
--- Create the Blue TANKER spawn objects
-Blue_Tanker = SPAWN:New("TANKER 135")
- :InitLimit(1, 99)
+-- ============================================================================
+-- SPAWN OBJECTS AND FUNCTIONS
+-- ============================================================================
-Blue_Tanker_MPRS = SPAWN:New("TANKER 135 MPRS")
- :InitLimit(1, 99)
-
--- Function to spawn the tankers
+-- Function to spawn KC-135
function SpawnTanker()
- Blue_Tanker:Spawn()
+ if TANKER_STATE.KC135.active then
+ MESSAGE:New(GetRandomMessage("ALREADY_ACTIVE", TANKER_CONFIG.KC135.displayName), 10):ToBlue()
+ return
+ end
+
+ env.info("[TANKER] Spawning KC-135")
+ local spawnedGroup = SpawnTankerFromConfig(
+ TANKER_CONFIG.KC135,
+ DEFAULT_SPAWN_COORD,
+ 0 -- heading north
+ )
+
+ if spawnedGroup then
+ ApplyDefaultOrbitRoute(spawnedGroup, DEFAULT_SPAWN_COORD, TANKER_CONFIG.KC135)
+ TANKER_STATE.KC135.active = true
+ TANKER_STATE.KC135.group = spawnedGroup
+ AnnounceTankerInfo(TANKER_CONFIG.KC135, true)
+
+ -- Start fuel monitoring
+ StartFuelMonitor("KC135", TANKER_CONFIG.KC135)
+
+ UpdateTankerMenus()
+ else
+ MESSAGE:New(GetRandomMessage("SPAWN_FAILURE", TANKER_CONFIG.KC135.displayName), 10, "ERROR"):ToBlue()
+ end
end
+-- Function to spawn KC-135 MPRS
function SpawnTankerMPRS()
- Blue_Tanker_MPRS:Spawn()
+ if TANKER_STATE.KC135_MPRS.active then
+ MESSAGE:New(GetRandomMessage("ALREADY_ACTIVE", TANKER_CONFIG.KC135_MPRS.displayName), 10):ToBlue()
+ return
+ end
+
+ env.info("[TANKER] Spawning KC-135 MPRS")
+ local spawnedGroup = SpawnTankerFromConfig(
+ TANKER_CONFIG.KC135_MPRS,
+ DEFAULT_SPAWN_COORD,
+ 0 -- heading north
+ )
+
+ if spawnedGroup then
+ ApplyDefaultOrbitRoute(spawnedGroup, DEFAULT_SPAWN_COORD, TANKER_CONFIG.KC135_MPRS)
+ TANKER_STATE.KC135_MPRS.active = true
+ TANKER_STATE.KC135_MPRS.group = spawnedGroup
+ AnnounceTankerInfo(TANKER_CONFIG.KC135_MPRS, true)
+
+ -- Start fuel monitoring
+ StartFuelMonitor("KC135_MPRS", TANKER_CONFIG.KC135_MPRS)
+
+ UpdateTankerMenus()
+ else
+ MESSAGE:New(GetRandomMessage("SPAWN_FAILURE", TANKER_CONFIG.KC135_MPRS.displayName), 10, "ERROR"):ToBlue()
+ end
end
--- Create a mission menu for requesting the tankers
-MenuCoalitionBlue = MENU_COALITION:New(coalition.side.BLUE, "Request TANKER", missionMenu)
-MenuCoalitionBlueTanker = MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Launch TANKER 135", MenuCoalitionBlue, SpawnTanker)
-MenuCoalitionBlueTankerMPRS = MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Launch TANKER 135 MPRS", MenuCoalitionBlue, SpawnTankerMPRS)
+-- Function to spawn KC-135 with custom route
+function SpawnCustomTanker()
+ SpawnCustomRouteTanker(
+ TANKER_CONFIG.KC135.callsign,
+ TANKER_CONFIG.KC135,
+ "KC135",
+ false
+ )
+end
+
+-- Function to spawn KC-135 MPRS with custom route
+function SpawnCustomTankerMPRS()
+ SpawnCustomRouteTanker(
+ TANKER_CONFIG.KC135_MPRS.callsign,
+ TANKER_CONFIG.KC135_MPRS,
+ "KC135_MPRS",
+ false
+ )
+end
+
+-- Function to spawn emergency KC-135 with custom route
+function SpawnEmergencyTanker()
+ -- Use emergency respawn delay
+ local originalDelay = TANKER_CONFIG.KC135.respawnDelay
+ TANKER_CONFIG.KC135.respawnDelay = TANKER_CONFIG.KC135.emergencyRespawnDelay
+
+ local success = SpawnCustomRouteTanker(
+ TANKER_CONFIG.KC135.callsign,
+ TANKER_CONFIG.KC135,
+ "KC135",
+ true
+ )
+
+ -- Restore original delay
+ TANKER_CONFIG.KC135.respawnDelay = originalDelay
+
+ return success
+end
+
+-- Function to spawn emergency KC-135 MPRS with custom route
+function SpawnEmergencyTankerMPRS()
+ local originalDelay = TANKER_CONFIG.KC135_MPRS.respawnDelay
+ TANKER_CONFIG.KC135_MPRS.respawnDelay = TANKER_CONFIG.KC135_MPRS.emergencyRespawnDelay
+
+ local success = SpawnCustomRouteTanker(
+ TANKER_CONFIG.KC135_MPRS.callsign,
+ TANKER_CONFIG.KC135_MPRS,
+ "KC135_MPRS",
+ true
+ )
+
+ TANKER_CONFIG.KC135_MPRS.respawnDelay = originalDelay
+
+ return success
+end
+
+-- Function to display tanker status
+function ShowTankerStatus()
+ local msg = "=== TANKER STATUS ===\n\n"
+
+ -- KC-135 Status
+ local kc135State = TANKER_STATE.KC135
+ if kc135State.active and kc135State.group and kc135State.group:IsAlive() then
+ local fuel = kc135State.group:GetFuel() * 100
+ local coord = kc135State.group:GetCoordinate()
+ local alt = coord:GetLandHeight() + coord.y
+ msg = msg .. string.format("%s: ACTIVE\n", TANKER_CONFIG.KC135.displayName)
+ msg = msg .. string.format(" Fuel: %.0f%%\n", fuel)
+ msg = msg .. string.format(" Altitude: FL%03d\n", math.floor(alt * 3.28084 / 100))
+ if TANKER_CONFIG.KC135.tacan then
+ msg = msg .. string.format(" TACAN: %s\n", TANKER_CONFIG.KC135.tacan)
+ end
+ if TANKER_CONFIG.KC135.frequency then
+ msg = msg .. string.format(" Radio: %s MHz\n", TANKER_CONFIG.KC135.frequency)
+ end
+ else
+ msg = msg .. string.format("%s: NOT ACTIVE\n", TANKER_CONFIG.KC135.displayName)
+ end
+
+ msg = msg .. "\n"
+
+ -- KC-135 MPRS Status
+ local mprsState = TANKER_STATE.KC135_MPRS
+ if mprsState.active and mprsState.group and mprsState.group:IsAlive() then
+ local fuel = mprsState.group:GetFuel() * 100
+ local coord = mprsState.group:GetCoordinate()
+ local alt = coord:GetLandHeight() + coord.y
+ msg = msg .. string.format("%s: ACTIVE\n", TANKER_CONFIG.KC135_MPRS.displayName)
+ msg = msg .. string.format(" Fuel: %.0f%%\n", fuel)
+ msg = msg .. string.format(" Altitude: FL%03d\n", math.floor(alt * 3.28084 / 100))
+ if TANKER_CONFIG.KC135_MPRS.tacan then
+ msg = msg .. string.format(" TACAN: %s\n", TANKER_CONFIG.KC135_MPRS.tacan)
+ end
+ if TANKER_CONFIG.KC135_MPRS.frequency then
+ msg = msg .. string.format(" Radio: %s MHz\n", TANKER_CONFIG.KC135_MPRS.frequency)
+ end
+ else
+ msg = msg .. string.format("%s: NOT ACTIVE\n", TANKER_CONFIG.KC135_MPRS.displayName)
+ end
+
+ MESSAGE:New(msg, 25):ToBlue()
+end
+
+-- Function to show custom route help
+function ShowCustomRouteHelp()
+ local msg = "╔════════════════════════════════════════════╗\n"
+ msg = msg .. "║ TANKER MANAGEMENT SYSTEM - GUIDE ║\n"
+ msg = msg .. "╚════════════════════════════════════════════╝\n\n"
+
+ msg = msg .. "━━━ QUICK START ━━━\n\n"
+ msg = msg .. "1. SIMPLE SPAWN:\n"
+ msg = msg .. " • F10 → Tanker Management → Launch KC-135\n"
+ msg = msg .. " • Tanker spawns at default location (FL220)\n"
+ msg = msg .. " • Automatically orbits and provides refueling\n\n"
+
+ msg = msg .. "2. CUSTOM ROUTE SPAWN:\n"
+ msg = msg .. " • Place numbered F10 map markers\n"
+ msg = msg .. " • Launch from Custom Route menu\n"
+ msg = msg .. " • Tanker follows your waypoints\n\n"
+
+ msg = msg .. "━━━ AVAILABLE TANKERS ━━━\n\n"
+ msg = msg .. string.format("• %s (SHELL)\n", TANKER_CONFIG.KC135.displayName)
+ msg = msg .. string.format(" TACAN: %s | Radio: %s MHz\n",
+ TANKER_CONFIG.KC135.tacan or "N/A", TANKER_CONFIG.KC135.frequency or "N/A")
+ msg = msg .. string.format(" Marker Prefix: %s\n\n", TANKER_CONFIG.KC135.callsign)
+
+ msg = msg .. string.format("• %s (ARCO)\n", TANKER_CONFIG.KC135_MPRS.displayName)
+ msg = msg .. string.format(" TACAN: %s | Radio: %s MHz\n",
+ TANKER_CONFIG.KC135_MPRS.tacan or "N/A", TANKER_CONFIG.KC135_MPRS.frequency or "N/A")
+ msg = msg .. string.format(" Marker Prefix: %s\n\n", TANKER_CONFIG.KC135_MPRS.callsign)
+
+ msg = msg .. "━━━ CUSTOM ROUTE MARKERS ━━━\n\n"
+ msg = msg .. "BASIC USAGE:\n"
+ msg = msg .. " Place markers in sequence: SHELL1, SHELL2, SHELL3\n"
+ msg = msg .. " Minimum 2 waypoints required\n"
+ msg = msg .. " Defaults: FL220 @ 330 knots\n\n"
+
+ msg = msg .. "ADVANCED SYNTAX:\n"
+ msg = msg .. " SHELL1:FL180 → Altitude override\n"
+ msg = msg .. " SHELL2::SP300 → Speed override\n"
+ msg = msg .. " SHELL3:FL200:SP280 → Both overrides\n"
+ msg = msg .. " SHELL4:RTB → Return to nearest base\n\n"
+
+ msg = msg .. "EXAMPLES:\n"
+ msg = msg .. " Simple 3-point orbit:\n"
+ msg = msg .. " ARCO1, ARCO2, ARCO3\n\n"
+ msg = msg .. " High altitude route with RTB:\n"
+ msg = msg .. " SHELL1:FL280, SHELL2:FL280, SHELL3:RTB\n\n"
+ msg = msg .. " Low-level tanker track:\n"
+ msg = msg .. " ARCO1:FL120:SP250, ARCO2:FL120:SP250\n\n"
+
+ msg = msg .. "━━━ REROUTING ACTIVE TANKERS ━━━\n\n"
+ msg = msg .. "Change an active tanker's route mid-mission:\n"
+ msg = msg .. " 1. Place new waypoint markers\n"
+ msg = msg .. " 2. F10 → Custom Route → Reroute Active Tanker\n"
+ msg = msg .. " 3. Tanker immediately follows new route\n\n"
+
+ msg = msg .. "Use cases:\n"
+ msg = msg .. " • Reposition for different theater\n"
+ msg = msg .. " • Avoid threat areas\n"
+ msg = msg .. " • Send tanker home (use :RTB)\n\n"
+
+ msg = msg .. "━━━ NOTES ━━━\n\n"
+ msg = msg .. "• Markers are auto-deleted after use\n"
+ msg = msg .. "• Tankers auto-respawn after 3 minutes if lost\n"
+ msg = msg .. "• Use Emergency Tanker for 1-minute respawn\n"
+ msg = msg .. "• RTB finds nearest friendly airbase & lands\n"
+ msg = msg .. "• Check Tanker Status for current position/fuel\n"
+
+ MESSAGE:New(msg, 45):ToBlue()
+end
+
+-- Function to reroute an active tanker with new waypoints
+function RerouteTanker()
+ if not TANKER_STATE.KC135.active or not TANKER_STATE.KC135.group then
+ MESSAGE:New("KC-135 is not active! Spawn it first.", 10):ToBlue()
+ return
+ end
+
+ -- Scan for waypoint markers
+ local waypoints, markerIds = ScanForWaypointMarkers(TANKER_CONFIG.KC135.callsign)
+
+ if #waypoints < ROUTE_CONFIG.minWaypoints then
+ MESSAGE:New(string.format("Reroute requires at least %d waypoints!\nPlace markers: %s1, %s2, etc.",
+ ROUTE_CONFIG.minWaypoints, TANKER_CONFIG.KC135.callsign, TANKER_CONFIG.KC135.callsign), 15, "ERROR"):ToBlue()
+ return
+ end
+
+ -- Build new route
+ local routePoints = {}
+ local routeDesc = ""
+ local hasRTB = false
+
+ for i, wp in ipairs(waypoints) do
+ local parsed = ParseWaypointMarker(wp.markerText, TANKER_CONFIG.KC135.defaultAltitude, TANKER_CONFIG.KC135.defaultSpeed)
+
+ if parsed.rtb then
+ hasRTB = true
+ local lastPos = #routePoints > 0 and routePoints[#routePoints].coord or TANKER_STATE.KC135.group:GetCoordinate()
+ local nearestAirbase = lastPos:GetClosestAirbase(Airbase.Category.AIRDROME, coalition.side.BLUE)
+
+ if nearestAirbase then
+ local airbaseName = nearestAirbase:GetName()
+ local airbaseCoord = nearestAirbase:GetCoordinate()
+ routeDesc = routeDesc .. string.format("\n WP%d: RTB to %s", i, airbaseName)
+
+ table.insert(routePoints, {
+ coord = airbaseCoord,
+ altitude = 0,
+ speed = parsed.speed,
+ rtb = true,
+ airbase = nearestAirbase,
+ airbaseName = airbaseName
+ })
+ end
+ break
+ else
+ routeDesc = routeDesc .. string.format("\n WP%d: FL%03d @ %d kts",
+ i, math.floor(parsed.altitude / 100), parsed.speed)
+ table.insert(routePoints, {
+ coord = wp.coordinate,
+ altitude = parsed.altitude,
+ speed = parsed.speed,
+ rtb = false
+ })
+ end
+ end
+
+ -- Build task route
+ local taskRoute = {}
+ for i, rp in ipairs(routePoints) do
+ local wp
+
+ if rp.rtb and rp.airbase then
+ wp = rp.coord:WaypointAirLanding(
+ rp.speed * 0.514444,
+ rp.airbase:GetDCSObject(),
+ {},
+ "RTB"
+ )
+ else
+ wp = rp.coord:WaypointAirFlyOverPoint(
+ COORDINATE.WaypointAltType.BARO,
+ rp.speed * 0.514444,
+ rp.altitude * 0.3048,
+ {},
+ "WP" .. i
+ )
+
+ if not rp.rtb then
+ wp.task = {
+ id = "ComboTask",
+ params = {
+ tasks = {
+ {id = "Tanker", params = {}}
+ }
+ }
+ }
+ end
+ end
+
+ table.insert(taskRoute, wp)
+ end
+
+ -- Apply new route
+ TANKER_STATE.KC135.group:Route(taskRoute)
+
+ MESSAGE:New(string.format("%s accepting new route with %d waypoints:%s",
+ TANKER_CONFIG.KC135.displayName, #routePoints, routeDesc), 20):ToBlue()
+
+ -- Delete markers
+ if ROUTE_CONFIG.deleteMarkersAfterUse then
+ for _, markerId in ipairs(markerIds) do
+ trigger.action.removeMark(markerId)
+ end
+ end
+
+ env.info(string.format("[TANKER] Rerouted %s with %d waypoints", TANKER_CONFIG.KC135.displayName, #routePoints))
+end
+
+-- Function to reroute KC-135 MPRS
+function RerouteTankerMPRS()
+ if not TANKER_STATE.KC135_MPRS.active or not TANKER_STATE.KC135_MPRS.group then
+ MESSAGE:New("KC-135 MPRS is not active! Spawn it first.", 10):ToBlue()
+ return
+ end
+
+ local waypoints, markerIds = ScanForWaypointMarkers(TANKER_CONFIG.KC135_MPRS.callsign)
+
+ if #waypoints < ROUTE_CONFIG.minWaypoints then
+ MESSAGE:New(string.format("Reroute requires at least %d waypoints!\nPlace markers: %s1, %s2, etc.",
+ ROUTE_CONFIG.minWaypoints, TANKER_CONFIG.KC135_MPRS.callsign, TANKER_CONFIG.KC135_MPRS.callsign), 15, "ERROR"):ToBlue()
+ return
+ end
+
+ local routePoints = {}
+ local routeDesc = ""
+ local hasRTB = false
+
+ for i, wp in ipairs(waypoints) do
+ local parsed = ParseWaypointMarker(wp.markerText, TANKER_CONFIG.KC135_MPRS.defaultAltitude, TANKER_CONFIG.KC135_MPRS.defaultSpeed)
+
+ if parsed.rtb then
+ hasRTB = true
+ local lastPos = #routePoints > 0 and routePoints[#routePoints].coord or TANKER_STATE.KC135_MPRS.group:GetCoordinate()
+ local nearestAirbase = lastPos:GetClosestAirbase(Airbase.Category.AIRDROME, coalition.side.BLUE)
+
+ if nearestAirbase then
+ local airbaseName = nearestAirbase:GetName()
+ local airbaseCoord = nearestAirbase:GetCoordinate()
+ routeDesc = routeDesc .. string.format("\n WP%d: RTB to %s", i, airbaseName)
+
+ table.insert(routePoints, {
+ coord = airbaseCoord,
+ altitude = 0,
+ speed = parsed.speed,
+ rtb = true,
+ airbase = nearestAirbase,
+ airbaseName = airbaseName
+ })
+ end
+ break
+ else
+ routeDesc = routeDesc .. string.format("\n WP%d: FL%03d @ %d kts",
+ i, math.floor(parsed.altitude / 100), parsed.speed)
+ table.insert(routePoints, {
+ coord = wp.coordinate,
+ altitude = parsed.altitude,
+ speed = parsed.speed,
+ rtb = false
+ })
+ end
+ end
+
+ local taskRoute = {}
+ for i, rp in ipairs(routePoints) do
+ local wp
+
+ if rp.rtb and rp.airbase then
+ wp = rp.coord:WaypointAirLanding(
+ rp.speed * 0.514444,
+ rp.airbase:GetDCSObject(),
+ {},
+ "RTB"
+ )
+ else
+ wp = rp.coord:WaypointAirFlyOverPoint(
+ COORDINATE.WaypointAltType.BARO,
+ rp.speed * 0.514444,
+ rp.altitude * 0.3048,
+ {},
+ "WP" .. i
+ )
+
+ if not rp.rtb then
+ wp.task = {
+ id = "ComboTask",
+ params = {
+ tasks = {
+ {id = "Tanker", params = {}}
+ }
+ }
+ }
+ end
+ end
+
+ table.insert(taskRoute, wp)
+ end
+
+ TANKER_STATE.KC135_MPRS.group:Route(taskRoute)
+
+ MESSAGE:New(string.format("%s accepting new route with %d waypoints:%s",
+ TANKER_CONFIG.KC135_MPRS.displayName, #routePoints, routeDesc), 20):ToBlue()
+
+ if ROUTE_CONFIG.deleteMarkersAfterUse then
+ for _, markerId in ipairs(markerIds) do
+ trigger.action.removeMark(markerId)
+ end
+ end
+
+ env.info(string.format("[TANKER] Rerouted %s with %d waypoints", TANKER_CONFIG.KC135_MPRS.displayName, #routePoints))
+end
+
+-- ============================================================================
+-- MISSION MENU SETUP
+-- ============================================================================
+
+-- Create mission menu for tanker requests
+-- Integrates with MenuManager to place under "Mission Options"
+-- This keeps CTLD at F2 and AFAC at F3 as intended
+if MenuManager and MenuManager.CreateCoalitionMenu then
+ -- Use MenuManager to create menu under "Mission Options"
+ MENU_TANKER_ROOT = MenuManager.CreateCoalitionMenu(coalition.side.BLUE, "Tanker Operations")
+ env.info("[TANKER] Using MenuManager - menu created under Mission Options")
+else
+ -- Fallback: create root menu if MenuManager not available
+ MENU_TANKER_ROOT = MENU_COALITION:New(coalition.side.BLUE, "Tanker Operations")
+ env.warning("[TANKER] MenuManager not found - creating root menu (load MenuManager first!)")
+end
+
+-- Standard tanker spawns
+MENU_KC135_LAUNCH = MENU_COALITION_COMMAND:New(
+ coalition.side.BLUE,
+ "Launch " .. TANKER_CONFIG.KC135.displayName,
+ MENU_TANKER_ROOT,
+ SpawnTanker
+)
+
+MENU_KC135_MPRS_LAUNCH = MENU_COALITION_COMMAND:New(
+ coalition.side.BLUE,
+ "Launch " .. TANKER_CONFIG.KC135_MPRS.displayName,
+ MENU_TANKER_ROOT,
+ SpawnTankerMPRS
+)
+
+-- Custom route submenu
+local MENU_CUSTOM_ROUTE = MENU_COALITION:New(
+ coalition.side.BLUE,
+ "Custom Route",
+ MENU_TANKER_ROOT
+)
+
+MENU_COALITION_COMMAND:New(
+ coalition.side.BLUE,
+ "How to Use Custom Routes",
+ MENU_CUSTOM_ROUTE,
+ ShowCustomRouteHelp
+)
+
+MENU_COALITION_COMMAND:New(
+ coalition.side.BLUE,
+ string.format("Launch %s (%s markers)", TANKER_CONFIG.KC135.displayName, TANKER_CONFIG.KC135.callsign),
+ MENU_CUSTOM_ROUTE,
+ SpawnCustomTanker
+)
+
+MENU_COALITION_COMMAND:New(
+ coalition.side.BLUE,
+ string.format("Launch %s (%s markers)", TANKER_CONFIG.KC135_MPRS.displayName, TANKER_CONFIG.KC135_MPRS.callsign),
+ MENU_CUSTOM_ROUTE,
+ SpawnCustomTankerMPRS
+)
+
+-- Reroute submenu for changing active tanker routes
+local MENU_REROUTE = MENU_COALITION:New(
+ coalition.side.BLUE,
+ "Reroute Active Tanker",
+ MENU_CUSTOM_ROUTE
+)
+
+MENU_COALITION_COMMAND:New(
+ coalition.side.BLUE,
+ string.format("Reroute %s (%s markers)", TANKER_CONFIG.KC135.displayName, TANKER_CONFIG.KC135.callsign),
+ MENU_REROUTE,
+ RerouteTanker
+)
+
+MENU_COALITION_COMMAND:New(
+ coalition.side.BLUE,
+ string.format("Reroute %s (%s markers)", TANKER_CONFIG.KC135_MPRS.displayName, TANKER_CONFIG.KC135_MPRS.callsign),
+ MENU_REROUTE,
+ RerouteTankerMPRS
+)
+
+-- Emergency spawns submenu
+local MENU_EMERGENCY = MENU_COALITION:New(
+ coalition.side.BLUE,
+ "Emergency Tanker",
+ MENU_TANKER_ROOT
+)
+
+MENU_COALITION_COMMAND:New(
+ coalition.side.BLUE,
+ string.format("Emergency %s (%s markers)", TANKER_CONFIG.KC135.displayName, TANKER_CONFIG.KC135.callsign),
+ MENU_EMERGENCY,
+ SpawnEmergencyTanker
+)
+
+MENU_COALITION_COMMAND:New(
+ coalition.side.BLUE,
+ string.format("Emergency %s (%s markers)", TANKER_CONFIG.KC135_MPRS.displayName, TANKER_CONFIG.KC135_MPRS.callsign),
+ MENU_EMERGENCY,
+ SpawnEmergencyTankerMPRS
+)
+
+-- Status and info
+MENU_COALITION_COMMAND:New(
+ coalition.side.BLUE,
+ "Tanker Status Report",
+ MENU_TANKER_ROOT,
+ ShowTankerStatus
+)
+
+-- ============================================================================
+-- EVENT HANDLER REGISTRATION
+-- ============================================================================
--- Add the event handler to the Blue TANKER group
BlueTankerEventHandler:HandleEvent(EVENTS.Birth)
BlueTankerEventHandler:HandleEvent(EVENTS.Dead)
-BlueTankerEventHandler:HandleEvent(EVENTS.Hit)
\ No newline at end of file
+BlueTankerEventHandler:HandleEvent(EVENTS.Crash)
+BlueTankerEventHandler:HandleEvent(EVENTS.EngineShutdown)
+BlueTankerEventHandler:HandleEvent(EVENTS.Hit)
+
+env.info("[TANKER] Tanker Management System initialized")
+MESSAGE:New("Tanker Management System online - Use F10 menu to request tankers", 15):ToBlue()
\ No newline at end of file
diff --git a/DCS_Afgainistan/Insurgent_Sandstorm/test.miz b/DCS_Afgainistan/Insurgent_Sandstorm/test.miz
deleted file mode 100644
index ab05969..0000000
Binary files a/DCS_Afgainistan/Insurgent_Sandstorm/test.miz and /dev/null differ
diff --git a/DCS_Afgainistan/src/main.lua b/DCS_Afgainistan/src/main.lua
deleted file mode 100644
index 1c67795..0000000
--- a/DCS_Afgainistan/src/main.lua
+++ /dev/null
@@ -1,4 +0,0 @@
-local function main()
-
-end
-main()
diff --git a/Moose_.lua b/Moose_.lua
index 6a99c26..c5a964c 100644
--- a/Moose_.lua
+++ b/Moose_.lua
@@ -1,4 +1,4 @@
-env.info('*** MOOSE GITHUB Commit Hash ID: 2025-11-14T17:27:02+01:00-cdbf1e147e76dcfab3d1bc471edf593a0e92182a ***')
+env.info('*** MOOSE GITHUB Commit Hash ID: 2025-11-16T16:54:04+01:00-5d1123e7df5a5578924c48a5dd93386739269191 ***')
if not MOOSE_DEVELOPMENT_FOLDER then
MOOSE_DEVELOPMENT_FOLDER='Scripts'
end
@@ -19229,41 +19229,6 @@ return nil
end
end
do
-POINT_VEC3={
-ClassName="POINT_VEC3",
-Metric=true,
-RoutePointAltType={
-BARO="BARO",
-},
-RoutePointType={
-TakeOffParking="TakeOffParking",
-TurningPoint="Turning Point",
-},
-RoutePointAction={
-FromParkingArea="From Parking Area",
-TurningPoint="Turning Point",
-},
-}
-function POINT_VEC3:New(x,y,z)
-local self=BASE:Inherit(self,COORDINATE:New(x,y,z))
-self:F2(self)
-return self
-end
-end
-do
-POINT_VEC2={
-ClassName="POINT_VEC2",
-}
-function POINT_VEC2:New(x,y,LandHeightAdd)
-local LandHeight=land.getHeight({["x"]=x,["y"]=y})
-LandHeightAdd=LandHeightAdd or 0
-LandHeight=LandHeight+LandHeightAdd
-local self=BASE:Inherit(self,COORDINATE:New(x,LandHeight,y))
-self:F2(self)
-return self
-end
-end
-do
VELOCITY={
ClassName="VELOCITY",
}
@@ -61930,6 +61895,265 @@ end
self:I({"Detected client spawn and applied internal functions and events.",PlayerName=self.PlayerName,UnitName=self.UnitName,GroupName=self.GroupName})
return self
end
+FORMATION={
+ClassName="FORMATION",
+FollowName=nil,
+FollowUnit=nil,
+FollowGroupSet=nil,
+FollowScheduler=nil,
+OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE,
+OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION,
+dtFollow=0.5,
+}
+FORMATION.Formation={
+None=1,
+Line=2,
+Trail=3,
+Stack=4,
+LeftLine=5,
+RightLine=6,
+LeftWing=7,
+RightWing=8,
+Vic=9,
+Box=10,
+}
+function FORMATION:New(FollowUnit,FollowGroupSet,FollowName)
+local self=BASE:Inherit(self,FSM_SET:New(FollowGroupSet))
+self:F({FollowUnit,FollowGroupSet,FollowName})
+self.FollowUnit=FollowUnit
+self.FollowGroupSet=FollowGroupSet
+self:SetFlightRandomization(2)
+self:SetStartState("None")
+self:AddTransition("*","Stop","Stopped")
+self:AddTransition({"None","Stopped"},"Start","Following")
+self:AddTransition("*","FormationLine","*")
+self:AddTransition("*","FormationTrail","*")
+self:AddTransition("*","FormationStack","*")
+self:AddTransition("*","FormationLeftLine","*")
+self:AddTransition("*","FormationRightLine","*")
+self:AddTransition("*","FormationLeftWing","*")
+self:AddTransition("*","FormationRightWing","*")
+self:AddTransition("*","FormationCenterWing","*")
+self:AddTransition("*","FormationVic","*")
+self:AddTransition("*","FormationBox","*")
+self:AddTransition("*","Follow","Following")
+self:FormationLeftLine(500,0,250,250)
+self.FollowName=FollowName
+self.CT1=0
+self.GT1=0
+return self
+end
+function FORMATION:SetFollowTimeInterval(dt)
+self.dtFollow=dt or 0.5
+return self
+end
+function FORMATION:TestSmokeDirectionVector(SmokeDirection)
+self.SmokeDirectionVector=(SmokeDirection==true)and true or false
+return self
+end
+function FORMATION:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,Formation)
+self:F({FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,Formation})
+XStart=XStart or self.XStart
+XSpace=XSpace or self.XSpace
+YStart=YStart or self.YStart
+YSpace=YSpace or self.YSpace
+ZStart=ZStart or self.ZStart
+ZSpace=ZSpace or self.ZSpace
+local FollowSet=FollowGroupSet:GetSet()
+local i=1
+for FollowID,FollowGroup in pairs(FollowSet)do
+local PointVec3=COORDINATE:New()
+PointVec3:SetX(XStart+i*XSpace)
+PointVec3:SetY(YStart+i*YSpace)
+PointVec3:SetZ(ZStart+i*ZSpace)
+local Vec3=PointVec3:GetVec3()
+FollowGroup:SetState(self,"FormationVec3",Vec3)
+i=i+1
+FollowGroup:SetState(FollowGroup,"Formation",Formation)
+end
+return self
+end
+function FORMATION:onafterFormationTrail(FollowGroupSet,From,Event,To,XStart,XSpace,YStart)
+self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,0,0,self.Formation.Trail)
+return self
+end
+function FORMATION:onafterFormationStack(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace)
+self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,0,0,self.Formation.Stack)
+return self
+end
+function FORMATION:onafterFormationLeftLine(FollowGroupSet,From,Event,To,XStart,YStart,ZStart,ZSpace)
+self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,-ZStart,-ZSpace,self.Formation.LeftLine)
+return self
+end
+function FORMATION:onafterFormationRightLine(FollowGroupSet,From,Event,To,XStart,YStart,ZStart,ZSpace)
+self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,ZStart,ZSpace,self.Formation.RightLine)
+return self
+end
+function FORMATION:onafterFormationLeftWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,ZStart,ZSpace)
+self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,-ZStart,-ZSpace,self.Formation.LeftWing)
+return self
+end
+function FORMATION:onafterFormationRightWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,ZStart,ZSpace)
+self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,ZStart,ZSpace,self.Formation.RightWing)
+return self
+end
+function FORMATION:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
+local FollowSet=FollowGroupSet:GetSet()
+local i=0
+for FollowID,FollowGroup in pairs(FollowSet)do
+local PointVec3=COORDINATE:New()
+local Side=(i%2==0)and 1 or-1
+local Row=i/2+1
+PointVec3:SetX(XStart+Row*XSpace)
+PointVec3:SetY(YStart)
+PointVec3:SetZ(Side*(ZStart+i*ZSpace))
+local Vec3=PointVec3:GetVec3()
+FollowGroup:SetState(self,"FormationVec3",Vec3)
+i=i+1
+FollowGroup:SetState(FollowGroup,"Formation",self.Formation.Vic)
+end
+return self
+end
+function FORMATION:onafterFormationVic(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
+self:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace)
+return self
+end
+function FORMATION:onafterFormationBox(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels)
+local FollowSet=FollowGroupSet:GetSet()
+local i=0
+for FollowID,FollowGroup in pairs(FollowSet)do
+local PointVec3=COORDINATE:New()
+local ZIndex=i%ZLevels
+local XIndex=math.floor(i/ZLevels)
+local YIndex=math.floor(i/ZLevels)
+PointVec3:SetX(XStart+XIndex*XSpace)
+PointVec3:SetY(YStart+YIndex*YSpace)
+PointVec3:SetZ(-ZStart-(ZSpace*ZLevels/2)+ZSpace*ZIndex)
+local Vec3=PointVec3:GetVec3()
+FollowGroup:SetState(self,"FormationVec3",Vec3)
+i=i+1
+FollowGroup:SetState(FollowGroup,"Formation",self.Formation.Box)
+end
+return self
+end
+function FORMATION:SetFlightRandomization(FlightRandomization)
+self.FlightRandomization=FlightRandomization
+return self
+end
+function FORMATION:onafterStop(FollowGroupSet,From,Event,To)
+self:E("Stopping formation.")
+end
+function FORMATION:onbeforeFollow(FollowGroupSet,From,Event,To)
+if From=="Stopped"then
+return false
+end
+return true
+end
+function FORMATION:onenterFollowing(FollowGroupSet)
+if self.FollowUnit:IsAlive()then
+local ClientUnit=self.FollowUnit
+local CT1,CT2,CV1,CV2
+CT1=ClientUnit:GetState(self,"CT1")
+local CuVec3=ClientUnit:GetVec3()
+if CT1==nil or CT1==0 then
+ClientUnit:SetState(self,"CV1",CuVec3)
+ClientUnit:SetState(self,"CT1",timer.getTime())
+else
+CT1=ClientUnit:GetState(self,"CT1")
+CT2=timer.getTime()
+CV1=ClientUnit:GetState(self,"CV1")
+CV2=CuVec3
+ClientUnit:SetState(self,"CT1",CT2)
+ClientUnit:SetState(self,"CV1",CV2)
+end
+for _,_group in pairs(FollowGroupSet:GetSet())do
+local group=_group
+if group and group:IsAlive()then
+self:FollowMe(group,ClientUnit,CT1,CV1,CT2,CV2)
+end
+end
+self:__Follow(-self.dtFollow)
+end
+end
+function FORMATION:FollowMe(FollowGroup,ClientUnit,CT1,CV1,CT2,CV2)
+if not self:Is("Stopped")then
+self:T({Mode=FollowGroup:GetState(FollowGroup,"Mode")})
+FollowGroup:OptionROTEvadeFire()
+FollowGroup:OptionROEReturnFire()
+local GroupUnit=FollowGroup:GetUnit(1)
+local GuVec3=GroupUnit:GetVec3()
+local FollowFormation=FollowGroup:GetState(self,"FormationVec3")
+if FollowFormation then
+local FollowDistance=FollowFormation.x
+local GT1=GroupUnit:GetState(self,"GT1")
+if CT1==nil or CT1==0 or GT1==nil or GT1==0 then
+GroupUnit:SetState(self,"GV1",GuVec3)
+GroupUnit:SetState(self,"GT1",timer.getTime())
+else
+local CD=((CV2.x-CV1.x)^2+(CV2.y-CV1.y)^2+(CV2.z-CV1.z)^2)^0.5
+local CT=CT2-CT1
+local CS=(3600/CT)*(CD/1000)/3.6
+local CDv={x=CV2.x-CV1.x,y=CV2.y-CV1.y,z=CV2.z-CV1.z}
+local Ca=math.atan2(CDv.x,CDv.z)
+local GT1=GroupUnit:GetState(self,"GT1")
+local GT2=timer.getTime()
+local GV1=GroupUnit:GetState(self,"GV1")
+local GV2=GuVec3
+GV2.x=GV2.x+math.random(-self.FlightRandomization/2,self.FlightRandomization/2)
+GV2.y=GV2.y+math.random(-self.FlightRandomization/2,self.FlightRandomization/2)
+GV2.z=GV2.z+math.random(-self.FlightRandomization/2,self.FlightRandomization/2)
+GroupUnit:SetState(self,"GT1",GT2)
+GroupUnit:SetState(self,"GV1",GV2)
+local GD=((GV2.x-GV1.x)^2+(GV2.y-GV1.y)^2+(GV2.z-GV1.z)^2)^0.5
+local GT=GT2-GT1
+local GDv={x=GV2.x-CV1.x,y=GV2.y-CV1.y,z=GV2.z-CV1.z}
+local Alpha_T=math.atan2(GDv.x,GDv.z)-math.atan2(CDv.x,CDv.z)
+local Alpha_R=(Alpha_T<0)and Alpha_T+2*math.pi or Alpha_T
+local Position=math.cos(Alpha_R)
+local GD=((GDv.x)^2+(GDv.z)^2)^0.5
+local Distance=GD*Position+-CS*0.5
+local GV={x=GV2.x-CV2.x,y=GV2.y-CV2.y,z=GV2.z-CV2.z}
+local GH2={x=GV2.x,y=CV2.y+FollowFormation.y,z=GV2.z}
+local alpha=math.atan2(GV.x,GV.z)
+local GVx=FollowFormation.z*math.cos(Ca)+FollowFormation.x*math.sin(Ca)
+local GVz=FollowFormation.x*math.cos(Ca)-FollowFormation.z*math.sin(Ca)
+local Inclination=(Distance+FollowFormation.x)/10
+if Inclination<-30 then
+Inclination=-30
+end
+local CVI={
+x=CV2.x+CS*10*math.sin(Ca),
+y=GH2.y+Inclination,
+z=CV2.z+CS*10*math.cos(Ca),
+}
+local DV={x=CV2.x-CVI.x,y=CV2.y-CVI.y,z=CV2.z-CVI.z}
+local DVu={x=DV.x/FollowDistance,y=DV.y,z=DV.z/FollowDistance}
+local GDV={x=CVI.x,y=CVI.y,z=CVI.z}
+local ADDx=FollowFormation.x*math.cos(alpha)-FollowFormation.z*math.sin(alpha)
+local ADDz=FollowFormation.z*math.cos(alpha)+FollowFormation.x*math.sin(alpha)
+local GDV_Formation={
+x=GDV.x-GVx,
+y=GDV.y,
+z=GDV.z-GVz
+}
+if self.SmokeDirectionVector==true then
+trigger.action.smoke(GDV,trigger.smokeColor.Green)
+trigger.action.smoke(GDV_Formation,trigger.smokeColor.White)
+end
+local Time=120
+local Speed=-(Distance+FollowFormation.x)/Time
+if Distance>-10000 then
+Speed=-(Distance+FollowFormation.x)/60
+end
+if Distance>-2500 then
+Speed=-(Distance+FollowFormation.x)/20
+end
+local GS=Speed+CS
+FollowGroup:RouteToVec3(GDV_Formation,GS)
+end
+end
+end
+end
AIRBOSS={
ClassName="AIRBOSS",
Debug=false,
@@ -62058,6 +62282,7 @@ AV8B="AV8BNA",
HORNET="FA-18C_hornet",
A4EC="A-4E-C",
F14A="F-14A-135-GR",
+F14A_Early="F-14A-135-GR-Early",
F14B="F-14B",
F14A_AI="F-14A",
FA18C="F/A-18C",
@@ -64024,7 +64249,7 @@ or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER
local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C
local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC
local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
-local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B
+local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early
local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW
local aoa={}
if hornet then
@@ -64080,7 +64305,7 @@ return aoa
end
function AIRBOSS:_AoAUnit2Deg(playerData,aoaunits)
local degrees=aoaunits
-if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
+if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then
degrees=-10+50/30*aoaunits
degrees=0.918*aoaunits-3.411
elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then
@@ -64090,7 +64315,7 @@ return degrees
end
function AIRBOSS:_AoADeg2Units(playerData,degrees)
local aoaunits=degrees
-if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
+if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then
aoaunits=(degrees+10)*30/50
aoaunits=1.089*degrees+3.715
elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then
@@ -67061,7 +67286,7 @@ local lueWire=self:_LineupWIRE(playerData.unit,true)
text=text..string.format("\nLineUpForWireCalls=%.2f° | lineup for Groove calls=%.2f°",lueWire or 0,lue or 0)
local unitClient=Unit.getByName(unit:GetName())
local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET
-local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B
+local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early
if hornet then
local nozzlePosL=0
local burnerPosL=unitClient:getDrawArgumentValue(28)
@@ -67177,7 +67402,7 @@ nozzlePosL=unitClient:getDrawArgumentValue(89)
else
nozzlePosL=0
end
-elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then
+elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then
nozzlePosL=unitClient:getDrawArgumentValue(434)
end
return nozzlePosL
@@ -67194,7 +67419,7 @@ nozzlePosR=unitClient:getDrawArgumentValue(90)
else
nozzlePosR=0
end
-elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then
+elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then
nozzlePosR=unitClient:getDrawArgumentValue(433)
end
return nozzlePosR
@@ -68770,7 +68995,7 @@ elseif actype==AIRBOSS.AircraftCarrier.E2D then
nickname="Hawkeye"
elseif actype==AIRBOSS.AircraftCarrier.C2A then
nickname="Greyhound"
-elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B then
+elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B or actype==AIRBOSS.AircraftCarrier.F14A_Early then
nickname="Tomcat"
elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then
nickname="Hornet"
@@ -71953,10 +72178,9 @@ end
self.followset=SET_GROUP:New()
self.followset:AddGroup(self.helo)
self.HeloFuel0=self.helo:GetFuel()
-self.formation=AI_FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier","Follow Carrier at given parameters.")
+self.formation=FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier")
self.formation:FormationCenterWing(-self.offsetX,50,math.abs(self.altitude),50,self.offsetZ,50)
self.formation:SetFollowTimeInterval(self.dtFollow)
-self.formation:SetFlightModeFormation(self.helo)
self.formation:__Start(delay)
self:__Status(1)
return self
@@ -105492,7 +105716,7 @@ if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then
local followSet=SET_GROUP:New():AddGroup(self.group)
local param=Task.dcstask.params
local followUnit=UNIT:FindByName(param.unitname)
-Task.formation=AI_FORMATION:New(followUnit,followSet,AUFTRAG.SpecialTask.FORMATION,"Follow X at given parameters.")
+Task.formation=FORMATION:New(followUnit,followSet,AUFTRAG.SpecialTask.FORMATION)
Task.formation:FormationCenterWing(-param.offsetX,50,math.abs(param.altitude),50,param.offsetZ,50)
Task.formation:SetFollowTimeInterval(param.dtFollow)
Task.formation:SetFlightModeFormation(self.group)
diff --git a/Moose_DualCoalitionZoneCapture/Moose_DualCoalitionZoneCapture.lua b/Moose_DualCoalitionZoneCapture/Moose_DualCoalitionZoneCapture.lua
index 4aaef21..3b3c739 100644
--- a/Moose_DualCoalitionZoneCapture/Moose_DualCoalitionZoneCapture.lua
+++ b/Moose_DualCoalitionZoneCapture/Moose_DualCoalitionZoneCapture.lua
@@ -201,8 +201,9 @@ end
-- ==========================================
-- Storage for all zone capture objects and metadata
-local zoneCaptureObjects = {}
-local zoneNames = {}
+-- NOTE: These are exported as globals for plugin compatibility (e.g., Moose_DynamicGroundBattle_Plugin.lua)
+zoneCaptureObjects = {} -- Global: accessible by other scripts
+zoneNames = {} -- Global: accessible by other scripts
local zoneMetadata = {} -- Stores coalition ownership info
-- Function to initialize all zones from configuration
@@ -1054,7 +1055,9 @@ end
local function SetupZoneStatusCommands()
-- Add F10 radio menu commands for BLUE coalition
if US_CC then
- local USMenu = MENU_COALITION:New( coalition.side.BLUE, "Zone Control" )
+ -- Use MenuManager to create zone control menu under Mission Options
+ local USMenu = MenuManager and MenuManager.CreateCoalitionMenu(coalition.side.BLUE, "Zone Control")
+ or MENU_COALITION:New( coalition.side.BLUE, "Zone Control" )
MENU_COALITION_COMMAND:New( coalition.side.BLUE, "Get Zone Status Report", USMenu, BroadcastZoneStatus )
MENU_COALITION_COMMAND:New( coalition.side.BLUE, "Check Victory Progress", USMenu, function()
@@ -1086,7 +1089,9 @@ local function SetupZoneStatusCommands()
-- Add F10 radio menu commands for RED coalition
if RU_CC then
- local RUMenu = MENU_COALITION:New( coalition.side.RED, "Zone Control" )
+ -- Use MenuManager to create zone control menu under Mission Options
+ local RUMenu = MenuManager and MenuManager.CreateCoalitionMenu(coalition.side.RED, "Zone Control")
+ or MENU_COALITION:New( coalition.side.RED, "Zone Control" )
MENU_COALITION_COMMAND:New( coalition.side.RED, "Get Zone Status Report", RUMenu, BroadcastZoneStatus )
MENU_COALITION_COMMAND:New( coalition.side.RED, "Check Victory Progress", RUMenu, function()
diff --git a/Moose_DynamicGroundBattle/Moose_DynamicGroundBattle_Plugin.lua b/Moose_DynamicGroundBattle/Moose_DynamicGroundBattle_Plugin.lua
index a606a40..0948182 100644
--- a/Moose_DynamicGroundBattle/Moose_DynamicGroundBattle_Plugin.lua
+++ b/Moose_DynamicGroundBattle/Moose_DynamicGroundBattle_Plugin.lua
@@ -3,8 +3,7 @@
Written by: [F99th-TracerFacer]
Version: 1.0.0
Date: 15 November 2024
- Description: Warehouse-driven ground unit spawning system that works as a plugin
- with Moose_DualCoalitionZoneCapture.lua
+ Description: Warehouse-driven ground unit spawning system that works as a plugin with Moose_DualCoalitionZoneCapture.lua
This script handles:
- Warehouse-based reinforcement system
@@ -71,7 +70,7 @@
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Infantry Patrol Settings
-local MOVING_INFANTRY_PATROLS = false -- Set to false to disable infantry movement (they spawn and hold position)
+local MOVING_INFANTRY_PATROLS = true -- Set to false to disable infantry movement (they spawn and hold position)
-- Warehouse Marker Settings
local ENABLE_WAREHOUSE_MARKERS = true -- Enable/disable warehouse map markers (disabled by default if you have other marker systems)
@@ -493,9 +492,19 @@ SCHEDULER:New(nil, MonitorWarehouses, {}, 30, 120)
-- Schedule task assignments
SCHEDULER:New(nil, AssignTasksToGroups, {}, 120, ASSIGN_TASKS_SCHED)
--- Add F10 menu for manual checks
-local missionMenu = MENU_MISSION:New("Ground Battle")
-MENU_MISSION_COMMAND:New("Check Warehouse Status", missionMenu, MonitorWarehouses)
+-- Add F10 menu for manual checks (using MenuManager if available)
+if MenuManager then
+ -- Create coalition-specific menus under Mission Options
+ local blueMenu = MenuManager.CreateCoalitionMenu(coalition.side.BLUE, "Ground Battle")
+ MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Check Warehouse Status", blueMenu, MonitorWarehouses)
+
+ local redMenu = MenuManager.CreateCoalitionMenu(coalition.side.RED, "Ground Battle")
+ MENU_COALITION_COMMAND:New(coalition.side.RED, "Check Warehouse Status", redMenu, MonitorWarehouses)
+else
+ -- Fallback to root-level mission menu
+ local missionMenu = MENU_MISSION:New("Ground Battle")
+ MENU_MISSION_COMMAND:New("Check Warehouse Status", missionMenu, MonitorWarehouses)
+end
env.info("[DGB PLUGIN] Dynamic Ground Battle Plugin initialized successfully!")
env.info(string.format("[DGB PLUGIN] Infantry movement: %s", MOVING_INFANTRY_PATROLS and "ENABLED" or "DISABLED"))