mirror of
https://github.com/iTracerFacer/Moose_CTLD_Pure.git
synced 2025-12-03 04:11:57 +00:00
Fixed player created FARP (it actually works now)
This commit is contained in:
parent
096f8f80b9
commit
5b1189afc4
640
Moose_CTLD.lua
640
Moose_CTLD.lua
@ -182,13 +182,14 @@ 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}!\nFARP Services: {services}\nFOB still active for logistics and troop operations.",
|
||||
farp_upgrade_complete = "{player} upgraded FOB to FARP Stage {stage}!\nFOB still active for logistics and troop operations.",
|
||||
farp_upgrade_complete_stage3 = "{player} upgraded FOB to FARP Stage 3!\nFunctional FARP operational! Use F8 Ground Crew for refuel/rearm 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 + 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_status = "FOB + FARP Status: Stage {stage}/{max_stage}\nInfrastructure only - upgrade to Stage 3 for services\nFOB logistics: ACTIVE\nNext upgrade: {next_cost} salvage (Stage {next_stage})",
|
||||
farp_status_stage3 = "FOB + FARP Status: Stage 3/3\nFunctional FARP operational - use F8 Ground Crew menu\nFOB logistics: ACTIVE\nNext upgrade: {next_cost} salvage (Stage {next_stage})",
|
||||
farp_status_maxed = "FOB + FARP Status: Stage 3/3 (FULLY UPGRADED)\nFunctional FARP operational - use F8 Ground Crew menu\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. 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.",
|
||||
@ -361,7 +362,7 @@ CTLD.Config = {
|
||||
-- 2 = INFO - Important state changes, initialization, cleanup (default for production)
|
||||
-- 3 = VERBOSE - Detailed operational info (zone validation, menus, builds, MEDEVAC events)
|
||||
-- 4 = DEBUG - Everything including hover checks, crate pickups, detailed troop spawns
|
||||
LogLevel = 1, -- lowered from DEBUG (4) to INFO (2) for production performance
|
||||
LogLevel = 4, -- lowered from DEBUG (4) to INFO (2) for production performance
|
||||
MessageDuration = 15, -- seconds for on-screen messages
|
||||
|
||||
-- Debug toggles for detailed crate proximity logging (useful when tuning hover coach / ground autoload)
|
||||
@ -702,88 +703,74 @@ CTLD.FARPConfig = {
|
||||
[3] = 8, -- Stage 2 -> Stage 3 (full forward airbase)
|
||||
},
|
||||
|
||||
-- Service zone radius for rearm/refuel at each stage
|
||||
-- FARP static object provides services via DCS F8 Ground Crew menu
|
||||
-- These radius values are for visual reference only
|
||||
ServiceRadius = {
|
||||
[1] = 50, -- Stage 1: basic pad only
|
||||
[2] = 65, -- Stage 2: fuel operations
|
||||
[3] = 80, -- Stage 3: full services
|
||||
[2] = 65, -- Stage 2: fuel depot added
|
||||
[3] = 80, -- Stage 3: full FARP with ammo
|
||||
},
|
||||
|
||||
-- Static object layouts for each FARP stage
|
||||
-- Format: { type = "DCS_Static_Name", x = offset_x, z = offset_z, heading = degrees, height = 0 }
|
||||
-- Positions are relative to FOB center point
|
||||
-- Layout: Square perimeter expanding outward
|
||||
-- NOTE: Functional FARP at Stage 3 is ~270m edge-to-edge, so Stages 1-2 must be outside that
|
||||
StageLayouts = {
|
||||
-- Stage 1: Basic FARP Pad (3 salvage) - Inner ring 30-60m
|
||||
-- Stage 1: Inner Square Perimeter (3 salvage) - 150m from center (outside FARP footprint)
|
||||
[1] = {
|
||||
{ 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 },
|
||||
-- North side
|
||||
{ type = "FARP Tent", x = 0, z = 150, heading = 180 },
|
||||
{ type = "Windsock", x = 40, z = 150, heading = 0 },
|
||||
{ type = "container_20ft", x = -40, z = 150, heading = 180 },
|
||||
|
||||
-- South side
|
||||
{ type = "FARP Tent", x = 0, z = -150, heading = 0 },
|
||||
{ type = "GeneratorF", x = 40, z = -150, heading = 0 },
|
||||
{ type = "container_20ft", x = -40, z = -150, heading = 0 },
|
||||
|
||||
-- East side
|
||||
{ type = "FARP Tent", x = 150, z = 0, heading = 270 },
|
||||
{ type = "container_20ft", x = 150, z = 35, heading = 270 },
|
||||
|
||||
-- West side
|
||||
{ type = "FARP Tent", x = -150, z = 0, heading = 90 },
|
||||
{ type = "container_20ft", x = -150, z = -35, heading = 90 },
|
||||
},
|
||||
|
||||
-- Stage 2: Operational FARP - adds fuel capability (5 more salvage) - Middle ring 80-130m
|
||||
-- Stage 2: Outer Square Perimeter (5 salvage) - 200m from center, logistics/support
|
||||
[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 },
|
||||
-- North side
|
||||
{ type = "FARP Fuel Depot", x = -50, z = 200, heading = 180 },
|
||||
{ type = "FARP Fuel Depot", x = 50, z = 200, heading = 180 },
|
||||
{ type = "FARP Tent", x = 0, z = 200, heading = 180 },
|
||||
{ type = "FARP CP Blindage", x = 100, z = 200, heading = 180 },
|
||||
|
||||
-- South side
|
||||
{ type = "FARP Ammo Dump Coating", x = -50, z = -200, heading = 0 },
|
||||
{ type = "FARP Ammo Dump Coating", x = 50, z = -200, heading = 0 },
|
||||
{ type = "container_40ft", x = 0, z = -200, heading = 0 },
|
||||
{ type = "Shelter", x = -100, z = -200, heading = 0 },
|
||||
|
||||
-- East side
|
||||
{ type = "FARP Tent", x = 200, z = 50, heading = 270 },
|
||||
{ type = "FARP Tent", x = 200, z = -50, heading = 270 },
|
||||
{ type = "GeneratorF", x = 200, z = 0, heading = 270 },
|
||||
|
||||
-- West side
|
||||
{ type = "FARP Tent", x = -200, z = 50, heading = 90 },
|
||||
{ type = "FARP Tent", x = -200, z = -50, heading = 90 },
|
||||
{ type = "Electric power box", x = -200, z = 0, heading = 90 },
|
||||
|
||||
-- Corner markers
|
||||
{ type = "container_20ft", x = 180, z = 180, heading = 225 },
|
||||
{ type = "container_20ft", x = -180, z = 180, heading = 135 },
|
||||
{ type = "container_20ft", x = 180, z = -180, heading = 315 },
|
||||
{ type = "container_20ft", x = -180, z = -180, heading = 45 },
|
||||
},
|
||||
|
||||
-- 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 = 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 },
|
||||
},
|
||||
-- Stage 3: Uses functional FARP - no static layout needed, decorations added separately
|
||||
[3] = {},
|
||||
},
|
||||
}
|
||||
|
||||
@ -2023,7 +2010,6 @@ CTLD._pendingTimers = CTLD._pendingTimers or {} -- [timerId] = true
|
||||
|
||||
-- FARP System state
|
||||
CTLD._farpData = CTLD._farpData or {} -- [fobZoneName] = { stage = 1/2/3, statics = {name1, name2...}, coalition = side }
|
||||
CTLD._farpZones = CTLD._farpZones or {} -- [farpZoneName] = { zone, side, stage }
|
||||
|
||||
local function _distanceXZ(a, b)
|
||||
if not a or not b then return math.huge end
|
||||
@ -9672,27 +9658,7 @@ function CTLD:ScanGroundAutoLoad()
|
||||
if groundCfg.RequirePickupZone then
|
||||
local inPickupZone = self:_isUnitInsidePickupZone(unit, true)
|
||||
|
||||
-- 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
|
||||
if not inPickupZone and groundCfg.AllowInFOBZones then
|
||||
-- Check FOB zones too
|
||||
for _, fobZone in ipairs(self.FOBZones or {}) do
|
||||
local fname = fobZone:GetName()
|
||||
@ -10670,6 +10636,15 @@ end
|
||||
function CTLD:InitFARP()
|
||||
if not (CTLD.FARPConfig and CTLD.FARPConfig.Enabled) then return end
|
||||
_logInfo('FARP system initialized')
|
||||
|
||||
-- Initialize unit ID counter for spawning service units
|
||||
CTLD._farpUnitIdCounter = CTLD._farpUnitIdCounter or 50000
|
||||
end
|
||||
|
||||
-- Get next unique unit ID for FARP service units
|
||||
function CTLD:GetNextUnitId()
|
||||
CTLD._farpUnitIdCounter = (CTLD._farpUnitIdCounter or 50000) + 1
|
||||
return CTLD._farpUnitIdCounter
|
||||
end
|
||||
|
||||
-- Get FARP data for a FOB zone
|
||||
@ -10717,61 +10692,132 @@ function CTLD:SpawnFARPStatics(zoneName, stage, centerPoint, coalitionId)
|
||||
-- Note: 'coalitionId' parameter is a number (1=red, 2=blue), not the coalition table
|
||||
local coalitionName = (coalitionId == 2) and 'blue' or 'red'
|
||||
|
||||
-- Service vehicle types that need to be spawned as ground units, not statics
|
||||
local serviceVehicles = {
|
||||
["M978 HEMTT Tanker"] = true, -- Refuel service
|
||||
["Ural-375 PBU"] = true, -- Fuel support
|
||||
["M978 HEMTT Tanker"] = true, -- Additional fuel
|
||||
["Ural-4320 APA-5D"] = true, -- Ammo truck for rearm
|
||||
["M1043 HMMWV Armament"] = true, -- Command/coordination
|
||||
["GAZ-66"] = true, -- Support vehicle
|
||||
}
|
||||
|
||||
for _, obj in ipairs(layout) do
|
||||
-- Calculate world position from relative offset
|
||||
local worldX = centerPoint.x + obj.x
|
||||
local worldZ = centerPoint.z + obj.z
|
||||
local worldY = land.getHeight({x = worldX, y = worldZ})
|
||||
|
||||
-- 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,
|
||||
["name"] = staticName,
|
||||
["heading"] = math.rad(obj.heading or 0),
|
||||
["x"] = worldX,
|
||||
["y"] = worldZ,
|
||||
["category"] = category,
|
||||
["canCargo"] = false,
|
||||
["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(coalitionId, staticData)
|
||||
end)
|
||||
|
||||
if success and staticObj then
|
||||
table.insert(farpData.statics, staticName)
|
||||
_logDebug(string.format('Spawned FARP static: %s at (%.1f, %.1f)', staticName, worldX, worldZ))
|
||||
-- Check if this should be a service unit instead of static
|
||||
if serviceVehicles[obj.type] then
|
||||
-- Spawn as ground unit for FARP services
|
||||
local unitName = string.format('FARP_%s_S%d_%s_%d', zoneName, stage, obj.type:gsub('%s+', '_'), math.random(10000, 99999))
|
||||
local groupName = unitName .. '_Group'
|
||||
|
||||
local groupData = {
|
||||
["visible"] = false,
|
||||
["taskSelected"] = true,
|
||||
["route"] = {
|
||||
["points"] = {
|
||||
[1] = {
|
||||
["alt"] = 0,
|
||||
["type"] = "Turning Point",
|
||||
["action"] = "Off Road",
|
||||
["alt_type"] = "BARO",
|
||||
["form"] = "Off Road",
|
||||
["y"] = worldZ,
|
||||
["x"] = worldX,
|
||||
["speed"] = 0,
|
||||
["task"] = {
|
||||
["id"] = "ComboTask",
|
||||
["params"] = {
|
||||
["tasks"] = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
["hidden"] = false,
|
||||
["units"] = {
|
||||
[1] = {
|
||||
["transportable"] = {["randomTransportable"] = false},
|
||||
["skill"] = "Average",
|
||||
["type"] = obj.type,
|
||||
["unitId"] = self:GetNextUnitId(),
|
||||
["y"] = worldZ,
|
||||
["x"] = worldX,
|
||||
["name"] = unitName,
|
||||
["heading"] = math.rad(obj.heading or 0),
|
||||
["playerCanDrive"] = false
|
||||
}
|
||||
},
|
||||
["y"] = worldZ,
|
||||
["x"] = worldX,
|
||||
["name"] = groupName,
|
||||
["start_time"] = 0
|
||||
}
|
||||
|
||||
local success, spawnedGroup = pcall(function()
|
||||
return coalition.addGroup(coalitionId, Group.Category.GROUND, groupData)
|
||||
end)
|
||||
|
||||
if success and spawnedGroup then
|
||||
table.insert(farpData.statics, unitName)
|
||||
_logDebug(string.format('Spawned FARP service unit: %s at (%.1f, %.1f)', unitName, worldX, worldZ))
|
||||
else
|
||||
_logError(string.format('Failed to spawn FARP service unit: %s (%s)', obj.type, tostring(spawnedGroup)))
|
||||
end
|
||||
else
|
||||
_logError(string.format('Failed to spawn FARP static: %s (%s)', obj.type, tostring(staticObj)))
|
||||
-- Spawn as static object (decorative)
|
||||
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,
|
||||
["name"] = staticName,
|
||||
["heading"] = math.rad(obj.heading or 0),
|
||||
["x"] = worldX,
|
||||
["y"] = worldZ,
|
||||
["category"] = category,
|
||||
["canCargo"] = false,
|
||||
["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(coalitionId, staticData)
|
||||
end)
|
||||
|
||||
if success and staticObj then
|
||||
table.insert(farpData.statics, staticName)
|
||||
_logDebug(string.format('Spawned FARP static: %s at (%.1f, %.1f)', staticName, worldX, worldZ))
|
||||
else
|
||||
_logError(string.format('Failed to spawn FARP static: %s (%s)', obj.type, tostring(staticObj)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -10783,6 +10829,13 @@ function CTLD:SpawnFARPStatics(zoneName, stage, centerPoint, coalitionId)
|
||||
return true
|
||||
end
|
||||
|
||||
-- FARP services are provided by DCS engine via F8 Ground Crew menu
|
||||
-- The FARP static object (spawned at stage 3) provides built-in services when helicopters:
|
||||
-- 1. Land within the FARP's service radius (handled by DCS)
|
||||
-- 2. Open F8 menu -> Ground Crew
|
||||
-- 3. Request Refuel, Rearm, or Repair
|
||||
-- Scripting cannot intercept or automate these services due to DCS API limitations
|
||||
|
||||
-- Upgrade a FOB to the next FARP stage
|
||||
function CTLD:UpgradeFARP(group, zoneName)
|
||||
if not (CTLD.FARPConfig and CTLD.FARPConfig.Enabled) then
|
||||
@ -10839,30 +10892,24 @@ function CTLD:UpgradeFARP(group, zoneName)
|
||||
-- Deduct salvage
|
||||
CTLD._salvagePoints[self.Side] = currentSalvage - upgradeCost
|
||||
|
||||
-- Spawn statics for this stage
|
||||
_eventSend(self, group, nil, 'farp_upgrade_started', { stage = nextStage })
|
||||
|
||||
local success = self:SpawnFARPStatics(zoneName, nextStage, centerPoint, self.Side)
|
||||
local success = false
|
||||
|
||||
-- Stage 3 uses MOOSE functional FARP, earlier stages use visual statics only
|
||||
if nextStage == 3 then
|
||||
success = self:SpawnFunctionalFARP(zoneName, centerPoint, self.Side)
|
||||
else
|
||||
success = self:SpawnFARPStatics(zoneName, nextStage, centerPoint, self.Side)
|
||||
end
|
||||
|
||||
if success then
|
||||
-- Determine services available
|
||||
local services = {}
|
||||
if nextStage >= 1 then table.insert(services, 'Landing Zone') end
|
||||
if nextStage >= 2 then table.insert(services, 'Refuel') end
|
||||
if nextStage >= 3 then
|
||||
table.insert(services, 'Rearm')
|
||||
table.insert(services, 'Repair')
|
||||
end
|
||||
|
||||
_eventSend(self, nil, self.Side, 'farp_upgrade_complete', {
|
||||
local msgKey = (nextStage == 3) and 'farp_upgrade_complete_stage3' or 'farp_upgrade_complete'
|
||||
_eventSend(self, nil, self.Side, msgKey, {
|
||||
player = _playerNameFromGroup(group),
|
||||
stage = nextStage,
|
||||
services = table.concat(services, ', ')
|
||||
stage = nextStage
|
||||
})
|
||||
|
||||
-- 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)',
|
||||
_playerNameFromGroup(group), zoneName, nextStage, upgradeCost))
|
||||
else
|
||||
@ -10872,154 +10919,127 @@ function CTLD:UpgradeFARP(group, zoneName)
|
||||
end
|
||||
end
|
||||
|
||||
-- Create FARP service zone for rearm/refuel
|
||||
function CTLD:CreateFARPServiceZone(zoneName, centerPoint, stage)
|
||||
if stage < 2 then return end -- Only stages 2+ have services
|
||||
-- Spawn a functional FARP using MOOSE utilities (Stage 3 only)
|
||||
function CTLD:SpawnFunctionalFARP(zoneName, centerPoint, coalitionId)
|
||||
_logInfo(string.format('Spawning functional FARP for zone %s (coalition %d)', zoneName, coalitionId))
|
||||
|
||||
local radius = CTLD.FARPConfig.ServiceRadius[stage] or 50
|
||||
local farpZoneName = string.format('%s_FARP_Service', zoneName)
|
||||
-- Convert coalition ID to country
|
||||
local countryId = (coalitionId == 2) and country.id.USA or country.id.RUSSIA
|
||||
|
||||
-- Create zone
|
||||
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)
|
||||
-- Generate unique FARP name and frequency
|
||||
CTLD._farpCounter = (CTLD._farpCounter or 0) + 1
|
||||
local farpNameNumber = ((CTLD._farpCounter - 1) % 10) + 1
|
||||
local farpFreq = 129 + CTLD._farpCounter
|
||||
|
||||
-- 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,
|
||||
stage = stage,
|
||||
parentFOB = zoneName
|
||||
local farpClearNames = {
|
||||
[1]="London", [2]="Dallas", [3]="Paris", [4]="Moscow", [5]="Berlin",
|
||||
[6]="Rome", [7]="Madrid", [8]="Warsaw", [9]="Dublin", [10]="Perth",
|
||||
}
|
||||
|
||||
-- Start service scheduler
|
||||
self:StartFARPServices(farpZoneName)
|
||||
local clearName = farpClearNames[farpNameNumber] or "Outpost"
|
||||
local farpName = string.format("%s FARP %dAM", clearName, farpFreq)
|
||||
|
||||
_logInfo(string.format('Created FARP service zone %s (radius: %dm, stage: %d)', farpZoneName, radius, stage))
|
||||
-- Create coordinate from centerPoint
|
||||
local coord = COORDINATE:New(centerPoint.x, land.getHeight({x = centerPoint.x, y = centerPoint.z}), centerPoint.z)
|
||||
|
||||
-- Spawn functional FARP using MOOSE utility
|
||||
-- This creates a FARP with actual service capability
|
||||
local success = pcall(function()
|
||||
UTILS.SpawnFARPAndFunctionalStatics(
|
||||
farpName, -- FARP name
|
||||
coord, -- Coordinate
|
||||
ENUMS.FARPType.FARP, -- FARP type (visible)
|
||||
coalitionId, -- Coalition
|
||||
countryId, -- Country
|
||||
farpNameNumber, -- Callsign number
|
||||
farpFreq, -- Frequency
|
||||
radio.modulation.AM, -- Modulation
|
||||
nil, -- Link unit (auto)
|
||||
nil, -- Loadout type (default)
|
||||
nil, -- Resources (default)
|
||||
20, -- Fuel tons (20 tons each type)
|
||||
50 -- Equipment quantity
|
||||
)
|
||||
end)
|
||||
|
||||
if success then
|
||||
-- Store FARP data
|
||||
local farpData = CTLD._farpData[zoneName] or { stage = 0, statics = {}, coalition = coalitionId }
|
||||
farpData.stage = 3
|
||||
farpData.farpName = farpName
|
||||
farpData.frequency = farpFreq
|
||||
CTLD._farpData[zoneName] = farpData
|
||||
|
||||
_logInfo(string.format('Functional FARP %s created at freq %dAM', farpName, farpFreq))
|
||||
|
||||
-- Also add decorative statics around it from Stage 1 and 2 layouts
|
||||
self:SpawnFARPDecorations(zoneName, centerPoint, coalitionId)
|
||||
|
||||
return true
|
||||
else
|
||||
_logError(string.format('Failed to spawn functional FARP for %s', zoneName))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Start FARP service scheduler
|
||||
function CTLD:StartFARPServices(farpZoneName)
|
||||
local farpInfo = CTLD._farpZones[farpZoneName]
|
||||
if not farpInfo then return end
|
||||
|
||||
local selfref = self
|
||||
CTLD._farpServiceState = CTLD._farpServiceState or {}
|
||||
|
||||
-- Service scheduler runs every 2 seconds
|
||||
SCHEDULER:New(nil, function()
|
||||
local zone = farpInfo.zone
|
||||
if not zone then return end
|
||||
-- Spawn decorative statics around the functional FARP
|
||||
function CTLD:SpawnFARPDecorations(zoneName, centerPoint, coalitionId)
|
||||
-- Decorative objects forming outermost square perimeter (120m from center)
|
||||
-- Functional FARP auto-spawns service objects in the center, these decorations complete the base
|
||||
local decorations = {
|
||||
-- North side - Command and operations
|
||||
{ type = "FARP CP Blindage", x = 0, z = 120, heading = 180 },
|
||||
{ type = "FARP Tent", x = -40, z = 120, heading = 180 },
|
||||
{ type = "FARP Tent", x = 40, z = 120, heading = 180 },
|
||||
{ type = "Shelter", x = -80, z = 120, heading = 180 },
|
||||
{ type = "Windsock", x = 80, z = 120, heading = 0 },
|
||||
|
||||
local stage = farpInfo.stage
|
||||
local now = timer.getTime()
|
||||
-- South side - Logistics and storage
|
||||
{ type = "container_40ft", x = 0, z = -120, heading = 0 },
|
||||
{ type = "FARP Tent", x = -50, z = -120, heading = 0 },
|
||||
{ type = "FARP Tent", x = 50, z = -120, heading = 0 },
|
||||
{ type = "FARP Ammo Dump Coating", x = -90, z = -120, heading = 0 },
|
||||
{ type = "FARP Ammo Dump Coating", x = 90, z = -120, heading = 0 },
|
||||
|
||||
-- Scan zone for units
|
||||
local zoneVec3 = zone:GetPointVec3()
|
||||
local radius = selfref:_getZoneRadius(zone)
|
||||
local volS = {
|
||||
id = world.VolumeType.SPHERE,
|
||||
params = {
|
||||
point = zoneVec3,
|
||||
radius = radius
|
||||
}
|
||||
-- East side - Fuel and support
|
||||
{ type = "FARP Fuel Depot", x = 120, z = 40, heading = 270 },
|
||||
{ type = "FARP Fuel Depot", x = 120, z = -40, heading = 270 },
|
||||
{ type = "FARP Tent", x = 120, z = 0, heading = 270 },
|
||||
{ type = "GeneratorF", x = 120, z = 80, heading = 270 },
|
||||
|
||||
-- West side - Power and maintenance
|
||||
{ type = "FARP Tent", x = -120, z = 40, heading = 90 },
|
||||
{ type = "FARP Tent", x = -120, z = -40, heading = 90 },
|
||||
{ type = "Electric power box", x = -120, z = 0, heading = 90 },
|
||||
{ type = "GeneratorF", x = -120, z = 80, heading = 90 },
|
||||
|
||||
-- Corner positions - Perimeter markers
|
||||
{ type = "container_20ft", x = 110, z = 110, heading = 225 },
|
||||
{ type = "container_20ft", x = -110, z = 110, heading = 135 },
|
||||
{ type = "container_20ft", x = 110, z = -110, heading = 315 },
|
||||
{ type = "container_20ft", x = -110, z = -110, heading = 45 },
|
||||
}
|
||||
|
||||
for _, obj in ipairs(decorations) do
|
||||
local worldX = centerPoint.x + obj.x
|
||||
local worldZ = centerPoint.z + obj.z
|
||||
local staticName = string.format('FARP_%s_Decor_%s_%d', zoneName, obj.type:gsub('%s+', '_'), math.random(10000, 99999))
|
||||
|
||||
local staticData = {
|
||||
["type"] = obj.type,
|
||||
["name"] = staticName,
|
||||
["heading"] = math.rad(obj.heading or 0),
|
||||
["x"] = worldX,
|
||||
["y"] = worldZ,
|
||||
["category"] = "Fortifications",
|
||||
["canCargo"] = false,
|
||||
["rate"] = 100,
|
||||
}
|
||||
|
||||
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
|
||||
if unit:IsAir() or unit:IsGround() then
|
||||
table.insert(foundUnits, unit)
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
pcall(function()
|
||||
coalition.addStaticObject(coalitionId, staticData)
|
||||
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 every 3 seconds to avoid spam
|
||||
if (now - state.lastService) >= 3 then
|
||||
local serviced = false
|
||||
|
||||
-- Stage 2+: Refuel
|
||||
if stage >= 2 then
|
||||
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
|
||||
if stage >= 3 then
|
||||
pcall(function()
|
||||
-- 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, 2) -- Start immediately, repeat every 2 seconds
|
||||
|
||||
_logDebug(string.format('FARP service scheduler started for %s', farpZoneName))
|
||||
end
|
||||
end
|
||||
|
||||
-- Show FARP status for nearby FOB
|
||||
@ -11043,32 +11063,20 @@ function CTLD:ShowFARPStatus(group)
|
||||
|
||||
if currentStage >= 3 then
|
||||
-- Fully upgraded
|
||||
local services = 'Landing Zone, Refuel, Rearm, Repair'
|
||||
_eventSend(self, group, nil, 'farp_status_maxed', {
|
||||
stage = currentStage,
|
||||
max_stage = 3,
|
||||
services = services
|
||||
})
|
||||
MESSAGE:New('FOB + FARP Status: Stage 3/3 (FULLY UPGRADED)\nFunctional FARP operational - use F8 Ground Crew menu\nFOB logistics: ACTIVE', 15):ToGroup(group)
|
||||
elseif currentStage > 0 then
|
||||
-- Partially upgraded
|
||||
local services = {}
|
||||
if currentStage >= 1 then table.insert(services, 'Landing Zone') end
|
||||
if currentStage >= 2 then table.insert(services, 'Refuel') end
|
||||
if currentStage >= 3 then
|
||||
table.insert(services, 'Rearm')
|
||||
table.insert(services, 'Repair')
|
||||
end
|
||||
|
||||
local nextStage = currentStage + 1
|
||||
local nextCost = CTLD.FARPConfig.StageCosts[nextStage] or 0
|
||||
|
||||
_eventSend(self, group, nil, 'farp_status', {
|
||||
stage = currentStage,
|
||||
max_stage = 3,
|
||||
services = table.concat(services, ', '),
|
||||
next_cost = nextCost,
|
||||
next_stage = nextStage
|
||||
})
|
||||
local statusMsg = string.format('FOB + FARP Status: Stage %d/3', currentStage)
|
||||
statusMsg = statusMsg .. '\nInfrastructure only - upgrade to Stage 3 for services'
|
||||
statusMsg = statusMsg .. '\nFOB logistics: ACTIVE'
|
||||
if nextStage <= 3 then
|
||||
statusMsg = statusMsg .. string.format('\nNext upgrade: %d salvage (Stage %d)', nextCost, nextStage)
|
||||
end
|
||||
|
||||
MESSAGE:New(statusMsg, 15):ToGroup(group)
|
||||
else
|
||||
-- Base FOB, not yet upgraded
|
||||
local nextCost = CTLD.FARPConfig.StageCosts[1] or 0
|
||||
|
||||
@ -366,6 +366,10 @@ function FAC:_wireBirth()
|
||||
local unit = e.IniUnit
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
if unit:GetCoalition() ~= selfref.Side then return end
|
||||
|
||||
-- Skip if this is a static object or doesn't have GetGroup method
|
||||
if not unit.GetGroup then return end
|
||||
|
||||
-- classify as AFAC / RECCE / Arty Director
|
||||
local name = unit:GetName()
|
||||
local tname = unit:GetTypeName()
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user