Fixed offset menus.

This commit is contained in:
iTracerFacer 2025-11-05 10:45:02 -06:00
parent 65eae0a317
commit ec967de71e
3 changed files with 122 additions and 113 deletions

View File

@ -196,7 +196,7 @@ CTLD.Config = {
-- Label placement tuning (simple):
-- Effective extra offset from the circle edge = r * LabelOffsetRatio + LabelOffsetFromEdge
LabelOffsetFromEdge = 0, -- meters beyond the zone radius to place the label (12 o'clock)
LabelOffsetRatio = 0.0, -- fraction of the radius to add to the offset (e.g., 0.1 => +10% of r)
LabelOffsetRatio = 0.5, -- fraction of the radius to add to the offset (e.g., 0.1 => +10% of r)
LabelOffsetX = 200, -- meters: horizontal nudge; adjust if text appears left-anchored in your DCS build
-- Per-kind label prefixes
LabelPrefixes = {
@ -206,13 +206,6 @@ CTLD.Config = {
}
},
-- Optional: bindings to mission flags to activate/deactivate zones via ME triggers
-- Each entry: { kind = 'Pickup'|'Drop'|'FOB', name = 'Zone Name', flag = 1001, activeWhen = 1 }
ZoneEventBindings = {
-- Example:
-- { kind = 'Pickup', name = 'Pickup Zone 1', flag = 9001, activeWhen = 1 },
-- { kind = 'Drop', name = 'DZ_WEST', flag = 9002, activeWhen = 1 },
},
-- Crate spawn placement within pickup zones
PickupZoneSpawnRandomize = true, -- if true, spawn crates at a random point within the pickup zone (avoids stacking)
PickupZoneSpawnEdgeBuffer = 10, -- meters: keep spawns at least this far inside the zone edge
@ -233,32 +226,6 @@ CTLD.Config = {
MaxSpeedMPS = 5 -- max allowed speed in m/s for hover pickup
},
Zones = { -- Optional: supply by name (ME trigger zones) or define coordinates inline
PickupZones = {
-- Examples:
-- Create 5 trigger zones in the Mission Editor named exactly as below to get started quickly.
-- If you run separate CTLD instances for BLUE and RED, give each side its own set of uniquely named zones
-- (recommended to avoid overlap). You can keep the simple "Pickup Zone #" pattern per side if you prefer,
-- just ensure the names in the ME match what you configure here.
--
-- Uncomment the lines you want to use:
-- { name = 'Pickup Zone 1', smoke = trigger.smokeColor.Green },
-- { name = 'Pickup Zone 2', smoke = trigger.smokeColor.Blue },
-- { name = 'Pickup Zone 3', smoke = trigger.smokeColor.Orange },
-- { name = 'Pickup Zone 4', smoke = trigger.smokeColor.White },
-- { name = 'Pickup Zone 5', smoke = trigger.smokeColor.Red },
--
-- Tip: You can also define zones purely in script (no ME zone needed):
-- { coord = { x = 12345, y = 0, z = 67890 }, radius = 150, name = 'Pickup Zone 1' },
},
DropZones = {
-- { name = 'DROP_BLUE_1' },
},
FOBZones = {
-- optional: where FOB crates can unpack to spawn FARP/FOB assets
},
},
-- Crate catalog: key -> crate properties and build recipe
-- No ME templates; unit compositions are defined directly here.
CrateCatalog = {
@ -1042,8 +1009,7 @@ end
function CTLD:InitMenus()
if self.Config.UseGroupMenus then
self:WireBirthHandler()
-- Always provide a coalition-level Admin/Help menu for mission makers
self:InitCoalitionAdminMenu()
-- No coalition-level root when using per-group menus; Admin/Help is nested under each group's CTLD menu
else
self.MenuRoot = MENU_COALITION:New(self.Side, 'CTLD')
self:BuildCoalitionMenus(self.MenuRoot)
@ -1073,6 +1039,16 @@ end
function CTLD:BuildGroupMenus(group)
local root = MENU_GROUP:New(group, 'CTLD')
-- Safe menu command helper: wraps callbacks to prevent silent errors
local function CMD(title, parent, cb)
return MENU_GROUP_COMMAND:New(group, title, parent, function()
local ok, err = pcall(cb)
if not ok then
env.info('[Moose_CTLD] Menu error: '..tostring(err))
MESSAGE:New('CTLD menu error: '..tostring(err), 8):ToGroup(group)
end
end)
end
-- Request crate submenu per catalog entry
local reqRoot = MENU_GROUP:New(group, 'Request Crate', root)
if self.Config.UseCategorySubmenus then
@ -1089,9 +1065,7 @@ function CTLD:BuildGroupMenus(group)
if sideOk then
local catLabel = (def and def.menuCategory) or 'Other'
local parent = getSubmenu(catLabel)
MENU_GROUP_COMMAND:New(group, label, parent, function()
self:RequestCrateForGroup(group, key)
end)
CMD(label, parent, function() self:RequestCrateForGroup(group, key) end)
end
end
else
@ -1099,43 +1073,35 @@ function CTLD:BuildGroupMenus(group)
local label = (def and (def.menu or def.description)) or key
local sideOk = (not def.side) or def.side == self.Side
if sideOk then
MENU_GROUP_COMMAND:New(group, label, reqRoot, function()
self:RequestCrateForGroup(group, key)
end)
CMD(label, reqRoot, function() self:RequestCrateForGroup(group, key) end)
end
end
end
-- Troops
MENU_GROUP_COMMAND:New(group, 'Load Troops', root, function() self:LoadTroops(group) end)
MENU_GROUP_COMMAND:New(group, 'Unload Troops', root, function() self:UnloadTroops(group) end)
CMD('Load Troops', root, function() self:LoadTroops(group) end)
CMD('Unload Troops', root, function() self:UnloadTroops(group) end)
-- Build
MENU_GROUP_COMMAND:New(group, 'Build Here', root, function()
self:BuildAtGroup(group)
end)
CMD('Build Here', root, function() self:BuildAtGroup(group) end)
-- Crate management (loaded crates)
MENU_GROUP_COMMAND:New(group, 'Drop One Loaded Crate', root, function()
self:DropLoadedCrates(group, 1)
end)
MENU_GROUP_COMMAND:New(group, 'Drop All Loaded Crates', root, function()
self:DropLoadedCrates(group, -1)
end)
CMD('Drop One Loaded Crate', root, function() self:DropLoadedCrates(group, 1) end)
CMD('Drop All Loaded Crates', root, function() self:DropLoadedCrates(group, -1) end)
-- Coach & Navigation utilities
local navRoot = MENU_GROUP:New(group, 'Coach & Nav', root)
local gname = group:GetName()
MENU_GROUP_COMMAND:New(group, 'Hover Coach: Enable', navRoot, function()
CMD('Hover Coach: Enable', navRoot, function()
CTLD._coachOverride = CTLD._coachOverride or {}
CTLD._coachOverride[gname] = true
_eventSend(self, group, nil, 'coach_enabled', {})
end)
MENU_GROUP_COMMAND:New(group, 'Hover Coach: Disable', navRoot, function()
CMD('Hover Coach: Disable', navRoot, function()
CTLD._coachOverride = CTLD._coachOverride or {}
CTLD._coachOverride[gname] = false
_eventSend(self, group, nil, 'coach_disabled', {})
end)
MENU_GROUP_COMMAND:New(group, 'Request Vectors to Nearest Crate', navRoot, function()
CMD('Request Vectors to Nearest Crate', navRoot, function()
local unit = group:GetUnit(1)
if not unit or not unit:IsAlive() then return end
local p = unit:GetPointVec3()
@ -1161,7 +1127,7 @@ function CTLD:BuildGroupMenus(group)
_msgGroup(group, 'No friendly crates found.')
end
end)
MENU_GROUP_COMMAND:New(group, 'Vectors to Nearest Pickup Zone', navRoot, function()
CMD('Vectors to Nearest Pickup Zone', navRoot, function()
local unit = group:GetUnit(1)
if not unit or not unit:IsAlive() then return end
local zone = nil
@ -1198,7 +1164,7 @@ function CTLD:BuildGroupMenus(group)
local rngV, rngU = _fmtRange(dist, isMetric)
_eventSend(self, group, nil, 'vectors_to_pickup_zone', { zone = zone:GetName(), brg = brg, rng = rngV, rng_u = rngU })
end)
MENU_GROUP_COMMAND:New(group, 'Re-mark Nearest Crate (Smoke)', navRoot, function()
CMD('Re-mark Nearest Crate (Smoke)', navRoot, function()
local unit = group:GetUnit(1)
if not unit or not unit:IsAlive() then return end
local p = unit:GetPointVec3()
@ -1223,6 +1189,37 @@ function CTLD:BuildGroupMenus(group)
end
end)
-- Admin/Help (nested under CTLD group menu when using group menus)
local admin = MENU_GROUP:New(group, 'Admin/Help', root)
CMD('Enable CTLD Debug Logging', admin, function()
self.Config.Debug = true
env.info(string.format('[Moose_CTLD][%s] Debug ENABLED via Admin menu', tostring(self.Side)))
MESSAGE:New('CTLD Debug logging ENABLED', 8):ToGroup(group)
end)
CMD('Disable CTLD Debug Logging', admin, function()
self.Config.Debug = false
env.info(string.format('[Moose_CTLD][%s] Debug DISABLED via Admin menu', tostring(self.Side)))
MESSAGE:New('CTLD Debug logging DISABLED', 8):ToGroup(group)
end)
CMD('Show CTLD Status (crates/zones)', admin, function()
-- Reuse the coalition summary builder but send to this group
local crates = 0
for _ in pairs(CTLD._crates) do crates = crates + 1 end
local msg = string.format('CTLD Status:\nActive crates: %d\nPickup zones: %d\nDrop zones: %d\nFOB zones: %d\nBuild Confirm: %s (%ds window)\nBuild Cooldown: %s (%ds)'
, crates, #(self.PickupZones or {}), #(self.DropZones or {}), #(self.FOBZones or {})
, self.Config.BuildConfirmEnabled and 'ON' or 'OFF', self.Config.BuildConfirmWindowSeconds or 0
, self.Config.BuildCooldownEnabled and 'ON' or 'OFF', self.Config.BuildCooldownSeconds or 0)
MESSAGE:New(msg, 20):ToGroup(group)
end)
CMD('Draw CTLD Zones on Map', admin, function()
self:DrawZonesOnMap()
MESSAGE:New('CTLD zones drawn on F10 map.', 8):ToGroup(group)
end)
CMD('Clear CTLD Map Drawings', admin, function()
self:ClearMapDrawings()
MESSAGE:New('CTLD map drawings cleared.', 8):ToGroup(group)
end)
return root
end
@ -1238,7 +1235,10 @@ end
function CTLD:InitCoalitionAdminMenu()
if self.AdminMenu then return end
local root = MENU_COALITION:New(self.Side, 'CTLD Admin/Help')
-- Ensure we have a coalition-level CTLD parent menu to nest Admin/Help under
local rootCaption = (self.Config and self.Config.UseGroupMenus) and 'CTLD Admin' or 'CTLD'
self.MenuRoot = self.MenuRoot or MENU_COALITION:New(self.Side, rootCaption)
local root = MENU_COALITION:New(self.Side, 'Admin/Help', self.MenuRoot)
MENU_COALITION_COMMAND:New(self.Side, 'Enable CTLD Debug Logging', root, function()
self.Config.Debug = true
env.info(string.format('[Moose_CTLD][%s] Debug ENABLED via Admin menu', tostring(self.Side)))

View File

@ -293,8 +293,10 @@ function FAC:New(ctld, cfg)
o._schedStatus = SCHEDULER:New(nil, function() o:_checkFacStatus() end, {}, 5, 1.0)
o._schedAI = SCHEDULER:New(nil, function() o:_artyAICall() end, {}, 10, 30)
-- Coalition-level Admin/Help menu
o:_ensureCoalitionMenu()
-- Only create coalition-level Admin/Help when not using per-group menus
if not o.Config.UseGroupMenus then
o:_ensureCoalitionMenu()
end
return o
end
@ -445,9 +447,14 @@ function FAC:_ensureMenus()
end
function FAC:_ensureCoalitionMenu()
-- Create a coalition-level Admin/Help menu regardless of per-group menus
if self.Config.UseGroupMenus then return end
-- Create a coalition-level Admin/Help menu, nested under a FAC parent (not at F10 root)
if self._coalitionMenus[self.Side] then return end
local root = MENU_COALITION:New(self.Side, 'FAC Admin/Help')
self._coalitionRoot = self._coalitionRoot or {}
-- Create or reuse the coalition-level parent menu for FAC
self._coalitionRoot[self.Side] = self._coalitionRoot[self.Side] or MENU_COALITION:New(self.Side, 'FAC/RECCE Admin')
local parent = self._coalitionRoot[self.Side]
local root = MENU_COALITION:New(self.Side, 'Admin/Help', parent)
MENU_COALITION_COMMAND:New(self.Side, 'Show FAC Codes In Use', root, function()
self:_showCodesCoalition()
end)
@ -467,40 +474,40 @@ end
function FAC:_buildGroupMenus(group)
-- Build the entire FAC/RECCE menu tree for a MOOSE GROUP
local root = MENU_GROUP:New(group, 'FAC/RECCE')
-- Safe menu command helper: wraps callbacks to avoid silent errors and report to group
local function CMD(title, parent, cb)
return MENU_GROUP_COMMAND:New(group, title, parent, function()
local ok, err = pcall(cb)
if not ok then
env.info('[FAC] Menu error: '..tostring(err))
MESSAGE:New('FAC menu error: '..tostring(err), 8):ToGroup(group)
end
end)
end
-- Status & On-Station
MENU_GROUP_COMMAND:New(group, 'FAC: Status', root, function()
self:_showFacStatus(group)
end)
CMD('FAC: Status', root, function() self:_showFacStatus(group) end)
local tgtRoot = MENU_GROUP:New(group, 'Targeting Mode', root)
MENU_GROUP_COMMAND:New(group, 'Auto Laze ON', tgtRoot, function()
self:_setOnStation(group, true)
end)
MENU_GROUP_COMMAND:New(group, 'Auto Laze OFF', tgtRoot, function()
self:_setOnStation(group, nil)
end)
MENU_GROUP_COMMAND:New(group, 'Scan for Close Targets', tgtRoot, function()
self:_scanManualList(group)
end)
CMD('Auto Laze ON', tgtRoot, function() self:_setOnStation(group, true) end)
CMD('Auto Laze OFF', tgtRoot, function() self:_setOnStation(group, nil) end)
CMD('Scan for Close Targets', tgtRoot, function() self:_scanManualList(group) end)
local selRoot = MENU_GROUP:New(group, 'Select Found Target', tgtRoot)
for i=1,10 do
MENU_GROUP_COMMAND:New(group, 'Target '..i, selRoot, function() self:_setManualTarget(group, i) end)
CMD('Target '..i, selRoot, function() self:_setManualTarget(group, i) end)
end
MENU_GROUP_COMMAND:New(group, 'Call arty on all manual targets', tgtRoot, function()
self:_multiStrike(group)
end)
CMD('Call arty on all manual targets', tgtRoot, function() self:_multiStrike(group) end)
-- Laser codes
local lzr = MENU_GROUP:New(group, 'Laser Code', root)
for _,code in ipairs(self.Config.FAC_laser_codes) do
MENU_GROUP_COMMAND:New(group, code, lzr, function() self:_setLaserCode(group, code) end)
CMD(code, lzr, function() self:_setLaserCode(group, code) end)
end
local cust = MENU_GROUP:New(group, 'Custom Code', lzr)
local function addDigitMenu(d, max)
local m = MENU_GROUP:New(group, 'Digit '..d, cust)
for n=1,max do
MENU_GROUP_COMMAND:New(group, tostring(n), m, function() self:_setLaserDigit(group, d, n) end)
CMD(tostring(n), m, function() self:_setLaserDigit(group, d, n) end)
end
end
addDigitMenu(1,1); addDigitMenu(2,6); addDigitMenu(3,8); addDigitMenu(4,8)
@ -512,41 +519,55 @@ function FAC:_buildGroupMenus(group)
local function setM(typeName, color)
return function() self:_setMarker(group, typeName, color) end
end
MENU_GROUP_COMMAND:New(group, 'GREEN', sm, setM('SMOKE', trigger.smokeColor.Green))
MENU_GROUP_COMMAND:New(group, 'RED', sm, setM('SMOKE', trigger.smokeColor.Red))
MENU_GROUP_COMMAND:New(group, 'WHITE', sm, setM('SMOKE', trigger.smokeColor.White))
MENU_GROUP_COMMAND:New(group, 'ORANGE', sm, setM('SMOKE', trigger.smokeColor.Orange))
MENU_GROUP_COMMAND:New(group, 'BLUE', sm, setM('SMOKE', trigger.smokeColor.Blue))
MENU_GROUP_COMMAND:New(group, 'GREEN', fl, setM('FLARES', trigger.smokeColor.Green))
MENU_GROUP_COMMAND:New(group, 'WHITE', fl, setM('FLARES', trigger.smokeColor.White))
MENU_GROUP_COMMAND:New(group, 'ORANGE', fl, setM('FLARES', trigger.smokeColor.Orange))
MENU_GROUP_COMMAND:New(group, 'Map Marker current target', mk, function() self:_setMapMarker(group) end)
CMD('GREEN', sm, setM('SMOKE', trigger.smokeColor.Green))
CMD('RED', sm, setM('SMOKE', trigger.smokeColor.Red))
CMD('WHITE', sm, setM('SMOKE', trigger.smokeColor.White))
CMD('ORANGE', sm, setM('SMOKE', trigger.smokeColor.Orange))
CMD('BLUE', sm, setM('SMOKE', trigger.smokeColor.Blue))
CMD('GREEN', fl, setM('FLARES', trigger.smokeColor.Green))
CMD('WHITE', fl, setM('FLARES', trigger.smokeColor.White))
CMD('ORANGE', fl, setM('FLARES', trigger.smokeColor.Orange))
CMD('Map Marker current target', mk, function() self:_setMapMarker(group) end)
-- Artillery
local arty = MENU_GROUP:New(group, 'Artillery', root)
MENU_GROUP_COMMAND:New(group, 'Check available arty', arty, function() self:_checkArty(group) end)
MENU_GROUP_COMMAND:New(group, 'Call Fire Mission (HE)', arty, function() self:_callFireMission(group, self.Config.fireMissionRounds, 0) end)
MENU_GROUP_COMMAND:New(group, 'Call Illumination', arty, function() self:_callFireMission(group, self.Config.fireMissionRounds, 1) end)
MENU_GROUP_COMMAND:New(group, 'Call Mortar Only (anti-infantry)', arty, function() self:_callFireMission(group, self.Config.fireMissionRounds, 2) end)
MENU_GROUP_COMMAND:New(group, 'Call Heavy Only (no smart)', arty, function() self:_callFireMission(group, 10, 3) end)
CMD('Check available arty', arty, function() self:_checkArty(group) end)
CMD('Call Fire Mission (HE)', arty, function() self:_callFireMission(group, self.Config.fireMissionRounds, 0) end)
CMD('Call Illumination', arty, function() self:_callFireMission(group, self.Config.fireMissionRounds, 1) end)
CMD('Call Mortar Only (anti-infantry)', arty, function() self:_callFireMission(group, self.Config.fireMissionRounds, 2) end)
CMD('Call Heavy Only (no smart)', arty, function() self:_callFireMission(group, 10, 3) end)
local air = MENU_GROUP:New(group, 'Air/Naval', arty)
MENU_GROUP_COMMAND:New(group, 'Single Target (GPS/Guided)', air, function() self:_callFireMission(group, 1, 4) end)
MENU_GROUP_COMMAND:New(group, 'Multi Target (Guided only)', air, function() self:_callFireMissionMulti(group, 1, 4) end)
MENU_GROUP_COMMAND:New(group, 'Carpet Bomb (attack heading = aircraft heading)', air, function() self:_callCarpetOnCurrent(group) end)
CMD('Single Target (GPS/Guided)', air, function() self:_callFireMission(group, 1, 4) end)
CMD('Multi Target (Guided only)', air, function() self:_callFireMissionMulti(group, 1, 4) end)
CMD('Carpet Bomb (attack heading = aircraft heading)', air, function() self:_callCarpetOnCurrent(group) end)
-- RECCE
MENU_GROUP_COMMAND:New(group, 'RECCE: Sweep & Mark', root, function() self:_recceDetect(group) end)
CMD('RECCE: Sweep & Mark', root, function() self:_recceDetect(group) end)
-- Admin/Help (nested inside FAC/RECCE group menu when using group menus)
local admin = MENU_GROUP:New(group, 'Admin/Help', root)
CMD('Show FAC Codes In Use', admin, function() self:_showCodesCoalition() end)
CMD('Enable FAC Debug Logging', admin, function()
self.Config.Debug = true
env.info(string.format('[FAC][%s] Debug ENABLED via Admin menu', tostring(self.Side)))
MESSAGE:New('FAC Debug logging ENABLED', 8):ToGroup(group)
end)
CMD('Disable FAC Debug Logging', admin, function()
self.Config.Debug = false
env.info(string.format('[FAC][%s] Debug DISABLED via Admin menu', tostring(self.Side)))
MESSAGE:New('FAC Debug logging DISABLED', 8):ToGroup(group)
end)
-- Debug controls (mission-maker convenience; per-instance toggle)
local dbg = MENU_GROUP:New(group, 'Debug', root)
MENU_GROUP_COMMAND:New(group, 'Enable Debug Logging', dbg, function()
CMD('Enable Debug Logging', dbg, function()
self.Config.Debug = true
local u = group:GetUnit(1); local who = (u and u:GetName()) or 'Unknown'
env.info(string.format('[FAC] Debug ENABLED by %s', who))
MESSAGE:New('FAC Debug logging ENABLED', 8):ToGroup(group)
end)
MENU_GROUP_COMMAND:New(group, 'Disable Debug Logging', dbg, function()
CMD('Disable Debug Logging', dbg, function()
self.Config.Debug = false
local u = group:GetUnit(1); local who = (u and u:GetName()) or 'Unknown'
env.info(string.format('[FAC] Debug DISABLED by %s', who))
@ -603,18 +624,6 @@ function FAC:_posString(u)
local p = u:getPosition().p
local lat, lon = coord.LOtoLL(p)
local dms = _llToDMS(lat, lon)
-- Append code summary across active FACs for situational awareness
local codes = self._reservedCodes[side] or {}
local any = false
local summary = '\nCodes in use:\n'
for _,code in ipairs(self.Config.FAC_laser_codes or {}) do
local owner = codes[tostring(code)]
if owner then
any = true
summary = summary .. string.format(' %s -> %s\n', tostring(code), self:_facName(owner))
end
end
if any then msg = msg .. summary end
local mgrs = _mgrsToString(coord.LLtoMGRS(lat, lon))
local altM = math.floor(p.y)
local altF = math.floor(p.y*3.28084)

Binary file not shown.