mirror of
https://github.com/iTracerFacer/Moose_CTLD_Pure.git
synced 2025-12-03 04:11:57 +00:00
Updated CTLD. Fixed FARP Upgrade not working. Fixed claimin cargo by putting a salvage zone on top of it.
This commit is contained in:
parent
70842b241d
commit
6994ca0642
559
Moose_CTLD.lua
559
Moose_CTLD.lua
@ -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 {}
|
||||
|
||||
|
||||
@ -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
BIN
Moose_CTLD_NoBattle.miz
Normal file
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
14911
temp_miz2/l10n/DEFAULT/Moose_CTLD.lua
Normal file
14911
temp_miz2/l10n/DEFAULT/Moose_CTLD.lua
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user