commit 65dbf048d7f68001d235af4d5da7e13e6649c7aa Author: iTracerFacer <134304944+iTracerFacer@users.noreply.github.com> Date: Mon Nov 17 07:32:56 2025 -0600 Initial Commit diff --git a/Docs/1ownership.png b/Docs/1ownership.png new file mode 100644 index 0000000..b58409d Binary files /dev/null and b/Docs/1ownership.png differ diff --git a/Docs/2captured.png b/Docs/2captured.png new file mode 100644 index 0000000..44cbe44 Binary files /dev/null and b/Docs/2captured.png differ diff --git a/Docs/3.attacked.png b/Docs/3.attacked.png new file mode 100644 index 0000000..4da67ef Binary files /dev/null and b/Docs/3.attacked.png differ diff --git a/Docs/4mgrs.png b/Docs/4mgrs.png new file mode 100644 index 0000000..cd5f30e Binary files /dev/null and b/Docs/4mgrs.png differ diff --git a/Docs/Example_Mission_Description.txt b/Docs/Example_Mission_Description.txt new file mode 100644 index 0000000..162d78c --- /dev/null +++ b/Docs/Example_Mission_Description.txt @@ -0,0 +1,48 @@ +MOOSE Dual Coalition Zone Capture - Example Mission +=================================================== + +MISSION OVERVIEW: +This is a demonstration mission showcasing the MOOSE Dual Coalition Zone Capture system's +dynamic behavior and visual feedback mechanisms. + +SCENARIO: +Four strategic zones have been marked across the battlefield: +• Zone 1: Initially controlled by RED forces +• Zone 2: Initially controlled by BLUE forces +• Zone 3 & 4: Neutral territory, awaiting capture + +DEMONSTRATION FEATURES: +Ground units from both coalitions patrol the battlefield in a continuous cycle, moving between +zones to demonstrate the system's real-time response to territorial changes. Units are configured +in a non-combat mode to clearly showcase zone state transitions without interference from combat. + +WHAT YOU'LL SEE: +✓ Color-coded zone boundaries changing as units enter/exit (Red/Blue/Green/Orange) +✓ Tactical information markers updating with force compositions +✓ Smoke signals marking zone status changes +✓ Automatic messaging system announcing captures and attacks +✓ F10 radio menu commands for zone status queries +✓ Victory condition tracking as zones are captured + +OBSERVING THE DEMONSTRATION: +• Use F10 Map to view zone boundaries and tactical markers +• Monitor chat messages for zone status updates +• Access F10 → Zone Control → "Get Zone Status Report" for detailed information +• Watch as zones transition between Empty → Captured → Attacked → Guarded states +• Victory condition triggers when one coalition controls all four zones + +PURPOSE: +This mission serves as both a tutorial and testing environment for mission makers looking to +implement the zone capture system in their own scenarios. It demonstrates proper configuration, +visual feedback, and system behavior without the chaos of active combat. + +MISSION MAKER NOTES: +Examine the mission file to see: +- Proper trigger zone naming conventions +- BLUEHQ and REDHQ group placement +- Script loading order in triggers +- Zone configuration in the Lua file +- Unit waypoint setup for continuous patrol demonstration + +This is a reference implementation - feel free to modify, expand, or use as a template for your +own mission designs! diff --git a/Docs/Mission_Maker_Guide.html b/Docs/Mission_Maker_Guide.html new file mode 100644 index 0000000..778eedf --- /dev/null +++ b/Docs/Mission_Maker_Guide.html @@ -0,0 +1,845 @@ + + + + + + MOOSE Dual Coalition Zone Capture - Mission Maker Guide + + + +
+
+

🎯 MOOSE Dual Coalition Zone Capture

+
Complete Mission Maker Implementation Guide
+
+ +
+ +
+

📖 Introduction

+

+ The MOOSE Dual Coalition Zone Capture system is a fully-featured, balanced territorial control framework for DCS World missions. + This script creates dynamic capture zones that both RED and BLUE coalitions can fight over, with automatic victory detection, + tactical information displays, and comprehensive player feedback systems. +

+ +
+

✨ Key Features

+
    +
  • Easy Configuration: Define zones with simple Lua tables - no complex coding required
  • +
  • Dual Coalition Support: Both RED and BLUE can capture zones and win
  • +
  • Dynamic Visual Feedback: Color-coded zone boundaries, smoke markers, and flares
  • +
  • Tactical Information: Real-time unit counts and MGRS coordinates for small enemy forces
  • +
  • F10 Radio Menu: Players can check status, progress, and refresh visuals
  • +
  • Automatic Victory Detection: First coalition to capture all zones wins
  • +
  • Flexible Initial Ownership: Set zones to start under RED, BLUE, or NEUTRAL control
  • +
  • Comprehensive Logging: Detailed debug output for troubleshooting
  • +
+
+
+ + + + + +
+

📋 Requirements

+

Essential Components

+
    +
  1. DCS World - Any recent version
  2. +
  3. MOOSE Framework - Latest version recommended + +
  4. +
  5. This Script - Moose_DualCoalitionZoneCapture.lua
  6. +
+ +

Mission Prerequisites

+
+

⚠️ Required Mission Elements

+

Your mission MUST contain the following groups (case-sensitive):

+
    +
  • BLUEHQ - A BLUE coalition ground unit group (can be a single unit)
  • +
  • REDHQ - A RED coalition ground unit group (can be a single unit)
  • +
+

Why? These groups serve as command centers for the messaging system. They can be placed anywhere, + even hidden or spawned late. Without them, the script will fail to initialize.

+
+
+ + +
+

🚀 Quick Start Guide

+

Get up and running in 5 minutes:

+ +

1Mission Editor Setup

+
    +
  1. Create trigger zones at locations you want to be capturable (e.g., airbases, towns)
  2. +
  3. Name each zone starting with Capture - for example: +
      +
    • Capture Severomorsk
    • +
    • Capture Murmansk
    • +
    • Capture Zone-1
    • +
    +
  4. +
  5. Create two ground unit groups named BLUEHQ and REDHQ
  6. +
+ +

2Load MOOSE Framework

+
    +
  1. In Mission Editor → Triggers → New Trigger
  2. +
  3. Type: MISSION START
  4. +
  5. Actions → DO SCRIPT FILE → Select Moose_.lua
  6. +
+ +

3Configure Zone Ownership

+

Open Moose_DualCoalitionZoneCapture.lua in a text editor and edit the ZONE_CONFIG section:

+
local ZONE_CONFIG = {
+  RED = {
+    "Capture Severomorsk",
+    "Capture Murmansk"
+  },
+  BLUE = {
+    "Capture Banak"
+  },
+  NEUTRAL = {
+    "Capture Contested Valley"
+  }
+}
+ +

4Load the Script

+
    +
  1. In the same trigger, add another action
  2. +
  3. Actions → DO SCRIPT FILE → Select Moose_DualCoalitionZoneCapture.lua
  4. +
+ +
+

✅ Done!

+

Save your mission, start it, and the zone capture system will automatically initialize. + Check the DCS.log file to verify successful zone creation.

+
+
+ + +
+

📐 Detailed Setup Instructions

+ +

Step 1: Creating Trigger Zones

+

In DCS Mission Editor:

+
    +
  1. Click the Trigger Zones button (or press F5)
  2. +
  3. Click New to create a zone
  4. +
  5. Set the zone type to Circular or Polygon
  6. +
  7. Position the zone over the area you want to be capturable
  8. +
  9. CRITICAL: Name the zone with any name you choose (e.g., "Capture Severomorsk")
  10. +
  11. Adjust the size to encompass the tactical area
  12. +
  13. Repeat for all capture locations
  14. +
+ +
+

⚠️ Zone Naming Rules

+
    +
  • Zone names in the mission editor MUST EXACTLY match the names in your Lua config
  • +
  • Names are case-sensitive: Capture Zone-1capture zone-1
  • +
  • Spaces matter: Capture Zone-1CaptureZone-1
  • +
  • Best practice: Use the "Capture [Location]" format for clarity
  • +
+
+ +

Step 2: Creating Command Centers

+

The script requires two "command center" groups to function:

+ +

BLUEHQ Setup:

+
    +
  1. Place any BLUE ground unit (e.g., M-1 Abrams, Infantry, HMMWV)
  2. +
  3. Name the GROUP (not the unit): BLUEHQ
  4. +
  5. You can make it immortal via triggers if desired
  6. +
  7. Location doesn't matter - can even be in an unreachable area
  8. +
+ +

REDHQ Setup:

+
    +
  1. Place any RED ground unit (e.g., T-90, BTR-80, Infantry)
  2. +
  3. Name the GROUP: REDHQ
  4. +
  5. Same rules as BLUEHQ
  6. +
+ +
+

💡 Pro Tip: Hidden Command Centers

+

You can hide these units by:

+
    +
  • Placing them far off-map
  • +
  • Setting them to "UNCONTROLLED" start state
  • +
  • Using the "Late Activation" option
  • +
  • Adding a trigger to activate them after mission start
  • +
+

They only need to exist in the mission file - they don't need to be active or visible.

+
+ +

Step 3: Script Loading Order

+

Create a MISSION START trigger with these actions IN ORDER:

+
    +
  1. DO SCRIPT FILE: Moose_.lua (MUST be first)
  2. +
  3. DO SCRIPT FILE: Moose_DualCoalitionZoneCapture.lua (MUST be after MOOSE)
  4. +
  5. (Optional) Any other scripts that depend on this system
  6. +
+ +
+

❌ Common Loading Errors

+
    +
  • Script loaded before MOOSE: Will crash with "attempt to index a nil value"
  • +
  • Wrong trigger type: Must be MISSION START, not TIME MORE or ONCE
  • +
  • DO SCRIPT instead of DO SCRIPT FILE: Use FILE to load external Lua files
  • +
+
+
+ + +
+

⚙️ Configuration Options

+ +

Zone Ownership Configuration

+

Edit the ZONE_CONFIG table at the top of the script:

+ +
local ZONE_CONFIG = {
+  -- Zones that start under RED coalition control
+  RED = {
+    "Capture Severomorsk-1",
+    "Capture Severomorsk-3",
+    "Capture Murmansk International"
+  },
+  
+  -- Zones that start under BLUE coalition control
+  BLUE = {
+    "Capture Banak",
+    "Capture Kirkenes"
+  },
+  
+  -- Zones that start neutral (empty/uncontrolled)
+  NEUTRAL = {
+    "Capture Contested Valley",
+    "Capture No Man's Land"
+  }
+}
+ +
+

⚠️ Important: Initial Ownership vs. Unit Presence

+

The ownership you set here is the starting state of the zone. However:

+
    +
  • If you place RED units in a zone configured as BLUE, the zone will immediately flip to RED when the mission starts
  • +
  • Empty zones (no units) will respect your configured ownership
  • +
  • The script scans for units every 30 seconds (configurable)
  • +
+

Best Practice: Either start zones empty/neutral, or ensure ground units match the configured ownership.

+
+ +

Advanced Settings

+
local ZONE_SETTINGS = {
+  guardDelay = 1,        -- Seconds before entering Guard state after capture
+  scanInterval = 30,     -- How often to scan for units in zones (seconds)
+  captureScore = 200     -- Points awarded for capturing a zone
+}
+ + + + + + + + + + + + + + + + + + + + + + +
SettingDefaultDescription
guardDelay1Delay (in seconds) before a captured zone enters the "Guarded" state. Shorter = faster transitions.
scanInterval30How frequently the script checks for units in zones. Lower = more responsive but more CPU usage.
captureScore200Points awarded when a zone is captured (requires MOOSE scoring system to be active).
+
+ + +
+

🎨 Understanding Zone States

+

Zones can be in one of four states, each with distinct visual indicators:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StateColorSmokeDescription
RED ControlledRed BorderRed SmokeZone is secured by RED coalition forces
BLUE ControlledBlue BorderBlue SmokeZone is secured by BLUE coalition forces
Neutral/EmptyGreen BorderGreen SmokeZone is uncontrolled and can be captured by either side
Contested/AttackedOrange BorderWhite SmokeZone is under attack - both coalitions have units present
+ +

State Transitions

+
+

How Zones Change Ownership:

+
    +
  1. Empty → Captured: Move ground units into an empty zone
  2. +
  3. Captured → Attacked: Enemy forces enter a controlled zone
  4. +
  5. Attacked → Captured: One side eliminates all enemy forces
  6. +
  7. Captured → Empty: All units leave the zone
  8. +
  9. Captured → Guard: Zone remains secured after a brief delay
  10. +
+
+ +

Tactical Information Markers

+

Each zone displays a tactical marker with real-time information:

+
    +
  • Force Counts: Shows R: (RED units), B: (BLUE units)
  • +
  • MGRS Coordinates: When ≤10 enemy units, shows their exact positions
  • +
  • Coalition-Specific: Each side sees their enemies marked
  • +
  • Auto-Refresh: Updates every 60 seconds
  • +
+ +

Example tactical marker:

+
TACTICAL: Capture Severomorsk-1
+Forces: R:5 B:12
+TGTS: T-90@38U LV 12345 67890, BTR-80@38U LV 12346 67891
+
+ + +
+

👥 Player Features

+ +

F10 Radio Menu Commands

+

Players from both coalitions have access to F10 radio menu commands under "Zone Control":

+ + + + + + + + + + + + + + + + + + +
CommandDescription
Get Zone Status ReportDisplays current ownership of all zones with detailed breakdown
Check Victory ProgressShows percentage toward victory and zones remaining
Refresh Zone ColorsManually redraws all zone boundaries (troubleshooting tool)
+ +

Automatic Status Updates

+

The script provides automatic notifications:

+
    +
  • Every 5 minutes: Zone control report broadcast to both coalitions
  • +
  • On zone capture: Both sides notified when a zone changes hands
  • +
  • On zone attack: Alerts when a zone comes under attack
  • +
  • At 80% victory: Warning that one side is close to winning
  • +
  • On victory: Dramatic announcement with 60-second countdown
  • +
+ +

Victory Conditions

+
+

🏆 How to Win

+

The first coalition to capture ALL zones wins the mission.

+
    +
  • Total zones must be controlled by one coalition
  • +
  • Victory triggers a 60-second countdown
  • +
  • Celebratory effects: smoke, flares, messages
  • +
  • Mission ends automatically with appropriate user flag set
  • +
+
+
+ + +
+

🔧 Troubleshooting

+ +

Script Won't Load

+
+

❌ Problem: "attempt to index a nil value"

+

Cause: MOOSE framework not loaded before this script

+

Solution: Ensure MOOSE is loaded FIRST in your MISSION START trigger

+
+ +
+

❌ Problem: "GROUP:FindByName() returned nil"

+

Cause: BLUEHQ or REDHQ groups don't exist or are misnamed

+

Solution: Verify you have groups (not units) named exactly BLUEHQ and REDHQ

+
+ +

Zones Not Working

+
+

❌ Problem: "Zone 'X' not found in mission editor!"

+

Cause: Zone name mismatch between Lua config and mission editor

+

Solution: +

    +
  1. Check DCS.log for the exact error message
  2. +
  3. Open mission editor and verify trigger zone names
  4. +
  5. Ensure names match EXACTLY (case-sensitive, spaces matter)
  6. +
  7. Update either the mission or Lua config to match
  8. +
+

+
+ +

Zones Not Capturing

+
+

⚠️ Problem: Units in zone but ownership not changing

+

Possible Causes:

+
    +
  • Wrong unit types: Only ground units, planes, and helicopters are scanned
  • +
  • Dead units: Script only counts alive units
  • +
  • Scan timing: Wait 30 seconds for the next scan cycle
  • +
  • Both coalitions present: Zone enters "Attacked" state, doesn't flip ownership
  • +
+

Solution: Eliminate all enemy forces to capture the zone

+
+ +

Visual Issues

+
+

💡 Problem: Zone colors wrong or not showing

+

Solutions:

+
    +
  • Use F10 → Zone Control → "Refresh Zone Colors"
  • +
  • Zone colors auto-refresh every 2 minutes
  • +
  • Check DCS graphics settings (markers must be enabled)
  • +
  • Restart mission if issue persists
  • +
+
+ +

Checking Logs

+

The script provides extensive logging. To view:

+
    +
  1. Open DCS.log file in your Saved Games\DCS folder
  2. +
  3. Search for [CAPTURE Module] or [INIT]
  4. +
  5. Look for initialization messages and error reports
  6. +
+ +

Successful initialization looks like:

+
[CAPTURE Module] [INIT] Starting zone initialization...
+[CAPTURE Module] [INIT] Creating zone: Capture Severomorsk-1 (Coalition: RED)
+[CAPTURE Module] [INIT] ✓ Zone 'Capture Severomorsk-1' initialized successfully
+[CAPTURE Module] [INIT] Zone initialization complete. Total zones created: 11
+
+ + +
+

🔬 Advanced Customization

+ +

Adjusting Scan Performance

+

If you experience performance issues with many units:

+
local ZONE_SETTINGS = {
+  scanInterval = 60,  -- Scan less frequently (every 60 seconds)
+}
+ +

For fast-paced action:

+
local ZONE_SETTINGS = {
+  scanInterval = 15,  -- Scan more frequently (every 15 seconds)
+}
+ +

Disabling Logging

+

To reduce log spam, add this BEFORE loading the script:

+
CAPTURE_ZONE_LOGGING = { enabled = false }
+ +

Mission Integration

+

The script sets user flags on victory:

+
    +
  • BLUE_VICTORY = 1 when BLUE wins
  • +
  • RED_VICTORY = 1 when RED wins
  • +
+

You can use these flags in mission triggers to:

+
    +
  • End the mission
  • +
  • Trigger victory cutscenes
  • +
  • Award points or bonuses
  • +
  • Transition to next phase
  • +
+ +

Modifying Messages

+

All player messages are in the event handler functions. Search for:

+
    +
  • OnEnterGuarded - Zone secured messages
  • +
  • OnEnterCaptured - Zone captured messages
  • +
  • OnEnterAttacked - Zone under attack messages
  • +
  • CheckVictoryCondition - Victory messages
  • +
+ +

Zone-Specific Behavior

+

To create different behaviors for specific zones, modify the event handlers to check zone names:

+
local function OnEnterCaptured(ZoneCapture)
+  local zoneName = ZoneCapture:GetZoneName()
+  
+  if zoneName == "Capture MainBase" then
+    -- Special behavior for main base capture
+    US_CC:MessageTypeToCoalition("CRITICAL: Main base captured!", MESSAGE.Type.Information)
+  else
+    -- Standard behavior for other zones
+  end
+end
+ +

Integration with Other Scripts

+

Access zone data from other scripts:

+
-- Get current ownership status
+local status = GetZoneOwnershipStatus()
+-- Returns: { blue = X, red = Y, neutral = Z, total = N, zones = {...} }
+
+-- Broadcast status manually
+BroadcastZoneStatus()
+
+-- Refresh visuals manually
+RefreshAllZoneColors()
+
+ + +
+

💡 Best Practices & Tips

+ +
+

✅ Mission Design Tips

+
    +
  • Zone Size: Make zones large enough to encompass tactical areas but not so large they overlap
  • +
  • Zone Placement: Position over airbases, FOBs, or strategic terrain features
  • +
  • Starting Balance: Consider giving RED more zones for defensive scenarios, or split evenly for balanced play
  • +
  • Ground Units: Populate zones with defending forces appropriate to difficulty level
  • +
  • Respawning: Consider using MOOSE's spawn system to continuously reinforce zones
  • +
  • AI Behavior: Set AI ground units to "Ground Hold" or "Ground On Road" for best results
  • +
+
+ +
+

🎮 Player Experience

+
    +
  • Brief players on F10 radio menu commands before mission start
  • +
  • Place zones at recognizable landmarks for easy navigation
  • +
  • Consider adding custom mission briefing with zone locations
  • +
  • Test zone sizes with different unit counts to ensure good balance
  • +
  • Use neutral zones as "no man's land" for dynamic front lines
  • +
+
+ +
+

🚀 Quick Reference Card

+

Required Groups: BLUEHQ, REDHQ

+

Zone Naming: Must match exactly between mission and Lua

+

Load Order: MOOSE → This Script

+

Victory Condition: Capture all zones

+

F10 Menu: Zone Control → Status/Progress/Refresh

+

Log Location: Saved Games\DCS\Logs\DCS.log

+
+
+ + +
+

📞 Support & Resources

+ +

Script Author

+
+

Author: F99th-TracerFacer

+

Discord Community: https://discord.gg/7wBVWKK3

+

Join the Discord for support, updates, and discussion about this script and other DCS mission development topics.

+
+ +

Additional Resources

+ + +
+

✨ Version Information

+

Script Version: 2.0 (Dual Coalition)

+

Author: F99th-TracerFacer

+

Last Updated: October 2025

+

Compatibility: DCS World 2.9+, MOOSE Latest

+
+
+
+ + +
+ + \ No newline at end of file diff --git a/Moose_DualCoalitionZoneCapture.lua b/Moose_DualCoalitionZoneCapture.lua new file mode 100644 index 0000000..3b3c739 --- /dev/null +++ b/Moose_DualCoalitionZoneCapture.lua @@ -0,0 +1,1143 @@ +-- Refactored version with configurable zone ownership + +-- ========================================== +-- MESSAGE AND TIMING CONFIGURATION +-- ========================================== +local MESSAGE_CONFIG = { + STATUS_BROADCAST_FREQUENCY = 3602, -- Zone status broadcast cadence (seconds) + STATUS_BROADCAST_START_DELAY = 10, -- Delay before first broadcast (seconds) + COLOR_VERIFICATION_FREQUENCY = 240, -- Zone color verification cadence (seconds) + COLOR_VERIFICATION_START_DELAY = 60, -- Delay before first color check (seconds) + TACTICAL_UPDATE_FREQUENCY = 180, -- Tactical marker update cadence (seconds) + TACTICAL_UPDATE_START_DELAY = 30, -- Delay before first tactical update (seconds) + STATUS_MESSAGE_DURATION = 15, -- How long general status messages stay onscreen + VICTORY_MESSAGE_DURATION = 300, -- How long victory/defeat alerts stay onscreen + CAPTURE_MESSAGE_DURATION = 15, -- Duration for capture/guard/empty notices + ATTACK_MESSAGE_DURATION = 15 -- Duration for attack alerts +} + +-- ========================================== +-- ZONE COLOR CONFIGURATION (Centralized) +-- ========================================== +-- Colors are in RGB format: {Red, Green, Blue} where each value is 0.0 to 1.0 +local ZONE_COLORS = { + -- Blue coalition zones + BLUE_CAPTURED = {0, 0, 1}, -- Blue (owned by Blue) + BLUE_ATTACKED = {0, 1, 1}, -- Cyan (owned by Blue, under attack) + + -- Red coalition zones + RED_CAPTURED = {1, 0, 0}, -- Red (owned by Red) + RED_ATTACKED = {1, 0.5, 0}, -- Orange (owned by Red, under attack) + + -- Neutral/Empty zones + EMPTY = {0, 1, 0} -- Green (no owner) +} + +-- Helper to get the appropriate color for a zone based on state/ownership +local function GetZoneColor(zoneCapture) + local zoneCoalition = zoneCapture:GetCoalition() + local state = zoneCapture:GetCurrentState() + + -- Priority 1: Attacked overrides ownership color + if state == "Attacked" then + if zoneCoalition == coalition.side.BLUE then + return ZONE_COLORS.BLUE_ATTACKED + elseif zoneCoalition == coalition.side.RED then + return ZONE_COLORS.RED_ATTACKED + end + end + + -- Priority 2: Empty/neutral + if state == "Empty" then + return ZONE_COLORS.EMPTY + end + + -- Priority 3: Ownership color + if zoneCoalition == coalition.side.BLUE then + return ZONE_COLORS.BLUE_CAPTURED + elseif zoneCoalition == coalition.side.RED then + return ZONE_COLORS.RED_CAPTURED + end + + -- Fallback + return ZONE_COLORS.EMPTY +end + +-- ========================================== +-- ZONE CONFIGURATION +-- ========================================== +-- Mission makers: Edit this table to define zones and their initial ownership +-- Just list the zone names under RED, BLUE, or NEUTRAL coalition +-- The script will automatically create and configure all zones +-- Make sure the zone names match exactly with those defined in the mission editor +-- Zones must be defined in the mission editor as trigger zones named "Capture " +-- Note: Red/Blue/Neutral zones defined below are only setting their initial ownership state. +-- If there are existing units in the zone at mission start, ownership may change based on unit presence. + + +local ZONE_CONFIG = { + -- Zones that start under RED coalition control + -- IMPORTANT: Use the EXACT zone names from the mission editor (including "Capture " prefix if present) + RED = { + "Capture Zone-1", + "Capture Zone-2", + "Capture Zone-3", + + -- Add more zone names here for RED starting zones + }, + + -- Zones that start under BLUE coalition control + BLUE = { + "Capture Zone-4", + "Capture Zone-5", + "Capture Zone-6", + }, + + -- Zones that start neutral (empty/uncontrolled) + NEUTRAL = { + + } +} + +-- Advanced settings (usually don't need to change these) +local ZONE_SETTINGS = { + guardDelay = 1, -- Delay before entering Guard state after capture + scanInterval = 30, -- How often to scan for units in the zone (seconds) + captureScore = 200 -- Points awarded for capturing a zone +} + +-- ========================================== +-- END OF CONFIGURATION +-- ========================================== + +-- Build Command Center and Mission for Blue Coalition +local blueHQ = GROUP:FindByName("BLUEHQ") +if blueHQ then + US_CC = COMMANDCENTER:New(blueHQ, "USA HQ") + US_Mission = MISSION:New(US_CC, "Zone Capture Example Mission", "Primary", "", coalition.side.BLUE) + US_Score = SCORING:New("Zone Capture Example Mission") + --US_Mission:AddScoring(US_Score) + --US_Mission:Start() + env.info("Blue Coalition Command Center and Mission started successfully") +else + env.info("ERROR: BLUEHQ group not found! Blue mission will not start.") +end + +--Build Command Center and Mission Red +local redHQ = GROUP:FindByName("REDHQ") +if redHQ then + RU_CC = COMMANDCENTER:New(redHQ, "Russia HQ") + RU_Mission = MISSION:New(RU_CC, "Zone Capture Example Mission", "Primary", "Hold what we have, take what we don't.", coalition.side.RED) + --RU_Score = SCORING:New("Zone Capture Example Mission") + --RU_Mission:AddScoring(RU_Score) + RU_Mission:Start() + env.info("Red Coalition Command Center and Mission started successfully") +else + env.info("ERROR: REDHQ group not found! Red mission will not start.") +end + + +-- Setup BLUE Missions +do -- BLUE Mission + + US_Mission_Capture_Airfields = MISSION:New( US_CC, "Capture the Zones", "Primary", + "Capture the Zones marked on your F10 map.\n" .. + "Destroy enemy ground forces in the surrounding area, " .. + "then occupy each capture zone with a platoon.\n " .. + "Your orders are to hold position until all capture zones are taken.\n" .. + "Use the map (F10) for a clear indication of the location of each capture zone.\n" .. + "Note that heavy resistance can be expected at the airbases!\n" + , coalition.side.BLUE) + + --US_Score = SCORING:New( "Capture Airfields" ) + + --US_Mission_Capture_Airfields:AddScoring( US_Score ) + + US_Mission_Capture_Airfields:Start() + +end + +-- Setup RED Missions +do -- RED Mission + + RU_Mission_Capture_Airfields = MISSION:New( RU_CC, "Defend the Motherland", "Primary", + "Defend Russian airfields and recapture lost territory.\n" .. + "Eliminate enemy forces in capture zones and " .. + "maintain control with ground units.\n" .. + "Your orders are to prevent the enemy from capturing all strategic zones.\n" .. + "Use the map (F10) for a clear indication of the location of each capture zone.\n" .. + "Expect heavy NATO resistance!\n" + , coalition.side.RED) + + --RU_Score = SCORING:New( "Defend Territory" ) + + --RU_Mission_Capture_Airfields:AddScoring( RU_Score ) + + RU_Mission_Capture_Airfields:Start() + +end + + +-- Logging configuration: toggle logging behavior for this module +-- Set `CAPTURE_ZONE_LOGGING.enabled = false` to silence module logs +if not CAPTURE_ZONE_LOGGING then + CAPTURE_ZONE_LOGGING = { enabled = false, prefix = "[CAPTURE Module]" } +end + +local function log(message, detailed) + if CAPTURE_ZONE_LOGGING.enabled then + -- Preserve the previous prefixing used across the module + if CAPTURE_ZONE_LOGGING.prefix then + env.info(tostring(CAPTURE_ZONE_LOGGING.prefix) .. " " .. tostring(message)) + else + env.info(tostring(message)) + end + end +end + + +-- ========================================== +-- ZONE INITIALIZATION SYSTEM +-- ========================================== + +-- Storage for all zone capture objects and metadata +-- NOTE: These are exported as globals for plugin compatibility (e.g., Moose_DynamicGroundBattle_Plugin.lua) +zoneCaptureObjects = {} -- Global: accessible by other scripts +zoneNames = {} -- Global: accessible by other scripts +local zoneMetadata = {} -- Stores coalition ownership info + +-- Function to initialize all zones from configuration +local function InitializeZones() + log("[INIT] Starting zone initialization from configuration...") + + local totalZones = 0 + + -- Process each coalition's zones + for coalitionName, zones in pairs(ZONE_CONFIG) do + local coalitionSide = nil + + -- Map coalition name to DCS coalition constant + if coalitionName == "RED" then + coalitionSide = coalition.side.RED + elseif coalitionName == "BLUE" then + coalitionSide = coalition.side.BLUE + elseif coalitionName == "NEUTRAL" then + coalitionSide = coalition.side.NEUTRAL + else + log(string.format("[INIT] WARNING: Unknown coalition '%s' in ZONE_CONFIG", coalitionName)) + end + + if coalitionSide then + for _, zoneName in ipairs(zones) do + log(string.format("[INIT] Creating zone: %s (Coalition: %s)", zoneName, coalitionName)) + + -- Create the MOOSE zone object (using exact name from config) + local zone = ZONE:New(zoneName) + + if zone then + -- Create the zone capture coalition object + local zoneCapture = ZONE_CAPTURE_COALITION:New(zone, coalitionSide) + + if zoneCapture then + -- Configure the zone + zoneCapture:__Guard(ZONE_SETTINGS.guardDelay) + zoneCapture:Start(ZONE_SETTINGS.scanInterval, ZONE_SETTINGS.scanInterval) + + -- Store in our data structures + table.insert(zoneCaptureObjects, zoneCapture) + table.insert(zoneNames, zoneName) + zoneMetadata[zoneName] = { + coalition = coalitionSide, + index = #zoneCaptureObjects + } + + totalZones = totalZones + 1 + log(string.format("[INIT] ✓ Zone '%s' initialized successfully", zoneName)) + else + log(string.format("[INIT] ✗ ERROR: Failed to create ZONE_CAPTURE_COALITION for '%s'", zoneName)) + end + else + log(string.format("[INIT] ✗ ERROR: Zone '%s' not found in mission editor!", zoneName)) + log(string.format("[INIT] Make sure you have a trigger zone named exactly: '%s'", zoneName)) + end + end + end + end + + log(string.format("[INIT] Zone initialization complete. Total zones created: %d", totalZones)) + return totalZones +end + +-- Initialize all zones +local totalZones = InitializeZones() + + +-- Global cached unit set - created once and maintained automatically by MOOSE +local CachedUnitSet = nil + +-- Utility guard to safely test whether a unit is inside a zone without throwing +local function IsUnitInZone(unit, zone) + if not unit or not zone then + return false + end + + local ok, point = pcall(function() + return unit:GetPointVec3() + end) + + if not ok or not point then + return false + end + + local inZone = false + pcall(function() + inZone = zone:IsPointVec3InZone(point) + end) + + return inZone +end + +-- Initialize the cached unit set once +local function InitializeCachedUnitSet() + if not CachedUnitSet then + CachedUnitSet = SET_UNIT:New() + :FilterCategories({"ground", "plane", "helicopter"}) -- Only scan relevant unit types + :FilterStart() -- Keep the set updated by MOOSE without recreating it + log("[PERFORMANCE] Initialized cached unit set for zone scanning") + end +end + +local function GetZoneForceStrengths(ZoneCapture) + if not ZoneCapture then + return { red = 0, blue = 0, neutral = 0 } + end + + local success, zone = pcall(function() + return ZoneCapture:GetZone() + end) + + if not success or not zone then + return { red = 0, blue = 0, neutral = 0 } + end + + local redCount = 0 + local blueCount = 0 + local neutralCount = 0 + + InitializeCachedUnitSet() + + if CachedUnitSet then + CachedUnitSet:ForEachUnit(function(unit) + if unit and unit:IsAlive() and IsUnitInZone(unit, zone) then + local unitCoalition = unit:GetCoalition() + if unitCoalition == coalition.side.RED then + redCount = redCount + 1 + elseif unitCoalition == coalition.side.BLUE then + blueCount = blueCount + 1 + elseif unitCoalition == coalition.side.NEUTRAL then + neutralCount = neutralCount + 1 + end + end + end) + end + + log(string.format("[TACTICAL] Zone %s scan result: R:%d B:%d N:%d", + ZoneCapture:GetZoneName(), redCount, blueCount, neutralCount)) + + return { + red = redCount, + blue = blueCount, + neutral = neutralCount + } +end + +local function GetEnemyUnitMGRSCoords(ZoneCapture, enemyCoalition) + if not ZoneCapture or not enemyCoalition then + return {} + end + + local success, zone = pcall(function() + return ZoneCapture:GetZone() + end) + + if not success or not zone then + return {} + end + + InitializeCachedUnitSet() + + local coords = {} + local totalUnits = 0 + local enemyUnits = 0 + local unitsWithCoords = 0 + + if CachedUnitSet then + CachedUnitSet:ForEachUnit(function(unit) + if unit and unit:IsAlive() and IsUnitInZone(unit, zone) then + totalUnits = totalUnits + 1 + local unitCoalition = unit:GetCoalition() + + if unitCoalition == enemyCoalition then + enemyUnits = enemyUnits + 1 + local coord = unit:GetCoordinate() + + if coord then + local mgrs = nil + local success_mgrs = false + + success_mgrs, mgrs = pcall(function() + return coord:ToStringMGRS(5) + end) + + if not success_mgrs or not mgrs then + success_mgrs, mgrs = pcall(function() + return coord:ToStringMGRS() + end) + end + + if not success_mgrs or not mgrs then + success_mgrs, mgrs = pcall(function() + return coord:ToMGRS() + end) + end + + if not success_mgrs or not mgrs then + success_mgrs, mgrs = pcall(function() + local lat, lon = coord:GetLLDDM() + return string.format("N%s E%s", lat, lon) + end) + end + + if success_mgrs and mgrs then + unitsWithCoords = unitsWithCoords + 1 + local unitType = unit:GetTypeName() or "Unknown" + table.insert(coords, { + name = unit:GetName(), + type = unitType, + mgrs = mgrs + }) + else + log(string.format("[TACTICAL DEBUG] All coordinate methods failed for unit %s", unit:GetName() or "unknown")) + end + else + log(string.format("[TACTICAL DEBUG] No coordinate for unit %s", unit:GetName() or "unknown")) + end + end + end + end) + end + + log(string.format("[TACTICAL DEBUG] %s - Total units scanned: %d, Enemy units: %d, units with MGRS: %d", + ZoneCapture:GetZoneName(), totalUnits, enemyUnits, unitsWithCoords)) + + log(string.format("[TACTICAL] Found %d enemy units with coordinates in %s", + #coords, ZoneCapture:GetZoneName())) + + return coords +end + +local function CreateTacticalInfoMarker(ZoneCapture) + -- Validate ZoneCapture + if not ZoneCapture then + log("[TACTICAL ERROR] ZoneCapture object is nil") + return + end + + -- Safely get the zone with error handling + local ok, zone = pcall(function() return ZoneCapture:GetZone() end) + if not ok or not zone then + log("[TACTICAL ERROR] Failed to get zone from ZoneCapture object") + return + end + + local forces = GetZoneForceStrengths(ZoneCapture) + local zoneName = ZoneCapture:GetZoneName() + + -- Build coalition-specific tactical info text + local function buildTacticalText(viewerCoalition) + local text = string.format("TACTICAL: %s\nForces: R:%d B:%d", zoneName, forces.red, forces.blue) + if forces.neutral and forces.neutral > 0 then + text = text .. string.format(" C:%d", forces.neutral) + end + + -- Append TGTS for the enemy of the viewer, capped at 10 units + local enemyCoalition = (viewerCoalition == coalition.side.BLUE) and coalition.side.RED or coalition.side.BLUE + local enemyCount = (enemyCoalition == coalition.side.RED) and (forces.red or 0) or (forces.blue or 0) + if enemyCount > 0 and enemyCount <= 10 then + local enemyCoords = GetEnemyUnitMGRSCoords(ZoneCapture, enemyCoalition) + log(string.format("[TACTICAL DEBUG] Building marker text for %s viewer: %d enemy units", (viewerCoalition==coalition.side.BLUE and "BLUE" or "RED"), #enemyCoords)) + if #enemyCoords > 0 then + text = text .. "\nTGTS:" + for i, unit in ipairs(enemyCoords) do + if i <= 10 then + local shortType = (unit.type or "Unknown"):gsub("^%w+%-", ""):gsub("%s.*", "") + local cleanMgrs = (unit.mgrs or ""):gsub("^MGRS%s+", ""):gsub("%s+", " ") + if i == 1 then + text = text .. string.format(" %s@%s", shortType, cleanMgrs) + else + text = text .. string.format(", %s@%s", shortType, cleanMgrs) + end + end + end + if #enemyCoords > 10 then + text = text .. string.format(" (+%d)", #enemyCoords - 10) + end + end + end + + return text + end + + local tacticalTextBLUE = buildTacticalText(coalition.side.BLUE) + local tacticalTextRED = buildTacticalText(coalition.side.RED) + + -- Debug: Log what will be displayed + log(string.format("[TACTICAL DEBUG] Marker text (BLUE) for %s:\n%s", zoneName, tacticalTextBLUE)) + log(string.format("[TACTICAL DEBUG] Marker text (RED) for %s:\n%s", zoneName, tacticalTextRED)) + + -- Create tactical marker offset from zone center + local coord = zone:GetCoordinate() + if coord then + local offsetCoord = coord:Translate(200, 45) -- 200m NE + + local function removeMarker(markerID) + if not markerID then + return + end + + local removed = pcall(function() + offsetCoord:RemoveMark(markerID) + end) + + if not removed then + removed = pcall(function() + trigger.action.removeMark(markerID) + end) + end + + if not removed then + pcall(function() + coord:RemoveMark(markerID) + end) + end + end + + -- Remove legacy single marker if present + if ZoneCapture.TacticalMarkerID then + log(string.format("[TACTICAL] Removing old marker ID %d for %s", ZoneCapture.TacticalMarkerID, zoneName)) + removeMarker(ZoneCapture.TacticalMarkerID) + ZoneCapture.TacticalMarkerID = nil + end + + -- BLUE Coalition Marker + if ZoneCapture.TacticalMarkerID_BLUE then + log(string.format("[TACTICAL] Removing old BLUE marker ID %d for %s", ZoneCapture.TacticalMarkerID_BLUE, zoneName)) + removeMarker(ZoneCapture.TacticalMarkerID_BLUE) + ZoneCapture.TacticalMarkerID_BLUE = nil + end + local successBlue, markerIDBlue = pcall(function() + return offsetCoord:MarkToCoalition(tacticalTextBLUE, coalition.side.BLUE) + end) + if successBlue and markerIDBlue then + ZoneCapture.TacticalMarkerID_BLUE = markerIDBlue + pcall(function() offsetCoord:SetMarkReadOnly(markerIDBlue, true) end) + log(string.format("[TACTICAL] Created BLUE marker for %s", zoneName)) + else + log(string.format("[TACTICAL] Failed to create BLUE marker for %s", zoneName)) + end + + -- RED Coalition Marker + if ZoneCapture.TacticalMarkerID_RED then + log(string.format("[TACTICAL] Removing old RED marker ID %d for %s", ZoneCapture.TacticalMarkerID_RED, zoneName)) + removeMarker(ZoneCapture.TacticalMarkerID_RED) + ZoneCapture.TacticalMarkerID_RED = nil + end + local successRed, markerIDRed = pcall(function() + return offsetCoord:MarkToCoalition(tacticalTextRED, coalition.side.RED) + end) + if successRed and markerIDRed then + ZoneCapture.TacticalMarkerID_RED = markerIDRed + pcall(function() offsetCoord:SetMarkReadOnly(markerIDRed, true) end) + log(string.format("[TACTICAL] Created RED marker for %s", zoneName)) + else + log(string.format("[TACTICAL] Failed to create RED marker for %s", zoneName)) + end + end +end + +-- Event handler functions - define them separately for each zone +local function OnEnterGuarded(ZoneCapture, From, Event, To) + if From ~= To then + local Coalition = ZoneCapture:GetCoalition() + if Coalition == coalition.side.BLUE then + ZoneCapture:Smoke( SMOKECOLOR.Blue ) + -- Update zone visual markers to BLUE + ZoneCapture:UndrawZone() + local color = ZONE_COLORS.BLUE_CAPTURED + ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true) + US_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION ) + RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION ) + else + ZoneCapture:Smoke( SMOKECOLOR.Red ) + -- Update zone visual markers to RED + ZoneCapture:UndrawZone() + local color = ZONE_COLORS.RED_CAPTURED + ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true) + RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION ) + US_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION ) + end + -- Create/update tactical information marker + CreateTacticalInfoMarker(ZoneCapture) + end +end + +local function OnEnterEmpty(ZoneCapture) + ZoneCapture:Smoke( SMOKECOLOR.Green ) + -- Update zone visual markers to GREEN (neutral) + ZoneCapture:UndrawZone() + local color = ZONE_COLORS.EMPTY + ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true) + US_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION ) + RU_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION ) + -- Create/update tactical information marker + CreateTacticalInfoMarker(ZoneCapture) +end + +local function OnEnterAttacked(ZoneCapture) + ZoneCapture:Smoke( SMOKECOLOR.White ) + -- Update zone visual markers based on owner (attacked state) + ZoneCapture:UndrawZone() + local Coalition = ZoneCapture:GetCoalition() + local color + if Coalition == coalition.side.BLUE then + color = ZONE_COLORS.BLUE_ATTACKED + US_CC:MessageTypeToCoalition( string.format( "%s is under attack by Russia", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION ) + RU_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION ) + else + color = ZONE_COLORS.RED_ATTACKED + RU_CC:MessageTypeToCoalition( string.format( "%s is under attack by the USA", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION ) + US_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION ) + end + ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true) + -- Create/update tactical information marker + CreateTacticalInfoMarker(ZoneCapture) +end + +-- Victory condition monitoring for BOTH coalitions +local function CheckVictoryCondition() + local blueZonesCount = 0 + local redZonesCount = 0 + local totalZones = #zoneCaptureObjects + + for i, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture then + local zoneCoalition = zoneCapture:GetCoalition() + if zoneCoalition == coalition.side.BLUE then + blueZonesCount = blueZonesCount + 1 + elseif zoneCoalition == coalition.side.RED then + redZonesCount = redZonesCount + 1 + end + end + end + + log(string.format("[VICTORY CHECK] Blue owns %d/%d zones, Red owns %d/%d zones", + blueZonesCount, totalZones, redZonesCount, totalZones)) + + -- Check for BLUE victory + if blueZonesCount >= totalZones then + log("[VICTORY] All zones captured by BLUE! Triggering victory sequence...") + + US_CC:MessageTypeToCoalition( + "VICTORY! All capture zones have been secured by coalition forces!\n\n" .. + "Operation Polar Shield is complete. Outstanding work!\n" .. + "Mission will end in 60 seconds.", + MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION + ) + + RU_CC:MessageTypeToCoalition( + "DEFEAT! All strategic positions have been lost to coalition forces.\n\n" .. + "Operation Polar Shield has failed. Mission ending in 60 seconds.", + MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION + ) + + -- Add victory celebration effects + for _, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture then + zoneCapture:Smoke( SMOKECOLOR.Blue ) + local zone = zoneCapture:GetZone() + if zone then + zone:FlareZone( FLARECOLOR.Blue, 90, 60 ) + end + end + end + + SCHEDULER:New( nil, function() + log("[VICTORY] Ending mission due to complete zone capture by BLUE") + trigger.action.setUserFlag("BLUE_VICTORY", 1) + + US_CC:MessageTypeToCoalition( + string.format("Mission Complete! Congratulations on your victory!\nFinal Status: All %d strategic zones secured.", totalZones), + MESSAGE.Type.Information, 10 + ) + end, {}, 60 ) + + return true + end + + -- Check for RED victory + if redZonesCount >= totalZones then + log("[VICTORY] All zones captured by RED! Triggering victory sequence...") + + RU_CC:MessageTypeToCoalition( + "VICTORY! All strategic positions secured for the Motherland!\n\n" .. + "NATO forces have been repelled. Outstanding work!\n" .. + "Mission will end in 60 seconds.", + MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION + ) + + US_CC:MessageTypeToCoalition( + "DEFEAT! All capture zones have been lost to Russian forces.\n\n" .. + "Operation Polar Shield has failed. Mission ending in 60 seconds.", + MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION + ) + + -- Add victory celebration effects + for _, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture then + zoneCapture:Smoke( SMOKECOLOR.Red ) + local zone = zoneCapture:GetZone() + if zone then + zone:FlareZone( FLARECOLOR.Red, 90, 60 ) + end + end + end + + SCHEDULER:New( nil, function() + log("[VICTORY] Ending mission due to complete zone capture by RED") + trigger.action.setUserFlag("RED_VICTORY", 1) + + RU_CC:MessageTypeToCoalition( + string.format("Mission Complete! Congratulations on your victory!\nFinal Status: All %d strategic zones secured.", totalZones), + MESSAGE.Type.Information, 10 + ) + end, {}, 60 ) + + return true + end + + return false -- Victory not yet achieved by either side +end + +local function OnEnterCaptured(ZoneCapture) + local Coalition = ZoneCapture:GetCoalition() + if Coalition == coalition.side.BLUE then + -- Update zone visual markers to BLUE for captured + ZoneCapture:UndrawZone() + local color = ZONE_COLORS.BLUE_CAPTURED + ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true) + RU_CC:MessageTypeToCoalition( string.format( "%s is captured by the USA, we lost it!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION ) + US_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION ) + else + -- Update zone visual markers to RED for captured + ZoneCapture:UndrawZone() + local color = ZONE_COLORS.RED_CAPTURED + ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true) + US_CC:MessageTypeToCoalition( string.format( "%s is captured by Russia, we lost it!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION ) + RU_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCapture:GetZoneName() ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION ) + end + + ZoneCapture:AddScore( "Captured", "Zone captured: Extra points granted.", ZONE_SETTINGS.captureScore ) + ZoneCapture:__Guard( 30 ) + + -- Create/update tactical information marker + CreateTacticalInfoMarker(ZoneCapture) + + -- Check victory condition after any zone capture + CheckVictoryCondition() +end + +-- Set up event handlers for each zone with proper MOOSE methods and debugging +for i, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture then + local zoneName = zoneNames[i] or ("Zone " .. i) + + -- Proper MOOSE event handlers for ZONE_CAPTURE_COALITION + zoneCapture.OnEnterGuarded = OnEnterGuarded + zoneCapture.OnEnterEmpty = OnEnterEmpty + zoneCapture.OnEnterAttacked = OnEnterAttacked + zoneCapture.OnEnterCaptured = OnEnterCaptured + + -- Debug: Check if the underlying zone exists + local success, zone = pcall(function() return zoneCapture:GetZone() end) + if success and zone then + log("✓ Zone '" .. zoneName .. "' successfully created and linked") + + -- Get initial coalition color for this zone + local initialCoalition = zoneCapture:GetCoalition() + local colorRGB = ZONE_COLORS.EMPTY + + if initialCoalition == coalition.side.RED then + colorRGB = ZONE_COLORS.RED_CAPTURED + elseif initialCoalition == coalition.side.BLUE then + colorRGB = ZONE_COLORS.BLUE_CAPTURED + end + + -- Initialize zone borders with appropriate initial color + local drawSuccess, drawError = pcall(function() + zone:DrawZone(-1, {0, 0, 0}, 1, colorRGB, 0.2, 2, true) + end) + + if not drawSuccess then + log("⚠ Zone 'Capture " .. zoneName .. "' border drawing failed: " .. tostring(drawError)) + -- Alternative: Try simpler zone marking + pcall(function() + if initialCoalition == coalition.side.RED then + zone:SmokeZone(SMOKECOLOR.Red, 30) + elseif initialCoalition == coalition.side.BLUE then + zone:SmokeZone(SMOKECOLOR.Blue, 30) + else + zone:SmokeZone(SMOKECOLOR.Green, 30) + end + end) + else + local coalitionName = "NEUTRAL" + if initialCoalition == coalition.side.RED then + coalitionName = "RED" + elseif initialCoalition == coalition.side.BLUE then + coalitionName = "BLUE" + end + log("✓ Zone '" .. zoneName .. "' border drawn successfully with " .. coalitionName .. " initial color") + end + else + log("✗ ERROR: Zone '" .. zoneName .. "' not found in mission editor!") + log(" Make sure you have a trigger zone named exactly: '" .. zoneName .. "'") + end + else + log("✗ ERROR: Zone capture object " .. i .. " (" .. (zoneNames[i] or "Unknown") .. ") is nil!") + end +end + +-- ========================================== +-- VICTORY MONITORING SYSTEM +-- ========================================== + +-- Function to get current zone ownership status +local function GetZoneOwnershipStatus() + local status = { + blue = 0, + red = 0, + neutral = 0, + total = #zoneCaptureObjects, + zones = {} + } + + -- Explicitly reference the global coalition table to avoid parameter shadowing + local coalitionTable = _G.coalition or coalition + + for i, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture then + local zoneCoalition = zoneCapture:GetCoalition() + local zoneName = zoneNames[i] or ("Zone " .. i) + + -- Get the current state of the zone + local currentState = zoneCapture:GetCurrentState() + local stateString = "" + + -- Determine status based on coalition and state + if zoneCoalition == coalitionTable.side.BLUE then + status.blue = status.blue + 1 + if currentState == "Attacked" then + status.zones[zoneName] = "BLUE (Under Attack)" + else + status.zones[zoneName] = "BLUE" + end + elseif zoneCoalition == coalitionTable.side.RED then + status.red = status.red + 1 + if currentState == "Attacked" then + status.zones[zoneName] = "RED (Under Attack)" + else + status.zones[zoneName] = "RED" + end + else + status.neutral = status.neutral + 1 + if currentState == "Attacked" then + status.zones[zoneName] = "NEUTRAL (Under Attack)" + else + status.zones[zoneName] = "NEUTRAL" + end + end + end + end + + return status +end + +-- Function to broadcast zone status report to BOTH coalitions +local function BroadcastZoneStatus() + local status = GetZoneOwnershipStatus() + + -- Build coalition-neutral report + local reportMessage = string.format( + "ZONE CONTROL REPORT:\n" .. + "Blue Coalition: %d/%d zones\n" .. + "Red Coalition: %d/%d zones\n" .. + "Neutral: %d/%d zones", + status.blue, status.total, + status.red, status.total, + status.neutral, status.total + ) + + -- Add detailed zone status + local detailMessage = "\nZONE DETAILS:\n" + for zoneName, owner in pairs(status.zones) do + detailMessage = detailMessage .. string.format("• %s: %s\n", zoneName, owner) + end + + local fullMessage = reportMessage .. detailMessage + + -- Broadcast to BOTH coalitions with their specific victory progress + local totalZones = math.max(status.total, 1) + local blueProgressPercent = math.floor((status.blue / totalZones) * 100) + local blueFullMessage = fullMessage .. string.format("\n\nYour Progress to Victory: %d%%", blueProgressPercent) + US_CC:MessageTypeToCoalition( blueFullMessage, MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION ) + + local redProgressPercent = math.floor((status.red / totalZones) * 100) + local redFullMessage = fullMessage .. string.format("\n\nYour Progress to Victory: %d%%", redProgressPercent) + RU_CC:MessageTypeToCoalition( redFullMessage, MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION ) + + log("[ZONE STATUS] " .. reportMessage:gsub("\n", " | ")) + + return status +end + +-- Periodic zone monitoring (every 5 minutes) for BOTH coalitions +local ZoneMonitorScheduler = SCHEDULER:New( nil, function() + local status = BroadcastZoneStatus() + + -- Check if BLUE is close to victory (80% or more zones captured) + if status.blue >= math.floor(status.total * 0.8) and status.blue < status.total then + US_CC:MessageTypeToCoalition( + string.format("APPROACHING VICTORY! %d more zone(s) needed for complete success!", + status.total - status.blue), + MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION + ) + + RU_CC:MessageTypeToCoalition( + string.format("CRITICAL SITUATION! Coalition forces control %d/%d zones! We must recapture territory!", + status.blue, status.total), + MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION + ) + end + + -- Check if RED is close to victory (80% or more zones captured) + if status.red >= math.floor(status.total * 0.8) and status.red < status.total then + RU_CC:MessageTypeToCoalition( + string.format("APPROACHING VICTORY! %d more zone(s) needed for complete success!", + status.total - status.red), + MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION + ) + + US_CC:MessageTypeToCoalition( + string.format("CRITICAL SITUATION! Russian forces control %d/%d zones! We must recapture territory!", + status.red, status.total), + MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION + ) + end + +end, {}, MESSAGE_CONFIG.STATUS_BROADCAST_START_DELAY, MESSAGE_CONFIG.STATUS_BROADCAST_FREQUENCY ) + +-- Periodic zone color verification system (every 2 minutes) +local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function() + log("[ZONE COLORS] Running periodic zone color verification...") + + -- Verify each zone's visual marker matches its CURRENT STATE (not just coalition) + for i, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture then + local zoneCoalition = zoneCapture:GetCoalition() + local zoneName = zoneNames[i] or ("Zone " .. i) + local currentState = zoneCapture:GetCurrentState() + + local zoneColor = GetZoneColor(zoneCapture) + + -- Force redraw the zone with correct color based on CURRENT STATE + zoneCapture:UndrawZone() + zoneCapture:DrawZone(-1, {0, 0, 0}, 1, zoneColor, 0.2, 2, true) + + -- Log the color assignment for debugging + local colorName = "UNKNOWN" + if currentState == "Attacked" then + colorName = (zoneCoalition == coalition.side.BLUE) and "LIGHT BLUE (Blue Attacked)" or "ORANGE (Red Attacked)" + elseif currentState == "Empty" then + colorName = "GREEN (Empty)" + elseif zoneCoalition == coalition.side.BLUE then + colorName = "BLUE (Owned)" + elseif zoneCoalition == coalition.side.RED then + colorName = "RED (Owned)" + else + colorName = "GREEN (Fallback)" + end + log(string.format("[ZONE COLORS] %s: Set to %s", zoneName, colorName)) + end + end + +end, {}, MESSAGE_CONFIG.COLOR_VERIFICATION_START_DELAY, MESSAGE_CONFIG.COLOR_VERIFICATION_FREQUENCY ) + +-- Periodic tactical marker update system with change detection +local __lastForceCountsByZone = {} +local TacticalMarkerUpdateScheduler = SCHEDULER:New( nil, function() + log("[TACTICAL] Running periodic tactical marker update (change-detected)...") + + for i, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture then + local zoneName = zoneCapture.GetZoneName and zoneCapture:GetZoneName() or (zoneNames[i] or ("Zone " .. i)) + local counts = GetZoneForceStrengths(zoneCapture) + local last = __lastForceCountsByZone[zoneName] + local changed = (not last) + or (last.red ~= counts.red) + or (last.blue ~= counts.blue) + or (last.neutral ~= counts.neutral) + + if changed then + __lastForceCountsByZone[zoneName] = { + red = counts.red, + blue = counts.blue, + neutral = counts.neutral + } + CreateTacticalInfoMarker(zoneCapture) + end + end + end + +end, {}, MESSAGE_CONFIG.TACTICAL_UPDATE_START_DELAY, MESSAGE_CONFIG.TACTICAL_UPDATE_FREQUENCY ) + +-- Function to refresh all zone colors based on current ownership +local function RefreshAllZoneColors() + log("[ZONE COLORS] Refreshing all zone visual markers...") + + for i, zoneCapture in ipairs(zoneCaptureObjects) do + if zoneCapture then + local zoneCoalition = zoneCapture:GetCoalition() + local zoneName = zoneNames[i] or ("Zone " .. i) + local currentState = zoneCapture:GetCurrentState() + + -- Get color for current state/ownership + local zoneColor = GetZoneColor(zoneCapture) + + -- Clear existing drawings + zoneCapture:UndrawZone() + + -- Redraw with correct color + zoneCapture:DrawZone(-1, {0, 0, 0}, 1, zoneColor, 0.2, 2, true) + + -- Log the color assignment for debugging + local colorName = "UNKNOWN" + if currentState == "Attacked" then + colorName = (zoneCoalition == coalition.side.BLUE) and "LIGHT BLUE (Blue Attacked)" or "ORANGE (Red Attacked)" + elseif currentState == "Empty" then + colorName = "GREEN (Empty)" + elseif zoneCoalition == coalition.side.BLUE then + colorName = "BLUE (Owned)" + elseif zoneCoalition == coalition.side.RED then + colorName = "RED (Owned)" + else + colorName = "GREEN (Fallback)" + end + log(string.format("[ZONE COLORS] %s: Set to %s", zoneName, colorName)) + end + end + + -- Notify BOTH coalitions + US_CC:MessageTypeToCoalition("Zone visual markers have been refreshed!", MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION) + RU_CC:MessageTypeToCoalition("Zone visual markers have been refreshed!", MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION) +end + +-- Manual zone status commands for players (F10 radio menu) - BOTH COALITIONS +local function SetupZoneStatusCommands() + -- Add F10 radio menu commands for BLUE coalition + if US_CC then + -- Use MenuManager to create zone control menu under Mission Options + local USMenu = MenuManager and MenuManager.CreateCoalitionMenu(coalition.side.BLUE, "Zone Control") + or MENU_COALITION:New( coalition.side.BLUE, "Zone Control" ) + MENU_COALITION_COMMAND:New( coalition.side.BLUE, "Get Zone Status Report", USMenu, BroadcastZoneStatus ) + + MENU_COALITION_COMMAND:New( coalition.side.BLUE, "Check Victory Progress", USMenu, function() + local status = GetZoneOwnershipStatus() + local totalZones = math.max(status.total, 1) + local progressPercent = math.floor((status.blue / totalZones) * 100) + + US_CC:MessageTypeToCoalition( + string.format( + "VICTORY PROGRESS: %d%%\n" .. + "Zones Captured: %d/%d\n" .. + "Remaining: %d zones\n\n" .. + "%s", + progressPercent, + status.blue, status.total, + status.total - status.blue, + progressPercent >= 100 and "MISSION COMPLETE!" or + progressPercent >= 80 and "ALMOST THERE!" or + progressPercent >= 50 and "GOOD PROGRESS!" or + "KEEP FIGHTING!" + ), + MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION + ) + end ) + + -- Add command to refresh zone colors (troubleshooting tool) + MENU_COALITION_COMMAND:New( coalition.side.BLUE, "Refresh Zone Colors", USMenu, RefreshAllZoneColors ) + end + + -- Add F10 radio menu commands for RED coalition + if RU_CC then + -- Use MenuManager to create zone control menu under Mission Options + local RUMenu = MenuManager and MenuManager.CreateCoalitionMenu(coalition.side.RED, "Zone Control") + or MENU_COALITION:New( coalition.side.RED, "Zone Control" ) + MENU_COALITION_COMMAND:New( coalition.side.RED, "Get Zone Status Report", RUMenu, BroadcastZoneStatus ) + + MENU_COALITION_COMMAND:New( coalition.side.RED, "Check Victory Progress", RUMenu, function() + local status = GetZoneOwnershipStatus() + local totalZones = math.max(status.total, 1) + local progressPercent = math.floor((status.red / totalZones) * 100) + + RU_CC:MessageTypeToCoalition( + string.format( + "VICTORY PROGRESS: %d%%\n" .. + "Zones Captured: %d/%d\n" .. + "Remaining: %d zones\n\n" .. + "%s", + progressPercent, + status.red, status.total, + status.total - status.red, + progressPercent >= 100 and "MISSION COMPLETE!" or + progressPercent >= 80 and "ALMOST THERE!" or + progressPercent >= 50 and "GOOD PROGRESS!" or + "KEEP FIGHTING!" + ), + MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION + ) + end ) + + -- Add command to refresh zone colors (troubleshooting tool) + MENU_COALITION_COMMAND:New( coalition.side.RED, "Refresh Zone Colors", RUMenu, RefreshAllZoneColors ) + end +end + +-- Initialize zone status monitoring +SCHEDULER:New( nil, function() + log("[VICTORY SYSTEM] Initializing zone monitoring system...") + + -- Initialize performance optimization caches + InitializeCachedUnitSet() + + SetupZoneStatusCommands() + + -- Initial status report + SCHEDULER:New( nil, function() + log("[VICTORY SYSTEM] Broadcasting initial zone status...") + BroadcastZoneStatus() + end, {}, 30 ) -- Initial report after 30 seconds + +end, {}, 5 ) -- Initialize after 5 seconds + +log("[VICTORY SYSTEM] Zone capture victory monitoring system loaded successfully!") +log(string.format("[CONFIG] Loaded %d zones from configuration", totalZones)) diff --git a/Moose_DualCoalitionZoneCapture.miz b/Moose_DualCoalitionZoneCapture.miz new file mode 100644 index 0000000..5b69833 Binary files /dev/null and b/Moose_DualCoalitionZoneCapture.miz differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9554990 --- /dev/null +++ b/README.md @@ -0,0 +1,288 @@ +# MOOSE Dual Coalition Zone Capture System + +A dynamic zone capture and control system for DCS World missions using the MOOSE framework. This script enables territory-based gameplay where RED and BLUE coalitions compete to capture and hold strategic zones across the battlefield. + +![Version](https://img.shields.io/badge/version-2.0-blue) +![DCS](https://img.shields.io/badge/DCS-2.9%2B-green) +![MOOSE](https://img.shields.io/badge/MOOSE-Latest-orange) +![License](https://img.shields.io/badge/license-MIT-lightgrey) + +## 🎯 Features + +- **🎨 Visual Feedback**: Color-coded zone boundaries (Red/Blue/Green/Orange) that change dynamically +- **💨 Smoke Signals**: Automatic smoke markers indicating zone status +- **📍 Tactical Information**: Real-time force composition and MGRS coordinates for enemies +- **🏆 Victory Conditions**: Automatic win detection when one coalition captures all zones +- **📻 F10 Radio Menu**: Player-accessible status reports and progress tracking +- **⚙️ Highly Configurable**: Simple zone ownership configuration via Lua tables +- **🔄 Dual Coalition**: Full support for both RED and BLUE coalitions +- **📊 Auto-Reporting**: Periodic status updates every 5 minutes +- **🎮 Player-Friendly**: Clear messaging and intuitive state transitions + +## 🚀 Quick Start + +### Prerequisites + +1. **DCS World** (version 2.9 or higher) +2. **MOOSE Framework** ([Download here](https://github.com/FlightControl-Master/MOOSE)) +3. Basic knowledge of DCS Mission Editor + +### Installation + +1. **Download the files:** + - `Moose_DualCoalitionZoneCapture.lua` - Main script + - `Moose_DualCoalitionZoneCapture.miz` - Example mission + - `Moose_.lua` - MOOSE framework (get latest version) + +2. **In DCS Mission Editor:** + - Create trigger zones for each capture point (e.g., "Capture Zone-1", "Capture Severomorsk") + - Create two groups: `BLUEHQ` (any BLUE ground unit) and `REDHQ` (any RED ground unit) + +3. **Configure zones** in `Moose_DualCoalitionZoneCapture.lua`: + +```lua +local ZONE_CONFIG = { + RED = { + "Capture Zone-1", + "Capture Zone-2" + }, + BLUE = { + "Capture Zone-3", + "Capture Zone-4" + }, + NEUTRAL = { + -- Empty zones at mission start + } +} +``` + +4. **Load scripts** via Mission Start trigger: + - Action 1: DO SCRIPT FILE → `Moose_.lua` + - Action 2: DO SCRIPT FILE → `Moose_DualCoalitionZoneCapture.lua` + +5. **Save and test** your mission! + +## 📖 How It Works + +### Zone States + +Zones transition between four distinct states: + +| State | Color | Smoke | Description | +|-------|-------|-------|-------------| +| **RED Controlled** | 🔴 Red Border | Red | Zone secured by RED coalition | +| **BLUE Controlled** | 🔵 Blue Border | Blue | Zone secured by BLUE coalition | +| **Neutral/Empty** | 🟢 Green Border | Green | Uncontrolled, ready for capture | +| **Contested** | 🟠 Orange Border | White | Multiple coalitions present - fighting for control | + +### Capture Mechanics + +- **To Capture**: Move ground units into a zone +- **To Hold**: Eliminate all enemy forces in the zone +- **To Win**: Capture ALL zones on the map + +The script automatically scans zones every 30 seconds (configurable) and updates ownership based on unit presence. + +### Tactical Information Markers + +Each zone displays real-time tactical data: + +``` +TACTICAL: Capture Severomorsk-1 +Forces: R:5 B:12 +TGTS: T-90@38U LV 12345 67890, BTR-80@38U LV 12346 67891 +``` + +- **Force Counts**: Number of units per coalition +- **MGRS Coordinates**: Precise enemy locations (when ≤10 units) +- **Coalition-Specific**: Each side sees their enemies marked + +## ⚙️ Configuration Options + +### Zone Settings + +```lua +local ZONE_SETTINGS = { + guardDelay = 1, -- Seconds before entering Guard state after capture + scanInterval = 30, -- How often to scan for units (seconds) + captureScore = 200 -- Points awarded for zone capture +} +``` + +### Performance Tuning + +For missions with many units: +```lua +scanInterval = 60 -- Scan less frequently +``` + +For fast-paced action: +```lua +scanInterval = 15 -- More responsive zone changes +``` + +### Logging Control + +Disable detailed logging: +```lua +CAPTURE_ZONE_LOGGING = { enabled = false } +``` + +## 👥 Player Features + +### F10 Radio Menu Commands + +Players access zone information via **F10 → Zone Control**: + +- **Get Zone Status Report**: Current ownership of all zones +- **Check Victory Progress**: Percentage toward victory +- **Refresh Zone Colors**: Manually redraw zone boundaries + +### Automatic Notifications + +- ✅ Zone capture/loss announcements +- ⚠️ Attack warnings when zones are contested +- 📊 Status reports every 5 minutes +- 🏆 Victory alerts at 80% and 100% completion +- 🎉 Victory countdown with celebratory effects + +## 🎮 Example Mission + +The included `Moose_DualCoalitionZoneCapture.miz` demonstrates: + +- Proper zone configuration +- HQ group placement +- Script loading order +- AI patrol patterns for testing +- All visual and messaging features + +**Use this mission as a template for your own scenarios!** + +## 🔧 Troubleshooting + +### Common Issues + +#### ❌ Script Won't Load +**Error**: "attempt to index a nil value" +- **Cause**: MOOSE not loaded first +- **Fix**: Ensure load order is MOOSE → Capture Script + +#### ❌ Zone Not Found +**Error**: "Zone 'X' not found in mission editor!" +- **Cause**: Zone name mismatch +- **Fix**: Verify zone names match EXACTLY (case-sensitive!) + +#### ⚠️ Zones Not Capturing +- Only ground units, planes, and helicopters are scanned +- Wait 30 seconds for scan cycle +- Eliminate ALL enemy forces to capture +- Check DCS.log for detailed information + +### Checking Logs + +Open `Saved Games\DCS\Logs\DCS.log` and search for: +- `[CAPTURE Module]` - General logging +- `[INIT]` - Initialization messages +- `[TACTICAL]` - Tactical marker updates +- `[VICTORY]` - Victory condition checks + +## 🏗️ Mission Design Tips + +### Best Practices + +- **Zone Size**: Large enough for tactical areas, avoid overlaps +- **Zone Placement**: Position over airbases, FOBs, strategic terrain +- **Starting Balance**: Consider defensive vs. offensive scenarios +- **AI Behavior**: Use "Ground Hold" or "Ground On Road" waypoints +- **Player Briefing**: Document F10 menu commands in mission brief + +### Integration with Other Scripts + +Access zone data from other scripts: + +```lua +-- Get current ownership status +local status = GetZoneOwnershipStatus() +-- Returns: { blue = X, red = Y, neutral = Z, total = N, zones = {...} } + +-- Manual status broadcast +BroadcastZoneStatus() + +-- Refresh zone visuals +RefreshAllZoneColors() +``` + +### Victory Flags + +The script sets user flags on victory: +- `BLUE_VICTORY = 1` when BLUE wins +- `RED_VICTORY = 1` when RED wins + +Use these in triggers to end missions or transition to next phase. + +## 📋 Requirements + +### Essential Components + +- ✅ DCS World 2.9 or higher +- ✅ MOOSE Framework (latest version) +- ✅ Trigger zones in mission editor +- ✅ BLUEHQ and REDHQ groups + +### Mission Prerequisites + +- At least one trigger zone per capture point +- Exact zone name matching between editor and Lua config +- Both HQ groups must exist (can be hidden/inactive) + +## 📞 Support & Resources + +### Get Help + +- **Discord Community**: [https://discord.gg/7wBVWKK3](https://discord.gg/7wBVWKK3) +- **Author**: F99th-TracerFacer +- **GitHub Issues**: Report bugs or request features + +### Additional Resources + +- [MOOSE Documentation](https://flightcontrol-master.github.io/MOOSE_DOCS/) +- [MOOSE Discord](https://discord.gg/gj68fm969S) +- [DCS Forums](https://forum.dcs.world) + +## 📄 License + +This script is provided free for use in DCS World missions. Feel free to modify and distribute. + +## 🙏 Credits + +- **Author**: F99th-TracerFacer +- **Framework**: MOOSE by FlightControl +- **Community**: DCS World Mission Makers + +## 🎯 Version History + +### Version 2.0 (Current) +- ✨ Full dual coalition support (RED & BLUE) +- ✨ Tactical information markers with MGRS coordinates +- ✨ Auto-victory detection and countdown +- ✨ F10 radio menu commands +- ✨ Periodic status reports +- ✨ Enhanced visual feedback system +- ✨ Configurable zone ownership via Lua tables + +### Version 1.0 +- Initial release +- Basic zone capture mechanics +- Single coalition focus + +--- + +
+ +**🎮 Happy Mission Making! 🚁** + +*Created with ❤️ for the DCS World Community* + +[Discord](https://discord.gg/7wBVWKK3) • [Documentation](Mission_Maker_Guide.html) • [Report Issue](#) + +