Added hover to pick up crates ability.

This commit is contained in:
iTracerFacer 2025-11-04 09:54:33 -06:00
parent 2c4bf36a47
commit 0e4d47353a
5 changed files with 193 additions and 110 deletions

View File

@ -37,6 +37,18 @@ CTLD.Config = {
Debug = false,
PickupZoneSmokeColor = trigger.smokeColor.Green, -- default smoke color when spawning crates at pickup zones
-- Hover pickup configuration (Ciribob-style inspired)
HoverPickup = {
Enabled = true, -- if true, auto-load the nearest crate when hovering close enough for a duration
Height = 3, -- meters AGL threshold for hover pickup
Radius = 15, -- meters horizontal distance to crate to consider for pickup
AutoPickupDistance = 25, -- meters max search distance for candidate crates
Duration = 3, -- seconds of continuous hover before loading occurs
MaxCratesPerLoad = 6, -- maximum crates the aircraft can carry simultaneously
RequireLowSpeed = true, -- require near-stationary hover
MaxSpeedMPS = 5 -- max allowed speed in m/s for hover pickup
},
Zones = { -- Optional: supply by name (ME trigger zones) or define coordinates inline
PickupZones = {
-- Examples:
@ -147,6 +159,9 @@ CTLD.Config = {
CTLD._instances = CTLD._instances or {}
CTLD._crates = {} -- [crateName] = { key, zone, side, spawnTime, point }
CTLD._troopsLoaded = {} -- [groupName] = { count, typeKey }
CTLD._loadedCrates = {} -- [groupName] = { total=n, byKey = { key -> count } }
CTLD._hoverState = {} -- [unitName] = { targetCrate=name, startTime=t }
CTLD._unitLast = {} -- [unitName] = { x, z, t }
-- =========================
-- Utilities
@ -290,6 +305,13 @@ function CTLD:New(cfg)
end, {}, 10, 10) -- check every 10 seconds
end
-- Optional: hover pickup scanner
if o.Config.HoverPickup and o.Config.HoverPickup.Enabled then
o.HoverSched = SCHEDULER:New(nil, function()
o:ScanHoverPickup()
end, {}, 0.5, 0.5)
end
table.insert(CTLD._instances, o)
_msgCoalition(o.Side, string.format('CTLD %s initialized for coalition', CTLD.Version))
return o
@ -389,6 +411,14 @@ function CTLD:BuildGroupMenus(group)
self:BuildAtGroup(group)
end)
-- Crate management (loaded crates)
MENU_GROUP_COMMAND:New(group, 'Drop One Loaded Crate', root, function()
self:DropLoadedCrates(group, 1)
end)
MENU_GROUP_COMMAND:New(group, 'Drop All Loaded Crates', root, function()
self:DropLoadedCrates(group, -1)
end)
return root
end
@ -486,9 +516,26 @@ function CTLD:BuildAtGroup(group)
counts[c.meta.key] = (counts[c.meta.key] or 0) + 1
end
-- Include loaded crates carried by this group
local gname = group:GetName()
local carried = CTLD._loadedCrates[gname]
if carried and carried.byKey then
for k,v in pairs(carried.byKey) do
counts[k] = (counts[k] or 0) + v
end
end
-- Helper to consume crates of a given key/qty
local function consumeCrates(key, qty)
local removed = 0
-- Consume from loaded crates first
if carried and carried.byKey and (carried.byKey[key] or 0) > 0 then
local take = math.min(qty, carried.byKey[key])
carried.byKey[key] = carried.byKey[key] - take
if carried.byKey[key] <= 0 then carried.byKey[key] = nil end
carried.total = math.max(0, (carried.total or 0) - take)
removed = removed + take
end
for _,c in ipairs(nearby) do
if removed >= qty then break end
if c.meta.key == key then
@ -559,6 +606,128 @@ function CTLD:BuildAtGroup(group)
_msgGroup(group, 'Insufficient crates to build any asset here')
end
-- =========================
-- Loaded crate management
-- =========================
function CTLD:_addLoadedCrate(group, crateKey)
local gname = group:GetName()
CTLD._loadedCrates[gname] = CTLD._loadedCrates[gname] or { total = 0, byKey = {} }
local lc = CTLD._loadedCrates[gname]
lc.total = lc.total + 1
lc.byKey[crateKey] = (lc.byKey[crateKey] or 0) + 1
end
function CTLD:DropLoadedCrates(group, howMany)
local gname = group:GetName()
local lc = CTLD._loadedCrates[gname]
if not lc or (lc.total or 0) == 0 then _msgGroup(group, 'No loaded crates to drop') return end
local unit = group:GetUnit(1)
if not unit or not unit:IsAlive() then return end
local p = unit:GetPointVec3()
local here = { x = p.x, z = p.z }
local toDrop = (howMany and howMany > 0) and howMany or lc.total
-- Drop in key order
for k,count in pairs(BASE:DeepCopy(lc.byKey)) do
if toDrop <= 0 then break end
local dropNow = math.min(count, toDrop)
for i=1,dropNow do
local cname = string.format('CTLD_CRATE_%s_%d', k, math.random(100000,999999))
local cat = self.Config.CrateCatalog[k]
_spawnStaticCargo(self.Side, here, (cat and cat.dcsCargoType) or 'uh1h_cargo', cname)
CTLD._crates[cname] = { key = k, side = self.Side, spawnTime = timer.getTime(), point = { x = here.x, z = here.z } }
lc.byKey[k] = lc.byKey[k] - 1
if lc.byKey[k] <= 0 then lc.byKey[k] = nil end
lc.total = lc.total - 1
toDrop = toDrop - 1
if toDrop <= 0 then break end
end
end
_msgGroup(group, 'Dropped loaded crates')
end
-- =========================
-- Hover pickup scanner
-- =========================
function CTLD:ScanHoverPickup()
local hp = self.Config.HoverPickup or {}
if not hp.Enabled then return end
-- iterate all groups that have menus (active transports)
for gname,root in pairs(self.MenusByGroup or {}) do
local group = GROUP:FindByName(gname)
if group and group:IsAlive() then
local unit = group:GetUnit(1)
if unit and unit:IsAlive() then
-- Allowed type check
local typ = _getUnitType(unit)
if _isIn(self.Config.AllowedAircraft, typ) then
local p3 = unit:GetPointVec3()
local agl = 0
if land and land.getHeight then
agl = math.max(0, p3.y - land.getHeight({ x = p3.x, y = p3.z }))
else
agl = p3.y -- fallback
end
-- speed estimate
local uname = unit:GetName()
local now = timer.getTime()
local last = CTLD._unitLast[uname]
local speed = 0
if last and (now > (last.t or 0)) then
local dx = (p3.x - last.x)
local dz = (p3.z - last.z)
local dt = now - last.t
if dt > 0 then speed = math.sqrt(dx*dx + dz*dz) / dt end
end
CTLD._unitLast[uname] = { x = p3.x, z = p3.z, t = now }
local speedOK = (not hp.RequireLowSpeed) or (speed <= (hp.MaxSpeedMPS or 5))
local heightOK = agl <= (hp.Height or 3)
if speedOK and heightOK then
-- find nearest crate within AutoPickupDistance
local bestName, bestMeta, bestd
local maxd = hp.AutoPickupDistance or hp.Radius or 25
for name,meta in pairs(CTLD._crates) do
if meta.side == self.Side then
local dx = (meta.point.x - p3.x)
local dz = (meta.point.z - p3.z)
local d = math.sqrt(dx*dx + dz*dz)
if d <= maxd and ((not bestd) or d < bestd) then
bestName, bestMeta, bestd = name, meta, d
end
end
end
if bestName and bestMeta and bestd <= (hp.Radius or maxd) then
-- check capacity
local carried = CTLD._loadedCrates[gname]
local total = carried and carried.total or 0
if total < (hp.MaxCratesPerLoad or 6) then
local hs = CTLD._hoverState[uname]
if not hs or hs.targetCrate ~= bestName then
CTLD._hoverState[uname] = { targetCrate = bestName, startTime = now }
else
if (now - hs.startTime) >= (hp.Duration or 3) then
-- load it
local obj = StaticObject.getByName(bestName)
if obj then obj:destroy() end
CTLD._crates[bestName] = nil
self:_addLoadedCrate(group, bestMeta.key)
_msgGroup(group, string.format('Loaded %s crate', tostring(bestMeta.key)))
CTLD._hoverState[uname] = nil
end
end
end
else
CTLD._hoverState[uname] = nil
end
else
CTLD._hoverState[uname] = nil
end
end
end
end
end
end
-- =========================
-- Troops
-- =========================

View File

@ -1,44 +0,0 @@
-- init_example.lua (optional dev harness snippet)
-- Load Moose before this. Then do:
-- dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\init_example.lua]])
local CTLD = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD.lua]])
local ctld = CTLD:New({
CoalitionSide = coalition.side.BLUE,
Zones = {
PickupZones = { { name = 'PICKUP_BLUE_MAIN' } },
DropZones = { { name = 'DROP_BLUE_1' } },
},
})
-- Load the extracted CTLD-based crate catalog
local extracted = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\catalogs\CrateCatalog_CTLD_Extract.lua]])
ctld:MergeCatalog(extracted)
-- Register a SAM site built from 4 crates
ctld:RegisterCrate('IR-SAM', {
description = '4x crates -> IR SAM site',
weight = 300,
dcsCargoType = 'container_cargo',
required = 4,
side = coalition.side.BLUE,
category = Group.Category.GROUND,
build = function(point, headingDeg)
local hdg = math.rad(headingDeg or 0)
local function off(dx, dz) return { x=point.x+dx, z=point.z+dz } end
local units = {
{ type='Strela-10M3', name='CTLD-SAM-1', x=point.x, y=point.z, heading=hdg },
{ type='Ural-375', name='CTLD-SAM-SUP', x=off(10,12).x, y=off(10,12).z, heading=hdg },
}
return { visible=false, lateActivation=false, units=units, name='CTLD_SAM_'..math.random(100000,999999) }
end,
})
-- FAC/RECCE setup
local FAC = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD_FAC.lua]])
local fac = FAC:New(ctld, {
CoalitionSide = coalition.side.BLUE,
Arty = { Enabled=true, Groups={'BLUE_ARTY_1'}, Rounds=2, Spread=80 },
})
fac:AddRecceZone({ name = 'RECCE_ZONE_1' })
fac:Run()

View File

@ -11,32 +11,36 @@
-- RED : PICKUP_RED_MAIN, DROP_RED_1, FOB_RED_A
-- Adjust names below if you use different zone names.
-- Create CTLD for BLUE
ctldBlue = _MOOSE_CTLD:New({
CoalitionSide = coalition.side.BLUE,
UseCategorySubmenus = true,
UseBuiltinCatalog = false, -- rely on external catalog (recommended)
RestrictFOBToZones = true,
AutoBuildFOBInZones = true,
Zones = {
PickupZones = { { name = 'PICKUP_BLUE_MAIN' } },
DropZones = { { name = 'DROP_BLUE_1' } },
FOBZones = { { name = 'FOB_BLUE_A' } },
},
CoalitionSide = coalition.side.BLUE,
PickupZoneSmokeColor = trigger.smokeColor.Blue,
AllowedAircraft = { -- transport-capable unit type names (case-sensitive as in DCS DB)
'UH-1H','Mi-8MTV2','Mi-24P','SA342M','SA342L','SA342Minigun','Ka-50','Ka-50_3','AH-64D_BLK_II','UH-60L','CH-47Fbl1','CH-47F','Mi-17','GazelleAI'
},
Zones = {
PickupZones = { { name = 'PICKUP_BLUE_MAIN', smoke = trigger.smokeColor.Blue } },
--DropZones = { { name = 'DROP_BLUE_1' } },
-- FOBZones = { { name = 'FOB_BLUE_A' } },
},
})
-- Create CTLD for RED
ctldRed = _MOOSE_CTLD:New({
CoalitionSide = coalition.side.RED,
UseCategorySubmenus = true,
UseBuiltinCatalog = false, -- rely on external catalog (recommended)
RestrictFOBToZones = true,
AutoBuildFOBInZones = true,
Zones = {
PickupZones = { { name = 'PICKUP_RED_MAIN' } },
DropZones = { { name = 'DROP_RED_1' } },
FOBZones = { { name = 'FOB_RED_A' } },
},
CoalitionSide = coalition.side.RED,
PickupZoneSmokeColor = trigger.smokeColor.Red,
AllowedAircraft = { -- transport-capable unit type names (case-sensitive as in DCS DB)
'UH-1H','Mi-8MTV2','Mi-24P','SA342M','SA342L','SA342Minigun','Ka-50','Ka-50_3','AH-64D_BLK_II','UH-60L','CH-47Fbl1','CH-47F','Mi-17','GazelleAI'
},
Zones = {
PickupZones = { { name = 'PICKUP_RED_MAIN', smoke = trigger.smokeColor.Red } },
--DropZones = { { name = 'DROP_RED_1' } },
-- FOBZones = { { name = 'FOB_RED_A' } },
},
})
-- If the external catalog was loaded (as _CTLD_EXTRACTED_CATALOG), both instances auto-merged it

View File

@ -1,46 +0,0 @@
-- init_mission_example.lua
-- Use this with Mission Editor triggers as DO SCRIPT (no paths)
-- Load order (Mission Start):
-- Option A (catalog first):
-- 1) DO SCRIPT FILE: Moose.lua
-- 2) DO SCRIPT FILE: Moose_CTLD_Pure/catalogs/CrateCatalog_CTLD_Extract.lua -- sets _CTLD_EXTRACTED_CATALOG
-- 3) DO SCRIPT FILE: Moose_CTLD_Pure/Moose_CTLD.lua
-- 4) DO SCRIPT FILE: Moose_CTLD_Pure/Moose_CTLD_FAC.lua (optional)
-- 5) DO SCRIPT: paste the block below
-- Option B (catalog after CTLD file):
-- 1) DO SCRIPT FILE: Moose.lua
-- 2) DO SCRIPT FILE: Moose_CTLD_Pure/Moose_CTLD.lua
-- 3) DO SCRIPT FILE: Moose_CTLD_Pure/catalogs/CrateCatalog_CTLD_Extract.lua -- still fine: load before ctld:New()
-- 4) DO SCRIPT FILE: Moose_CTLD_Pure/Moose_CTLD_FAC.lua (optional)
-- 5) DO SCRIPT: paste the block below
-- create CTLD instance for BLUE
ctld = _MOOSE_CTLD:New({
CoalitionSide = coalition.side.BLUE,
UseCategorySubmenus = true,
-- Set to false if you want ONLY the external catalog and no built-in examples
-- UseBuiltinCatalog = false,
RestrictFOBToZones = true,
AutoBuildFOBInZones = true,
Zones = {
PickupZones = { { name = 'PICKUP_BLUE_MAIN' } },
DropZones = { { name = 'DROP_BLUE_1' } },
FOBZones = { { name = 'FOB_ALPHA' } },
},
})
-- No manual merge needed: Moose_CTLD auto-detects and merges global catalogs
-- supported global names: _CTLD_EXTRACTED_CATALOG, CTLD_CATALOG, MOOSE_CTLD_CATALOG
-- set allowed transports (optional override)
-- ctld:SetAllowedAircraft({'UH-1H','Mi-8MTV2','Mi-24P'})
-- FAC/RECCE (optional)
if _MOOSE_CTLD_FAC then
fac = _MOOSE_CTLD_FAC:New(ctld, {
CoalitionSide = coalition.side.BLUE,
Arty = { Enabled=true, Groups={'BLUE_ARTY_1'}, Rounds=2, Spread=80 },
})
fac:AddRecceZone({ name = 'RECCE_ZONE_1' })
fac:Run()
end