Added perplayer multi language support.

This commit is contained in:
iTracerFacer
2025-12-12 13:00:09 -06:00
parent d54a04a212
commit 63006a3d99
8 changed files with 4968 additions and 533 deletions
+655 -179
View File
@@ -38,35 +38,6 @@ local MESSAGE_CONFIG = {
GARBAGE_COLLECTION_FREQUENCY = 600 -- Lua garbage collection cadence (seconds) - helps prevent memory buildup
}
-- ==========================================
-- 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)
}
-- ==========================================
-- COALITION TITLES CONFIGURATION
-- ==========================================
-- Mission makers: Customize the display names for each coalition
-- These will be used in messages, mission names, and UI elements
local COALITION_TITLES = {
BLUE = "USA", -- Display name for Blue coalition (e.g., "USA", "NATO", "Allied Forces")
RED = "Russia", -- Display name for Red coalition (e.g., "Russia", "Germany", "Axis Powers")
BLUE_OPERATION = "Operation Polar Shield", -- Name of Blue coalition's operation
RED_OPERATION = "Defend the Motherland" -- Name of Red coalition's operation
}
-- ==========================================
-- ZONE CONFIGURATION
-- ==========================================
@@ -109,6 +80,422 @@ local ZONE_SETTINGS = {
captureScore = 200 -- Points awarded for capturing a zone
}
-- ==========================================
-- 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)
}
-- ==========================================
-- COALITION TITLES CONFIGURATION
-- ==========================================
-- Mission makers: Customize the display names for each coalition
-- These will be used in messages, mission names, and UI elements
local COALITION_TITLES = {
BLUE = "USA", -- Display name for Blue coalition (e.g., "USA", "NATO", "Allied Forces")
RED = "Russia", -- Display name for Red coalition (e.g., "Russia", "Germany", "Axis Powers")
BLUE_OPERATION = "Operation Polar Shield", -- Name of Blue coalition's operation
RED_OPERATION = "Defend the Motherland" -- Name of Red coalition's operation
}
-- ==========================================
-- LANGUAGE CONFIGURATION
-- ==========================================
local LANGUAGE_CONFIG = {
defaultLanguage = "EN", -- Default language: "EN", "DE", "FR", "ES", "RU"
}
-- Multilingual message table
-- Each language contains all messages and menu text
local ZONE_CAPTURE_LANGUAGES = {
EN = {
-- Zone state messages
zoneGuarded = "%s is under protection of the %s",
zoneEmpty = "%s is unprotected, and can be captured!",
zoneUnderAttack = "%s is under attack by %s",
zoneWeAttacking = "We are attacking %s",
zoneCapturedByEnemy = "%s is captured by the %s, we lost it!",
zoneCapturedByUs = "We captured %s, Excellent job!",
-- Victory messages
victoryBlue = "VICTORY! All capture zones have been secured by %s forces!\n\n%s is complete. Outstanding work!\nMission will end in 60 seconds.",
defeatBlue = "DEFEAT! All strategic positions have been lost to %s forces.\n\n%s has failed. Mission ending in 60 seconds.",
victoryRed = "VICTORY! All strategic positions secured for %s!\n\n%s forces have been repelled. Outstanding work!\nMission will end in 60 seconds.",
defeatRed = "DEFEAT! All capture zones have been lost to %s forces.\n\n%s has failed. Mission ending in 60 seconds.",
missionComplete = "Mission Complete! Congratulations on your victory!\nFinal Status: All %d strategic zones secured.",
-- Status report messages
zoneControlReport = "ZONE CONTROL REPORT:\nBlue Coalition: %d/%d zones\nRed Coalition: %d/%d zones\nNeutral: %d/%d zones",
zoneDetails = "\nZONE DETAILS:\n",
zoneDetailLine = "• %s: %s\n",
yourProgress = "\n\nYour Progress to Victory: %d%%",
-- Progress warning messages
blueNearVictory = "TACTICAL ALERT: %s forces control %d%% of strategic zones!\nVictory is within reach!",
redNearVictory = "WARNING: %s forces have captured %d%% of zones!\nDefend our positions!",
-- Coalition names
coalitionBlue = "Blue Coalition",
coalitionRed = "Red Coalition",
coalitionNeutral = "Neutral",
-- Menu items
menuZoneControl = "Zone Control",
menuStatusReport = "Get Zone Status Report",
menuVictoryProgress = "Check Victory Progress",
menuRefreshColors = "Refresh Zone Colors",
menuLanguage = "Language / Sprache / Langue",
menuEnglish = "English",
menuGerman = "Deutsch (German)",
menuFrench = "Français (French)",
menuSpanish = "Español (Spanish)",
menuRussian = "Русский (Russian)",
-- Victory progress messages
victoryProgressTitle = "VICTORY PROGRESS: %d%%",
zonesCaptured = "Zones Captured: %d/%d",
zonesRemaining = "Remaining: %d zones",
progressComplete = "MISSION COMPLETE!",
progressAlmostThere = "ALMOST THERE!",
progressGood = "GOOD PROGRESS!",
progressKeepFighting = "KEEP FIGHTING!",
-- System messages
zoneMarkersRefreshed = "Zone visual markers have been refreshed!",
languageChanged = "Language changed to English",
},
DE = {
-- Zone state messages
zoneGuarded = "%s steht unter dem Schutz der %s",
zoneEmpty = "%s ist ungeschützt und kann erobert werden!",
zoneUnderAttack = "%s wird von %s angegriffen",
zoneWeAttacking = "Wir greifen %s an",
zoneCapturedByEnemy = "%s wurde von %s erobert, wir haben es verloren!",
zoneCapturedByUs = "Wir haben %s erobert, hervorragende Arbeit!",
-- Victory messages
victoryBlue = "SIEG! Alle Eroberungszonen wurden von %s Streitkräften gesichert!\n\n%s ist abgeschlossen. Hervorragende Arbeit!\nMission endet in 60 Sekunden.",
defeatBlue = "NIEDERLAGE! Alle strategischen Positionen wurden an %s Streitkräfte verloren.\n\n%s ist gescheitert. Mission endet in 60 Sekunden.",
victoryRed = "SIEG! Alle strategischen Positionen für %s gesichert!\n\n%s Streitkräfte wurden zurückgeschlagen. Hervorragende Arbeit!\nMission endet in 60 Sekunden.",
defeatRed = "NIEDERLAGE! Alle Eroberungszonen wurden an %s Streitkräfte verloren.\n\n%s ist gescheitert. Mission endet in 60 Sekunden.",
missionComplete = "Mission erfolgreich! Herzlichen Glückwunsch zu Ihrem Sieg!\nEndstatus: Alle %d strategischen Zonen gesichert.",
-- Status report messages
zoneControlReport = "ZONENKONTROLLBERICHT:\nBlaue Koalition: %d/%d Zonen\nRote Koalition: %d/%d Zonen\nNeutral: %d/%d Zonen",
zoneDetails = "\nZONENDETAILS:\n",
zoneDetailLine = "• %s: %s\n",
yourProgress = "\n\nIhr Fortschritt zum Sieg: %d%%",
-- Progress warning messages
blueNearVictory = "TAKTISCHER ALARM: %s Streitkräfte kontrollieren %d%% der strategischen Zonen!\nSieg ist in Reichweite!",
redNearVictory = "WARNUNG: %s Streitkräfte haben %d%% der Zonen erobert!\nVerteidigt unsere Positionen!",
-- Coalition names
coalitionBlue = "Blaue Koalition",
coalitionRed = "Rote Koalition",
coalitionNeutral = "Neutral",
-- Menu items
menuZoneControl = "Zonenkontrolle",
menuStatusReport = "Zonenstatusbericht abrufen",
menuVictoryProgress = "Siegfortschritt prüfen",
menuRefreshColors = "Zonenfarben aktualisieren",
menuLanguage = "Language / Sprache / Langue",
menuEnglish = "English",
menuGerman = "Deutsch (German)",
menuFrench = "Français (French)",
menuSpanish = "Español (Spanish)",
menuRussian = "Русский (Russian)",
-- Victory progress messages
victoryProgressTitle = "SIEGFORTSCHRITT: %d%%",
zonesCaptured = "Zonen erobert: %d/%d",
zonesRemaining = "Verbleibend: %d Zonen",
progressComplete = "MISSION ABGESCHLOSSEN!",
progressAlmostThere = "FAST GESCHAFFT!",
progressGood = "GUTER FORTSCHRITT!",
progressKeepFighting = "WEITER KÄMPFEN!",
-- System messages
zoneMarkersRefreshed = "Zonenmarkierungen wurden aktualisiert!",
languageChanged = "Sprache auf Deutsch geändert",
},
FR = {
-- Zone state messages
zoneGuarded = "%s est sous la protection de %s",
zoneEmpty = "%s est sans protection et peut être capturée !",
zoneUnderAttack = "%s est attaquée par %s",
zoneWeAttacking = "Nous attaquons %s",
zoneCapturedByEnemy = "%s est capturée par %s, nous l'avons perdue !",
zoneCapturedByUs = "Nous avons capturé %s, excellent travail !",
-- Victory messages
victoryBlue = "VICTOIRE ! Toutes les zones de capture ont été sécurisées par les forces %s !\n\n%s est terminée. Excellent travail !\nLa mission se terminera dans 60 secondes.",
defeatBlue = "DÉFAITE ! Toutes les positions stratégiques ont été perdues face aux forces %s.\n\n%s a échoué. Mission se terminant dans 60 secondes.",
victoryRed = "VICTOIRE ! Toutes les positions stratégiques sécurisées pour %s !\n\nLes forces %s ont été repoussées. Excellent travail !\nLa mission se terminera dans 60 secondes.",
defeatRed = "DÉFAITE ! Toutes les zones de capture ont été perdues face aux forces %s.\n\n%s a échoué. Mission se terminant dans 60 secondes.",
missionComplete = "Mission accomplie ! Félicitations pour votre victoire !\nStatut final : Toutes les %d zones stratégiques sécurisées.",
-- Status report messages
zoneControlReport = "RAPPORT DE CONTRÔLE DES ZONES :\nCoalition bleue : %d/%d zones\nCoalition rouge : %d/%d zones\nNeutre : %d/%d zones",
zoneDetails = "\nDÉTAILS DES ZONES :\n",
zoneDetailLine = "• %s : %s\n",
yourProgress = "\n\nVotre progression vers la victoire : %d%%",
-- Progress warning messages
blueNearVictory = "ALERTE TACTIQUE : Les forces %s contrôlent %d%% des zones stratégiques !\nLa victoire est à portée de main !",
redNearVictory = "AVERTISSEMENT : Les forces %s ont capturé %d%% des zones !\nDéfendez nos positions !",
-- Coalition names
coalitionBlue = "Coalition bleue",
coalitionRed = "Coalition rouge",
coalitionNeutral = "Neutre",
-- Menu items
menuZoneControl = "Contrôle des zones",
menuStatusReport = "Obtenir le rapport d'état des zones",
menuVictoryProgress = "Vérifier la progression vers la victoire",
menuRefreshColors = "Actualiser les couleurs des zones",
menuLanguage = "Language / Sprache / Langue",
menuEnglish = "English",
menuGerman = "Deutsch (German)",
menuFrench = "Français (French)",
menuSpanish = "Español (Spanish)",
menuRussian = "Русский (Russian)",
-- Victory progress messages
victoryProgressTitle = "PROGRÈS DE LA VICTOIRE : %d%%",
zonesCaptured = "Zones capturées : %d/%d",
zonesRemaining = "Restantes : %d zones",
progressComplete = "MISSION ACCOMPLIE !",
progressAlmostThere = "PRESQUE TERMINÉ !",
progressGood = "BON PROGRÈS !",
progressKeepFighting = "CONTINUEZ À COMBATTRE !",
-- System messages
zoneMarkersRefreshed = "Les marqueurs de zone ont été actualisés !",
languageChanged = "Langue changée en français",
},
ES = {
-- Zone state messages
zoneGuarded = "%s está bajo la protección de %s",
zoneEmpty = "¡%s está desprotegida y puede ser capturada!",
zoneUnderAttack = "%s está siendo atacada por %s",
zoneWeAttacking = "Estamos atacando %s",
zoneCapturedByEnemy = "¡%s ha sido capturada por %s, la hemos perdido!",
zoneCapturedByUs = "¡Hemos capturado %s, excelente trabajo!",
-- Victory messages
victoryBlue = "¡VICTORIA! ¡Todas las zonas de captura han sido aseguradas por las fuerzas %s!\n\n%s está completa. ¡Excelente trabajo!\nLa misión terminará en 60 segundos.",
defeatBlue = "¡DERROTA! Todas las posiciones estratégicas se han perdido ante las fuerzas %s.\n\n%s ha fallado. Misión terminando en 60 segundos.",
victoryRed = "¡VICTORIA! ¡Todas las posiciones estratégicas aseguradas para %s!\n\n¡Las fuerzas %s han sido repelidas. Excelente trabajo!\nLa misión terminará en 60 segundos.",
defeatRed = "¡DERROTA! Todas las zonas de captura se han perdido ante las fuerzas %s.\n\n%s ha fallado. Misión terminando en 60 segundos.",
missionComplete = "¡Misión completada! ¡Felicitaciones por tu victoria!\nEstado final: Todas las %d zonas estratégicas aseguradas.",
-- Status report messages
zoneControlReport = "INFORME DE CONTROL DE ZONAS:\nCoalición Azul: %d/%d zonas\nCoalición Roja: %d/%d zonas\nNeutral: %d/%d zonas",
zoneDetails = "\nDETALLES DE ZONAS:\n",
zoneDetailLine = "• %s: %s\n",
yourProgress = "\n\nTu progreso hacia la victoria: %d%%",
-- Progress warning messages
blueNearVictory = "ALERTA TÁCTICA: ¡Las fuerzas %s controlan el %d%% de las zonas estratégicas!\n¡La victoria está al alcance!",
redNearVictory = "ADVERTENCIA: ¡Las fuerzas %s han capturado el %d%% de las zonas!\n¡Defiende nuestras posiciones!",
-- Coalition names
coalitionBlue = "Coalición Azul",
coalitionRed = "Coalición Roja",
coalitionNeutral = "Neutral",
-- Menu items
menuZoneControl = "Control de zonas",
menuStatusReport = "Obtener informe de estado de zonas",
menuVictoryProgress = "Verificar progreso de victoria",
menuRefreshColors = "Actualizar colores de zonas",
menuLanguage = "Language / Sprache / Langue",
menuEnglish = "English",
menuGerman = "Deutsch (German)",
menuFrench = "Français (French)",
menuSpanish = "Español (Spanish)",
menuRussian = "Русский (Russian)",
-- Victory progress messages
victoryProgressTitle = "PROGRESO DE VICTORIA: %d%%",
zonesCaptured = "Zonas capturadas: %d/%d",
zonesRemaining = "Restantes: %d zonas",
progressComplete = "¡MISIÓN COMPLETADA!",
progressAlmostThere = "¡CASI TERMINADO!",
progressGood = "¡BUEN PROGRESO!",
progressKeepFighting = "¡SIGUE LUCHANDO!",
-- System messages
zoneMarkersRefreshed = "¡Los marcadores de zona se han actualizado!",
languageChanged = "Idioma cambiado a español",
},
RU = {
-- Zone state messages
zoneGuarded = "%s находится под защитой %s",
zoneEmpty = "%s не защищена и может быть захвачена!",
zoneUnderAttack = "%s атакована силами %s",
zoneWeAttacking = "Мы атакуем %s",
zoneCapturedByEnemy = "%s захвачена %s, мы её потеряли!",
zoneCapturedByUs = "Мы захватили %s, отличная работа!",
-- Victory messages
victoryBlue = "ПОБЕДА! Все зоны захвата обеспечены силами %s!\n\n%s завершена. Отличная работа!\nМиссия закончится через 60 секунд.",
defeatBlue = "ПОРАЖЕНИЕ! Все стратегические позиции потеряны силами %s.\n\n%s провалена. Миссия заканчивается через 60 секунд.",
victoryRed = "ПОБЕДА! Все стратегические позиции обеспечены для %s!\n\nСилы %s отброшены. Отличная работа!\nМиссия закончится через 60 секунд.",
defeatRed = "ПОРАЖЕНИЕ! Все зоны захвата потеряны силами %s.\n\n%s провалена. Миссия заканчивается через 60 секунд.",
missionComplete = "Миссия выполнена! Поздравляем с победой!\nИтоговый статус: Все %d стратегических зон обеспечены.",
-- Status report messages
zoneControlReport = "ОТЧЁТ О КОНТРОЛЕ ЗОН:\nСиняя коалиция: %d/%d зон\nКрасная коалиция: %d/%d зон\nНейтральные: %d/%d зон",
zoneDetails = "\nПОДРОБНОСТИ ЗОН:\n",
zoneDetailLine = "• %s: %s\n",
yourProgress = "\n\nВаш прогресс к победе: %d%%",
-- Progress warning messages
blueNearVictory = "ТАКТИЧЕСКАЯ ТРЕВОГА: Силы %s контролируют %d%% стратегических зон!\nПобеда близка!",
redNearVictory = "ВНИМАНИЕ: Силы %s захватили %d%% зон!\nЗащищайте наши позиции!",
-- Coalition names
coalitionBlue = "Синяя коалиция",
coalitionRed = "Красная коалиция",
coalitionNeutral = "Нейтральные",
-- Menu items
menuZoneControl = "Контроль зон",
menuStatusReport = "Получить отчёт о статусе зон",
menuVictoryProgress = "Проверить прогресс победы",
menuRefreshColors = "Обновить цвета зон",
menuLanguage = "Language / Sprache / Langue",
menuEnglish = "English",
menuGerman = "Deutsch (German)",
menuFrench = "Français (French)",
menuSpanish = "Español (Spanish)",
menuRussian = "Русский (Russian)",
-- Victory progress messages
victoryProgressTitle = "ПРОГРЕСС ПОБЕДЫ: %d%%",
zonesCaptured = "Зоны захвачены: %d/%d",
zonesRemaining = "Осталось: %d зон",
progressComplete = "МИССИЯ ВЫПОЛНЕНА!",
progressAlmostThere = "ПОЧТИ ГОТОВО!",
progressGood = "ХОРОШИЙ ПРОГРЕСС!",
progressKeepFighting = "ПРОДОЛЖАЙТЕ БОРОТЬСЯ!",
-- System messages
zoneMarkersRefreshed = "Маркеры зон обновлены!",
languageChanged = "Язык изменён на русский",
}
}
-- ==========================================
-- PLAYER LANGUAGE TRACKING
-- ==========================================
-- Store per-player language preferences
local playerLanguages = {}
-- Helper function to get player's language preference
local function GetPlayerLanguage(playerName)
if playerName and playerLanguages[playerName] then
return playerLanguages[playerName]
end
return LANGUAGE_CONFIG.defaultLanguage
end
-- Helper function to set player's language preference
local function SetPlayerLanguage(playerName, language)
if playerName and language then
playerLanguages[playerName] = language
env.info(string.format("[LANGUAGE] Player %s language set to %s", playerName, language))
end
end
-- Helper function to get translated text
local function GetText(textKey, playerName)
local lang = GetPlayerLanguage(playerName)
local langTable = ZONE_CAPTURE_LANGUAGES[lang] or ZONE_CAPTURE_LANGUAGES.EN
return langTable[textKey] or textKey
end
-- Helper function to get coalition-specific text
local function GetCoalitionText(coalitionSide, playerName)
local lang = GetPlayerLanguage(playerName)
local langTable = ZONE_CAPTURE_LANGUAGES[lang] or ZONE_CAPTURE_LANGUAGES.EN
if coalitionSide == coalition.side.BLUE then
return langTable.coalitionBlue
elseif coalitionSide == coalition.side.RED then
return langTable.coalitionRed
else
return langTable.coalitionNeutral
end
end
-- Helper function to send message to coalition with language support
local function MessageToCoalition(commandCenter, messageKey, params, coalitionSide, duration)
if not commandCenter then return end
-- Get all players in the coalition
local playerList = coalition.getPlayers(coalitionSide)
if not playerList or #playerList == 0 then
-- No players in coalition, send default language message
local defaultText = ZONE_CAPTURE_LANGUAGES[LANGUAGE_CONFIG.defaultLanguage][messageKey]
if params then
defaultText = string.format(defaultText, unpack(params))
end
commandCenter:MessageTypeToCoalition(defaultText, MESSAGE.Type.Information, duration)
return
end
-- Group players by language preference
local languageGroups = {}
for _, unit in ipairs(playerList) do
if unit then
local playerName = unit:getPlayerName()
if playerName then
local lang = GetPlayerLanguage(playerName)
if not languageGroups[lang] then
languageGroups[lang] = {}
end
table.insert(languageGroups[lang], playerName)
end
end
end
-- Send message in each language to respective players
for lang, players in pairs(languageGroups) do
local langTable = ZONE_CAPTURE_LANGUAGES[lang] or ZONE_CAPTURE_LANGUAGES.EN
local text = langTable[messageKey]
if params then
text = string.format(text, unpack(params))
end
-- For now, send to entire coalition (MOOSE doesn't support per-player filtering easily)
-- This is a limitation, but better than nothing
commandCenter:MessageTypeToCoalition(text, MESSAGE.Type.Information, duration)
end
end
-- ==========================================
-- END OF CONFIGURATION
-- ==========================================
@@ -607,16 +994,16 @@ local function OnEnterGuarded(ZoneCapture, From, Event, To)
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 %s", ZoneCapture:GetZoneName(), COALITION_TITLES.BLUE ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of the %s", ZoneCapture:GetZoneName(), COALITION_TITLES.BLUE ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
MessageToCoalition(US_CC, "zoneGuarded", {ZoneCapture:GetZoneName(), COALITION_TITLES.BLUE}, coalition.side.BLUE, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION)
MessageToCoalition(RU_CC, "zoneGuarded", {ZoneCapture:GetZoneName(), COALITION_TITLES.BLUE}, coalition.side.RED, 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 the %s", ZoneCapture:GetZoneName(), COALITION_TITLES.RED ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
US_CC:MessageTypeToCoalition( string.format( "%s is under protection of the %s", ZoneCapture:GetZoneName(), COALITION_TITLES.RED ), MESSAGE.Type.Information, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION )
MessageToCoalition(RU_CC, "zoneGuarded", {ZoneCapture:GetZoneName(), COALITION_TITLES.RED}, coalition.side.RED, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION)
MessageToCoalition(US_CC, "zoneGuarded", {ZoneCapture:GetZoneName(), COALITION_TITLES.RED}, coalition.side.BLUE, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION)
end
-- Create/update tactical information marker
CreateTacticalInfoMarker(ZoneCapture)
@@ -629,8 +1016,8 @@ local function OnEnterEmpty(ZoneCapture)
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 )
MessageToCoalition(US_CC, "zoneEmpty", {ZoneCapture:GetZoneName()}, coalition.side.BLUE, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION)
MessageToCoalition(RU_CC, "zoneEmpty", {ZoneCapture:GetZoneName()}, coalition.side.RED, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION)
-- Create/update tactical information marker
CreateTacticalInfoMarker(ZoneCapture)
end
@@ -643,12 +1030,12 @@ local function OnEnterAttacked(ZoneCapture)
local color
if Coalition == coalition.side.BLUE then
color = ZONE_COLORS.BLUE_ATTACKED
US_CC:MessageTypeToCoalition( string.format( "%s is under attack by %s", ZoneCapture:GetZoneName(), COALITION_TITLES.RED ), 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 )
MessageToCoalition(US_CC, "zoneUnderAttack", {ZoneCapture:GetZoneName(), COALITION_TITLES.RED}, coalition.side.BLUE, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION)
MessageToCoalition(RU_CC, "zoneWeAttacking", {ZoneCapture:GetZoneName()}, coalition.side.RED, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION)
else
color = ZONE_COLORS.RED_ATTACKED
RU_CC:MessageTypeToCoalition( string.format( "%s is under attack by %s", ZoneCapture:GetZoneName(), COALITION_TITLES.BLUE ), 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 )
MessageToCoalition(RU_CC, "zoneUnderAttack", {ZoneCapture:GetZoneName(), COALITION_TITLES.BLUE}, coalition.side.RED, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION)
MessageToCoalition(US_CC, "zoneWeAttacking", {ZoneCapture:GetZoneName()}, coalition.side.BLUE, MESSAGE_CONFIG.ATTACK_MESSAGE_DURATION)
end
ZoneCapture:DrawZone(-1, {0, 0, 0}, 1, color, 0.2, 2, true)
-- Create/update tactical information marker
@@ -679,18 +1066,8 @@ local function CheckVictoryCondition()
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_TITLES.BLUE .. " forces!\n\n" ..
COALITION_TITLES.BLUE_OPERATION .. " 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_TITLES.BLUE .. " forces.\n\n" ..
COALITION_TITLES.BLUE_OPERATION .. " has failed. Mission ending in 60 seconds.",
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
)
MessageToCoalition(US_CC, "victoryBlue", {COALITION_TITLES.BLUE, COALITION_TITLES.BLUE_OPERATION}, coalition.side.BLUE, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION)
MessageToCoalition(RU_CC, "defeatBlue", {COALITION_TITLES.BLUE, COALITION_TITLES.BLUE_OPERATION}, coalition.side.RED, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION)
-- Add victory celebration effects
for _, zoneCapture in ipairs(zoneCaptureObjects) do
@@ -707,10 +1084,7 @@ local function CheckVictoryCondition()
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
)
MessageToCoalition(US_CC, "missionComplete", {totalZones}, coalition.side.BLUE, 10)
end, {}, 60 )
return true
@@ -720,18 +1094,8 @@ local function CheckVictoryCondition()
if redZonesCount >= totalZones then
log("[VICTORY] All zones captured by RED! Triggering victory sequence...")
RU_CC:MessageTypeToCoalition(
"VICTORY! All strategic positions secured for " .. COALITION_TITLES.RED_OPERATION .. "!\n\n" ..
COALITION_TITLES.BLUE .. " 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 " .. COALITION_TITLES.RED .. " forces.\n\n" ..
COALITION_TITLES.BLUE_OPERATION .. " has failed. Mission ending in 60 seconds.",
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
)
MessageToCoalition(RU_CC, "victoryRed", {COALITION_TITLES.RED_OPERATION, COALITION_TITLES.BLUE}, coalition.side.RED, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION)
MessageToCoalition(US_CC, "defeatRed", {COALITION_TITLES.RED, COALITION_TITLES.BLUE_OPERATION}, coalition.side.BLUE, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION)
-- Add victory celebration effects
for _, zoneCapture in ipairs(zoneCaptureObjects) do
@@ -748,10 +1112,7 @@ local function CheckVictoryCondition()
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
)
MessageToCoalition(RU_CC, "missionComplete", {totalZones}, coalition.side.RED, 10)
end, {}, 60 )
return true
@@ -767,15 +1128,15 @@ local function OnEnterCaptured(ZoneCapture)
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 %s, we lost it!", ZoneCapture:GetZoneName(), COALITION_TITLES.BLUE ), 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 )
MessageToCoalition(RU_CC, "zoneCapturedByEnemy", {ZoneCapture:GetZoneName(), COALITION_TITLES.BLUE}, coalition.side.RED, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION)
MessageToCoalition(US_CC, "zoneCapturedByUs", {ZoneCapture:GetZoneName()}, coalition.side.BLUE, 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 the %s, we lost it!", ZoneCapture:GetZoneName(), COALITION_TITLES.RED ), 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 )
MessageToCoalition(US_CC, "zoneCapturedByEnemy", {ZoneCapture:GetZoneName(), COALITION_TITLES.RED}, coalition.side.BLUE, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION)
MessageToCoalition(RU_CC, "zoneCapturedByUs", {ZoneCapture:GetZoneName()}, coalition.side.RED, MESSAGE_CONFIG.CAPTURE_MESSAGE_DURATION)
end
ZoneCapture:AddScore( "Captured", "Zone captured: Extra points granted.", ZONE_SETTINGS.captureScore )
@@ -908,36 +1269,60 @@ end
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",
-- Build report for BLUE coalition in their language
local bluePlayerList = coalition.getPlayers(coalition.side.BLUE)
local bluePlayerName = nil
if bluePlayerList and #bluePlayerList > 0 then
local unit = Unit.getByName(Unit.getName(bluePlayerList[1]))
if unit then
bluePlayerName = unit:getPlayerName()
end
end
local blueReportMessage = string.format(
GetText("zoneControlReport", bluePlayerName),
status.blue, status.total,
status.red, status.total,
status.neutral, status.total
)
-- Add detailed zone status
local detailMessage = "\nZONE DETAILS:\n"
local blueDetailMessage = GetText("zoneDetails", bluePlayerName)
for zoneName, owner in pairs(status.zones) do
detailMessage = detailMessage .. string.format("• %s: %s\n", zoneName, owner)
blueDetailMessage = blueDetailMessage .. string.format(GetText("zoneDetailLine", bluePlayerName), 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)
local blueFullMessage = blueReportMessage .. blueDetailMessage .. string.format(GetText("yourProgress", bluePlayerName), blueProgressPercent)
US_CC:MessageTypeToCoalition( blueFullMessage, MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION )
-- Build report for RED coalition in their language
local redPlayerList = coalition.getPlayers(coalition.side.RED)
local redPlayerName = nil
if redPlayerList and #redPlayerList > 0 then
local unit = Unit.getByName(Unit.getName(redPlayerList[1]))
if unit then
redPlayerName = unit:getPlayerName()
end
end
local redReportMessage = string.format(
GetText("zoneControlReport", redPlayerName),
status.blue, status.total,
status.red, status.total,
status.neutral, status.total
)
local redDetailMessage = GetText("zoneDetails", redPlayerName)
for zoneName, owner in pairs(status.zones) do
redDetailMessage = redDetailMessage .. string.format(GetText("zoneDetailLine", redPlayerName), zoneName, owner)
end
local redProgressPercent = math.floor((status.red / totalZones) * 100)
local redFullMessage = fullMessage .. string.format("\n\nYour Progress to Victory: %d%%", redProgressPercent)
local redFullMessage = redReportMessage .. redDetailMessage .. string.format(GetText("yourProgress", redPlayerName), redProgressPercent)
RU_CC:MessageTypeToCoalition( redFullMessage, MESSAGE.Type.Information, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION )
log("[ZONE STATUS] " .. reportMessage:gsub("\n", " | "))
log("[ZONE STATUS] Blue:" .. status.blue .. "/" .. status.total .. " Red:" .. status.red .. "/" .. status.total)
return status
end
@@ -948,32 +1333,16 @@ local ZoneMonitorScheduler = SCHEDULER:New( nil, function()
-- 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! %s forces control %d/%d zones! We must recapture territory!",
COALITION_TITLES.BLUE, status.blue, status.total),
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
)
local bluePercent = math.floor((status.blue / status.total) * 100)
MessageToCoalition(US_CC, "blueNearVictory", {COALITION_TITLES.BLUE, bluePercent}, coalition.side.BLUE, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION)
MessageToCoalition(RU_CC, "redNearVictory", {COALITION_TITLES.BLUE, bluePercent}, coalition.side.RED, 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! %s forces control %d/%d zones! We must recapture territory!",
COALITION_TITLES.RED, status.red, status.total),
MESSAGE.Type.Information, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION
)
local redPercent = math.floor((status.red / status.total) * 100)
MessageToCoalition(RU_CC, "blueNearVictory", {COALITION_TITLES.RED, redPercent}, coalition.side.RED, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION)
MessageToCoalition(US_CC, "redNearVictory", {COALITION_TITLES.RED, redPercent}, coalition.side.BLUE, MESSAGE_CONFIG.VICTORY_MESSAGE_DURATION)
end
end, {}, MESSAGE_CONFIG.STATUS_BROADCAST_START_DELAY, MESSAGE_CONFIG.STATUS_BROADCAST_FREQUENCY )
@@ -1086,81 +1455,188 @@ local function RefreshAllZoneColors()
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)
MessageToCoalition(US_CC, "zoneMarkersRefreshed", nil, coalition.side.BLUE, MESSAGE_CONFIG.STATUS_MESSAGE_DURATION)
MessageToCoalition(RU_CC, "zoneMarkersRefreshed", nil, coalition.side.RED, 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 )
-- NOTE: All menus are now created per-player in CreatePlayerLanguageMenu() function below
-- No coalition-wide menus needed
-- ==========================================
-- PER-PLAYER MENU SETUP (ALL ZONE CONTROL FUNCTIONS)
-- ==========================================
-- Track player menus to avoid duplicates per group
-- Key format: "playerName|groupName" to handle aircraft switches
local playerMenus = {}
-- Function to create complete Zone Control menu for a specific player
local function CreatePlayerMenu(playerUnit)
env.info("[MENU DEBUG] CreatePlayerMenu called")
if not playerUnit then
env.info("[MENU DEBUG] No playerUnit")
return
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 )
local playerName = playerUnit:getPlayerName()
env.info(string.format("[MENU DEBUG] PlayerName: %s", tostring(playerName)))
if not playerName then return end
-- Get DCS group and convert to MOOSE GROUP wrapper
local dcsGroup = playerUnit:getGroup()
env.info(string.format("[MENU DEBUG] DCS Group: %s", tostring(dcsGroup and dcsGroup:getName() or "nil")))
if not dcsGroup then return end
local groupName = dcsGroup:getName()
local group = GROUP:FindByName(groupName)
env.info(string.format("[MENU DEBUG] MOOSE Group found: %s", tostring(group ~= nil)))
if not group then return end
local menuKey = playerName .. "|" .. groupName
-- Don't create duplicate menus for same player in same group
if playerMenus[menuKey] then
env.info(string.format("[MENU DEBUG] Menu already exists for %s", menuKey))
return
end
local playerCoalition = playerUnit:getCoalition()
-- Get appropriate command center
local commandCenter = playerCoalition == coalition.side.BLUE and US_CC or RU_CC
if not commandCenter then
env.info("[MENU DEBUG] No command center found")
return
end
env.info(string.format("[MENU] Creating Zone Control menu for player %s (group: %s)", playerName, groupName))
-- Create Zone Control root menu (use GROUP object, not group name string)
-- Use MenuManager if available to nest under "Mission Options"
local zoneControlMenu = MenuManager and MenuManager.CreateGroupMenu(group, GetText("menuZoneControl", playerName))
or MENU_GROUP:New(group, GetText("menuZoneControl", playerName))
-- Add Zone Status Report command
MENU_GROUP_COMMAND:New(group, GetText("menuStatusReport", playerName), zoneControlMenu, function()
BroadcastZoneStatus()
end)
-- Add Victory Progress command
MENU_GROUP_COMMAND:New(group, GetText("menuVictoryProgress", playerName), zoneControlMenu, function()
local status = GetZoneOwnershipStatus()
local totalZones = math.max(status.total, 1)
local progressPercent = 0
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 )
-- Calculate progress based on player's coalition
if playerCoalition == coalition.side.BLUE then
progressPercent = math.floor((status.blue / totalZones) * 100)
else
progressPercent = math.floor((status.red / totalZones) * 100)
end
-- Add command to refresh zone colors (troubleshooting tool)
MENU_COALITION_COMMAND:New( coalition.side.RED, "Refresh Zone Colors", RUMenu, RefreshAllZoneColors )
-- Determine progress message based on percentage
local progressMsg
if progressPercent >= 100 then
progressMsg = GetText("progressComplete", playerName)
elseif progressPercent >= 80 then
progressMsg = GetText("progressAlmostThere", playerName)
elseif progressPercent >= 50 then
progressMsg = GetText("progressGood", playerName)
else
progressMsg = GetText("progressKeepFighting", playerName)
end
local zonesCaptured = playerCoalition == coalition.side.BLUE and status.blue or status.red
local zonesRemaining = status.total - zonesCaptured
MESSAGE:New(
string.format(
"%s\n%s\n%s\n\n%s",
string.format(GetText("victoryProgressTitle", playerName), progressPercent),
string.format(GetText("zonesCaptured", playerName), zonesCaptured, status.total),
string.format(GetText("zonesRemaining", playerName), zonesRemaining),
progressMsg
),
MESSAGE_CONFIG.STATUS_MESSAGE_DURATION
):ToGroup(group)
end)
-- Add Refresh Zone Colors command
MENU_GROUP_COMMAND:New(group, GetText("menuRefreshColors", playerName), zoneControlMenu, function()
RefreshAllZoneColors()
end)
-- Create Language submenu
local langMenu = MENU_GROUP:New(group, GetText("menuLanguage", playerName), zoneControlMenu)
-- Add language options
MENU_GROUP_COMMAND:New(group, GetText("menuEnglish", playerName), langMenu, function()
SetPlayerLanguage(playerName, "EN")
MESSAGE:New(GetText("languageChanged", playerName), 5):ToGroup(group)
end)
MENU_GROUP_COMMAND:New(group, GetText("menuGerman", playerName), langMenu, function()
SetPlayerLanguage(playerName, "DE")
MESSAGE:New(GetText("languageChanged", playerName), 5):ToGroup(group)
end)
MENU_GROUP_COMMAND:New(group, GetText("menuFrench", playerName), langMenu, function()
SetPlayerLanguage(playerName, "FR")
MESSAGE:New(GetText("languageChanged", playerName), 5):ToGroup(group)
end)
MENU_GROUP_COMMAND:New(group, GetText("menuSpanish", playerName), langMenu, function()
SetPlayerLanguage(playerName, "ES")
MESSAGE:New(GetText("languageChanged", playerName), 5):ToGroup(group)
end)
MENU_GROUP_COMMAND:New(group, GetText("menuRussian", playerName), langMenu, function()
SetPlayerLanguage(playerName, "RU")
MESSAGE:New(GetText("languageChanged", playerName), 5):ToGroup(group)
end)
playerMenus[menuKey] = true
log(string.format("[MENU] Zone Control menu created for %s in group %s", playerName, groupName))
end
-- Event handler to create player menus when players spawn
local function OnPlayerBirth(event)
env.info(string.format("[MENU DEBUG] OnPlayerBirth called, event id: %s", tostring(event.id)))
if event.id == world.event.S_EVENT_BIRTH and event.initiator then
local unit = event.initiator
local playerName = unit and unit:getPlayerName()
env.info(string.format("[MENU DEBUG] Birth event for player: %s", tostring(playerName)))
if unit and playerName then
-- Small delay to ensure everything is initialized
timer.scheduleFunction(function()
CreatePlayerMenu(unit)
end, nil, timer.getTime() + 2)
end
end
end
-- Set up event handler
world.addEventHandler({
onEvent = OnPlayerBirth
})
-- Create menus for any players already in the mission
timer.scheduleFunction(function()
env.info("[MENU] Checking for existing players...")
for _, coalitionSide in pairs({coalition.side.BLUE, coalition.side.RED}) do
local players = coalition.getPlayers(coalitionSide)
env.info(string.format("[MENU DEBUG] Found %d players in coalition %d", players and #players or 0, coalitionSide))
if players then
for _, unit in ipairs(players) do
local playerName = unit and unit:getPlayerName()
env.info(string.format("[MENU DEBUG] Processing existing player: %s", tostring(playerName)))
if unit and playerName then
CreatePlayerMenu(unit)
end
end
end
end
end, nil, timer.getTime() + 5)
-- Initialize zone status monitoring
SCHEDULER:New( nil, function()
log("[VICTORY SYSTEM] Initializing zone monitoring system...")
@@ -1168,7 +1644,7 @@ SCHEDULER:New( nil, function()
-- Initialize performance optimization caches
InitializeCachedUnitSet()
SetupZoneStatusCommands()
-- Note: All menus are created per-player via event handlers
-- Initial status report
SCHEDULER:New( nil, function()
Binary file not shown.
+399 -62
View File
@@ -192,6 +192,318 @@ local blueArmorTemplates = {
-- DO NOT EDIT BELOW THIS LINE
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ==========================================
-- MULTILINGUAL SUPPORT (uses main script's language settings)
-- ==========================================
-- This plugin uses the playerLanguages table from Moose_DualCoalitionZoneCapture.lua
-- Players set their language preference in the main script's F10 menu
local DGB_LANGUAGES = {
EN = {
-- Warehouse messages
warehouseStatus = "[Warehouse Status]\nRed warehouses alive: %d Reinforcements: %d%%\nBlue warehouses alive: %d Reinforcements: %d%%\n",
reinforcementCapacity = "%s reinforcement capacity: %d%%",
warehouseFriendly = "Warehouse: %s\nThis warehouse needs to be protected.\n",
warehouseEnemy = "Warehouse: %s\nThis is a primary target as it is directly supplying enemy units.\n",
-- Menu items
menuGroundBattle = "Ground Battle",
menuWarehouseStatus = "Check Warehouse Status",
menuSystemStats = "Show System Statistics",
-- System statistics
statsTitle = "DYNAMIC GROUND BATTLE - SYSTEM STATUS",
statsConfiguration = "【CONFIGURATION】",
statsDefendersPerZone = " Defenders per Zone: %d",
statsDefenderRotation = " Defender Rotation: %s",
statsInfantryMovement = " Infantry Movement: %s",
statsTaskReassignment = " Task Reassignment: Every %ds",
statsWarehouseMarkers = " Warehouse Markers: %s",
statsSpawnLimits = "【SPAWN LIMITS】",
statsRedInfantry = " Red Infantry: %d/%d",
statsRedArmor = " Red Armor: %d/%d",
statsBlueInfantry = " Blue Infantry: %d/%d",
statsBlueArmor = " Blue Armor: %d/%d",
statsRedCoalition = "【RED COALITION】",
statsBlueCoalition = "【BLUE COALITION】",
statsWarehouses = " Warehouses: %d/%d (%d%%)",
statsActiveUnits = " Active Units: %d (%d inf, %d armor)",
statsDefenders = " Defenders: %d | Mobile: %d",
statsControlledZones = " Controlled Zones: %d",
statsGarrisoned = " - Garrisoned: %d",
statsUnderGarrisoned = " - Under-Garrisoned: %d",
statsInfantrySpawn = " Infantry Spawn: %ds",
statsInfantrySpawnPaused = " Infantry Spawn: PAUSED (no warehouses)",
statsArmorSpawn = " Armor Spawn: %ds",
statsArmorSpawnPaused = " Armor Spawn: PAUSED (no warehouses)",
statsSystemInfo = "【SYSTEM INFO】",
statsTotalZones = " Total Zones: %d",
statsActiveGarrisons = " Active Garrisons: %d",
statsTotalActiveUnits = " Total Active Units: %d",
statsTrackedGroups = " Tracked Groups: %d",
statsLuaMemory = " Lua Memory: %.1f MB",
statsWarningMemory = " ⚠️ WARNING: High memory usage!",
statsWarningGroups = " ⚠️ WARNING: High group count!",
enabled = "ENABLED",
disabled = "DISABLED",
},
DE = {
-- Warehouse messages
warehouseStatus = "[Lagerstatus]\nRote Lager aktiv: %d Verstärkungen: %d%%\nBlaue Lager aktiv: %d Verstärkungen: %d%%\n",
reinforcementCapacity = "%s Verstärkungskapazität: %d%%",
warehouseFriendly = "Lager: %s\nDieses Lager muss geschützt werden.\n",
warehouseEnemy = "Lager: %s\nDies ist ein Hauptziel, da es feindliche Einheiten direkt versorgt.\n",
-- Menu items
menuGroundBattle = "Bodenkampf",
menuWarehouseStatus = "Lagerstatus prüfen",
menuSystemStats = "Systemstatistiken anzeigen",
-- System statistics
statsTitle = "DYNAMISCHER BODENKAMPF - SYSTEMSTATUS",
statsConfiguration = "【KONFIGURATION】",
statsDefendersPerZone = " Verteidiger pro Zone: %d",
statsDefenderRotation = " Verteidiger-Rotation: %s",
statsInfantryMovement = " Infanterie-Bewegung: %s",
statsTaskReassignment = " Aufgabenzuweisung: Alle %ds",
statsWarehouseMarkers = " Lagermarkierungen: %s",
statsSpawnLimits = "【SPAWN-LIMITS】",
statsRedInfantry = " Rote Infanterie: %d/%d",
statsRedArmor = " Rote Panzer: %d/%d",
statsBlueInfantry = " Blaue Infanterie: %d/%d",
statsBlueArmor = " Blaue Panzer: %d/%d",
statsRedCoalition = "【ROTE KOALITION】",
statsBlueCoalition = "【BLAUE KOALITION】",
statsWarehouses = " Lager: %d/%d (%d%%)",
statsActiveUnits = " Aktive Einheiten: %d (%d Inf, %d Panzer)",
statsDefenders = " Verteidiger: %d | Mobil: %d",
statsControlledZones = " Kontrollierte Zonen: %d",
statsGarrisoned = " - Besetzt: %d",
statsUnderGarrisoned = " - Unterbesetzt: %d",
statsInfantrySpawn = " Infanterie-Spawn: %ds",
statsInfantrySpawnPaused = " Infanterie-Spawn: PAUSIERT (keine Lager)",
statsArmorSpawn = " Panzer-Spawn: %ds",
statsArmorSpawnPaused = " Panzer-Spawn: PAUSIERT (keine Lager)",
statsSystemInfo = "【SYSTEMINFO】",
statsTotalZones = " Zonen gesamt: %d",
statsActiveGarrisons = " Aktive Garnisonen: %d",
statsTotalActiveUnits = " Aktive Einheiten gesamt: %d",
statsTrackedGroups = " Verfolgte Gruppen: %d",
statsLuaMemory = " Lua-Speicher: %.1f MB",
statsWarningMemory = " ⚠️ WARNUNG: Hoher Speicherverbrauch!",
statsWarningGroups = " ⚠️ WARNUNG: Hohe Gruppenanzahl!",
enabled = "AKTIVIERT",
disabled = "DEAKTIVIERT",
},
FR = {
-- Warehouse messages
warehouseStatus = "[Statut des entrepôts]\nEntrepôts rouges actifs : %d Renforts : %d%%\nEntrepôts bleus actifs : %d Renforts : %d%%\n",
reinforcementCapacity = "Capacité de renfort %s : %d%%",
warehouseFriendly = "Entrepôt : %s\nCet entrepôt doit être protégé.\n",
warehouseEnemy = "Entrepôt : %s\nC'est une cible prioritaire car il approvisionne directement les unités ennemies.\n",
-- Menu items
menuGroundBattle = "Combat terrestre",
menuWarehouseStatus = "Vérifier le statut des entrepôts",
menuSystemStats = "Afficher les statistiques système",
-- System statistics
statsTitle = "COMBAT TERRESTRE DYNAMIQUE - ÉTAT DU SYSTÈME",
statsConfiguration = "【CONFIGURATION】",
statsDefendersPerZone = " Défenseurs par zone : %d",
statsDefenderRotation = " Rotation des défenseurs : %s",
statsInfantryMovement = " Mouvement infanterie : %s",
statsTaskReassignment = " Réaffectation des tâches : Tous les %ds",
statsWarehouseMarkers = " Marqueurs d'entrepôt : %s",
statsSpawnLimits = "【LIMITES DE SPAWN】",
statsRedInfantry = " Infanterie rouge : %d/%d",
statsRedArmor = " Blindés rouges : %d/%d",
statsBlueInfantry = " Infanterie bleue : %d/%d",
statsBlueArmor = " Blindés bleus : %d/%d",
statsRedCoalition = "【COALITION ROUGE】",
statsBlueCoalition = "【COALITION BLEUE】",
statsWarehouses = " Entrepôts : %d/%d (%d%%)",
statsActiveUnits = " Unités actives : %d (%d inf, %d blindés)",
statsDefenders = " Défenseurs : %d | Mobiles : %d",
statsControlledZones = " Zones contrôlées : %d",
statsGarrisoned = " - En garnison : %d",
statsUnderGarrisoned = " - Sous-garnison : %d",
statsInfantrySpawn = " Spawn infanterie : %ds",
statsInfantrySpawnPaused = " Spawn infanterie : PAUSE (pas d'entrepôts)",
statsArmorSpawn = " Spawn blindés : %ds",
statsArmorSpawnPaused = " Spawn blindés : PAUSE (pas d'entrepôts)",
statsSystemInfo = "【INFO SYSTÈME】",
statsTotalZones = " Zones totales : %d",
statsActiveGarrisons = " Garnisons actives : %d",
statsTotalActiveUnits = " Unités actives totales : %d",
statsTrackedGroups = " Groupes suivis : %d",
statsLuaMemory = " Mémoire Lua : %.1f Mo",
statsWarningMemory = " ⚠️ ATTENTION : Utilisation mémoire élevée !",
statsWarningGroups = " ⚠️ ATTENTION : Nombre de groupes élevé !",
enabled = "ACTIVÉ",
disabled = "DÉSACTIVÉ",
},
ES = {
-- Warehouse messages
warehouseStatus = "[Estado de almacenes]\nAlmacenes rojos activos: %d Refuerzos: %d%%\nAlmacenes azules activos: %d Refuerzos: %d%%\n",
reinforcementCapacity = "Capacidad de refuerzo %s: %d%%",
warehouseFriendly = "Almacén: %s\nEste almacén necesita ser protegido.\n",
warehouseEnemy = "Almacén: %s\nEste es un objetivo prioritario ya que está suministrando directamente unidades enemigas.\n",
-- Menu items
menuGroundBattle = "Batalla terrestre",
menuWarehouseStatus = "Verificar estado de almacenes",
menuSystemStats = "Mostrar estadísticas del sistema",
-- System statistics
statsTitle = "BATALLA TERRESTRE DINÁMICA - ESTADO DEL SISTEMA",
statsConfiguration = "【CONFIGURACIÓN】",
statsDefendersPerZone = " Defensores por zona: %d",
statsDefenderRotation = " Rotación de defensores: %s",
statsInfantryMovement = " Movimiento de infantería: %s",
statsTaskReassignment = " Reasignación de tareas: Cada %ds",
statsWarehouseMarkers = " Marcadores de almacén: %s",
statsSpawnLimits = "【LÍMITES DE APARICIÓN】",
statsRedInfantry = " Infantería roja: %d/%d",
statsRedArmor = " Blindados rojos: %d/%d",
statsBlueInfantry = " Infantería azul: %d/%d",
statsBlueArmor = " Blindados azules: %d/%d",
statsRedCoalition = "【COALICIÓN ROJA】",
statsBlueCoalition = "【COALICIÓN AZUL】",
statsWarehouses = " Almacenes: %d/%d (%d%%)",
statsActiveUnits = " Unidades activas: %d (%d inf, %d blindados)",
statsDefenders = " Defensores: %d | Móviles: %d",
statsControlledZones = " Zonas controladas: %d",
statsGarrisoned = " - Guarnecidas: %d",
statsUnderGarrisoned = " - Subguarnecidas: %d",
statsInfantrySpawn = " Aparición infantería: %ds",
statsInfantrySpawnPaused = " Aparición infantería: PAUSADA (sin almacenes)",
statsArmorSpawn = " Aparición blindados: %ds",
statsArmorSpawnPaused = " Aparición blindados: PAUSADA (sin almacenes)",
statsSystemInfo = "【INFO DEL SISTEMA】",
statsTotalZones = " Zonas totales: %d",
statsActiveGarrisons = " Guarniciones activas: %d",
statsTotalActiveUnits = " Unidades activas totales: %d",
statsTrackedGroups = " Grupos rastreados: %d",
statsLuaMemory = " Memoria Lua: %.1f MB",
statsWarningMemory = " ⚠️ ADVERTENCIA: ¡Uso de memoria elevado!",
statsWarningGroups = " ⚠️ ADVERTENCIA: ¡Cantidad de grupos elevada!",
enabled = "HABILITADO",
disabled = "DESHABILITADO",
},
RU = {
-- Warehouse messages
warehouseStatus = "[Статус складов]\nКрасные склады активны: %d Подкрепления: %d%%\nСиние склады активны: %d Подкрепления: %d%%\n",
reinforcementCapacity = "Мощность подкреплений %s: %d%%",
warehouseFriendly = "Склад: %s\nЭтот склад нужно защищать.\n",
warehouseEnemy = "Склад: %s\nЭто приоритетная цель, так как она напрямую снабжает вражеские подразделения.\n",
-- Menu items
menuGroundBattle = "Наземный бой",
menuWarehouseStatus = "Проверить статус складов",
menuSystemStats = "Показать статистику системы",
-- System statistics
statsTitle = "ДИНАМИЧЕСКИЙ НАЗЕМНЫЙ БОЙ - СТАТУС СИСТЕМЫ",
statsConfiguration = "【КОНФИГУРАЦИЯ】",
statsDefendersPerZone = " Защитников на зону: %d",
statsDefenderRotation = " Ротация защитников: %s",
statsInfantryMovement = " Движение пехоты: %s",
statsTaskReassignment = " Переназначение задач: Каждые %ds",
statsWarehouseMarkers = " Маркеры складов: %s",
statsSpawnLimits = "【ЛИМИТЫ ПОЯВЛЕНИЯ】",
statsRedInfantry = " Красная пехота: %d/%d",
statsRedArmor = " Красная бронетехника: %d/%d",
statsBlueInfantry = " Синяя пехота: %d/%d",
statsBlueArmor = " Синяя бронетехника: %d/%d",
statsRedCoalition = "【КРАСНАЯ КОАЛИЦИЯ】",
statsBlueCoalition = "【СИНЯЯ КОАЛИЦИЯ】",
statsWarehouses = " Склады: %d/%d (%d%%)",
statsActiveUnits = " Активные подразделения: %d (%d пех, %d брон)",
statsDefenders = " Защитники: %d | Мобильные: %d",
statsControlledZones = " Контролируемые зоны: %d",
statsGarrisoned = " - С гарнизоном: %d",
statsUnderGarrisoned = " - Недоукомплектованные: %d",
statsInfantrySpawn = " Появление пехоты: %ds",
statsInfantrySpawnPaused = " Появление пехоты: ПАУЗА (нет складов)",
statsArmorSpawn = " Появление техники: %ds",
statsArmorSpawnPaused = " Появление техники: ПАУЗА (нет складов)",
statsSystemInfo = "【ИНФОРМАЦИЯ О СИСТЕМЕ】",
statsTotalZones = " Всего зон: %d",
statsActiveGarrisons = " Активные гарнизоны: %d",
statsTotalActiveUnits = " Всего активных единиц: %d",
statsTrackedGroups = " Отслеживаемые группы: %d",
statsLuaMemory = " Память Lua: %.1f МБ",
statsWarningMemory = " ⚠️ ВНИМАНИЕ: Высокое использование памяти!",
statsWarningGroups = " ⚠️ ВНИМАНИЕ: Высокое количество групп!",
enabled = "ВКЛЮЧЕНО",
disabled = "ВЫКЛЮЧЕНО",
}
}
-- Helper function to get player's language from main script
-- Falls back to English if playerLanguages is not available or player not found
local function DGB_GetPlayerLanguage(playerName)
-- Try to use the main script's playerLanguages table
if playerLanguages and playerName and playerLanguages[playerName] then
return playerLanguages[playerName]
end
-- Try to use the main script's GetPlayerLanguage function if available
if GetPlayerLanguage and type(GetPlayerLanguage) == "function" and playerName then
local lang = GetPlayerLanguage(playerName)
if lang then return lang end
end
-- Fall back to main script's default language
if LANGUAGE_CONFIG and LANGUAGE_CONFIG.defaultLanguage then
return LANGUAGE_CONFIG.defaultLanguage
end
-- Ultimate fallback to English
return "EN"
end
-- Helper function to get coalition's language (uses first player's language)
local function DGB_GetCoalitionLanguage(coalitionSide)
local playerList = coalition.getPlayers(coalitionSide)
if playerList and #playerList > 0 then
local unit = playerList[1]
if unit then
local playerName = unit:getPlayerName()
if playerName then
return DGB_GetPlayerLanguage(playerName)
end
end
end
-- Fall back to default language
if LANGUAGE_CONFIG and LANGUAGE_CONFIG.defaultLanguage then
return LANGUAGE_CONFIG.defaultLanguage
end
return "EN"
end
-- Helper function to get translated text
local function DGB_GetText(textKey, playerName)
local lang = DGB_GetPlayerLanguage(playerName)
local langTable = DGB_LANGUAGES[lang] or DGB_LANGUAGES.EN
return langTable[textKey] or DGB_LANGUAGES.EN[textKey] or textKey
end
-- Helper function to get translated text for a coalition
local function DGB_GetTextForCoalition(textKey, coalitionSide)
local lang = DGB_GetCoalitionLanguage(coalitionSide)
local langTable = DGB_LANGUAGES[lang] or DGB_LANGUAGES.EN
return langTable[textKey] or DGB_LANGUAGES.EN[textKey] or textKey
end
env.info("[DGB PLUGIN] Dynamic Ground Battle Plugin initializing...")
-- Validate that DualCoalitionZoneCapture is loaded
@@ -418,15 +730,15 @@ local function addMarkPoints(warehouses, coalition)
if coalition == 2 then -- Blue viewing
if warehouse:GetCoalition() == 2 then
details = "Warehouse: " .. warehouse:GetName() .. "\nThis warehouse needs to be protected.\n"
details = string.format(DGB_GetTextForCoalition("warehouseFriendly", coalition), warehouse:GetName())
else
details = "Warehouse: " .. warehouse:GetName() .. "\nThis is a primary target as it is directly supplying enemy units.\n"
details = string.format(DGB_GetTextForCoalition("warehouseEnemy", coalition), warehouse:GetName())
end
elseif coalition == 1 then -- Red viewing
if warehouse:GetCoalition() == 1 then
details = "Warehouse: " .. warehouse:GetName() .. "\nThis warehouse needs to be protected.\n"
details = string.format(DGB_GetTextForCoalition("warehouseFriendly", coalition), warehouse:GetName())
else
details = "Warehouse: " .. warehouse:GetName() .. "\nThis is a primary target as it is directly supplying enemy units.\n"
details = string.format(DGB_GetTextForCoalition("warehouseEnemy", coalition), warehouse:GetName())
end
end
@@ -818,10 +1130,23 @@ local function MonitorWarehouses()
local blueSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(blueWarehouses)
if ENABLE_WAREHOUSE_STATUS_MESSAGES then
local msg = "[Warehouse Status]\n"
msg = msg .. "Red warehouses alive: " .. redWarehousesAlive .. " Reinforcements: " .. redSpawnFrequencyPercentage .. "%\n"
msg = msg .. "Blue warehouses alive: " .. blueWarehousesAlive .. " Reinforcements: " .. blueSpawnFrequencyPercentage .. "%\n"
MESSAGE:New(msg, 30):ToAll()
-- Send to Blue coalition in their language
local blueLang = DGB_GetCoalitionLanguage(coalition.side.BLUE)
local blueMsg = string.format(
DGB_LANGUAGES[blueLang].warehouseStatus,
redWarehousesAlive, redSpawnFrequencyPercentage,
blueWarehousesAlive, blueSpawnFrequencyPercentage
)
MESSAGE:New(blueMsg, 30):ToBlue()
-- Send to Red coalition in their language
local redLang = DGB_GetCoalitionLanguage(coalition.side.RED)
local redMsg = string.format(
DGB_LANGUAGES[redLang].warehouseStatus,
redWarehousesAlive, redSpawnFrequencyPercentage,
blueWarehousesAlive, blueSpawnFrequencyPercentage
)
MESSAGE:New(redMsg, 30):ToRed()
end
env.info(string.format("[DGB PLUGIN] Warehouse status - Red: %d/%d (%d%%), Blue: %d/%d (%d%%)",
@@ -920,73 +1245,77 @@ local function ShowSystemStatistics(playerCoalition)
local blueInfantryInterval = CalculateSpawnFrequency(blueWarehouses, SPAWN_SCHED_BLUE_INFANTRY, BLUE_INFANTRY_CADENCE_SCALAR)
local blueArmorInterval = CalculateSpawnFrequency(blueWarehouses, SPAWN_SCHED_BLUE_ARMOR, BLUE_ARMOR_CADENCE_SCALAR)
-- Get language for this coalition
local lang = DGB_GetCoalitionLanguage(playerCoalition)
local T = DGB_LANGUAGES[lang]
-- Build comprehensive report
local msg = "═══════════════════════════════════════\n"
msg = msg .. "DYNAMIC GROUND BATTLE - SYSTEM STATUS\n"
msg = msg .. T.statsTitle .. "\n"
msg = msg .. "═══════════════════════════════════════\n\n"
-- Configuration Section
msg = msg .. "【CONFIGURATION】\n"
msg = msg .. " Defenders per Zone: " .. DEFENDERS_PER_ZONE .. "\n"
msg = msg .. " Defender Rotation: " .. (ALLOW_DEFENDER_ROTATION and "ENABLED" or "DISABLED") .. "\n"
msg = msg .. " Infantry Movement: " .. (MOVING_INFANTRY_PATROLS and "ENABLED" or "DISABLED") .. "\n"
msg = msg .. " Task Reassignment: Every " .. ASSIGN_TASKS_SCHED .. "s\n"
msg = msg .. " Warehouse Markers: " .. (ENABLE_WAREHOUSE_MARKERS and "ENABLED" or "DISABLED") .. "\n\n"
msg = msg .. T.statsConfiguration .. "\n"
msg = msg .. string.format(T.statsDefendersPerZone, DEFENDERS_PER_ZONE) .. "\n"
msg = msg .. string.format(T.statsDefenderRotation, ALLOW_DEFENDER_ROTATION and T.enabled or T.disabled) .. "\n"
msg = msg .. string.format(T.statsInfantryMovement, MOVING_INFANTRY_PATROLS and T.enabled or T.disabled) .. "\n"
msg = msg .. string.format(T.statsTaskReassignment, ASSIGN_TASKS_SCHED) .. "\n"
msg = msg .. string.format(T.statsWarehouseMarkers, ENABLE_WAREHOUSE_MARKERS and T.enabled or T.disabled) .. "\n\n"
-- Spawn Limits Section
msg = msg .. "【SPAWN LIMITS】\n"
msg = msg .. " Red Infantry: " .. INIT_RED_INFANTRY .. "/" .. MAX_RED_INFANTRY .. "\n"
msg = msg .. " Red Armor: " .. INIT_RED_ARMOR .. "/" .. MAX_RED_ARMOR .. "\n"
msg = msg .. " Blue Infantry: " .. INIT_BLUE_INFANTRY .. "/" .. MAX_BLUE_INFANTRY .. "\n"
msg = msg .. " Blue Armor: " .. INIT_BLUE_ARMOR .. "/" .. MAX_BLUE_ARMOR .. "\n\n"
msg = msg .. T.statsSpawnLimits .. "\n"
msg = msg .. string.format(T.statsRedInfantry, INIT_RED_INFANTRY, MAX_RED_INFANTRY) .. "\n"
msg = msg .. string.format(T.statsRedArmor, INIT_RED_ARMOR, MAX_RED_ARMOR) .. "\n"
msg = msg .. string.format(T.statsBlueInfantry, INIT_BLUE_INFANTRY, MAX_BLUE_INFANTRY) .. "\n"
msg = msg .. string.format(T.statsBlueArmor, INIT_BLUE_ARMOR, MAX_BLUE_ARMOR) .. "\n\n"
-- Red Coalition Section
msg = msg .. "【RED COALITION】\n"
msg = msg .. " Warehouses: " .. redWarehousesAlive .. "/" .. redWarehouseTotal .. " (" .. redSpawnFreqPct .. "%)\n"
msg = msg .. " Active Units: " .. redUnits.total .. " (" .. redUnits.infantry .. " inf, " .. redUnits.armor .. " armor)\n"
msg = msg .. " Defenders: " .. redUnits.defenders .. " | Mobile: " .. redUnits.mobile .. "\n"
msg = msg .. " Controlled Zones: " .. redGarrison.totalZones .. "\n"
msg = msg .. " - Garrisoned: " .. redGarrison.garrisoned .. "\n"
msg = msg .. " - Under-Garrisoned: " .. redGarrison.underGarrisoned .. "\n"
msg = msg .. T.statsRedCoalition .. "\n"
msg = msg .. string.format(T.statsWarehouses, redWarehousesAlive, redWarehouseTotal, redSpawnFreqPct) .. "\n"
msg = msg .. string.format(T.statsActiveUnits, redUnits.total, redUnits.infantry, redUnits.armor) .. "\n"
msg = msg .. string.format(T.statsDefenders, redUnits.defenders, redUnits.mobile) .. "\n"
msg = msg .. string.format(T.statsControlledZones, redGarrison.totalZones) .. "\n"
msg = msg .. string.format(T.statsGarrisoned, redGarrison.garrisoned) .. "\n"
msg = msg .. string.format(T.statsUnderGarrisoned, redGarrison.underGarrisoned) .. "\n"
if redInfantryInterval then
msg = msg .. " Infantry Spawn: " .. math.floor(redInfantryInterval) .. "s\n"
msg = msg .. string.format(T.statsInfantrySpawn, math.floor(redInfantryInterval)) .. "\n"
else
msg = msg .. " Infantry Spawn: PAUSED (no warehouses)\n"
msg = msg .. T.statsInfantrySpawnPaused .. "\n"
end
if redArmorInterval then
msg = msg .. " Armor Spawn: " .. math.floor(redArmorInterval) .. "s\n\n"
msg = msg .. string.format(T.statsArmorSpawn, math.floor(redArmorInterval)) .. "\n\n"
else
msg = msg .. " Armor Spawn: PAUSED (no warehouses)\n\n"
msg = msg .. T.statsArmorSpawnPaused .. "\n\n"
end
-- Blue Coalition Section
msg = msg .. "【BLUE COALITION】\n"
msg = msg .. " Warehouses: " .. blueWarehousesAlive .. "/" .. blueWarehouseTotal .. " (" .. blueSpawnFreqPct .. "%)\n"
msg = msg .. " Active Units: " .. blueUnits.total .. " (" .. blueUnits.infantry .. " inf, " .. blueUnits.armor .. " armor)\n"
msg = msg .. " Defenders: " .. blueUnits.defenders .. " | Mobile: " .. blueUnits.mobile .. "\n"
msg = msg .. " Controlled Zones: " .. blueGarrison.totalZones .. "\n"
msg = msg .. " - Garrisoned: " .. blueGarrison.garrisoned .. "\n"
msg = msg .. " - Under-Garrisoned: " .. blueGarrison.underGarrisoned .. "\n"
msg = msg .. T.statsBlueCoalition .. "\n"
msg = msg .. string.format(T.statsWarehouses, blueWarehousesAlive, blueWarehouseTotal, blueSpawnFreqPct) .. "\n"
msg = msg .. string.format(T.statsActiveUnits, blueUnits.total, blueUnits.infantry, blueUnits.armor) .. "\n"
msg = msg .. string.format(T.statsDefenders, blueUnits.defenders, blueUnits.mobile) .. "\n"
msg = msg .. string.format(T.statsControlledZones, blueGarrison.totalZones) .. "\n"
msg = msg .. string.format(T.statsGarrisoned, blueGarrison.garrisoned) .. "\n"
msg = msg .. string.format(T.statsUnderGarrisoned, blueGarrison.underGarrisoned) .. "\n"
if blueInfantryInterval then
msg = msg .. " Infantry Spawn: " .. math.floor(blueInfantryInterval) .. "s\n"
msg = msg .. string.format(T.statsInfantrySpawn, math.floor(blueInfantryInterval)) .. "\n"
else
msg = msg .. " Infantry Spawn: PAUSED (no warehouses)\n"
msg = msg .. T.statsInfantrySpawnPaused .. "\n"
end
if blueArmorInterval then
msg = msg .. " Armor Spawn: " .. math.floor(blueArmorInterval) .. "s\n\n"
msg = msg .. string.format(T.statsArmorSpawn, math.floor(blueArmorInterval)) .. "\n\n"
else
msg = msg .. " Armor Spawn: PAUSED (no warehouses)\n\n"
msg = msg .. T.statsArmorSpawnPaused .. "\n\n"
end
-- System Info
msg = msg .. "【SYSTEM INFO】\n"
msg = msg .. " Total Zones: " .. #zoneCaptureObjects .. "\n"
msg = msg .. " Active Garrisons: " .. (redGarrison.garrisoned + blueGarrison.garrisoned) .. "\n"
msg = msg .. " Total Active Units: " .. (redUnits.total + blueUnits.total) .. "\n"
msg = msg .. T.statsSystemInfo .. "\n"
msg = msg .. string.format(T.statsTotalZones, #zoneCaptureObjects) .. "\n"
msg = msg .. string.format(T.statsActiveGarrisons, redGarrison.garrisoned + blueGarrison.garrisoned) .. "\n"
msg = msg .. string.format(T.statsTotalActiveUnits, redUnits.total + blueUnits.total) .. "\n"
-- Memory and Performance Tracking
local totalSpawnedGroups = 0
@@ -995,17 +1324,17 @@ local function ShowSystemStatistics(playerCoalition)
end
local luaMemoryKB = collectgarbage("count")
msg = msg .. " Tracked Groups: " .. totalSpawnedGroups .. "\n"
msg = msg .. " Lua Memory: " .. string.format("%.1f MB", luaMemoryKB / 1024) .. "\n"
msg = msg .. string.format(T.statsTrackedGroups, totalSpawnedGroups) .. "\n"
msg = msg .. string.format(T.statsLuaMemory, luaMemoryKB / 1024) .. "\n"
-- Warning if memory is high
if luaMemoryKB > 512000 then -- More than 500MB
msg = msg .. " ⚠️ WARNING: High memory usage!\n"
msg = msg .. T.statsWarningMemory .. "\n"
end
-- Warning if too many groups
if totalSpawnedGroups > 200 then
msg = msg .. " ⚠️ WARNING: High group count!\n"
msg = msg .. T.statsWarningGroups .. "\n"
end
msg = msg .. "\n"
@@ -1028,8 +1357,13 @@ local blueZones = GetZonesByCoalition(coalition.side.BLUE)
local redSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(redWarehouses)
local blueSpawnFrequencyPercentage = CalculateSpawnFrequencyPercentage(blueWarehouses)
MESSAGE:New("Red reinforcement capacity: " .. redSpawnFrequencyPercentage .. "%", 30):ToRed()
MESSAGE:New("Blue reinforcement capacity: " .. blueSpawnFrequencyPercentage .. "%", 30):ToBlue()
local redLang = DGB_GetCoalitionLanguage(coalition.side.RED)
local redMsg = string.format(DGB_LANGUAGES[redLang].reinforcementCapacity, "Red", redSpawnFrequencyPercentage)
MESSAGE:New(redMsg, 30):ToRed()
local blueLang = DGB_GetCoalitionLanguage(coalition.side.BLUE)
local blueMsg = string.format(DGB_LANGUAGES[blueLang].reinforcementCapacity, "Blue", blueSpawnFrequencyPercentage)
MESSAGE:New(blueMsg, 30):ToBlue()
-- Initialize spawners
env.info("[DGB PLUGIN] Initializing spawn systems...")
@@ -1277,25 +1611,28 @@ SCHEDULER:New(nil, AssignTasksToGroups, {}, 15, ASSIGN_TASKS_SCHED)
-- Add F10 menu for manual checks (using MenuManager if available)
if MenuManager then
-- Create coalition-specific menus under Mission Options
local blueMenu = MenuManager.CreateCoalitionMenu(coalition.side.BLUE, "Ground Battle")
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Check Warehouse Status", blueMenu, MonitorWarehouses)
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show System Statistics", blueMenu, function()
local blueMenuText = DGB_GetTextForCoalition("menuGroundBattle", coalition.side.BLUE)
local blueMenu = MenuManager.CreateCoalitionMenu(coalition.side.BLUE, blueMenuText)
MENU_COALITION_COMMAND:New(coalition.side.BLUE, DGB_GetTextForCoalition("menuWarehouseStatus", coalition.side.BLUE), blueMenu, MonitorWarehouses)
MENU_COALITION_COMMAND:New(coalition.side.BLUE, DGB_GetTextForCoalition("menuSystemStats", coalition.side.BLUE), blueMenu, function()
ShowSystemStatistics(coalition.side.BLUE)
end)
local redMenu = MenuManager.CreateCoalitionMenu(coalition.side.RED, "Ground Battle")
MENU_COALITION_COMMAND:New(coalition.side.RED, "Check Warehouse Status", redMenu, MonitorWarehouses)
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show System Statistics", redMenu, function()
local redMenuText = DGB_GetTextForCoalition("menuGroundBattle", coalition.side.RED)
local redMenu = MenuManager.CreateCoalitionMenu(coalition.side.RED, redMenuText)
MENU_COALITION_COMMAND:New(coalition.side.RED, DGB_GetTextForCoalition("menuWarehouseStatus", coalition.side.RED), redMenu, MonitorWarehouses)
MENU_COALITION_COMMAND:New(coalition.side.RED, DGB_GetTextForCoalition("menuSystemStats", coalition.side.RED), redMenu, function()
ShowSystemStatistics(coalition.side.RED)
end)
else
-- Fallback to root-level mission menu
local missionMenu = MENU_MISSION:New("Ground Battle")
MENU_MISSION_COMMAND:New("Check Warehouse Status", missionMenu, MonitorWarehouses)
MENU_MISSION_COMMAND:New("Show Blue Statistics", missionMenu, function()
local missionMenuText = DGB_GetTextForCoalition("menuGroundBattle", coalition.side.BLUE)
local missionMenu = MENU_MISSION:New(missionMenuText)
MENU_MISSION_COMMAND:New(DGB_GetTextForCoalition("menuWarehouseStatus", coalition.side.BLUE), missionMenu, MonitorWarehouses)
MENU_MISSION_COMMAND:New(DGB_GetTextForCoalition("menuSystemStats", coalition.side.BLUE) .. " (Blue)", missionMenu, function()
ShowSystemStatistics(coalition.side.BLUE)
end)
MENU_MISSION_COMMAND:New("Show Red Statistics", missionMenu, function()
MENU_MISSION_COMMAND:New(DGB_GetTextForCoalition("menuSystemStats", coalition.side.RED) .. " (Red)", missionMenu, function()
ShowSystemStatistics(coalition.side.RED)
end)
end
+164
View File
@@ -0,0 +1,164 @@
--[[
Unified F10 Menu Manager
Purpose: Provides a centralized menu system to organize all mission scripts
into a consistent F10 menu structure.
Menu Organization:
F10 -> F1: Mission Options (all other scripts go here)
F10 -> F2: CTLD (reserved position)
F10 -> F3: AFAC Control (reserved position)
Usage:
1. Load this script FIRST before any other menu-creating scripts
2. Other scripts should use MenuManager to register their menus
Example:
-- In your script, instead of:
-- local MyMenu = MENU_COALITION:New(coalition.side.BLUE, "My Script")
-- Use:
-- local MyMenu = MenuManager.CreateCoalitionMenu(coalition.side.BLUE, "My Script")
]]--
MenuManager = {}
MenuManager.Version = "1.1"
-- Configuration
MenuManager.Config = {
EnableMissionOptionsMenu = true, -- Set to false to disable the parent menu system
MissionOptionsMenuName = "Mission Options", -- Name of the parent menu
Debug = false -- Set to true for debug messages
}
-- Storage for menu references
MenuManager.Menus = {
Blue = {},
Red = {},
Mission = {}
}
-- Parent menu references (created on first use)
MenuManager.ParentMenus = {
BlueCoalition = nil,
RedCoalition = nil,
Mission = nil
}
-- Initialize the parent menus
function MenuManager.Initialize()
if MenuManager.Config.EnableMissionOptionsMenu then
-- Create the parent "Mission Options" menu for each coalition
MenuManager.ParentMenus.BlueCoalition = MENU_COALITION:New(
coalition.side.BLUE,
MenuManager.Config.MissionOptionsMenuName
)
MenuManager.ParentMenus.RedCoalition = MENU_COALITION:New(
coalition.side.RED,
MenuManager.Config.MissionOptionsMenuName
)
-- Note: MENU_MISSION not created to avoid duplicate empty menu
-- Scripts that need mission-wide menus should use MENU_MISSION directly
if MenuManager.Config.Debug then
env.info("MenuManager: Initialized parent coalition menus")
end
end
end
-- Create a coalition menu under "Mission Options"
-- @param coalitionSide: coalition.side.BLUE or coalition.side.RED
-- @param menuName: Name of the menu
-- @param parentMenu: (Optional) If provided, creates as submenu of this parent instead of Mission Options
-- @return: MENU_COALITION object
function MenuManager.CreateCoalitionMenu(coalitionSide, menuName, parentMenu)
if MenuManager.Config.EnableMissionOptionsMenu and not parentMenu then
-- Create under Mission Options
local parent = (coalitionSide == coalition.side.BLUE)
and MenuManager.ParentMenus.BlueCoalition
or MenuManager.ParentMenus.RedCoalition
local menu = MENU_COALITION:New(coalitionSide, menuName, parent)
if MenuManager.Config.Debug then
local coalitionName = (coalitionSide == coalition.side.BLUE) and "BLUE" or "RED"
env.info(string.format("MenuManager: Created coalition menu '%s' for %s", menuName, coalitionName))
end
return menu
else
-- Create as root menu or under provided parent
local menu = MENU_COALITION:New(coalitionSide, menuName, parentMenu)
return menu
end
end
-- Create a mission menu (not nested under Mission Options, as that causes duplicates)
-- @param menuName: Name of the menu
-- @param parentMenu: (Optional) Parent menu
-- @return: MENU_MISSION object
-- Note: Mission menus are visible to all players and cannot be nested under coalition menus
function MenuManager.CreateMissionMenu(menuName, parentMenu)
-- Always create as root menu or under provided parent
-- Mission menus can't be nested under coalition-specific "Mission Options"
local menu = MENU_MISSION:New(menuName, parentMenu)
if MenuManager.Config.Debug then
env.info(string.format("MenuManager: Created mission menu '%s'", menuName))
end
return menu
end
-- Create a group menu under "Mission Options" for the player's coalition
-- @param group: GROUP object
-- @param menuName: Name of the menu
-- @param parentMenu: (Optional) If provided, creates as submenu of this parent instead of Mission Options
-- @return: MENU_GROUP object
function MenuManager.CreateGroupMenu(group, menuName, parentMenu)
if MenuManager.Config.EnableMissionOptionsMenu and not parentMenu then
-- Get group's coalition and create under appropriate Mission Options
local groupCoalition = group:getCoalition()
local parent = (groupCoalition == coalition.side.BLUE)
and MenuManager.ParentMenus.BlueCoalition
or MenuManager.ParentMenus.RedCoalition
local menu = MENU_GROUP:New(group, menuName, parent)
if MenuManager.Config.Debug then
local coalitionName = (groupCoalition == coalition.side.BLUE) and "BLUE" or "RED"
env.info(string.format("MenuManager: Created group menu '%s' for %s (coalition %s)",
menuName, group:getName(), coalitionName))
end
return menu
else
-- Create as root menu or under provided parent
local menu = MENU_GROUP:New(group, menuName, parentMenu)
return menu
end
end
-- Helper to disable the parent menu system at runtime
function MenuManager.DisableParentMenus()
MenuManager.Config.EnableMissionOptionsMenu = false
env.info("MenuManager: Parent menu system disabled")
end
-- Helper to enable the parent menu system at runtime
function MenuManager.EnableParentMenus()
MenuManager.Config.EnableMissionOptionsMenu = true
if not MenuManager.ParentMenus.BlueCoalition then
MenuManager.Initialize()
end
env.info("MenuManager: Parent menu system enabled")
end
-- Initialize on load
MenuManager.Initialize()
-- Announcement
env.info(string.format("MenuManager v%s loaded - Mission Options menu system ready", MenuManager.Version))
File diff suppressed because it is too large Load Diff
-292
View File
@@ -1,293 +1 @@
# MOOSE Dual Coalition Zone Capture System
- **Author**: F99th-TracerFacer
- **Discord:** https://discord.gg/NdZ2JuSU (The Fighting 99th Discord Server where I spend most of my time.)
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 count.
- Wait 30 seconds for scan cycle
- Eliminate ALL enemy forces to capture
- Check DCS.log for detailed information
- Increase logging and capture logs before reaching out to me. Happy to help troubleshoot. Discord info below.
### 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
- **F99th Mission Maker Discord Community**: https://discord.gg/kTNmMScQNf
- **Author**: F99th-TracerFacer
- **GitHub Issues**: Report bugs or request features: https://github.com/users/iTracerFacer/projects/3
### 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
- **Discord:** https://discord.gg/NdZ2JuSU (The Fighting 99th Discord Server where I spend most of my time.)
- **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
---
<div align="center">
**🎮 Happy Mission Making! 🚁**
*Created with ❤️ for the DCS World Community*
[Discord](https://discord.gg/kTNmMScQNf) • [Documentation](Mission_Maker_Guide.html) • [Report Issue](#)
</div>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB