Updated CTLD. Fixed FARP Upgrade not working. Fixed claimin cargo by putting a salvage zone on top of it.

This commit is contained in:
iTracerFacer 2025-11-29 16:07:29 -06:00
parent 70842b241d
commit 6994ca0642
8 changed files with 23534 additions and 249 deletions

8226
CTLD.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@ -182,13 +182,13 @@ CTLD.Messages = {
-- FARP System messages
farp_upgrade_started = "Upgrading FOB to FARP Stage {stage}... Building in progress.",
farp_upgrade_complete = "{player} upgraded FOB to FARP Stage {stage}! Services available: {services}",
farp_upgrade_complete = "{player} upgraded FOB to FARP Stage {stage}!\nFARP Services: {services}\nFOB still active for logistics and troop operations.",
farp_upgrade_insufficient_salvage = "Insufficient salvage to upgrade to FARP Stage {stage}. Need {need} points (have {current}). Deliver crews to MASH or sling-load salvage!",
farp_status = "FOB Status: FARP Stage {stage}/{max_stage}\nServices: {services}\nNext upgrade: {next_cost} salvage (Stage {next_stage})",
farp_status_maxed = "FOB Status: FARP Stage {stage}/{max_stage} (FULLY UPGRADED)\nServices: {services}",
farp_status = "FOB + FARP Status: Stage {stage}/{max_stage}\nFARP Services: {services}\nFOB logistics: ACTIVE\nNext upgrade: {next_cost} salvage (Stage {next_stage})",
farp_status_maxed = "FOB + FARP Status: Stage {stage}/{max_stage} (FULLY UPGRADED)\nFARP Services: {services}\nFOB logistics: ACTIVE",
farp_not_at_fob = "You must be near a FOB Pickup Zone to upgrade it to a FARP.",
farp_already_maxed = "This FOB is already at maximum FARP stage (Stage 3).",
farp_service_available = "FARP services available: Rearm, Refuel, Repair for ground vehicles and helicopters within {radius}m.",
farp_service_available = "FARP services available: Rearm, Refuel, Repair for ground vehicles and helicopters within {radius}m. FOB logistics remain active.",
slingload_salvage_warn_5min = "SALVAGE URGENT: Crate {id} at {grid} expires in 5 minutes!",
slingload_salvage_hooked_in_zone = "Salvage crate {id} is inside {zone}. Release the sling to complete delivery.",
slingload_salvage_wrong_zone = "Salvage crate {id} is sitting in {zone_type} zone {zone}. Take it to an active Salvage zone for credit.",
@ -621,16 +621,16 @@ CTLD.Config = {
-- Spawn probability when enemy ground units die
SpawnChance = {
[coalition.side.BLUE] = 0.20, -- 20% chance when BLUE unit dies (RED can collect the salvage)
[coalition.side.RED] = 0.20, -- 20% chance when RED unit dies (BLUE can collect the salvage)
[coalition.side.BLUE] = 0.10, -- 20% chance when BLUE unit dies (RED can collect the salvage)
[coalition.side.RED] = 0.10, -- 20% chance when RED unit dies (BLUE can collect the salvage)
},
-- Weight classes with spawn probabilities and reward rates
WeightClasses = {
{ name = 'Light', min = 500, max = 1000, probability = 0.50, rewardPer500kg = 2 }, -- Huey-capable
{ name = 'Medium', min = 2501, max = 5000, probability = 0.30, rewardPer500kg = 3 }, -- Hip/Mi-8
{ name = 'Heavy', min = 5001, max = 8000, probability = 0.15, rewardPer500kg = 5 }, -- Large helos
{ name = 'SuperHeavy', min = 8001, max = 12000, probability = 0.05, rewardPer500kg = 8 }, -- Chinook only
{ name = 'Light', min = 500, max = 1000, probability = 0.50, rewardPer500kg = 0.5 }, -- 1-2 pts (reduced from 2)
{ name = 'Medium', min = 2501, max = 5000, probability = 0.30, rewardPer500kg = 1 }, -- 5-10 pts (reduced from 3)
{ name = 'Heavy', min = 5001, max = 8000, probability = 0.15, rewardPer500kg = 1.5 }, -- 15-24 pts (reduced from 5)
{ name = 'SuperHeavy', min = 8001, max = 12000, probability = 0.05, rewardPer500kg = 2 }, -- 32-48 pts (reduced from 8)
},
-- Condition-based reward multipliers (based on crate health when delivered)
@ -713,134 +713,76 @@ CTLD.FARPConfig = {
-- Format: { type = "DCS_Static_Name", x = offset_x, z = offset_z, heading = degrees, height = 0 }
-- Positions are relative to FOB center point
StageLayouts = {
-- Stage 1: Basic FARP Pad (3 salvage)
-- Stage 1: Basic FARP Pad (3 salvage) - Inner ring 30-60m
[1] = {
{ type = "FARP CP Blindage", x = 0, z = 25, heading = 180 },
{ type = "FARP Tent", x = 17.3, z = 10, heading = 240 },
{ type = "FARP Tent", x = -17.3, z = 10, heading = 120 },
{ type = "container_20ft", x = 15.4, z = -6.2, heading = 90 },
{ type = "container_20ft", x = -15.4, z = -6.2, heading = 90 },
{ type = "Windsock", x = 0, z = 30, heading = 0 },
{ type = "FARP Ammo Dump Coating", x = 13, z = -10.6, heading = 30 },
{ type = "FARP Ammo Dump Coating", x = -13, z = -10.6, heading = 330 },
{ type = ".Ammunition depot", x = 17, z = 12, heading = 0 },
{ type = ".Ammunition depot", x = -17, z = 12, heading = 0 },
{ type = "BarrelCargo", x = 8, z = -18, heading = 0 },
{ type = "BarrelCargo", x = -8, z = -18, heading = 0 },
{ type = "BarrelCargo", x = 12, z = 20, heading = 0 },
{ type = "BarrelCargo", x = -12, z = 20, heading = 0 },
{ type = "GeneratorF", x = 22, z = -3, heading = 270 },
{ type = "Sandbox", x = 10, z = -16, heading = 0 },
{ type = "Sandbox", x = -10, z = -16, heading = 0 },
{ type = "Sandbox", x = 10, z = 16, heading = 0 },
{ type = "Sandbox", x = -10, z = 16, heading = 0 },
{ type = "Sandbox", x = 18, z = 0, heading = 0 },
{ type = "Sandbox", x = -18, z = 0, heading = 0 },
{ type = "FARP CP Blindage", x = 0, z = 40, heading = 0 },
{ type = "FARP Tent", x = 45, z = 20, heading = 30 },
{ type = "FARP Tent", x = -45, z = 20, heading = 330 },
{ type = "container_20ft", x = 35, z = -30, heading = 338 },
{ type = "container_20ft", x = -35, z = -30, heading = 202 },
{ type = "Windsock", x = 0, z = 60, heading = 0 },
{ type = "FARP Ammo Dump Coating", x = 30, z = -25, heading = 219 },
{ type = "FARP Ammo Dump Coating", x = -30, z = -25, heading = 141 },
{ type = "FARP Fuel Depot", x = 40, z = 30, heading = 30 },
{ type = "FARP Fuel Depot", x = -40, z = 30, heading = 330 },
{ type = "GeneratorF", x = 50, z = -10, heading = 278 },
{ type = "container_20ft", x = -50, z = -10, heading = 98 },
},
-- Stage 2: Operational FARP - adds fuel capability (5 more salvage)
-- Stage 2: Operational FARP - adds fuel capability (5 more salvage) - Middle ring 80-130m
[2] = {
{ type = "M978 HEMTT Tanker", x = 90, z = 0, heading = 270 },
{ type = "M978 HEMTT Tanker", x = -90, z = 0, heading = 90 },
{ type = "FARP Fuel Depot", x = 95, z = -40, heading = 281 },
{ type = "FARP Fuel Depot", x = -95, z = -40, heading = 79 },
{ type = "FARP Tent", x = 80, z = 60, heading = 39 },
{ type = "FARP Tent", x = -80, z = 60, heading = 321 },
{ type = "container_40ft", x = 0, z = -100, heading = 180 },
{ type = "container_40ft", x = 50, z = -100, heading = 180 },
{ type = "FARP Ammo Dump Coating", x = 85, z = 70, heading = 30 },
{ type = "FARP Ammo Dump Coating", x = -85, z = 70, heading = 330 },
{ type = "Ural-375 PBU", x = 105, z = -30, heading = 247 },
{ type = "Electric power box", x = 90, z = 50, heading = 36 },
{ type = "Electric power box", x = -90, z = 50, heading = 324 },
{ type = "GeneratorF", x = 0, z = -85, heading = 180 },
},
-- Stage 3: Full Forward Airbase - adds ammo and comms (8 more salvage) - Outer ring 150-250m
[3] = {
{ type = "FARP", x = 0, z = 0, heading = 0 },
-- Service objects near pad for scripted services
{ type = "M978 HEMTT Tanker", x = 35, z = 0, heading = 270 },
{ type = "M978 HEMTT Tanker", x = -35, z = 0, heading = 90 },
{ type = "FARP Fuel Depot", x = 40, z = -8, heading = 0 },
{ type = "FARP Fuel Depot", x = -40, z = -8, heading = 0 },
{ type = "FARP Tent", x = 26.5, z = 21.7, heading = 210 },
{ type = "FARP Tent", x = -26.5, z = 21.7, heading = 150 },
{ type = "container_40ft", x = 0, z = -35, heading = 0 },
{ type = "container_40ft", x = 8, z = -35, heading = 0 },
{ type = "Hesco_wallperimeter_7", x = 0, z = 42, heading = 0 },
{ type = "Hesco_wallperimeter_7", x = 30, z = 30, heading = 315 },
{ type = "Hesco_wallperimeter_7", x = -30, z = 30, heading = 45 },
{ type = "Red_Flag", x = 0, z = 45, heading = 0 },
{ type = "Red_Flag", x = 45, z = 0, heading = 0 },
{ type = "Red_Flag", x = -45, z = 0, heading = 0 },
{ type = "Red_Flag", x = 0, z = -45, heading = 0 },
{ type = "Ural-375 PBU", x = 32.9, z = -13.1, heading = 225 },
{ type = "Electric power box", x = 28, z = 20, heading = 0 },
{ type = "Electric power box", x = -28, z = 20, heading = 0 },
{ type = "Landmine pot", x = 36, z = 8, heading = 0 },
{ type = "Landmine pot", x = -36, z = 8, heading = 0 },
{ type = "Landmine pot", x = 30, z = -25, heading = 0 },
{ type = "Landmine pot", x = -30, z = -25, heading = 0 },
{ type = "Landmine pot", x = 20, z = 30, heading = 0 },
{ type = "Landmine pot", x = -20, z = 30, heading = 0 },
{ type = "Tetrapod", x = 42, z = 15, heading = 0 },
{ type = "Tetrapod", x = -42, z = 15, heading = 0 },
{ type = "Tetrapod", x = 42, z = -15, heading = 0 },
{ type = "Tetrapod", x = -42, z = -15, heading = 0 },
{ type = "Tetrapod", x = 15, z = 42, heading = 0 },
{ type = "Tetrapod", x = -15, z = 42, heading = 0 },
{ type = "Tetrapod", x = 15, z = -42, heading = 0 },
{ type = "Tetrapod", x = -15, z = -42, heading = 0 },
{ type = "FARP Command Post", x = 0, z = 25, heading = 180 },
},
-- Stage 3: Full Forward Airbase - adds ammo and comms (8 more salvage)
[3] = {
{ type = "M939 Heavy", x = 38.9, z = -21.9, heading = 225 },
{ type = "M939 Heavy", x = -38.9, z = -21.9, heading = 135 },
{ type = "Shelter", x = 0, z = -50, heading = 0 },
{ type = "FARP Ammo Dump Coating", x = 48, z = -10, heading = 0 },
{ type = "FARP Ammo Dump Coating", x = -48, z = -10, heading = 0 },
{ type = "FARP Ammo Dump Coating", x = 45, z = -20, heading = 0 },
{ type = "FARP Ammo Dump Coating", x = -45, z = -20, heading = 0 },
{ type = "SKP-11", x = 0, z = 55, heading = 180 },
{ type = "ZiL-131 APA-80", x = 8, z = 52, heading = 180 },
{ type = "Hesco_wallperimeter_1", x = 52, z = 30, heading = 0 },
{ type = "Hesco_wallperimeter_1", x = -52, z = 30, heading = 0 },
{ type = "Hesco_wallperimeter_1", x = 52, z = -30, heading = 0 },
{ type = "Hesco_wallperimeter_1", x = -52, z = -30, heading = 0 },
{ type = "Hesco_wallperimeter_1", x = 30, z = 52, heading = 90 },
{ type = "Hesco_wallperimeter_1", x = -30, z = 52, heading = 90 },
{ type = "Hesco_wallperimeter_1", x = 30, z = -52, heading = 90 },
{ type = "Hesco_wallperimeter_1", x = -30, z = -52, heading = 90 },
{ type = "Hesco_wallperimeter_1", x = 45, z = 40, heading = 45 },
{ type = "Hesco_wallperimeter_1", x = -45, z = 40, heading = 315 },
{ type = "Hesco_wallperimeter_1", x = 45, z = -40, heading = 135 },
{ type = "Hesco_wallperimeter_1", x = -45, z = -40, heading = 225 },
{ type = "FARP Tent", x = 52, z = 0, heading = 270 },
{ type = "FARP Tent", x = -52, z = 0, heading = 90 },
{ type = "FARP Tent", x = 36.8, z = 36.8, heading = 225 },
{ type = "container_20ft", x = -30, z = -38, heading = 45 },
{ type = "container_20ft", x = -35, z = -33, heading = 45 },
{ type = "container_20ft", x = -25, z = -43, heading = 45 },
{ type = "container_20ft", x = -33, z = -45, heading = 135 },
{ type = "GeneratorF", x = 43.1, z = -31.4, heading = 225 },
{ type = "UAZ-469", x = 12, z = 48, heading = 200 },
{ type = "UAZ-469", x = 16, z = 50, heading = 170 },
{ type = "Ural-375", x = -25, z = -35, heading = 45 },
{ type = "Landmine pot", x = 50, z = 15, heading = 0 },
{ type = "Landmine pot", x = -50, z = 15, heading = 0 },
{ type = "Landmine pot", x = 50, z = -15, heading = 0 },
{ type = "Landmine pot", x = -50, z = -15, heading = 0 },
{ type = "Landmine pot", x = 15, z = 50, heading = 0 },
{ type = "Landmine pot", x = -15, z = 50, heading = 0 },
{ type = "Landmine pot", x = 40, z = 30, heading = 0 },
{ type = "Landmine pot", x = -40, z = 30, heading = 0 },
{ type = "Landmine pot", x = 30, z = -40, heading = 0 },
{ type = "Landmine pot", x = -30, z = -40, heading = 0 },
{ type = "Landmine pot", x = 45, z = 25, heading = 0 },
{ type = "Landmine pot", x = -45, z = 25, heading = 0 },
{ type = "billboard_motorized rifle troops", x = 0, z = -58, heading = 0 },
{ type = "Sandbox", x = 38, z = -8, heading = 0 },
{ type = "Sandbox", x = -38, z = -8, heading = 0 },
{ type = "Sandbox", x = 38, z = 8, heading = 0 },
{ type = "Sandbox", x = -38, z = 8, heading = 0 },
{ type = "Black_Tyre", x = 20, z = -48, heading = 0 },
{ type = "Black_Tyre", x = -20, z = -48, heading = 0 },
{ type = "Black_Tyre", x = 24, z = -46, heading = 0 },
{ type = "Black_Tyre", x = -24, z = -46, heading = 0 },
{ type = "Black_Tyre", x = 28, z = -44, heading = 0 },
{ type = "Black_Tyre", x = -28, z = -44, heading = 0 },
{ type = "Black_Tyre", x = 48, z = 20, heading = 0 },
{ type = "Black_Tyre", x = -48, z = 20, heading = 0 },
{ type = "WatchTower", x = -38.9, z = 38.9, heading = 225 },
{ type = "warning_board_c", x = 0, z = 48, heading = 180 },
{ type = "warning_board_c", x = 48, z = 0, heading = 270 },
{ type = "warning_board_c", x = -48, z = 0, heading = 90 },
{ type = "warning_board_c", x = 35, z = -35, heading = 45 },
{ type = "warning_board_c", x = -35, z = -35, heading = 315 },
{ type = "warning_board_c", x = 35, z = 35, heading = 225 },
{ type = "FARP Fuel Depot", x = 30, z = 25, heading = 315 },
{ type = "FARP Fuel Depot", x = -30, z = 25, heading = 45 },
{ type = "FARP Ammo Dump Coating", x = 40, z = -20, heading = 282 },
{ type = "FARP Ammo Dump Coating", x = -40, z = -20, heading = 78 },
-- Extended support structures at outer ring
{ type = "FARP CP Blindage", x = 0, z = 150, heading = 0 },
{ type = "Shelter", x = 0, z = -160, heading = 180 },
{ type = "SKP-11", x = 0, z = 180, heading = 0 },
{ type = "ZiL-131 APA-80", x = 50, z = 165, heading = 8 },
{ type = "FARP Tent", x = 170, z = 0, heading = 270 },
{ type = "FARP Tent", x = -170, z = 0, heading = 90 },
{ type = "FARP Tent", x = 120, z = 120, heading = 315 },
{ type = "FARP Tent", x = -120, z = 120, heading = 45 },
{ type = "FARP Ammo Dump Coating", x = 160, z = 80, heading = 30 },
{ type = "FARP Ammo Dump Coating", x = -160, z = 80, heading = 330 },
{ type = "container_20ft", x = -140, z = -130, heading = 128 },
{ type = "container_20ft", x = -150, z = -120, heading = 133 },
{ type = "container_20ft", x = 140, z = -130, heading = 52 },
{ type = "container_20ft", x = 150, z = -120, heading = 47 },
{ type = "GeneratorF", x = 155, z = -140, heading = 234 },
{ type = "GeneratorF", x = -155, z = -140, heading = 126 },
{ type = "UAZ-469", x = 70, z = 160, heading = 14 },
{ type = "UAZ-469", x = -70, z = 160, heading = 346 },
{ type = "Ural-375", x = -125, z = -135, heading = 125 },
{ type = "Sandbox", x = 350, z = 0, heading = 270 },
{ type = "Sandbox", x = -350, z = 0, heading = 90 },
{ type = "Sandbox", x = 247, z = 247, heading = 315 },
{ type = "Sandbox", x = -247, z = 247, heading = 45 },
{ type = "Sandbox", x = 247, z = -247, heading = 225 },
{ type = "Sandbox", x = -247, z = -247, heading = 135 },
},
},
}
@ -859,8 +801,8 @@ CTLD.HoverCoachConfig = {
arrivalDist = 1000, -- m: start guidance "You're close…"
closeDist = 100, -- m: reduce speed / set AGL guidance
precisionDist = 8, -- m: start precision hints
captureHoriz = 10, -- m: horizontal sweet spot radius
captureVert = 10, -- m: vertical sweet spot tolerance around AGL window
captureHoriz = 15, -- m: horizontal sweet spot radius
captureVert = 15, -- m: vertical sweet spot tolerance around AGL window
aglMin = 5, -- m: hover window min AGL
aglMax = 20, -- m: hover window max AGL
maxGS = 8/3.6, -- m/s: 8 km/h for precision, used for errors
@ -4184,6 +4126,36 @@ function CTLD:ClearMapDrawings()
self._MapMarkup = { Pickup = {}, Drop = {}, FOB = {}, MASH = {}, SalvageDrop = {} }
end
function CTLD:_updateMobileMASHDrawing(mashId)
local data = CTLD._mashZones and CTLD._mashZones[mashId]
if not data or data.side ~= self.Side or not data.isMobile then return end
if not (self.Config.MapDraw and self.Config.MapDraw.Enabled and self.Config.MapDraw.DrawMASHZones) then return end
local zoneName = data.displayName or mashId
if self._ZoneActive.MASH[zoneName] == false then return end
-- Remove old drawing
self:_removeZoneDrawing('MASH', zoneName)
-- Redraw at new position
local md = self.Config.MapDraw
local opts = {
OutlineColor = md.OutlineColor,
LineType = (md.LineTypes and md.LineTypes.MASH) or md.LineType or 1,
FillColor = (md.FillColors and md.FillColors.MASH) or nil,
FontSize = md.FontSize,
ReadOnly = (md.ReadOnly ~= false),
LabelPrefix = (md.LabelPrefixes and md.LabelPrefixes.MASH) or 'MASH',
LabelOffsetX = md.LabelOffsetX,
LabelOffsetFromEdge = md.LabelOffsetFromEdge,
LabelOffsetRatio = md.LabelOffsetRatio,
ForAll = (md.ForAll == true),
}
if data.zone then
self:_drawZoneCircleAndLabel('MASH', data.zone, opts)
end
end
function CTLD:_removeZoneDrawing(kind, zname)
if not (self._MapMarkup and self._MapMarkup[kind] and self._MapMarkup[kind][zname]) then return end
local ids = self._MapMarkup[kind][zname]
@ -4310,13 +4282,14 @@ function CTLD:DrawZonesOnMap()
local v2 = (VECTOR2 and VECTOR2.New) and VECTOR2:New(pos.x, pos.z) or { x = pos.x, y = pos.z }
zoneObj = ZONE_RADIUS:New(zoneName, v2, data.radius or 500)
else
local posCopy = { x = pos.x, z = pos.z }
-- Create zone that references data.position directly for live updates
zoneObj = {}
function zoneObj:GetName()
return zoneName
end
function zoneObj:GetPointVec3()
return { x = posCopy.x, y = 0, z = posCopy.z }
local currentPos = data.position or { x = 0, z = 0 }
return { x = currentPos.x, y = 0, z = currentPos.z }
end
function zoneObj:GetRadius()
return data.radius or 500
@ -7179,9 +7152,10 @@ function CTLD:BuildSpecificAtGroup(group, recipeKey, opts)
counts[reqKey] = (counts[reqKey] or 0) - (qty or 0)
end
_eventSend(self, nil, self.Side, 'build_success_coalition', { build = def.description or recipeKey, player = _playerNameFromGroup(group) })
_logInfo(string.format('[BUILD_DEBUG] Built key=%s desc=%s isFOB=%s isMobileMASH=%s', tostring(recipeKey), tostring(def.description), tostring(def.isFOB), tostring(def.isMobileMASH)))
if def.isFOB then pcall(function() self:_CreateFOBPickupZone({ x = spawnAt.x, z = spawnAt.z }, def, hdg) end) end
if def.isMobileMASH then
_logDebug(string.format('[MobileMASH] BuildSpecificAtGroup invoking _CreateMobileMASH for key %s at (%.1f, %.1f)', tostring(recipeKey), spawnAt.x or -1, spawnAt.z or -1))
_logInfo(string.format('[MobileMASH] BuildSpecificAtGroup invoking _CreateMobileMASH for key %s at (%.1f, %.1f)', tostring(recipeKey), spawnAt.x or -1, spawnAt.z or -1))
local ok, err = pcall(function() self:_CreateMobileMASH(g, { x = spawnAt.x, z = spawnAt.z }, def) end)
if not ok then
_logError(string.format('[MobileMASH] _CreateMobileMASH invocation failed: %s', tostring(err)))
@ -8785,6 +8759,14 @@ function CTLD:BuildAtGroup(group, opts)
self:_CreateFOBPickupZone({ x = actualSpawn.x, z = actualSpawn.z }, cat, hdg)
end)
end
-- If this was a Mobile MASH, create the tracking zone
if cat.isMobileMASH then
_logInfo(string.format('[MobileMASH] BuildAtGroup invoking _CreateMobileMASH for key %s at (%.1f, %.1f)', tostring(recipeKey), actualSpawn.x or -1, actualSpawn.z or -1))
local ok, err = pcall(function() self:_CreateMobileMASH(g, { x = actualSpawn.x, z = actualSpawn.z }, cat) end)
if not ok then
_logError(string.format('[MobileMASH] _CreateMobileMASH invocation failed: %s', tostring(err)))
end
end
-- Assign optional behavior for built vehicles/groups
local behavior = opts and opts.behavior or nil
if behavior == 'attack' and self.Config.AttackAI and self.Config.AttackAI.Enabled then
@ -9690,7 +9672,27 @@ function CTLD:ScanGroundAutoLoad()
if groundCfg.RequirePickupZone then
local inPickupZone = self:_isUnitInsidePickupZone(unit, true)
if not inPickupZone and groundCfg.AllowInFOBZones then
-- Explicitly exclude FARP service zones from auto-load
local inFARPZone = false
local unitPoint = unit:GetPointVec3()
for farpZoneName, farpInfo in pairs(CTLD._farpZones or {}) do
local farpZone = farpInfo.zone
if farpZone then
local farpPoint = farpZone:GetPointVec3()
local dx = (farpPoint.x - unitPoint.x)
local dz = (farpPoint.z - unitPoint.z)
local d = math.sqrt(dx*dx + dz*dz)
local farpRadius = self:_getZoneRadius(farpZone)
if d <= farpRadius then
inFARPZone = true
break
end
end
end
if inFARPZone then
inValidZone = false -- Never auto-load in FARP zones
elseif not inPickupZone and groundCfg.AllowInFOBZones then
-- Check FOB zones too
for _, fobZone in ipairs(self.FOBZones or {}) do
local fname = fobZone:GetName()
@ -10700,20 +10702,20 @@ function CTLD:FindNearestFOBZone(point)
end
-- Spawn static objects for a FARP stage
function CTLD:SpawnFARPStatics(zoneName, stage, centerPoint, coalition)
function CTLD:SpawnFARPStatics(zoneName, stage, centerPoint, coalitionId)
if not (CTLD.FARPConfig and CTLD.FARPConfig.StageLayouts[stage]) then
_logError(string.format('Invalid FARP stage %d or missing layout config', stage))
return false
end
local layout = CTLD.FARPConfig.StageLayouts[stage]
local farpData = CTLD._farpData[zoneName] or { stage = 0, statics = {}, coalition = coalition }
local farpData = CTLD._farpData[zoneName] or { stage = 0, statics = {}, coalition = coalitionId }
_logInfo(string.format('Spawning FARP Stage %d statics for zone %s (coalition %d)', stage, zoneName, coalition))
_logInfo(string.format('Spawning FARP Stage %d statics for zone %s (coalition %d)', stage, zoneName, coalitionId))
-- Get coalition name for DCS
-- Note: 'coalition' parameter is a number (1=red, 2=blue), not the coalition table
local coalitionName = (coalition == 2) and 'blue' or 'red'
-- Note: 'coalitionId' parameter is a number (1=red, 2=blue), not the coalition table
local coalitionName = (coalitionId == 2) and 'blue' or 'red'
for _, obj in ipairs(layout) do
-- Calculate world position from relative offset
@ -10724,6 +10726,20 @@ function CTLD:SpawnFARPStatics(zoneName, stage, centerPoint, coalition)
-- Generate unique name
local staticName = string.format('FARP_%s_S%d_%s_%d', zoneName, stage, obj.type:gsub('%s+', '_'), math.random(10000, 99999))
-- Determine category and shape_name based on object type
local category = "Fortifications"
local shapeName = ""
local linkUnit = nil
local linkOffset = false
local callsignID = math.random(1, 99)
if obj.type == "FARP" then
category = "Heliports"
shapeName = "FARP"
linkUnit = 0
linkOffset = true
end
-- Create static object data
local staticData = {
["type"] = obj.type,
@ -10731,15 +10747,24 @@ function CTLD:SpawnFARPStatics(zoneName, stage, centerPoint, coalition)
["heading"] = math.rad(obj.heading or 0),
["x"] = worldX,
["y"] = worldZ,
["category"] = "Fortifications",
["category"] = category,
["canCargo"] = false,
["shape_name"] = "",
["shape_name"] = shapeName,
["rate"] = 100,
}
-- Add FARP-specific data
if obj.type == "FARP" then
staticData["linkUnit"] = linkUnit
staticData["linkOffset"] = linkOffset
staticData["callsign_id"] = callsignID
staticData["frequencyList"] = {127.5, 129.5, 121.5}
staticData["modulation"] = 0
end
-- Spawn the static
local success, staticObj = pcall(function()
return coalition.addStaticObject(coalition, staticData)
return coalition.addStaticObject(coalitionId, staticData)
end)
if success and staticObj then
@ -10751,7 +10776,7 @@ function CTLD:SpawnFARPStatics(zoneName, stage, centerPoint, coalition)
end
farpData.stage = stage
farpData.coalition = coalition
farpData.coalition = coalitionId
CTLD._farpData[zoneName] = farpData
_logInfo(string.format('FARP Stage %d complete for zone %s - spawned %d statics', stage, zoneName, #farpData.statics))
@ -10808,7 +10833,8 @@ function CTLD:UpgradeFARP(group, zoneName)
end
local center = zone:GetVec2()
local centerPoint = { x = center.x, z = center.y }
-- Offset FARP 80m north from FOB zone center to avoid spawned trucks
local centerPoint = { x = center.x, z = center.y + 80 }
-- Deduct salvage
CTLD._salvagePoints[self.Side] = currentSalvage - upgradeCost
@ -10834,7 +10860,7 @@ function CTLD:UpgradeFARP(group, zoneName)
services = table.concat(services, ', ')
})
-- Create or update FARP service zone
-- Create or update FARP service zone (use same offset centerPoint)
self:CreateFARPServiceZone(zoneName, centerPoint, nextStage)
_logInfo(string.format('%s upgraded FOB %s to FARP Stage %d (cost: %d salvage)',
@ -10857,6 +10883,13 @@ function CTLD:CreateFARPServiceZone(zoneName, centerPoint, stage)
local v2 = (VECTOR2 and VECTOR2.New) and VECTOR2:New(centerPoint.x, centerPoint.z) or { x = centerPoint.x, y = centerPoint.z }
local serviceZone = ZONE_RADIUS:New(farpZoneName, v2, radius)
-- Enable scanning for the zone (scan for units and helicopters)
if serviceZone.Scan then
pcall(function()
serviceZone:Scan({Object.Category.UNIT, Object.Category.STATIC})
end)
end
CTLD._farpZones[farpZoneName] = {
zone = serviceZone,
side = self.Side,
@ -10876,56 +10909,115 @@ function CTLD:StartFARPServices(farpZoneName)
if not farpInfo then return end
local selfref = self
CTLD._farpServiceState = CTLD._farpServiceState or {}
-- Service scheduler runs every 5 seconds
-- Service scheduler runs every 2 seconds
SCHEDULER:New(nil, function()
local zone = farpInfo.zone
if not zone then return end
local stage = farpInfo.stage
local units = zone:GetScannedUnits()
local now = timer.getTime()
for _, unit in ipairs(units or {}) do
-- Scan zone for units
local zoneVec3 = zone:GetPointVec3()
local radius = selfref:_getZoneRadius(zone)
local volS = {
id = world.VolumeType.SPHERE,
params = {
point = zoneVec3,
radius = radius
}
}
local foundUnits = {}
world.searchObjects(Object.Category.UNIT, volS, function(obj)
local unit = UNIT:Find(obj)
if unit and unit:IsAlive() then
local unitCoalition = unit:GetCoalition()
-- Only service friendly units
if unitCoalition == farpInfo.side then
local unitType = unit:GetTypeName()
local group = unit:GetGroup()
if unit:IsAir() or unit:IsGround() then
table.insert(foundUnits, unit)
end
end
end
return true
end)
for _, unit in ipairs(foundUnits) do
local dcsUnit = unit:GetDCSObject()
if dcsUnit then
local uname = unit:GetName()
local agl = unit:GetAltitude() - land.getHeight(unit:GetPointVec2())
local vel = unit:GetVelocityKMH()
-- Check if aircraft is on ground (low AGL and low speed)
local onGround = (agl < 5) and (vel < 5)
if onGround then
CTLD._farpServiceState[uname] = CTLD._farpServiceState[uname] or { lastService = 0 }
local state = CTLD._farpServiceState[uname]
-- Service helicopters and ground vehicles
if group and (unit:IsHelicopter() or unit:IsGround()) then
-- Service every 3 seconds to avoid spam
if (now - state.lastService) >= 3 then
local serviced = false
-- Stage 2+: Refuel
if stage >= 2 then
-- Trigger refuel (DCS built-in command)
pcall(function()
local controller = unit:GetUnit():getController()
if controller then
controller:setCommand({
id = 'RefuelInFlight',
params = {}
})
end
end)
local fuel = dcsUnit:getFuel()
if fuel < 0.95 then
-- Add 10% fuel per service tick (takes ~30 seconds for full refuel)
dcsUnit:setFuel(math.min(1.0, fuel + 0.10))
serviced = true
end
end
-- Stage 3: Rearm and Repair
-- Stage 3: Rearm
if stage >= 3 then
pcall(function()
local dcsUnit = unit:GetUnit()
if dcsUnit then
-- Note: DCS doesn't have direct Lua API for ground rearm/repair
-- This simulates the presence of the service zone
-- In practice, DCS may auto-service units near FARP statics
-- Rearm all pylons to full
local ammo = dcsUnit:getAmmo()
if ammo then
for _, wpn in ipairs(ammo) do
if wpn.count then
-- Set to full ammo (this is a workaround - DCS has limited rearm API)
dcsUnit:setAmmo(wpn.type_name, wpn.count + 100)
end
end
serviced = true
end
end)
-- Repair (restore hit points)
local life = dcsUnit:getLife()
local life0 = dcsUnit:getLife0()
if life and life0 and life < life0 then
-- Repair 20% per tick (takes ~15 seconds for full repair)
dcsUnit:setLife(math.min(life0, life + (life0 * 0.20)))
serviced = true
end
end
if serviced then
state.lastService = now
local gname = unit:GetGroup():GetName()
if stage >= 3 then
MESSAGE:New(string.format('FARP: Servicing %s (Refuel/Rearm/Repair)', unit:GetTypeName()), 5):ToGroup(GROUP:FindByName(gname))
else
MESSAGE:New(string.format('FARP: Refueling %s', unit:GetTypeName()), 5):ToGroup(GROUP:FindByName(gname))
end
end
end
else
-- Aircraft not on ground, reset service timer
if CTLD._farpServiceState[uname] then
CTLD._farpServiceState[uname].lastService = 0
end
end
end
end
end, {}, 0, 5) -- Start immediately, repeat every 5 seconds
end, {}, 0, 2) -- Start immediately, repeat every 2 seconds
_logDebug(string.format('FARP service scheduler started for %s', farpZoneName))
end
@ -11121,7 +11213,12 @@ function CTLD:InitMEDEVAC()
-- Initialize salvage pools
if CTLD.MEDEVAC.Salvage and CTLD.MEDEVAC.Salvage.Enabled then
CTLD._salvagePoints[self.Side] = CTLD._salvagePoints[self.Side] or 0
local before = CTLD._salvagePoints[self.Side]
-- Check instance config for InitialSalvage override
local initialValue = (self.Config.MEDEVAC and self.Config.MEDEVAC.InitialSalvage) or 0
CTLD._salvagePoints[self.Side] = CTLD._salvagePoints[self.Side] or initialValue
local after = CTLD._salvagePoints[self.Side]
env.info(string.format('[InitMEDEVAC] Side=%s Salvage BEFORE=%s InitialValue=%s AFTER=%s', tostring(self.Side), tostring(before), tostring(initialValue), tostring(after)))
end
-- Setup event handler for unit deaths
@ -12937,8 +13034,11 @@ function CTLD:_TryUseSalvageForCrate(group, crateKey, catalogEntry)
if not cfg or not cfg.Enabled then return false end
if not cfg.AutoApply then return false end
-- Check if item has salvage value
local salvageCost = (catalogEntry and catalogEntry.salvageValue) or 0
-- Check if item has salvage value (use same fallback logic as MEDEVAC)
local salvageCost = catalogEntry.salvageValue
if not salvageCost then
salvageCost = catalogEntry.required or cfg.DefaultValue or 1
end
if salvageCost <= 0 then return false end
-- Check if we have enough salvage
@ -12981,7 +13081,12 @@ function CTLD:_CanUseSalvageForCrate(crateKey, catalogEntry, quantity)
if not cfg.AutoApply then return false end
quantity = quantity or 1
local salvageCost = ((catalogEntry and catalogEntry.salvageValue) or 0) * quantity
-- Check if item has salvage value (use same fallback logic as MEDEVAC)
local salvageCost = catalogEntry.salvageValue
if not salvageCost then
salvageCost = catalogEntry.required or cfg.DefaultValue or 1
end
salvageCost = salvageCost * quantity
if salvageCost <= 0 then return false end
local available = CTLD._salvagePoints[self.Side] or 0
@ -13266,6 +13371,7 @@ function CTLD:ShowSalvagePoints(group)
end
local salvage = CTLD._salvagePoints[self.Side] or 0
env.info('ShowSalvagePoints: self.Side = ' .. tostring(self.Side) .. ', CTLD._salvagePoints[self.Side] = ' .. tostring(CTLD._salvagePoints and CTLD._salvagePoints[self.Side] or 'nil') .. ', salvage = ' .. tostring(salvage))
local lines = {}
table.insert(lines, '=== Coalition Salvage Points ===')
@ -13641,18 +13747,19 @@ end
-- Create a Mobile MASH zone and start announcements
function CTLD:_CreateMobileMASH(group, position, catalogDef)
local cfg = CTLD.MEDEVAC
_logInfo('[MobileMASH] _CreateMobileMASH called')
local cfg = self.Config.MEDEVAC
if not cfg or not cfg.Enabled then
_logDebug('[MobileMASH] Config missing or MEDEVAC disabled; aborting mobile deployment')
_logInfo('[MobileMASH] Config missing or MEDEVAC disabled; aborting mobile deployment')
return
end
if not cfg.MobileMASH or not cfg.MobileMASH.Enabled then
_logDebug('[MobileMASH] MobileMASH feature disabled in config; aborting')
_logInfo('[MobileMASH] MobileMASH feature disabled in config; aborting')
return
end
if not position or not position.x or not position.z then
_logError('[MobileMASH] Missing build position; aborting Mobile MASH deployment')
_logInfo('[MobileMASH] Missing build position; aborting Mobile MASH deployment')
return
end
@ -13661,7 +13768,7 @@ function CTLD:_CreateMobileMASH(group, position, catalogDef)
local okPreview, namePreview = pcall(function() return group:getName() end)
if okPreview and namePreview and namePreview ~= '' then groupNamePreview = namePreview end
end
_logVerbose(string.format('[MobileMASH] Build requested for group %s at (%.1f, %.1f)', groupNamePreview, position.x or 0, position.z or 0))
_logInfo(string.format('[MobileMASH] Build requested for group %s at (%.1f, %.1f)', groupNamePreview, position.x or 0, position.z or 0))
local function safeGetName(g)
if not g then return nil end
@ -13681,12 +13788,12 @@ function CTLD:_CreateMobileMASH(group, position, catalogDef)
_logError('[MobileMASH] Unable to determine coalition side; aborting Mobile MASH deployment')
return
end
_logDebug(string.format('[MobileMASH] Using coalition side %s (%s)', tostring(side), tostring(catalogDef.side or self.Side)))
_logInfo(string.format('[MobileMASH] Using coalition side %s (%s)', tostring(side), tostring(catalogDef.side or self.Side)))
CTLD._mobileMASHCounter = CTLD._mobileMASHCounter or { [coalition.side.BLUE] = 0, [coalition.side.RED] = 0 }
CTLD._mobileMASHCounter[side] = (CTLD._mobileMASHCounter[side] or 0) + 1
local index = CTLD._mobileMASHCounter[side]
_logDebug(string.format('[MobileMASH] Assigned deployment index %d for side %s', index, tostring(side)))
_logInfo(string.format('[MobileMASH] Assigned deployment index %d for side %s', index, tostring(side)))
local mashId = string.format('MOBILE_MASH_%d_%d', side, index)
local displayName
@ -13695,13 +13802,13 @@ function CTLD:_CreateMobileMASH(group, position, catalogDef)
else
displayName = string.format('Mobile MASH %d', index)
end
_logDebug(string.format('[MobileMASH] mashId=%s displayName=%s recipeDesc=%s', mashId, tostring(displayName), tostring(catalogDef.description)))
_logInfo(string.format('[MobileMASH] mashId=%s displayName=%s recipeDesc=%s', mashId, tostring(displayName), tostring(catalogDef.description)))
local initialPos = { x = position.x, z = position.z }
local radius = cfg.MobileMASH.ZoneRadius or 500
local beaconFreq = cfg.MobileMASH.BeaconFrequency or '30.0 FM'
local mashGroupName = safeGetName(group)
_logDebug(string.format('[MobileMASH] Initial position (%.1f, %.1f) radius %.1f freq %s groupName=%s', initialPos.x or 0, initialPos.z or 0, radius, tostring(beaconFreq), tostring(mashGroupName)))
_logInfo(string.format('[MobileMASH] Initial position (%.1f, %.1f) radius %.1f freq %s groupName=%s', initialPos.x or 0, initialPos.z or 0, radius, tostring(beaconFreq), tostring(mashGroupName)))
local function buildZoneObject(name, r, pos)
if ZONE_RADIUS and VECTOR2 and VECTOR2.New then
@ -13844,7 +13951,7 @@ function CTLD:_CreateMobileMASH(group, position, catalogDef)
}
CTLD._mashZones[mashId] = mashData
_logDebug(string.format('[MobileMASH] Registered mashId=%s displayName=%s zoneRadius=%.1f freq=%s', mashId, displayName, radius, tostring(beaconFreq)))
_logInfo(string.format('[MobileMASH] Registered mashId=%s displayName=%s zoneRadius=%.1f freq=%s', mashId, displayName, radius, tostring(beaconFreq)))
self._ZoneDefs = self._ZoneDefs or { PickupZones = {}, DropZones = {}, FOBZones = {}, MASHZones = {} }
self._ZoneDefs.MASHZones = self._ZoneDefs.MASHZones or {}
@ -13857,31 +13964,31 @@ function CTLD:_CreateMobileMASH(group, position, catalogDef)
-- Add zone to MASHZones array so it's recognized by the system
self.MASHZones = self.MASHZones or {}
table.insert(self.MASHZones, zoneObj)
_logDebug(string.format('[MobileMASH] Added zone to MASHZones array, total count: %d', #self.MASHZones))
_logInfo(string.format('[MobileMASH] Added zone to MASHZones array, total count: %d', #self.MASHZones))
local md = self.Config and self.Config.MapDraw or {}
if md.Enabled then
local ok, err = pcall(function() self:DrawZonesOnMap() end)
if not ok then
_logError(string.format('DrawZonesOnMap failed after Mobile MASH creation: %s', tostring(err)))
-- Add to MEDEVAC zones if MEDEVAC is active
if self.MEDEVAC and self.MEDEVAC.AddZone then
local ok, err = pcall(function()
self.MEDEVAC:AddZone(displayName, zoneObj)
end)
if ok then
_logInfo(string.format('[MobileMASH] Added zone to MEDEVAC system: %s', displayName))
-- Refresh MEDEVAC menu to include the new zone
pcall(function() self.MEDEVAC:__Start(1) end)
else
_logDebug(string.format('[MobileMASH] Could not add to MEDEVAC system: %s', tostring(err)))
end
else
local circleId = _nextMarkupId()
local textId = _nextMarkupId()
local p = { x = initialPos.x, y = 0, z = initialPos.z }
end
local colors = cfg.MASHZoneColors or {}
local borderColor = colors.border or {1, 1, 0, 0.85}
local fillColor = colors.fill or {1, 0.75, 0.8, 0.25}
trigger.action.circleToCoalition(side, circleId, p, radius, borderColor, fillColor, 1, true, "")
local textPos = { x = p.x, y = 0, z = p.z - radius - 50 }
trigger.action.textToCoalition(side, textId, textPos, {1,1,1,0.9}, {0,0,0,0}, 18, true, displayName)
mashData.circleId = circleId
mashData.textId = textId
_logDebug(string.format('[MobileMASH] Drawn map circleId=%d textId=%d', circleId, textId))
-- Auto-draw the new zone on the map using the update function
-- This ensures the drawing is tracked properly and can be updated/removed later
local md = self.Config and self.Config.MapDraw or {}
if md.Enabled and md.DrawMASHZones then
_logInfo('[MobileMASH] Drawing new Mobile MASH zone on map')
local ok, err = pcall(function() self:_updateMobileMASHDrawing(mashId) end)
if not ok then
_logError(string.format('_updateMobileMASHDrawing failed after Mobile MASH creation: %s', tostring(err)))
end
end
local gridStr = self:_GetMGRSString(initialPos)
@ -13930,7 +14037,9 @@ function CTLD:_CreateMobileMASH(group, position, catalogDef)
-- Create a separate frequent position update scheduler for mobile MASH tracking
-- This ensures the zone follows the vehicle even if announcements are infrequent
local ctldInstance = self
local positionUpdateInterval = 5 -- Update position every 5 seconds
local positionUpdateInterval = 15 -- Update position every 15 seconds
local mapRedrawInterval = 15 -- Redraw map every 15 seconds
local updatesSinceRedraw = 0
local posScheduler = SCHEDULER:New(nil, function()
local ok, err = pcall(function()
if not groupIsAlive() then
@ -13949,13 +14058,20 @@ function CTLD:_CreateMobileMASH(group, position, catalogDef)
end
end
_logDebug(string.format('[MobileMASH] Position updated for %s at (%.1f, %.1f)', displayName, vec3.x, vec3.z))
-- Redraw map only every 120 seconds
updatesSinceRedraw = updatesSinceRedraw + positionUpdateInterval
if updatesSinceRedraw >= mapRedrawInterval then
pcall(function() ctldInstance:_updateMobileMASHDrawing(mashId) end)
updatesSinceRedraw = 0
end
end
end)
if not ok then _logError('Mobile MASH position update scheduler error: '..tostring(err)) end
end, {}, positionUpdateInterval, positionUpdateInterval)
mashData.positionScheduler = posScheduler
_logDebug(string.format('[MobileMASH] Position update scheduler started every %ds', positionUpdateInterval))
_logDebug(string.format('[MobileMASH] Position update scheduler started every %ds (map redraw every %ds)', positionUpdateInterval, mapRedrawInterval))
if EVENTHANDLER then
local ctldInstance = self
@ -14025,6 +14141,12 @@ function CTLD:_RemoveMobileMASH(mashId)
end
end
-- Remove from MEDEVAC system if possible
if self.MEDEVAC and self.MEDEVAC.RemoveZone then
pcall(function() self.MEDEVAC:RemoveZone(name) end)
_logDebug(string.format('[MobileMASH] Attempted to remove zone from MEDEVAC system: %s', name))
end
-- Send destruction message
local msg = _fmtTemplate(CTLD.Messages.medevac_mash_destroyed, {
mash_id = string.match(mashId, 'MOBILE_MASH_%d+_(%d+)') or '?'
@ -14700,6 +14822,19 @@ function CTLD:CreateSalvageZoneAtGroup(group)
local coord = COORDINATE:NewFromVec3(pos)
local radius = cfg.DefaultZoneRadius or 300
-- Check for nearby salvage cargo (prevent zone placement within 1km of cargo)
local minDistance = 1000 -- 1km
for crateName, meta in pairs(CTLD._salvageCrates or {}) do
if meta and meta.position then
local cratePos = meta.position
local distance = math.sqrt((pos.x - cratePos.x)^2 + (pos.z - cratePos.z)^2)
if distance < minDistance then
_msgGroup(group, string.format('Cannot create salvage zone within %.0fm of existing salvage cargo. Nearest cargo is %.0fm away.', minDistance, distance))
return
end
end
end
self._DynamicSalvageZones = self._DynamicSalvageZones or {}
self._DynamicSalvageQueue = self._DynamicSalvageQueue or {}

View File

@ -25,18 +25,29 @@ local blueCfg = {
'UH-1H','Mi-8MTV2','Mi-24P','SA342M','SA342L','SA342Minigun','UH-60L','CH-47Fbl1','CH-47F','Mi-17','GazelleAI'
},
-- Optional: drive zone activation from mission flags (preferred: set per-zone below via flag/activeWhen)
MapDraw = {
Enabled = true,
DrawMASHZones = true, -- Enable MASH zone drawing
},
Zones = {
MEDEVAC = {
Enabled = true,
InitialSalvage = 25, -- Starting salvage points for this coalition
MobileMASH = {
Enabled = true,
ZoneRadius = 300,
BeaconFrequency = '32.0 FM',
AnnouncementInterval = 300, -- Announce position every 5 minutes
},
},
MapDraw = {
Enabled = true,
DrawMASHZones = true, -- Enable MASH zone drawing
},
Zones = {
PickupZones = { { name = 'ALPHA', flag = 9001, activeWhen = 0 } },
DropZones = { { name = 'BRAVO', flag = 9002, activeWhen = 0 } },
FOBZones = { { name = 'CHARLIE', flag = 9003, activeWhen = 0 } },
MASHZones = { { name = 'MASH Alpha', freq = '251.0 AM', radius = 500, flag = 9010, activeWhen = 0 } },
SalvageDropZones = { { name = 'S1', flag = 9020, radius = 500, activeWhen = 0 } },
MASHZones = { { name = 'MASH Alpha', freq = '251.0 AM', radius = 300, flag = 9010, activeWhen = 0 } },
SalvageDropZones = { { name = 'S1', flag = 9020, radius = 300, activeWhen = 0 } },
},
BuildRequiresGroundCrates = true,
}
@ -45,6 +56,7 @@ if blueCfg.Zones and blueCfg.Zones.MASHZones and blueCfg.Zones.MASHZones[1] then
env.info('[DEBUG] blueCfg.Zones.MASHZones[1].name: ' .. tostring(blueCfg.Zones.MASHZones[1].name))
end
ctldBlue = _MOOSE_CTLD:New(blueCfg)
env.info('[CTLD_INIT] After BLUE init, salvage = ' .. tostring((CTLD._salvagePoints and CTLD._salvagePoints[coalition.side.BLUE]) or 'nil'))
local redCfg = {
CoalitionSide = coalition.side.RED,
@ -55,17 +67,28 @@ local redCfg = {
},
-- Optional: drive zone activation for RED via per-zone flag/activeWhen
MapDraw = {
Enabled = true,
DrawMASHZones = true, -- Enable MASH zone drawing
},
Zones = {
MEDEVAC = {
Enabled = true,
InitialSalvage = 25, -- Starting salvage points for this coalition
MobileMASH = {
Enabled = true,
ZoneRadius = 300,
BeaconFrequency = '30.0 FM',
AnnouncementInterval = 1800, -- Announce position every 30 minutes
},
},
MapDraw = {
Enabled = true,
DrawMASHZones = true, -- Enable MASH zone drawing
},
Zones = {
PickupZones = { { name = 'DELTA', flag = 9101, activeWhen = 0 } },
DropZones = { { name = 'ECHO', flag = 9102, activeWhen = 0 } },
FOBZones = { { name = 'FOXTROT', flag = 9103, activeWhen = 0 } },
MASHZones = { { name = 'MASH Bravo', freq = '252.0 AM', radius = 500, flag = 9111, activeWhen = 0 } },
SalvageDropZones = { { name = 'S2', flag = 9020, radius = 500, activeWhen = 0 } },
MASHZones = { { name = 'MASH Bravo', freq = '252.0 AM', radius = 300, flag = 9111, activeWhen = 0 } },
SalvageDropZones = { { name = 'S2', flag = 9020, radius = 300, activeWhen = 0 } },
},
BuildRequiresGroundCrates = true,
}
@ -74,6 +97,7 @@ if redCfg.Zones and redCfg.Zones.MASHZones and redCfg.Zones.MASHZones[1] then
env.info('[DEBUG] redCfg.Zones.MASHZones[1].name: ' .. tostring(redCfg.Zones.MASHZones[1].name))
end
ctldRed = _MOOSE_CTLD:New(redCfg)
env.info('[CTLD_INIT] After RED init, salvage = ' .. tostring((CTLD._salvagePoints and CTLD._salvagePoints[coalition.side.RED]) or 'nil'))
-- Merge catalog into both CTLD instances if catalog was loaded
env.info('[init_mission_dual_coalition] Checking for catalog: '..((_CTLD_EXTRACTED_CATALOG and 'FOUND') or 'NOT FOUND'))
@ -93,6 +117,7 @@ else
env.info('[init_mission_dual_coalition] WARNING: _CTLD_EXTRACTED_CATALOG not found - catalog not loaded!')
env.info('[init_mission_dual_coalition] Available globals: '..((_G._CTLD_EXTRACTED_CATALOG and 'in _G') or 'not in _G'))
end
env.info('[CTLD_INIT] End of init - BLUE salvage: ' .. tostring(CTLD._salvagePoints and CTLD._salvagePoints[coalition.side.BLUE] or 'nil') .. ', RED salvage: ' .. tostring(CTLD._salvagePoints and CTLD._salvagePoints[coalition.side.RED] or 'nil'))
else
env.info('[init_mission_dual_coalition] Moose or CTLD missing; skipping CTLD init')
end

BIN
Moose_CTLD_NoBattle.miz Normal file

Binary file not shown.

Binary file not shown.

View File

@ -268,20 +268,14 @@ cat['BLUE_MQ9'] = { menuCategory='Drones', menu='MQ-9 Reaper - JTA
cat['RED_WINGLOONG'] = { menuCategory='Drones', menu='WingLoong-I - JTAC', description='WingLoong-I JTAC', dcsCargoType='container_cargo', required=1, initialStock=3, side=RED, category=Group.Category.AIRPLANE, build=singleAirUnit('WingLoong-I'), roles={'JTAC'}, jtac={ platform='air' } }
-- FOB crates (Support) — three small crates build a FOB site
cat['FOB_SMALL'] = { hidden=true, description='FOB small crate', dcsCargoType='container_cargo', required=1, initialStock=12, side=nil, category=Group.Category.GROUND, build=function(point, headingDeg)
-- spawns a harmless placeholder truck for visibility; consumed by FOB_SITE build
return singleUnit('Ural-375')(point, headingDeg)
end }
cat['FOB_SMALL'] = { hidden=true, description='FOB small crate', dcsCargoType='container_cargo', required=1, initialStock=12, side=nil, category=Group.Category.GROUND }
cat['FOB_SITE'] = { menuCategory='Support', menu='FOB Crates - All', description='FOB Site', isFOB=true, dcsCargoType='container_cargo', requires={ FOB_SMALL=3 }, initialStock=0, side=nil, category=Group.Category.GROUND,
build=multiUnits({ {type='HEMTT TFFT'}, {type='Ural-375 PBU', dx=10, dz=8}, {type='Ural-375', dx=-10, dz=8} }) }
-- Mobile MASH (Support) — three crates build a Mobile MASH unit
cat['MOBILE_MASH_SMALL'] = { hidden=true, description='Mobile MASH crate', dcsCargoType='container_cargo', required=1, initialStock=6, side=nil, category=Group.Category.GROUND, build=function(point, headingDeg)
-- spawns placeholder truck for visibility; consumed by MOBILE_MASH build
return singleUnit('Ural-375')(point, headingDeg)
end }
cat['BLUE_MOBILE_MASH'] = { menuCategory='Support', menu='Mobile MASH - All', description='Blue Mobile MASH Unit', isMobileMASH=true, dcsCargoType='container_cargo', requires={ MOBILE_MASH_SMALL=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-113') }
cat['RED_MOBILE_MASH'] = { menuCategory='Support', menu='Mobile MASH - All', description='Red Mobile MASH Unit', isMobileMASH=true, dcsCargoType='container_cargo', requires={ MOBILE_MASH_SMALL=3 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('BTR_D') }
cat['MOBILE_MASH_SMALL'] = { hidden=true, description='Mobile MASH crate', dcsCargoType='container_cargo', required=1, initialStock=3, side=nil, category=Group.Category.GROUND }
cat['BLUE_MOBILE_MASH'] = { menuCategory='Support', menu='Mobile MASH - All', description='Blue Mobile MASH Unit', isMobileMASH=true, dcsCargoType='container_cargo', requires={ MOBILE_MASH_SMALL=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=multiUnits({ {type='M-113'}, {type='M-113', dx=10, dz=8}, {type='M-113', dx=-10, dz=8} }) }
cat['RED_MOBILE_MASH'] = { menuCategory='Support', menu='Mobile MASH - All', description='Red Mobile MASH Unit', isMobileMASH=true, dcsCargoType='container_cargo', requires={ MOBILE_MASH_SMALL=3 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=multiUnits({ {type='BTR_D'}, {type='BTR_D', dx=10, dz=8}, {type='BTR_D', dx=-10, dz=8} }) }
-- =========================
-- Troop Type Definitions

View File

@ -268,20 +268,14 @@ cat['BLUE_MQ9'] = { menuCategory='Drones', menu='MQ-9 Reaper - JTA
cat['RED_WINGLOONG'] = { menuCategory='Drones', menu='WingLoong-I - JTAC', description='WingLoong-I JTAC', dcsCargoType='container_cargo', required=1, initialStock=1, side=RED, category=Group.Category.AIRPLANE, build=singleAirUnit('WingLoong-I'), roles={'JTAC'}, jtac={ platform='air' } }
-- FOB crates (Support) — three small crates build a FOB site
cat['FOB_SMALL'] = { hidden=true, description='FOB small crate', dcsCargoType='container_cargo', required=1, initialStock=6, side=nil, category=Group.Category.GROUND, build=function(point, headingDeg)
-- spawns a harmless placeholder truck for visibility; consumed by FOB_SITE build
return singleUnit('Ural-375')(point, headingDeg)
end }
cat['FOB_SMALL'] = { hidden=true, description='FOB small crate', dcsCargoType='container_cargo', required=1, initialStock=6, side=nil, category=Group.Category.GROUND }
cat['FOB_SITE'] = { menuCategory='Support', menu='FOB Crates - All', description='FOB Site', isFOB=true, dcsCargoType='container_cargo', requires={ FOB_SMALL=3 }, initialStock=0, side=nil, category=Group.Category.GROUND,
build=multiUnits({ {type='HEMTT TFFT'}, {type='Ural-375 PBU', dx=10, dz=8}, {type='Ural-375', dx=-10, dz=8} }) }
-- Mobile MASH (Support) — three crates build a Mobile MASH unit
cat['MOBILE_MASH_SMALL'] = { hidden=true, description='Mobile MASH crate', dcsCargoType='container_cargo', required=1, initialStock=3, side=nil, category=Group.Category.GROUND, build=function(point, headingDeg)
-- spawns placeholder truck for visibility; consumed by MOBILE_MASH build
return singleUnit('Ural-375')(point, headingDeg)
end }
cat['BLUE_MOBILE_MASH'] = { menuCategory='Support', menu='Mobile MASH - All', description='Blue Mobile MASH Unit', isMobileMASH=true, dcsCargoType='container_cargo', requires={ MOBILE_MASH_SMALL=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-113') }
cat['RED_MOBILE_MASH'] = { menuCategory='Support', menu='Mobile MASH - All', description='Red Mobile MASH Unit', isMobileMASH=true, dcsCargoType='container_cargo', requires={ MOBILE_MASH_SMALL=3 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=singleUnit('BTR_D') }
cat['MOBILE_MASH_SMALL'] = { hidden=true, description='Mobile MASH crate', dcsCargoType='container_cargo', required=1, initialStock=3, side=nil, category=Group.Category.GROUND }
cat['BLUE_MOBILE_MASH'] = { menuCategory='Support', menu='Mobile MASH - All', description='Blue Mobile MASH Unit', isMobileMASH=true, dcsCargoType='container_cargo', requires={ MOBILE_MASH_SMALL=3 }, initialStock=0, side=BLUE, category=Group.Category.GROUND, build=multiUnits({ {type='M-113'}, {type='M-113', dx=10, dz=8}, {type='M-113', dx=-10, dz=8} }) }
cat['RED_MOBILE_MASH'] = { menuCategory='Support', menu='Mobile MASH - All', description='Red Mobile MASH Unit', isMobileMASH=true, dcsCargoType='container_cargo', requires={ MOBILE_MASH_SMALL=3 }, initialStock=0, side=RED, category=Group.Category.GROUND, build=multiUnits({ {type='BTR_D'}, {type='BTR_D', dx=10, dz=8}, {type='BTR_D', dx=-10, dz=8} }) }
-- =========================
-- Troop Type Definitions

File diff suppressed because it is too large Load Diff