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"))