From 6134b167be073e6ec1de8f2890f00f535b703902 Mon Sep 17 00:00:00 2001 From: iTracerFacer <134304944+iTracerFacer@users.noreply.github.com> Date: Mon, 10 Nov 2025 21:51:10 -0600 Subject: [PATCH] AutoLoad and Unload of MEDEVAC units with extensive messaging and workflow. --- Moose_CTLD_Pure/Moose_CTLD.lua | 286 ++++++++++++++++++++++++++-- Moose_CTLD_Pure/Moose_CTLD_Pure.miz | Bin 1667404 -> 1667992 bytes 2 files changed, 268 insertions(+), 18 deletions(-) diff --git a/Moose_CTLD_Pure/Moose_CTLD.lua b/Moose_CTLD_Pure/Moose_CTLD.lua index 83c4cc5..8c1ab0d 100644 --- a/Moose_CTLD_Pure/Moose_CTLD.lua +++ b/Moose_CTLD_Pure/Moose_CTLD.lua @@ -1144,12 +1144,16 @@ CTLD.MEDEVAC = { RequireGroundContact = true, -- when true, helicopter must be firmly on the ground before crews move GroundContactAGL = 3, -- meters AGL threshold treated as “landed” for ground contact purposes MaxLandingSpeed = 2, -- m/s ground speed limit while parked; prevents chasing sliding helicopters + LoadDelay = 15, -- seconds crews need to board after reaching helicopter (must stay landed) + SettledAGL = 6.0, -- maximum AGL considered safely settled during boarding hold + AirAbortGrace = 2, -- seconds of hover tolerated during boarding before aborting }, AutoUnload = { Enabled = true, -- if true, crews automatically unload when landed in MASH zone UnloadDelay = 15, -- seconds after landing before auto-unload triggers - GroundContactAGL = 2.0, -- meters AGL treated as “on the ground” for auto-unload + GroundContactAGL = 3.5, -- meters AGL treated as “on the ground” for auto-unload (taller skids/mod helos) + SettledAGL = 6.0, -- maximum AGL considered safely settled for the unload hold to run (relative to terrain) MaxLandingSpeed = 2.0, -- m/s ground speed limit while holding to unload AirAbortGrace = 2, -- seconds of hover wiggle tolerated before aborting the unload hold }, @@ -1250,6 +1254,7 @@ CTLD._medevacStats = CTLD._medevacStats or { -- [coalition.side] = { spawne [coalition.side.RED] = { spawned = 0, rescued = 0, delivered = 0, timedOut = 0, killed = 0, salvageEarned = 0, vehiclesRespawned = 0, salvageUsed = 0 }, } CTLD._medevacUnloadStates = CTLD._medevacUnloadStates or {} -- [groupName] = { startTime, delay, holdAnnounced, nextReminder } +CTLD._medevacLoadStates = CTLD._medevacLoadStates or {} -- [groupName] = { startTime, delay, crewGroupName, crewData, holdAnnounced, nextReminder } CTLD._medevacEnrouteStates = CTLD._medevacEnrouteStates or {} -- [groupName] = { nextSend, lastIndex } -- #endregion State @@ -8153,10 +8158,37 @@ function CTLD:CheckMEDEVACCrewArrival() local dz = heliPos.z - crewPos.z local dist = math.sqrt(dx*dx + dz*dz) - -- If within 30m and helicopter is still on ground, auto-load + -- If within 30m and helicopter is still on ground, start load hold if dist <= 30 and not _isUnitInAir(heliUnit) then - self:_HandleMEDEVACPickup(heliGroup, crewGroupName, data) - crewGroup:destroy() + local loadCfg = cfg.AutoPickup or {} + local delay = loadCfg.LoadDelay or 15 + local now = timer.getTime() + local heliName = heliGroup:GetName() + + -- Check if already in a load hold + local existingState = CTLD._medevacLoadStates[heliName] + if existingState then + -- Already loading, just log refresh + _logDebug(string.format('[MEDEVAC][AutoLoad] Hold refreshed for %s (trigger=auto, crew=%s)', + heliName, crewGroupName)) + else + -- Start new load hold + CTLD._medevacLoadStates[heliName] = { + startTime = now, + delay = delay, + crewGroupName = crewGroupName, + crewData = data, + holdAnnounced = true, + nextReminder = now + math.max(1.5, delay / 3), + lastQualified = now, + } + + _msgGroup(heliGroup, string.format("MEDEVAC crew boarding. Hold position for %d seconds...", delay), 10) + _logVerbose(string.format('[MEDEVAC][AutoLoad] Hold started for %s (delay=%.1fs, trigger=auto, crew=%s)', + heliName, delay, crewGroupName)) + end + + -- Mark crew as enroute to prevent re-triggering data.enrouteToHeli = false data.targetHeli = nil end @@ -8180,7 +8212,8 @@ function CTLD:ScanMEDEVACAutoActions() local cfg = CTLD.MEDEVAC if not cfg or not cfg.Enabled then return end - -- Progress any ongoing unload holds before new scans + -- Progress any ongoing load and unload holds before new scans + self:_UpdateMedevacLoadStates() self:_UpdateMedevacUnloadStates() -- Check if any crews have reached their target helicopter @@ -8245,10 +8278,12 @@ function CTLD:AutoUnloadMEDEVACCrew(group) local unit = group:GetUnit(1) if not unit or not unit:IsAlive() then return end + local gname = group:GetName() or 'UNKNOWN' local autoCfg = cfg.AutoUnload or {} local aglLimit = autoCfg.GroundContactAGL or 2.0 local gsLimit = autoCfg.MaxLandingSpeed or 2.0 + local settleLimit = autoCfg.SettledAGL or (aglLimit + 2.0) local agl = _getUnitAGL(unit) if agl == nil then agl = 0 end @@ -8259,24 +8294,45 @@ function CTLD:AutoUnloadMEDEVACCrew(group) -- Treat the helicopter as landed when weight-on-wheels flips or when the skid height is within tolerance. local hasGroundContact = (not inAir) or (agl <= aglLimit) if not hasGroundContact then + _logDebug(string.format('[MEDEVAC][AutoUnload] %s skipped: no ground contact (agl=%.2f, limit=%.2f, inAir=%s)', gname, agl, aglLimit, tostring(inAir))) return end - if agl > aglLimit then + if inAir and agl > aglLimit then + _logDebug(string.format('[MEDEVAC][AutoUnload] %s skipped: AGL %.2f above limit %.2f while still airborne', gname, agl, aglLimit)) return end if gs > gsLimit then + _logDebug(string.format('[MEDEVAC][AutoUnload] %s skipped: ground speed %.2f above limit %.2f', gname, gs, gsLimit)) + return + end + + if settleLimit and settleLimit > 0 and agl > settleLimit then + _logDebug(string.format('[MEDEVAC][AutoUnload] %s skipped: AGL %.2f above settled limit %.2f', gname, agl, settleLimit)) return end local crews = self:_CollectRescuedCrewsForGroup(group:GetName()) - if #crews == 0 then return end + if #crews == 0 then + _logDebug(string.format('[MEDEVAC][AutoUnload] %s skipped: no rescued crews onboard', gname)) + return + end -- Check if inside MASH zone local pos = unit:GetPointVec3() local inMASH, mashZone = self:_IsPositionInMASHZone({ x = pos.x, z = pos.z }) - if not inMASH then return end + if not inMASH then + _logDebug(string.format('[MEDEVAC][AutoUnload] %s skipped: not inside MASH zone (crews=%d)', gname, #crews)) + return + end + + _logVerbose(string.format('[MEDEVAC][AutoUnload] %s qualified for unload in MASH %s (crews=%d, agl=%.2f, gs=%.2f)', + gname, + tostring((mashZone and (mashZone.name or mashZone.unitName)) or 'UNKNOWN'), + #crews, + agl, + gs)) -- Begin or maintain the unload hold state self:_EnsureMedevacUnloadState(group, mashZone, crews, { trigger = 'auto' }) @@ -8392,12 +8448,22 @@ function CTLD:_EnsureMedevacUnloadState(group, mashZone, crews, opts) } CTLD._medevacUnloadStates[gname] = state self:_AnnounceMedevacUnloadHold(group, state) + _logVerbose(string.format('[MEDEVAC][AutoUnload] Hold started for %s (delay=%0.1fs, trigger=%s, mash=%s, crews=%d)', + gname, + state.delay, + tostring(state.triggeredBy), + tostring(state.mashZoneName or 'UNKNOWN'), + crews and #crews or 0)) else state.delay = delay state.triggeredBy = opts and opts.trigger or state.triggeredBy if mashZone then state.mashZoneName = mashZone.name or mashZone.unitName or state.mashZoneName end + _logDebug(string.format('[MEDEVAC][AutoUnload] Hold refreshed for %s (trigger=%s, crews=%d)', + gname, + tostring(state.triggeredBy), + crews and #crews or 0)) end state.lastQualified = now @@ -8449,6 +8515,8 @@ function CTLD:_NotifyMedevacUnloadAbort(group, state, reasonKey) reasonText = 'wheels up too soon' elseif reasonKey == 'zone' then reasonText = 'left the MASH zone' + elseif reasonKey == 'agl' then + reasonText = 'climbed above unload height' elseif reasonKey == 'crew' then reasonText = 'no MEDEVAC patients onboard' else @@ -8484,6 +8552,172 @@ function CTLD:_CompleteMedevacUnload(group, crews) _logVerbose(string.format('[MEDEVAC] Auto unload complete for %s (%d crew group(s) delivered)', group:GetName(), #crews)) end +-- Send loading reminder message to pilot +function CTLD:_SendMedevacLoadReminder(group) + if not group then return end + local loadingMsgs = (self.MEDEVAC and self.MEDEVAC.LoadingMessages) or {} + if #loadingMsgs == 0 then return end + + local msg = loadingMsgs[math.random(1, #loadingMsgs)] + _msgGroup(group, msg, 6) +end + +-- Inform the pilot that the loading was cancelled and the hold must restart +function CTLD:_NotifyMedevacLoadAbort(group, state, reasonKey) + if not group or not state or state.abortNotified or not state.holdAnnounced then return end + + local reasonText + if reasonKey == 'air' then + reasonText = 'wheels up too soon' + elseif reasonKey == 'agl' then + reasonText = 'climbed above loading height' + elseif reasonKey == 'crew' then + reasonText = 'crew lost contact' + else + reasonText = 'hold interrupted' + end + + local delay = math.ceil(state.delay or 0) + if delay < 1 then delay = 1 end + + _msgGroup(group, string.format("MEDEVAC boarding aborted: %s. Land and hold for %d seconds to restart.", + reasonText, delay), 10) + + state.abortNotified = true +end + +-- Complete the load, pick up crew, and show success message +function CTLD:_CompleteMedevacLoad(group, crewGroupName, crewData) + if not group or not group:IsAlive() then return end + if not crewGroupName or not crewData then return end + + -- Destroy the crew unit + local crewGroup = Group.getByName(crewGroupName) + if crewGroup and crewGroup:isExist() then + crewGroup:destroy() + end + + -- Handle the actual pickup (respawn vehicle, etc.) + self:_HandleMEDEVACPickup(group, crewGroupName, crewData) + + -- Show completion message + local successMsgs = (self.MEDEVAC and self.MEDEVAC.LoadMessages) or {} + if #successMsgs > 0 then + local msg = successMsgs[math.random(1, #successMsgs)] + _msgGroup(group, msg, 10) + end + + _logVerbose(string.format('[MEDEVAC] Auto load complete for %s (crew %s)', group:GetName(), crewGroupName)) +end + +-- Maintain load hold states, handling completion or interruption +function CTLD:_UpdateMedevacLoadStates() + local states = CTLD._medevacLoadStates + if not states or not next(states) then return end + + local now = timer.getTime() + local cfg = self.MEDEVAC or {} + local cfgAuto = cfg.AutoPickup or {} + local aglLimit = cfgAuto.GroundContactAGL or 3 + local settleLimit = cfgAuto.SettledAGL or 6 + local gsLimit = cfgAuto.MaxLandingSpeed or 2 + local airGrace = cfgAuto.AirAbortGrace or 2 + + for gname, state in pairs(states) do + local group = GROUP:FindByName(gname) + if not group or not group:IsAlive() then + states[gname] = nil + _logDebug(string.format('[MEDEVAC][AutoLoad] %s removed: group not alive', gname)) + else + local unit = group:GetUnit(1) + if not unit or not unit:IsAlive() then + states[gname] = nil + _logDebug(string.format('[MEDEVAC][AutoLoad] %s removed: unit not alive', gname)) + else + local removeState = false + local agl = _getUnitAGL(unit) + local gs = _getGroundSpeed(unit) + + -- Check if crew still exists + local crewGroup = Group.getByName(state.crewGroupName) + if not crewGroup or not crewGroup:isExist() then + _logVerbose(string.format('[MEDEVAC][AutoLoad] Hold abort for %s: crew %s no longer exists', gname, state.crewGroupName)) + removeState = true + else + -- Check distance to crew + local crewUnit = crewGroup:getUnit(1) + if crewUnit then + local crewPos = crewUnit:getPoint() + local heliPos = unit:GetPointVec3() + local dx = heliPos.x - crewPos.x + local dz = heliPos.z - crewPos.z + local dist = math.sqrt(dx*dx + dz*dz) + + if dist > 40 then + self:_NotifyMedevacLoadAbort(group, state, 'crew') + _logVerbose(string.format('[MEDEVAC][AutoLoad] Hold abort for %s: moved too far from crew (%.1fm)', gname, dist)) + removeState = true + end + end + + if not removeState then + -- Check landing status (similar to unload logic) + local landed = not _isUnitInAir(unit) + if landed then + if settleLimit and settleLimit > 0 and agl > settleLimit then + landed = false + state.highAglSince = state.highAglSince or now + _logDebug(string.format('[MEDEVAC][AutoLoad] %s hold paused: AGL %.2f above settled limit %.2f', gname, agl, settleLimit)) + else + state.highAglSince = nil + end + else + state.highAglSince = nil + if agl <= aglLimit and gs <= gsLimit then + landed = true + end + end + + if landed then + state.airborneSince = nil + state.lastQualified = now + + -- Send reminders while holding + if state.nextReminder and now >= state.nextReminder then + self:_SendMedevacLoadReminder(group) + local spacing = state.delay or 2 + spacing = math.max(1.5, math.min(4, spacing / 2)) + state.nextReminder = now + spacing + end + + -- Complete load after delay + if (now - state.startTime) >= state.delay then + self:_CompleteMedevacLoad(group, state.crewGroupName, state.crewData) + _logVerbose(string.format('[MEDEVAC][AutoLoad] Hold complete for %s', gname)) + removeState = true + end + else + state.airborneSince = state.airborneSince or now + if (now - state.airborneSince) >= airGrace then + self:_NotifyMedevacLoadAbort(group, state, 'air') + _logVerbose(string.format('[MEDEVAC][AutoLoad] Hold abort for %s: airborne for %.1fs (grace=%.1f)', + gname, + now - state.airborneSince, + airGrace)) + removeState = true + end + end + end + end + + if removeState then + states[gname] = nil + end + end + end + end +end + -- Maintain unload hold states, handling completion or interruption function CTLD:_UpdateMedevacUnloadStates() local states = CTLD._medevacUnloadStates @@ -8512,14 +8746,26 @@ function CTLD:_UpdateMedevacUnloadStates() local crews = self:_CollectRescuedCrewsForGroup(gname) if #crews == 0 then self:_NotifyMedevacUnloadAbort(group, state, 'crew') + _logVerbose(string.format('[MEDEVAC][AutoUnload] Hold abort for %s: crew list empty', gname)) removeState = true else + local agl = _getUnitAGL(unit) + if agl == nil then agl = 0 end + local gs = _getGroundSpeed(unit) + if gs == nil then gs = 0 end + local settleLimit = cfgAuto.SettledAGL or (aglLimit + 2.0) + local landed = not _isUnitInAir(unit) - if not landed then - local agl = _getUnitAGL(unit) - if agl == nil then agl = 0 end - local gs = _getGroundSpeed(unit) - if gs == nil then gs = 0 end + if landed then + if settleLimit and settleLimit > 0 and agl > settleLimit then + landed = false + state.highAglSince = state.highAglSince or now + _logDebug(string.format('[MEDEVAC][AutoUnload] %s hold paused: AGL %.2f above settled limit %.2f', gname, agl, settleLimit)) + else + state.highAglSince = nil + end + else + state.highAglSince = nil if agl <= aglLimit and gs <= gsLimit then landed = true end @@ -8532,14 +8778,12 @@ function CTLD:_UpdateMedevacUnloadStates() local inMASH, mashZone = self:_IsPositionInMASHZone({ x = pos.x, z = pos.z }) if not inMASH then self:_NotifyMedevacUnloadAbort(group, state, 'zone') + _logVerbose(string.format('[MEDEVAC][AutoUnload] Hold abort for %s: left MASH zone', gname)) removeState = true else state.mashZoneName = mashZone and (mashZone.name or mashZone.unitName or state.mashZoneName) - - if not state.holdAnnounced then - self:_AnnounceMedevacUnloadHold(group, state) - end - + + -- Send reminders while holding if state.nextReminder and now >= state.nextReminder then self:_SendMedevacUnloadReminder(group) local spacing = state.delay or 2 @@ -8547,8 +8791,10 @@ function CTLD:_UpdateMedevacUnloadStates() state.nextReminder = now + spacing end + -- Complete unload after delay if (now - state.startTime) >= state.delay then self:_CompleteMedevacUnload(group, crews) + _logVerbose(string.format('[MEDEVAC][AutoUnload] Hold complete for %s (crews delivered=%d)', gname, #crews)) removeState = true end end @@ -8556,6 +8802,10 @@ function CTLD:_UpdateMedevacUnloadStates() state.airborneSince = state.airborneSince or now if (now - state.airborneSince) >= airGrace then self:_NotifyMedevacUnloadAbort(group, state, 'air') + _logVerbose(string.format('[MEDEVAC][AutoUnload] Hold abort for %s: airborne for %.1fs (grace=%.1f)', + gname, + now - state.airborneSince, + airGrace)) removeState = true end end diff --git a/Moose_CTLD_Pure/Moose_CTLD_Pure.miz b/Moose_CTLD_Pure/Moose_CTLD_Pure.miz index 66ed0cd7403f8edcca2805df7e1d0918833eea7b..659f747c1439d072b190d41026bd7c4df319efb8 100644 GIT binary patch delta 91988 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