diff --git a/DCS_Syria/Battle for Rayak Valley/F99th-Battle for Rayak Valley-1.0.0.miz b/DCS_Syria/Battle for Rayak Valley/F99th-Battle for Rayak Valley-1.0.0.miz new file mode 100644 index 0000000..c201ac2 Binary files /dev/null and b/DCS_Syria/Battle for Rayak Valley/F99th-Battle for Rayak Valley-1.0.0.miz differ diff --git a/DCS_Syria/Battle for Rayak Valley/Moose_BattleForRayakValley.lua b/DCS_Syria/Battle for Rayak Valley/Moose_BattleForRayakValley.lua new file mode 100644 index 0000000..679a146 --- /dev/null +++ b/DCS_Syria/Battle for Rayak Valley/Moose_BattleForRayakValley.lua @@ -0,0 +1,139 @@ +--[[ Battle for Rayak Valley - Moose Script +Author: F9tth-TracerFacer + + + +]] + +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, "Battle for Rayak Valley", "Primary", "Clear the front lines of enemy activity.", coalition.side.BLUE) +US_Score = SCORING:New( "Battle for Rayak Valley - 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, "Battle for Rayak Valley", "Primary", "Destroy U.S. and NATO forces.", coalition.side.RED) +RU_Score = SCORING:New("Battle for Rayak Valley - Red") +RU_Mission:AddScoring( RU_Score) +RU_Mission:Start() +RU_Score:SetMessagesHit(false) +RU_Score:SetMessagesDestroy(false) +RU_Score:SetMessagesScore(false) + + +------------------------------------------------------------------------------------------------------------------------------------------------------ +-- 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 ) + + + +------------------------------------------------------------------------------------------------------------------------------------------------------ +-- Setup SAM Systems +------------------------------------------------------------------------------------------------------------------------------------------------------ + +local RED_AA_ZONES = { + ZONE:New("Red-SAM-Zone-1"), + ZONE:New("Red-SAM-Zone-2"), + ZONE:New("Red-SAM-Zone-3"), + ZONE:New("Red-SAM-Zone-4"), + ZONE:New("Red-SAM-Zone-5"), + ZONE:New("Red-SAM-Zone-6"), + ZONE:New("Red-SAM-Zone-7"), + ZONE:New("Red-SAM-Zone-8"), + ZONE:New("Red-SAM-Zone-9"), + ZONE:New("Red-SAM-Zone-10"), + ZONE:New("Red-SAM-Zone-11"), + ZONE:New("Red-SAM-Zone-12"), + ZONE:New("Red-SAM-Zone-13"), + ZONE:New("Red-SAM-Zone-14"), + ZONE:New("Red-SAM-Zone-15"), + ZONE:New("Red-SAM-Zone-16"), + ZONE:New("Red-SAM-Zone-17"), + ZONE:New("Red-SAM-Zone-18"), + ZONE:New("Red-SAM-Zone-19"), + ZONE:New("Red-SAM-Zone-20"), + ZONE:New("Red-SAM-Zone-21") + } + + -- 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 SAM 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 SAM SA10") + :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 SAM SA11") + :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 SAM SA6") + :InitRandomizeZones(RED_AA_ZONES) + :InitLimit(33, 33) + :SpawnScheduled(1800, 0.5) + + RED_SA02 = SPAWN:New("RED SAM SA2") + :InitRandomizeZones(RED_AA_ZONES) + :InitLimit(60, 60) + :SpawnScheduled(1800, 0.5) + + RED_EWR = SPAWN:New("RED EWR") + :InitRandomizeZones(RED_AA_ZONES) + :InitLimit(10, 10) + :SpawnScheduled(1800, 0.5) + + + end \ No newline at end of file diff --git a/DCS_Syria/Battle for Rayak Valley/Moose_CTLD.lua b/DCS_Syria/Battle for Rayak Valley/Moose_CTLD.lua new file mode 100644 index 0000000..540c71a --- /dev/null +++ b/DCS_Syria/Battle for Rayak Valley/Moose_CTLD.lua @@ -0,0 +1,441 @@ +--[ MOOSE CTLD v1.0] +--[ Created by: F99th-TracerFacer +--[ Date: Nov2024 + +local pointsAwardedTroopsDeployed = 1 +local pointsAwardedTroopsExtracted = 1 +local pointsAwardedTroopsPickedup = 1 +local pointsAwardedTroopsRTB = 1 +local pointsAwardedCrateDropped = 1 +local pointsAwardedCrateBuilt = 1 +local pointsAwardedCrateRepair = 1 +local msgTime = 10 + + +-- Setup CTLD for Red and Blue Coalitions +local red_helos = SET_GROUP:New():FilterCoalitions("red"):FilterCategoryHelicopter():FilterStart() +local red_ctld = CTLD:New(coalition.side.RED) +red_ctld.SetOwnSetPilotGroups(red_helos) + + +local blue_helos = SET_GROUP:New():FilterCoalitions("blue"):FilterCategoryHelicopter():FilterStart() +local blue_ctld = CTLD:New(coalition.side.BLUE) +blue_ctld.SetOwnSetPilotGroups(blue_helos) + + +red_ctld.useprefix = false -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. +red_ctld.CrateDistance = 50 -- List and Load crates in this radius only. +red_ctld.PackDistance = 50 -- Pack crates in this radius only +red_ctld.dropcratesanywhere = true -- Option to allow crates to be dropped anywhere. +red_ctld.dropAsCargoCrate = false -- Parachuted herc cargo is not unpacked automatically but placed as crate to be unpacked. Needs a cargo with the same name defined like the cargo that was dropped. +red_ctld.maximumHoverHeight = 15 -- Hover max this high to load. +red_ctld.minimumHoverHeight = 4 -- Hover min this low to load. +red_ctld.forcehoverload = false -- Crates (not: troops) can **only** be loaded while hovering. +red_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows. +red_ctld.smokedistance = 10000 -- Smoke or flares can be request for zones this far away (in meters). +red_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... +red_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) +red_ctld.suppressmessages = false -- Set to true if you want to script your own messages. +red_ctld.repairtime = 300 -- Number of seconds it takes to repair a unit. +red_ctld.buildtime = 300 -- Number of seconds it takes to build a unit. Set to zero or nil to build instantly. +red_ctld.cratecountry = country.id.GERMANY -- ID of crates. Will default to country.id.RUSSIA for RED coalition setups. +red_ctld.allowcratepickupagain = true -- allow re-pickup crates that were dropped. +red_ctld.enableslingload = true -- allow cargos to be slingloaded - might not work for all cargo types +red_ctld.pilotmustopendoors = true -- force opening of doors +red_ctld.SmokeColor = SMOKECOLOR.Red -- default color to use when dropping smoke from heli +red_ctld.FlareColor = FLARECOLOR.Red -- color to use when flaring from heli +red_ctld.basetype = "ammo_cargo" -- default shape of the cargo container +red_ctld.droppedbeacontimeout = 600 -- dropped beacon lasts 10 minutes +red_ctld.usesubcats = false -- use sub-category names for crates, adds an extra menu layer in "Get Crates", useful if you have > 10 crate types. +red_ctld.placeCratesAhead = true -- place crates straight ahead of the helicopter, in a random way. If true, crates are more neatly sorted. +red_ctld.nobuildinloadzones = true -- forbid players to build stuff in LOAD zones if set to `true` +red_ctld.movecratesbeforebuild = false -- crates must be moved once before they can be build. Set to false for direct builds. +red_ctld.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} -- surfaces for loading back objects. +red_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you. +red_ctld.RadioSound = "beaconsilent.ogg" -- -- this sound will be hearable if you tune in the beacon frequency. Add the sound file to your miz. +red_ctld.RadioSoundFC3 = "beaconsilent.ogg" -- this sound will be hearable by FC3 users (actually all UHF radios); change to something like "beaconsilent.ogg" and add the sound file to your miz if you don't want to annoy FC3 pilots. +red_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIc types for CTLD for the Chinook +red_ctld.TroopUnloadDistGround = 5 -- If hovering, spawn dropped troops this far away in meters from the helo +red_ctld.TroopUnloadDistHover = 1.5 -- If grounded, spawn dropped troops this far away in meters from the helo +red_ctld.TroopUnloadDistGroundHerc = 25 -- On the ground, unload troops this far behind the Hercules +red_ctld.TroopUnloadDistGroundHook = 15 -- On the ground, unload troops this far behind the Chinook +red_ctld.EngineerSearch = 2000 -- Search radius for engineers. + +blue_ctld.useprefix = false -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. +blue_ctld.CrateDistance = 50 -- List and Load crates in this radius only. +blue_ctld.PackDistance = 50 -- Pack crates in this radius only +blue_ctld.dropcratesanywhere = true -- Option to allow crates to be dropped anywhere. +blue_ctld.dropAsCargoCrate = false -- Parachuted herc cargo is not unpacked automatically but placed as crate to be unpacked. Needs a cargo with the same name defined like the cargo that was dropped. +blue_ctld.maximumHoverHeight = 15 -- Hover max this high to load. +blue_ctld.minimumHoverHeight = 4 -- Hover min this low to load. +blue_ctld.forcehoverload = false -- Crates (not: troops) can **only** be loaded while hovering. +blue_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows. +blue_ctld.smokedistance = 10000 -- Smoke or flares can be request for zones this far away (in meters). +blue_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... +blue_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) +blue_ctld.suppressmessages = false -- Set to true if you want to script your own messages. +blue_ctld.repairtime = 120 -- Number of seconds it takes to repair a unit. +blue_ctld.buildtime = 120 -- Number of seconds it takes to build a unit. Set to zero or nil to build instantly. +blue_ctld.cratecountry = country.id.GERMANY -- ID of crates. Will default to country.id.RUSSIA for RED coalition setups. +blue_ctld.allowcratepickupagain = true -- allow re-pickup crates that were dropped. +blue_ctld.enableslingload = true -- allow cargos to be slingloaded - might not work for all cargo types +blue_ctld.pilotmustopendoors = true -- force opening of doors +blue_ctld.SmokeColor = SMOKECOLOR.Blue -- default color to use when dropping smoke from heli +blue_ctld.FlareColor = FLARECOLOR.Blue -- color to use when flaring from heli +blue_ctld.basetype = "ammo_cargo" -- default shape of the cargo container +blue_ctld.droppedbeacontimeout = 600 -- dropped beacon lasts 10 minutes +blue_ctld.usesubcats = false -- use sub-category names for crates, adds an extra menu layer in "Get Crates", useful if you have > 10 crate types. +blue_ctld.placeCratesAhead = true -- place crates straight ahead of the helicopter, in a random way. If true, crates are more neatly sorted. +blue_ctld.nobuildinloadzones = true -- forbid players to build stuff in LOAD zones if set to `true` +blue_ctld.movecratesbeforebuild = false -- crates must be moved once before they can be build. Set to false for direct builds. +blue_ctld.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} -- surfaces for loading back objects. +blue_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you. +blue_ctld.RadioSound = "beacon.ogg" -- -- this sound will be hearable if you tune in the beacon frequency. Add the sound file to your miz. +blue_ctld.RadioSoundFC3 = "beacon.ogg" -- this sound will be hearable by FC3 users (actually all UHF radios); change to something like "beaconsilent.ogg" and add the sound file to your miz if you don't want to annoy FC3 pilots. +blue_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIc types for CTLD for the Chinook +blue_ctld.TroopUnloadDistGround = 5 -- If hovering, spawn dropped troops this far away in meters from the helo +blue_ctld.TroopUnloadDistHover = 1.5 -- If grounded, spawn dropped troops this far away in meters from the helo +blue_ctld.TroopUnloadDistGroundHerc = 25 -- On the ground, unload troops this far behind the Hercules +blue_ctld.TroopUnloadDistGroundHook = 15 -- On the ground, unload troops this far behind the Chinook +blue_ctld.EngineerSearch = 2000 -- Search radius for engineers. + +--[[] +-- Default unit type capabilities are: +["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400}, +["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400}, +["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12, cargoweightlimit = 400}, +["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400}, +["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15, cargoweightlimit = 700}, +["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, +["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, +["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0}, +["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, +["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, +["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, +["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, +["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, +["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats +["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats +["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, +["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, +["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, +["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, +["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 8000}, +--]] + +-- Start the CTLD Instances +red_ctld:Start(5) +blue_ctld:Start(5) + +-- Add Anti Tank Teams +red_ctld:AddTroopsCargo("Anti-Tank Team Small (3)",{"Red-ATS"}, CTLD_CARGO.Enum.TROOPS, 3, 80) +red_ctld:AddTroopsCargo("Anti-Tank Team Medium (10)",{"Red-ATM"}, CTLD_CARGO.Enum.TROOPS, 10, 80) + +blue_ctld:AddTroopsCargo("Anti-Tank Team Small (3)",{"Blue-ATS"}, CTLD_CARGO.Enum.TROOPS, 3, 80) +blue_ctld:AddTroopsCargo("Anti-Tank Team Medium (10)",{"Blue-ATM"}, CTLD_CARGO.Enum.TROOPS, 10, 80) + +-- Add Mortar Teams +red_ctld:AddTroopsCargo("Mortar Team (3)",{"Red-MTS"}, CTLD_CARGO.Enum.TROOPS, 3, 80) +red_ctld:AddTroopsCargo("Mortar Team (10)",{"Red-MTM"}, CTLD_CARGO.Enum.TROOPS, 10, 80) + +blue_ctld:AddTroopsCargo("Mortar Team (3)",{"Blue-MTS"}, CTLD_CARGO.Enum.TROOPS, 3, 80) +blue_ctld:AddTroopsCargo("Mortar Team (10)",{"Blue-MTM"}, CTLD_CARGO.Enum.TROOPS, 10, 80) + +-- Add Anti Air Teams +red_ctld:AddTroopsCargo("Anti-Air (4)",{"Red-AA"},CTLD_CARGO.Enum.TROOPS, 4, 80, 10) + +blue_ctld:AddTroopsCargo("Anti-Air(4)",{"Blue-AA"},CTLD_CARGO.Enum.TROOPS, 4, 80, 10) + +-- Add Engineers +--red_ctld:AddTroopsCargo("Engineer Team (3)",{"Red-Eng"},CTLD_CARGO.Enum.ENGINEERS, 3, 80) +--blue_ctld:AddTroopsCargo("Engineer Team (3)",{"Blue-Eng"},CTLD_CARGO.Enum.ENGINEERS, 3, 80) + +-- Add Ammo Trucks +red_ctld:AddCratesCargo("Ammo Truck",{"Red-Ammo"},CTLD_CARGO.Enum.VEHICLE, 2, 500, 10) +blue_ctld:AddCratesCargo("Ammo Truck",{"Blue-Ammo"},CTLD_CARGO.Enum.VEHICLE, 2, 500, 10) + +-- Add JTACs +--red_ctld:AddCratesCargo("JTAC",{"Red-JTAC"},CTLD_CARGO.Enum.VEHICLE, 1, 2775, 10) -- no soup for you commie bitches! +blue_ctld:AddCratesCargo("JTAC",{"FAC GROUND"},CTLD_CARGO.Enum.VEHICLE, 1, 500, 10) + +-- Add HUMVEE +-- blue_ctld:AddCratesCargo("HUMVEE",{"Blue-HUMVEE"},CTLD_CARGO.Enum.VEHICLE, 1, 500, 25) + +-- Add Tanks +red_ctld:AddCratesCargo("T-90",{"Red-T90"},CTLD_CARGO.Enum.VEHICLE, 1, 8500, 25) +blue_ctld:AddCratesCargo("M1A2",{"Blue-M1A2"},CTLD_CARGO.Enum.VEHICLE, 1, 8500, 25) + +-- Add FOBs +red_ctld:AddCratesCargo("Forward Ops Base",{"Red-FOB"},CTLD_CARGO.Enum.FOB, 4, 500, 3) +blue_ctld:AddCratesCargo("Forward Ops Base",{"Blue-FOB"},CTLD_CARGO.Enum.FOB, 4, 500, 3) + +-- AA Crates +red_ctld:AddCratesCargo("SA-8",{"SA8"},CTLD_CARGO.Enum.CRATE, 4, 500, 10) +red_ctld:AddCratesCargo("SA-6",{"SA6"},CTLD_CARGO.Enum.CRATE, 4, 500, 10) +red_ctld:AddCratesCargo("SA-10",{"SA10"},CTLD_CARGO.Enum.CRATE, 6, 500, 10) + +blue_ctld:AddCratesCargo("Linebacker",{"LINEBACKER"},CTLD_CARGO.Enum.CRATE, 2, 500, 5) +blue_ctld:AddCratesCargo("Hawk Site",{"HAWK"},CTLD_CARGO.Enum.CRATE, 4, 500, 4) +blue_ctld:AddCratesCargo("Patriot Site",{"PATRIOT"},CTLD_CARGO.Enum.CRATE, 12, 500, 4) + + + + +-- Add 6 Red Load Zones that are active but have no beacon. +red_ctld:AddCTLDZone("RedLoadZone1", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Red, true, false) +red_ctld:AddCTLDZone("RedLoadZone2", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Red, true, false) +red_ctld:AddCTLDZone("RedLoadZone3", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Red, true, false) +red_ctld:AddCTLDZone("RedLoadZone4", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Red, true, false) +red_ctld:AddCTLDZone("RedLoadZone5", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Red, true, false) +red_ctld:AddCTLDZone("RedLoadZone6", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Red, true, false) + +-- Add 6 Blue Load Zones that are active but have no beacon. +blue_ctld:AddCTLDZone("BlueLoadZone1", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Blue, true, false) +blue_ctld:AddCTLDZone("BlueLoadZone2", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Blue, true, false) +blue_ctld:AddCTLDZone("BlueLoadZone3", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Blue, true, false) +blue_ctld:AddCTLDZone("BlueLoadZone4", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Blue, true, false) +blue_ctld:AddCTLDZone("BlueLoadZone5", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Blue, true, false) +blue_ctld:AddCTLDZone("BlueLoadZone6", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Blue, true, false) +blue_ctld:AddCTLDZone("BlueLoadZone7", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Blue, true, false) +blue_ctld:AddCTLDZone("BlueLoadZone8", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Blue, true, false) +blue_ctld:AddCTLDZone("BlueLoadZone9", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Blue, true, false) +blue_ctld:AddCTLDZone("BlueLoadZone10", CTLD.CargoZoneType.LOAD, SMOKECOLOR.Blue, true, false) + + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- DO NOT EDIT BELOW THIS LINE +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +function blue_ctld:OnAfterTroopsDeployed(From,Event,To,Group,Unit,Troops) + if Unit then + local PlayerName = Unit:GetPlayerName() + local vname = Troops:GetName() + local points = pointsAwardedTroopsDeployed + MESSAGE:New("Pilot " .. PlayerName .. " has deployed " .. vname .. " to the field!", msgTime, "[ Mission Info ]", false):ToBlue() + USERSOUND:New("combatAudio5.ogg"):ToCoalition(coalition.side.BLUE) + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for deploying troops!", PlayerName, points), points) + end + end + + function blue_ctld:OnAfterTroopsExtracted(From,Event,To,Group,Unit,Cargo) + if Unit then + local PlayerName = Unit:GetPlayerName() + local vname = Cargo:GetName() + local points = pointsAwardedTroopsExtracted + MESSAGE:New("Pilot " .. PlayerName .. " has extracted " .. vname .. " from the field!", msgTime, "[ Mission Info ]", false):ToBlue() + USERSOUND:New("getToTheChoppa.ogg"):ToCoalition(coalition.side.BLUE) + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for extracting troops!", PlayerName, points), points) + end + end + + function blue_ctld:OnAfterTroopsPickedUp(From,Event,To,Group,Unit,Cargo) + if Unit then + local PlayerName = Unit:GetPlayerName() + local vname = Cargo:GetName() + local points = pointsAwardedTroopsPickedup + MESSAGE:New("Pilot " .. PlayerName .. " has picked up " .. vname .. " from a supply base!", msgTime, "[ Mission Info ]", false):ToBlue() + USERSOUND:New("JoinTheArmy.ogg"):ToCoalition(coalition.side.BLUE) + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for picking up troops!", PlayerName, points), points) + end + end + + function blue_ctld:OnAfterTroopsRTB(From,Event,To,Group,Unit) + if Unit then + local PlayerName = Unit:GetPlayerName() + local points = pointsAwardedTroopsRTB + MESSAGE:New("Pilot " .. PlayerName .. " returned troops to home base!", msgTime, "[ Mission Info ]", false):ToBlue() + USERSOUND:New("cheering.ogg"):ToCoalition(coalition.side.BLUE) + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for returning troops!", PlayerName, points), points) + end + end + + + -- Scoring and messaging + function blue_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) + if Unit then + local points = pointsAwardedCrateDropped + local PlayerName = Unit:GetPlayerName() + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) + end + end + + + function blue_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) + if Unit then + local points = pointsAwardedCrateBuilt + local PlayerName = Unit:GetPlayerName() + local vname = Vehicle:GetName() + + USERSOUND:New("construction.ogg"):ToCoalition(coalition.side.BLUE) + MESSAGE:New("Pilot " .. PlayerName .. " has deployed " .. vname .. " to the field!", msgTime, "[ Mission Info ]", false):ToBlue() + US_Score:_AddPlayerFromUnit(Unit) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the construction of Units!", PlayerName, points), points) + + -- Debugging information + env.info("DEBUG: OnAfterCratesBuild called for Unit: " .. PlayerName .. ", Vehicle: " .. vname) + + -- Is this a FOB being built? If so add a Load Zone around the deployed crate. + env.info("CRATEBUILD: Is this a fob?: " .. vname, false) + if string.match(vname, "FOB", 1, true) then + env.info("CRATEBUILD: Yes, this is a FOB, building: " .. vname, false) + local Coord = Vehicle:GetCoordinate():GetVec2() + local mCoord = Vehicle:GetCoordinate() + local zonename = "FOB-" .. math.random(1, 10000) + local fobzone = ZONE_RADIUS:New(zonename, Coord, 1000) + local fobmarker = MARKER:New(mCoord, "FORWARD OPERATING BASE:\nBUILT BY: " .. PlayerName .. "\n\nTransport Helos may pick up troops and equipment from this location."):ReadOnly():ToCoalition(coalition.side.BLUE) + fobzone:DrawZone(2, {.25, .63, .79}, 1, {0, 0, 0}, 0.25, 2, true) + blue_ctld:AddCTLDZone(zonename, CTLD.CargoZoneType.LOAD, SMOKECOLOR.Blue, true, true) + MESSAGE:New("Pilot " .. PlayerName .. " has created a new loading zone for troops and equipment! See your F10 Map for marker!", msgTime, "[ Mission Info ]", false):ToBlue() + else + env.info("CRATEBUILD: No! Not a FOB: " .. vname, false) + end + end + end + + function blue_ctld:OnBeforeCratesRepaired(From, Event, To, Group, Unit, Vehicle) + if Unit then + local points = pointsAwardedCrateRepair + local GroupCategory = Group:GetCategoryName() + local PlayerName = Unit:GetPlayerName() + + MESSAGE:New("Pilot " .. PlayerName .. " has started repairs on " .. GroupCategory .. "! Nice Job!", msgTime, "[ Mission Info ]", false):ToBlue() + + end + end + + function blue_ctld:OnAfterCratesRepaired(From, Event, To, Group, Unit, Vehicle) + if Unit then + local points = pointsAwardedCrateRepair + local PlayerName = Unit:GetPlayerName() + USERSOUND:New("repair.ogg"):ToCoalition(coalition.side.BLUE) + MESSAGE:New("Pilot " .. PlayerName .. " has conducted repears on " .. Vehicle "! Nice Job!", msgTime, "[ Mission Info ]", false):ToRed() + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the repair of Units!", PlayerName, points), points) + end + end + + + + ------------------------------------------------------------------------------------------------------------------------------------------------------------ + -- Red CTLD Functions + ------------------------------------------------------------------------------------------------------------------------------------------------------------ + + function red_ctld:OnAfterTroopsDeployed(From,Event,To,Group,Unit,Troops) + if Unit then + local PlayerName = Unit:GetPlayerName() + local vname = Troops:GetName() + local points = pointsAwardedTroopsDeployed + MESSAGE:New("Pilot " .. PlayerName .. " has deployed " .. vname .. " to the field!", msgTime, "[ Mission Info ]", false):ToRed() + USERSOUND:New("combatAudio5.ogg"):ToCoalition(coalition.side.RED) + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for deploying troops!", PlayerName, points), points) + end + end + + function red_ctld:OnAfterTroopsExtracted(From,Event,To,Group,Unit,Cargo) + if Unit then + local PlayerName = Unit:GetPlayerName() + local vname = Cargo:GetName() + local points = pointsAwardedTroopsExtracted + MESSAGE:New("Pilot " .. PlayerName .. " has extracted " .. vname .. " from the field!", msgTime, "[ Mission Info ]", false):ToRed() + USERSOUND:New("getToTheChoppa.ogg"):ToCoalition(coalition.side.RED) + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for extracting troops!", PlayerName, points), points) + end + end + + function red_ctld:OnAfterTroopsPickedUp(From,Event,To,Group,Unit,Cargo) + if Unit then + local PlayerName = Unit:GetPlayerName() + local vname = Cargo:GetName() + local points = pointsAwardedTroopsPickedup + MESSAGE:New("Pilot " .. PlayerName .. " has picked up " .. vname .. " from a supply base!", msgTime, "[ Mission Info ]", false):ToRed() + USERSOUND:New("JoinTheArmy.ogg"):ToCoalition(coalition.side.RED) + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for picking up troops!", PlayerName, points), points) + end + end + + function red_ctld:OnAfterTroopsRTB(From,Event,To,Group,Unit) + if Unit then + local PlayerName = Unit:GetPlayerName() + local points = pointsAwardedTroopsRTB + MESSAGE:New("Pilot " .. PlayerName .. " returned troops to home base!", msgTime, "[ Mission Info ]", false):ToRed() + USERSOUND:New("cheering.ogg"):ToCoalition(coalition.side.RED) + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for returning troops!", PlayerName, points), points) + end + end + + + -- Scoring and messaging + function red_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) + if Unit then + local points = pointsAwardedCrateDropped + local PlayerName = Unit:GetPlayerName() + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) + end + end + + + function red_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) + if Unit then + local points = pointsAwardedCrateBuilt + local PlayerName = Unit:GetPlayerName() + local vname = Vehicle:GetName() + + USERSOUND:New("construction.ogg"):ToCoalition(coalition.side.RED) + MESSAGE:New("Pilot " .. PlayerName .. " has deployed " .. vname .. " to the field!", msgTime, "[ Mission Info ]", false):ToRed() + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the construction of Units!", PlayerName, points), points) + + -- Debugging information + env.info("DEBUG: OnAfterCratesBuild called for Unit: " .. PlayerName .. ", Vehicle: " .. vname) + + -- Is this a FOB being built? If so add a Load Zone around the deployed crate. + env.info("CRATEBUILD: Is this a fob?: " .. vname,false) + if string.match(vname,"FOB",1,true) then + env.info("CRATEBUILD: Yes, this is a FOB, building: " .. vname,false) + local Coord = Vehicle:GetCoordinate():GetVec2() + local mCoord = Vehicle:GetCoordinate() + local zonename = "FOB-" .. math.random(1,10000) + local fobzone = ZONE_RADIUS:New(zonename,Coord,1000) + local fobmarker = MARKER:New(mCoord, "FORWARD OPERATING BASE:\nBUILT BY: " .. PlayerName .. "\n\nTransport Helos may pick up troops and equipment from this location."):ReadOnly():ToCoalition(coalition.side.RED) + fobzone:DrawZone(2,{.25,.63,.79},1,{0,0,0},0.25,2,true) + red_ctld:AddCTLDZone(zonename,CTLD.CargoZoneType.LOAD,SMOKECOLOR.Red,true,true) + MESSAGE:New("Pilot " .. PlayerName .. " has created a new loading zone for troops and equipment! See your F10 Map for marker!", msgTime, "[ Mission Info ]", false):ToRed() + else + env.info("CRATEBUILD: No! Not a FOB: " .. vname,false) + end + + end + end + + function red_ctld:OnBeforeCratesRepaired(From, Event, To, Group, Unit, Vehicle) + if Unit then + local points = pointsAwardedCrateRepair + local GroupCategory = Group:GetCategoryName() + local PlayerName = Unit:GetPlayerName() + + MESSAGE:New("Pilot " .. PlayerName .. " has started repairs on " .. GroupCategory .. "! Nice Job!", msgTime, "[ Mission Info ]", false):ToRed() + + end + end + + function red_ctld:OnAfterCratesRepaired(From, Event, To, Group, Unit, Vehicle) + if Unit then + local points = pointsAwardedCrateRepair + local PlayerName = Unit:GetPlayerName() + USERSOUND:New("repair.ogg"):ToCoalition(coalition.side.RED) + MESSAGE:New("Pilot " .. PlayerName .. " has conducted repears on " .. Vehicle "! Nice Job!", msgTime, "[ Mission Info ]", false):ToRed() + US_Score:_AddPlayerFromUnit( Unit ) + US_Score:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the repair of Units!", PlayerName, points), points) + end + end diff --git a/DCS_Syria/Battle for Rayak Valley/Moose_DroneOps.lua b/DCS_Syria/Battle for Rayak Valley/Moose_DroneOps.lua new file mode 100644 index 0000000..5bb9a55 --- /dev/null +++ b/DCS_Syria/Battle for Rayak Valley/Moose_DroneOps.lua @@ -0,0 +1,20 @@ +-- Define the drone using the MOOSE SPAWN class +Blue_Drone = SPAWN:New("FAC DRONE") + :InitLimit(1, 99) + :InitRepeatOnLanding() + :SpawnScheduled(1, 0.5) + + +-- Define FAC Set +BlueFACSet = SET_GROUP:New():FilterPrefixes("FAC"):FilterStart() +BlueDetectionSet = DETECTION_AREAS:New(BlueFACSet, 5000) +BlueAttackSet = SET_GROUP:New():FilterCoalitions("blue"):FilterStart() +BlueDesignator = DESIGNATE:New(US_CC, BlueDetectionSet, BlueAttackSet) +BlueDesignator:SetAutoLase(false) +BlueDesignator:SetThreatLevelPrioritization(true) +BlueDesignator:GenerateLaserCodes(true) + + + + + diff --git a/DCS_Syria/Battle for Rayak Valley/Moose_DynamicGroundBattle.lua b/DCS_Syria/Battle for Rayak Valley/Moose_DynamicGroundBattle.lua new file mode 100644 index 0000000..236f50f --- /dev/null +++ b/DCS_Syria/Battle for Rayak Valley/Moose_DynamicGroundBattle.lua @@ -0,0 +1,1039 @@ +--[[ + Script: Moose_DynamicGroundBattle.lua + Written by: [F99th-TracerFacer] + Version: 1.0.3 + Date: 11 November 2024 + Updated: 12 November 2024 + Description: This script creates a dynamic ground battle between Red and Blue coalitions + along a series of zones which can be arranged in a line or any other configuration creating a dynamic ground battle. + + Capture Zone Behavior + - Zone Capture states: Captured, Guarded, Empty, Attacked, Neutral + - Zone Colors: Red, Blue, Green, Orange + Red: Captured by Red + Blue: Captured by Blue + Orange: Contested + Green: Empty + + Spawning And Patrol Behavior: + - Infantry and armor groups for both sides spawn at random locations in their own zones. + - Each group then calculates the shortest distance to the nearest enemy zone and moves to that zone to patrol. + - Every ASSIGN_TASKS_SCHED seconds, the script will check the ZONE_CAPTURE states of all zones and assign tasks to groups accordingly. + - Any group NOT moving, will recieve orders to patrol the nearest enemy zone. Any unit already moving will be left alone. + - Any troops dropped off through CTLD in these zones will begin to obey these orders as well. + - Spawn frequency calculated based on the number of alive warehouses. + - Infantry can be disabled from moving patrols if desired. + - In the event of DCS assigning a ridiculous path to an object, simply stop the object and it will be reassigned a new patrol path next round. + + Warehouse System & Spawn Frequencey Behavior: + 1. Warehouses: + - Each side (Red and Blue) has a set of warehouses defined in the `redWarehouses` and `blueWarehouses` tables. + - The number of warehouses can be adjusted by adding or removing entries from these tables and ensuring there is a matching object in the mission editor. + + 2. Spawn Frequency Calculation: + - The function `CalculateSpawnFrequency` calculates the spawn frequency based on the number of alive warehouses. + - The spawn frequency is a ratio of alive warehouses to total warehouses. + - If all warehouses are alive, the spawn frequency is (100%) of the base setting. + - If half of the warehouses are alive, the spawn frequency is (50%). + - If no warehouses are alive, the spawn frequency is (0%) (no more spawns). + - So for example, if you set your spawn frequency to 300 seconds, and only 50% of your warehouses are alive, the actual spawn frequency will be 600 seconds. + - This dynamic adjustment ensures that the reinforcement rate is directly impacted by the number of operational warehouses. + + 3. Mark points are automatically added to the map for each warehouse. + - Include the warehouse name and a list of nearby ground units within a specified radius. + - Uppdated every `UPDATE_MARK_POINTS_SCHED` seconds. + - The maximum distance to search for units near a warehouse is defined by the `MAX_WAREHOUSE_UNIT_LIST_DISTANCE` variable. + - The mark points are displayed to all players on the map as a form of "intel" on the battlefield. + - Can be disabled by setting `ENABLE_WAREHOUSE_MARKERS` to false. + + General Setup Requirements: + - The script relies on the MOOSE framework for DCS World. Ensure that the MOOSE framework is installed and running on the mission. + - Ensure that all groups and zones mentioned below are created in the mission editor. You can adjust the names of the groups and zones as needed or + add more groups and zones to the script. Ensure that the names in this script match the names in the mission editor. + + + Groups and Zones to be created in the editor (all LATE ACTIVATE): + - Red Infantry Groups: RedInfantry1, RedInfantry2, RedInfantry3, RedInfantry4, RedInfantry5, RedInfantry6 + - Red Armor Groups: RedArmor1, RedArmor2, RedArmor3, RedArmor4, RedArmor5, RedArmor6 + - Blue Infantry Groups: BlueInfantry1, BlueInfantry2, BlueInfantry3, BlueInfantry4, BlueInfantry5, BlueInfantry6 + - Blue Armor Groups: BlueArmor1, BlueArmor2, BlueArmor3, BlueArmor4, BlueArmor5 + + - Red Zones: FrontLine1, FrontLine2, FrontLine3, FrontLine4, FrontLine5, FrontLine6 + - Blue Zones: FrontLine7, FrontLine8, FrontLine9, FrontLine10, FrontLine11, FrontLine12 + + - Red Warehouses: RedWarehouse1-1, RedWarehouse2-1, RedWarehouse3-1, RedWarehouse4-1, RedWarehouse5-1, RedWarehouse6-1 + - Blue Warehouses: BlueWarehouse1-1, BlueWarehouse2-1, BlueWarehouse3-1, BlueWarehouse4-1, BlueWarehouse5-1, BlueWarehouse6-1 + - ** Note Warehouse names are based on the static "unit name" in the mission editor. ** + +--]] + +--[[ + --If you don't have command centers setup in another file, uncommnent this section below: + -- Create Command Centers and Missions for each side + -- Must have a blue unit named "BLUEHQ" and a red unit named "REDHQ" in the mission editor. + + --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) + +]] + +-- Infantry Patrol Settings +-- Due to some maps or locations where infantry moving is either not desired or has problems with the terrain you can disable infantry moving patrols. +-- Set to false, infantry units will spawn, and never move from their spawn location. This could be considered a defensive position and probably a good idea. +local ENABLE_CAPTURE_ZONE_MESSAGES = false -- Enable or disable attack messages when a zone is attacked. +local MOVING_INFANTRY_PATROLS = false -- Units with infantry will not move to patrol zones if set to false. (default is false, to many moving units can cause performance issues) +local ENABLE_WAREHOUSE_MARKERS = true -- Enable or disable the warehouse markers on the map. +local UPDATE_MARK_POINTS_SCHED = 60 -- Update the map markers for warehouses every 60 seconds. ENABLE_WAREHOUSE_MARKERS must be set to true for this to work. + +-- Control Spawn frequency and limits of ground units. +local INIT_RED_INFANTRY = 25 -- Initial number of Red Infantry groups +local MAX_RED_INFANTRY = 100 -- Maximum number of Red Infantry groups +local SPAWN_SCHED_RED_INFANTRY = 1800 -- Spawn Red Infantry groups every 1800 seconds + +local INIT_RED_ARMOR = 15 -- Initial number of Red Armor groups +local MAX_RED_ARMOR = 200 -- Maximum number of Red Armor groups +local SPAWN_SCHED_RED_ARMOR = 600 -- Spawn Red Armor groups every 300 seconds + +local INIT_BLUE_INFANTRY = 25 -- Initial number of Blue Infantry groups +local MAX_BLUE_INFANTRY = 100 -- Maximum number of Blue Infantry groups +local SPAWN_SCHED_BLUE_INFANTRY = 1800 -- Spawn Blue Infantry groups every 1800 seconds + +local INIT_BLUE_ARMOR = 15 -- Initial number of Blue Armor groups0 +local MAX_BLUE_ARMOR = 200 -- Maximum number of Blue Armor groups +local SPAWN_SCHED_BLUE_ARMOR = 600 -- Spawn Blue Armor groups every 300 seconds + +local ASSIGN_TASKS_SCHED = 900 -- Assign tasks to groups every 600 seconds. New groups added will wait this long before moving. + +-- !!!! REQUIRED !!!!! +-- Place in the mission an infantry unit called "RedInfantryGroup" and "BlueInfantryGroup" for each side. Set to LATE ACTIVATE. +-- Place in the mission an armor unit called "RedArmorGroup" and "BlueArmorGroup" for each side. Set to LATE ACTIVATE. + + +-- Define capture zones for each side with a visible radius. +-- These zones will be used to create capture zones for each side. The capture zones will be used to determine the state of each zone (captured, guarded, empty, attacked, neutral). +-- The zones will also be used to spawn ground units for each side. +-- The zones should be created in the mission editor and named accordingly. +-- You can add more zones as needed. The script will create capture zones for each zone and assign tasks to groups based on the zone states. +-- Maybe the zones are along a front line, or they follow a road, or they are scattered around the map. You can arrange the zones in any configuration you like. + +local redZones = { + ZONE:New("FrontLine-21"), + ZONE:New("FrontLine-22"), + ZONE:New("FrontLine-23"), + ZONE:New("FrontLine-24"), + ZONE:New("FrontLine-25"), + ZONE:New("FrontLine-26"), + ZONE:New("FrontLine-27"), + ZONE:New("FrontLine-28"), + ZONE:New("FrontLine-29"), + ZONE:New("FrontLine-30"), + ZONE:New("FrontLine-31"), + ZONE:New("FrontLine-32"), + ZONE:New("FrontLine-33"), + ZONE:New("FrontLine-34"), + ZONE:New("FrontLine-35"), + ZONE:New("FrontLine-36"), + ZONE:New("FrontLine-37"), + ZONE:New("FrontLine-38"), + ZONE:New("FrontLine-39"), + ZONE:New("FrontLine-40") +} + +local blueZones = { + ZONE:New("FrontLine-1"), + ZONE:New("FrontLine-2"), + ZONE:New("FrontLine-3"), + ZONE:New("FrontLine-4"), + ZONE:New("FrontLine-5"), + ZONE:New("FrontLine-6"), + ZONE:New("FrontLine-7"), + ZONE:New("FrontLine-8"), + ZONE:New("FrontLine-9"), + ZONE:New("FrontLine-10"), + ZONE:New("FrontLine-11"), + ZONE:New("FrontLine-12"), + ZONE:New("FrontLine-13"), + ZONE:New("FrontLine-14"), + ZONE:New("FrontLine-15"), + ZONE:New("FrontLine-16"), + ZONE:New("FrontLine-17"), + ZONE:New("FrontLine-18"), + ZONE:New("FrontLine-19"), + ZONE:New("FrontLine-20") +} + +-- Define warehouses for each side. These warehouses will be used to calculate the spawn frequency of ground units. +-- The warehouses should be created in the mission editor and named accordingly. +local redWarehouses = { + STATIC:FindByName("RedWarehouse-1-1"), -- Static units key of off unit name in mission editor rather than just the name field. weird. =\ (hours wasted! ha!) + STATIC:FindByName("RedWarehouse-2-1"), + STATIC:FindByName("RedWarehouse-3-1"), + STATIC:FindByName("RedWarehouse-4-1"), + STATIC:FindByName("RedWarehouse-5-1"), + STATIC:FindByName("RedWarehouse-6-1"), + STATIC:FindByName("RedWarehouse-7-1"), + STATIC:FindByName("RedWarehouse-8-1"), + STATIC:FindByName("RedWarehouse-9-1"), + STATIC:FindByName("RedWarehouse-10-1"), + STATIC:FindByName("RedWarehouse-11-1"), + STATIC:FindByName("RedWarehouse-12-1"), + STATIC:FindByName("RedWarehouse-13-1"), + STATIC:FindByName("RedWarehouse-14-1"), + STATIC:FindByName("RedWarehouse-15-1") + +} + +local blueWarehouses = { + STATIC:FindByName("BlueWarehouse-1-1"), + STATIC:FindByName("BlueWarehouse-2-1"), + STATIC:FindByName("BlueWarehouse-3-1"), + STATIC:FindByName("BlueWarehouse-4-1"), + STATIC:FindByName("BlueWarehouse-5-1"), + STATIC:FindByName("BlueWarehouse-6-1") + +} + +-- Define templates for infantry and armor groups. These templates will be used to randomize the groups spawned in the zones. +-- The templates should be created in the mission editor and named accordingly. +-- You can add more templates as needed. The script will randomly select a template for each group spawned. +-- The more templates you make, the more variety you can add to the groups that are spawned. +local redInfantryTemplates = { + "RedInfantry1", + "RedInfantry2", + "RedInfantry3", + +} + +local redArmorTemplates = { + "RedArmor1", + "RedArmor2", + "RedArmor3", + "RedArmor4", + "RedArmor5", + "RedArmor6", + "RedArmor7", + "RedArmor8", + "RedArmor9", + "RedArmor10" + +} + +local blueInfantryTemplates = { + "BlueInfantry1", + "BlueInfantry2", + "BlueInfantry3" +} + +local blueArmorTemplates = { + "BlueArmor1", + "BlueArmor2", + "BlueArmor3", + "BlueArmor4", + "BlueArmor5", + "BlueArmor6" +} + +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- DO NOT EDIT BELOW THIS LINE +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Function to handle warehouse destruction +local function onWarehouseDestroyed(warehouseName, coalition) + local message = string.format("%s warehouse %s has been destroyed!", coalition, warehouseName) + MESSAGE:New(message, 15):ToAll() + USERSOUND:New("beeps-and-clicks.wav"):ToAll() + env.info(message) +end + +-- Create an event handler class +local WarehouseEventHandler = EVENTHANDLER:New() + +-- Define the event handler function +function WarehouseEventHandler:OnEventDead(EventData) + env.info("OnEventDead triggered") + if EventData then + local unitName = EventData.IniUnitName or EventData.IniDCSUnitName + if unitName then + + -- Check red warehouses + for _, warehouse in ipairs(redWarehouses) do + if warehouse:GetName() == unitName then + onWarehouseDestroyed(unitName, "Red") + return + end + end + + -- Check blue warehouses + for _, warehouse in ipairs(blueWarehouses) do + if warehouse:GetName() == unitName then + onWarehouseDestroyed(unitName, "Blue") + return + end + end + + local notWarehouseMessage = "Destroyed unit is not a warehouse: " .. unitName + env.info(notWarehouseMessage) + else + local noUnitMessage = "No unit name available in EventData" + env.info(noUnitMessage) + end + else + env.info("EventData is nil") + end +end + +-- Set up the event handler globally +WarehouseEventHandler:HandleEvent(EVENTS.Dead) + +-- Function to add mark points on the map for each warehouse in the provided list +local function addMarkPoints(warehouses, coalition) + for _, warehouse in ipairs(warehouses) do + if warehouse then + local warehousePos = warehouse:GetVec3() + local details + if warehouse:IsAlive() then + if coalition == 2 then + if warehouse:GetCoalition() == 2 then + details = "Warehouse: " .. warehouse:GetName() .. "\nThis warehouse needs to be protected.\n" + else + details = "Warehouse: " .. warehouse:GetName() .. "\nThis is a primary target as it is directly supplying enemy units.\n" + end + elseif coalition == 1 then + if warehouse:GetCoalition() == 1 then + details = "Warehouse: " .. warehouse:GetName() .. "\nThis warehouse needs to be protected.\nNearby Units:\n" + else + details = "Warehouse: " .. warehouse:GetName() .. "\nThis is a primary target as it is directly supplying enemy units.\n" + end + end + else + details = "Warehouse: " .. warehouse:GetName() .. "\nTHIS TARGET HAS BEEN DESTROYED" + end + + local coordinate = COORDINATE:NewFromVec3(warehousePos) + local marker = MARKER:New(coordinate, details):ToCoalition(coalition):ReadOnly() + marker:Remove(UPDATE_MARK_POINTS_SCHED) + else + env.info("addMarkPoints: Warehouse not found or is nil") + end + end +end + +local function updateMarkPoints() + addMarkPoints(redWarehouses, 2) -- Blue coalition sees red warehouses as targets + addMarkPoints(blueWarehouses, 2) -- Blue coalition sees blue warehouses as needing protection + addMarkPoints(redWarehouses, 1) -- Red coalition sees red warehouses as needing protection + addMarkPoints(blueWarehouses, 1) -- Red coalition sees blue warehouses as targets +end + +-- If enabled, update the mark points for the warehouses every UPDATE_MARK_POINTS_SCHED seconds. +if ENABLE_WAREHOUSE_MARKERS then + SCHEDULER:New(nil, updateMarkPoints, {}, 10, UPDATE_MARK_POINTS_SCHED) +end + +-- Table to keep track of zones and their statuses +local zoneStatuses = {} + +-- Function to create a capture zone +local function CreateCaptureZone(zone, coalition) + local captureZone = ZONE_CAPTURE_COALITION:New(zone, coalition) + if captureZone then + local coordinate = captureZone:GetCoordinate() + if coordinate then + env.info("Created capture zone at coordinates: " .. coordinate:ToStringLLDMS()) + captureZone:Start(5, 30) -- Check every 5 seconds, capture after 30 seconds + else + env.error("Failed to get coordinates for zone: " .. zone:GetName() .. " Did you add the group to the editor?") + end + else + env.error("Failed to create capture zone for zone: " .. zone:GetName() .. " Did you add the group to the editor?") + end + return captureZone +end + +-- Custom OnEnterCaptured method +--- @param Functional.Protect#ZONE_CAPTURE_COALITION self +function ZONE_CAPTURE_COALITION:OnEnterCaptured(From, Event, To) + if From ~= To then + local Coalition = self:GetCoalition() + self:E({ Coalition = Coalition }) + local zoneName = self:GetZoneName() + zoneStatuses[zoneName] = { zone = self, coalition = Coalition } + if Coalition == coalition.side.BLUE then + self:Smoke(SMOKECOLOR.Blue) + self:UndrawZone() + self:DrawZone(-1, {0, 0, 1}, 2) -- Draw the zone on the map for 30 seconds, blue color, and thickness 2 + --US_CC:MessageTypeToCoalition(string.format("%s has been captured by the USA", self:GetZoneName()), MESSAGE.Type.Information) + --RU_CC:MessageTypeToCoalition(string.format("%s has been captured by the USA", self:GetZoneName()), MESSAGE.Type.Information) + else + self:Smoke(SMOKECOLOR.Red) + self:UndrawZone() + self:DrawZone(-1, {1, 0, 0}, 2) -- Draw the zone on the map for 30 seconds, red color, and thickness 2 + --RU_CC:MessageTypeToCoalition(string.format("%s has been captured by Russia", self:GetZoneName()), MESSAGE.Type.Information) + --US_CC:MessageTypeToCoalition(string.format("%s has been captured by Russia", self:GetZoneName()), MESSAGE.Type.Information) + end + end +end + +-- Custom OnEnterGuarded method +--- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self +function ZONE_CAPTURE_COALITION:OnEnterGuarded(From, Event, To) + if From ~= To then + local Coalition = self:GetCoalition() + self:E({ Coalition = Coalition }) + local zoneName = self:GetZoneName() + zoneStatuses[zoneName] = { zone = self, coalition = Coalition } + if Coalition == coalition.side.BLUE then + self:Smoke(SMOKECOLOR.Blue) + -- Draw zone DARK BLUE for guarded + self:UndrawZone() + self:DrawZone(-1, {0, 0, 0.5}, 2) -- Draw the zone on the map for 30 seconds, dark blue color, and thickness 2 + if ENABLE_CAPTURE_ZONE_MESSAGES then + --US_CC:MessageTypeToCoalition(string.format("%s is under protection of the USA", self:GetZoneName()), MESSAGE.Type.Information) + --RU_CC:MessageTypeToCoalition(string.format("%s is under protection of the USA", self:GetZoneName()), MESSAGE.Type.Information) + end + else + self:Smoke(SMOKECOLOR.Red) + -- Draw zone DARK RED for guarded + self:UndrawZone() + self:DrawZone(-1, {0.5, 0, 0}, 2) -- Draw the zone on the map for 30 seconds, dark red color, and thickness 2 + if ENABLE_CAPTURE_ZONE_MESSAGES then + --RU_CC:MessageTypeToCoalition(string.format("%s is under protection of Russia", self:GetZoneName()), MESSAGE.Type.Information) + --US_CC:MessageTypeToCoalition(string.format("%s is under protection of Russia", self:GetZoneName()), MESSAGE.Type.Information) + end + end + end +end + +-- Custom OnEnterEmpty method +--- @param Functional.Protect#ZONE_CAPTURE_COALITION self +function ZONE_CAPTURE_COALITION:OnEnterEmpty(From, Event, To) + if From ~= To then + self:E({ Coalition = "None" }) + local zoneName = self:GetZoneName() + zoneStatuses[zoneName] = { zone = self, coalition = "None" } + self:Smoke(SMOKECOLOR.Green) + self:UndrawZone() + self:DrawZone(-1, {0, 1, 0}, 2) -- Draw the zone on the map for 30 seconds, green color, and thickness 2 + if ENABLE_CAPTURE_ZONE_MESSAGES then + --US_CC:MessageTypeToCoalition(string.format("%s is now empty", self:GetZoneName()), MESSAGE.Type.Information) + --RU_CC:MessageTypeToCoalition(string.format("%s is now empty", self:GetZoneName()), MESSAGE.Type.Information) + end + end +end + +-- Custom OnEnterAttacked method +--- @param Functional.Protect#ZONE_CAPTURE_COALITION self +function ZONE_CAPTURE_COALITION:OnEnterAttacked(From, Event, To) + if From ~= To then + local Coalition = self:GetCoalition() + self:E({ Coalition = Coalition }) + local zoneName = self:GetZoneName() + zoneStatuses[zoneName] = { zone = self, coalition = Coalition } + if Coalition == coalition.side.BLUE then + self:Smoke(SMOKECOLOR.Blue) + -- Draw the zone orange for contested + self:UndrawZone() + self:DrawZone(-1, {1, 0.5, 0}, 2) -- Draw the zone on the map for 30 seconds, orange color, and thickness 2 + if ENABLE_CAPTURE_ZONE_MESSAGES then + --US_CC:MessageTypeToCoalition(string.format("%s is under attack by Russia", self:GetZoneName()), MESSAGE.Type.Information) + --RU_CC:MessageTypeToCoalition(string.format("%s is attacking the USA", self:GetZoneName()), MESSAGE.Type.Information) + end + else + self:Smoke(SMOKECOLOR.Red) + self:UndrawZone() + self:DrawZone(-1, {1, 0.5, 0}, 2) -- Draw the zone on the map for 30 seconds, orange color, and thickness 2 + if ENABLE_CAPTURE_ZONE_MESSAGES then + --RU_CC:MessageTypeToCoalition(string.format("%s is under attack by the USA", self:GetZoneName()), MESSAGE.Type.Information) + --US_CC:MessageTypeToCoalition(string.format("%s is attacking Russia", self:GetZoneName()), MESSAGE.Type.Information) + end + end + end +end + +-- Custom OnEnterNeutral method +--- @param Functional.Protect#ZONE_CAPTURE_COALITION self +function ZONE_CAPTURE_COALITION:OnEnterNeutral(From, Event, To) + if From ~= To then + self:E({ Coalition = "Neutral" }) + local zoneName = self:GetZoneName() + zoneStatuses[zoneName] = { zone = self, coalition = "Neutral" } + self:Smoke(SMOKECOLOR.Green) + self:UndrawZone() + self:DrawZone(-1, {0, 1, 0}, 2) -- Draw the zone on the map for 30 seconds, green color, and thickness 2 + if ENABLE_CAPTURE_ZONE_MESSAGES then + --US_CC:MessageTypeToCoalition(string.format("%s is now neutral", self:GetZoneName()), MESSAGE.Type.Information) + --RU_CC:MessageTypeToCoalition(string.format("%s is now neutral", self:GetZoneName()), MESSAGE.Type.Information) + end + end +end + +-- Create capture zones for Red and Blue +local redCaptureZones = {} +local blueCaptureZones = {} + +-- Iterate over all red zones to create capture zones +for _, zone in ipairs(redZones) do + -- Attempt to create a capture zone for the current red zone + local captureZone = CreateCaptureZone(zone, coalition.side.RED) + if captureZone then + -- If successful, add the capture zone to the redCaptureZones table + table.insert(redCaptureZones, captureZone) + -- Log the creation of the capture zone + env.info("Created Red capture zone: " .. zone:GetName()) + -- Draw the zone on the map with infinite duration, red color, and thickness 2 + zone:DrawZone(30, {1, 0, 0}, 2) + -- Initialize the zone status + zoneStatuses[zone:GetName()] = { zone = captureZone, coalition = coalition.side.RED } + else + -- If creation fails, log an error message + env.error("Failed to create Red capture zone: " .. zone:GetName()) + end +end + +-- Iterate over all blue zones to create capture zones +for _, zone in ipairs(blueZones) do + -- Attempt to create a capture zone for the current blue zone + local captureZone = CreateCaptureZone(zone, coalition.side.BLUE) + if captureZone then + -- If successful, add the capture zone to the blueCaptureZones table + table.insert(blueCaptureZones, captureZone) + -- Log the creation of the capture zone + env.info("Created Blue capture zone: " .. zone:GetName()) + -- Draw the zone on the map with infinite duration, blue color, and thickness 2 + zone:DrawZone(30, {0, 0, 1}, 2) + -- Initialize the zone status + zoneStatuses[zone:GetName()] = { zone = captureZone, coalition = coalition.side.BLUE } + else + -- If creation fails, log an error message + env.error("Failed to create Blue capture zone: " .. zone:GetName()) + end +end + +-- Function to handle zone capture +local function OnZoneCaptured(event) + local zone = event.zone + local coalition = event.coalition + + if zone and coalition then + env.info("OnZoneCaptured: Zone " .. zone:GetName() .. " captured by coalition " .. coalition) + + -- Update the zone state + if coalition == coalition.side.RED then + zoneStates[zone:GetName()] = "RED" + zone:SetCoalition(coalition.side.RED) + elseif coalition == coalition.side.BLUE then + zoneStates[zone:GetName()] = "BLUE" + zone:SetCoalition(coalition.side.BLUE) + else + zoneStates[zone:GetName()] = "NEUTRAL" + zone:SetCoalition(coalition.side.NEUTRAL) + end + else + env.error("OnZoneCaptured: Invalid zone or coalition") + end +end + +-- Function to handle zone guarded events +local function OnZoneGuarded(event) + local zone = event.Zone + local coalition = event.Coalition + + if coalition == coalition.side.RED then + env.info("Red is guarding zone: " .. zone:GetName()) + elseif coalition == coalition.side.BLUE then + env.info("Blue is guarding zone: " .. zone:GetName()) + end +end + +-- Function to handle zone empty events +local function OnZoneEmpty(event) + local zone = event.Zone + env.info("Zone is empty: " .. zone:GetName()) +end + +-- Function to handle zone attacked events +local function OnZoneAttacked(event) + local zone = event.Zone + local attackingGroups = zone:GetGroups() + local makeup = {} + + for _, group in ipairs(attackingGroups) do + local groupName = group:GetName() + local unitTypes = {} + + for _, unit in ipairs(group:GetUnits()) do + local unitType = unit:GetTypeName() + unitTypes[unitType] = (unitTypes[unitType] or 0) + 1 + end + + table.insert(makeup, {groupName = groupName, unitTypes = unitTypes}) + end + + local makeupMessage = "" + for _, groupInfo in ipairs(makeup) do + makeupMessage = makeupMessage .. "Group: " .. groupInfo.groupName .. "\n" + for unitType, count in pairs(groupInfo.unitTypes) do + makeupMessage = makeupMessage .. " " .. unitType .. ": " .. count .. "\n" + end + end + + local messageText = "Zone is being attacked: " .. zone:GetName() .. "\n" .. makeupMessage + env.info(messageText) + + -- Announce to the player + if ENABLE_CAPTURE_ZONE_MESSAGES then + MESSAGE:New(messageText, 15):ToAll() + end +end + +-- Function to handle zone neutral events +local function OnZoneNeutral(event) + local zone = event.Zone + env.info("Zone is neutral: " .. zone:GetName()) +end + +-- Function to check the ZONE_CAPTURE states of all zones +local function CheckZoneStates() + env.info("Checking zone states...") + + local zoneStates = {} + + local function processZones(zones, zoneType) + env.info("Processing " .. zoneType) + env.info("Number of zones: " .. #zones) + + local allGroups = SET_GROUP:New():FilterActive():FilterStart() + + for _, zone in ipairs(zones) do + if zone then + env.info("processZones: Zone object is valid") + -- Check if the zone is of the correct type + if zone.ClassName == "ZONE_CAPTURE_COALITION" then + env.info("processZones: Zone is of type ZONE_CAPTURE_COALITION") + local coalition = zone:GetCoalition() + env.info("processZones: Zone coalition: " .. tostring(coalition)) + if coalition == 1 then + zoneStates[zone:GetZoneName()] = "RED" + env.info("processZones: Zone: " .. (zone:GetZoneName() or "nil") .. " State: RED") + elseif coalition == 2 then + zoneStates[zone:GetZoneName()] = "BLUE" + env.info("processZones: Zone: " .. (zone:GetZoneName() or "nil") .. " State: BLUE") + else + zoneStates[zone:GetZoneName()] = "NEUTRAL" + env.info("processZones: Zone: " .. (zone:GetZoneName() or "nil") .. " State: NEUTRAL") + end + + local groupsInZone = {} + allGroups:ForEachGroup(function(group) + if group then + env.info("processZones: Checking group: " .. group:GetName()) + if group.IsCompletelyInZone then + if group:IsCompletelyInZone(zone) then + table.insert(groupsInZone, group) + end + else + env.error("processZones: IsCompletelyInZone method not found in group: " .. group:GetName()) + -- Log available methods on the group object + for k, v in pairs(group) do + env.info("processZones: Group method: " .. tostring(k) .. " = " .. tostring(v)) + end + end + else + env.error("processZones: Invalid group") + end + end) + + env.info("processZones: Number of groups in zone: " .. #groupsInZone) + else + env.error("processZones: Zone is not of type ZONE_CAPTURE_COALITION") + -- Log available methods on the zone object + for k, v in pairs(zone) do + env.info("processZones: Zone method: " .. tostring(k) .. " = " .. tostring(v)) + end + end + if not zone.GetZoneName then + env.error("processZones: Missing GetZoneName method in " .. zoneType) + end + else + env.error("processZones: Invalid zone in " .. zoneType) + end + end + + end + + processZones(redCaptureZones, "redZones") + processZones(blueCaptureZones, "blueZones") + + -- Log the zoneStates table + for zoneName, state in pairs(zoneStates) do + env.info("CheckZoneStates: Zone: " .. zoneName .. " State: " .. state) + end + + return zoneStates +end + +-- Function to assign tasks to groups +local function AssignTasks(group, zoneStates) + if not group or not group.GetCoalition or not group.GetCoordinate or not group.GetVelocity then + env.info("AssignTasks: Invalid group or missing methods") + return + end + + local velocity = group:GetVelocityVec3() + local speed = math.sqrt(velocity.x^2 + velocity.y^2 + velocity.z^2) + if speed > 0 then + env.info("AssignTasks: Group " .. group:GetName() .. " is already moving. No new orders sent.") + return + end + + env.info("Assigning tasks to group: " .. group:GetName()) + local groupCoalition = group:GetCoalition() + local groupCoordinate = group:GetCoordinate() + local closestZone = nil + local closestDistance = math.huge + + env.info("Group Coalition: " .. tostring(groupCoalition)) + env.info("Group Coordinate: " .. groupCoordinate:ToStringLLDMS()) + + for zoneName, state in pairs(zoneStates) do + env.info("Checking Zone: " .. zoneName .. " with state: " .. tostring(state)) + + -- Convert state to a number for comparison + local stateCoalition = (state == "RED" and 1) or (state == "BLUE" and 2) or nil + + if stateCoalition and stateCoalition ~= groupCoalition then + local zone = ZONE:FindByName(zoneName) + if zone then + local zoneCoordinate = zone:GetCoordinate() + local distance = groupCoordinate:Get2DDistance(zoneCoordinate) + --env.info("Zone Coordinate: " .. zoneCoordinate:ToStringLLDMS()) + --env.info("Distance to zone " .. zoneName .. ": " .. distance) + if distance < closestDistance then + closestDistance = distance + closestZone = zone + env.info("New closest zone: " .. zoneName .. " with distance: " .. distance) + end + else + env.info("AssignTasks: Zone not found - " .. zoneName) + end + else + env.info("Zone " .. zoneName .. " is already controlled by coalition: " .. tostring(state)) + end + end + + if closestZone then + env.info(group:GetName() .. " is moving to and patrolling zone " .. closestZone:GetName()) + --MESSAGE:New(group:GetName() .. " is moving to and patrolling zone " .. closestZone:GetName(), 10):ToAll() + + -- Create a patrol task using the GROUP:PatrolZones method + local patrolZones = {closestZone} + local speed = 20 -- Example speed, adjust as needed + local formation = "Cone" -- Example formation, adjust as needed + local delayMin = 30 -- Example minimum delay, adjust as needed + local delayMax = 60 -- Example maximum delay, adjust as needed + + group:PatrolZones(patrolZones, speed, formation, delayMin, delayMax) + else + env.info("AssignTasks: No suitable zone found for group " .. group:GetName()) + end +end + + +-- Function to check if a group contains infantry units +local function IsInfantryGroup(group) + env.info("IsInfantryGroup: Checking group: " .. group:GetName()) + for _, unit in ipairs(group:GetUnits()) do + local unitTypeName = unit:GetTypeName() + env.info("IsInfantryGroup: Checking unit: " .. unit:GetName() .. " with type: " .. unitTypeName) + if unitTypeName:find("Infantry") or unitTypeName:find("Soldier") or unitTypeName:find("Paratrooper") then + env.info("IsInfantryGroup: Found infantry unit in group: " .. group:GetName()) + return true + end + end + return false +end + +-- Function to assign tasks to groups +local function AssignTasksToGroups() + env.info("AssignTasksToGroups: Starting task assignments") + local zoneStates = CheckZoneStates() + local allGroups = SET_GROUP:New():FilterActive():FilterStart() + + local function processZone(zone, zoneColor) + if zone then + env.info("AssignTasksToGroups: Processing " .. zoneColor .. " zone: " .. zone:GetName()) + local groupsInZone = {} + allGroups:ForEachGroup(function(group) + if group then + if group.IsCompletelyInZone then + if group:IsCompletelyInZone(zone) then + table.insert(groupsInZone, group) + end + else + env.error("AssignTasksToGroups: IsCompletelyInZone method not found in group: " .. group:GetName()) + for k, v in pairs(group) do + env.info("AssignTasksToGroups: Group method: " .. tostring(k) .. " = " .. tostring(v)) + end + end + else + env.error("AssignTasksToGroups: Invalid group") + end + end) + env.info("AssignTasksToGroups: Found " .. #groupsInZone .. " groups in " .. zoneColor .. " zone: " .. zone:GetName()) + for _, group in ipairs(groupsInZone) do + if IsInfantryGroup(group) == true then + if MOVING_INFANTRY_PATROLS == true then + env.info("AssignTasksToGroups: Assigning tasks to infantry group: " .. group:GetName()) + AssignTasks(group, zoneStates) + else + env.info("AssignTasksToGroups: Skipping infantry group: " .. group:GetName()) + end + else + env.info("AssignTasksToGroups: Assigning tasks to group: " .. group:GetName()) + AssignTasks(group, zoneStates) + end + end + else + env.info("AssignTasksToGroups: Invalid " .. zoneColor .. " zone") + end + end + + for _, zone in ipairs(redZones) do + processZone(zone, "red") + end + + for _, zone in ipairs(blueZones) do + processZone(zone, "blue") + end + + env.info("AssignTasksToGroups: Task assignments completed. Running again in " .. ASSIGN_TASKS_SCHED .. " seconds.") +end + + +-- Function to calculate spawn frequency in seconds +local function CalculateSpawnFrequency(warehouses, baseFrequency) + local totalWarehouses = #warehouses + local aliveWarehouses = 0 + + for _, warehouse in ipairs(warehouses) do + local life = warehouse:GetLife() + if life and life > 0 then + aliveWarehouses = aliveWarehouses + 1 + end + end + + if totalWarehouses == 0 or aliveWarehouses == 0 then + return math.huge -- Stop spawning if there are no warehouses or no alive warehouses + end + + local frequency = baseFrequency * (totalWarehouses / aliveWarehouses) + + return frequency +end + +local function CalculateSpawnFrequencyPercentage(warehouses) + local totalWarehouses = #warehouses + local aliveWarehouses = 0 + + for _, warehouse in ipairs(warehouses) do + local life = warehouse:GetLife() + if life and life > 0 then + aliveWarehouses = aliveWarehouses + 1 + end + end + + if totalWarehouses == 0 then + return 0 -- Avoid division by zero + end + + local percentage = (aliveWarehouses / totalWarehouses) * 100 + return math.floor(percentage) +end + +-- Add event handlers for zone capture +for _, captureZone in ipairs(redCaptureZones) do + captureZone:OnEnterCaptured(OnZoneCaptured) + captureZone:OnEnterGuarded(captureZone.OnEnterGuarded) + captureZone:OnEnterEmpty(OnZoneEmpty) + captureZone:OnEnterAttacked(OnZoneAttacked) + captureZone:OnEnterNeutral(OnZoneNeutral) +end + +for _, captureZone in ipairs(blueCaptureZones) do + captureZone:OnEnterCaptured(OnZoneCaptured) + captureZone:OnEnterGuarded(captureZone.OnEnterGuarded) + captureZone:OnEnterEmpty(OnZoneEmpty) + captureZone:OnEnterAttacked(OnZoneAttacked) + captureZone:OnEnterNeutral(OnZoneNeutral) +end + +-- Calculate spawn frequencies +local redInfantrySpawnFrequency = CalculateSpawnFrequency(redWarehouses, SPAWN_SCHED_RED_INFANTRY) +local redArmorSpawnFrequency = CalculateSpawnFrequency(redWarehouses, SPAWN_SCHED_RED_ARMOR) +local blueInfantrySpawnFrequency = CalculateSpawnFrequency(blueWarehouses, SPAWN_SCHED_BLUE_INFANTRY) +local blueArmorSpawnFrequency = CalculateSpawnFrequency(blueWarehouses, SPAWN_SCHED_BLUE_ARMOR) + +-- Calculate spawn frequency percentages +local redSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(redWarehouses, coalition.side.RED) +local blueSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(blueWarehouses, coalition.side.BLUE) + +-- Display spawn frequency percentages to the user +MESSAGE:New("Red side spawn frequency: " .. redSpawnFrequencyPercentage .. "%", 30):ToRed() +MESSAGE:New("Blue side spawn frequency: " .. blueSpawnFrequencyPercentage .. "%", 30):ToBlue() + +-- Schedule ground spawns using the calculated frequencies +redInfantrySpawn = SPAWN:New("RedInfantryGroup") + :InitRandomizeTemplate(redInfantryTemplates) + :InitRandomizeZones(redZones) + :InitLimit(INIT_RED_INFANTRY, MAX_RED_INFANTRY) + :SpawnScheduled(redInfantrySpawnFrequency, 0.5) + +redArmorSpawn = SPAWN:New("RedArmorGroup") + :InitRandomizeTemplate(redArmorTemplates) + :InitRandomizeZones(redZones) + :InitLimit(INIT_RED_ARMOR, MAX_RED_ARMOR) + :SpawnScheduled(redArmorSpawnFrequency, 0.5) + +blueInfantrySpawn = SPAWN:New("BlueInfantryGroup") + :InitRandomizeTemplate(blueInfantryTemplates) + :InitRandomizeZones(blueZones) + :InitLimit(INIT_BLUE_INFANTRY, MAX_BLUE_INFANTRY) + :SpawnScheduled(blueInfantrySpawnFrequency, 0.5) + +blueArmorSpawn = SPAWN:New("BlueArmorGroup") + :InitRandomizeTemplate(blueArmorTemplates) + :InitRandomizeZones(blueZones) + :InitLimit(INIT_BLUE_ARMOR, MAX_BLUE_ARMOR) + :SpawnScheduled(blueArmorSpawnFrequency, 0.5) + +env.info("Dynamic Ground Battle & Zone capture initialized.") + + +-- Function to monitor and announce warehouse status +local function MonitorWarehouses() + local blueWarehousesAlive = 0 + local redWarehousesAlive = 0 + + for _, warehouse in ipairs(blueWarehouses) do + if warehouse:IsAlive() then + blueWarehousesAlive = blueWarehousesAlive + 1 + end + end + + for _, warehouse in ipairs(redWarehouses) do + if warehouse:IsAlive() then + redWarehousesAlive = redWarehousesAlive + 1 + end + end + + -- Debug messages to check values + env.info("MonitorWarehouses: blueWarehousesAlive = " .. blueWarehousesAlive) + env.info("MonitorWarehouses: redWarehousesAlive = " .. redWarehousesAlive) + + -- Calculate spawn frequencies + local redInfantrySpawnFrequency = CalculateSpawnFrequency(redWarehouses, SPAWN_SCHED_RED_INFANTRY) + local redArmorSpawnFrequency = CalculateSpawnFrequency(redWarehouses, SPAWN_SCHED_RED_ARMOR) + local blueInfantrySpawnFrequency = CalculateSpawnFrequency(blueWarehouses, SPAWN_SCHED_BLUE_INFANTRY) + local blueArmorSpawnFrequency = CalculateSpawnFrequency(blueWarehouses, SPAWN_SCHED_BLUE_ARMOR) + + -- Calculate spawn frequency percentages + local redSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(redWarehouses) + local blueSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(blueWarehouses) + + -- Log the values + env.info("MonitorWarehouses: redInfantrySpawnFrequency = " .. redInfantrySpawnFrequency) + env.info("MonitorWarehouses: redArmorSpawnFrequency = " .. redArmorSpawnFrequency) + env.info("MonitorWarehouses: blueInfantrySpawnFrequency = " .. blueInfantrySpawnFrequency) + env.info("MonitorWarehouses: blueArmorSpawnFrequency = " .. blueArmorSpawnFrequency) + env.info("MonitorWarehouses: redSpawnFrequencyPercentage = " .. redSpawnFrequencyPercentage) + env.info("MonitorWarehouses: blueSpawnFrequencyPercentage = " .. blueSpawnFrequencyPercentage) + + local msg = "[Warehouse status:]\n" + msg = msg .. "Red warehouses alive: " .. redWarehousesAlive .. " Reinforcements Capacity: " .. redSpawnFrequencyPercentage .. "%" .. "\n" + msg = msg .. "Blue warehouses alive: " .. blueWarehousesAlive .. " Reinforcements Capacity: " .. blueSpawnFrequencyPercentage .. "%" .. "\n" + MESSAGE:New(msg, 30):ToAll() + + +end + +-- Function to check the wincondition. If either side owns all zones, mission ends. +local function checkWinCondition() + local blueOwned = true + local redOwned = true + + for zoneName, owner in pairs(zoneStatuses) do + if owner ~= 1 then + redOwned = false + end + if owner ~= 2 then + blueOwned = false + end + end + + if blueOwned then + MESSAGE:New("Blue side wins! They own all the capture zones.", 1800):ToAll() + USERSOUND:New("UsaTheme.ogg"):ToAll() + return true + elseif redOwned then + MESSAGE:New("Red side wins! They own all the capture zones.", 1800):ToAll() + USERSOUND:New("MotherRussia.ogg"):ToAll() + return true + end + return false +end + +-- Function to toggle capture zone messages +local function ToggleCaptureZoneMessages() + ENABLE_CAPTURE_ZONE_MESSAGES = not ENABLE_CAPTURE_ZONE_MESSAGES + local status = ENABLE_CAPTURE_ZONE_MESSAGES and "enabled" or "disabled" + MESSAGE:New("Capture zone messages are now " .. status, 15):ToAll() +end + + -- Timer function to periodically check the win condition + local function monitorWinCondition() + if not checkWinCondition() then + -- Schedule the next check in 60 seconds + TIMER:New(monitorWinCondition):Start(60) + end + end + + -- Start monitoring the win condition + monitorWinCondition() + +-- Scheduler to monitor warehouses every 120 seconds +SCHEDULER:New(nil, MonitorWarehouses, {}, 0, 120) + +-- Scheduler to assign tasks to groups periodically +SCHEDULER:New(nil, AssignTasksToGroups, {}, 0, ASSIGN_TASKS_SCHED) -- Check every 600 seconds (10 minutes) - Adjust as needed + + + +MENU_MISSION_COMMAND:New("Check Warehouse Status", missionMenu, MonitorWarehouses) + +-- Add a menu item to toggle capture zone messages under the sub menu +MENU_MISSION_COMMAND:New("Toggle Capture Zone Messages", missionMenu, ToggleCaptureZoneMessages) + + + + + + + + diff --git a/DCS_Syria/Battle for Rayak Valley/Moose_Intel.lua b/DCS_Syria/Battle for Rayak Valley/Moose_Intel.lua new file mode 100644 index 0000000..c6ff0b4 --- /dev/null +++ b/DCS_Syria/Battle for Rayak Valley/Moose_Intel.lua @@ -0,0 +1,145 @@ +--Ops - Office of Military Intelligence. +-- +--Main Features: +---Detect and track contacts consistently +---Detect and track clusters of contacts consistently +---Once detected and still alive, planes will be tracked 10 minutes, helicopters 20 minutes, ships and trains 1 hour, ground units 2 hours +-- Docs: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Ops.Intel.html + +-- Setup Detection Group +local msgTime = 6 +Blue_Intel_Message_Setting = false +Blue_Intel_Sound_Setting = false + +Blue_Intel_DetectionGroup = SET_GROUP:New() +Blue_Intel_DetectionGroup:FilterCoalitions("blue"):FilterActive(true):FilterStart() + +-- Setup the INTEL +Blue_Intel = INTEL:New(Blue_Intel_DetectionGroup, "blue", "CIA") +Blue_Intel:SetClusterAnalysis(true, true) +Blue_Intel:SetClusterRadius(5) +Blue_Intel:SetVerbosity(2) +Blue_Intel:__Start(10) + +-- On After New Contact +function Blue_Intel:OnAfterNewContact(From, Event, To, Contact) + local text = string.format("NEW contact %s detected by %s", Contact.groupname, Contact.recce or "unknown") + + if (Blue_Intel_Message_Setting == true) then + MESSAGE:New(text, msgTime, "CIA"):ToBlue() + if (Blue_Intel_Sound_Setting == true) then + USERSOUND:New("morsecode.ogg"):ToCoalition(coalition.side.BLUE) + end + end +end + +-- On After New Cluster +function Blue_Intel:OnAfterNewCluster(From, Event, To, Cluster) + local text = string.format("NEW cluster #%d of size %d", Cluster.index, Cluster.size) + + if (Blue_Intel_Message_Setting == true) then + MESSAGE:New(text, msgTime,"CIA"):ToBlue() + if (Blue_Intel_Sound_Setting == true) then + USERSOUND:New("morsecode.ogg"):ToCoalition(coalition.side.BLUE) + end + end +end + +function Blue_IntelMessageSettingOn() + if (Blue_Intel_Message_Setting == true) then + MESSAGE:New("Setting INTEL messages to ON", msgTime,"CIA"):ToBlue() + Blue_Intel_Message_Setting = true + end +end + +function Blue_IntelMessageSettingOff() + if (Blue_Intel_Message_Setting == true) then + MESSAGE:New("Setting INTEL messages to OFF", msgTime,"CIA"):ToBlue() + Blue_Intel_Message_Setting = false + end +end + +function Blue_IntelSoundSettingOff() + MESSAGE:New("Disabling morse code sound", msgTime, "CIA"):ToBlue() + Blue_Intel_Sound_Setting = false +end + +function Blue_IntelSoundSettingOn() + MESSAGE:New("Enabling morse code sound", msgTime, "CIA"):ToBlue() + Blue_Intel_Sound_Setting = true +end + +local INTELMenu = MENU_COALITION:New(coalition.side.BLUE,"INTEL HQ", missionMenu) +MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Display Messages (ON)", INTELMenu, Blue_IntelMessageSettingOn) +MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Display Messages (OFF)", INTELMenu, Blue_IntelMessageSettingOff) +MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Disable Morse Code Sound", INTELMenu, Blue_IntelSoundSettingOff) +MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Enable Morse Code Sound", INTELMenu, Blue_IntelSoundSettingOn) + + +Red_Intel_Message_Setting = false +Red_Intel_Sound_Setting = true + +Red_Intel_DetectionGroup = SET_GROUP:New() +--Red_Intel_DetectionGroup:FilterPrefixes( { "RED EWR", "RED RECON" } ) +Red_Intel_DetectionGroup:FilterCoalitions("red"):FilterActive(true):FilterStart() + + +-- Setup the INTEL +Red_Intel = INTEL:New(Red_Intel_DetectionGroup, "red", "KGB") +Red_Intel:SetClusterAnalysis(true, true) +Red_Intel:SetClusterRadius(5) +Red_Intel:SetVerbosity(2) +Red_Intel:__Start(10) + +-- On After New Contact +function Red_Intel:OnAfterNewContact(From, Event, To, Contact) + local text = string.format("NEW contact %s detected by %s", Contact.groupname, Contact.recce or "unknown") + + if (Red_Intel_Message_Setting == true) then + MESSAGE:New(text, msgTime, "KGB"):ToRed() + USERSOUND:New("morsecode.ogg"):ToCoalition(coalition.side.RED) + end +end +-- On After New Cluster +function Red_Intel:OnAfterNewCluster(From, Event, To, Cluster) + local text = string.format("NEW cluster #%d of size %d", Cluster.index, Cluster.size) + + if (Red_Intel_Message_Setting == true) then + MESSAGE:New(text, msgTime,"KGB"):ToRed() + USERSOUND:New("morsecode.ogg"):ToCoalition(coalition.side.RED) + end +end + +function Red_IntelMessageSettingOn() + if (Red_Intel_Message_Setting == true) then + MESSAGE:New("Setting INTEL messages to ON", msgTime,"KGB"):ToRed() + USERSOUND:New("morsecode.ogg"):ToCoalition(coalition.side.RED) + Red_Intel_Message_Setting = true + end +end + +function Red_IntelMessageSettingOff() + if (Red_Intel_Message_Setting == true) then + MESSAGE:New("Setting INTEL messages to OFF", msgTime,"KGB"):ToRed() + Red_Intel_Message_Setting = false + end +end + +function Red_IntelSoundSettingOff() + MESSAGE:New("Disabling morse code sound", msgTime, "KGB"):ToRed() + Red_Intel_Sound_Setting = false +end + +function Red_IntelSoundSettingOn() + MESSAGE:New("Enabling morse code sound", msgTime, "KGB"):ToRed() + Red_Intel_Sound_Setting = true +end + +-- Create the "INTEL HQ" submenu under "Tanker & Other Settings" +local RedINTELMenu = MENU_COALITION:New(coalition.side.RED, "INTEL HQ", missionMenu) + +-- Add menu items to the "INTEL HQ" submenu +MENU_COALITION_COMMAND:New(coalition.side.RED, "Display Messages (ON)", RedINTELMenu, Red_IntelMessageSettingOn) +MENU_COALITION_COMMAND:New(coalition.side.RED, "Display Messages (OFF)", RedINTELMenu, Red_IntelMessageSettingOff) +MENU_COALITION_COMMAND:New(coalition.side.RED, "Disable Morse Code Sound", RedINTELMenu, Red_IntelSoundSettingOff) +MENU_COALITION_COMMAND:New(coalition.side.RED, "Enable Morse Code Sound", RedINTELMenu, Red_IntelSoundSettingOn) \ No newline at end of file diff --git a/DCS_Syria/F99th-Battle for Rayak Valley-1.0.0.miz b/DCS_Syria/F99th-Battle for Rayak Valley-1.0.0.miz new file mode 100644 index 0000000..89181d8 Binary files /dev/null and b/DCS_Syria/F99th-Battle for Rayak Valley-1.0.0.miz differ