mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Updating Insurgent Sandstorm.
This commit is contained in:
parent
b79a8f9b0a
commit
7dfe72afc6
Binary file not shown.
Binary file not shown.
@ -0,0 +1,122 @@
|
|||||||
|
-- init_mission_dual_coalition.lua
|
||||||
|
-- Use in Mission Editor with DO SCRIPT FILE load order:
|
||||||
|
-- 1) Moose.lua
|
||||||
|
-- 2) Moose_CTLD_Pure/Moose_CTLD.lua
|
||||||
|
-- 3) Moose_CTLD_Pure/catalogs/CrateCatalog_CTLD_Extract.lua -- optional but recommended catalog with BLUE+RED items (_CTLD_EXTRACTED_CATALOG)
|
||||||
|
-- 4) Moose_CTLD_Pure/Moose_CTLD_FAC.lua -- optional FAC/RECCE support
|
||||||
|
-- 5) DO SCRIPT: dofile on this file OR paste the block below directly
|
||||||
|
--
|
||||||
|
-- IMPORTANT: F10 menu ordering depends on script execution order!
|
||||||
|
-- Load this initialization script BEFORE other mission scripts (TADC, CVN, Intel, etc.)
|
||||||
|
-- to ensure CTLD and FAC appear earlier in the F10 menu.
|
||||||
|
--
|
||||||
|
-- Zones you should create in the Mission Editor (as trigger zones):
|
||||||
|
-- BLUE: PICKUP_BLUE_MAIN, DROP_BLUE_1, FOB_BLUE_A
|
||||||
|
-- RED : PICKUP_RED_MAIN, DROP_RED_1, FOB_RED_A
|
||||||
|
-- Adjust names below if you use different zone names.
|
||||||
|
|
||||||
|
|
||||||
|
-- Create CTLD instances only if Moose and CTLD are available
|
||||||
|
if _MOOSE_CTLD and _G.BASE then
|
||||||
|
local blueCfg = {
|
||||||
|
CoalitionSide = coalition.side.BLUE,
|
||||||
|
PickupZoneSmokeColor = trigger.smokeColor.Green,
|
||||||
|
AllowedAircraft = { -- transport-capable unit type names (case-sensitive as in DCS DB)
|
||||||
|
'UH-1H','Mi-8MTV2','Mi-24P','SA342M','SA342L','SA342Minigun','UH-60L','CH-47Fbl1','CH-47F','Mi-17','GazelleAI'
|
||||||
|
},
|
||||||
|
-- Optional: drive zone activation from mission flags (preferred: set per-zone below via flag/activeWhen)
|
||||||
|
|
||||||
|
MapDraw = {
|
||||||
|
Enabled = true,
|
||||||
|
DrawMASHZones = true, -- Enable MASH zone drawing
|
||||||
|
},
|
||||||
|
|
||||||
|
Zones = {
|
||||||
|
PickupZones = { { name = 'S1', flag = 9001, activeWhen = 0 },
|
||||||
|
{ name = "S2", flag = 9004, activeWhen = 0 },
|
||||||
|
{ name = "S3", flag = 9005, activeWhen = 0 } },
|
||||||
|
--DropZones = { { name = 'BRAVO', flag = 9002, activeWhen = 0 } },
|
||||||
|
--FOBZones = { { name = 'CHARLIE', flag = 9003, activeWhen = 0 } },
|
||||||
|
--MASHZones = { { name = 'MASH Alpha', freq = '251.0 AM', radius = 500, flag = 9010, activeWhen = 0 } },
|
||||||
|
--SalvageDropZones = { { name = 'S1', flag = 9020, radius = 500, activeWhen = 0 } },
|
||||||
|
},
|
||||||
|
BuildRequiresGroundCrates = true,
|
||||||
|
}
|
||||||
|
env.info('[DEBUG] blueCfg.Zones.MASHZones count: ' .. tostring(blueCfg.Zones and blueCfg.Zones.MASHZones and #blueCfg.Zones.MASHZones or 'NIL'))
|
||||||
|
if blueCfg.Zones and blueCfg.Zones.MASHZones and blueCfg.Zones.MASHZones[1] then
|
||||||
|
env.info('[DEBUG] blueCfg.Zones.MASHZones[1].name: ' .. tostring(blueCfg.Zones.MASHZones[1].name))
|
||||||
|
end
|
||||||
|
ctldBlue = _MOOSE_CTLD:New(blueCfg)
|
||||||
|
|
||||||
|
local redCfg = {
|
||||||
|
CoalitionSide = coalition.side.RED,
|
||||||
|
PickupZoneSmokeColor = trigger.smokeColor.Green,
|
||||||
|
AllowedAircraft = { -- transport-capable unit type names (case-sensitive as in DCS DB)
|
||||||
|
'UH-1H','Mi-8MTV2','Mi-24P','SA342M','SA342L','SA342Minigun','UH-60L','CH-47Fbl1','CH-47F','Mi-17','GazelleAI'
|
||||||
|
|
||||||
|
},
|
||||||
|
-- Optional: drive zone activation for RED via per-zone flag/activeWhen
|
||||||
|
|
||||||
|
MapDraw = {
|
||||||
|
Enabled = true,
|
||||||
|
DrawMASHZones = true, -- Enable MASH zone drawing
|
||||||
|
},
|
||||||
|
|
||||||
|
Zones = {
|
||||||
|
PickupZones = { { name = 'ReadLoadZone1', flag = 9101, activeWhen = 0 },
|
||||||
|
{ name = "ReadLoadZone2", flag = 9104, activeWhen = 0 },
|
||||||
|
{ name = "ReadLoadZone3", flag = 9105, activeWhen = 0 } },
|
||||||
|
--DropZones = { { name = 'ECHO', flag = 9102, activeWhen = 0 } },
|
||||||
|
--FOBZones = { { name = 'FOXTROT', flag = 9103, activeWhen = 0 } },
|
||||||
|
--MASHZones = { { name = 'MASH Bravo', freq = '252.0 AM', radius = 500, flag = 9111, activeWhen = 0 } },
|
||||||
|
--SalvageDropZones = { { name = 'S2', flag = 9020, radius = 500, activeWhen = 0 } },
|
||||||
|
},
|
||||||
|
BuildRequiresGroundCrates = true,
|
||||||
|
}
|
||||||
|
env.info('[DEBUG] redCfg.Zones.MASHZones count: ' .. tostring(redCfg.Zones and redCfg.Zones.MASHZones and #redCfg.Zones.MASHZones or 'NIL'))
|
||||||
|
if redCfg.Zones and redCfg.Zones.MASHZones and redCfg.Zones.MASHZones[1] then
|
||||||
|
env.info('[DEBUG] redCfg.Zones.MASHZones[1].name: ' .. tostring(redCfg.Zones.MASHZones[1].name))
|
||||||
|
end
|
||||||
|
ctldRed = _MOOSE_CTLD:New(redCfg)
|
||||||
|
|
||||||
|
-- Merge catalog into both CTLD instances if catalog was loaded
|
||||||
|
env.info('[init_mission_dual_coalition] Checking for catalog: '..((_CTLD_EXTRACTED_CATALOG and 'FOUND') or 'NOT FOUND'))
|
||||||
|
if _CTLD_EXTRACTED_CATALOG then
|
||||||
|
local count = 0
|
||||||
|
for k,v in pairs(_CTLD_EXTRACTED_CATALOG) do count = count + 1 end
|
||||||
|
env.info('[init_mission_dual_coalition] Catalog has '..tostring(count)..' entries')
|
||||||
|
env.info('[init_mission_dual_coalition] Merging catalog into CTLD instances')
|
||||||
|
ctldBlue:MergeCatalog(_CTLD_EXTRACTED_CATALOG)
|
||||||
|
ctldRed:MergeCatalog(_CTLD_EXTRACTED_CATALOG)
|
||||||
|
env.info('[init_mission_dual_coalition] Catalog merged successfully')
|
||||||
|
-- Verify merge
|
||||||
|
local blueCount = 0
|
||||||
|
for k,v in pairs(ctldBlue.Config.CrateCatalog) do blueCount = blueCount + 1 end
|
||||||
|
env.info('[init_mission_dual_coalition] BLUE catalog now has '..tostring(blueCount)..' entries')
|
||||||
|
else
|
||||||
|
env.info('[init_mission_dual_coalition] WARNING: _CTLD_EXTRACTED_CATALOG not found - catalog not loaded!')
|
||||||
|
env.info('[init_mission_dual_coalition] Available globals: '..((_G._CTLD_EXTRACTED_CATALOG and 'in _G') or 'not in _G'))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
env.info('[init_mission_dual_coalition] Moose or CTLD missing; skipping CTLD init')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Optional: FAC/RECCE for both sides (requires Moose_CTLD_FAC.lua)
|
||||||
|
if _MOOSE_CTLD_FAC and _G.BASE and ctldBlue and ctldRed then
|
||||||
|
facBlue = _MOOSE_CTLD_FAC:New(ctldBlue, {
|
||||||
|
CoalitionSide = coalition.side.BLUE,
|
||||||
|
Arty = { Enabled = false },
|
||||||
|
})
|
||||||
|
-- facBlue:AddRecceZone({ name = 'RECCE_BLUE_1' })
|
||||||
|
facBlue:Run()
|
||||||
|
|
||||||
|
facRed = _MOOSE_CTLD_FAC:New(ctldRed, {
|
||||||
|
CoalitionSide = coalition.side.RED,
|
||||||
|
Arty = { Enabled = false },
|
||||||
|
})
|
||||||
|
-- facRed:AddRecceZone({ name = 'RECCE_RED_1' })
|
||||||
|
facRed:Run()
|
||||||
|
else
|
||||||
|
env.info('[init_mission_dual_coalition] FAC not initialized (missing Moose/CTLD/FAC or CTLD not created)')
|
||||||
|
end
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,199 @@
|
|||||||
|
local ENABLE_SAMS = true -- used for testing purposes. Set to true to enable SAMs, false to disable.
|
||||||
|
local TAC_DISPLAY = false -- Set to false to disable Tacview display for AI flights (default = false)
|
||||||
|
|
||||||
|
-- How many red/blue aircraft are in the air by default.
|
||||||
|
local RedA2ADefaultOverhead = 1.5
|
||||||
|
local RedDefaultCAP = 1
|
||||||
|
local BlueA2ADefaultOverhead = 1.5
|
||||||
|
local BlueDefaultCAP = 1
|
||||||
|
|
||||||
|
-- Create the main mission menu.
|
||||||
|
missionMenu = MENU_MISSION:New("Mission Menu")
|
||||||
|
|
||||||
|
--Build Command Center and Mission for Blue
|
||||||
|
US_CC = COMMANDCENTER:New( GROUP:FindByName( "BLUEHQ" ), "USA HQ" )
|
||||||
|
US_Mission = MISSION:New( US_CC, "Insurgent Sandstorm", "Primary", "Clear the front lines of enemy activity.", coalition.side.BLUE)
|
||||||
|
US_Score = SCORING:New( "Insurgent Sandstorm - Blue" )
|
||||||
|
US_Mission:AddScoring( US_Score )
|
||||||
|
US_Mission:Start()
|
||||||
|
US_Score:SetMessagesHit(false)
|
||||||
|
US_Score:SetMessagesDestroy(false)
|
||||||
|
US_Score:SetMessagesScore(false)
|
||||||
|
|
||||||
|
--Build Command Center and Mission Red
|
||||||
|
RU_CC = COMMANDCENTER:New( GROUP:FindByName( "REDHQ" ), "Russia HQ" )
|
||||||
|
RU_Mission = MISSION:New (RU_CC, "Insurgent Sandstorm", "Primary", "Destroy U.S. and NATO forces.", coalition.side.RED)
|
||||||
|
RU_Score = SCORING:New("Insurgent Sandstorm - Red")
|
||||||
|
RU_Mission:AddScoring( RU_Score)
|
||||||
|
RU_Mission:Start()
|
||||||
|
RU_Score:SetMessagesHit(false)
|
||||||
|
RU_Score:SetMessagesDestroy(false)
|
||||||
|
RU_Score:SetMessagesScore(false)
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
-- Setup SAM Systems
|
||||||
|
------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local RED_AA_ZONES = {
|
||||||
|
ZONE:New("RED-AA-1"),
|
||||||
|
ZONE:New("RED-AA-2"),
|
||||||
|
ZONE:New("RED-AA-3"),
|
||||||
|
ZONE:New("RED-AA-4"),
|
||||||
|
ZONE:New("RED-AA-5"),
|
||||||
|
ZONE:New("RED-AA-6"),
|
||||||
|
ZONE:New("RED-AA-7"),
|
||||||
|
ZONE:New("RED-AA-8"),
|
||||||
|
ZONE:New("RED-AA-9"),
|
||||||
|
ZONE:New("RED-AA-10"),
|
||||||
|
ZONE:New("RED-AA-11"),
|
||||||
|
ZONE:New("RED-AA-12"),
|
||||||
|
ZONE:New("RED-AA-13"),
|
||||||
|
ZONE:New("RED-AA-14"),
|
||||||
|
ZONE:New("RED-AA-15"),
|
||||||
|
ZONE:New("RED-AA-16"),
|
||||||
|
ZONE:New("RED-AA-17"),
|
||||||
|
ZONE:New("RED-AA-18"),
|
||||||
|
ZONE:New("RED-AA-19"),
|
||||||
|
ZONE:New("RED-AA-20"),
|
||||||
|
ZONE:New("RED-AA-21"),
|
||||||
|
ZONE:New("RED-AA-22"),
|
||||||
|
ZONE:New("RED-AA-23"),
|
||||||
|
ZONE:New("RED-AA-24"),
|
||||||
|
ZONE:New("RED-AA-25"),
|
||||||
|
ZONE:New("RED-AA-26"),
|
||||||
|
ZONE:New("RED-AA-27"),
|
||||||
|
ZONE:New("RED-AA-28"),
|
||||||
|
ZONE:New("RED-AA-29"),
|
||||||
|
ZONE:New("RED-AA-30"),
|
||||||
|
ZONE:New("RED-AA-31"),
|
||||||
|
ZONE:New("RED-AA-32"),
|
||||||
|
ZONE:New("RED-AA-33"),
|
||||||
|
ZONE:New("RED-AA-34"),
|
||||||
|
ZONE:New("RED-AA-35"),
|
||||||
|
ZONE:New("RED-AA-36"),
|
||||||
|
ZONE:New("RED-AA-37"),
|
||||||
|
ZONE:New("RED-AA-38"),
|
||||||
|
ZONE:New("RED-AA-39"),
|
||||||
|
ZONE:New("RED-AA-40")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Schedule RED AA spawns using the calculated frequencies
|
||||||
|
-- Must allow enough room for an entire group to spawn. If the group only has 1 unit and you put 5, 5 will spawn,
|
||||||
|
-- but if the group has 5 units, and you put 5, only 1 will spawn..if you only put 4, it will never spawn.
|
||||||
|
-- If you put 10, 2 of them will spawn, etc etc.
|
||||||
|
|
||||||
|
if ENABLE_SAMS then
|
||||||
|
RED_SA08 = SPAWN:New("RED EWR SA08")
|
||||||
|
:InitRandomizeZones(RED_AA_ZONES)
|
||||||
|
:InitLimit(8, 8)
|
||||||
|
:SpawnScheduled(1800, 0.5)
|
||||||
|
|
||||||
|
-- There are 18 units in this group. Need space for each one in the numbers. So if I want 3 SA10s i'm just rounding up to 60.
|
||||||
|
RED_SA10 = SPAWN:New("RED EWR AA-SA10-1")
|
||||||
|
:InitRandomizeZones(RED_AA_ZONES)
|
||||||
|
:InitLimit(60, 60)
|
||||||
|
:SpawnScheduled(1800, 0.5)
|
||||||
|
|
||||||
|
-- There are 12 units in this group. Need space for each one in the numbers. So if I want 4 SA11s i'm just rounding up to 48
|
||||||
|
RED_SA11 = SPAWN:New("RED EWR AA SA112-1")
|
||||||
|
:InitRandomizeZones(RED_AA_ZONES)
|
||||||
|
:InitLimit(36, 36)
|
||||||
|
:SpawnScheduled(1800, 0.5)
|
||||||
|
|
||||||
|
-- There are 11 units in this group. Need space for each one in the numbers. So if I want 4 SA11s i'm just rounding up to 44
|
||||||
|
RED_SA06 = SPAWN:New("RED EWR SA6")
|
||||||
|
:InitRandomizeZones(RED_AA_ZONES)
|
||||||
|
:InitLimit(33, 33)
|
||||||
|
:SpawnScheduled(1800, 0.5)
|
||||||
|
|
||||||
|
RED_SA02 = SPAWN:New("RED EWR SA2")
|
||||||
|
:InitRandomizeZones(RED_AA_ZONES)
|
||||||
|
:InitLimit(60, 60)
|
||||||
|
:SpawnScheduled(1800, 0.5)
|
||||||
|
end
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
-- Setup Air Dispatchers for RED and BLUE
|
||||||
|
------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
BLUEBorderZone = ZONE_POLYGON:New( "BLUE BORDER", GROUP:FindByName( "BLUE BORDER" ) )
|
||||||
|
BLUEA2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "BLUE EWR" }, { "FIGHTER SWEEP BLUE" }, 'BLUE BORDER', 'BLUE BORDER', BlueDefaultCAP, 10000, 50000, 75000, 100)
|
||||||
|
BLUEA2ADispatcher:SetDefaultLandingAtRunway()
|
||||||
|
BLUEA2ADispatcher:SetDefaultTakeoffInAir()
|
||||||
|
BLUEA2ADispatcher:SetTacticalDisplay(TAC_DISPLAY)
|
||||||
|
BLUEA2ADispatcher:SetDefaultFuelThreshold( 0.20 )
|
||||||
|
BLUEA2ADispatcher:SetRefreshTimeInterval( 300 )
|
||||||
|
BLUEA2ADispatcher:SetDefaultOverhead(BlueA2ADefaultOverhead)
|
||||||
|
BLUEA2ADispatcher:SetDisengageRadius( 100000 )
|
||||||
|
BLUEA2ADispatcher:SetEngageRadius( 50000 )
|
||||||
|
BLUEA2ADispatcher:SetGciRadius( 75000 )
|
||||||
|
|
||||||
|
CCCPBorderZone = ZONE_POLYGON:New( "RED BORDER", GROUP:FindByName( "RED BORDER" ) )
|
||||||
|
RedA2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "RED EWR" }, { "FIGHTER SWEEP RED" }, "RED BORDER", "RED BORDER", RedDefaultCAP, 10000, 50000, 75000, 100)
|
||||||
|
RedA2ADispatcher:SetDefaultLandingAtRunway()
|
||||||
|
RedA2ADispatcher:SetDefaultTakeoffInAir()
|
||||||
|
RedA2ADispatcher:SetTacticalDisplay(TAC_DISPLAY)
|
||||||
|
RedA2ADispatcher:SetDefaultFuelThreshold( 0.20 )
|
||||||
|
RedA2ADispatcher:SetRefreshTimeInterval( 300 )
|
||||||
|
RedA2ADispatcher:SetDefaultOverhead(RedA2ADefaultOverhead)
|
||||||
|
RedA2ADispatcher:SetDisengageRadius( 100000 )
|
||||||
|
RedA2ADispatcher:SetEngageRadius( 50000 )
|
||||||
|
RedA2ADispatcher:SetGciRadius( 75000 )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DwyerBorderZone = ZONE_POLYGON:New( "DwyerBorderZone", GROUP:FindByName( "DwyerBorderZone" ) )
|
||||||
|
DwyerDispatcher = AI_A2A_GCICAP:NewWithBorder( { "RED EWR" }, { "DwyerBorderCAP" }, "DwyerBorderZone", "DwyerBorderZone", RedDefaultCAP, 10000, 50000, 75000, 100)
|
||||||
|
DwyerDispatcher:SetDefaultLandingAtRunway()
|
||||||
|
DwyerDispatcher:SetDefaultTakeoffInAir()
|
||||||
|
DwyerDispatcher:SetBorderZone( DwyerBorderZone )
|
||||||
|
DwyerDispatcher:SetTacticalDisplay(TAC_DISPLAY)
|
||||||
|
DwyerDispatcher:SetDefaultFuelThreshold( 0.20 )
|
||||||
|
DwyerDispatcher:SetRefreshTimeInterval( 300 )
|
||||||
|
DwyerDispatcher:SetDefaultOverhead(RedA2ADefaultOverhead)
|
||||||
|
DwyerDispatcher:SetDisengageRadius( 100000 )
|
||||||
|
DwyerDispatcher:SetEngageRadius( 50000 )
|
||||||
|
DwyerDispatcher:SetGciRadius( 75000 )
|
||||||
|
|
||||||
|
|
||||||
|
BostZone = ZONE_POLYGON:New( "BostBorderZone", GROUP:FindByName( "BostBorderZone" ) )
|
||||||
|
BostDispatcher = AI_A2A_GCICAP:NewWithBorder( { "RED EWR" }, { "BostBorderCAP" }, "BostBorderZone", "BostBorderZone", RedDefaultCAP, 10000, 50000, 75000, 100)
|
||||||
|
BostDispatcher:SetDefaultLandingAtRunway()
|
||||||
|
BostDispatcher:SetDefaultTakeoffInAir()
|
||||||
|
BostDispatcher:SetBorderZone(BostZone)
|
||||||
|
BostDispatcher:SetTacticalDisplay(TAC_DISPLAY)
|
||||||
|
BostDispatcher:SetDefaultFuelThreshold( 0.20 )
|
||||||
|
BostDispatcher:SetRefreshTimeInterval( 300 )
|
||||||
|
BostDispatcher:SetDefaultOverhead(RedA2ADefaultOverhead)
|
||||||
|
BostDispatcher:SetDisengageRadius( 100000 )
|
||||||
|
BostDispatcher:SetEngageRadius( 50000 )
|
||||||
|
BostDispatcher:SetGciRadius( 75000 )
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
-- Clean up the airbases of debris and stuck aircraft.
|
||||||
|
------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
CleanUpAirports = CLEANUP_AIRBASE:New( {
|
||||||
|
AIRBASE.Afghanistan.Kandahar,
|
||||||
|
AIRBASE.Afghanistan.Camp_Bastion
|
||||||
|
|
||||||
|
} )
|
||||||
|
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
-- Misc Spawns
|
||||||
|
------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- Spawn the RED Bunker Buster
|
||||||
|
Red_Bunker_Buster = SPAWN:New("BUNKER BUSTER")
|
||||||
|
:InitLimit(1, 99)
|
||||||
|
:SpawnScheduled(900, 0.5)
|
||||||
|
|
||||||
|
|
||||||
|
Red_Bunker_Buster2 = SPAWN:New("BUNKER BUSTER-1")
|
||||||
|
:InitLimit(1, 99)
|
||||||
|
:SpawnScheduled(1800, 0.5)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
135
DCS_Afgainistan/Insurgent_Sandstorm/Moose_MenuManager.lua
Normal file
135
DCS_Afgainistan/Insurgent_Sandstorm/Moose_MenuManager.lua
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
--[[
|
||||||
|
Unified F10 Menu Manager
|
||||||
|
|
||||||
|
Purpose: Provides a centralized menu system to organize all mission scripts
|
||||||
|
into a consistent F10 menu structure.
|
||||||
|
|
||||||
|
Menu Organization:
|
||||||
|
F10 -> F1: Mission Options (all other scripts go here)
|
||||||
|
F10 -> F2: CTLD (reserved position)
|
||||||
|
F10 -> F3: AFAC Control (reserved position)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
1. Load this script FIRST before any other menu-creating scripts
|
||||||
|
2. Other scripts should use MenuManager to register their menus
|
||||||
|
|
||||||
|
Example:
|
||||||
|
-- In your script, instead of:
|
||||||
|
-- local MyMenu = MENU_COALITION:New(coalition.side.BLUE, "My Script")
|
||||||
|
|
||||||
|
-- Use:
|
||||||
|
-- local MyMenu = MenuManager.CreateCoalitionMenu(coalition.side.BLUE, "My Script")
|
||||||
|
|
||||||
|
]]--
|
||||||
|
|
||||||
|
MenuManager = {}
|
||||||
|
MenuManager.Version = "1.1"
|
||||||
|
|
||||||
|
-- Configuration
|
||||||
|
MenuManager.Config = {
|
||||||
|
EnableMissionOptionsMenu = true, -- Set to false to disable the parent menu system
|
||||||
|
MissionOptionsMenuName = "Mission Options", -- Name of the parent menu
|
||||||
|
Debug = false -- Set to true for debug messages
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Storage for menu references
|
||||||
|
MenuManager.Menus = {
|
||||||
|
Blue = {},
|
||||||
|
Red = {},
|
||||||
|
Mission = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Parent menu references (created on first use)
|
||||||
|
MenuManager.ParentMenus = {
|
||||||
|
BlueCoalition = nil,
|
||||||
|
RedCoalition = nil,
|
||||||
|
Mission = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Initialize the parent menus
|
||||||
|
function MenuManager.Initialize()
|
||||||
|
if MenuManager.Config.EnableMissionOptionsMenu then
|
||||||
|
-- Create the parent "Mission Options" menu for each coalition
|
||||||
|
MenuManager.ParentMenus.BlueCoalition = MENU_COALITION:New(
|
||||||
|
coalition.side.BLUE,
|
||||||
|
MenuManager.Config.MissionOptionsMenuName
|
||||||
|
)
|
||||||
|
|
||||||
|
MenuManager.ParentMenus.RedCoalition = MENU_COALITION:New(
|
||||||
|
coalition.side.RED,
|
||||||
|
MenuManager.Config.MissionOptionsMenuName
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Note: MENU_MISSION not created to avoid duplicate empty menu
|
||||||
|
-- Scripts that need mission-wide menus should use MENU_MISSION directly
|
||||||
|
|
||||||
|
if MenuManager.Config.Debug then
|
||||||
|
env.info("MenuManager: Initialized parent coalition menus")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create a coalition menu under "Mission Options"
|
||||||
|
-- @param coalitionSide: coalition.side.BLUE or coalition.side.RED
|
||||||
|
-- @param menuName: Name of the menu
|
||||||
|
-- @param parentMenu: (Optional) If provided, creates as submenu of this parent instead of Mission Options
|
||||||
|
-- @return: MENU_COALITION object
|
||||||
|
function MenuManager.CreateCoalitionMenu(coalitionSide, menuName, parentMenu)
|
||||||
|
if MenuManager.Config.EnableMissionOptionsMenu and not parentMenu then
|
||||||
|
-- Create under Mission Options
|
||||||
|
local parent = (coalitionSide == coalition.side.BLUE)
|
||||||
|
and MenuManager.ParentMenus.BlueCoalition
|
||||||
|
or MenuManager.ParentMenus.RedCoalition
|
||||||
|
|
||||||
|
local menu = MENU_COALITION:New(coalitionSide, menuName, parent)
|
||||||
|
|
||||||
|
if MenuManager.Config.Debug then
|
||||||
|
local coalitionName = (coalitionSide == coalition.side.BLUE) and "BLUE" or "RED"
|
||||||
|
env.info(string.format("MenuManager: Created coalition menu '%s' for %s", menuName, coalitionName))
|
||||||
|
end
|
||||||
|
|
||||||
|
return menu
|
||||||
|
else
|
||||||
|
-- Create as root menu or under provided parent
|
||||||
|
local menu = MENU_COALITION:New(coalitionSide, menuName, parentMenu)
|
||||||
|
return menu
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create a mission menu (not nested under Mission Options, as that causes duplicates)
|
||||||
|
-- @param menuName: Name of the menu
|
||||||
|
-- @param parentMenu: (Optional) Parent menu
|
||||||
|
-- @return: MENU_MISSION object
|
||||||
|
-- Note: Mission menus are visible to all players and cannot be nested under coalition menus
|
||||||
|
function MenuManager.CreateMissionMenu(menuName, parentMenu)
|
||||||
|
-- Always create as root menu or under provided parent
|
||||||
|
-- Mission menus can't be nested under coalition-specific "Mission Options"
|
||||||
|
local menu = MENU_MISSION:New(menuName, parentMenu)
|
||||||
|
|
||||||
|
if MenuManager.Config.Debug then
|
||||||
|
env.info(string.format("MenuManager: Created mission menu '%s'", menuName))
|
||||||
|
end
|
||||||
|
|
||||||
|
return menu
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Helper to disable the parent menu system at runtime
|
||||||
|
function MenuManager.DisableParentMenus()
|
||||||
|
MenuManager.Config.EnableMissionOptionsMenu = false
|
||||||
|
env.info("MenuManager: Parent menu system disabled")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Helper to enable the parent menu system at runtime
|
||||||
|
function MenuManager.EnableParentMenus()
|
||||||
|
MenuManager.Config.EnableMissionOptionsMenu = true
|
||||||
|
if not MenuManager.ParentMenus.BlueCoalition then
|
||||||
|
MenuManager.Initialize()
|
||||||
|
end
|
||||||
|
env.info("MenuManager: Parent menu system enabled")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Initialize on load
|
||||||
|
MenuManager.Initialize()
|
||||||
|
|
||||||
|
-- Announcement
|
||||||
|
env.info(string.format("MenuManager v%s loaded - Mission Options menu system ready", MenuManager.Version))
|
||||||
@ -0,0 +1,871 @@
|
|||||||
|
--[[
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
Moose_TDAC_CargoDispatcher.lua
|
||||||
|
Automated Logistics System for TADC Squadron Replenishment
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
DESCRIPTION:
|
||||||
|
This script monitors RED and BLUE squadrons for low aircraft counts and automatically dispatches CARGO aircraft from a list of supply airfields to replenish them.
|
||||||
|
It spawns cargo aircraft and routes them to destination airbases. Delivery detection and replenishment is handled by the main TADC system.
|
||||||
|
|
||||||
|
CONFIGURATION:
|
||||||
|
- Update static templates and airfield lists as needed for your mission.
|
||||||
|
- Set thresholds and supply airfields in CARGO_SUPPLY_CONFIG.
|
||||||
|
- Replace static templates with actual group templates from the mission editor for realism.
|
||||||
|
|
||||||
|
REQUIRES:
|
||||||
|
- MOOSE framework (for SPAWN, AIRBASE, etc.)
|
||||||
|
- Optional: MIST for deep copy of templates
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Single-run guard to prevent duplicate dispatcher loops if script is reloaded
|
||||||
|
if _G.__TDAC_DISPATCHER_RUNNING then
|
||||||
|
env.info("[TDAC] CargoDispatcher already running; aborting duplicate load")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
_G.__TDAC_DISPATCHER_RUNNING = true
|
||||||
|
|
||||||
|
--[[
|
||||||
|
GLOBAL STATE AND CONFIGURATION
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Tracks all active cargo missions and dispatcher configuration.
|
||||||
|
]]
|
||||||
|
if not cargoMissions then
|
||||||
|
cargoMissions = { red = {}, blue = {} }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Dispatcher config (interval in seconds)
|
||||||
|
if not DISPATCHER_CONFIG then
|
||||||
|
-- default interval (seconds) and a slightly larger grace period to account for slow servers/networks
|
||||||
|
DISPATCHER_CONFIG = { interval = 60, gracePeriod = 25 }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Safety flag: when false, do NOT fall back to spawning from in-memory template tables.
|
||||||
|
-- Set to true if you understand the tweaked-template warning and accept the risk.
|
||||||
|
if DISPATCHER_CONFIG.ALLOW_FALLBACK_TO_INMEM_TEMPLATE == nil then
|
||||||
|
DISPATCHER_CONFIG.ALLOW_FALLBACK_TO_INMEM_TEMPLATE = false
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
CARGO SUPPLY CONFIGURATION
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Set supply airfields, cargo template names, and resupply thresholds for each coalition.
|
||||||
|
]]
|
||||||
|
local CARGO_SUPPLY_CONFIG = {
|
||||||
|
red = {
|
||||||
|
supplyAirfields = { "Farah", "Nimroz", "Herat", "Shindand" }, -- replace with your RED supply airbase names
|
||||||
|
cargoTemplate = "CARGO_RED_AN26", -- replace with your RED cargo aircraft template name
|
||||||
|
threshold = 0.90 -- ratio below which to trigger resupply (testing)
|
||||||
|
},
|
||||||
|
blue = {
|
||||||
|
supplyAirfields = { "Sharana", "Tarinkot" }, -- replace with your BLUE supply airbase names
|
||||||
|
cargoTemplate = "CARGO_BLUE_C130", -- replace with your BLUE cargo aircraft template name
|
||||||
|
threshold = 0.90 -- ratio below which to trigger resupply (testing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--[[
|
||||||
|
UTILITY STUBS
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
selectRandomAirfield: Picks a random airfield from a list.
|
||||||
|
announceToCoalition: Stub for in-game coalition messaging.
|
||||||
|
Replace with your own logic as needed.
|
||||||
|
]]
|
||||||
|
if not selectRandomAirfield then
|
||||||
|
function selectRandomAirfield(airfieldList)
|
||||||
|
if type(airfieldList) == "table" and #airfieldList > 0 then
|
||||||
|
return airfieldList[math.random(1, #airfieldList)]
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Stub for announceToCoalition (replace with your own logic if needed)
|
||||||
|
if not announceToCoalition then
|
||||||
|
function announceToCoalition(coalitionKey, message)
|
||||||
|
-- Replace with actual in-game message logic
|
||||||
|
env.info("[ANNOUNCE] [" .. tostring(coalitionKey) .. "]: " .. tostring(message))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--[[
|
||||||
|
LOGGING
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Advanced logging configuration and helper function for debug output.
|
||||||
|
]]
|
||||||
|
local ADVANCED_LOGGING = {
|
||||||
|
enableDetailedLogging = false,
|
||||||
|
logPrefix = "[TADC Cargo]"
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Logging function (must be defined before any log() calls)
|
||||||
|
local function log(message, detailed)
|
||||||
|
if not detailed or ADVANCED_LOGGING.enableDetailedLogging then
|
||||||
|
env.info(ADVANCED_LOGGING.logPrefix .. " " .. message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log("═══════════════════════════════════════════════════════════════════════════════", true)
|
||||||
|
log("Moose_TDAC_CargoDispatcher.lua loaded.", true)
|
||||||
|
log("═══════════════════════════════════════════════════════════════════════════════", true)
|
||||||
|
|
||||||
|
|
||||||
|
-- Provide a safe deepCopy if MIST is not available
|
||||||
|
local function deepCopy(obj)
|
||||||
|
if type(obj) ~= 'table' then return obj end
|
||||||
|
local res = {}
|
||||||
|
for k, v in pairs(obj) do
|
||||||
|
if type(v) == 'table' then
|
||||||
|
res[k] = deepCopy(v)
|
||||||
|
else
|
||||||
|
res[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Dispatch cooldown per airbase (seconds) to avoid repeated immediate retries
|
||||||
|
local CARGO_DISPATCH_COOLDOWN = DISPATCHER_CONFIG and DISPATCHER_CONFIG.cooldown or 300 -- default 5 minutes
|
||||||
|
local lastDispatchAttempt = { red = {}, blue = {} }
|
||||||
|
|
||||||
|
local function getCoalitionSide(coalitionKey)
|
||||||
|
if coalitionKey == 'blue' then return coalition.side.BLUE end
|
||||||
|
if coalitionKey == 'red' then return coalition.side.RED end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Forward-declare parking check helper so functions defined earlier can call it
|
||||||
|
local destinationHasSuitableParking
|
||||||
|
|
||||||
|
-- Validate dispatcher configuration: check that supply airfields exist and templates appear valid
|
||||||
|
local function validateDispatcherConfig()
|
||||||
|
local problems = {}
|
||||||
|
|
||||||
|
-- Check supply airfields exist
|
||||||
|
for coalitionKey, cfg in pairs(CARGO_SUPPLY_CONFIG) do
|
||||||
|
if cfg and cfg.supplyAirfields and type(cfg.supplyAirfields) == 'table' then
|
||||||
|
for _, abName in ipairs(cfg.supplyAirfields) do
|
||||||
|
local ok, ab = pcall(function() return AIRBASE:FindByName(abName) end)
|
||||||
|
if not ok or not ab then
|
||||||
|
table.insert(problems, string.format("Missing airbase for %s supply list: '%s'", tostring(coalitionKey), tostring(abName)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(problems, string.format("Missing or invalid supplyAirfields for coalition '%s'", tostring(coalitionKey)))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check cargo template presence (best-effort using SPAWN:New if available)
|
||||||
|
if cfg and cfg.cargoTemplate and type(cfg.cargoTemplate) == 'string' and cfg.cargoTemplate ~= '' then
|
||||||
|
local okSpawn, spawnObj = pcall(function() return SPAWN:New(cfg.cargoTemplate) end)
|
||||||
|
if not okSpawn or not spawnObj then
|
||||||
|
-- SPAWN:New may not be available at load time; warn but don't fail hard
|
||||||
|
table.insert(problems, string.format("Cargo template suspicious or missing: '%s' (coalition: %s)", tostring(cfg.cargoTemplate), tostring(coalitionKey)))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(problems, string.format("Missing cargoTemplate for coalition '%s'", tostring(coalitionKey)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #problems == 0 then
|
||||||
|
log("TDAC Dispatcher config validation passed ✓", true)
|
||||||
|
MESSAGE:New("TDAC Dispatcher config validation passed ✓", 15):ToAll()
|
||||||
|
return true, {}
|
||||||
|
else
|
||||||
|
log("TDAC Dispatcher config validation found issues:", true)
|
||||||
|
MESSAGE:New("TDAC Dispatcher config validation found issues:" .. table.concat(problems, ", "), 15):ToAll()
|
||||||
|
for _, p in ipairs(problems) do
|
||||||
|
log(" ✗ " .. p, true)
|
||||||
|
end
|
||||||
|
return false, problems
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Expose console helper to run the check manually
|
||||||
|
function _G.TDAC_RunConfigCheck()
|
||||||
|
local ok, problems = validateDispatcherConfig()
|
||||||
|
if ok then
|
||||||
|
return true, "OK"
|
||||||
|
else
|
||||||
|
return false, problems
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--[[
|
||||||
|
getSquadronStatus(squadron, coalitionKey)
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Returns the current, max, and ratio of aircraft for a squadron.
|
||||||
|
If you track current aircraft in a table, update this logic accordingly.
|
||||||
|
Returns: currentCount, maxCount, ratio
|
||||||
|
]]
|
||||||
|
local function getSquadronStatus(squadron, coalitionKey)
|
||||||
|
local current = squadron.current or squadron.count or squadron.aircraft or 0
|
||||||
|
local max = squadron.max or squadron.aircraft or 1
|
||||||
|
if squadron.templateName and _G.squadronAircraftCounts and _G.squadronAircraftCounts[coalitionKey] then
|
||||||
|
current = _G.squadronAircraftCounts[coalitionKey][squadron.templateName] or current
|
||||||
|
end
|
||||||
|
local ratio = (max > 0) and (current / max) or 0
|
||||||
|
return current, max, ratio
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--[[
|
||||||
|
hasActiveCargoMission(coalitionKey, airbaseName)
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Returns true if there is an active (not completed/failed) cargo mission for the given airbase.
|
||||||
|
Failed missions are immediately removed from tracking to allow retries.
|
||||||
|
]]
|
||||||
|
local function hasActiveCargoMission(coalitionKey, airbaseName)
|
||||||
|
for i = #cargoMissions[coalitionKey], 1, -1 do
|
||||||
|
local mission = cargoMissions[coalitionKey][i]
|
||||||
|
if mission.destination == airbaseName then
|
||||||
|
-- Remove completed or failed missions immediately to allow retries
|
||||||
|
if mission.status == "completed" or mission.status == "failed" then
|
||||||
|
log("Removing " .. mission.status .. " cargo mission for " .. airbaseName .. " from tracking")
|
||||||
|
table.remove(cargoMissions[coalitionKey], i)
|
||||||
|
else
|
||||||
|
-- Consider mission active only if the group is alive OR we're still within the grace window
|
||||||
|
local stillActive = false
|
||||||
|
if mission.group and mission.group.IsAlive and mission.group:IsAlive() then
|
||||||
|
stillActive = true
|
||||||
|
else
|
||||||
|
local pending = mission._pendingStartTime
|
||||||
|
local grace = mission._gracePeriod or DISPATCHER_CONFIG.gracePeriod or 8
|
||||||
|
if pending and (timer.getTime() - pending) <= grace then
|
||||||
|
stillActive = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if stillActive then
|
||||||
|
log("Active cargo mission found for " .. airbaseName .. " (" .. coalitionKey .. ")")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log("No active cargo mission for " .. airbaseName .. " (" .. coalitionKey .. ")")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
trackCargoMission(coalitionKey, mission)
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Adds a new cargo mission to the tracking table and logs it.
|
||||||
|
]]
|
||||||
|
local function trackCargoMission(coalitionKey, mission)
|
||||||
|
table.insert(cargoMissions[coalitionKey], mission)
|
||||||
|
log("Tracking new cargo mission: " .. (mission.group and mission.group:GetName() or "nil group") .. " from " .. mission.origin .. " to " .. mission.destination)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
cleanupCargoMissions()
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Removes failed cargo missions from the tracking table if their group is no longer alive.
|
||||||
|
]]
|
||||||
|
local function cleanupCargoMissions()
|
||||||
|
for _, coalitionKey in ipairs({"red", "blue"}) do
|
||||||
|
for i = #cargoMissions[coalitionKey], 1, -1 do
|
||||||
|
local m = cargoMissions[coalitionKey][i]
|
||||||
|
if m.status == "failed" or m.status == "completed" then
|
||||||
|
if not (m.group and m.group:IsAlive()) then
|
||||||
|
log("Cleaning up " .. m.status .. " cargo mission: " .. (m.group and m.group:GetName() or "nil group"))
|
||||||
|
table.remove(cargoMissions[coalitionKey], i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
dispatchCargo(squadron, coalitionKey)
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Spawns a cargo aircraft from a supply airfield to the destination squadron airbase.
|
||||||
|
Uses static templates for each coalition, assigns a unique group name, and sets a custom route.
|
||||||
|
Tracks the mission and schedules route assignment with a delay to ensure group is alive.
|
||||||
|
]]
|
||||||
|
local function dispatchCargo(squadron, coalitionKey)
|
||||||
|
local config = CARGO_SUPPLY_CONFIG[coalitionKey]
|
||||||
|
local origin
|
||||||
|
local attempts = 0
|
||||||
|
local maxAttempts = 10
|
||||||
|
local coalitionSide = getCoalitionSide(coalitionKey)
|
||||||
|
|
||||||
|
repeat
|
||||||
|
origin = selectRandomAirfield(config.supplyAirfields)
|
||||||
|
attempts = attempts + 1
|
||||||
|
|
||||||
|
-- Ensure origin is not the same as destination
|
||||||
|
if origin == squadron.airbaseName then
|
||||||
|
origin = nil
|
||||||
|
else
|
||||||
|
-- Validate that origin airbase exists and is controlled by correct coalition
|
||||||
|
local originAirbase = AIRBASE:FindByName(origin)
|
||||||
|
if not originAirbase then
|
||||||
|
log("WARNING: Origin airbase '" .. tostring(origin) .. "' does not exist. Trying another...")
|
||||||
|
origin = nil
|
||||||
|
elseif originAirbase:GetCoalition() ~= coalitionSide then
|
||||||
|
log("WARNING: Origin airbase '" .. tostring(origin) .. "' is not controlled by " .. coalitionKey .. " coalition. Trying another...")
|
||||||
|
origin = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
until origin or attempts >= maxAttempts
|
||||||
|
|
||||||
|
-- enforce cooldown per destination to avoid immediate retries
|
||||||
|
lastDispatchAttempt[coalitionKey] = lastDispatchAttempt[coalitionKey] or {}
|
||||||
|
local last = lastDispatchAttempt[coalitionKey][squadron.airbaseName]
|
||||||
|
if last and (timer.getTime() - last) < CARGO_DISPATCH_COOLDOWN then
|
||||||
|
log("Skipping dispatch to " .. squadron.airbaseName .. " (cooldown active)")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not origin then
|
||||||
|
log("No valid origin airfield found for cargo dispatch to " .. squadron.airbaseName .. " (avoiding same origin/destination)")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local destination = squadron.airbaseName
|
||||||
|
local cargoTemplate = config.cargoTemplate
|
||||||
|
-- Safety: check if destination has suitable parking for larger transports. If not, warn in log.
|
||||||
|
local okParking = true
|
||||||
|
-- Only check for likely large transports (C-130 / An-26 are large-ish) — keep conservative
|
||||||
|
if cargoTemplate and (string.find(cargoTemplate:upper(), "C130") or string.find(cargoTemplate:upper(), "C-17") or string.find(cargoTemplate:upper(), "C17") or string.find(cargoTemplate:upper(), "AN26") ) then
|
||||||
|
okParking = destinationHasSuitableParking(destination)
|
||||||
|
if not okParking then
|
||||||
|
log("WARNING: Destination '" .. tostring(destination) .. "' may not have suitable parking for " .. tostring(cargoTemplate) .. ". Skipping dispatch to prevent despawn.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local groupName = cargoTemplate .. "_to_" .. destination .. "_" .. math.random(1000,9999)
|
||||||
|
|
||||||
|
log("Dispatching cargo: " .. groupName .. " from " .. origin .. " to " .. destination)
|
||||||
|
|
||||||
|
-- Spawn cargo aircraft at origin using the template name ONLY for SPAWN
|
||||||
|
-- Note: cargoTemplate is a config string; script uses in-file Lua template tables (CARGO_AIRCRAFT_TEMPLATE_*)
|
||||||
|
log("DEBUG: Attempting spawn for group: '" .. groupName .. "' at airbase: '" .. origin .. "' (using in-file Lua template)", true)
|
||||||
|
local airbaseObj = AIRBASE:FindByName(origin)
|
||||||
|
if not airbaseObj then
|
||||||
|
log("ERROR: AIRBASE:FindByName failed for '" .. tostring(origin) .. "'. Airbase object is nil!")
|
||||||
|
else
|
||||||
|
log("DEBUG: AIRBASE object found for '" .. origin .. "'. Proceeding with spawn.", true)
|
||||||
|
end
|
||||||
|
-- Select the correct template based on coalition
|
||||||
|
local templateBase, uniqueGroupName
|
||||||
|
if coalitionKey == "blue" then
|
||||||
|
templateBase = CARGO_AIRCRAFT_TEMPLATE_BLUE
|
||||||
|
uniqueGroupName = "CARGO_C130_DYNAMIC_" .. math.random(1000,9999)
|
||||||
|
else
|
||||||
|
templateBase = CARGO_AIRCRAFT_TEMPLATE_RED
|
||||||
|
uniqueGroupName = "CARGO_AN26_DYNAMIC_" .. math.random(1000,9999)
|
||||||
|
end
|
||||||
|
-- Clone the template and set the group/unit name
|
||||||
|
-- Prepare a mission placeholder. We'll set the group and spawnPos after successful spawn.
|
||||||
|
local mission = {
|
||||||
|
group = nil,
|
||||||
|
origin = origin,
|
||||||
|
destination = destination,
|
||||||
|
squadron = squadron,
|
||||||
|
status = "pending",
|
||||||
|
-- Anchor a pending start time now to avoid the monitor loop expiring a mission
|
||||||
|
-- before MOOSE has a chance to finalize the OnSpawnGroup callback.
|
||||||
|
_pendingStartTime = timer.getTime(),
|
||||||
|
_spawnPos = nil,
|
||||||
|
_gracePeriod = DISPATCHER_CONFIG.gracePeriod or 8
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Helper to finalize mission after successful spawn
|
||||||
|
local function finalizeMissionAfterSpawn(spawnedGroup, spawnPos)
|
||||||
|
mission.group = spawnedGroup
|
||||||
|
mission._spawnPos = spawnPos
|
||||||
|
trackCargoMission(coalitionKey, mission)
|
||||||
|
lastDispatchAttempt[coalitionKey][squadron.airbaseName] = timer.getTime()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- MOOSE-only spawn-by-name flow
|
||||||
|
if type(cargoTemplate) ~= 'string' or cargoTemplate == '' then
|
||||||
|
log("ERROR: cargoTemplate for coalition '" .. tostring(coalitionKey) .. "' must be a valid mission template name string. Aborting dispatch.")
|
||||||
|
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " aborted (invalid cargo template)!")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Use a per-dispatch RAT object to spawn and route cargo aircraft.
|
||||||
|
-- Create a unique alias to avoid naming collisions and let RAT handle routing/landing.
|
||||||
|
local alias = cargoTemplate .. "_TO_" .. destination .. "_" .. tostring(math.random(1000,9999))
|
||||||
|
log("DEBUG: Attempting RAT spawn for template: '" .. cargoTemplate .. "' alias: '" .. alias .. "'", true)
|
||||||
|
|
||||||
|
-- Validate destination airbase: RAT's "Airbase doesn't exist" error actually means
|
||||||
|
-- "Airbase not found OR not owned by the correct coalition" because RAT filters by coalition internally.
|
||||||
|
-- We perform the same validation here to fail fast with better error messages.
|
||||||
|
local destAirbase = AIRBASE:FindByName(destination)
|
||||||
|
local coalitionSide = getCoalitionSide(coalitionKey)
|
||||||
|
|
||||||
|
if not destAirbase then
|
||||||
|
log("ERROR: Destination airbase '" .. destination .. "' does not exist in DCS (invalid name or not on this map). Skipping dispatch.")
|
||||||
|
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " failed (airbase not found on map)!")
|
||||||
|
-- Mark mission as failed and cleanup immediately
|
||||||
|
mission.status = "failed"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local destCoalition = destAirbase:GetCoalition()
|
||||||
|
if destCoalition ~= coalitionSide then
|
||||||
|
log("INFO: Destination airbase '" .. destination .. "' captured by enemy - cargo dispatch skipped (normal mission state).", true)
|
||||||
|
-- No announcement to coalition - this is expected behavior when base is captured
|
||||||
|
-- Mark mission as failed and cleanup immediately
|
||||||
|
mission.status = "failed"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Validate origin airbase with same coalition filtering logic
|
||||||
|
local originAirbase = AIRBASE:FindByName(origin)
|
||||||
|
if not originAirbase then
|
||||||
|
log("ERROR: Origin airbase '" .. origin .. "' does not exist in DCS (invalid name or not on this map). Skipping dispatch.")
|
||||||
|
announceToCoalition(coalitionKey, "Resupply mission from " .. origin .. " failed (airbase not found on map)!")
|
||||||
|
-- Mark mission as failed and cleanup immediately
|
||||||
|
mission.status = "failed"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local originCoalition = originAirbase:GetCoalition()
|
||||||
|
if originCoalition ~= coalitionSide then
|
||||||
|
log("INFO: Origin airbase '" .. origin .. "' captured by enemy - trying another supply source.", true)
|
||||||
|
-- Don't announce or mark as failed - the dispatcher will try another origin
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local okNew, rat = pcall(function() return RAT:New(cargoTemplate, alias) end)
|
||||||
|
if not okNew or not rat then
|
||||||
|
log("ERROR: RAT:New failed for template '" .. tostring(cargoTemplate) .. "'. Error: " .. tostring(rat))
|
||||||
|
if debug and debug.traceback then
|
||||||
|
log("TRACEBACK: " .. tostring(debug.traceback(rat)), true)
|
||||||
|
end
|
||||||
|
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " failed (spawn init error)!")
|
||||||
|
-- Mark mission as failed and cleanup immediately - do NOT track failed RAT spawns
|
||||||
|
mission.status = "failed"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Configure RAT for a single, non-respawning dispatch
|
||||||
|
rat:SetDeparture(origin)
|
||||||
|
rat:SetDestination(destination)
|
||||||
|
rat:NoRespawn()
|
||||||
|
rat:InitUnControlled(false) -- force departing transports to spawn in a controllable state
|
||||||
|
rat:InitLateActivated(false)
|
||||||
|
rat:SetSpawnLimit(1)
|
||||||
|
rat:SetSpawnDelay(1)
|
||||||
|
|
||||||
|
-- CRITICAL: Force takeoff from runway to prevent aircraft getting stuck at parking
|
||||||
|
-- SetTakeoffRunway() ensures aircraft spawn directly on runway and take off immediately
|
||||||
|
if rat.SetTakeoffRunway then
|
||||||
|
rat:SetTakeoffRunway()
|
||||||
|
log("DEBUG: Configured cargo to take off from runway at " .. origin, true)
|
||||||
|
else
|
||||||
|
log("WARNING: SetTakeoffRunway() not available - falling back to SetTakeoffHot()", true)
|
||||||
|
if rat.SetTakeoffHot then rat:SetTakeoffHot() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ensure RAT will look for parking and not despawn the group immediately on landing.
|
||||||
|
-- This makes the group taxi to parking and come to a stop so other scripts (e.g. Load2nd)
|
||||||
|
-- that detect parked/stopped cargo aircraft can register the delivery.
|
||||||
|
if rat.SetParkingScanRadius then rat:SetParkingScanRadius(80) end
|
||||||
|
if rat.SetParkingSpotSafeON then rat:SetParkingSpotSafeON() end
|
||||||
|
if rat.SetDespawnAirOFF then rat:SetDespawnAirOFF() end
|
||||||
|
-- Check on runway to ensure proper landing behavior (distance in meters)
|
||||||
|
if rat.CheckOnRunway then rat:CheckOnRunway(true, 75) end
|
||||||
|
|
||||||
|
rat:OnSpawnGroup(function(spawnedGroup)
|
||||||
|
-- Mark the canonical start time when MOOSE reports the group exists
|
||||||
|
mission._pendingStartTime = timer.getTime()
|
||||||
|
|
||||||
|
local spawnPos = nil
|
||||||
|
local dcsGroup = spawnedGroup:GetDCSObject()
|
||||||
|
if dcsGroup then
|
||||||
|
local units = dcsGroup:getUnits()
|
||||||
|
if units and #units > 0 then
|
||||||
|
spawnPos = units[1]:getPoint()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log("RAT spawned cargo aircraft group: " .. tostring(spawnedGroup:GetName()))
|
||||||
|
|
||||||
|
-- Temporary debug: log group state every 10s for 10 minutes to trace landing/parking behavior
|
||||||
|
local debugChecks = 60 -- 60 * 10s = 10 minutes
|
||||||
|
local checkInterval = 10
|
||||||
|
local function debugLogState(iter)
|
||||||
|
if iter > debugChecks then return end
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
local name = spawnedGroup:GetName()
|
||||||
|
local dcs = spawnedGroup:GetDCSObject()
|
||||||
|
if dcs then
|
||||||
|
local units = dcs:getUnits()
|
||||||
|
if units and #units > 0 then
|
||||||
|
local u = units[1]
|
||||||
|
local pos = u:getPoint()
|
||||||
|
-- Use dot accessor to test for function existence; colon-call to invoke
|
||||||
|
local vel = (u.getVelocity and u:getVelocity()) or {x=0,y=0,z=0}
|
||||||
|
local speed = math.sqrt((vel.x or 0)^2 + (vel.y or 0)^2 + (vel.z or 0)^2)
|
||||||
|
local controller = dcs:getController()
|
||||||
|
local airbaseObj = AIRBASE:FindByName(destination)
|
||||||
|
local dist = nil
|
||||||
|
if airbaseObj then
|
||||||
|
local dest = airbaseObj:GetCoordinate():GetVec2()
|
||||||
|
local dx = pos.x - dest.x
|
||||||
|
local dz = pos.z - dest.y
|
||||||
|
dist = math.sqrt(dx*dx + dz*dz)
|
||||||
|
end
|
||||||
|
log(string.format("[TDAC DEBUG] %s state check %d: alive=%s pos=(%.1f,%.1f) speed=%.2f m/s distToDest=%s", name, iter, tostring(spawnedGroup:IsAlive()), pos.x or 0, pos.z or 0, speed, tostring(dist)), true)
|
||||||
|
else
|
||||||
|
log(string.format("[TDAC DEBUG] %s state check %d: DCS group has no units", tostring(spawnedGroup:GetName()), iter), true)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log(string.format("[TDAC DEBUG] %s state check %d: no DCS group object", tostring(spawnedGroup:GetName()), iter), true)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
if not ok then
|
||||||
|
log("[TDAC DEBUG] Error during debugLogState: " .. tostring(err), true)
|
||||||
|
end
|
||||||
|
timer.scheduleFunction(function() debugLogState(iter + 1) end, {}, timer.getTime() + checkInterval)
|
||||||
|
end
|
||||||
|
timer.scheduleFunction(function() debugLogState(1) end, {}, timer.getTime() + checkInterval)
|
||||||
|
|
||||||
|
-- RAT should handle routing/taxi/parking. Finalize mission tracking now.
|
||||||
|
finalizeMissionAfterSpawn(spawnedGroup, spawnPos)
|
||||||
|
mission.status = "enroute"
|
||||||
|
mission._pendingStartTime = timer.getTime()
|
||||||
|
announceToCoalition(coalitionKey, "CARGO aircraft departing (airborne) for " .. destination .. ". Defend it!")
|
||||||
|
end)
|
||||||
|
|
||||||
|
local okSpawn, errSpawn = pcall(function() rat:Spawn(1) end)
|
||||||
|
if not okSpawn then
|
||||||
|
log("ERROR: rat:Spawn() failed for template '" .. tostring(cargoTemplate) .. "'. Error: " .. tostring(errSpawn))
|
||||||
|
if debug and debug.traceback then
|
||||||
|
log("TRACEBACK: " .. tostring(debug.traceback(errSpawn)), true)
|
||||||
|
end
|
||||||
|
announceToCoalition(coalitionKey, "Resupply mission to " .. destination .. " failed (spawn error)!")
|
||||||
|
-- Mark mission as failed and cleanup immediately - do NOT track failed spawns
|
||||||
|
mission.status = "failed"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Parking diagnostics helper
|
||||||
|
-- Call from DCS console: _G.TDAC_LogAirbaseParking("Luostari Pechenga")
|
||||||
|
function _G.TDAC_LogAirbaseParking(airbaseName)
|
||||||
|
if type(airbaseName) ~= 'string' then
|
||||||
|
log("TDAC Parking helper: airbaseName must be a string", true)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local base = AIRBASE:FindByName(airbaseName)
|
||||||
|
if not base then
|
||||||
|
log("TDAC Parking helper: AIRBASE:FindByName returned nil for '" .. tostring(airbaseName) .. "'", true)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local function spotsFor(term)
|
||||||
|
local ok, n = pcall(function() return base:GetParkingSpotsNumber(term) end)
|
||||||
|
if not ok then return nil end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
local openBig = spotsFor(AIRBASE.TerminalType.OpenBig)
|
||||||
|
local openMed = spotsFor(AIRBASE.TerminalType.OpenMed)
|
||||||
|
local openMedOrBig = spotsFor(AIRBASE.TerminalType.OpenMedOrBig)
|
||||||
|
local runway = spotsFor(AIRBASE.TerminalType.Runway)
|
||||||
|
log(string.format("TDAC Parking: %s -> OpenBig=%s OpenMed=%s OpenMedOrBig=%s Runway=%s", airbaseName, tostring(openBig), tostring(openMed), tostring(openMedOrBig), tostring(runway)), true)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Pre-dispatch safety check: ensure destination can accommodate larger transport types
|
||||||
|
destinationHasSuitableParking = function(destination, preferredTermTypes)
|
||||||
|
local base = AIRBASE:FindByName(destination)
|
||||||
|
if not base then return false end
|
||||||
|
preferredTermTypes = preferredTermTypes or { AIRBASE.TerminalType.OpenBig, AIRBASE.TerminalType.OpenMedOrBig, AIRBASE.TerminalType.OpenMed }
|
||||||
|
for _, term in ipairs(preferredTermTypes) do
|
||||||
|
local ok, n = pcall(function() return base:GetParkingSpotsNumber(term) end)
|
||||||
|
if ok and n and n > 0 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--[[
|
||||||
|
monitorSquadrons()
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Checks all squadrons for each coalition. If a squadron is below the resupply threshold and has no active cargo mission,
|
||||||
|
triggers a supply request and dispatches a cargo aircraft.
|
||||||
|
Skips squadrons that are captured or not operational.
|
||||||
|
]]
|
||||||
|
local function monitorSquadrons()
|
||||||
|
for _, coalitionKey in ipairs({"red", "blue"}) do
|
||||||
|
local config = CARGO_SUPPLY_CONFIG[coalitionKey]
|
||||||
|
local squadrons = (coalitionKey == "red") and RED_SQUADRON_CONFIG or BLUE_SQUADRON_CONFIG
|
||||||
|
for _, squadron in ipairs(squadrons) do
|
||||||
|
-- Skip non-operational squadrons (captured, destroyed, etc.)
|
||||||
|
if squadron.state and squadron.state ~= "operational" then
|
||||||
|
log("Squadron " .. squadron.displayName .. " (" .. coalitionKey .. ") is " .. squadron.state .. " - skipping cargo dispatch", true)
|
||||||
|
else
|
||||||
|
local current, max, ratio = getSquadronStatus(squadron, coalitionKey)
|
||||||
|
log("Squadron status: " .. squadron.displayName .. " (" .. coalitionKey .. ") " .. current .. "/" .. max .. " ratio: " .. string.format("%.2f", ratio))
|
||||||
|
if ratio <= config.threshold and not hasActiveCargoMission(coalitionKey, squadron.airbaseName) then
|
||||||
|
log("Supply request triggered for " .. squadron.displayName .. " at " .. squadron.airbaseName)
|
||||||
|
announceToCoalition(coalitionKey, "Supply requested for " .. squadron.airbaseName .. "! Squadron: " .. squadron.displayName)
|
||||||
|
dispatchCargo(squadron, coalitionKey)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
monitorCargoMissions()
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Monitors all cargo missions, updates their status, and cleans up failed ones.
|
||||||
|
Handles mission failure after a grace period.
|
||||||
|
]]
|
||||||
|
local function monitorCargoMissions()
|
||||||
|
for _, coalitionKey in ipairs({"red", "blue"}) do
|
||||||
|
for _, mission in ipairs(cargoMissions[coalitionKey]) do
|
||||||
|
if mission.group == nil then
|
||||||
|
log("[DEBUG] Mission group object is nil for mission to " .. tostring(mission.destination), true)
|
||||||
|
else
|
||||||
|
log("[DEBUG] Mission group: " .. tostring(mission.group:GetName()) .. ", IsAlive(): " .. tostring(mission.group:IsAlive()), true)
|
||||||
|
local dcsGroup = mission.group:GetDCSObject()
|
||||||
|
if dcsGroup then
|
||||||
|
local units = dcsGroup:getUnits()
|
||||||
|
if units and #units > 0 then
|
||||||
|
local pos = units[1]:getPoint()
|
||||||
|
log(string.format("[DEBUG] Group position: x=%.1f y=%.1f z=%.1f", pos.x, pos.y, pos.z), true)
|
||||||
|
else
|
||||||
|
log("[DEBUG] No units found in DCS group for mission to " .. tostring(mission.destination), true)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log("[DEBUG] DCS group object is nil for mission to " .. tostring(mission.destination), true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local graceElapsed = mission._pendingStartTime and (timer.getTime() - mission._pendingStartTime > (mission._gracePeriod or 8))
|
||||||
|
|
||||||
|
-- Only allow mission to be failed after grace period, and only if group is truly dead.
|
||||||
|
-- Some DCS/MOOSE group objects may momentarily report IsAlive() == false while units still exist, so
|
||||||
|
-- also check DCS object/unit presence before declaring failure.
|
||||||
|
if (mission.status == "pending" or mission.status == "enroute") and graceElapsed then
|
||||||
|
local isAlive = mission.group and mission.group:IsAlive()
|
||||||
|
local dcsGroup = mission.group and mission.group:GetDCSObject()
|
||||||
|
local unitsPresent = false
|
||||||
|
if dcsGroup then
|
||||||
|
local units = dcsGroup:getUnits()
|
||||||
|
unitsPresent = units and (#units > 0)
|
||||||
|
end
|
||||||
|
if not isAlive and not unitsPresent then
|
||||||
|
mission.status = "failed"
|
||||||
|
log("Cargo mission failed (after grace period): " .. (mission.group and mission.group:GetName() or "nil group") .. " to " .. mission.destination)
|
||||||
|
announceToCoalition(coalitionKey, "Resupply mission to " .. mission.destination .. " failed!")
|
||||||
|
else
|
||||||
|
log("DEBUG: Mission appears to still have DCS units despite IsAlive=false; skipping failure for " .. tostring(mission.destination), true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cleanupCargoMissions()
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
MAIN DISPATCHER LOOP
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Runs the main dispatcher logic on a timer interval.
|
||||||
|
]]
|
||||||
|
local function cargoDispatcherMain()
|
||||||
|
log("═══════════════════════════════════════════════════════════════════════════════", true)
|
||||||
|
log("Cargo Dispatcher main loop running.", true)
|
||||||
|
monitorSquadrons()
|
||||||
|
monitorCargoMissions()
|
||||||
|
-- Schedule the next run inside a protected call to avoid unhandled errors
|
||||||
|
timer.scheduleFunction(function()
|
||||||
|
local ok, err = pcall(cargoDispatcherMain)
|
||||||
|
if not ok then
|
||||||
|
log("FATAL: cargoDispatcherMain crashed on scheduled run: " .. tostring(err))
|
||||||
|
-- do not reschedule to avoid crash loops
|
||||||
|
end
|
||||||
|
end, {}, timer.getTime() + DISPATCHER_CONFIG.interval)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Start the dispatcher
|
||||||
|
local ok, err = pcall(cargoDispatcherMain)
|
||||||
|
if not ok then
|
||||||
|
log("FATAL: cargoDispatcherMain crashed on startup: " .. tostring(err))
|
||||||
|
end
|
||||||
|
|
||||||
|
log("═══════════════════════════════════════════════════════════════════════════════", true)
|
||||||
|
-- End Moose_TDAC_CargoDispatcher.lua
|
||||||
|
|
||||||
|
|
||||||
|
--[[
|
||||||
|
DIAGNOSTIC CONSOLE HELPERS
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
Functions you can call from the DCS Lua console (F12) to debug issues.
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Check airbase coalition ownership for all configured supply airbases
|
||||||
|
-- Usage: _G.TDAC_CheckAirbaseOwnership()
|
||||||
|
function _G.TDAC_CheckAirbaseOwnership()
|
||||||
|
env.info("[TDAC DIAGNOSTIC] ═══════════════════════════════════════")
|
||||||
|
env.info("[TDAC DIAGNOSTIC] Checking Coalition Ownership of All Supply Airbases")
|
||||||
|
env.info("[TDAC DIAGNOSTIC] ═══════════════════════════════════════")
|
||||||
|
|
||||||
|
for _, coalitionKey in ipairs({"red", "blue"}) do
|
||||||
|
local config = CARGO_SUPPLY_CONFIG[coalitionKey]
|
||||||
|
local expectedCoalition = getCoalitionSide(coalitionKey)
|
||||||
|
|
||||||
|
env.info(string.format("[TDAC DIAGNOSTIC] %s COALITION (expected coalition ID: %s)", coalitionKey:upper(), tostring(expectedCoalition)))
|
||||||
|
|
||||||
|
if config and config.supplyAirfields then
|
||||||
|
for _, airbaseName in ipairs(config.supplyAirfields) do
|
||||||
|
local airbase = AIRBASE:FindByName(airbaseName)
|
||||||
|
if not airbase then
|
||||||
|
env.info(string.format("[TDAC DIAGNOSTIC] ✗ %-30s - NOT FOUND (invalid name or not on this map)", airbaseName))
|
||||||
|
else
|
||||||
|
local actualCoalition = airbase:GetCoalition()
|
||||||
|
local coalitionName = "UNKNOWN"
|
||||||
|
local status = "✗"
|
||||||
|
|
||||||
|
if actualCoalition == coalition.side.NEUTRAL then
|
||||||
|
coalitionName = "NEUTRAL"
|
||||||
|
elseif actualCoalition == coalition.side.RED then
|
||||||
|
coalitionName = "RED"
|
||||||
|
elseif actualCoalition == coalition.side.BLUE then
|
||||||
|
coalitionName = "BLUE"
|
||||||
|
end
|
||||||
|
|
||||||
|
if actualCoalition == expectedCoalition then
|
||||||
|
status = "✓"
|
||||||
|
end
|
||||||
|
|
||||||
|
env.info(string.format("[TDAC DIAGNOSTIC] %s %-30s - %s (coalition ID: %s)", status, airbaseName, coalitionName, tostring(actualCoalition)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
env.info("[TDAC DIAGNOSTIC] ERROR: No supply airfields configured!")
|
||||||
|
end
|
||||||
|
env.info("[TDAC DIAGNOSTIC] ───────────────────────────────────────")
|
||||||
|
end
|
||||||
|
|
||||||
|
env.info("[TDAC DIAGNOSTIC] ═══════════════════════════════════════")
|
||||||
|
env.info("[TDAC DIAGNOSTIC] Check complete. ✓ = Owned by correct coalition, ✗ = Wrong coalition or not found")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check specific airbase coalition ownership
|
||||||
|
-- Usage: _G.TDAC_CheckAirbase("Olenya")
|
||||||
|
function _G.TDAC_CheckAirbase(airbaseName)
|
||||||
|
if type(airbaseName) ~= 'string' then
|
||||||
|
env.info("[TDAC DIAGNOSTIC] ERROR: airbaseName must be a string")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local airbase = AIRBASE:FindByName(airbaseName)
|
||||||
|
if not airbase then
|
||||||
|
env.info(string.format("[TDAC DIAGNOSTIC] Airbase '%s' NOT FOUND (invalid name or not on this map)", airbaseName))
|
||||||
|
return false, "not_found"
|
||||||
|
end
|
||||||
|
|
||||||
|
local actualCoalition = airbase:GetCoalition()
|
||||||
|
local coalitionName = "UNKNOWN"
|
||||||
|
|
||||||
|
if actualCoalition == coalition.side.NEUTRAL then
|
||||||
|
coalitionName = "NEUTRAL"
|
||||||
|
elseif actualCoalition == coalition.side.RED then
|
||||||
|
coalitionName = "RED"
|
||||||
|
elseif actualCoalition == coalition.side.BLUE then
|
||||||
|
coalitionName = "BLUE"
|
||||||
|
end
|
||||||
|
|
||||||
|
env.info(string.format("[TDAC DIAGNOSTIC] Airbase '%s' - Coalition: %s (ID: %s)", airbaseName, coalitionName, tostring(actualCoalition)))
|
||||||
|
env.info(string.format("[TDAC DIAGNOSTIC] IsAlive: %s", tostring(airbase:IsAlive())))
|
||||||
|
|
||||||
|
-- Check parking spots
|
||||||
|
local function spotsFor(term, termName)
|
||||||
|
local ok, n = pcall(function() return airbase:GetParkingSpotsNumber(term) end)
|
||||||
|
if ok and n then
|
||||||
|
env.info(string.format("[TDAC DIAGNOSTIC] Parking %-15s: %d spots", termName, n))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
spotsFor(AIRBASE.TerminalType.OpenBig, "OpenBig")
|
||||||
|
spotsFor(AIRBASE.TerminalType.OpenMed, "OpenMed")
|
||||||
|
spotsFor(AIRBASE.TerminalType.OpenMedOrBig, "OpenMedOrBig")
|
||||||
|
spotsFor(AIRBASE.TerminalType.Runway, "Runway")
|
||||||
|
|
||||||
|
return true, coalitionName, actualCoalition
|
||||||
|
end
|
||||||
|
|
||||||
|
env.info("[TDAC DIAGNOSTIC] Console helpers loaded:")
|
||||||
|
env.info("[TDAC DIAGNOSTIC] _G.TDAC_CheckAirbaseOwnership() - Check all supply airbases")
|
||||||
|
env.info("[TDAC DIAGNOSTIC] _G.TDAC_CheckAirbase('Olenya') - Check specific airbase")
|
||||||
|
env.info("[TDAC DIAGNOSTIC] _G.TDAC_RunConfigCheck() - Validate dispatcher config")
|
||||||
|
env.info("[TDAC DIAGNOSTIC] _G.TDAC_LogAirbaseParking('Olenya') - Check parking availability")
|
||||||
|
|
||||||
|
|
||||||
|
-- Diagnostic helper: call from DCS console to test spawn-by-name and routing.
|
||||||
|
-- Example (paste into DCS Lua console):
|
||||||
|
-- _G.TDAC_CargoDispatcher_TestSpawn("CARGO_BLUE_C130_TEMPLATE", "Kittila", "Luostari Pechenga")
|
||||||
|
function _G.TDAC_CargoDispatcher_TestSpawn(templateName, originAirbase, destinationAirbase)
|
||||||
|
log("[TDAC TEST] Starting test spawn for template: " .. tostring(templateName), true)
|
||||||
|
local ok, err
|
||||||
|
if type(templateName) ~= 'string' then
|
||||||
|
env.info("[TDAC TEST] templateName must be a string")
|
||||||
|
return false, "invalid templateName"
|
||||||
|
end
|
||||||
|
local spawnByName = nil
|
||||||
|
ok, spawnByName = pcall(function() return SPAWN:New(templateName) end)
|
||||||
|
if not ok or not spawnByName then
|
||||||
|
log("[TDAC TEST] SPAWN:New failed for template " .. tostring(templateName) .. ". Error: " .. tostring(spawnByName), true)
|
||||||
|
if debug and debug.traceback then log("TRACEBACK: " .. tostring(debug.traceback(tostring(spawnByName))), true) end
|
||||||
|
return false, "spawn_new_failed"
|
||||||
|
end
|
||||||
|
|
||||||
|
spawnByName:OnSpawnGroup(function(spawnedGroup)
|
||||||
|
log("[TDAC TEST] OnSpawnGroup called for: " .. tostring(spawnedGroup:GetName()), true)
|
||||||
|
local dcsGroup = spawnedGroup:GetDCSObject()
|
||||||
|
if dcsGroup then
|
||||||
|
local units = dcsGroup:getUnits()
|
||||||
|
if units and #units > 0 then
|
||||||
|
local pos = units[1]:getPoint()
|
||||||
|
log(string.format("[TDAC TEST] Spawned pos x=%.1f y=%.1f z=%.1f", pos.x, pos.y, pos.z), true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if destinationAirbase then
|
||||||
|
local okAssign, errAssign = pcall(function()
|
||||||
|
local base = AIRBASE:FindByName(destinationAirbase)
|
||||||
|
if base and spawnedGroup and spawnedGroup.RouteToAirbase then
|
||||||
|
spawnedGroup:RouteToAirbase(base, AI_Task_Land.Runway)
|
||||||
|
log("[TDAC TEST] RouteToAirbase assigned to " .. tostring(destinationAirbase), true)
|
||||||
|
else
|
||||||
|
log("[TDAC TEST] RouteToAirbase not available or base not found", true)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
if not okAssign then
|
||||||
|
log("[TDAC TEST] RouteToAirbase pcall failed: " .. tostring(errAssign), true)
|
||||||
|
if debug and debug.traceback then log("TRACEBACK: " .. tostring(debug.traceback(tostring(errAssign))), true) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
ok, err = pcall(function() spawnByName:Spawn() end)
|
||||||
|
if not ok then
|
||||||
|
log("[TDAC TEST] spawnByName:Spawn() failed: " .. tostring(err), true)
|
||||||
|
if debug and debug.traceback then log("TRACEBACK: " .. tostring(debug.traceback(tostring(err))), true) end
|
||||||
|
return false, "spawn_failed"
|
||||||
|
end
|
||||||
|
log("[TDAC TEST] spawnByName:Spawn() returned successfully", true)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
log("═══════════════════════════════════════════════════════════════════════════════", true)
|
||||||
|
-- End Moose_TDAC_CargoDispatcher.lua
|
||||||
|
|
||||||
2617
DCS_Afgainistan/Insurgent_Sandstorm/Moose_TADC_Load2nd.lua
Normal file
2617
DCS_Afgainistan/Insurgent_Sandstorm/Moose_TADC_Load2nd.lua
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,374 @@
|
|||||||
|
|
||||||
|
--[[ THIS FILE MUST BE LOADED BEFORE THE MAIN Moose_TADC.lua SCRIPT
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
SQUADRON CONFIGURATION
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
INSTRUCTIONS:
|
||||||
|
1. Create fighter aircraft templates for BOTH coalitions in the mission editor
|
||||||
|
2. Place them at or near the airbases you want them to operate from
|
||||||
|
3. Configure RED squadrons in RED_SQUADRON_CONFIG
|
||||||
|
4. Configure BLUE squadrons in BLUE_SQUADRON_CONFIG
|
||||||
|
|
||||||
|
TEMPLATE NAMING SUGGESTIONS:
|
||||||
|
• RED: "RED_CAP_Batumi_F15", "RED_INTERCEPT_Senaki_MiG29"
|
||||||
|
• BLUE: "BLUE_CAP_Nellis_F16", "BLUE_INTERCEPT_Creech_F22"
|
||||||
|
• Include coalition and airbase name for easy identification
|
||||||
|
|
||||||
|
AIRBASE NAMES:
|
||||||
|
• Use exact names as they appear in DCS (case sensitive)
|
||||||
|
• RED examples: "Batumi", "Senaki", "Gudauta"
|
||||||
|
• BLUE examples: "Nellis AFB", "McCarran International", "Tonopah Test Range"
|
||||||
|
• Find airbase names in the mission editor
|
||||||
|
|
||||||
|
AIRCRAFT NUMBERS:
|
||||||
|
• Set realistic numbers based on mission requirements
|
||||||
|
• Consider aircraft consumption and cargo replenishment
|
||||||
|
• Balance between realism and gameplay performance
|
||||||
|
|
||||||
|
ZONE-BASED AREAS OF RESPONSIBILITY:
|
||||||
|
• Create zones in mission editor (MOOSE polygons, circles, etc.)
|
||||||
|
• primaryZone: Squadron's main area (full response)
|
||||||
|
• secondaryZone: Backup/support area (reduced response)
|
||||||
|
• tertiaryZone: Emergency fallback area (enhanced response)
|
||||||
|
• Leave zones as nil for global threat response
|
||||||
|
• Multiple squadrons can share overlapping zones
|
||||||
|
• Use zone names exactly as they appear in mission editor
|
||||||
|
|
||||||
|
ZONE BEHAVIOR EXAMPLES:
|
||||||
|
• Border Defense: primaryZone = "SECTOR_ALPHA", secondaryZone = "BUFFER_ZONE"
|
||||||
|
• Base Defense: tertiaryZone = "BASE_PERIMETER", enableFallback = true
|
||||||
|
• Layered Defense: Different zones per squadron with overlap
|
||||||
|
• Emergency Response: High tertiaryResponse ratio for critical areas
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
-- RED COALITION SQUADRONS
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
RED_SQUADRON_CONFIG = {
|
||||||
|
--[[ EXAMPLE RED SQUADRON - CUSTOMIZE FOR YOUR MISSION
|
||||||
|
{
|
||||||
|
templateName = "RED_CAP_Batumi_F15", -- Template name from mission editor
|
||||||
|
displayName = "Batumi F-15C CAP", -- Human-readable name for logs
|
||||||
|
airbaseName = "Batumi", -- Exact airbase name from DCS
|
||||||
|
aircraft = 12, -- Maximum aircraft in squadron
|
||||||
|
skill = AI.Skill.GOOD, -- AI skill level
|
||||||
|
altitude = 20000, -- Patrol altitude (feet)
|
||||||
|
speed = 350, -- Patrol speed (knots)
|
||||||
|
patrolTime = 25, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER"
|
||||||
|
|
||||||
|
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
|
||||||
|
primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
|
||||||
|
secondaryZone = nil, -- Secondary coverage area (zone name)
|
||||||
|
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
|
||||||
|
|
||||||
|
-- Zone behavior settings (optional - uses defaults if not specified)
|
||||||
|
zoneConfig = {
|
||||||
|
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
|
||||||
|
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
|
||||||
|
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
|
||||||
|
maxRange = 200, -- Maximum engagement range from airbase (nm)
|
||||||
|
enableFallback = false, -- Auto-switch to tertiary when base threatened
|
||||||
|
priorityThreshold = 4, -- Min aircraft count for "major threat"
|
||||||
|
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- ADD YOUR RED SQUADRONS HERE
|
||||||
|
{
|
||||||
|
templateName = "Shindand MiG-31", -- Change to your RED template name
|
||||||
|
displayName = "Shindand MiG-31", -- Change to your preferred name
|
||||||
|
airbaseName = "Shindand", -- Change to your RED airbase
|
||||||
|
aircraft = 12, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT, ACE
|
||||||
|
altitude = 20000, -- Patrol altitude (feet)
|
||||||
|
speed = 350, -- Patrol speed (knots)
|
||||||
|
patrolTime = 25, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER",
|
||||||
|
|
||||||
|
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
|
||||||
|
primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
|
||||||
|
secondaryZone = nil, -- Secondary coverage area (zone name)
|
||||||
|
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
|
||||||
|
|
||||||
|
-- Zone behavior settings (optional - uses defaults if not specified)
|
||||||
|
zoneConfig = {
|
||||||
|
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
|
||||||
|
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
|
||||||
|
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
|
||||||
|
maxRange = 200, -- Maximum engagement range from airbase (nm)
|
||||||
|
enableFallback = false, -- Auto-switch to tertiary when base threatened
|
||||||
|
priorityThreshold = 4, -- Min aircraft count for "major threat"
|
||||||
|
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
templateName = "Shindand F-14B", -- Change to your RED template name
|
||||||
|
displayName = "Shindand F-14B", -- Change to your preferred name
|
||||||
|
airbaseName = "Shindand", -- Change to your RED airbase
|
||||||
|
aircraft = 12, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT
|
||||||
|
altitude = 20000, -- Patrol altitude (feet)
|
||||||
|
speed = 350, -- Patrol speed (knots)
|
||||||
|
patrolTime = 25, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER",
|
||||||
|
|
||||||
|
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
|
||||||
|
primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
|
||||||
|
secondaryZone = nil, -- Secondary coverage area (zone name)
|
||||||
|
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
|
||||||
|
|
||||||
|
-- Zone behavior settings (optional - uses defaults if not specified)
|
||||||
|
zoneConfig = {
|
||||||
|
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
|
||||||
|
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
|
||||||
|
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
|
||||||
|
maxRange = 200, -- Maximum engagement range from airbase (nm)
|
||||||
|
enableFallback = false, -- Auto-switch to tertiary when base threatened
|
||||||
|
priorityThreshold = 4, -- Min aircraft count for "major threat"
|
||||||
|
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
templateName = "Shindand MiG-21", -- Change to your RED template name
|
||||||
|
displayName = "Shindand MiG-21", -- Change to your preferred name
|
||||||
|
airbaseName = "Shindand", -- Change to your RED airbase
|
||||||
|
aircraft = 14, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT
|
||||||
|
altitude = 20000, -- Patrol altitude (feet)
|
||||||
|
speed = 350, -- Patrol speed (knots)
|
||||||
|
patrolTime = 25, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER",
|
||||||
|
|
||||||
|
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
|
||||||
|
primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
|
||||||
|
secondaryZone = nil, -- Secondary coverage area (zone name)
|
||||||
|
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
|
||||||
|
|
||||||
|
-- Zone behavior settings (optional - uses defaults if not specified)
|
||||||
|
zoneConfig = {
|
||||||
|
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
|
||||||
|
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
|
||||||
|
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
|
||||||
|
maxRange = 200, -- Maximum engagement range from airbase (nm)
|
||||||
|
enableFallback = false, -- Auto-switch to tertiary when base threatened
|
||||||
|
priorityThreshold = 4, -- Min aircraft count for "major threat"
|
||||||
|
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{ templateName = "Farah MiG-21", -- Change to your RED template name
|
||||||
|
displayName = "Farah MiG-21", -- Change to your preferred name
|
||||||
|
airbaseName = "Farah", -- Change to your RED airbase
|
||||||
|
aircraft = 14, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT
|
||||||
|
altitude = 20000, -- Patrol altitude (feet)
|
||||||
|
speed = 350, -- Patrol speed (knots)
|
||||||
|
patrolTime = 25, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER",
|
||||||
|
|
||||||
|
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
|
||||||
|
primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
|
||||||
|
secondaryZone = nil, -- Secondary coverage area (zone name)
|
||||||
|
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
|
||||||
|
|
||||||
|
-- Zone behavior settings (optional - uses defaults if not specified)
|
||||||
|
zoneConfig = {
|
||||||
|
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
|
||||||
|
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
|
||||||
|
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
|
||||||
|
maxRange = 200, -- Maximum engagement range from airbase (nm)
|
||||||
|
enableFallback = false, -- Auto-switch to tertiary when base threatened
|
||||||
|
priorityThreshold = 4, -- Min aircraft count for "major threat"
|
||||||
|
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ templateName = "Camp Bastion F-14B", -- Change to your RED template name
|
||||||
|
displayName = "Camp Bastion F-14B", -- Change to your preferred name
|
||||||
|
airbaseName = "Camp Bastion", -- Change to your RED airbase
|
||||||
|
aircraft = 12, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT
|
||||||
|
altitude = 20000, -- Patrol altitude (feet)
|
||||||
|
speed = 350, -- Patrol speed (knots)
|
||||||
|
patrolTime = 25, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER",
|
||||||
|
|
||||||
|
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
|
||||||
|
primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
|
||||||
|
secondaryZone = nil, -- Secondary coverage area (zone name)
|
||||||
|
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
|
||||||
|
|
||||||
|
-- Zone behavior settings (optional - uses defaults if not specified)
|
||||||
|
zoneConfig = {
|
||||||
|
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
|
||||||
|
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
|
||||||
|
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
|
||||||
|
maxRange = 200, -- Maximum engagement range from airbase (nm)
|
||||||
|
enableFallback = false, -- Auto-switch to tertiary when base threatened
|
||||||
|
priorityThreshold = 4, -- Min aircraft count for "major threat"
|
||||||
|
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ templateName = "Camp Bastion Su-27", -- Change to your RED template name
|
||||||
|
displayName = "Camp Bastion Su-27", -- Change to your preferred name
|
||||||
|
airbaseName = "Camp Bastion", -- Change to your RED airbase
|
||||||
|
aircraft = 12, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT
|
||||||
|
altitude = 20000, -- Patrol altitude (feet)
|
||||||
|
speed = 350, -- Patrol speed (knots)
|
||||||
|
patrolTime = 25, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER",
|
||||||
|
|
||||||
|
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
|
||||||
|
primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
|
||||||
|
secondaryZone = nil, -- Secondary coverage area (zone name)
|
||||||
|
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
|
||||||
|
|
||||||
|
-- Zone behavior settings (optional - uses defaults if not specified)
|
||||||
|
zoneConfig = {
|
||||||
|
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
|
||||||
|
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
|
||||||
|
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
|
||||||
|
maxRange = 200, -- Maximum engagement range from airbase (nm)
|
||||||
|
enableFallback = false, -- Auto-switch to tertiary when base threatened
|
||||||
|
priorityThreshold = 4, -- Min aircraft count for "major threat"
|
||||||
|
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ templateName = "Camp Bastion Mig-31", -- Change to your RED template name
|
||||||
|
displayName = "Camp Bastion Mig-31", -- Change to your preferred name
|
||||||
|
airbaseName = "Camp Bastion", -- Change to your RED airbase
|
||||||
|
aircraft = 12, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.ACE, -- AVERAGE, GOOD, HIGH, EXCELLENT
|
||||||
|
altitude = 20000, -- Patrol altitude (feet)
|
||||||
|
speed = 350, -- Patrol speed (knots)
|
||||||
|
patrolTime = 25, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER",
|
||||||
|
|
||||||
|
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
|
||||||
|
primaryZone = "RED_BORDER", -- Main responsibility area (zone name from mission editor)
|
||||||
|
secondaryZone = nil, -- Secondary coverage area (zone name)
|
||||||
|
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
|
||||||
|
|
||||||
|
-- Zone behavior settings (optional - uses defaults if not specified)
|
||||||
|
zoneConfig = {
|
||||||
|
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
|
||||||
|
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
|
||||||
|
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
|
||||||
|
maxRange = 200, -- Maximum engagement range from airbase (nm)
|
||||||
|
enableFallback = false, -- Auto-switch to tertiary when base threatened
|
||||||
|
priorityThreshold = 4, -- Min aircraft count for "major threat"
|
||||||
|
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
-- BLUE COALITION SQUADRONS
|
||||||
|
-- ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
BLUE_SQUADRON_CONFIG = {
|
||||||
|
--[[ EXAMPLE BLUE SQUADRON - CUSTOMIZE FOR YOUR MISSION
|
||||||
|
{
|
||||||
|
templateName = "BLUE_CAP_Nellis_F16", -- Template name from mission editor
|
||||||
|
displayName = "Nellis F-16C CAP", -- Human-readable name for logs
|
||||||
|
airbaseName = "Nellis AFB", -- Exact airbase name from DCS
|
||||||
|
aircraft = 14, -- Maximum aircraft in squadron
|
||||||
|
skill = AI.Skill.EXCELLENT, -- AI skill level
|
||||||
|
altitude = 22000, -- Patrol altitude (feet)
|
||||||
|
speed = 380, -- Patrol speed (knots)
|
||||||
|
patrolTime = 28, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER" -- Aircraft type
|
||||||
|
},
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- ADD YOUR BLUE SQUADRONS HERE
|
||||||
|
|
||||||
|
{
|
||||||
|
templateName = "Kandahar F-4E", -- Change to your BLUE template name
|
||||||
|
displayName = "Kandahar F-4E", -- Change to your preferred name
|
||||||
|
airbaseName = "Kandahar", -- Change to your BLUE airbase
|
||||||
|
aircraft = 18, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.EXCELLENT, -- AVERAGE, GOOD, HIGH, EXCELLENT
|
||||||
|
altitude = 18000, -- Patrol altitude (feet)
|
||||||
|
speed = 320, -- Patrol speed (knots)
|
||||||
|
patrolTime = 22, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER",
|
||||||
|
|
||||||
|
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
|
||||||
|
primaryZone = "BLUE BORDER", -- Main responsibility area (zone name from mission editor)
|
||||||
|
secondaryZone = nil, -- Secondary coverage area (zone name)
|
||||||
|
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
|
||||||
|
|
||||||
|
-- Zone behavior settings (optional - uses defaults if not specified)
|
||||||
|
zoneConfig = {
|
||||||
|
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
|
||||||
|
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
|
||||||
|
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
|
||||||
|
maxRange = 200, -- Maximum engagement range from airbase (nm)
|
||||||
|
enableFallback = true, -- Auto-switch to tertiary when base threatened
|
||||||
|
priorityThreshold = 4, -- Min aircraft count for "major threat"
|
||||||
|
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
templateName = "Kandahar F-5E", -- Change to your BLUE template name
|
||||||
|
displayName = "Kandahar F-5E", -- Change to your preferred name
|
||||||
|
airbaseName = "Kandahar", -- Change to your BLUE airbase
|
||||||
|
aircraft = 18, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.EXCELLENT, -- AVERAGE, GOOD, HIGH, EXCELLENT
|
||||||
|
altitude = 18000, -- Patrol altitude (feet)
|
||||||
|
speed = 320, -- Patrol speed (knots)
|
||||||
|
patrolTime = 22, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER",
|
||||||
|
|
||||||
|
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
|
||||||
|
primaryZone = "BLUE BORDER", -- Main responsibility area (zone name from mission editor)
|
||||||
|
secondaryZone = nil, -- Secondary coverage area (zone name)
|
||||||
|
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
|
||||||
|
|
||||||
|
-- Zone behavior settings (optional - uses defaults if not specified)
|
||||||
|
zoneConfig = {
|
||||||
|
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
|
||||||
|
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
|
||||||
|
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
|
||||||
|
maxRange = 200, -- Maximum engagement range from airbase (nm)
|
||||||
|
enableFallback = true, -- Auto-switch to tertiary when base threatened
|
||||||
|
priorityThreshold = 4, -- Min aircraft count for "major threat"
|
||||||
|
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ templateName = "Kandahar F-16", -- Change to your BLUE template name
|
||||||
|
displayName = "Kandahar F-16", -- Change to your preferred name
|
||||||
|
airbaseName = "Kandahar", -- Change to your BLUE airbase
|
||||||
|
aircraft = 18, -- Adjust aircraft count
|
||||||
|
skill = AI.Skill.EXCELLENT, -- AVERAGE, GOOD, HIGH, EXCELLENT
|
||||||
|
altitude = 18000, -- Patrol altitude (feet)
|
||||||
|
speed = 320, -- Patrol speed (knots)
|
||||||
|
patrolTime = 22, -- Time on station (minutes)
|
||||||
|
type = "FIGHTER",
|
||||||
|
|
||||||
|
-- Zone-based Areas of Responsibility (optional - leave nil for global response)
|
||||||
|
primaryZone = "BLUE BORDER", -- Main responsibility area (zone name from mission editor)
|
||||||
|
secondaryZone = nil, -- Secondary coverage area (zone name)
|
||||||
|
tertiaryZone = nil, -- Emergency/fallback zone (zone name)
|
||||||
|
|
||||||
|
-- Zone behavior settings (optional - uses defaults if not specified)
|
||||||
|
zoneConfig = {
|
||||||
|
primaryResponse = 1.0, -- Intercept ratio multiplier in primary zone
|
||||||
|
secondaryResponse = 0.6, -- Intercept ratio multiplier in secondary zone
|
||||||
|
tertiaryResponse = 1.4, -- Intercept ratio multiplier in tertiary zone
|
||||||
|
maxRange = 200, -- Maximum engagement range from airbase (nm)
|
||||||
|
enableFallback = true, -- Auto-switch to tertiary when base threatened
|
||||||
|
priorityThreshold = 4, -- Min aircraft count for "major threat"
|
||||||
|
ignoreLowPriority = false, -- Ignore threats below threshold in secondary zones
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user