mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Added enroute medevac message that includes brg and range.
This commit is contained in:
parent
6e3a046672
commit
3deaf947b2
@ -988,30 +988,134 @@ CTLD.MEDEVAC = {
|
|||||||
|
|
||||||
-- Unload completion messages (shown when offload finishes)
|
-- Unload completion messages (shown when offload finishes)
|
||||||
UnloadCompleteMessages = {
|
UnloadCompleteMessages = {
|
||||||
"Crew: Offload complete! Medical teams have the wounded!",
|
"MASH: Offload complete! Medical teams have the wounded!",
|
||||||
"Crew: Patients transferred! You're cleared to lift!",
|
"MASH: Patients transferred! You're cleared to lift!",
|
||||||
"Crew: All casualties delivered! Incredible flying!",
|
"MASH: All casualties delivered! Incredible flying!",
|
||||||
"Crew: They're inside! Mission accomplished!",
|
"MASH: They're inside! Mission accomplished!",
|
||||||
"Crew: Every patient is in triage! Thank you!",
|
"MASH: Every patient is in triage! Thank you!",
|
||||||
"Crew: Transfer complete! Head back when ready!",
|
"MASH: Transfer complete! Head back when ready!",
|
||||||
"Crew: Doctors have them! Outstanding job!",
|
"MASH: Doctors have them! Outstanding job!",
|
||||||
"Crew: Wounded are inside! You saved them!",
|
"MASH: Wounded are inside! You saved them!",
|
||||||
"Crew: Hand-off confirmed! You're good to go!",
|
"MASH: Hand-off confirmed! You're good to go!",
|
||||||
"Crew: Casualties secure! Medical team standing by!",
|
"MASH: Casualties secure! Medical team standing by!",
|
||||||
"Crew: Delivery confirmed! Take a breather, pilot!",
|
"MASH: Delivery confirmed! Take a breather, pilot!",
|
||||||
"Crew: All stretchers filled! We are done here!",
|
"MASH: All stretchers filled! We are done here!",
|
||||||
"Crew: Hospital staff has the patients! Great work!",
|
"MASH: Hospital staff has the patients! Great work!",
|
||||||
"Crew: Unload complete! You nailed that landing!",
|
"MASH: Unload complete! You nailed that landing!",
|
||||||
"Crew: MASH has control! You're clear, thank you!",
|
"MASH: MASH has control! You're clear, thank you!",
|
||||||
"Crew: Every survivor is inside! Hell yes!",
|
"MASH: Every survivor is inside! Hell yes!",
|
||||||
"Crew: Docs have them! Back to the fight when ready!",
|
"MASH: Docs have them! Back to the fight when ready!",
|
||||||
"Crew: Handoff complete! You earned the praise!",
|
"MASH: Handoff complete! You earned the praise!",
|
||||||
"Crew: Medical team secured the wounded! Legend!",
|
"MASH: Medical team secured the wounded! Legend!",
|
||||||
"Crew: Transfer complete! Outstanding steady hover!",
|
"MASH: Transfer complete! Outstanding steady hover!",
|
||||||
"Crew: They're in the OR! You rock, pilot!",
|
"MASH: They're in the OR! You rock, pilot!",
|
||||||
"Crew: Casualties delivered! Spin it back up when ready!",
|
"MASH: Casualties delivered! Spin it back up when ready!",
|
||||||
"Crew: MASH confirms receipt! You're a lifesaver!",
|
"MASH: MASH confirms receipt! You're a lifesaver!",
|
||||||
"Crew: Every patient is safe! Mission complete!",
|
"MASH: Every patient is safe! Mission complete!",
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Enroute messages (periodic chatter with bearing/distance to MASH)
|
||||||
|
EnrouteToMashMessages = {
|
||||||
|
"Crew: Steady hands—{mash} sits at bearing {brg}°, {rng} {rng_u} ahead; patients are trying to nap.",
|
||||||
|
"Crew: Nav board says {mash} is {brg}° for {rng} {rng_u}; keep it gentle so the IVs stay put.",
|
||||||
|
"Crew: If you hold {brg}° for {rng} {rng_u}, {mash} will have hot coffee waiting—no promises on taste.",
|
||||||
|
"Crew: Confirmed, {mash} straight off the nose at {brg}°, {rng} {rng_u}; wounded are counting on you.",
|
||||||
|
"Crew: Stay on {brg}° for {rng} {rng_u} and we’ll roll into {mash} like heroes instead of hooligans.",
|
||||||
|
"Crew: Tilt a hair left—{mash} lies {brg}° at {rng} {rng_u}; let’s not overshoot the hospital.",
|
||||||
|
"Crew: Keep the climb smooth; {mash} is {brg}° at {rng} {rng_u} and the patients already look green.",
|
||||||
|
"Crew: Plot shows {mash} bearing {brg}°, range {rng} {rng_u}; mother hen wants her chicks delivered.",
|
||||||
|
"Crew: Hold that heading {brg}° and we’ll be on final to {mash} in {rng} {rng_u}; medics are on standby.",
|
||||||
|
"Crew: Reminder—{mash} is {brg}° at {rng} {rng_u}; try not to buzz the command tent this run.",
|
||||||
|
"Crew: Flight doc says keep turbulence down; {mash} sits {brg}° out at {rng} {rng_u}.",
|
||||||
|
"Crew: Stay focused—{mash} ahead {brg}°, {rng} {rng_u}; every bump costs us more paperwork.",
|
||||||
|
"Crew: We owe those medics a beer; {mash} is {brg}° for {rng} {rng_u}, so let’s get there in one piece.",
|
||||||
|
"Crew: Update from ops: {mash} remains {brg}° at {rng} {rng_u}; throttle down before the pad sneaks up.",
|
||||||
|
"Crew: Patients are asking if this thing comes with a smoother ride—{mash} {brg}°, {rng} {rng_u} to go.",
|
||||||
|
"Crew: Keep your cool—{mash} is {brg}° at {rng} {rng_u}; med bay is laying out stretchers now.",
|
||||||
|
"Crew: Good news, {mash} has fresh morphine; bad news, it’s {brg}° and {rng} {rng_u} away—step on it.",
|
||||||
|
"Crew: Command wants ETA—tell them {mash} is {brg}° for {rng} {rng_u} and we’re hauling wounded and sass.",
|
||||||
|
"Crew: That squeak you hear is the stretcher—stay on {brg}° for {rng} {rng_u} to {mash}.",
|
||||||
|
"Crew: Don’t mind the swearing; we’re {rng} {rng_u} from {mash} on bearing {brg}° and the pain meds wore off.",
|
||||||
|
"Crew: Eyes outside—{mash} sits {brg}° at {rng} {rng_u}; flak gunners better keep their heads down.",
|
||||||
|
"Crew: Weather’s clear—{mash} is {brg}° out {rng} {rng_u}; let’s not invent new IFR procedures.",
|
||||||
|
"Crew: Remember your autorotation drills? Neither do we. Fly {brg}° for {rng} {rng_u} to {mash} and keep her humming.",
|
||||||
|
"Crew: The guy on stretcher two wants to know if {mash} is really {brg}° at {rng} {rng_u}; I told him yes, please prove me right.",
|
||||||
|
"Crew: Rotor check good; {mash} bearing {brg}°, distance {rng} {rng_u}. Try to act like professionals.",
|
||||||
|
"Crew: Stay low and fast—{mash} {brg}° {rng} {rng_u}; enemy radios are whining already.",
|
||||||
|
"Crew: You’re doing great—just keep {brg}° for {rng} {rng_u} and {mash} will take the baton.",
|
||||||
|
"Crew: Map scribble says {mash} is {brg}° and {rng} {rng_u}; let’s prove cartography still works.",
|
||||||
|
"Crew: Pilot, the patients voted: less banking, more {mash}. Bearing {brg}°, {rng} {rng_u}.",
|
||||||
|
"Crew: We cross the line into {mash} territory in {rng} {rng_u} at {brg}°; keep the blades happy.",
|
||||||
|
"Crew: Hot tip—{mash} chefs saved us soup if we make {brg}° in {rng} {rng_u}; pretty sure it’s edible.",
|
||||||
|
"Crew: Another bump like that and I’m filing a complaint; {mash} is {brg}° at {rng} {rng_u}, so aim true.",
|
||||||
|
"Crew: The wounded in back just made side bets on landing—bearing {brg}°, range {rng} {rng_u} to {mash}.",
|
||||||
|
"Crew: Stay on that compass—{mash} sits {brg}° at {rng} {rng_u}; medics already prepped the triage tent.",
|
||||||
|
"Crew: Copy tower—{mash} runway metaphorically lies {brg}° and {rng} {rng_u} ahead; no victory rolls.",
|
||||||
|
"Crew: Someone alert the chaplain—we’re {rng} {rng_u} out from {mash} on {brg}° and our patients could use jokes.",
|
||||||
|
"Crew: Keep chatter clear—{mash} is {brg}° away at {rng} {rng_u}; let’s land before the morphine fades.",
|
||||||
|
"Crew: They promised me coffee at {mash} if we stick {brg}° for {rng} {rng_u}; don’t ruin this.",
|
||||||
|
"Crew: Plotting intercept—{mash} coordinates show {brg}°/{rng} {rng_u}; maintain this track.",
|
||||||
|
"Crew: I know the gauges say fine but the guys in back disagree; {mash} {brg}°, {rng} {rng_u}.",
|
||||||
|
"Crew: Remember, no barrel rolls; {mash} lies {brg}° at {rng} {rng_u}, and the surgeon will kill us if we’re late.",
|
||||||
|
"Crew: Keep the skids level; {mash} is {brg}° and {rng} {rng_u} away begging for customers.",
|
||||||
|
"Crew: We’re on schedule—{mash} sits {brg}° at {rng} {rng_u}; try not to invent new delays.",
|
||||||
|
"Crew: Latest wind check says {mash} {brg}°, {rng} {rng_u}; adjust trim before the patients revolt.",
|
||||||
|
"Crew: The medic in back just promised cookies if we hit {brg}° for {rng} {rng_u} to {mash}.",
|
||||||
|
"Crew: Hold blades steady—{mash} is {brg}° at {rng} {rng_u}; stretcher straps can only do so much.",
|
||||||
|
"Crew: Copy you’re bored, but {mash} is {brg}° for {rng} {rng_u}; no scenic detours today.",
|
||||||
|
"Crew: If you overshoot {mash} by {rng} {rng_u} I’m telling command it was deliberate; target bearing {brg}°.",
|
||||||
|
"Crew: Serious faces—we’re {rng} {rng_u} out from {mash} on {brg}° and these folks hurt like hell.",
|
||||||
|
"Crew: Hey pilot, the guy with the busted leg says thanks—just keep {brg}° for {rng} {rng_u} to {mash}.",
|
||||||
|
"Crew: That was a nice thermal—maybe avoid the next one; {mash} sits {brg}° at {rng} {rng_u}.",
|
||||||
|
"Crew: Keep those eyes up; {mash} is {brg}° away {rng} {rng_u}; CAS flights are buzzing around.",
|
||||||
|
"Crew: Reminder: {mash} won’t accept deliveries dumped on the lawn; {brg}° and {rng} {rng_u} to touchdown.",
|
||||||
|
"Crew: Ops pinged again; told them we’re {rng} {rng_u} from {mash} on heading {brg}° and flying like pros.",
|
||||||
|
"Crew: We promised the patients a soft landing; {mash} bearing {brg}°, distance {rng} {rng_u}.",
|
||||||
|
"Crew: Keep the profile low—{mash} is {brg}° at {rng} {rng_u}; AAA spots are grumpy today.",
|
||||||
|
"Crew: Message from tower: {mash} pad is clear; track {brg}° for {rng} {rng_u} and watch the dust.",
|
||||||
|
"Crew: Someone in back just yanked an IV—slow the hell down; {mash} {brg}°, {rng} {rng_u}.",
|
||||||
|
"Crew: We’re so close I can smell antiseptic—{mash} is {brg}° and {rng} {rng_u} from here.",
|
||||||
|
"Crew: If we shave more time the medics might actually smile; {mash} lies {brg}° at {rng} {rng_u}.",
|
||||||
|
"Crew: Friendly reminder—{mash} is {brg}° at {rng} {rng_u}; try not to park on their tent again.",
|
||||||
|
"Crew: The patients voted you best pilot if we hit {mash} at {brg}° in {rng} {rng_u}; don’t blow the election.",
|
||||||
|
"Crew: I’ve got morphine bets riding on you; {mash} sits {brg}° for {rng} {rng_u}.",
|
||||||
|
"Crew: Keep your head in the game—{mash} {brg}°, {rng} {rng_u}; enemy gunners love tall rotor masts.",
|
||||||
|
"Crew: That rattle is the litter, not the engine; {mash} is {brg}° and {rng} {rng_u} out.",
|
||||||
|
"Crew: Flight lead wants a status—reported {mash} bearing {brg}°, {rng} {rng_u}; keep us honest.",
|
||||||
|
"Crew: Patient three says thanks for not crashing—yet; {mash} {brg}°, {rng} {rng_u}.",
|
||||||
|
"Crew: If you see the chaplain waving, you missed—{mash} sits {brg}° at {rng} {rng_u}.",
|
||||||
|
"Crew: Med bay just radioed; they’re warming blankets. That’s {mash} {brg}° at {rng} {rng_u}.",
|
||||||
|
"Crew: Stay locked on {brg}° for {rng} {rng_u}; {mash} already cleared a pad.",
|
||||||
|
"Crew: Little turbulence ahead; {mash} bearing {brg}°, {rng} {rng_u}; grip it and grin.",
|
||||||
|
"Crew: The guy on the stretcher wants to know if we’re lost—tell him {mash} {brg}°, {rng} {rng_u}.",
|
||||||
|
"Crew: Hold altitude; {mash} is {brg}° away {rng} {rng_u} and the medics hate surprise autorotations.",
|
||||||
|
"Crew: Confirming nav—{mash} at {brg}°, {rng} {rng_u}; you keep flying, we’ll keep them calm.",
|
||||||
|
"Crew: If anyone asks, yes we’re inbound; {mash} sits {brg}° {rng} {rng_u} out.",
|
||||||
|
"Crew: Think happy thoughts—{mash} is {brg}° at {rng} {rng_u}; patients can smell fear.",
|
||||||
|
"Crew: Quit sightseeing—{mash} lies {brg}° and {rng} {rng_u}; let’s deliver the meat wagon.",
|
||||||
|
"Crew: Keep that nose pointed {brg}°; {mash} is only {rng} {rng_u} away and my nerves are shot.",
|
||||||
|
"Crew: We promised a fast ride; {mash} sits {brg}° at {rng} {rng_u}. No pressure.",
|
||||||
|
"Crew: You’re lined up perfect—{mash} {brg}°, {rng} {rng_u}; now just keep it that way.",
|
||||||
|
"Crew: The surgeon texted—he wants his patients now. {mash} bearing {brg}°, {rng} {rng_u}.",
|
||||||
|
"Crew: The wounded are timing us; {mash} is {brg}° at {rng} {rng_u} so don’t dilly-dally.",
|
||||||
|
"Crew: Another five minutes and {mash} will start nagging—hold {brg}° for {rng} {rng_u}.",
|
||||||
|
"Crew: Keep the blade slap mellow; {mash} sits {brg}° at {rng} {rng_u}.",
|
||||||
|
"Crew: Airspeed’s good; {mash} is {brg}° for {rng} {rng_u}; cue inspirational soundtrack.",
|
||||||
|
"Crew: Patient four says if we keep {brg}° for {rng} {rng_u}, drinks are on him at {mash}.",
|
||||||
|
"Crew: Don’t ask why the stretcher smells like smoke; just fly {brg}° {rng} {rng_u} to {mash}.",
|
||||||
|
"Crew: Tower says we’re clear direct {mash}; bearing {brg}°, {rng} {rng_u}.",
|
||||||
|
"Crew: If the engine coughs again we’re walking—{mash} sits {brg}° at {rng} {rng_u}; keep the RPM up.",
|
||||||
|
"Crew: Calm voices only—{mash} sits {brg}° {rng} {rng_u}; the patients listen to tone more than words.",
|
||||||
|
"Crew: Promise the guys in back we’ll hit {brg}° for {rng} {rng_u} and land like silk at {mash}.",
|
||||||
|
"Crew: There’s a small bet you’ll flare too high; prove them wrong—{mash} {brg}°, {rng} {rng_u}.",
|
||||||
|
"Crew: The medic wants you to skip the cowboy routine; {mash} lies {brg}° at {rng} {rng_u}.",
|
||||||
|
"Crew: That vibration is fine; what’s not fine is missing {mash} at {brg}° in {rng} {rng_u}.",
|
||||||
|
"Crew: Keep the collective steady—{mash} {brg}°, {rng} {rng_u}; we’re hauling precious cargo.",
|
||||||
|
"Crew: Someone promised me a hot meal at {mash}; stay on {brg}° for {rng} {rng_u} and make it happen.",
|
||||||
|
"Crew: The patients say if you wobble again they’re walking; {mash} {brg}°, {rng} {rng_u}.",
|
||||||
|
"Crew: Hold that horizon—{mash} is {brg}° for {rng} {rng_u}; the doc already scrubbed in.",
|
||||||
|
"Crew: Eyes on the prize—{mash} {brg}°, {rng} {rng_u}; don’t let the wind push us off.",
|
||||||
|
"Crew: Finish strong; {mash} sits {brg}° {rng} {rng_u}. Wheels down and we’re heroes again.",
|
||||||
},
|
},
|
||||||
|
|
||||||
-- Crew unit types per coalition (fallback if not specified in catalog)
|
-- Crew unit types per coalition (fallback if not specified in catalog)
|
||||||
@ -1034,14 +1138,22 @@ CTLD.MEDEVAC = {
|
|||||||
-- Automatic pickup/unload settings
|
-- Automatic pickup/unload settings
|
||||||
AutoPickup = {
|
AutoPickup = {
|
||||||
Enabled = true, -- if true, crews will run to landed helicopters and board automatically
|
Enabled = true, -- if true, crews will run to landed helicopters and board automatically
|
||||||
MaxDistance = 500, -- meters - max distance crew will detect and run to helicopter
|
MaxDistance = 200, -- meters - max distance crew will detect and run to a helicopter
|
||||||
CrewMoveSpeed = 25, -- meters/second - speed crew runs to helicopter (25 = sprint)
|
CrewMoveSpeed = 25, -- meters/second - speed crew runs to helicopter (25 = sprint)
|
||||||
CheckInterval = 3, -- seconds between checks for landed helicopters
|
CheckInterval = 3, -- seconds between checks for landed helicopters
|
||||||
|
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
|
||||||
},
|
},
|
||||||
|
|
||||||
AutoUnload = {
|
AutoUnload = {
|
||||||
Enabled = true, -- if true, crews automatically unload when landed in MASH zone
|
Enabled = true, -- if true, crews automatically unload when landed in MASH zone
|
||||||
UnloadDelay = 8, -- seconds after landing before auto-unload triggers
|
UnloadDelay = 15, -- seconds after landing before auto-unload triggers
|
||||||
|
},
|
||||||
|
|
||||||
|
EnrouteMessages = {
|
||||||
|
Enabled = true,
|
||||||
|
Interval = 180, -- seconds between in-flight status quips while MEDEVAC patients onboard
|
||||||
},
|
},
|
||||||
|
|
||||||
-- Salvage system
|
-- Salvage system
|
||||||
@ -1134,6 +1246,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 },
|
[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._medevacUnloadStates = CTLD._medevacUnloadStates or {} -- [groupName] = { startTime, delay, holdAnnounced, nextReminder }
|
||||||
|
CTLD._medevacEnrouteStates = CTLD._medevacEnrouteStates or {} -- [groupName] = { nextSend, lastIndex }
|
||||||
|
|
||||||
-- #endregion State
|
-- #endregion State
|
||||||
|
|
||||||
@ -1806,6 +1919,21 @@ local function _getGroundSpeed(unit)
|
|||||||
return math.sqrt(vel.x * vel.x + vel.z * vel.z)
|
return math.sqrt(vel.x * vel.x + vel.z * vel.z)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Calculate height above ground level for a unit (meters)
|
||||||
|
local function _getUnitAGL(unit)
|
||||||
|
if not unit then return math.huge end
|
||||||
|
local pos = unit:GetPointVec3()
|
||||||
|
if not pos then return math.huge end
|
||||||
|
local terrain = 0
|
||||||
|
if land and land.getHeight then
|
||||||
|
local success, h = pcall(land.getHeight, { x = pos.x, y = pos.z })
|
||||||
|
if success and type(h) == 'number' then
|
||||||
|
terrain = h
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return pos.y - terrain
|
||||||
|
end
|
||||||
|
|
||||||
local function _nearestZonePoint(unit, list)
|
local function _nearestZonePoint(unit, list)
|
||||||
if not unit or not unit:IsAlive() then return nil end
|
if not unit or not unit:IsAlive() then return nil end
|
||||||
-- Get unit position using DCS API to avoid dependency on MOOSE point methods
|
-- Get unit position using DCS API to avoid dependency on MOOSE point methods
|
||||||
@ -7865,9 +7993,23 @@ function CTLD:AutoPickupMEDEVACCrew(group)
|
|||||||
|
|
||||||
-- Only work with landed helicopters
|
-- Only work with landed helicopters
|
||||||
if _isUnitInAir(unit) then return end
|
if _isUnitInAir(unit) then return end
|
||||||
|
|
||||||
|
local autoCfg = cfg.AutoPickup
|
||||||
|
local requireGround = (autoCfg.RequireGroundContact ~= false)
|
||||||
|
if requireGround then
|
||||||
|
local agl = _getUnitAGL(unit)
|
||||||
|
if agl > (autoCfg.GroundContactAGL or 3) then
|
||||||
|
return -- still hovering/high skid - wait for full touchdown
|
||||||
|
end
|
||||||
|
local gs = _getGroundSpeed(unit)
|
||||||
|
if gs > (autoCfg.MaxLandingSpeed or 2) then
|
||||||
|
return -- helicopter is sliding/taxiing - hold crews until stable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local pos = unit:GetPointVec3()
|
local pos = unit:GetPointVec3()
|
||||||
local maxDist = cfg.AutoPickup.MaxDistance or 500
|
if not pos then return end
|
||||||
|
local maxDist = autoCfg.MaxDistance or 200
|
||||||
|
|
||||||
-- Find nearby MEDEVAC crews
|
-- Find nearby MEDEVAC crews
|
||||||
for crewGroupName, data in pairs(CTLD._medevacCrews) do
|
for crewGroupName, data in pairs(CTLD._medevacCrews) do
|
||||||
@ -7978,24 +8120,43 @@ function CTLD:ScanMEDEVACAutoActions()
|
|||||||
local group = GROUP:FindByName(gname)
|
local group = GROUP:FindByName(gname)
|
||||||
if group and group:IsAlive() then
|
if group and group:IsAlive() then
|
||||||
local unit = group:GetUnit(1)
|
local unit = group:GetUnit(1)
|
||||||
if unit and unit:IsAlive() and not _isUnitInAir(unit) then
|
if unit and unit:IsAlive() then
|
||||||
-- Helicopter is landed
|
local isAirborne = _isUnitInAir(unit)
|
||||||
|
|
||||||
-- Check for auto-pickup opportunities
|
if not isAirborne then
|
||||||
if cfg.AutoPickup and cfg.AutoPickup.Enabled then
|
-- Helicopter is landed
|
||||||
self:AutoPickupMEDEVACCrew(group)
|
if cfg.AutoPickup and cfg.AutoPickup.Enabled then
|
||||||
end
|
self:AutoPickupMEDEVACCrew(group)
|
||||||
|
end
|
||||||
-- Check for auto-unload opportunities
|
|
||||||
if cfg.AutoUnload and cfg.AutoUnload.Enabled then
|
if cfg.AutoUnload and cfg.AutoUnload.Enabled then
|
||||||
self:AutoUnloadMEDEVACCrew(group)
|
self:AutoUnloadMEDEVACCrew(group)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self:_TickMedevacEnrouteMessage(group, unit, isAirborne)
|
||||||
|
else
|
||||||
|
CTLD._medevacEnrouteStates[gname] = nil
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
CTLD._medevacEnrouteStates[gname] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Finalize unload checks after handling current landings
|
-- Finalize unload checks after handling current landings
|
||||||
self:_UpdateMedevacUnloadStates()
|
self:_UpdateMedevacUnloadStates()
|
||||||
|
|
||||||
|
local enrouteStates = CTLD._medevacEnrouteStates
|
||||||
|
if enrouteStates then
|
||||||
|
for gname, _ in pairs(enrouteStates) do
|
||||||
|
if not (self.MenusByGroup and self.MenusByGroup[gname]) then
|
||||||
|
local group = GROUP:FindByName(gname)
|
||||||
|
if not group or not group:IsAlive() then
|
||||||
|
enrouteStates[gname] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Auto-unload: Automatically unload MEDEVAC crews when landed in MASH zone
|
-- Auto-unload: Automatically unload MEDEVAC crews when landed in MASH zone
|
||||||
@ -8036,6 +8197,76 @@ function CTLD:_CollectRescuedCrewsForGroup(groupName)
|
|||||||
return crews
|
return crews
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Periodically deliver enroute status chatter while MEDEVAC patients are onboard
|
||||||
|
function CTLD:_TickMedevacEnrouteMessage(group, unit, isAirborne, forceSend)
|
||||||
|
local cfg = CTLD.MEDEVAC
|
||||||
|
if not cfg or not cfg.Enabled then return end
|
||||||
|
|
||||||
|
local enrouteCfg = cfg.EnrouteMessages or {}
|
||||||
|
if enrouteCfg.Enabled == false then return end
|
||||||
|
|
||||||
|
if not group or not unit or not unit:IsAlive() then
|
||||||
|
if group then
|
||||||
|
local gname = group:GetName()
|
||||||
|
if gname and gname ~= '' then
|
||||||
|
CTLD._medevacEnrouteStates[gname] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local gname = group:GetName()
|
||||||
|
if not gname or gname == '' then return end
|
||||||
|
|
||||||
|
local crews = self:_CollectRescuedCrewsForGroup(gname)
|
||||||
|
if not crews or #crews == 0 then
|
||||||
|
CTLD._medevacEnrouteStates[gname] = nil
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not isAirborne and not forceSend then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local interval = enrouteCfg.Interval or 180
|
||||||
|
if interval <= 0 then interval = 180 end
|
||||||
|
|
||||||
|
CTLD._medevacEnrouteStates = CTLD._medevacEnrouteStates or {}
|
||||||
|
local now = timer.getTime()
|
||||||
|
local state = CTLD._medevacEnrouteStates[gname]
|
||||||
|
|
||||||
|
if not state then
|
||||||
|
state = { nextSend = now + interval, lastIndex = nil }
|
||||||
|
CTLD._medevacEnrouteStates[gname] = state
|
||||||
|
end
|
||||||
|
|
||||||
|
if not forceSend and now < (state.nextSend or 0) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local vector = self:_ComputeNearestMASHVector(unit)
|
||||||
|
if not vector then return end
|
||||||
|
|
||||||
|
local messages = cfg.EnrouteToMashMessages or {}
|
||||||
|
if #messages == 0 then return end
|
||||||
|
|
||||||
|
local idx = math.random(1, #messages)
|
||||||
|
if state.lastIndex and #messages > 1 and idx == state.lastIndex then
|
||||||
|
idx = (idx % #messages) + 1
|
||||||
|
end
|
||||||
|
state.lastIndex = idx
|
||||||
|
state.nextSend = now + interval
|
||||||
|
|
||||||
|
local text = _fmtTemplate(messages[idx], {
|
||||||
|
mash = vector.name,
|
||||||
|
brg = vector.bearing,
|
||||||
|
rng = vector.rangeValue,
|
||||||
|
rng_u = vector.rangeUnit
|
||||||
|
})
|
||||||
|
|
||||||
|
_msgGroup(group, text, math.min(self.Config.MessageDuration or 15, 18))
|
||||||
|
end
|
||||||
|
|
||||||
-- Ensure an unload hold state exists for the group and announce if newly started
|
-- Ensure an unload hold state exists for the group and announce if newly started
|
||||||
function CTLD:_EnsureMedevacUnloadState(group, mashZone, crews, opts)
|
function CTLD:_EnsureMedevacUnloadState(group, mashZone, crews, opts)
|
||||||
CTLD._medevacUnloadStates = CTLD._medevacUnloadStates or {}
|
CTLD._medevacUnloadStates = CTLD._medevacUnloadStates or {}
|
||||||
@ -8261,6 +8492,11 @@ function CTLD:_HandleMEDEVACPickup(rescueGroup, crewGroupName, crewData)
|
|||||||
vehicle = crewData.vehicleType,
|
vehicle = crewData.vehicleType,
|
||||||
crew_size = crewData.crewSize
|
crew_size = crewData.crewSize
|
||||||
}), 10)
|
}), 10)
|
||||||
|
|
||||||
|
local unit = g:GetUnit(1)
|
||||||
|
if unit and unit:IsAlive() then
|
||||||
|
self:_TickMedevacEnrouteMessage(g, unit, _isUnitInAir(unit), true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Track statistics
|
-- Track statistics
|
||||||
@ -8515,18 +8751,135 @@ function CTLD:_CanUseSalvageForCrate(crateKey, catalogEntry, quantity)
|
|||||||
return available >= salvageCost
|
return available >= salvageCost
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Resolve the 2D position of a MASH zone, handling fixed and mobile variants
|
||||||
|
function CTLD:_ResolveMASHPosition(mashData, mashKey)
|
||||||
|
if not mashData then return nil end
|
||||||
|
|
||||||
|
if mashData.position and mashData.position.x and mashData.position.z then
|
||||||
|
return { x = mashData.position.x, z = mashData.position.z }
|
||||||
|
end
|
||||||
|
|
||||||
|
local zone = mashData.zone
|
||||||
|
if zone then
|
||||||
|
if zone.GetPointVec3 then
|
||||||
|
local ok, vec3 = pcall(function() return zone:GetPointVec3() end)
|
||||||
|
if ok and vec3 then
|
||||||
|
return { x = vec3.x, z = vec3.z }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if zone.GetPointVec2 then
|
||||||
|
local ok, vec2 = pcall(function() return zone:GetPointVec2() end)
|
||||||
|
if ok and vec2 then
|
||||||
|
return { x = vec2.x, z = vec2.y }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if zone.GetCoordinate then
|
||||||
|
local ok, coord = pcall(function() return zone:GetCoordinate() end)
|
||||||
|
if ok and coord then
|
||||||
|
local vec3 = coord.GetVec3 and coord:GetVec3()
|
||||||
|
if vec3 then
|
||||||
|
return { x = vec3.x, z = vec3.z }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if mashKey and trigger and trigger.misc and trigger.misc.getZone then
|
||||||
|
local ok, zoneInfo = pcall(function() return trigger.misc.getZone(mashKey) end)
|
||||||
|
if ok and zoneInfo and zoneInfo.point then
|
||||||
|
return { x = zoneInfo.point.x, z = zoneInfo.point.z }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Find the nearest friendly MASH zone to a given point (x/z expected)
|
||||||
|
function CTLD:_FindNearestMASHForPoint(point)
|
||||||
|
if not point then return nil end
|
||||||
|
|
||||||
|
local nearestName, nearestData, nearestPos
|
||||||
|
local nearestDist = math.huge
|
||||||
|
|
||||||
|
for name, data in pairs(CTLD._mashZones or {}) do
|
||||||
|
if data.side == self.Side then
|
||||||
|
local pos = self:_ResolveMASHPosition(data, name)
|
||||||
|
if pos then
|
||||||
|
local dx = pos.x - point.x
|
||||||
|
local dz = pos.z - point.z
|
||||||
|
local dist = math.sqrt(dx * dx + dz * dz)
|
||||||
|
if dist < nearestDist then
|
||||||
|
nearestDist = dist
|
||||||
|
nearestName = name
|
||||||
|
nearestData = data
|
||||||
|
nearestPos = pos
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not nearestData or not nearestPos then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local displayName = nearestData.displayName or nearestData.catalogKey
|
||||||
|
if not displayName then
|
||||||
|
local zone = nearestData.zone
|
||||||
|
if zone and zone.GetName then
|
||||||
|
local ok, zname = pcall(function() return zone:GetName() end)
|
||||||
|
if ok and zname then
|
||||||
|
displayName = zname
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
displayName = displayName or nearestName or 'MASH'
|
||||||
|
|
||||||
|
return {
|
||||||
|
name = displayName,
|
||||||
|
position = nearestPos,
|
||||||
|
distance = nearestDist,
|
||||||
|
data = nearestData,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build directional info toward the nearest MASH for a specific unit
|
||||||
|
function CTLD:_ComputeNearestMASHVector(unit)
|
||||||
|
if not unit or not unit:IsAlive() then return nil end
|
||||||
|
local pos = unit:GetPointVec3()
|
||||||
|
if not pos then return nil end
|
||||||
|
|
||||||
|
local info = self:_FindNearestMASHForPoint({ x = pos.x, z = pos.z })
|
||||||
|
if not info or not info.position then return nil end
|
||||||
|
|
||||||
|
local bearing = _bearingDeg({ x = pos.x, z = pos.z }, info.position)
|
||||||
|
local isMetric = _getPlayerIsMetric(unit)
|
||||||
|
local rangeValue, rangeUnit = _fmtRange(info.distance, isMetric)
|
||||||
|
|
||||||
|
if rangeUnit == 'm' and rangeValue >= 1000 then
|
||||||
|
rangeValue = _round(rangeValue / 1000, 1)
|
||||||
|
rangeUnit = 'km'
|
||||||
|
end
|
||||||
|
|
||||||
|
local valueText
|
||||||
|
if math.abs(rangeValue - math.floor(rangeValue)) < 0.05 then
|
||||||
|
valueText = string.format('%d', math.floor(rangeValue + 0.5))
|
||||||
|
else
|
||||||
|
valueText = string.format('%.1f', rangeValue)
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
name = info.name,
|
||||||
|
bearing = bearing,
|
||||||
|
rangeValue = valueText,
|
||||||
|
rangeUnit = rangeUnit,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
-- Check if position is inside any MASH zone
|
-- Check if position is inside any MASH zone
|
||||||
function CTLD:_IsPositionInMASHZone(position)
|
function CTLD:_IsPositionInMASHZone(position)
|
||||||
for zoneName, mashData in pairs(CTLD._mashZones) do
|
for zoneName, mashData in pairs(CTLD._mashZones) do
|
||||||
if mashData.side == self.Side then
|
if mashData.side == self.Side then
|
||||||
local zone = mashData.zone
|
local zonePos = self:_ResolveMASHPosition(mashData, zoneName)
|
||||||
local zonePos = nil
|
|
||||||
if zone and zone.GetPointVec3 then
|
|
||||||
zonePos = zone:GetPointVec3()
|
|
||||||
end
|
|
||||||
if (not zonePos) and mashData.position then
|
|
||||||
zonePos = { x = mashData.position.x, y = 0, z = mashData.position.z }
|
|
||||||
end
|
|
||||||
if zonePos then
|
if zonePos then
|
||||||
local radius = mashData.radius or CTLD.MEDEVAC.MASHZoneRadius or 500
|
local radius = mashData.radius or CTLD.MEDEVAC.MASHZoneRadius or 500
|
||||||
local dx = position.x - zonePos.x
|
local dx = position.x - zonePos.x
|
||||||
|
|||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user