-
Universal TADC System - Mission Maker's Guide
-
Tactical Air Defense Controller with Automated Logistics
-
-
-
📋 Table of Contents
-
-
-
-
What is TADC?
-
-
TADC (Tactical Air Defense Controller) is an automated air defense system for DCS missions that creates realistic, dynamic fighter aircraft responses to airborne threats. Think of it as an AI commander that:
-
-
- - Detects enemy aircraft automatically
- - Launches fighters to intercept threats
- - Manages squadron resources (aircraft availability, cooldowns)
- - Replenishes squadrons through cargo aircraft deliveries
- - Operates independently for both RED and BLUE coalitions
-
-
-
Why Use TADC?
-
-
- - Realistic Air Defense - Squadrons respond intelligently to threats
- - Dynamic Gameplay - Air battles happen organically without manual triggers
- - Balanced Competition - Both sides operate with equal capabilities
- - Sustainable Operations - Cargo system allows long missions with resupply
- - Easy Configuration - Simple tables instead of complex scripting
-
-
-
System Overview
-
-
The TADC system consists of three main scripts that work together:
-
-
1. Squadron Configuration (Moose_TADC_SquadronConfigs_Load1st.lua)
-
Purpose: Define all fighter squadrons for RED and BLUE coalitions
- Contains: Aircraft templates, airbases, patrol parameters, zone assignments
- Load Order: FIRST (must load before main TADC script)
-
-
2. Main TADC System (Moose_TADC_Load2nd.lua)
-
Purpose: Core threat detection and interceptor management
- Contains: Threat scanning, squadron selection, intercept logic, F10 menus
- Load Order: SECOND (after squadron config)
-
-
3. Cargo Dispatcher (Moose_TADC_CargoDispatcher.lua)
-
Purpose: Automated squadron resupply through cargo aircraft
- Contains: Squadron monitoring, cargo spawning, delivery tracking
- Load Order: THIRD (optional, only if using resupply system)
-
-
Quick Start Guide
-
-
Prerequisites
-
-
Before setting up TADC, you need:
-
-
- - MOOSE Framework loaded in your mission (download from MOOSE GitHub)
- - Fighter aircraft templates created in DCS mission editor (as GROUPS, not units)
- - Airbases under correct coalition control
- - (Optional) Cargo aircraft templates for resupply missions
-
-
-
5-Minute Setup
-
-
1 Create Fighter Templates
-
-
- - Open your mission in DCS Mission Editor
- - Place fighter aircraft as LATE ACTIVATION GROUPS (not individual units)
- - Name them clearly (example:
RED_CAP_Kilpyavr_MiG29)
- - Position them at or near the airbases they'll operate from
- - Set them to the correct coalition (RED or BLUE)
-
-
-
- Important: Use GROUP templates, not UNIT templates!
-
-
-
2 Load MOOSE Framework
-
-
- - In mission editor, go to Triggers
- - Create a new trigger: MISSION START
- - Add action: DO SCRIPT FILE
- - Select your MOOSE.lua file
- - This must be the FIRST script loaded
-
-
-
3 Load Squadron Configuration
-
-
- - Create another DO SCRIPT FILE action (after MOOSE)
- - Select
Moose_TADC_SquadronConfigs_Load1st.lua
- - Edit the file to configure your squadrons (see below)
-
-
-
4 Load Main TADC System
-
-
- - Create another DO SCRIPT FILE action
- - Select
Moose_TADC_Load2nd.lua
- - (Optional) Adjust settings in the file if needed
-
-
-
5 (Optional) Load Cargo Dispatcher
-
-
- - If using resupply system, create another DO SCRIPT FILE action
- - Select
Moose_TADC_CargoDispatcher.lua
-
-
-
-
Load Order in Mission Editor:
-
1. MOOSE.lua
-2. Moose_TADC_SquadronConfigs_Load1st.lua
-3. Moose_TADC_Load2nd.lua
-4. Moose_TADC_CargoDispatcher.lua (optional)
-
-
-
Detailed Configuration
-
-
Squadron Configuration Explained
-
-
Open Moose_TADC_SquadronConfigs_Load1st.lua and find the squadron configuration sections.
-
-
Basic Squadron Example
-
-
{
- templateName = "RED_CAP_Kilpyavr_MiG29", -- Must match mission editor template name
- displayName = "Kilpyavr CAP MiG-29A", -- Human-readable name for logs/messages
- airbaseName = "Kilpyavr", -- Exact airbase name from DCS
- aircraft = 12, -- Maximum aircraft in squadron
- skill = AI.Skill.EXCELLENT, -- AI pilot skill level
- altitude = 20000, -- Patrol altitude (feet)
- speed = 350, -- Patrol speed (knots)
- patrolTime = 30, -- Time on station (minutes)
- type = "FIGHTER" -- Aircraft role
-}
-
-
Parameter Guide
-
-
-
-
- | Parameter |
- Description |
- Example Values |
-
-
-
-
- | templateName |
- Group name from mission editor (EXACT match) |
- "RED_CAP_Base_F15" |
-
-
- | displayName |
- Friendly name shown in messages |
- "Kilpyavr CAP Squadron" |
-
-
- | airbaseName |
- DCS airbase name (case sensitive) |
- "Kilpyavr", "Nellis AFB" |
-
-
- | aircraft |
- Max squadron size |
- 8, 12, 16 |
-
-
- | skill |
- AI difficulty |
- AI.Skill.AVERAGE, GOOD, HIGH, EXCELLENT, ACE |
-
-
- | altitude |
- CAP patrol altitude |
- 15000 (feet) |
-
-
- | speed |
- CAP patrol speed |
- 300 (knots) |
-
-
- | patrolTime |
- Minutes on station before RTB |
- 20, 30, 40 |
-
-
- | type |
- Aircraft role |
- "FIGHTER" |
-
-
-
-
-
Finding Airbase Names
-
-
Method 1: Mission Editor
-
- - Open mission editor
- - Click on any airbase
- - The exact name appears in the properties panel
- - Copy this name EXACTLY (case sensitive!)
-
-
-
Method 2: Common Airbases
-
-
Kola Peninsula (Example Map):
-
- - RED:
"Kilpyavr", "Severomorsk-1", "Severomorsk-3", "Murmansk International"
- - BLUE:
"Luostari Pechenga", "Ivalo", "Alakurtti"
-
-
-
Nevada:
-
- "Nellis AFB", "McCarran International", "Creech AFB", "Tonopah Test Range"
-
-
-
Caucasus:
-
- "Batumi", "Gudauta", "Senaki-Kolkhi", "Kobuleti", "Kutaisi"
-
-
-
Adding Multiple Squadrons
-
-
You can add as many squadrons as you want. Just copy the squadron block and modify the values:
-
-
RED_SQUADRON_CONFIG = {
- -- First Squadron
- {
- templateName = "RED_CAP_Base1_MiG29",
- displayName = "Base 1 CAP",
- airbaseName = "Kilpyavr",
- aircraft = 12,
- skill = AI.Skill.EXCELLENT,
- altitude = 20000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER"
- },
-
- -- Second Squadron (different base)
- {
- templateName = "RED_CAP_Base2_SU27",
- displayName = "Base 2 CAP",
- airbaseName = "Severomorsk-1",
- aircraft = 16,
- skill = AI.Skill.ACE,
- altitude = 25000,
- speed = 380,
- patrolTime = 25,
- type = "FIGHTER"
- },
-
- -- Add more squadrons here...
-}
-
-
- Repeat the same process for BLUE squadrons in the BLUE_SQUADRON_CONFIG section.
-
-
-
Zone-Based Defense Setup
-
-
Zones allow squadrons to have specific areas of responsibility, creating realistic layered defense.
-
-
Why Use Zones?
-
-
- - Border Defense: Squadrons patrol specific sectors
- - Layered Defense: Multiple squadrons cover overlapping areas
- - Priority Response: Squadrons respond differently based on threat location
- - Realistic Behavior: Fighters don't fly across the entire map for minor threats
-
-
-
Zone Types
-
-
Each squadron can have up to 3 zone types:
-
-
- - Primary Zone - Main area of responsibility (full response)
- - Secondary Zone - Support area (reduced response, 60% by default)
- - Tertiary Zone - Emergency fallback (enhanced response when squadron weakened)
-
-
-
Creating Zones in Mission Editor
-
-
Method: Helicopter Waypoint Method
-
-
- - Place a helicopter group (late activation, any type)
- - Name it clearly (example:
"RED BORDER")
- - Add waypoints that outline your zone boundary
- - The script will automatically create a polygon zone from these waypoints
- - Repeat for each zone you want to create
-
-
-
-
Example Zone Setup:
-
Mission Editor:
-- Helicopter Group: "RED BORDER" with waypoints forming a polygon
-- Helicopter Group: "BLUE BORDER" with waypoints forming a polygon
-- Helicopter Group: "CONTESTED ZONE" with waypoints forming a polygon
-
-
-
Configuring Zone Response
-
-
Add zone configuration to your squadron:
-
-
{
- templateName = "RED_CAP_Kilpyavr_MiG29",
- displayName = "Kilpyavr CAP",
- airbaseName = "Kilpyavr",
- aircraft = 12,
- skill = AI.Skill.EXCELLENT,
- altitude = 20000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER",
-
- -- Zone Configuration
- primaryZone = "RED BORDER", -- Main responsibility area
- secondaryZone = "CONTESTED ZONE", -- Backup coverage
- tertiaryZone = nil, -- No tertiary zone
-
- -- Optional: Customize zone behavior
- zoneConfig = {
- primaryResponse = 1.0, -- Full response in primary zone
- secondaryResponse = 0.6, -- 60% response in secondary
- tertiaryResponse = 1.4, -- 140% response in tertiary
- enableFallback = false, -- Don't auto-switch to tertiary
- fallbackThreshold = 0.3, -- Switch when <30% aircraft remain
- secondaryLowPriorityFilter = true, -- Ignore small threats in secondary
- secondaryLowPriorityThreshold = 2 -- "Small threat" = 2 or fewer aircraft
- }
-}
-
-
Zone Behavior Examples
-
-
Example 1: Border Defense Squadron
-
primaryZone = "RED BORDER", -- Patrols the border
-secondaryZone = "INTERIOR", -- Helps with interior threats if needed
-tertiaryZone = nil -- No fallback
-
-
Example 2: Base Defense with Fallback
-
primaryZone = "NORTHERN SECTOR", -- Main patrol area
-secondaryZone = nil, -- No secondary
-tertiaryZone = "BASE PERIMETER", -- Falls back to defend base when weakened
-enableFallback = true, -- Auto-switch to tertiary when low
-fallbackThreshold = 0.4 -- Switch at 40% strength
-
-
Example 3: Layered Defense
-
-- Squadron A: Outer layer
-primaryZone = "OUTER PERIMETER"
-
--- Squadron B: Middle layer
-primaryZone = "MIDDLE PERIMETER"
-
--- Squadron C: Inner/base defense
-primaryZone = "BASE DEFENSE"
-
-
Global Response (No Zones)
-
-
If you DON'T want zone restrictions, simply leave all zones as nil:
-
-
{
- templateName = "RED_CAP_Base_MiG29",
- displayName = "Global Response CAP",
- airbaseName = "Kilpyavr",
- aircraft = 12,
- skill = AI.Skill.EXCELLENT,
- altitude = 20000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER",
-
- -- No zones = responds to threats anywhere on the map
- primaryZone = nil,
- secondaryZone = nil,
- tertiaryZone = nil
-}
-
-
Cargo Replenishment System
-
-
The cargo system automatically replenishes squadrons by spawning transport aircraft that fly supplies to airbases.
-
-
How Cargo Works
-
-
- - Monitoring: Script checks squadron aircraft counts every minute
- - Detection: When squadron drops below threshold (90% by default), cargo is dispatched
- - Spawning: Transport aircraft spawns at a supply airfield
- - Delivery: Flies to destination airbase and lands
- - Replenishment: Squadron aircraft count increases upon delivery
- - Cooldown: 5-minute cooldown before next delivery to same base
-
-
-
Cargo Aircraft Detection
-
-
The system detects cargo by aircraft name patterns:
-
- CARGO
- TRANSPORT
- C130 or C-130
- AN26 or AN-26
-
-
-
Delivery Methods:
-
- - Landing: Aircraft lands at destination airbase
-
-
-
Configuring Cargo Templates
-
-
Edit Moose_TADC_CargoDispatcher.lua and find CARGO_SUPPLY_CONFIG:
-
-
local CARGO_SUPPLY_CONFIG = {
- red = {
- cargoTemplate = "CARGO_RED_AN26_TEMPLATE", -- Template name from mission editor
- supplyAirfields = {"Airbase1", "Airbase2"}, -- List of supply bases
- replenishAmount = 4, -- Aircraft added per delivery
- threshold = 0.90 -- Trigger at 90% capacity
- },
- blue = {
- cargoTemplate = "CARGO_BLUE_C130_TEMPLATE",
- supplyAirfields = {"Airbase3", "Airbase4"},
- replenishAmount = 4,
- threshold = 0.90
- }
-}
-
-
Creating Cargo Templates
-
-
1. In Mission Editor:
-
- - Place transport aircraft group (C-130, An-26, etc.)
- - Name it:
CARGO_RED_AN26_TEMPLATE or CARGO_BLUE_C130_TEMPLATE
- - Set LATE ACTIVATION
- - Position at any friendly airbase (starting position doesn't matter)
-
-
-
2. In Configuration:
-
- - Use the EXACT template name in
cargoTemplate field
- - List supply airbases in
supplyAirfields array
- - Set how many aircraft each delivery adds (
replenishAmount)
-
-
-
Supply Airfield Strategy
-
-
Choose rear/safe airbases for supplies:
-
-
red = {
- cargoTemplate = "CARGO_RED_AN26_TEMPLATE",
- supplyAirfields = {
- "Rear_Base_1", -- Far from frontline, safe
- "Rear_Base_2", -- Alternate supply source
- "Central_Logistics_Hub" -- Main supply depot
- },
- replenishAmount = 4,
- threshold = 0.90
-}
-
-
Tips:
-
- - Use 3-5 supply airbases for redundancy
- - Choose bases far from combat zones
- - Ensure supply bases are well-defended
- - Balance geographic coverage
-
-
-
Disabling Cargo System
-
-
If you don't want automated resupply:
-
- - Don't load
Moose_TADC_CargoDispatcher.lua
- - Squadrons will operate with their initial aircraft count only
- - System still works perfectly for shorter missions
-
-
-
Testing & Troubleshooting
-
-
Validation Tools
-
-
The system includes built-in validation. Check the DCS log file after mission start for:
-
-
-
[Universal TADC] ═══════════════════════════════════════
-[Universal TADC] Configuration Validation Results:
-[Universal TADC] ✓ All templates exist
-[Universal TADC] ✓ All airbases valid
-[Universal TADC] ✓ All zones found
-[Universal TADC] Configuration is VALID
-[Universal TADC] ═══════════════════════════════════════
-
-
-
In-Game F10 Menu Commands
-
-
Press F10 in-game to access TADC utilities:
-
-
Available to Each Coalition:
-
- - Show Squadron Resource Summary - Current aircraft counts
- - Show Airbase Status Report - Operational status of all bases
- - Show Active Interceptors - Currently airborne fighters
- - Show Threat Summary - Detected enemy aircraft
- - Broadcast Squadron Summary Now - Force immediate status update
- - Show Cargo Delivery Log - Recent supply missions
- - Show Zone Coverage Map - Squadron zone assignments
-
-
-
Available to All (Mission Commands):
-
- - Emergency Cleanup Interceptors - Remove stuck/dead groups
- - Show TADC System Status - Uptime and system health
- - Check for Stuck Aircraft - Manual stuck aircraft check
- - Show Airbase Health Status - Parking/spawn issues
-
-
-
Common Issues & Solutions
-
-
Issue: "Template not found in mission"
-
-
Cause: Template name in config doesn't match mission editor
-
Solution:
-
- - Check exact spelling and case
- - Ensure template is in mission editor
- - Verify template is a GROUP (not a unit)
- - Check template name in mission editor properties
-
-
-
-
Issue: "Airbase not found or wrong coalition"
-
-
Cause: Airbase name wrong or captured by enemy
-
Solution:
-
- - Check exact airbase spelling (case sensitive)
- - Verify airbase is owned by correct coalition in mission editor
- - Use
_G.TDAC_CheckAirbase("AirbaseName") in DCS console
-
-
-
-
Issue: "No interceptors launching"
-
-
Check:
-
- - Are enemy aircraft detected? (F10 → Show Threat Summary)
- - Are squadrons operational? (F10 → Show Squadron Resource Summary)
- - Is airbase captured/destroyed? (F10 → Show Airbase Status Report)
- - Are squadrons on cooldown? (F10 → Show Squadron Resource Summary)
- - Check intercept ratio settings (might be too low)
-
-
-
-
Issue: "Cargo not delivering"
-
-
Check:
-
- - Is cargo template name correct?
- - Are supply airbases valid and friendly?
- - Is destination airbase captured/operational?
- - Check parking availability (F10 → Show Airbase Health Status)
- - Look for "Cargo delivery detected" messages in log
-
-
-
-
Issue: "Aircraft spawning stuck at parking"
-
-
Cause: Parking spots occupied or insufficient space
-
Solution:
-
- - Use F10 → Check for Stuck Aircraft
- - Use F10 → Emergency Cleanup Interceptors
- - Check airbase parking capacity (larger aircraft need more space)
- - Reduce squadron sizes if parking is limited
-
-
-
-
DCS Console Diagnostics
-
-
Open DCS Lua console (F12 or scripting console) and run:
-
-
-- Check all supply airbase ownership
-_G.TDAC_CheckAirbaseOwnership()
-
--- Check specific airbase
-_G.TDAC_CheckAirbase("Kilpyavr")
-
--- Validate dispatcher configuration
-_G.TDAC_RunConfigCheck()
-
--- Check airbase parking availability
-_G.TDAC_LogAirbaseParking("Kilpyavr")
-
--- Test cargo spawn (debugging)
-_G.TDAC_CargoDispatcher_TestSpawn("CARGO_RED_AN26_TEMPLATE", "SupplyBase", "DestinationBase")
-
-
Advanced Features
-
-
Intercept Ratio System
-
-
The interceptRatio setting controls how many fighters launch per enemy aircraft.
-
-
In Moose_TADC_Load2nd.lua:
-
-
local TADC_SETTINGS = {
- red = {
- interceptRatio = 0.8, -- RED launches 0.8 fighters per threat
- maxActiveCAP = 8, -- Max 8 groups in air simultaneously
- defaultCooldown = 300, -- 5-minute cooldown after engagement
- },
- blue = {
- interceptRatio = 1.2, -- BLUE launches 1.2 fighters per threat
- maxActiveCAP = 10, -- Max 10 groups in air simultaneously
- defaultCooldown = 300,
- }
-}
-
-
Intercept Ratio Chart
-
-
-
-
- | Ratio |
- 1 Enemy |
- 4 Enemies |
- 8 Enemies |
- Effect |
-
-
-
-
- | 0.5 |
- 1 fighter |
- 2 fighters |
- 4 fighters |
- Conservative response |
-
-
- | 0.8 |
- 1 fighter |
- 4 fighters |
- 7 fighters |
- Balanced (default) |
-
-
- | 1.0 |
- 1 fighter |
- 4 fighters |
- 8 fighters |
- 1:1 parity |
-
-
- | 1.4 |
- 2 fighters |
- 6 fighters |
- 12 fighters |
- Strong response |
-
-
- | 2.0 |
- 2 fighters |
- 8 fighters |
- 16 fighters |
- Overwhelming force |
-
-
-
-
-
Tactical Effects:
-
- - Low (0.5-0.8): Sustainable defense, squadrons last longer
- - Medium (0.8-1.2): Balanced dogfights, realistic attrition
- - High (1.4-2.0): Strong defense, rapid squadron depletion
-
-
-
Asymmetric Scenarios:
-
-- RED advantage
-red = { interceptRatio = 1.4 },
-blue = { interceptRatio = 0.8 }
-
--- BLUE advantage
-red = { interceptRatio = 0.8 },
-blue = { interceptRatio = 1.4 }
-
-
Distance-Based Engagement
-
-
Control how far squadrons will chase threats:
-
-
{
- templateName = "RED_CAP_Base_MiG29",
- displayName = "Base Defense",
- airbaseName = "Kilpyavr",
- aircraft = 12,
- -- ... other settings ...
-
- zoneConfig = {
- maxEngagementRange = 50000, -- Won't engage threats >50km from base
- primaryResponse = 1.0,
- secondaryResponse = 0.6
- }
-}
-
-
Cooldown System
-
-
After launching interceptors, squadrons go on cooldown to prevent spam:
-
-
local TADC_SETTINGS = {
- red = {
- defaultCooldown = 300, -- 5 minutes between launches
- -- ... other settings ...
- }
-}
-
-
Per-Squadron Cooldown (optional):
-
{
- templateName = "RED_CAP_Base_MiG29",
- cooldownOverride = 600, -- This squadron: 10-minute cooldown
- -- ... other settings ...
-}
-
-
Aircraft Skill Levels
-
-
Adjust AI difficulty per squadron:
-
-
skill = AI.Skill.AVERAGE -- Easiest, good for training
-skill = AI.Skill.GOOD -- Below average
-skill = AI.Skill.HIGH -- Average pilots
-skill = AI.Skill.EXCELLENT -- Above average (recommended)
-skill = AI.Skill.ACE -- Hardest, expert pilots
-
-
Mixed Difficulty Example:
-
RED_SQUADRON_CONFIG = {
- {
- displayName = "Elite Squadron",
- skill = AI.Skill.ACE, -- Best pilots
- aircraft = 8,
- -- ...
- },
- {
- displayName = "Regular Squadron",
- skill = AI.Skill.GOOD, -- Average pilots
- aircraft = 12,
- -- ...
- }
-}
-
-
Common Scenarios
-
-
Scenario 1: Simple Border Defense
-
-
Goal: RED defends northern border, BLUE defends southern border
-
-
-- RED Configuration
-RED_SQUADRON_CONFIG = {
- {
- templateName = "RED_CAP_North_MiG29",
- displayName = "Northern Border CAP",
- airbaseName = "Northern_Base",
- aircraft = 12,
- skill = AI.Skill.EXCELLENT,
- altitude = 20000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER",
- primaryZone = "RED BORDER"
- }
-}
-
--- BLUE Configuration
-BLUE_SQUADRON_CONFIG = {
- {
- templateName = "BLUE_CAP_South_F16",
- displayName = "Southern Border CAP",
- airbaseName = "Southern_Base",
- aircraft = 12,
- skill = AI.Skill.EXCELLENT,
- altitude = 20000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER",
- primaryZone = "BLUE BORDER"
- }
-}
-
-
-
In Mission Editor:
-
- - Create zone "RED BORDER" (helicopter waypoints on northern border)
- - Create zone "BLUE BORDER" (helicopter waypoints on southern border)
-
-
-
-
Scenario 2: Layered Defense Network
-
-
Goal: Multiple squadrons covering overlapping zones with different priorities
-
-
RED_SQUADRON_CONFIG = {
- -- Outer Layer: Long-range interceptors
- {
- templateName = "RED_LONG_RANGE_MiG31",
- displayName = "Long Range Interceptors",
- airbaseName = "Forward_Base",
- aircraft = 8,
- skill = AI.Skill.EXCELLENT,
- altitude = 35000,
- speed = 450,
- patrolTime = 20,
- type = "FIGHTER",
- primaryZone = "OUTER PERIMETER"
- },
-
- -- Middle Layer: General defense
- {
- templateName = "RED_CAP_MiG29",
- displayName = "Middle Defense CAP",
- airbaseName = "Central_Base",
- aircraft = 12,
- skill = AI.Skill.EXCELLENT,
- altitude = 25000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER",
- primaryZone = "MIDDLE PERIMETER",
- secondaryZone = "OUTER PERIMETER"
- },
-
- -- Inner Layer: Point defense
- {
- templateName = "RED_BASE_DEFENSE_SU27",
- displayName = "Base Defense",
- airbaseName = "Main_Base",
- aircraft = 16,
- skill = AI.Skill.ACE,
- altitude = 20000,
- speed = 320,
- patrolTime = 40,
- type = "FIGHTER",
- primaryZone = "BASE PERIMETER",
- tertiaryZone = "BASE PERIMETER",
- zoneConfig = {
- enableFallback = true,
- fallbackThreshold = 0.3
- }
- }
-}
-
-
Scenario 3: Sustained Operations with Resupply
-
-
Goal: Long mission with automated squadron replenishment
-
-
Squadron Config:
-
RED_SQUADRON_CONFIG = {
- {
- templateName = "RED_CAP_Frontline_MiG29",
- displayName = "Frontline CAP",
- airbaseName = "Frontline_Base",
- aircraft = 12, -- Will be resupplied
- skill = AI.Skill.EXCELLENT,
- altitude = 20000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER",
- primaryZone = "COMBAT ZONE"
- }
-}
-
-
Cargo Config (in Moose_TADC_CargoDispatcher.lua):
-
local CARGO_SUPPLY_CONFIG = {
- red = {
- cargoTemplate = "CARGO_RED_AN26",
- supplyAirfields = {
- "Rear_Base_1", -- Safe logistics hub
- "Rear_Base_2", -- Backup supply source
- "Central_Depot" -- Main supply depot
- },
- replenishAmount = 4, -- +4 aircraft per delivery
- threshold = 0.75 -- Trigger at 75% (9/12 aircraft)
- }
-}
-
-
Mission Flow:
-
- - Frontline squadron intercepts threats
- - Squadron drops to 9 aircraft (75%)
- - Cargo automatically dispatched from rear base
- - Transport flies to frontline base
- - Cargo delivers, squadron back to 12 aircraft
- - Cycle repeats throughout mission
-
-
-
Scenario 4: Asymmetric Warfare
-
-
Goal: RED has numerical superiority, BLUE has quality advantage
-
-
-- RED: More squadrons, lower skill
-local TADC_SETTINGS = {
- red = {
- interceptRatio = 0.8, -- Conservative response
- maxActiveCAP = 12, -- More groups allowed
- }
-}
-
-RED_SQUADRON_CONFIG = {
- {
- templateName = "RED_CAP_1",
- airbaseName = "Base_1",
- aircraft = 16, -- Large squadron
- skill = AI.Skill.GOOD, -- Average skill
- -- ...
- },
- {
- templateName = "RED_CAP_2",
- airbaseName = "Base_2",
- aircraft = 16,
- skill = AI.Skill.GOOD,
- -- ...
- },
- {
- templateName = "RED_CAP_3",
- airbaseName = "Base_3",
- aircraft = 16,
- skill = AI.Skill.GOOD,
- -- ...
- }
-}
-
--- BLUE: Fewer squadrons, higher skill
-local TADC_SETTINGS = {
- blue = {
- interceptRatio = 1.2, -- Aggressive response
- maxActiveCAP = 8, -- Fewer groups
- }
-}
-
-BLUE_SQUADRON_CONFIG = {
- {
- templateName = "BLUE_CAP_1",
- airbaseName = "Base_1",
- aircraft = 10, -- Smaller squadron
- skill = AI.Skill.ACE, -- Elite pilots
- -- ...
- },
- {
- templateName = "BLUE_CAP_2",
- airbaseName = "Base_2",
- aircraft = 10,
- skill = AI.Skill.ACE,
- -- ...
- }
-}
-
-
Tips for New Mission Makers
-
-
Start Simple
-
-
- - First Mission: Use 1-2 squadrons per side with no zones
- - Second Mission: Add zone-based defense
- - Third Mission: Add cargo resupply system
- - Advanced: Multi-squadron layered defense with fallback
-
-
-
Realistic Aircraft Numbers
-
-
Small Airbase: 6-8 aircraft per squadron
- Medium Airbase: 10-12 aircraft per squadron
- Large Airbase: 14-18 aircraft per squadron
-
-
Balance across map: If RED has 40 total aircraft, BLUE should have similar unless asymmetric
-
-
Performance Considerations
-
-
- - Limit active groups: Use
maxActiveCAP to prevent FPS drops
- - Zone sizes matter: Smaller zones = less scanning overhead
- - Cargo cooldowns: Prevent cargo spam with reasonable cooldowns
- - Squadron counts: 3-5 squadrons per side is a good starting point
-
-
-
Testing Workflow
-
-
- - Create minimal setup (1 squadron each side)
- - Test in mission editor using "Fly Now"
- - Check F10 menus for squadron status
- - Spawn enemy aircraft to test intercepts
- - Review DCS.log for validation messages
- - Expand gradually once basic system works
-
-
-
Common Mistakes to Avoid
-
-
- - Using UNIT templates instead of GROUP templates
- - Misspelling airbase names
- - Loading scripts in wrong order
- - Setting intercept ratio too high
- - Forgetting to load MOOSE first
-
-
-
- - Use GROUP templates (late activation groups)
- - Copy exact names from mission editor
- - Squadron Config → Main TADC → Cargo Dispatcher
- - Start with 0.8-1.0, adjust after testing
- - MOOSE must be first script loaded
-
-
-
Conclusion
-
-
The Universal TADC system provides mission makers with powerful, automated air defense capabilities that create dynamic, realistic air combat scenarios. By following this guide, even new mission makers can create sophisticated missions with minimal scripting knowledge.
-
-
Key Takeaways
-
-
- - Three scripts work together: Squadron Config → Main TADC → Cargo Dispatcher
- - Configuration is simple: Edit tables, not complex code
- - Both coalitions operate independently: Balanced or asymmetric scenarios
- - Zones enable tactical behavior: Realistic area-of-responsibility system
- - Cargo enables sustained operations: Long missions with automatic resupply
- - Built-in validation: Checks configuration before mission starts
- - F10 menus provide visibility: Monitor system status in real-time
-
-
-
Getting Help
-
-
If you encounter issues:
-
-
- - Check DCS.log for validation errors
- - Use F10 menu diagnostics
- - Run console commands for detailed info
- - Review this guide's troubleshooting section
- - Start simple and expand gradually
-
-
-
Next Steps
-
-
- - Set up your first squadron (1 RED, 1 BLUE)
- - Test basic intercept behavior
- - Add zones for tactical depth
- - Implement cargo resupply for long missions
- - Experiment with advanced features
-
-
-
Happy mission making! 🚁✈️
-
-
-
-
-
\ No newline at end of file
diff --git a/Moose_TADC/TADC_SYSTEM_GUIDE.md b/Moose_TADC/TADC_SYSTEM_GUIDE.md
deleted file mode 100644
index 70bf452..0000000
--- a/Moose_TADC/TADC_SYSTEM_GUIDE.md
+++ /dev/null
@@ -1,965 +0,0 @@
-# Universal TADC System - Mission Maker's Guide
-## Tactical Air Defense Controller with Automated Logistics
-
----
-
-## 📋 Table of Contents
-
-1. [What is TADC?](#what-is-tadc)
-2. [System Overview](#system-overview)
-3. [Quick Start Guide](#quick-start-guide)
-4. [Detailed Configuration](#detailed-configuration)
-5. [Zone-Based Defense Setup](#zone-based-defense-setup)
-6. [Cargo Replenishment System](#cargo-replenishment-system)
-7. [Testing & Troubleshooting](#testing--troubleshooting)
-8. [Advanced Features](#advanced-features)
-9. [Common Scenarios](#common-scenarios)
-
----
-
-## What is TADC?
-
-**TADC (Tactical Air Defense Controller)** is an automated air defense system for DCS missions that creates realistic, dynamic fighter aircraft responses to airborne threats. Think of it as an AI commander that:
-
-- **Detects enemy aircraft** automatically
-- **Launches fighters** to intercept threats
-- **Manages squadron resources** (aircraft availability, cooldowns)
-- **Replenishes squadrons** through cargo aircraft deliveries
-- **Operates independently** for both RED and BLUE coalitions
-
-### Why Use TADC?
-
-✅ **Realistic Air Defense** - Squadrons respond intelligently to threats
-✅ **Dynamic Gameplay** - Air battles happen organically without manual triggers
-✅ **Balanced Competition** - Both sides operate with equal capabilities
-✅ **Sustainable Operations** - Cargo system allows long missions with resupply
-✅ **Easy Configuration** - Simple tables instead of complex scripting
-
----
-
-## System Overview
-
-The TADC system consists of **three main scripts** that work together:
-
-### 1. Squadron Configuration (`Moose_TADC_SquadronConfigs_Load1st.lua`)
-**Purpose:** Define all fighter squadrons for RED and BLUE coalitions
-**Contains:** Aircraft templates, airbases, patrol parameters, zone assignments
-**Load Order:** **FIRST** (must load before main TADC script)
-
-### 2. Main TADC System (`Moose_TADC_Load2nd.lua`)
-**Purpose:** Core threat detection and interceptor management
-**Contains:** Threat scanning, squadron selection, intercept logic, F10 menus
-**Load Order:** **SECOND** (after squadron config)
-
-### 3. Cargo Dispatcher (`Moose_TADC_CargoDispatcher.lua`)
-**Purpose:** Automated squadron resupply through cargo aircraft
-**Contains:** Squadron monitoring, cargo spawning, delivery tracking
-**Load Order:** **THIRD** (optional, only if using resupply system)
-
----
-
-## Quick Start Guide
-
-### Prerequisites
-
-Before setting up TADC, you need:
-
-- ✅ **MOOSE Framework** loaded in your mission (download from [MOOSE GitHub](https://github.com/FlightControl-Master/MOOSE))
-- ✅ **Fighter aircraft templates** created in DCS mission editor (as GROUPS, not units)
-- ✅ **Airbases** under correct coalition control
-- ✅ (Optional) **Cargo aircraft templates** for resupply missions
-
-### 5-Minute Setup
-
-#### Step 1: Create Fighter Templates
-
-1. Open your mission in DCS Mission Editor
-2. Place fighter aircraft as **LATE ACTIVATION GROUPS** (not individual units)
-3. Name them clearly (example: `RED_CAP_Kilpyavr_MiG29`)
-4. Position them at or near the airbases they'll operate from
-5. Set them to the correct coalition (RED or BLUE)
-
-**Important:** Use GROUP templates, not UNIT templates!
-
-#### Step 2: Load MOOSE Framework
-
-1. In mission editor, go to **Triggers**
-2. Create a new trigger: **MISSION START**
-3. Add action: **DO SCRIPT FILE**
-4. Select your MOOSE.lua file
-5. This must be the FIRST script loaded
-
-#### Step 3: Load Squadron Configuration
-
-1. Create another **DO SCRIPT FILE** action (after MOOSE)
-2. Select `Moose_TADC_SquadronConfigs_Load1st.lua`
-3. Edit the file to configure your squadrons (see below)
-
-#### Step 4: Load Main TADC System
-
-1. Create another **DO SCRIPT FILE** action
-2. Select `Moose_TADC_Load2nd.lua`
-3. (Optional) Adjust settings in the file if needed
-
-#### Step 5: (Optional) Load Cargo Dispatcher
-
-1. If using resupply system, create another **DO SCRIPT FILE** action
-2. Select `Moose_TADC_CargoDispatcher.lua`
-
-**Load Order in Mission Editor:**
-```
-1. MOOSE.lua
-2. Moose_TADC_SquadronConfigs_Load1st.lua
-3. Moose_TADC_Load2nd.lua
-4. Moose_TADC_CargoDispatcher.lua (optional)
-```
-
----
-
-## Detailed Configuration
-
-### Squadron Configuration Explained
-
-Open `Moose_TADC_SquadronConfigs_Load1st.lua` and find the squadron configuration sections.
-
-#### Basic Squadron Example
-
-```lua
-{
- templateName = "RED_CAP_Kilpyavr_MiG29", -- Must match mission editor template name
- displayName = "Kilpyavr CAP MiG-29A", -- Human-readable name for logs/messages
- airbaseName = "Kilpyavr", -- Exact airbase name from DCS
- aircraft = 12, -- Maximum aircraft in squadron
- skill = AI.Skill.EXCELLENT, -- AI pilot skill level
- altitude = 20000, -- Patrol altitude (feet)
- speed = 350, -- Patrol speed (knots)
- patrolTime = 30, -- Time on station (minutes)
- type = "FIGHTER" -- Aircraft role
-}
-```
-
-#### Parameter Guide
-
-| Parameter | Description | Example Values |
-|-----------|-------------|----------------|
-| **templateName** | Group name from mission editor (EXACT match) | `"RED_CAP_Base_F15"` |
-| **displayName** | Friendly name shown in messages | `"Kilpyavr CAP Squadron"` |
-| **airbaseName** | DCS airbase name (case sensitive) | `"Kilpyavr"`, `"Nellis AFB"` |
-| **aircraft** | Max squadron size | `8`, `12`, `16` |
-| **skill** | AI difficulty | `AI.Skill.AVERAGE`, `GOOD`, `HIGH`, `EXCELLENT`, `ACE` |
-| **altitude** | CAP patrol altitude | `15000` (feet) |
-| **speed** | CAP patrol speed | `300` (knots) |
-| **patrolTime** | Minutes on station before RTB | `20`, `30`, `40` |
-| **type** | Aircraft role | `"FIGHTER"` |
-
-### Finding Airbase Names
-
-**Method 1: Mission Editor**
-1. Open mission editor
-2. Click on any airbase
-3. The exact name appears in the properties panel
-4. Copy this name EXACTLY (case sensitive!)
-
-**Method 2: Common Airbases**
-
-**Kola Peninsula (Example Map):**
-- RED: `"Kilpyavr"`, `"Severomorsk-1"`, `"Severomorsk-3"`, `"Murmansk International"`
-- BLUE: `"Luostari Pechenga"`, `"Ivalo"`, `"Alakurtti"`
-
-**Nevada:**
-- `"Nellis AFB"`, `"McCarran International"`, `"Creech AFB"`, `"Tonopah Test Range"`
-
-**Caucasus:**
-- `"Batumi"`, `"Gudauta"`, `"Senaki-Kolkhi"`, `"Kobuleti"`, `"Kutaisi"`
-
-### Adding Multiple Squadrons
-
-You can add as many squadrons as you want. Just copy the squadron block and modify the values:
-
-```lua
-RED_SQUADRON_CONFIG = {
- -- First Squadron
- {
- templateName = "RED_CAP_Base1_MiG29",
- displayName = "Base 1 CAP",
- airbaseName = "Kilpyavr",
- aircraft = 12,
- skill = AI.Skill.EXCELLENT,
- altitude = 20000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER"
- },
-
- -- Second Squadron (different base)
- {
- templateName = "RED_CAP_Base2_SU27",
- displayName = "Base 2 CAP",
- airbaseName = "Severomorsk-1",
- aircraft = 16,
- skill = AI.Skill.ACE,
- altitude = 25000,
- speed = 380,
- patrolTime = 25,
- type = "FIGHTER"
- },
-
- -- Add more squadrons here...
-}
-```
-
-**Repeat the same process for BLUE squadrons** in the `BLUE_SQUADRON_CONFIG` section.
-
----
-
-## Zone-Based Defense Setup
-
-Zones allow squadrons to have specific areas of responsibility, creating realistic layered defense.
-
-### Why Use Zones?
-
-- **Border Defense:** Squadrons patrol specific sectors
-- **Layered Defense:** Multiple squadrons cover overlapping areas
-- **Priority Response:** Squadrons respond differently based on threat location
-- **Realistic Behavior:** Fighters don't fly across the entire map for minor threats
-
-### Zone Types
-
-Each squadron can have up to 3 zone types:
-
-1. **Primary Zone** - Main area of responsibility (full response)
-2. **Secondary Zone** - Support area (reduced response, 60% by default)
-3. **Tertiary Zone** - Emergency fallback (enhanced response when squadron weakened)
-
-### Creating Zones in Mission Editor
-
-**Method: Helicopter Waypoint Method**
-
-1. Place a **helicopter group** (late activation, any type)
-2. Name it clearly (example: `"RED BORDER"`)
-3. Add waypoints that outline your zone boundary
-4. The script will automatically create a polygon zone from these waypoints
-5. Repeat for each zone you want to create
-
-**Example Zone Setup:**
-```
-Mission Editor:
-- Helicopter Group: "RED BORDER" with waypoints forming a polygon
-- Helicopter Group: "BLUE BORDER" with waypoints forming a polygon
-- Helicopter Group: "CONTESTED ZONE" with waypoints forming a polygon
-```
-
-### Configuring Zone Response
-
-Add zone configuration to your squadron:
-
-```lua
-{
- templateName = "RED_CAP_Kilpyavr_MiG29",
- displayName = "Kilpyavr CAP",
- airbaseName = "Kilpyavr",
- aircraft = 12,
- skill = AI.Skill.EXCELLENT,
- altitude = 20000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER",
-
- -- Zone Configuration
- primaryZone = "RED BORDER", -- Main responsibility area
- secondaryZone = "CONTESTED ZONE", -- Backup coverage
- tertiaryZone = nil, -- No tertiary zone
-
- -- Optional: Customize zone behavior
- zoneConfig = {
- primaryResponse = 1.0, -- Full response in primary zone
- secondaryResponse = 0.6, -- 60% response in secondary
- tertiaryResponse = 1.4, -- 140% response in tertiary
- enableFallback = false, -- Don't auto-switch to tertiary
- fallbackThreshold = 0.3, -- Switch when <30% aircraft remain
- secondaryLowPriorityFilter = true, -- Ignore small threats in secondary
- secondaryLowPriorityThreshold = 2 -- "Small threat" = 2 or fewer aircraft
- }
-}
-```
-
-### Zone Behavior Examples
-
-**Example 1: Border Defense Squadron**
-```lua
-primaryZone = "RED BORDER", -- Patrols the border
-secondaryZone = "INTERIOR", -- Helps with interior threats if needed
-tertiaryZone = nil -- No fallback
-```
-
-**Example 2: Base Defense with Fallback**
-```lua
-primaryZone = "NORTHERN SECTOR", -- Main patrol area
-secondaryZone = nil, -- No secondary
-tertiaryZone = "BASE PERIMETER", -- Falls back to defend base when weakened
-enableFallback = true, -- Auto-switch to tertiary when low
-fallbackThreshold = 0.4 -- Switch at 40% strength
-```
-
-**Example 3: Layered Defense**
-```lua
--- Squadron A: Outer layer
-primaryZone = "OUTER PERIMETER"
-
--- Squadron B: Middle layer
-primaryZone = "MIDDLE PERIMETER"
-
--- Squadron C: Inner/base defense
-primaryZone = "BASE DEFENSE"
-```
-
-### Global Response (No Zones)
-
-If you **DON'T** want zone restrictions, simply leave all zones as `nil`:
-
-```lua
-{
- templateName = "RED_CAP_Base_MiG29",
- displayName = "Global Response CAP",
- airbaseName = "Kilpyavr",
- aircraft = 12,
- skill = AI.Skill.EXCELLENT,
- altitude = 20000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER",
-
- -- No zones = responds to threats anywhere on the map
- primaryZone = nil,
- secondaryZone = nil,
- tertiaryZone = nil
-}
-```
-
----
-
-## Cargo Replenishment System
-
-The cargo system automatically replenishes squadrons by spawning transport aircraft that fly supplies to airbases.
-
-### How Cargo Works
-
-1. **Monitoring:** Script checks squadron aircraft counts every minute
-2. **Detection:** When squadron drops below threshold (90% by default), cargo is dispatched
-3. **Spawning:** Transport aircraft spawns at a supply airfield
-4. **Delivery:** Flies to destination airbase and lands
-5. **Replenishment:** Squadron aircraft count increases upon delivery
-6. **Cooldown:** 5-minute cooldown before next delivery to same base
-
-### Cargo Aircraft Detection
-
-The system detects cargo by aircraft name patterns:
-- `CARGO`
-- `TRANSPORT`
-- `C130` or `C-130`
-- `AN26` or `AN-26`
-
-**Delivery Methods:**
-- **Landing:** Aircraft lands at destination airbase
-
-### Configuring Cargo Templates
-
-Edit `Moose_TADC_CargoDispatcher.lua` and find `CARGO_SUPPLY_CONFIG`:
-
-```lua
-local CARGO_SUPPLY_CONFIG = {
- red = {
- cargoTemplate = "CARGO_RED_AN26_TEMPLATE", -- Template name from mission editor
- supplyAirfields = {"Airbase1", "Airbase2"}, -- List of supply bases
- replenishAmount = 4, -- Aircraft added per delivery
- threshold = 0.90 -- Trigger at 90% capacity
- },
- blue = {
- cargoTemplate = "CARGO_BLUE_C130_TEMPLATE",
- supplyAirfields = {"Airbase3", "Airbase4"},
- replenishAmount = 4,
- threshold = 0.90
- }
-}
-```
-
-### Creating Cargo Templates
-
-1. **In Mission Editor:**
- - Place transport aircraft group (C-130, An-26, etc.)
- - Name it: `CARGO_RED_AN26_TEMPLATE` or `CARGO_BLUE_C130_TEMPLATE`
- - Set **LATE ACTIVATION**
- - Position at any friendly airbase (starting position doesn't matter)
-
-2. **In Configuration:**
- - Use the EXACT template name in `cargoTemplate` field
- - List supply airbases in `supplyAirfields` array
- - Set how many aircraft each delivery adds (`replenishAmount`)
-
-### Supply Airfield Strategy
-
-**Choose rear/safe airbases for supplies:**
-
-```lua
-red = {
- cargoTemplate = "CARGO_RED_AN26_TEMPLATE",
- supplyAirfields = {
- "Rear_Base_1", -- Far from frontline, safe
- "Rear_Base_2", -- Alternate supply source
- "Central_Logistics_Hub" -- Main supply depot
- },
- replenishAmount = 4,
- threshold = 0.90
-}
-```
-
-**Tips:**
-- Use 3-5 supply airbases for redundancy
-- Choose bases far from combat zones
-- Ensure supply bases are well-defended
-- Balance geographic coverage
-
-### Disabling Cargo System
-
-If you don't want automated resupply:
-1. **Don't load** `Moose_TADC_CargoDispatcher.lua`
-2. Squadrons will operate with their initial aircraft count only
-3. System still works perfectly for shorter missions
-
----
-
-## Testing & Troubleshooting
-
-### Validation Tools
-
-The system includes built-in validation. Check the DCS log file after mission start for:
-
-```
-[Universal TADC] ═══════════════════════════════════════
-[Universal TADC] Configuration Validation Results:
-[Universal TADC] ✓ All templates exist
-[Universal TADC] ✓ All airbases valid
-[Universal TADC] ✓ All zones found
-[Universal TADC] Configuration is VALID
-[Universal TADC] ═══════════════════════════════════════
-```
-
-### In-Game F10 Menu Commands
-
-Press **F10** in-game to access TADC utilities:
-
-**Available to Each Coalition:**
-- **Show Squadron Resource Summary** - Current aircraft counts
-- **Show Airbase Status Report** - Operational status of all bases
-- **Show Active Interceptors** - Currently airborne fighters
-- **Show Threat Summary** - Detected enemy aircraft
-- **Broadcast Squadron Summary Now** - Force immediate status update
-- **Show Cargo Delivery Log** - Recent supply missions
-- **Show Zone Coverage Map** - Squadron zone assignments
-
-**Available to All (Mission Commands):**
-- **Emergency Cleanup Interceptors** - Remove stuck/dead groups
-- **Show TADC System Status** - Uptime and system health
-- **Check for Stuck Aircraft** - Manual stuck aircraft check
-- **Show Airbase Health Status** - Parking/spawn issues
-
-### Common Issues & Solutions
-
-#### Issue: "Template not found in mission"
-
-**Cause:** Template name in config doesn't match mission editor
-**Solution:**
-1. Check exact spelling and case
-2. Ensure template is in mission editor
-3. Verify template is a GROUP (not a unit)
-4. Check template name in mission editor properties
-
-#### Issue: "Airbase not found or wrong coalition"
-
-**Cause:** Airbase name wrong or captured by enemy
-**Solution:**
-1. Check exact airbase spelling (case sensitive)
-2. Verify airbase is owned by correct coalition in mission editor
-3. Use `_G.TDAC_CheckAirbase("AirbaseName")` in DCS console
-
-#### Issue: "No interceptors launching"
-
-**Check:**
-1. Are enemy aircraft detected? (F10 → Show Threat Summary)
-2. Are squadrons operational? (F10 → Show Squadron Resource Summary)
-3. Is airbase captured/destroyed? (F10 → Show Airbase Status Report)
-4. Are squadrons on cooldown? (F10 → Show Squadron Resource Summary)
-5. Check intercept ratio settings (might be too low)
-
-#### Issue: "Cargo not delivering"
-
-**Check:**
-1. Is cargo template name correct?
-2. Are supply airbases valid and friendly?
-3. Is destination airbase captured/operational?
-4. Check parking availability (F10 → Show Airbase Health Status)
-5. Look for "Cargo delivery detected" messages in log
-
-#### Issue: "Aircraft spawning stuck at parking"
-
-**Cause:** Parking spots occupied or insufficient space
-**Solution:**
-1. Use F10 → Check for Stuck Aircraft
-2. Use F10 → Emergency Cleanup Interceptors
-3. Check airbase parking capacity (larger aircraft need more space)
-4. Reduce squadron sizes if parking is limited
-
-### DCS Console Diagnostics
-
-Open DCS Lua console (**F12** or scripting console) and run:
-
-```lua
--- Check all supply airbase ownership
-_G.TDAC_CheckAirbaseOwnership()
-
--- Check specific airbase
-_G.TDAC_CheckAirbase("Kilpyavr")
-
--- Validate dispatcher configuration
-_G.TDAC_RunConfigCheck()
-
--- Check airbase parking availability
-_G.TDAC_LogAirbaseParking("Kilpyavr")
-
--- Test cargo spawn (debugging)
-_G.TDAC_CargoDispatcher_TestSpawn("CARGO_RED_AN26_TEMPLATE", "SupplyBase", "DestinationBase")
-```
-
----
-
-## Advanced Features
-
-### Intercept Ratio System
-
-The `interceptRatio` setting controls how many fighters launch per enemy aircraft.
-
-**In `Moose_TADC_Load2nd.lua`:**
-
-```lua
-local TADC_SETTINGS = {
- red = {
- interceptRatio = 0.8, -- RED launches 0.8 fighters per threat
- maxActiveCAP = 8, -- Max 8 groups in air simultaneously
- defaultCooldown = 300, -- 5-minute cooldown after engagement
- },
- blue = {
- interceptRatio = 1.2, -- BLUE launches 1.2 fighters per threat
- maxActiveCAP = 10, -- Max 10 groups in air simultaneously
- defaultCooldown = 300,
- }
-}
-```
-
-**Intercept Ratio Chart:**
-
-| Ratio | 1 Enemy | 4 Enemies | 8 Enemies | Effect |
-|-------|---------|-----------|-----------|--------|
-| 0.5 | 1 fighter | 2 fighters | 4 fighters | Conservative response |
-| 0.8 | 1 fighter | 4 fighters | 7 fighters | **Balanced (default)** |
-| 1.0 | 1 fighter | 4 fighters | 8 fighters | 1:1 parity |
-| 1.4 | 2 fighters | 6 fighters | 12 fighters | Strong response |
-| 2.0 | 2 fighters | 8 fighters | 16 fighters | Overwhelming force |
-
-**Tactical Effects:**
-- **Low (0.5-0.8):** Sustainable defense, squadrons last longer
-- **Medium (0.8-1.2):** Balanced dogfights, realistic attrition
-- **High (1.4-2.0):** Strong defense, rapid squadron depletion
-
-**Asymmetric Scenarios:**
-```lua
--- RED advantage
-red = { interceptRatio = 1.4 },
-blue = { interceptRatio = 0.8 }
-
--- BLUE advantage
-red = { interceptRatio = 0.8 },
-blue = { interceptRatio = 1.4 }
-```
-
-### Distance-Based Engagement
-
-Control how far squadrons will chase threats:
-
-```lua
-{
- templateName = "RED_CAP_Base_MiG29",
- displayName = "Base Defense",
- airbaseName = "Kilpyavr",
- aircraft = 12,
- -- ... other settings ...
-
- zoneConfig = {
- maxEngagementRange = 50000, -- Won't engage threats >50km from base
- primaryResponse = 1.0,
- secondaryResponse = 0.6
- }
-}
-```
-
-### Cooldown System
-
-After launching interceptors, squadrons go on cooldown to prevent spam:
-
-```lua
-local TADC_SETTINGS = {
- red = {
- defaultCooldown = 300, -- 5 minutes between launches
- -- ... other settings ...
- }
-}
-```
-
-**Per-Squadron Cooldown (optional):**
-```lua
-{
- templateName = "RED_CAP_Base_MiG29",
- cooldownOverride = 600, -- This squadron: 10-minute cooldown
- -- ... other settings ...
-}
-```
-
-### Aircraft Skill Levels
-
-Adjust AI difficulty per squadron:
-
-```lua
-skill = AI.Skill.AVERAGE -- Easiest, good for training
-skill = AI.Skill.GOOD -- Below average
-skill = AI.Skill.HIGH -- Average pilots
-skill = AI.Skill.EXCELLENT -- Above average (recommended)
-skill = AI.Skill.ACE -- Hardest, expert pilots
-```
-
-**Mixed Difficulty Example:**
-```lua
-RED_SQUADRON_CONFIG = {
- {
- displayName = "Elite Squadron",
- skill = AI.Skill.ACE, -- Best pilots
- aircraft = 8,
- -- ...
- },
- {
- displayName = "Regular Squadron",
- skill = AI.Skill.GOOD, -- Average pilots
- aircraft = 12,
- -- ...
- }
-}
-```
-
----
-
-## Common Scenarios
-
-### Scenario 1: Simple Border Defense
-
-**Goal:** RED defends northern border, BLUE defends southern border
-
-```lua
--- RED Configuration
-RED_SQUADRON_CONFIG = {
- {
- templateName = "RED_CAP_North_MiG29",
- displayName = "Northern Border CAP",
- airbaseName = "Northern_Base",
- aircraft = 12,
- skill = AI.Skill.EXCELLENT,
- altitude = 20000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER",
- primaryZone = "RED BORDER"
- }
-}
-
--- BLUE Configuration
-BLUE_SQUADRON_CONFIG = {
- {
- templateName = "BLUE_CAP_South_F16",
- displayName = "Southern Border CAP",
- airbaseName = "Southern_Base",
- aircraft = 12,
- skill = AI.Skill.EXCELLENT,
- altitude = 20000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER",
- primaryZone = "BLUE BORDER"
- }
-}
-```
-
-**In Mission Editor:**
-- Create zone "RED BORDER" (helicopter waypoints on northern border)
-- Create zone "BLUE BORDER" (helicopter waypoints on southern border)
-
----
-
-### Scenario 2: Layered Defense Network
-
-**Goal:** Multiple squadrons covering overlapping zones with different priorities
-
-```lua
-RED_SQUADRON_CONFIG = {
- -- Outer Layer: Long-range interceptors
- {
- templateName = "RED_LONG_RANGE_MiG31",
- displayName = "Long Range Interceptors",
- airbaseName = "Forward_Base",
- aircraft = 8,
- skill = AI.Skill.EXCELLENT,
- altitude = 35000,
- speed = 450,
- patrolTime = 20,
- type = "FIGHTER",
- primaryZone = "OUTER PERIMETER"
- },
-
- -- Middle Layer: General defense
- {
- templateName = "RED_CAP_MiG29",
- displayName = "Middle Defense CAP",
- airbaseName = "Central_Base",
- aircraft = 12,
- skill = AI.Skill.EXCELLENT,
- altitude = 25000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER",
- primaryZone = "MIDDLE PERIMETER",
- secondaryZone = "OUTER PERIMETER"
- },
-
- -- Inner Layer: Point defense
- {
- templateName = "RED_BASE_DEFENSE_SU27",
- displayName = "Base Defense",
- airbaseName = "Main_Base",
- aircraft = 16,
- skill = AI.Skill.ACE,
- altitude = 20000,
- speed = 320,
- patrolTime = 40,
- type = "FIGHTER",
- primaryZone = "BASE PERIMETER",
- tertiaryZone = "BASE PERIMETER",
- zoneConfig = {
- enableFallback = true,
- fallbackThreshold = 0.3
- }
- }
-}
-```
-
----
-
-### Scenario 3: Sustained Operations with Resupply
-
-**Goal:** Long mission with automated squadron replenishment
-
-**Squadron Config:**
-```lua
-RED_SQUADRON_CONFIG = {
- {
- templateName = "RED_CAP_Frontline_MiG29",
- displayName = "Frontline CAP",
- airbaseName = "Frontline_Base",
- aircraft = 12, -- Will be resupplied
- skill = AI.Skill.EXCELLENT,
- altitude = 20000,
- speed = 350,
- patrolTime = 30,
- type = "FIGHTER",
- primaryZone = "COMBAT ZONE"
- }
-}
-```
-
-**Cargo Config** (in `Moose_TADC_CargoDispatcher.lua`):
-```lua
-local CARGO_SUPPLY_CONFIG = {
- red = {
- cargoTemplate = "CARGO_RED_AN26",
- supplyAirfields = {
- "Rear_Base_1", -- Safe logistics hub
- "Rear_Base_2", -- Backup supply source
- "Central_Depot" -- Main supply depot
- },
- replenishAmount = 4, -- +4 aircraft per delivery
- threshold = 0.75 -- Trigger at 75% (9/12 aircraft)
- }
-}
-```
-
-**Mission Flow:**
-1. Frontline squadron intercepts threats
-2. Squadron drops to 9 aircraft (75%)
-3. Cargo automatically dispatched from rear base
-4. Transport flies to frontline base
-5. Cargo delivers, squadron back to 12 aircraft
-6. Cycle repeats throughout mission
-
----
-
-### Scenario 4: Asymmetric Warfare
-
-**Goal:** RED has numerical superiority, BLUE has quality advantage
-
-```lua
--- RED: More squadrons, lower skill
-local TADC_SETTINGS = {
- red = {
- interceptRatio = 0.8, -- Conservative response
- maxActiveCAP = 12, -- More groups allowed
- }
-}
-
-RED_SQUADRON_CONFIG = {
- {
- templateName = "RED_CAP_1",
- airbaseName = "Base_1",
- aircraft = 16, -- Large squadron
- skill = AI.Skill.GOOD, -- Average skill
- -- ...
- },
- {
- templateName = "RED_CAP_2",
- airbaseName = "Base_2",
- aircraft = 16,
- skill = AI.Skill.GOOD,
- -- ...
- },
- {
- templateName = "RED_CAP_3",
- airbaseName = "Base_3",
- aircraft = 16,
- skill = AI.Skill.GOOD,
- -- ...
- }
-}
-
--- BLUE: Fewer squadrons, higher skill
-local TADC_SETTINGS = {
- blue = {
- interceptRatio = 1.2, -- Aggressive response
- maxActiveCAP = 8, -- Fewer groups
- }
-}
-
-BLUE_SQUADRON_CONFIG = {
- {
- templateName = "BLUE_CAP_1",
- airbaseName = "Base_1",
- aircraft = 10, -- Smaller squadron
- skill = AI.Skill.ACE, -- Elite pilots
- -- ...
- },
- {
- templateName = "BLUE_CAP_2",
- airbaseName = "Base_2",
- aircraft = 10,
- skill = AI.Skill.ACE,
- -- ...
- }
-}
-```
-
----
-
-## Tips for New Mission Makers
-
-### Start Simple
-
-1. **First Mission:** Use 1-2 squadrons per side with no zones
-2. **Second Mission:** Add zone-based defense
-3. **Third Mission:** Add cargo resupply system
-4. **Advanced:** Multi-squadron layered defense with fallback
-
-### Realistic Aircraft Numbers
-
-**Small Airbase:** 6-8 aircraft per squadron
-**Medium Airbase:** 10-12 aircraft per squadron
-**Large Airbase:** 14-18 aircraft per squadron
-
-**Balance across map:** If RED has 40 total aircraft, BLUE should have similar unless asymmetric
-
-### Performance Considerations
-
-- **Limit active groups:** Use `maxActiveCAP` to prevent FPS drops
-- **Zone sizes matter:** Smaller zones = less scanning overhead
-- **Cargo cooldowns:** Prevent cargo spam with reasonable cooldowns
-- **Squadron counts:** 3-5 squadrons per side is a good starting point
-
-### Testing Workflow
-
-1. **Create minimal setup** (1 squadron each side)
-2. **Test in mission editor** using "Fly Now"
-3. **Check F10 menus** for squadron status
-4. **Spawn enemy aircraft** to test intercepts
-5. **Review DCS.log** for validation messages
-6. **Expand gradually** once basic system works
-
-### Common Mistakes to Avoid
-
-❌ **Using UNIT templates instead of GROUP templates**
-✅ Use GROUP templates (late activation groups)
-
-❌ **Misspelling airbase names**
-✅ Copy exact names from mission editor
-
-❌ **Loading scripts in wrong order**
-✅ Squadron Config → Main TADC → Cargo Dispatcher
-
-❌ **Setting intercept ratio too high**
-✅ Start with 0.8-1.0, adjust after testing
-
-❌ **Forgetting to load MOOSE first**
-✅ MOOSE must be first script loaded
-
----
-
-## Conclusion
-
-The Universal TADC system provides mission makers with powerful, automated air defense capabilities that create dynamic, realistic air combat scenarios. By following this guide, even new mission makers can create sophisticated missions with minimal scripting knowledge.
-
-### Key Takeaways
-
-✅ **Three scripts work together:** Squadron Config → Main TADC → Cargo Dispatcher
-✅ **Configuration is simple:** Edit tables, not complex code
-✅ **Both coalitions operate independently:** Balanced or asymmetric scenarios
-✅ **Zones enable tactical behavior:** Realistic area-of-responsibility system
-✅ **Cargo enables sustained operations:** Long missions with automatic resupply
-✅ **Built-in validation:** Checks configuration before mission starts
-✅ **F10 menus provide visibility:** Monitor system status in real-time
-
-### Getting Help
-
-If you encounter issues:
-
-1. Check DCS.log for validation errors
-2. Use F10 menu diagnostics
-3. Run console commands for detailed info
-4. Review this guide's troubleshooting section
-5. Start simple and expand gradually
-
-### Next Steps
-
-1. Set up your first squadron (1 RED, 1 BLUE)
-2. Test basic intercept behavior
-3. Add zones for tactical depth
-4. Implement cargo resupply for long missions
-5. Experiment with advanced features
-
-Happy mission making! 🚁✈️
-
----
-*Author: F99th-TracerFacer
-*Document Version: 1.0*
-*Last Updated: October 2025*
-*Compatible with: MOOSE Framework & DCS World*
diff --git a/Moose_Tanker/Moose_Tanker.lua b/Moose_Tanker/Moose_Tanker.lua
deleted file mode 100644
index 85baf7f..0000000
--- a/Moose_Tanker/Moose_Tanker.lua
+++ /dev/null
@@ -1,2493 +0,0 @@
--- ============================================================================
--- 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()
-
-function BlueTankerEventHandler:OnEventBirth(EventData)
- 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
-
-function BlueTankerEventHandler:OnEventDead(EventData)
- 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
-
-function BlueTankerEventHandler:OnEventHit(EventData)
- 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
-
--- ============================================================================
--- SPAWN OBJECTS AND FUNCTIONS
--- ============================================================================
-
--- Function to spawn KC-135
-function SpawnTanker()
- 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()
- 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
-
--- 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
--- ============================================================================
-
-BlueTankerEventHandler:HandleEvent(EVENTS.Birth)
-BlueTankerEventHandler:HandleEvent(EVENTS.Dead)
-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/Patch-MooseMissions/Download-MooseInclude.ps1 b/Patch-MooseMissions/Download-MooseInclude.ps1
deleted file mode 100644
index b04c7b2..0000000
--- a/Patch-MooseMissions/Download-MooseInclude.ps1
+++ /dev/null
@@ -1,105 +0,0 @@
-# Download latest Moose_.lua from GitHub
-
-[CmdletBinding()]
-param(
- # Destination path for Moose_.lua. If not provided, defaults to repo root alongside Patch-MooseMissions folder
- [Parameter(Mandatory=$false)]
- [string]$MooseLuaPath,
-
- # Use -Force to actually download; otherwise runs in WhatIf/preview mode
- [Parameter(Mandatory=$false)]
- [switch]$Force
-)
-
-# Resolve a robust script root that works even when dot-sourced or in older hosts
-$__scriptRoot = $PSScriptRoot
-if ([string]::IsNullOrWhiteSpace($__scriptRoot)) {
- try { $__scriptRoot = Split-Path -Parent $PSCommandPath } catch {}
-}
-if ([string]::IsNullOrWhiteSpace($__scriptRoot)) {
- try { $__scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path } catch {}
-}
-
-# If destination wasn't provided, default to the repo root (parent of Patch-MooseMissions)
-if ([string]::IsNullOrWhiteSpace($MooseLuaPath)) {
- $parentOfScriptRoot = $__scriptRoot
- if (-not [string]::IsNullOrWhiteSpace($parentOfScriptRoot)) {
- try { $parentOfScriptRoot = Split-Path -Path $parentOfScriptRoot -Parent } catch { $parentOfScriptRoot = $null }
- }
- if ([string]::IsNullOrWhiteSpace($parentOfScriptRoot)) {
- # Final fallback: current directory
- $parentOfScriptRoot = (Get-Location).Path
- }
- $MooseLuaPath = Join-Path -Path $parentOfScriptRoot -ChildPath 'Moose_.lua'
-}
-
-# Determine WhatIf mode (preview by default)
-$runInWhatIfMode = -not $Force
-
-# Ensure destination directory exists (avoid null/invalid path issues)
-if ([string]::IsNullOrWhiteSpace($MooseLuaPath)) {
- throw "Destination path for Moose_.lua is empty. Provide -MooseLuaPath."
-}
-
-$destDir = Split-Path -Path $MooseLuaPath -Parent
-if (-not [string]::IsNullOrWhiteSpace($destDir) -and -not (Test-Path $destDir)) {
- New-Item -Path $destDir -ItemType Directory -Force -WhatIf:$false | Out-Null
-}
-
-# Enable TLS 1.2 for GitHub downloads on older environments
-try {
- [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12
-} catch {}
-
-if (-not $runInWhatIfMode) {
- Write-Host "Downloading latest Moose_.lua from GitHub..." -ForegroundColor Yellow
-
- $mooseGitHubUrl = "https://raw.githubusercontent.com/FlightControl-Master/MOOSE_INCLUDE/develop/Moose_Include_Static/Moose_.lua"
- $backupPath = "$MooseLuaPath.backup"
-
- try {
- # Create a single backup of existing Moose_.lua if it exists and backup doesn't exist yet
- if ((Test-Path $MooseLuaPath) -and -not (Test-Path $backupPath)) {
- Write-Host " Creating backup: $backupPath" -ForegroundColor Gray
- Copy-Item $MooseLuaPath $backupPath -Force -WhatIf:$false
- }
-
- # Download the file
- $ProgressPreference = 'SilentlyContinue' # Speeds up Invoke-WebRequest
- Invoke-WebRequest -Uri $mooseGitHubUrl -OutFile $MooseLuaPath -ErrorAction Stop
- $ProgressPreference = 'Continue'
-
- # Verify the download
- if (Test-Path $MooseLuaPath) {
- $fileSize = (Get-Item $MooseLuaPath).Length
- Write-Host " SUCCESS: Downloaded Moose_.lua ($([math]::Round($fileSize/1MB, 2)) MB)" -ForegroundColor Green
- } else {
- throw "Downloaded file not found after download"
- }
- }
- catch {
- Write-Error "Failed to download Moose_.lua from GitHub: $_"
- Write-Host " URL: $mooseGitHubUrl" -ForegroundColor Red
-
- # Check if we have a backup or existing file to use
- if (Test-Path $MooseLuaPath) {
- Write-Warning "Using existing Moose_.lua file instead"
- } else {
- Write-Error "No Moose_.lua file available. Cannot proceed."
- exit 1
- }
- }
-
- Write-Host ""
-} else {
- Write-Host "WHATIF: Would download latest Moose_.lua from GitHub" -ForegroundColor Magenta
- Write-Host " URL: https://raw.githubusercontent.com/FlightControl-Master/MOOSE_INCLUDE/develop/Moose_Include_Static/Moose_.lua" -ForegroundColor Gray
- Write-Host " Destination: $MooseLuaPath" -ForegroundColor Gray
-
- # Still need to verify file exists for WhatIf to show what would be patched
- if (-not (Test-Path $MooseLuaPath)) {
- Write-Warning "Moose_.lua not found at $MooseLuaPath - run with -Force to download it"
- Write-Warning "Continuing with WhatIf to show which missions would be processed..."
- }
- Write-Host ""
-}
\ No newline at end of file
diff --git a/Patch-MooseMissions/Patch-MooseMissions.ps1 b/Patch-MooseMissions/Patch-MooseMissions.ps1
deleted file mode 100644
index e6e3909..0000000
--- a/Patch-MooseMissions/Patch-MooseMissions.ps1
+++ /dev/null
@@ -1,306 +0,0 @@
-<#
-.SYNOPSIS
- Patches DCS mission files (.miz) with updated Lua scripts.
-
-.DESCRIPTION
- This script extracts a DCS mission file (which is a ZIP archive), replaces or adds
- a Lua script file, and repackages the mission. This allows you to update scripts
- like Moose_.lua across multiple missions without opening the DCS Mission Editor.
-
-.PARAMETER MissionPath
- Path to the .miz mission file to patch. Can be a single file or multiple files.
-
-.PARAMETER LuaScriptPath
- Path to the Lua script file to insert/replace in the mission.
-
-.PARAMETER ScriptName
- Optional. The name the script should have inside the mission file.
- If not specified, uses the filename from LuaScriptPath.
-
-.PARAMETER OutputPath
- Optional. Directory where patched missions should be saved.
- If not specified, saves to the same directory as the input mission.
-
-.PARAMETER NoVersionIncrement
- If specified, does not increment the version number in the filename.
- By default, the script automatically increments the patch version (e.g., 1.1.2 -> 1.1.3).
- WARNING: Using this flag will OVERWRITE the original mission file!
-
-.EXAMPLE
- .\Patch-MooseMissions.ps1 -MissionPath "C:\Missions\MyMission-1.2.3.miz" -LuaScriptPath "C:\Scripts\Moose_.lua"
- Creates: MyMission-1.2.4.miz (original 1.2.3 remains untouched)
-
-.EXAMPLE
- Get-ChildItem "C:\Missions\*.miz" | .\Patch-MooseMissions.ps1 -LuaScriptPath "C:\Scripts\Moose_.lua"
-
-.EXAMPLE
- .\Patch-MooseMissions.ps1 -MissionPath "Mission-2.1.miz" -LuaScriptPath "MyScript.lua" -NoVersionIncrement
- WARNING: Overwrites Mission-2.1.miz
-
-.NOTES
- Author: F99th-TracerFacer
- Version: 2.0
- DCS mission files are ZIP archives containing a 'l10n' folder with a 'DEFAULT' subfolder
- where Lua scripts are stored.
-#>
-
-[CmdletBinding()]
-param(
- [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
- [Alias("FullName", "Path")]
- [string[]]$MissionPath,
-
- [Parameter(Mandatory=$true)]
- [string]$LuaScriptPath,
-
- [Parameter(Mandatory=$false)]
- [string]$ScriptName,
-
- [Parameter(Mandatory=$false)]
- [string]$OutputPath,
-
- [Parameter(Mandatory=$false)]
- [switch]$NoVersionIncrement
-)
-
-begin {
- # Verify Lua script exists
- if (-not (Test-Path $LuaScriptPath)) {
- throw "Lua script not found: $LuaScriptPath"
- }
-
- # Determine script name to use inside mission
- if ([string]::IsNullOrWhiteSpace($ScriptName)) {
- $ScriptName = Split-Path $LuaScriptPath -Leaf
- }
-
- Write-Host "========================================" -ForegroundColor Cyan
- Write-Host "DCS Mission Patcher" -ForegroundColor Cyan
- Write-Host "========================================" -ForegroundColor Cyan
- Write-Host "Lua Script: $ScriptName" -ForegroundColor Yellow
- Write-Host "Source: $LuaScriptPath" -ForegroundColor Gray
- Write-Host "Version Increment: $(if ($NoVersionIncrement) { 'Disabled (OVERWRITES ORIGINAL!)' } else { 'Enabled' })" -ForegroundColor $(if ($NoVersionIncrement) { 'Red' } else { 'Green' })
- Write-Host ""
-
- # Validate output path if specified
- if ($OutputPath -and -not (Test-Path $OutputPath)) {
- Write-Host "Creating output directory: $OutputPath" -ForegroundColor Yellow
- New-Item -Path $OutputPath -ItemType Directory -Force -WhatIf:$false | Out-Null
- }
-
- $successCount = 0
- $failCount = 0
-
- # Function to increment version number in filename
- function Get-IncrementedFilename {
- param(
- [string]$FileName
- )
-
- # Remove extension
- $nameWithoutExt = [System.IO.Path]::GetFileNameWithoutExtension($FileName)
- $extension = [System.IO.Path]::GetExtension($FileName)
-
- # Try to find version patterns: X.Y.Z or X.Y or just X at the end
- # Patterns to match (in order of specificity):
- # 1. Major.Minor.Patch (e.g., 1.2.3)
- # 2. Major.Minor (e.g., 1.2)
- # 3. Just version number at end (e.g., v5 or -5)
-
- # Pattern 1: X.Y.Z (most common)
- if ($nameWithoutExt -match '^(.+?)[-_\s]?(\d+)\.(\d+)\.(\d+)$') {
- $baseName = $matches[1]
- $major = $matches[2]
- $minor = $matches[3]
- $patch = [int]$matches[4]
- $newPatch = $patch + 1
- $separator = if ($nameWithoutExt -match '[-_\s](\d+\.\d+\.\d+)$') { $matches[0][0] } else { '' }
- return "$baseName$separator$major.$minor.$newPatch$extension"
- }
- # Pattern 2: X.Y
- elseif ($nameWithoutExt -match '^(.+?)[-_\s]?(\d+)\.(\d+)$') {
- $baseName = $matches[1]
- $major = $matches[2]
- $minor = [int]$matches[3]
- $newMinor = $minor + 1
- $separator = if ($nameWithoutExt -match '[-_\s](\d+\.\d+)$') { $matches[0][0] } else { '' }
- return "$baseName$separator$major.$newMinor$extension"
- }
- # Pattern 3: Just a number at the end
- elseif ($nameWithoutExt -match '^(.+?)[-_\s]?(\d+)$') {
- $baseName = $matches[1]
- $version = [int]$matches[2]
- $newVersion = $version + 1
- $separator = if ($nameWithoutExt -match '[-_\s]\d+$') { $matches[0][0] } else { '' }
- return "$baseName$separator$newVersion$extension"
- }
- # No version found - append .1
- else {
- return "$nameWithoutExt-1.0.1$extension"
- }
- }
-}
-
-process {
- foreach ($mission in $MissionPath) {
- try {
- # Resolve full path
- $missionFile = Resolve-Path $mission -ErrorAction Stop
-
- Write-Host "Processing: " -NoNewline -ForegroundColor White
- Write-Host "$missionFile" -ForegroundColor Cyan
-
- # Verify mission file exists and is a .miz file
- if (-not (Test-Path $missionFile)) {
- throw "Mission file not found: $missionFile"
- }
-
- if ([System.IO.Path]::GetExtension($missionFile) -ne ".miz") {
- throw "File is not a .miz mission file: $missionFile"
- }
-
- # Create temporary extraction directory
- $tempDir = Join-Path $env:TEMP ("DCS_Mission_Patch_" + [System.Guid]::NewGuid().ToString())
- New-Item -Path $tempDir -ItemType Directory -Force -WhatIf:$false | Out-Null
-
- try {
- # Extract mission file (it's a ZIP archive)
- # Use .NET classes instead of Expand-Archive for better .miz support
- Write-Host " Extracting mission..." -ForegroundColor Gray
-
- Add-Type -Assembly System.IO.Compression.FileSystem
- [System.IO.Compression.ZipFile]::ExtractToDirectory($missionFile, $tempDir)
-
- # Determine Lua script destination in mission structure
- # DCS stores Lua scripts in: l10n/DEFAULT/ folder
- $luaDestDir = Join-Path $tempDir "l10n\DEFAULT"
-
- # Create directory if it doesn't exist
- if (-not (Test-Path $luaDestDir)) {
- Write-Host " Creating l10n/DEFAULT directory..." -ForegroundColor Yellow
- New-Item -Path $luaDestDir -ItemType Directory -Force -WhatIf:$false | Out-Null
- }
-
- $luaDestPath = Join-Path $luaDestDir $ScriptName
-
- # Check if script already exists
- if (Test-Path $luaDestPath) {
- Write-Host " Replacing existing script: $ScriptName" -ForegroundColor Yellow
- } else {
- Write-Host " Adding new script: $ScriptName" -ForegroundColor Green
- }
-
- # Copy Lua script to mission
- Copy-Item $LuaScriptPath $luaDestPath -Force -WhatIf:$false
-
- # Determine output filename and location
- $originalFileName = Split-Path $missionFile -Leaf
-
- if ($NoVersionIncrement) {
- # Keep original filename
- $outputFileName = $originalFileName
- } else {
- # Increment version number
- $outputFileName = Get-IncrementedFilename -FileName $originalFileName
- Write-Host " Version increment: $originalFileName -> $outputFileName" -ForegroundColor Cyan
- }
-
- # Determine output directory
- if ($OutputPath) {
- $outputDir = $OutputPath
- } else {
- $outputDir = Split-Path $missionFile -Parent
- }
-
- $outputMission = Join-Path $outputDir $outputFileName
-
- # Remove existing mission file if it exists
- if (Test-Path $outputMission) {
- Remove-Item $outputMission -Force -WhatIf:$false
- }
-
- # Repackage mission file
- Write-Host " Repackaging mission..." -ForegroundColor Gray
-
- # Use .NET classes for better compatibility
- Add-Type -Assembly System.IO.Compression.FileSystem
- $compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
-
- # Create ZIP manually to ensure proper path separators (forward slashes required by ZIP spec)
- # CreateFromDirectory uses backslashes on Windows which corrupts DCS mission files
- $zipStream = New-Object System.IO.FileStream($outputMission, [System.IO.FileMode]::Create)
- $archive = New-Object System.IO.Compression.ZipArchive($zipStream, [System.IO.Compression.ZipArchiveMode]::Create)
-
- try {
- # Get all files in temp directory and add them to ZIP
- $files = Get-ChildItem -Path $tempDir -Recurse -File
-
- foreach ($file in $files) {
- # Get relative path for entry name
- $relativePath = $file.FullName.Substring($tempDir.Length + 1)
- # CRITICAL: Normalize path separators to forward slashes (ZIP standard)
- # DCS will fail to load missions with backslashes in ZIP entry names
- $entryName = $relativePath.Replace('\', '/')
-
- # Create entry in ZIP
- $entry = $archive.CreateEntry($entryName, $compressionLevel)
-
- # Write file content to entry
- $entryStream = $entry.Open()
- try {
- $fileStream = [System.IO.File]::OpenRead($file.FullName)
- try {
- $fileStream.CopyTo($entryStream)
- }
- finally {
- $fileStream.Close()
- }
- }
- finally {
- $entryStream.Close()
- }
- }
- }
- finally {
- $archive.Dispose()
- $zipStream.Close()
- }
-
- Write-Host " SUCCESS: Mission patched successfully!" -ForegroundColor Green
- Write-Host " Output: $outputMission" -ForegroundColor Gray
- Write-Host ""
-
- $successCount++
- }
- finally {
- # Clean up temporary directory
- if (Test-Path $tempDir) {
- Remove-Item $tempDir -Recurse -Force -WhatIf:$false
- }
- }
- }
- catch {
- Write-Host " ERROR: $_" -ForegroundColor Red
- Write-Host ""c
- $failCount++
- }
- }
-}
-
-end {
- Write-Host "========================================" -ForegroundColor Cyan
- Write-Host "Patching Complete" -ForegroundColor Cyan
- Write-Host "========================================" -ForegroundColor Cyan
- Write-Host "Successful: $successCount" -ForegroundColor Green
- Write-Host "Failed: $failCount" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "Gray" })
- Write-Host ""
-
- if ($successCount -gt 0) {
- if ($NoVersionIncrement) {
- Write-Host "WARNING: Original mission files were OVERWRITTEN!" -ForegroundColor Red
- } else {
- Write-Host "INFO: Original mission files remain untouched. New versioned files created." -ForegroundColor Green
- }
- Write-Host "TIP: Test your patched missions in DCS before using them!" -ForegroundColor Yellow
- }
-}
\ No newline at end of file
diff --git a/Patch-MooseMissions/Patch-MyMooseMissions.ps1 b/Patch-MooseMissions/Patch-MyMooseMissions.ps1
deleted file mode 100644
index e3b84e3..0000000
--- a/Patch-MooseMissions/Patch-MyMooseMissions.ps1
+++ /dev/null
@@ -1,384 +0,0 @@
-<#
-.SYNOPSIS
- Batch updates all DCS missions with the latest Moose framework.
-
-.DESCRIPTION
- This script scans through the DCS mission development folder structure,
- finds the latest version of each mission (.miz files with version numbers),
- and patches them with the updated Moose_.lua script using Patch-MooseMissions.ps1.
-
- It processes missions in the structure: C:\DCS_MissionDev\DCS_[Terrain]\[MissionFolder]\
- For each mission folder, it identifies the latest version (by version number or file date)
- and creates a new patched version.
-
-.PARAMETER RootPath
- Root directory containing DCS terrain folders. Defaults to C:\DCS_MissionDev
-
-.PARAMETER MooseLuaPath
- Path to the Moose_.lua file to patch into missions. Defaults to C:\DCS_MissionDev\Moose_.lua
-
-.PARAMETER WhatIf
- Shows what would be processed without actually patching any files.
-
-.PARAMETER ExcludeTerrain
- Array of terrain folder names to exclude from processing (e.g., @("DCS_Normandy", "DCS_Falklands"))
-
-.EXAMPLE
- .\Patch-MyMooseMissions.ps1
- Shows what missions would be processed (WhatIf mode - no changes made)
-
-.EXAMPLE
- .\Patch-MyMooseMissions.ps1 -Force
- Actually patches all missions with the latest Moose_.lua
-
-.EXAMPLE
- .\Patch-MyMooseMissions.ps1 -Force -ExcludeTerrain @("DCS_Normandy", "DCS_Falklands")
- Processes all missions except those in Normandy and Falklands terrains
-
-.NOTES
- Author: F99th-TracerFacer
- Version: 1.0
- Requires: Patch-MooseMissions.ps1 in the same directory
-#>
-
-[CmdletBinding()]
-param(
- [Parameter(Mandatory=$false)]
- [string]$RootPath = "C:\DCS_MissionDev",
-
- [Parameter(Mandatory=$false)]
- [string]$MooseLuaPath = "C:\DCS_MissionDev\Moose_.lua",
-
- [Parameter(Mandatory=$false)]
- [string[]]$ExcludeTerrain = @(),
-
- [Parameter(Mandatory=$false)]
- [switch]$Force
-)
-
-Clear-Host
-
-# Enable WhatIf mode by default unless -Force is specified
-$runInWhatIfMode = -not $Force
-
-if ($runInWhatIfMode) {
- Write-Host "========================================" -ForegroundColor Magenta
- Write-Host "RUNNING IN WHATIF MODE (Preview Only)" -ForegroundColor Magenta
- Write-Host "No files will be modified" -ForegroundColor Magenta
- Write-Host "Use -Force parameter to actually patch missions" -ForegroundColor Magenta
- Write-Host "========================================" -ForegroundColor Magenta
- Write-Host ""
-}
-
-# Verify paths exist
-if (-not (Test-Path $RootPath)) {
- Write-Error "Root path not found: $RootPath"
- exit 1
-}
-
-# Verify the Patch-MooseMissions.ps1 script exists
-$patchScriptPath = Join-Path $PSScriptRoot "Patch-MooseMissions.ps1"
-if (-not (Test-Path $patchScriptPath)) {
- Write-Error "Patch-MooseMissions.ps1 not found in: $PSScriptRoot"
- exit 1
-}
-
-Write-Host "========================================" -ForegroundColor Cyan
-Write-Host "Batch Moose Mission Patcher" -ForegroundColor Cyan
-Write-Host "========================================" -ForegroundColor Cyan
-Write-Host "Root Path: $RootPath" -ForegroundColor Yellow
-Write-Host "Moose Script: $MooseLuaPath" -ForegroundColor Yellow
-Write-Host "Patch Script: $patchScriptPath" -ForegroundColor Yellow
-if ($ExcludeTerrain.Count -gt 0) {
- Write-Host "Excluded Terrains: $($ExcludeTerrain -join ', ')" -ForegroundColor Yellow
-}
-Write-Host ""
-
-# Download latest Moose_.lua from GitHub
-if (-not $runInWhatIfMode) {
- Write-Host "Downloading latest Moose_.lua from GitHub..." -ForegroundColor Yellow
-
- $mooseGitHubUrl = "https://raw.githubusercontent.com/FlightControl-Master/MOOSE_INCLUDE/develop/Moose_Include_Static/Moose_.lua"
- $backupPath = "$MooseLuaPath.backup"
-
- try {
- # Create a single backup of existing Moose_.lua if it exists and backup doesn't exist yet
- if ((Test-Path $MooseLuaPath) -and -not (Test-Path $backupPath)) {
- Write-Host " Creating backup: $backupPath" -ForegroundColor Gray
- Copy-Item $MooseLuaPath $backupPath -Force -WhatIf:$false
- }
-
- # Download the file
- $ProgressPreference = 'SilentlyContinue' # Speeds up Invoke-WebRequest
- Invoke-WebRequest -Uri $mooseGitHubUrl -OutFile $MooseLuaPath -ErrorAction Stop
- $ProgressPreference = 'Continue'
-
- # Verify the download
- if (Test-Path $MooseLuaPath) {
- $fileSize = (Get-Item $MooseLuaPath).Length
- Write-Host " SUCCESS: Downloaded Moose_.lua ($([math]::Round($fileSize/1MB, 2)) MB)" -ForegroundColor Green
- } else {
- throw "Downloaded file not found after download"
- }
- }
- catch {
- Write-Error "Failed to download Moose_.lua from GitHub: $_"
- Write-Host " URL: $mooseGitHubUrl" -ForegroundColor Red
-
- # Check if we have a backup or existing file to use
- if (Test-Path $MooseLuaPath) {
- Write-Warning "Using existing Moose_.lua file instead"
- } else {
- Write-Error "No Moose_.lua file available. Cannot proceed."
- exit 1
- }
- }
-
- Write-Host ""
-} else {
- Write-Host "WHATIF: Would download latest Moose_.lua from GitHub" -ForegroundColor Magenta
- Write-Host " URL: https://raw.githubusercontent.com/FlightControl-Master/MOOSE_INCLUDE/develop/Moose_Include_Static/Moose_.lua" -ForegroundColor Gray
- Write-Host " Destination: $MooseLuaPath" -ForegroundColor Gray
-
- # Still need to verify file exists for WhatIf to show what would be patched
- if (-not (Test-Path $MooseLuaPath)) {
- Write-Warning "Moose_.lua not found at $MooseLuaPath - run with -Force to download it"
- Write-Warning "Continuing with WhatIf to show which missions would be processed..."
- }
- Write-Host ""
-}
-
-# Function to parse version number from filename
-function Get-VersionNumber {
- param([string]$FileName)
-
- $nameWithoutExt = [System.IO.Path]::GetFileNameWithoutExtension($FileName)
-
- # Try X.Y.Z pattern
- if ($nameWithoutExt -match '(\d+)\.(\d+)\.(\d+)') {
- $major = [int]$matches[1]
- $minor = [int]$matches[2]
- $patch = [int]$matches[3]
- return [PSCustomObject]@{
- Major = $major
- Minor = $minor
- Patch = $patch
- Value = ($major * 1000000) + ($minor * 1000) + $patch
- HasVersion = $true
- }
- }
- # Try X.Y pattern
- elseif ($nameWithoutExt -match '(\d+)\.(\d+)') {
- $major = [int]$matches[1]
- $minor = [int]$matches[2]
- return [PSCustomObject]@{
- Major = $major
- Minor = $minor
- Patch = 0
- Value = ($major * 1000000) + ($minor * 1000)
- HasVersion = $true
- }
- }
- # Try single version number
- elseif ($nameWithoutExt -match '(\d+)$') {
- $version = [int]$matches[1]
- return [PSCustomObject]@{
- Major = $version
- Minor = 0
- Patch = 0
- Value = $version * 1000000
- HasVersion = $true
- }
- }
-
- # No version found
- return [PSCustomObject]@{
- Major = 0
- Minor = 0
- Patch = 0
- Value = 0
- HasVersion = $false
- }
-}
-
-# Function to get the latest mission file from a directory
-function Get-LatestMission {
- param([string]$DirectoryPath)
-
- # Get all .miz files in the directory (not subdirectories)
- $mizFiles = Get-ChildItem -Path $DirectoryPath -Filter "*.miz" -File -ErrorAction SilentlyContinue
-
- if ($mizFiles.Count -eq 0) {
- return $null
- }
-
- # Separate files with version numbers from those without
- $versionedFiles = @()
- $nonVersionedFiles = @()
-
- foreach ($file in $mizFiles) {
- $version = Get-VersionNumber -FileName $file.Name
- if ($version.HasVersion) {
- $versionedFiles += [PSCustomObject]@{
- File = $file
- Version = $version
- }
- } else {
- $nonVersionedFiles += $file
- }
- }
-
- # If we have versioned files, return the one with the highest version
- if ($versionedFiles.Count -gt 0) {
- $latest = $versionedFiles | Sort-Object -Property { $_.Version.Value } -Descending | Select-Object -First 1
- return $latest.File
- }
-
- # If no versioned files, return the most recently modified file
- if ($nonVersionedFiles.Count -gt 0) {
- return $nonVersionedFiles | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1
- }
-
- return $null
-}
-
-# Get all DCS terrain folders (folders starting with "DCS_")
-$terrainFolders = Get-ChildItem -Path $RootPath -Directory | Where-Object { $_.Name -match '^DCS_' }
-
-if ($terrainFolders.Count -eq 0) {
- Write-Warning "No DCS terrain folders found in: $RootPath"
- exit 0
-}
-
-Write-Host "Found $($terrainFolders.Count) terrain folder(s)" -ForegroundColor Green
-Write-Host ""
-
-# Track statistics
-$totalMissionsFound = 0
-$totalMissionsPatched = 0
-$totalMissionsFailed = 0
-$skippedTerrains = 0
-
-# Process each terrain folder
-foreach ($terrainFolder in $terrainFolders) {
- $terrainName = $terrainFolder.Name
-
- # Check if this terrain should be excluded
- if ($ExcludeTerrain -contains $terrainName) {
- Write-Host "SKIPPING TERRAIN: $terrainName (excluded)" -ForegroundColor DarkGray
- $skippedTerrains++
- continue
- }
-
- Write-Host "TERRAIN: $terrainName" -ForegroundColor Cyan
-
- # Get all subdirectories (mission folders)
- $missionFolders = Get-ChildItem -Path $terrainFolder.FullName -Directory -ErrorAction SilentlyContinue
-
- if ($missionFolders.Count -eq 0) {
- Write-Host " No mission folders found" -ForegroundColor DarkGray
- Write-Host ""
- continue
- }
-
- Write-Host " Found $($missionFolders.Count) mission folder(s)" -ForegroundColor Gray
-
- # Process each mission folder
- foreach ($missionFolder in $missionFolders) {
- $missionFolderName = $missionFolder.Name
-
- # Get the latest mission file
- $latestMission = Get-LatestMission -DirectoryPath $missionFolder.FullName
-
- if ($null -eq $latestMission) {
- Write-Host " [$missionFolderName] No .miz files found" -ForegroundColor DarkGray
- continue
- }
-
- $totalMissionsFound++
-
- Write-Host " [$missionFolderName] Latest: " -NoNewline -ForegroundColor White
- Write-Host "$($latestMission.Name)" -ForegroundColor Yellow
-
- # Execute the patch script
- if (-not $runInWhatIfMode) {
- try {
- # Get the expected new filename before patching
- $expectedNewFile = $null
- $originalFileName = $latestMission.Name
- $missionDir = $latestMission.DirectoryName
-
- # Calculate what the new version filename should be (simplified version increment logic)
- $nameWithoutExt = [System.IO.Path]::GetFileNameWithoutExtension($originalFileName)
- $extension = [System.IO.Path]::GetExtension($originalFileName)
-
- if ($nameWithoutExt -match '^(.+?)[-_\s]?(\d+)\.(\d+)\.(\d+)$') {
- $baseName = $matches[1]
- $major = $matches[2]
- $minor = $matches[3]
- $patch = [int]$matches[4] + 1
- $separator = if ($nameWithoutExt -match '[-_\s](\d+\.\d+\.\d+)$') { $matches[0][0] } else { '' }
- $expectedNewFile = Join-Path $missionDir "$baseName$separator$major.$minor.$patch$extension"
- }
- elseif ($nameWithoutExt -match '^(.+?)[-_\s]?(\d+)\.(\d+)$') {
- $baseName = $matches[1]
- $major = $matches[2]
- $minor = [int]$matches[3] + 1
- $separator = if ($nameWithoutExt -match '[-_\s](\d+\.\d+)$') { $matches[0][0] } else { '' }
- $expectedNewFile = Join-Path $missionDir "$baseName$separator$major.$minor$extension"
- }
-
- # Capture output from Patch-MooseMissions.ps1
- $patchOutput = & $patchScriptPath -MissionPath $latestMission.FullName -LuaScriptPath $MooseLuaPath 2>&1
-
- # Display the output
- $patchOutput | ForEach-Object { Write-Output $_ }
-
- # Check if it succeeded by verifying the new file was created
- $patchSucceeded = $false
- if ($expectedNewFile -and (Test-Path $expectedNewFile)) {
- $patchSucceeded = $true
- }
-
- if ($patchSucceeded) {
- $totalMissionsPatched++
- } else {
- $totalMissionsFailed++
- Write-Host " Mission patch failed - new version file not created" -ForegroundColor Red
- }
- }
- catch {
- Write-Host " ERROR: Failed to execute patch script - $_" -ForegroundColor Red
- $totalMissionsFailed++
- }
- }
- else {
- Write-Host " WHATIF: Would patch this mission with Moose_.lua" -ForegroundColor Magenta
- }
- }
-
- Write-Host ""
-}
-
-# Final summary
-Write-Host "========================================" -ForegroundColor Cyan
-Write-Host "Batch Processing Complete" -ForegroundColor Cyan
-Write-Host "========================================" -ForegroundColor Cyan
-Write-Host "Terrains Processed: $($terrainFolders.Count - $skippedTerrains)" -ForegroundColor White
-if ($skippedTerrains -gt 0) {
- Write-Host "Terrains Skipped: $skippedTerrains" -ForegroundColor DarkGray
-}
-Write-Host "Missions Found: $totalMissionsFound" -ForegroundColor White
-Write-Host "Missions Patched: $totalMissionsPatched" -ForegroundColor Green
-if ($totalMissionsFailed -gt 0) {
- Write-Host "Missions Failed: $totalMissionsFailed" -ForegroundColor Red
-}
-Write-Host ""
-
-if ($runInWhatIfMode) {
- Write-Host "INFO: This was a WhatIf run - no files were modified" -ForegroundColor Magenta
- Write-Host "INFO: Run with -Force parameter to actually patch missions" -ForegroundColor Magenta
-} elseif ($totalMissionsPatched -gt 0) {
- Write-Host "SUCCESS: All missions have been patched with the latest Moose framework!" -ForegroundColor Green
- Write-Host "TIP: Test your patched missions in DCS before deployment!" -ForegroundColor Yellow
-}
diff --git a/Patch-MooseMissions/README.md b/Patch-MooseMissions/README.md
deleted file mode 100644
index d6032b9..0000000
--- a/Patch-MooseMissions/README.md
+++ /dev/null
@@ -1,218 +0,0 @@
-# DCS Mission Patcher
-
-PowerShell script to automatically patch DCS mission files (.miz) with updated Lua scripts and increment version numbers.
-
-## Features
-
-- ✅ Updates Lua scripts in .miz files without opening DCS Mission Editor
-- ✅ **Automatically increments version numbers** in mission filenames (e.g., 1.2.3 → 1.2.4)
-- ✅ **Preserves original missions** - creates new versioned files instead of overwriting
-- ✅ Supports single or batch processing of multiple missions
-- ✅ Can output to a different directory
-- ✅ Handles both new script insertion and existing script replacement
-- ✅ Pipeline support for processing multiple missions
-- ✅ Smart version detection (supports X.Y.Z, X.Y, and X formats)
-
-## Version Increment Examples
-
-| Input Filename | Output Filename |
-|----------------|-----------------|
-| `Mission-1.2.3.miz` | `Mission-1.2.4.miz` |
-| `Operation Black Gold 2.8.miz` | `Operation Black Gold 2.9.miz` |
-| `F99th-Battle of Gori 1.3.miz` | `F99th-Battle of Gori 1.4.miz` |
-| `MyMission-5.miz` | `MyMission-6.miz` |
-| `Test.miz` | `Test-1.0.1.miz` |
-
-## Usage
-
-### Get the latest Moose_.lua
-
-Use the helper script to fetch the latest Moose_.lua into the repo root (C:\DCS_MissionDev\Moose_.lua by default):
-
-```powershell
-# Preview (no changes)
-./Download-MooseInclude.ps1
-
-# Actually download
-./Download-MooseInclude.ps1 -Force
-
-# Optional: choose a different destination
-./Download-MooseInclude.ps1 -Force -MooseLuaPath 'D:\Scripts\Moose_.lua'
-```
-
-If your system blocks script execution, temporarily allow scripts for the current session only:
-
-```powershell
-Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
-```
-
-### Basic Usage
-
-Update a mission with automatic version increment:
-
-```powershell
-.\Patch-MooseMissions.ps1 -MissionPath "MyMission-1.2.3.miz" -LuaScriptPath "Moose_.lua"
-# Original: MyMission-1.2.3.miz (untouched)
-# Creates: MyMission-1.2.4.miz
-```
-
-### Batch Processing
-
-Update all .miz files in a directory:
-
-```powershell
-Get-ChildItem "C:\DCS_Missions\*.miz" | .\Patch-MooseMissions.ps1 -LuaScriptPath "C:\Scripts\Moose_.lua"
-# Each mission gets a new version
-```
-
-### Output to Different Directory
-
-Patch missions and save versioned outputs to a different folder:
-
-```powershell
-.\Patch-MooseMissions.ps1 -MissionPath "Mission-1.5.miz" -LuaScriptPath "Moose_.lua" -OutputPath "C:\PatchedMissions"
-# Creates: C:\PatchedMissions\Mission-1.6.miz
-```
-
-### Disable Version Increment (CAUTION!)
-
-⚠️ Overwrite the original mission file without creating a new version:
-
-```powershell
-.\Patch-MooseMissions.ps1 -MissionPath "Mission-1.2.3.miz" -LuaScriptPath "Moose_.lua" -NoVersionIncrement
-# WARNING: Overwrites Mission-1.2.3.miz (original is lost!)
-```
-
-### Custom Script Name
-
-Insert a script with a different name than the source file:
-
-```powershell
-.\Patch-MooseMissions.ps1 -MissionPath "Mission.miz" -LuaScriptPath "MyScript.lua" -ScriptName "CustomName.lua"
-```
-
-## Parameters
-
-| Parameter | Required | Description |
-|-----------|----------|-------------|
-| `-MissionPath` | Yes | Path to .miz mission file(s) to patch |
-| `-LuaScriptPath` | Yes | Path to the Lua script to insert/replace |
-| `-ScriptName` | No | Name for the script inside the mission (defaults to source filename) |
-| `-OutputPath` | No | Directory for patched missions (defaults to source directory) |
-| `-NoVersionIncrement` | No | **CAUTION:** Overwrites original instead of creating new version |
-
-## Examples
-
-### Example 1: Update All Caucasus Missions with Version Increment
-
-```powershell
-Get-ChildItem "C:\DCS_MissionDev\DCS_Caucasus\*.miz" | .\Patch-MooseMissions.ps1 -LuaScriptPath "C:\Moose\Moose_.lua"
-# Each mission gets a new version: 1.2.miz -> 1.3.miz
-# Originals remain untouched
-```
-
-### Example 2: Update Specific Missions to Output Folder
-
-```powershell
-$missions = @(
- "F99th-Operation Black Gold 2.8.miz",
- "F99th-Operation Ronin 1.4.miz",
- "F99th-Battle of Gori 1.3.miz"
-)
-
-$missions | .\Patch-MooseMissions.ps1 -LuaScriptPath "Moose_.lua" -OutputPath "C:\UpdatedMissions"
-# Creates: Operation Black Gold 2.9.miz, Operation Ronin 1.5.miz, Battle of Gori 1.4.miz in C:\UpdatedMissions
-```
-
-### Example 3: Overwrite Original (Use with Caution)
-
-```powershell
-.\Patch-MooseMissions.ps1 -MissionPath "TestMission-1.0.miz" -LuaScriptPath "Moose_.lua" -NoVersionIncrement
-# WARNING: Overwrites TestMission-1.0.miz (original is lost)
-```
-
-## How It Works
-
-1. **Extraction**: The script treats .miz files as ZIP archives and extracts them to a temporary directory
-2. **Script Replacement**: Locates the Lua script in `l10n/DEFAULT/` folder and replaces/adds it
-3. **Version Increment**: Automatically detects version pattern and increments the patch number
-4. **Repackaging**: Compresses the modified mission into a new .miz file with incremented version
-5. **Cleanup**: Removes temporary files
-
-## Version Detection Logic
-
-The script intelligently detects and increments version numbers:
-
-- **X.Y.Z format** (e.g., `Mission-1.2.3.miz`) → Increments Z: `Mission-1.2.4.miz`
-- **X.Y format** (e.g., `Mission-2.8.miz`) → Increments Y: `Mission-2.9.miz`
-- **X format** (e.g., `Mission-5.miz`) → Increments X: `Mission-6.miz`
-- **No version** (e.g., `Mission.miz`) → Adds version: `Mission-1.0.1.miz`
-
-Supports various separators: `-`, `_`, or space
-
-## Mission File Structure
-
-DCS mission files (.miz) are ZIP archives with this structure:
-
-```
-Mission.miz
-├── mission
-├── options
-├── warehouses
-├── l10n/
-│ └── DEFAULT/
-│ ├── dictionary
-│ ├── mapResource
-│ └── *.lua ← Scripts are stored here
-```
-
-## Important Notes
-
-⚠️ **Always test patched missions** before using them in production or multiplayer!
-
-⚠️ **Originals are safe**: By default, the script creates NEW versioned files and never modifies originals
-
-⚠️ **Script order matters**: If your mission has script load order dependencies, this tool only replaces the file content, not the trigger order
-
-⚠️ **Version increment default**: By default, versions are incremented. Use `-NoVersionIncrement` to overwrite originals (NOT recommended)
-
-✅ **Safe for**: Updating framework files like Moose_.lua, mist.lua, or any embedded Lua script
-
-✅ **Preserves originals**: Original missions remain untouched (new versioned files are created)
-
-✅ **No backups needed**: Since originals aren't modified, you always have your previous version
-
-## Troubleshooting
-
-### "File is not a .miz mission file"
-- Ensure you're pointing to a valid .miz file, not a .lua or other file type
-
-### "Lua script not found"
-- Verify the path to your Lua script is correct and the file exists
-
-### Mission doesn't work after patching
-- Restore from backup
-- Verify the Lua script is valid
-- Check DCS.log for script errors
-- Ensure you updated the correct script name
-
-### Permission errors
-- Run PowerShell as Administrator if modifying files in protected directories
-- Ensure mission files are not read-only
-
-## Requirements
-
-- Windows PowerShell 5.1 or later (or PowerShell Core 7+)
-- Write permissions to mission file locations
-- Valid .miz mission files
-- Valid Lua script to insert
-
-## Author
-
-**F99th-TracerFacer**
-
-Discord: https://discord.gg/7wBVWKK3
-
-## License
-
-Free to use and modify for the DCS community.