mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Added hover to pick up crates ability.
This commit is contained in:
parent
2c4bf36a47
commit
0e4d47353a
@ -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
|
||||
-- =========================
|
||||
|
||||
@ -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()
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user