mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Configured messages to be one table for simplicity.
This commit is contained in:
parent
d74226ee40
commit
2d7ec65c65
@ -46,22 +46,57 @@ CTLD.HoverCoachConfig = {
|
||||
generic = 3.0, -- s between non-coach messages
|
||||
repeatSame = 6.0 -- s before repeating same message key
|
||||
},
|
||||
}
|
||||
|
||||
messages = {
|
||||
-- Placeholders: {id}, {type}, {brg}, {rng}, {rng_u}, {gs}, {gs_u}, {agl}, {agl_u}, {hints}
|
||||
spawned = "Crate’s live! {type} [{id}]. Bearing {brg}° range {rng} {rng_u}. Call for vectors if you need a hand.",
|
||||
arrival = "You’re close—nice and easy. Hover at 5–20 meters.",
|
||||
close = "Reduce speed below 15 km/h and set 5–20 m AGL.",
|
||||
coach = "{hints} GS {gs} {gs_u}.",
|
||||
tooFast = "Too fast for pickup: GS {gs} {gs_u}. Reduce below 8 km/h.",
|
||||
tooHigh = "Too high: AGL {agl} {agl_u}. Target 5–20 m.",
|
||||
tooLow = "Too low: AGL {agl} {agl_u}. Maintain at least 5 m.",
|
||||
drift = "Outside pickup window. Re-center within 25 m.",
|
||||
hold = "Oooh, right there! HOLD POSITION…",
|
||||
loaded = "Crate is hooked! Nice flying!",
|
||||
hoverLost = "Movement detected—recover hover to load.",
|
||||
abort = "Hover lost. Reacquire within 25 m, GS < 8 km/h, AGL 5–20 m.",
|
||||
}
|
||||
-- General CTLD event messages (non-hover). Tweak freely.
|
||||
CTLD.Messages = {
|
||||
-- Crates
|
||||
crate_spawn_requested = "Request received—spawning {type} crate at {zone}.",
|
||||
pickup_zone_required = "Move within {zone_dist} {zone_dist_u} of a Supply Zone to request crates.",
|
||||
crate_re_marked = "Re-marking crate {id} with {mark}.",
|
||||
crate_expired = "Crate {id} expired and was removed.",
|
||||
crate_max_capacity = "Max load reached ({total}). Drop or build before picking up more.",
|
||||
crate_spawned = "Crate’s live! {type} [{id}]. Bearing {brg}° range {rng} {rng_u}. Call for vectors if you need a hand.",
|
||||
|
||||
-- Drops
|
||||
drop_initiated = "Dropping {count} crate(s) here…",
|
||||
dropped_crates = "Dropped {count} crate(s) at your location.",
|
||||
no_loaded_crates = "No loaded crates to drop.",
|
||||
|
||||
-- Build
|
||||
build_insufficient_crates = "Insufficient crates to build {build}.",
|
||||
build_requires_ground = "You have {total} crate(s) onboard—drop them first to build here.",
|
||||
build_started = "Building {build} at your position…",
|
||||
build_success = "{build} deployed to the field!",
|
||||
build_success_coalition = "{player} deployed {build} to the field!",
|
||||
build_failed = "Build failed: {reason}.",
|
||||
fob_restricted = "FOB building is restricted to designated FOB zones.",
|
||||
auto_fob_built = "FOB auto-built at {zone}.",
|
||||
|
||||
-- Troops
|
||||
troops_loaded = "Loaded {count} troops—ready to deploy.",
|
||||
troops_unloaded = "Deployed {count} troops.",
|
||||
troops_unloaded_coalition = "{player} deployed {count} troops.",
|
||||
no_troops = "No troops onboard.",
|
||||
troops_deploy_failed = "Deploy failed: {reason}.",
|
||||
|
||||
-- Coach & nav
|
||||
vectors_to_crate = "Nearest crate {id}: bearing {brg}°, range {rng} {rng_u}.",
|
||||
coach_enabled = "Hover Coach enabled.",
|
||||
coach_disabled = "Hover Coach disabled.",
|
||||
|
||||
-- Hover Coach guidance
|
||||
coach_arrival = "You’re close—nice and easy. Hover at 5–20 meters.",
|
||||
coach_close = "Reduce speed below 15 km/h and set 5–20 m AGL.",
|
||||
coach_hint = "{hints} GS {gs} {gs_u}.",
|
||||
coach_too_fast = "Too fast for pickup: GS {gs} {gs_u}. Reduce below 8 km/h.",
|
||||
coach_too_high = "Too high: AGL {agl} {agl_u}. Target 5–20 m.",
|
||||
coach_too_low = "Too low: AGL {agl} {agl_u}. Maintain at least 5 m.",
|
||||
coach_drift = "Outside pickup window. Re-center within 25 m.",
|
||||
coach_hold = "Oooh, right there! HOLD POSITION…",
|
||||
coach_loaded = "Crate is hooked! Nice flying!",
|
||||
coach_hover_lost = "Movement detected—recover hover to load.",
|
||||
coach_abort = "Hover lost. Reacquire within 25 m, GS < 8 km/h, AGL 5–20 m.",
|
||||
}
|
||||
|
||||
CTLD.Config = {
|
||||
@ -209,6 +244,7 @@ CTLD._loadedCrates = {} -- [groupName] = { total=n, byKey = { key -> count }
|
||||
CTLD._hoverState = {} -- [unitName] = { targetCrate=name, startTime=t }
|
||||
CTLD._unitLast = {} -- [unitName] = { x, z, t }
|
||||
CTLD._coachState = {} -- [unitName] = { lastKeyTimes = {key->time}, lastHint = "", phase = "", lastPhaseMsg = 0, target = crateName, holdStart = nil }
|
||||
CTLD._msgState = { } -- messaging throttle state: [scopeKey] = { lastKeyTimes = { key -> time } }
|
||||
|
||||
-- =========================
|
||||
-- Utilities
|
||||
@ -360,19 +396,26 @@ local function _projectToBodyFrame(dx, dz, hdg)
|
||||
return right, fwd
|
||||
end
|
||||
|
||||
local function _playerNameFromGroup(group)
|
||||
if not group then return 'Player' end
|
||||
local unit = group:GetUnit(1)
|
||||
local pname = unit and unit.GetPlayerName and unit:GetPlayerName()
|
||||
if pname and pname ~= '' then return pname end
|
||||
return group:GetName() or 'Player'
|
||||
end
|
||||
|
||||
local function _coachSend(self, group, unitName, key, data, isCoach)
|
||||
local cfg = CTLD.HoverCoachConfig
|
||||
if not (cfg and cfg.enabled) then return end
|
||||
local cfg = CTLD.HoverCoachConfig or {}
|
||||
local now = timer.getTime()
|
||||
CTLD._coachState[unitName] = CTLD._coachState[unitName] or { lastKeyTimes = {} }
|
||||
local st = CTLD._coachState[unitName]
|
||||
local last = st.lastKeyTimes[key] or 0
|
||||
local minGap = isCoach and (cfg.throttle.coachUpdate or 1.5) or (cfg.throttle.generic or 3.0)
|
||||
local repeatGap = cfg.throttle.repeatSame or (minGap * 2)
|
||||
local minGap = isCoach and ((cfg.throttle and cfg.throttle.coachUpdate) or 1.5) or ((cfg.throttle and cfg.throttle.generic) or 3.0)
|
||||
local repeatGap = (cfg.throttle and cfg.throttle.repeatSame) or (minGap * 2)
|
||||
if last > 0 and (now - last) < minGap then return end
|
||||
-- prevent repeat spam of identical key too fast (only after first send)
|
||||
if last > 0 and (now - last) < repeatGap then return end
|
||||
local tpl = cfg.messages[key]
|
||||
local tpl = CTLD.Messages and CTLD.Messages[key]
|
||||
if not tpl then return end
|
||||
local text = _fmtTemplate(tpl, data)
|
||||
if text and text ~= '' then
|
||||
@ -381,6 +424,27 @@ local function _coachSend(self, group, unitName, key, data, isCoach)
|
||||
end
|
||||
end
|
||||
|
||||
local function _eventSend(self, group, side, key, data)
|
||||
local tpl = CTLD.Messages and CTLD.Messages[key]
|
||||
if not tpl then return end
|
||||
local now = timer.getTime()
|
||||
local scopeKey
|
||||
if group then scopeKey = 'GRP:'..group:GetName() else scopeKey = 'COAL:'..tostring(side or self.Side) end
|
||||
CTLD._msgState[scopeKey] = CTLD._msgState[scopeKey] or { lastKeyTimes = {} }
|
||||
local st = CTLD._msgState[scopeKey]
|
||||
local last = st.lastKeyTimes[key] or 0
|
||||
local cfg = CTLD.HoverCoachConfig
|
||||
local minGap = (cfg and cfg.throttle and cfg.throttle.generic) or 3.0
|
||||
local repeatGap = (cfg and cfg.throttle and cfg.throttle.repeatSame) or (minGap * 2)
|
||||
if last > 0 and (now - last) < minGap then return end
|
||||
if last > 0 and (now - last) < repeatGap then return end
|
||||
|
||||
local text = _fmtTemplate(tpl, data)
|
||||
if not text or text == '' then return end
|
||||
if group then _msgGroup(group, text) else _msgCoalition(side or self.Side, text) end
|
||||
st.lastKeyTimes[key] = now
|
||||
end
|
||||
|
||||
-- Determine an approximate radius for a ZONE. Tries MOOSE radius, then trigger zone radius, then configured radius.
|
||||
function CTLD:_getZoneRadius(zone)
|
||||
if zone and zone.Radius then return zone.Radius end
|
||||
@ -565,6 +629,70 @@ function CTLD:BuildGroupMenus(group)
|
||||
self:DropLoadedCrates(group, -1)
|
||||
end)
|
||||
|
||||
-- Coach & Navigation utilities
|
||||
local navRoot = MENU_GROUP:New(group, 'Coach & Nav', root)
|
||||
local gname = group:GetName()
|
||||
MENU_GROUP_COMMAND:New(group, 'Hover Coach: Enable', navRoot, function()
|
||||
CTLD._coachOverride = CTLD._coachOverride or {}
|
||||
CTLD._coachOverride[gname] = true
|
||||
_eventSend(self, group, nil, 'coach_enabled', {})
|
||||
end)
|
||||
MENU_GROUP_COMMAND:New(group, 'Hover Coach: Disable', navRoot, function()
|
||||
CTLD._coachOverride = CTLD._coachOverride or {}
|
||||
CTLD._coachOverride[gname] = false
|
||||
_eventSend(self, group, nil, 'coach_disabled', {})
|
||||
end)
|
||||
MENU_GROUP_COMMAND:New(group, 'Request Vectors to Nearest Crate', navRoot, function()
|
||||
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 }
|
||||
-- find nearest same-side crate
|
||||
local bestName, bestMeta, bestd
|
||||
for name,meta in pairs(CTLD._crates) do
|
||||
if meta.side == self.Side then
|
||||
local dx = (meta.point.x - here.x)
|
||||
local dz = (meta.point.z - here.z)
|
||||
local d = math.sqrt(dx*dx + dz*dz)
|
||||
if (not bestd) or d < bestd then
|
||||
bestName, bestMeta, bestd = name, meta, d
|
||||
end
|
||||
end
|
||||
end
|
||||
if bestName and bestMeta then
|
||||
local brg = _bearingDeg(here, bestMeta.point)
|
||||
local isMetric = _getPlayerIsMetric(unit)
|
||||
local rngV, rngU = _fmtRange(bestd, isMetric)
|
||||
_eventSend(self, group, nil, 'vectors_to_crate', { id = bestName, brg = brg, rng = rngV, rng_u = rngU })
|
||||
else
|
||||
_msgGroup(group, 'No friendly crates found.')
|
||||
end
|
||||
end)
|
||||
MENU_GROUP_COMMAND:New(group, 'Re-mark Nearest Crate (Smoke)', navRoot, function()
|
||||
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 bestName, bestMeta, bestd
|
||||
for name,meta in pairs(CTLD._crates) do
|
||||
if meta.side == self.Side then
|
||||
local dx = (meta.point.x - here.x)
|
||||
local dz = (meta.point.z - here.z)
|
||||
local d = math.sqrt(dx*dx + dz*dz)
|
||||
if (not bestd) or d < bestd then
|
||||
bestName, bestMeta, bestd = name, meta, d
|
||||
end
|
||||
end
|
||||
end
|
||||
if bestName and bestMeta then
|
||||
local zdef = { smoke = self.Config.PickupZoneSmokeColor }
|
||||
trigger.action.smoke({ x = bestMeta.point.x, z = bestMeta.point.z }, (zdef and zdef.smoke) or self.Config.PickupZoneSmokeColor)
|
||||
_eventSend(self, group, nil, 'crate_re_marked', { id = bestName, mark = 'smoke' })
|
||||
else
|
||||
_msgGroup(group, 'No friendly crates found to mark.')
|
||||
end
|
||||
end)
|
||||
|
||||
return root
|
||||
end
|
||||
|
||||
@ -589,6 +717,8 @@ function CTLD:RequestCrateForGroup(group, crateKey)
|
||||
local zone, dist = _nearestZonePoint(unit, self.Config.Zones.PickupZones)
|
||||
local spawnPoint
|
||||
local maxd = (self.Config.PickupZoneMaxDistance or 10000)
|
||||
-- Announce request
|
||||
_eventSend(self, group, nil, 'crate_spawn_requested', { type = tostring(crateKey), zone = zone and zone:GetName() or 'nearest zone' })
|
||||
if zone and dist <= maxd then
|
||||
spawnPoint = zone:GetPointVec3()
|
||||
-- if pickup zone has smoke configured, mark it
|
||||
@ -600,7 +730,9 @@ function CTLD:RequestCrateForGroup(group, crateKey)
|
||||
else
|
||||
-- Either require a pickup zone proximity, or fallback to near-aircraft spawn (legacy behavior)
|
||||
if self.Config.RequirePickupZoneForCrateRequest then
|
||||
_msgGroup(group, string.format('You are not close enough to a Supply Zone to request resources. Move within %.1f km.', (maxd or 10000)/1000))
|
||||
local isMetric = _getPlayerIsMetric(unit)
|
||||
local v, u = _fmtRange(math.max(0, dist - maxd), isMetric)
|
||||
_eventSend(self, group, nil, 'pickup_zone_required', { zone_dist = v, zone_dist_u = u })
|
||||
return
|
||||
else
|
||||
-- fallback: spawn near aircraft current position (safe offset)
|
||||
@ -615,6 +747,7 @@ function CTLD:RequestCrateForGroup(group, crateKey)
|
||||
side = self.Side,
|
||||
spawnTime = timer.getTime(),
|
||||
point = { x = spawnPoint.x, z = spawnPoint.z },
|
||||
requester = group:GetName(),
|
||||
}
|
||||
-- Immersive spawn message with bearing/range per player units
|
||||
do
|
||||
@ -632,7 +765,7 @@ function CTLD:RequestCrateForGroup(group, crateKey)
|
||||
rng = rngV,
|
||||
rng_u = rngU,
|
||||
}
|
||||
_coachSend(self, group, unit:GetName(), 'spawned', data, false)
|
||||
_eventSend(self, group, nil, 'crate_spawned', data)
|
||||
end
|
||||
end
|
||||
|
||||
@ -658,6 +791,14 @@ function CTLD:CleanupCrates()
|
||||
if obj then obj:destroy() end
|
||||
CTLD._crates[name] = nil
|
||||
if self.Config.Debug then env.info('[CTLD] Cleaned up crate '..name) end
|
||||
-- Notify requester group if still around; else coalition
|
||||
local gname = meta.requester
|
||||
local group = gname and GROUP:FindByName(gname) or nil
|
||||
if group and group:IsAlive() then
|
||||
_eventSend(self, group, nil, 'crate_expired', { id = name })
|
||||
else
|
||||
_eventSend(self, nil, self.Side, 'crate_expired', { id = name })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -678,7 +819,10 @@ function CTLD:BuildAtGroup(group)
|
||||
if c.meta.side == self.Side then table.insert(filtered, c) end
|
||||
end
|
||||
nearby = filtered
|
||||
if #nearby == 0 then _msgGroup(group, 'No crates within '..radius..'m') return end
|
||||
if #nearby == 0 then
|
||||
_eventSend(self, group, nil, 'build_insufficient_crates', { build = 'asset' })
|
||||
return
|
||||
end
|
||||
|
||||
-- Count by key
|
||||
local counts = {}
|
||||
@ -736,13 +880,14 @@ function CTLD:BuildAtGroup(group)
|
||||
if ok then
|
||||
local hdg = unit:GetHeading()
|
||||
local gdata = cat.build({ x = here.x, z = here.z }, math.deg(hdg), cat.side or self.Side)
|
||||
_eventSend(self, group, nil, 'build_started', { build = cat.description or recipeKey })
|
||||
local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata)
|
||||
if g then
|
||||
for reqKey,qty in pairs(cat.requires) do consumeCrates(reqKey, qty) end
|
||||
_msgGroup(group, string.format('Built %s at your location', cat.description or recipeKey))
|
||||
_eventSend(self, nil, self.Side, 'build_success_coalition', { build = cat.description or recipeKey, player = _playerNameFromGroup(group) })
|
||||
return
|
||||
else
|
||||
_msgGroup(group, 'Build failed: DCS group spawn error')
|
||||
_eventSend(self, group, nil, 'build_failed', { reason = 'DCS group spawn error' })
|
||||
return
|
||||
end
|
||||
end
|
||||
@ -759,13 +904,14 @@ function CTLD:BuildAtGroup(group)
|
||||
else
|
||||
local hdg = unit:GetHeading()
|
||||
local gdata = cat.build({ x = here.x, z = here.z }, math.deg(hdg), cat.side or self.Side)
|
||||
_eventSend(self, group, nil, 'build_started', { build = cat.description or key })
|
||||
local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata)
|
||||
if g then
|
||||
consumeCrates(key, cat.required or 1)
|
||||
_msgGroup(group, string.format('Built %s at your location', cat.description or key))
|
||||
_eventSend(self, nil, self.Side, 'build_success_coalition', { build = cat.description or key, player = _playerNameFromGroup(group) })
|
||||
return
|
||||
else
|
||||
_msgGroup(group, 'Build failed: DCS group spawn error')
|
||||
_eventSend(self, group, nil, 'build_failed', { reason = 'DCS group spawn error' })
|
||||
return
|
||||
end
|
||||
end
|
||||
@ -773,7 +919,7 @@ function CTLD:BuildAtGroup(group)
|
||||
end
|
||||
|
||||
if fobBlocked then
|
||||
_msgGroup(group, 'FOB building is restricted to designated FOB zones. Move inside a FOB zone to build.')
|
||||
_eventSend(self, group, nil, 'fob_restricted', {})
|
||||
return
|
||||
end
|
||||
|
||||
@ -781,12 +927,11 @@ function CTLD:BuildAtGroup(group)
|
||||
if self.Config.BuildRequiresGroundCrates == true then
|
||||
local carried = CTLD._loadedCrates[gname]
|
||||
if carried and (carried.total or 0) > 0 then
|
||||
_msgGroup(group, string.format('Insufficient ground crates to build here. You have %d loaded crate(s) onboard — drop them first (F10 -> Drop Loaded Crates).', carried.total))
|
||||
_eventSend(self, group, nil, 'build_requires_ground', { total = carried.total })
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
_msgGroup(group, 'Insufficient crates to build any asset here')
|
||||
_eventSend(self, group, nil, 'build_insufficient_crates', { build = 'asset' })
|
||||
end
|
||||
|
||||
-- =========================
|
||||
@ -803,12 +948,15 @@ 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
|
||||
if not lc or (lc.total or 0) == 0 then _eventSend(self, group, nil, 'no_loaded_crates', {}) 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
|
||||
local initialTotal = lc.total or 0
|
||||
local requested = (howMany and howMany > 0) and howMany or initialTotal
|
||||
local toDrop = math.min(requested, initialTotal)
|
||||
_eventSend(self, group, nil, 'drop_initiated', { count = toDrop })
|
||||
-- Drop in key order
|
||||
for k,count in pairs(BASE:DeepCopy(lc.byKey)) do
|
||||
if toDrop <= 0 then break end
|
||||
@ -825,7 +973,8 @@ function CTLD:DropLoadedCrates(group, howMany)
|
||||
if toDrop <= 0 then break end
|
||||
end
|
||||
end
|
||||
_msgGroup(group, 'Dropped loaded crates')
|
||||
local actualDropped = initialTotal - (lc.total or 0)
|
||||
_eventSend(self, group, nil, 'dropped_crates', { count = actualDropped })
|
||||
end
|
||||
|
||||
-- =========================
|
||||
@ -877,16 +1026,21 @@ function CTLD:ScanHoverPickup()
|
||||
end
|
||||
end
|
||||
|
||||
-- Resolve per-group coach enable override
|
||||
local coachEnabled = coachCfg.enabled
|
||||
if CTLD._coachOverride and CTLD._coachOverride[gname] ~= nil then
|
||||
coachEnabled = CTLD._coachOverride[gname]
|
||||
end
|
||||
-- If coach is on, provide phased guidance
|
||||
if coachCfg.enabled and bestName and bestMeta then
|
||||
if coachEnabled and bestName and bestMeta then
|
||||
local isMetric = _getPlayerIsMetric(unit)
|
||||
-- Arrival phase
|
||||
if bestd <= (coachCfg.thresholds.arrivalDist or 1000) then
|
||||
_coachSend(self, group, uname, 'arrival', {}, false)
|
||||
_coachSend(self, group, uname, 'coach_arrival', {}, false)
|
||||
end
|
||||
-- Close-in
|
||||
if bestd <= (coachCfg.thresholds.closeDist or 100) then
|
||||
_coachSend(self, group, uname, 'close', {}, false)
|
||||
_coachSend(self, group, uname, 'coach_close', {}, false)
|
||||
end
|
||||
|
||||
-- Precision phase
|
||||
@ -926,7 +1080,7 @@ function CTLD:ScanHoverPickup()
|
||||
local gsV, gsU = _fmtSpeed(gs, isMetric)
|
||||
local data = { hints = (hints ~= '' and (hints..'.') or ''), gs = gsV, gs_u = gsU }
|
||||
|
||||
_coachSend(self, group, uname, 'coach', data, true)
|
||||
_coachSend(self, group, uname, 'coach_hint', data, true)
|
||||
|
||||
-- Error prompts (dominant one)
|
||||
local maxGS = coachCfg.thresholds.maxGS or (8/3.6)
|
||||
@ -934,13 +1088,13 @@ function CTLD:ScanHoverPickup()
|
||||
local aglMaxT = aglMax
|
||||
if gs > maxGS then
|
||||
local v, u = _fmtSpeed(gs, isMetric)
|
||||
_coachSend(self, group, uname, 'tooFast', { gs = v, gs_u = u }, false)
|
||||
_coachSend(self, group, uname, 'coach_too_fast', { gs = v, gs_u = u }, false)
|
||||
elseif agl > aglMaxT then
|
||||
local v, u = _fmtAGL(agl, isMetric)
|
||||
_coachSend(self, group, uname, 'tooHigh', { agl = v, agl_u = u }, false)
|
||||
_coachSend(self, group, uname, 'coach_too_high', { agl = v, agl_u = u }, false)
|
||||
elseif agl < aglMinT then
|
||||
local v, u = _fmtAGL(agl, isMetric)
|
||||
_coachSend(self, group, uname, 'tooLow', { agl = v, agl_u = u }, false)
|
||||
_coachSend(self, group, uname, 'coach_too_low', { agl = v, agl_u = u }, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -973,7 +1127,7 @@ function CTLD:ScanHoverPickup()
|
||||
local hs = CTLD._hoverState[uname]
|
||||
if not hs or hs.targetCrate ~= bestName then
|
||||
CTLD._hoverState[uname] = { targetCrate = bestName, startTime = now }
|
||||
if coachCfg.enabled then _coachSend(self, group, uname, 'hold', {}, false) end
|
||||
if coachCfg.enabled then _coachSend(self, group, uname, 'coach_hold', {}, false) end
|
||||
else
|
||||
-- stability hold timer
|
||||
local holdNeeded = coachCfg.enabled and (coachCfg.thresholds.stabilityHold or (hp.Duration or 3)) or (hp.Duration or 3)
|
||||
@ -983,8 +1137,8 @@ function CTLD:ScanHoverPickup()
|
||||
if obj then obj:destroy() end
|
||||
CTLD._crates[bestName] = nil
|
||||
self:_addLoadedCrate(group, bestMeta.key)
|
||||
if coachCfg.enabled then
|
||||
_coachSend(self, group, uname, 'loaded', {}, false)
|
||||
if coachEnabled then
|
||||
_coachSend(self, group, uname, 'coach_loaded', {}, false)
|
||||
else
|
||||
_msgGroup(group, string.format('Loaded %s crate', tostring(bestMeta.key)))
|
||||
end
|
||||
@ -994,13 +1148,13 @@ function CTLD:ScanHoverPickup()
|
||||
end
|
||||
else
|
||||
-- lost precision window
|
||||
if coachCfg.enabled then _coachSend(self, group, uname, 'hoverLost', {}, false) end
|
||||
if coachEnabled then _coachSend(self, group, uname, 'coach_hover_lost', {}, false) end
|
||||
CTLD._hoverState[uname] = nil
|
||||
end
|
||||
else
|
||||
-- reset hover state when outside primary envelope
|
||||
if CTLD._hoverState[uname] then
|
||||
if coachCfg.enabled then _coachSend(self, group, uname, 'abort', {}, false) end
|
||||
if coachEnabled then _coachSend(self, group, uname, 'coach_abort', {}, false) end
|
||||
end
|
||||
CTLD._hoverState[uname] = nil
|
||||
end
|
||||
@ -1023,13 +1177,13 @@ function CTLD:LoadTroops(group, opts)
|
||||
count = capacity,
|
||||
typeKey = 'RIFLE',
|
||||
}
|
||||
_msgGroup(group, string.format('Loaded %d troops (virtual). Use Unload Troops to deploy.', capacity))
|
||||
_eventSend(self, group, nil, 'troops_loaded', { count = capacity })
|
||||
end
|
||||
|
||||
function CTLD:UnloadTroops(group)
|
||||
local gname = group:GetName()
|
||||
local load = CTLD._troopsLoaded[gname]
|
||||
if not load or (load.count or 0) == 0 then _msgGroup(group, 'No troops onboard') return end
|
||||
if not load or (load.count or 0) == 0 then _eventSend(self, group, nil, 'no_troops', {}) return end
|
||||
|
||||
local unit = group:GetUnit(1)
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
@ -1053,9 +1207,9 @@ function CTLD:UnloadTroops(group)
|
||||
local spawned = _coalitionAddGroup(self.Side, Group.Category.GROUND, groupData)
|
||||
if spawned then
|
||||
CTLD._troopsLoaded[gname] = nil
|
||||
_msgGroup(group, string.format('Deployed %d troops', #units))
|
||||
_eventSend(self, nil, self.Side, 'troops_unloaded_coalition', { count = #units, player = _playerNameFromGroup(group) })
|
||||
else
|
||||
_msgGroup(group, 'Deploy failed: DCS group spawn error')
|
||||
_eventSend(self, group, nil, 'troops_deploy_failed', { reason = 'DCS group spawn error' })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user