3077 lines
115 KiB
Lua

-- ============================================================================
-- MOOSE TANKER MANAGEMENT SYSTEM
-- Comprehensive tanker lifecycle management with auto-respawn, fuel monitoring,
-- TACAN/frequency announcements, and menu controls
-- ============================================================================
-- ============================================================================
-- USER CONFIGURATION
-- ============================================================================
-- Tanker Configuration
local TANKER_CONFIG = {
KC135 = {
groupName = "TANKER 135",
unitName = "TANKER 135-1",
displayName = "TANKER KC-135",
aircraftType = "KC-135", -- DCS aircraft type name
livery = nil, -- nil for default, or livery_id string
callsign = "SHELL", -- Map marker prefix for custom routes
tacan = "50X", -- Set to match ME or nil if none
frequency = "251.000", -- Set to match ME or nil if none
respawnDelay = 180, -- seconds before auto-respawn after destruction
emergencyRespawnDelay = 60, -- Emergency spawn delay
fuelWarningPercent = 25, -- Warn when fuel drops below this %
fuelBingoPercent = 15, -- RTB fuel level
defaultAltitude = 22000, -- Default altitude in feet (FL220)
defaultSpeed = 330, -- Default speed in knots
},
KC135_MPRS = {
groupName = "TANKER 135 MPRS",
unitName = "TANKER 135 MPRS-1",
displayName = "TANKER KC-135 MPRS",
aircraftType = "KC135MPRS", -- DCS aircraft type name
livery = nil,
callsign = "ARCO", -- Map marker prefix for custom routes
tacan = "51X",
frequency = "252.000",
respawnDelay = 180,
emergencyRespawnDelay = 60,
fuelWarningPercent = 25,
fuelBingoPercent = 15,
defaultAltitude = 22000,
defaultSpeed = 330,
}
}
-- Custom Route Configuration
local ROUTE_CONFIG = {
minWaypoints = 2, -- Minimum waypoints required
maxWaypoints = 10, -- Maximum waypoints allowed
deleteMarkersAfterUse = true, -- Delete markers after route creation
waypointPrefix = { -- Recognized marker prefixes
SHELL = "KC135", -- SHELL1, SHELL2, etc. → KC-135
ARCO = "KC135_MPRS", -- ARCO1, ARCO2, etc. → KC-135 MPRS
}
}
-- Emergency Tanker Configuration
local EMERGENCY_CONFIG = {
minAltitudeFeet = 10000, -- Minimum altitude for emergency spawn (10k ft)
maxAltitudeFeet = 45000, -- Maximum altitude for emergency spawn (45k ft)
spawnDistanceKM = 5, -- Distance ahead of player to spawn tanker (5km)
waypointDistanceNM = 100, -- Distance for egress waypoint (100nm)
markerKeywords = { -- Recognized emergency marker keywords (case insensitive)
"EMERGENCY TANKER",
"EMERGENCY KC135",
"EMERGENCY MPRS",
"EMERGENCY"
},
markerSearchRadius = 50000, -- Search for markers within 50km of players (meters)
}
-- Monitoring Configuration
local FUEL_CHECK_INTERVAL = 60 -- Check fuel every 60 seconds
local DAMAGE_RTB_THRESHOLD = 50 -- RTB if hull damage exceeds this %
-- Default Spawn Location (for non-custom route spawns)
-- Note: Using lat/lon with SetAltitude to ensure proper altitude MSL
local DEFAULT_SPAWN_COORD = COORDINATE:NewFromLLDD(34.564, 69.212):SetAltitude(22000 * 0.3048, true) -- Kabul area, FL220
-- ============================================================================
-- GLOBAL STATE TRACKING
-- ============================================================================
TANKER_STATE = {
KC135 = {
active = false,
group = nil,
dcsGroupName = nil,
fuelWarned = false,
bingoWarned = false,
respawnScheduler = nil,
fuelMonitor = nil,
},
KC135_MPRS = {
active = false,
group = nil,
dcsGroupName = nil,
fuelWarned = false,
bingoWarned = false,
respawnScheduler = nil,
fuelMonitor = nil,
}
}
local UNIQUE_NAME_COUNTER = 0
local function NextUniqueIndex()
UNIQUE_NAME_COUNTER = UNIQUE_NAME_COUNTER + 1
return UNIQUE_NAME_COUNTER
end
local function GenerateGroupName(base, index)
return string.format("%s #%03d", base, index)
end
local function GenerateUnitName(base, index)
return string.format("%s-%03d", base, index)
end
-- ============================================================================
-- MENU REFERENCES (for enable/disable)
-- ============================================================================
local MENU_TANKER_ROOT = nil
-- ============================================================================
-- MESSAGE POOLS FOR VARIETY
-- Randomized messages provide immersive variety across tanker operations.
-- Each category contains 100 variations selected randomly via GetRandomMessage()
-- ============================================================================
local TANKER_MESSAGES = {
-- Spawn Confirmation (success)
SPAWN_SUCCESS = {
"%s is airborne and ready for refueling operations.",
"%s has launched and is standing by for fuel.",
"%s is now on station and ready to pump gas.",
"%s has departed and is available for refueling.",
"%s is up and ready to service aircraft.",
"%s is airborne. Refueling services now available.",
"%s has checked in on station.",
"%s is overhead and ready for business.",
"%s is now available for aerial refueling.",
"%s has arrived on station. Ready to refuel.",
"%s is up! Time to get your drink on.",
"%s has joined the party. Bring your cups!",
"%s reporting. The bar is now open.",
"%s is flying. Get in line for your juice.",
"%s on station. Don't be shy, we got plenty.",
"%s airborne. Unlike Mo's last attempt at flying.",
"%s has successfully launched. No thanks to Mo.",
"%s is ready. Mo said he could do this but we know better.",
"%s in position. Fuel truck of the sky is open for business!",
"%s has arrived fashionably late but ready to pump.",
"%s checking in. Your gas station with wings is here.",
"%s is up there doing tanker things.",
"%s launched without hitting anything. Good start!",
"%s airborne and hasn't broken anything yet.",
"%s is ready to make it rain... JP-8.",
"%s in the pattern. Come get some dinosaur juice!",
"%s reporting for duty. Time to feed some thirsty birds.",
"%s has spawned successfully. Mo's jealous.",
"%s is flying high and ready to share the wealth.",
"%s on station. Dispensary is OPEN.",
"%s has graced you with its presence. You're welcome.",
"%s is here to save your ass from flameout.",
"%s launched. The sky gas station is open 24/7.",
"%s airborne. Better than Mo's last tanker spawn attempt.",
"%s ready to refuel. Unlike your love life, this actually works.",
"%s has arrived to keep you from embarrassing yourself.",
"%s on station and totally not judging your fuel planning.",
"%s is up. Try not to break the boom this time.",
"%s launched successfully. Mo couldn't get his off the ground.",
"%s airborne. Your aerial bartender has arrived!",
"%s ready for action. The juice is loose!",
"%s has spawned. Time to get wet... with fuel.",
"%s on station. We promise not to tell anyone you needed us.",
"%s reporting. Because someone forgot to fuel before takeoff.",
"%s is here! The flying fuel truck has arrived!",
"%s airborne and ready to fill your tanks. That's what she said.",
"%s launched. Even Mo could refuel from this... maybe.",
"%s on station. Your poor planning is our opportunity!",
"%s has arrived. The aerial milk truck is ready.",
"%s ready to pump. Get your minds out of the gutter.",
"%s airborne because you can't manage fuel apparently.",
"%s is up there waiting. Don't keep us hovering forever.",
"%s has joined the fight. By 'fight' we mean 'hovering lazily.'",
"%s on station. Premium unleaded is on tap!",
"%s launched and looking sexy up here.",
"%s ready to refuel. Try not to scratch the paint this time.",
"%s has arrived. Mo said this was impossible but here we are.",
"%s airborne. The sky's full service station is open!",
"%s on station ready to save your bacon.",
"%s has launched into the wild blue yonder!",
"%s reporting for gas pumping duty.",
"%s is up and Mo isn't. Winner: us.",
"%s airborne. Fuel flows like wine at a wedding!",
"%s on station. Unlike Mo, we actually showed up.",
"%s ready to top you off. No phrasing.",
"%s has successfully taken off. Mo's still taxiing.",
"%s airborne and operational. The real MVP.",
"%s on station doing the lord's work.",
"%s has arrived to prevent your walk of shame.",
"%s ready for refueling ops. Try to connect this time.",
"%s launched because someone has to be the adult here.",
"%s airborne. Probably more reliable than your ex.",
"%s on station ready to give you the good stuff.",
"%s has spawned. Mo's tanker is still in the hangar.",
"%s reporting. Your airborne gas station awaits!",
"%s is up! Time for some hot refueling action.",
"%s airborne and Mo's not invited to this party.",
"%s on station. We have fuel, you have need. Let's dance.",
"%s ready to dispense freedom molecules!",
"%s has arrived to fix your fuel management issues.",
"%s launched. The flying gas can is ready for customers.",
"%s airborne because apparently nobody can calculate bingo.",
"%s on station. Come get your fix!",
"%s ready to pump premium into your thirsty bird.",
"%s has spawned successfully. Suck it, Mo.",
"%s reporting. Your aerial enabler is on station.",
"%s is up there waiting like a patient parent.",
"%s airborne and ready to make your fuel gauge happy.",
"%s on station. Mo said we couldn't do it. We did it.",
"%s launched with more grace than Mo's last landing.",
"%s ready for business. The boom is ready to boom.",
"%s has arrived fashionably and ready to serve.",
"%s airborne. Your fuel problems are about to be solved!",
"%s on station doing God's work up here.",
"%s ready to refuel. We got the good stuff.",
"%s has spawned. Time to feed the hungry jets!",
"%s reporting for duty with full tanks!",
"%s launched successfully without Mo's help, thank God.",
"%s airborne. The flying filling station is OPEN!",
},
-- Taxi Phase
TAXI = {
"%s is taxiing to the runway.",
"%s has started taxi procedures.",
"%s is rolling to the active runway.",
"%s beginning taxi operations.",
"%s is on the move to takeoff position.",
"%s taxiing for departure.",
"%s proceeding to runway.",
"%s has commenced taxi.",
"%s is rolling out for takeoff.",
"%s taxiing to active runway now.",
"%s is taxi-ing. Unlike Mo who got lost on the taxiway.",
"%s has started moving. Mo would've hit something by now.",
"%s rolling to the runway. Try not to watch, it's boring.",
"%s is taxi-ing. This is taking forever but we're getting there.",
"%s proceeding to runway. Mo's still trying to start the engines.",
"%s on the move. Slowly. Very slowly.",
"%s taxiing out. Don't hold your breath, this takes a while.",
"%s rolling to takeoff position. Mo crashed into a light pole doing this.",
"%s has begun taxi. Yes, it's as exciting as it sounds.",
"%s proceeding to departure runway. Mo would be lost already.",
"%s is moving... eventually we'll get there.",
"%s taxiing now. Mo said this was the hardest part. He's not wrong.",
"%s rolling out. This ain't a drag race, it's gonna take a minute.",
"%s is on the taxi. Mo took a wrong turn and ended up at the terminal.",
"%s proceeding to active. At least we're not hitting anything.",
"%s has started taxi. Mo managed to taxi into a ditch once.",
"%s rolling to runway. This is about as exciting as watching paint dry.",
"%s taxiing for departure. Mo's still trying to figure out the brakes.",
"%s is moving. Barely. But we're moving.",
"%s proceeding to takeoff position. Mo would've needed a tow truck by now.",
"%s has commenced taxi operations. Don't get too excited.",
"%s rolling out. Mo said he could taxi backwards. He was wrong.",
"%s is taxiing. Yes, it's taking this long. Deal with it.",
"%s on the move to runway. Mo's still reading the taxi diagram.",
"%s proceeding to active. At least we know where we're going. Mo didn't.",
"%s taxiing now. Mo thought the taxiway was the runway. Classic Mo.",
"%s rolling to departure. This is why we don't let Mo taxi.",
"%s has started moving. Mo would've requested a ground abort by now.",
"%s taxiing to runway. Mo once taxied into the grass. Good times.",
"%s proceeding to takeoff. Mo asked for taxi clearance three times. For the same runway.",
"%s is on the move. Mo's still looking for the parking brake release.",
"%s rolling out for departure. Mo managed to get a flat tire taxiing once.",
"%s taxiing to active runway. At least we're not Mo.",
"%s proceeding to position. Mo would need vectors to the runway.",
"%s has begun taxi. Mo's idea of taxiing involved several near misses.",
"%s rolling to runway. Mo once followed the wrong aircraft and ended up lost.",
"%s is moving to departure. Mo's still trying to read the taxi chart upside down.",
"%s taxiing now. Mo thought ATC said 'taxi to parking' when they said 'runway.'",
"%s proceeding to runway. This is taking forever but at least we're not crashed.",
"%s has started taxi. Mo's version involved more screaming.",
},
-- Takeoff Phase
TAKEOFF = {
"%s is rolling for takeoff!",
"%s departing runway now!",
"%s is airborne and climbing!",
"%s has lifted off!",
"%s wheels up and departing!",
"%s is taking off now!",
"%s airborne from runway!",
"%s has departed!",
"%s is climbing out!",
"%s wheels up, departure in progress!",
"%s is taking off! Mo's still on the taxiway.",
"%s wheels up! Unlike Mo's last three attempts.",
"%s airborne! Mo would've aborted by now.",
"%s has lifted off! Mo's probably jealous.",
"%s departing! At least we're using the runway, unlike Mo.",
"%s rolling for takeoff! Mo once tried this on a taxiway.",
"%s is airborne! Mo said this was impossible. He was wrong.",
"%s wheels up! Mo's takeoff looked more like a lawn dart.",
"%s climbing out! Mo's still trying to rotate.",
"%s has departed! Mo would've hit something by now.",
"%s is taking off! Mo's version involved more screaming and fire.",
"%s airborne and climbing! Mo never made it past ground roll.",
"%s wheels up! Mo thought you needed full flaps for this.",
"%s departing runway! Mo once forgot to release the parking brake.",
"%s has lifted off! Mo's takeoff was more of a controlled crash.",
"%s is climbing! Mo would've stalled by now.",
"%s airborne! Mo once took off with the tow bar still attached.",
"%s wheels up and departing! Mo's still calculating V1.",
"%s taking off! Mo's last takeoff involved ATC screaming.",
"%s has departed! Mo would've gone off the side of the runway.",
"%s is airborne! Mo thought you could skip the takeoff roll.",
"%s climbing out! Mo's still figuring out which way is up.",
"%s wheels up! Mo once rotated at 50 knots. It didn't work.",
"%s departing! Mo's takeoff technique was 'hope for the best.'",
"%s has lifted off! Mo's version involved the tower yelling 'ABORT!'",
"%s is taking off! Mo once forgot to retract the gear. For 20 minutes.",
"%s airborne and climbing! Mo's takeoff looked like a drunk pelican.",
"%s wheels up! Mo thought you needed afterburner for this. In a tanker.",
"%s departing runway! Mo once tried to take off downwind.",
"%s has lifted off successfully! Mo's success rate was approximately zero.",
"%s is climbing! Mo would've hit the ILS antenna by now.",
"%s airborne! Mo's takeoff checklist was 'close eyes and pray.'",
"%s wheels up! Mo once departed the runway sideways. Somehow.",
"%s taking off! Mo's technique was more crash than takeoff.",
"%s has departed! Mo would still be bouncing down the runway.",
"%s is airborne! Mo thought V2 was a type of engine oil.",
"%s climbing out! Mo's last takeoff involved emergency vehicles.",
"%s wheels up and climbing! Mo would've forgotten to raise the gear.",
"%s departing! Mo once took off in the wrong direction. Tower was not pleased.",
"%s has lifted off! Mo's takeoffs usually ended with paperwork.",
},
-- Enroute to Station
ENROUTE = {
"%s enroute to station.",
"%s proceeding to waypoint 1.",
"%s is flying to the refueling track.",
"%s heading to station now.",
"%s enroute to first waypoint.",
"%s proceeding to patrol area.",
"%s flying to assigned station.",
"%s is inbound to refueling track.",
"%s heading to the pattern.",
"%s enroute to operational area.",
"%s is flying to station. ETA: when we get there.",
"%s proceeding to track. Mo's still trying to read the map.",
"%s enroute. Mo would be lost by now.",
"%s heading to waypoint 1. Mo would've missed it.",
"%s inbound to station. Mo once flew the wrong direction for 30 minutes.",
"%s proceeding to pattern. At least we're going the right way.",
"%s enroute to track. Mo's GPS shows him in a different country.",
"%s flying to station. Mo would need vectors. Lots of vectors.",
"%s heading to refueling area. Mo thought the refueling area was the ground.",
"%s inbound now. Mo would've requested a tanker by now. Wait...",
"%s proceeding to waypoint. Mo's version of navigation was 'turn until it looks right.'",
"%s enroute to station. Mo once navigated by following highways.",
"%s flying to track. Mo would be orbiting the airfield asking for help.",
"%s heading to pattern. Mo thought patterns were for quilts.",
"%s inbound to area. Mo's navigation skills were 'find it eventually.'",
"%s proceeding to station. Mo needed GPS, TACAN, and a seeing-eye dog.",
"%s enroute now. Mo would've declared an emergency for being lost.",
"%s flying to waypoint 1. Mo would be at waypoint 7 somehow.",
"%s heading to track. Mo once flew in circles for an hour looking for his assigned area.",
"%s inbound to station. Mo's navigation was sponsored by 'hope and prayer.'",
"%s proceeding to area. Mo would need a search and rescue team by now.",
"%s enroute to pattern. Mo thought autopilot meant 'automatic navigation.'",
"%s flying to station. Mo once mistook a lake for his waypoint.",
"%s heading to track. Mo's sense of direction was 'non-existent.'",
"%s inbound now. Mo would be requesting divert fuel.",
"%s proceeding to waypoint. Mo's GPS once told him to turn around. He argued with it.",
"%s enroute to station. Mo thought following the road was acceptable IFR navigation.",
"%s flying to area. Mo would be overhead the wrong country by now.",
"%s heading to track. Mo once asked 'which way is north?'",
"%s inbound to pattern. Mo's navigation checklist was one item: 'get lost.'",
"%s proceeding to station. Mo would've run out of fuel looking for it.",
"%s enroute now. Mo thought the GPS coordinates were a phone number.",
"%s flying to waypoint. Mo would be flying in the opposite direction with confidence.",
"%s heading to station. Mo once flew an entire mission backwards.",
"%s inbound to track. Mo's navigation was 'fly until you see something familiar.'",
"%s proceeding to area. Mo would need a road map and a miracle.",
"%s enroute to station. Mo thought TACAN stood for 'Totally Can't Navigate.'",
"%s flying to pattern. Mo once mistook a mountain for a waypoint. He was at sea level.",
"%s heading to track. Mo's idea of navigation was 'point and hope.'",
"%s inbound now. Mo would be declaring minimum fuel asking where the hell he is.",
},
-- On Station (Arriving at Pattern)
ON_STATION = {
"%s is on station and ready for refueling!",
"%s has arrived at the refueling track!",
"%s is established in the pattern!",
"%s is now on station, ready to service aircraft!",
"%s has reached the patrol area and is ready!",
"%s is on station, refueling operations active!",
"%s has arrived and is ready to pump gas!",
"%s is established on track, ready for business!",
"%s is on station, come get your fuel!",
"%s has reached the pattern and is operational!",
"%s is on station! Mo never made it this far.",
"%s has arrived! Mo would still be enroute asking for help.",
"%s is established in the pattern! Mo would be lost.",
"%s is on station ready to pump! Mo's still trying to find the area.",
"%s has reached the track! Mo thought the track was a NASCAR thing.",
"%s is operational! Mo would've requested emergency fuel by now.",
"%s has arrived and ready! Mo's idea of 'on station' was 'vaguely nearby.'",
"%s is on track pumping gas! Mo would be circling aimlessly.",
"%s is established! Mo would still be 50 miles off asking for vectors.",
"%s has reached station! Mo once declared on station at the wrong airbase.",
"%s is ready for refueling! Mo would've forgotten why he came.",
"%s is on station! Mo's version involved being 'kinda close.'",
"%s has arrived! Mo would still be arguing with his GPS.",
"%s is established and ready! Mo thought on station meant parked at the gate.",
"%s is operational now! Mo would've flown past it twice.",
"%s has reached the pattern! Mo's pattern was more of a random squiggle.",
"%s is on station! Mo would be declaring minimum fuel trying to find it.",
"%s is ready to pump! Mo once showed up to the wrong station. In the wrong country.",
"%s has arrived at track! Mo thought arriving meant 'within visual range of something.'",
"%s is on station pumping! Mo would've given up and gone home.",
"%s is established! Unlike Mo who was never established anywhere.",
"%s has reached station! Mo's idea of on station was 'I think I'm close?'",
"%s is operational! Mo once declared on station while still on the ground.",
"%s is ready for service! Mo would be requesting a tanker. The irony.",
"%s has arrived! Mo's version of arriving was 'eventually, maybe.'",
"%s is on station! Mo would still be trying to program the waypoint.",
"%s is established and pumping! Mo's station-keeping was 'fucking nonexistent.'",
"%s has reached track! Mo once orbited the wrong mountain for an hour.",
"%s is operational now! Mo would've hit bingo looking for the station.",
"%s is on station ready! Mo's readiness was 'perpetually unprepared.'",
"%s has arrived at pattern! Mo thought the pattern was optional.",
"%s is on track! Mo would be on the wrong track. Or off all tracks.",
"%s is established! Mo's establishment was 'loosely interpreted.'",
"%s has reached station! Mo once mistook a cloud for his station.",
"%s is operational! Mo would be squawking 7700 lost and confused.",
"%s is on station pumping gas! Mo never made it past the departure airport.",
"%s has arrived! Mo's arrival time was 'never.'",
"%s is ready for refueling ops! Mo would still be reading the mission brief.",
"%s is on station! Mo thought on station meant 'near enough.'",
"%s is established! Mo's idea of established was 'I give up, I'm here now.'",
},
-- Already Active Warning
ALREADY_ACTIVE = {
"%s is already airborne!",
"%s is currently active.",
"%s is already on station.",
"%s is already flying. Check status for details.",
"%s is already up - can't spawn another.",
"%s is currently operating.",
"Cannot spawn - %s already active.",
"%s is already out there!",
"%s already flying. One at a time, please.",
"%s is already working the pattern.",
"%s is already up there, genius.",
"Dude, %s is ALREADY flying. Pay attention.",
"%s is currently active. Are you even looking?",
"Hey Einstein, %s is already airborne!",
"%s is up there right now. Use your eyes.",
"What part of '%s is active' don't you understand?",
"%s is already flying. Did Mo program this button?",
"Seriously? %s is already up. Check your radar.",
"%s is currently operational. Nice try though.",
"Negative. %s is already in the air.",
"%s is already active. Mo would have known that.",
"Can't spawn two, buttercup. %s is already flying.",
"%s is already out there doing tanker things.",
"Nice try. %s is already airborne, hotshot.",
"%s is currently flying. One's enough.",
"Hold your horses! %s is already active.",
"%s is already up. We're not running a bus service here.",
"Bruh. %s is already flying.",
"%s is currently active. Maybe learn to read?",
"Negative ghostrider. %s is already up.",
"%s is already airborne. Unlike your awareness.",
"Um, %s is ALREADY flying. Hello?",
"%s is currently on station. Wake up.",
"You can't spawn %s twice. Physics doesn't work that way.",
"%s is already active. Not sure what you expected.",
"Denied! %s is already in the pattern.",
"%s is currently flying around. Look outside.",
"News flash: %s is already airborne!",
"%s is already up there. Mo makes better decisions than this.",
"Request denied. %s is already active, chief.",
"%s is currently operational. Check your instruments.",
"Already got one! %s is flying right now.",
"%s is already airborne. Reading is fundamental.",
"Uh, no. %s is currently active.",
"%s is already out there. Situational awareness: zero.",
"Can't spawn %s again. Not a video game, buddy.",
"%s is currently flying. We only get one.",
"That's a negative. %s is already up.",
"%s is already on station. Did you even check?",
"Seriously? %s has been flying for 20 minutes.",
"%s is already active. This isn't rocket science.",
"Request rejected. %s is currently airborne.",
"%s is already up there pumping gas. Pay attention!",
"Nope. %s is already flying. Check the status board.",
"%s is currently active. Even Mo knew this.",
"Cannot comply. %s is already operational.",
"%s is already airborne. Try the status menu next time.",
"That's a no-go. %s is currently flying.",
"%s is already up. Did you think we had two?",
"Denied. %s is already on station doing its thing.",
"%s is currently active. Surprised you didn't notice.",
"Can't do it. %s is already flying around up there.",
"%s is already operational. One tanker at a time, pal.",
"Negative. %s has been active for a while now.",
"%s is already up there. Spawn button isn't a toy.",
"Request denied. %s is currently on station.",
"%s is already flying. Not cloning aircraft today.",
"Can't spawn another. %s is already airborne.",
"%s is currently active. Stop button mashing.",
"That's not happening. %s is already up.",
"%s is already operational. One's all you get.",
"No can do. %s is already in the pattern.",
"%s is currently flying. Check before clicking, maybe?",
"Request rejected. %s is already on duty.",
"%s is already airborne. Unlike your attention span.",
"Nope! %s is currently active and doing fine.",
"%s is already up there. Stop spamming the spawn button.",
"Cannot spawn duplicate. %s is already flying.",
"%s is currently operational. Mo's spawn would work better.",
"Denied! %s is already on station, genius.",
"%s is already flying. Check your tanker status!",
"That's a negative. %s is currently active.",
"%s is already airborne. One tanker per customer.",
"Can't do that. %s is already up and working.",
"%s is currently on station. Read the room.",
"Request denied. %s is already operational, chief.",
"%s is already active. Try paying attention.",
"No dice. %s is already flying the pattern.",
"%s is currently airborne. Spawn limit: 1.",
"Negative. %s is already up there doing tanker stuff.",
"%s is already active. Maybe check the status screen?",
"Can't spawn %s again. We're not made of tankers here.",
"%s is currently flying. One at a time, hotshot.",
"Request rejected. %s is already on station.",
"%s is already operational. Even Mo knows you only get one.",
"That's not possible. %s is currently airborne.",
"%s is already up there. Better situational awareness needed.",
"Denied. %s is currently active and wondering why you asked.",
"%s is already flying. The spawn button isn't for spam.",
"Cannot comply. %s is already operational, Einstein.",
},
-- Spawn Failure
SPAWN_FAILURE = {
"Failed to spawn %s!",
"Unable to launch %s. Try again.",
"%s spawn aborted!",
"Cannot spawn %s at this time.",
"%s failed to launch!",
"Error spawning %s. Contact support.",
"%s launch unsuccessful.",
"Unable to activate %s. Retry required.",
"%s spawn failed. Check logs.",
"Launch failure for %s!",
"%s spawn went sideways. Oops.",
"Well that didn't work. %s failed to spawn.",
"%s couldn't get off the ground. Awkward.",
"Houston, we have a problem. %s didn't spawn.",
"%s spawn failed harder than Mo's last landing.",
"Oof. %s spawn went to hell.",
"%s launch aborted. This is embarrassing.",
"Yeah, %s didn't spawn. Our bad.",
"Spawn failed for %s. Not our finest moment.",
"%s couldn't launch. Try again, genius.",
"That's a big negative on %s spawn.",
"%s failed to spawn. Did Mo write this code?",
"Error: %s spawn went boom. The bad kind.",
"%s launch unsuccessful. Better luck next time.",
"Spawn failed. %s is still in the hangar.",
"%s didn't want to fly today apparently.",
"Well crap. %s spawn totally failed.",
"%s launch aborted. Something broke.",
"That didn't work. %s spawn failed miserably.",
"%s couldn't spawn. Technical difficulties.",
"Negative spawn for %s. Try again maybe?",
"%s spawn went tits up. Sorry.",
"Launch failure! %s is grounded.",
"%s spawn crashed and burned. Not literally.",
"Unable to spawn %s. Computer says no.",
"%s launch failed. Mo could have done better.",
"Spawn error for %s. This is awkward.",
"%s didn't spawn. The universe said no.",
"Failed to launch %s. Not our day.",
"%s spawn aborted. Probably for the best.",
"Yeah... %s spawn didn't happen.",
"%s failed to spawn. Check your setup.",
"Spawn unsuccessful for %s. Womp womp.",
"%s launch went south. Way south.",
"That's a no-go. %s failed to spawn.",
"%s spawn error. Better call tech support.",
"Launch failure! %s stayed on the ground.",
"%s couldn't spawn. Even we're confused.",
"Spawn failed for %s. Mo's laughing right now.",
"%s launch aborted. Something went wrong.",
"Unable to activate %s. Try turning it off and on again.",
"%s spawn went nowhere fast.",
"Failed spawn alert: %s is still parked.",
"%s launch unsuccessful. This is fine. Everything's fine.",
"Spawn error for %s. Not ideal.",
"%s couldn't get airborne. Rough.",
"Launch aborted. %s is taking a day off.",
"%s spawn failed spectacularly.",
"Cannot spawn %s. System said 'nah.'",
"%s launch went sideways. Try again.",
"Spawn failure! %s is grounded indefinitely.",
"%s didn't spawn. Murphy's Law in effect.",
"Failed to launch %s. Mo's spawn worked better.",
"%s spawn unsuccessful. Check the logs.",
"Error spawning %s. This shouldn't happen.",
"%s launch aborted. Technical difficulties ahead.",
"Spawn failed. %s is staying home today.",
"%s couldn't spawn. Better luck next time, champ.",
"Launch failure for %s. Not sure why.",
"%s spawn went wrong. Very wrong.",
"Unable to spawn %s. Try again later.",
"%s launch failed. Mo would be disappointed.",
"Spawn error: %s didn't make it.",
"%s failed to spawn. Computer threw a tantrum.",
"Launch aborted for %s. Sorry about that.",
"%s spawn unsuccessful. Try again maybe?",
"Cannot activate %s. Spawn failed.",
"%s launch went nowhere. Like Mo's career.",
"Spawn failure! %s is MIA.",
"%s couldn't spawn. System error.",
"Failed to launch %s. This is awkward.",
"%s spawn aborted. Not today, apparently.",
"Launch error for %s. Check your setup.",
"%s didn't spawn. Computer says no way.",
"Spawn unsuccessful. %s is grounded.",
"%s launch failed. Mo's code was better.",
"Cannot spawn %s. Technical issues.",
"%s failed to activate. Try again.",
"Launch aborted. %s spawn went south.",
"%s spawn error. This isn't good.",
"Failed to spawn %s. Maybe next time.",
"%s launch unsuccessful. Something broke.",
"Spawn failure for %s. Not our best work.",
"%s couldn't get off the ground. Awkward moment.",
"Launch error! %s is still parked.",
"%s spawn went wrong. Very, very wrong.",
"Unable to spawn %s. System malfunction.",
"%s launch failed harder than expected.",
"Spawn aborted. %s is taking a sick day.",
},
-- Custom Route Accepted
ROUTE_ACCEPTED = {
"%s accepting custom route with %d waypoints.%s",
"%s has your route. %d waypoints loaded.%s",
"%s acknowledges custom flight plan. %d waypoints.%s",
"%s route confirmed. %d waypoints programmed.%s",
"%s copy your route. %d waypoints accepted.%s",
"%s roger. %d waypoint route loaded.%s",
"%s has the route. %d points confirmed.%s",
"%s flight plan accepted. %d waypoints.%s",
"%s confirms route. %d waypoints in the box.%s",
"%s routing confirmed with %d waypoints.%s",
"%s has your custom route. %d waypoints loaded.%s",
"%s accepts your flight plan. %d points confirmed.%s",
"%s copies custom route with %d waypoints.%s",
"%s acknowledges %d waypoint route.%s",
"%s route programmed. %d waypoints locked in.%s",
"%s flight plan confirmed with %d points.%s",
"%s roger your route. %d waypoints loaded.%s",
"%s accepts %d waypoint custom plan.%s",
"%s has your %d waypoint route locked in.%s",
"%s confirms %d waypoint flight plan.%s",
"%s copy that. %d waypoint route programmed.%s",
"%s routing accepted. %d points confirmed.%s",
"%s has the route. %d waypoints ready.%s",
"%s acknowledges %d point route.%s",
"%s flight plan loaded with %d waypoints.%s",
"%s custom route confirmed. %d points.%s",
"%s accepts your %d waypoint plan.%s",
"%s roger. %d waypoints programmed.%s",
"%s has your %d waypoint custom route.%s",
"%s routing confirmed with %d points.%s",
"%s copies %d waypoint route. Unlike Mo's attempt.%s",
"%s accepts your custom %d waypoint plan.%s",
"%s has loaded %d waypoint route.%s",
"%s confirms %d waypoint routing.%s",
"%s flight plan locked in. %d waypoints.%s",
"%s roger your %d waypoint route.%s",
"%s accepts custom route with %d points.%s",
"%s has programmed %d waypoint plan.%s",
"%s acknowledges %d waypoint custom route.%s",
"%s routing confirmed. %d waypoints ready.%s",
"%s copies %d waypoint flight plan.%s",
"%s accepts your %d point custom route.%s",
"%s has %d waypoint route confirmed.%s",
"%s roger custom plan with %d waypoints.%s",
"%s routing accepted. %d points programmed.%s",
"%s flight plan confirmed. %d waypoints loaded.%s",
"%s has your custom %d waypoint routing.%s",
"%s accepts %d waypoint plan.%s",
"%s confirms custom route. %d waypoints.%s",
"%s roger that. %d waypoint route accepted.%s",
"%s has %d waypoint custom plan loaded.%s",
"%s acknowledges %d point custom route.%s",
"%s routing programmed. %d waypoints confirmed.%s",
"%s flight plan accepted with %d points.%s",
"%s copies your %d waypoint custom route.%s",
"%s has %d waypoint route ready.%s",
"%s accepts custom plan. %d waypoints.%s",
"%s confirms %d point flight plan.%s",
"%s roger custom %d waypoint route.%s",
"%s routing locked in. %d waypoints.%s",
"%s has %d waypoint plan confirmed.%s",
"%s flight plan loaded. %d waypoints accepted.%s",
"%s acknowledges custom %d waypoint route.%s",
"%s accepts %d waypoint routing.%s",
"%s copies custom %d waypoint plan.%s",
"%s has %d waypoint route programmed.%s",
"%s confirms your %d waypoint custom route.%s",
"%s roger. %d waypoint custom plan loaded.%s",
"%s routing accepted with %d points.%s",
"%s flight plan programmed. %d waypoints.%s",
"%s has custom route with %d waypoints.%s",
"%s accepts %d waypoint custom plan.%s",
"%s acknowledges %d waypoint routing.%s",
"%s copies %d waypoint custom route.%s",
"%s has %d waypoint flight plan confirmed.%s",
"%s roger custom route. %d waypoints.%s",
"%s routing confirmed with %d waypoints.%s",
"%s flight plan accepted. %d points loaded.%s",
"%s has your %d waypoint custom plan.%s",
"%s accepts custom %d waypoint route.%s",
"%s confirms %d waypoint custom plan.%s",
"%s acknowledges %d waypoint flight plan.%s",
"%s copies %d waypoint route confirmed.%s",
"%s has custom %d waypoint routing ready.%s",
"%s roger. %d waypoints accepted and locked.%s",
"%s routing programmed with %d waypoints.%s",
"%s flight plan confirmed with %d points.%s",
"%s has %d waypoint custom route loaded.%s",
"%s accepts your custom %d waypoint routing.%s",
"%s confirms %d waypoint plan confirmed.%s",
"%s acknowledges custom route with %d points.%s",
"%s copies %d waypoint custom flight plan.%s",
"%s has %d waypoint route locked and loaded.%s",
"%s roger that. %d waypoint custom route ready.%s",
"%s routing accepted. %d waypoints programmed.%s",
"%s flight plan loaded with %d waypoints.%s",
},
-- Emergency Spawn
EMERGENCY_SPAWN = {
"EMERGENCY: %s launching immediately!",
"PRIORITY LAUNCH: %s is scrambling now!",
"EMERGENCY TANKER: %s departing expedited!",
"URGENT: %s is launching on priority status!",
"EMERGENCY RESPONSE: %s airborne ASAP!",
"PRIORITY: %s scrambling for emergency fuel!",
"EMERGENCY: %s launching hot!",
"URGENT LAUNCH: %s is wheels up now!",
"EMERGENCY TANKER: %s responding immediately!",
"PRIORITY STATUS: %s emergency launch in progress!",
"EMERGENCY! %s wheels up NOW!",
"SCRAMBLE SCRAMBLE: %s launching immediately!",
"PRIORITY LAUNCH: %s getting airborne right now!",
"EMERGENCY TANKER: %s departing hot and fast!",
"URGENT: %s scrambling for emergency refuel!",
"PRIORITY: %s launching on expedited status!",
"EMERGENCY RESPONSE: %s airborne immediately!",
"URGENT LAUNCH: %s departing NOW!",
"EMERGENCY: %s getting up there ASAP!",
"PRIORITY STATUS: %s scrambling right now!",
"EMERGENCY TANKER: %s wheels up immediately!",
"URGENT: %s launching on priority!",
"SCRAMBLE: %s departing expedited!",
"EMERGENCY: %s getting airborne fast!",
"PRIORITY LAUNCH: %s launching NOW!",
"URGENT TANKER: %s scrambling immediately!",
"EMERGENCY: %s departing hot!",
"PRIORITY: %s wheels up ASAP!",
"URGENT LAUNCH: %s airborne right now!",
"EMERGENCY TANKER: %s launching immediately!",
"SCRAMBLE SCRAMBLE: %s getting up there now!",
"PRIORITY: %s launching on emergency status!",
"URGENT: %s departing immediately!",
"EMERGENCY: %s scrambling for urgent refuel!",
"PRIORITY LAUNCH: %s wheels up hot!",
"URGENT TANKER: %s airborne ASAP!",
"EMERGENCY: %s launching right now!",
"PRIORITY: %s scrambling expedited!",
"URGENT LAUNCH: %s departing NOW NOW NOW!",
"EMERGENCY TANKER: %s getting airborne fast!",
"PRIORITY STATUS: %s launching immediately!",
"URGENT: %s wheels up on priority!",
"EMERGENCY: %s scrambling now!",
"PRIORITY LAUNCH: %s departing fast!",
"URGENT TANKER: %s launching ASAP!",
"EMERGENCY: %s airborne immediately!",
"PRIORITY: %s scrambling hot!",
"URGENT LAUNCH: %s wheels up right now!",
"EMERGENCY TANKER: %s departing expedited!",
"PRIORITY: %s launching on urgent status!",
"URGENT: %s getting airborne now!",
"EMERGENCY SCRAMBLE: %s departing immediately!",
"PRIORITY TANKER: %s wheels up fast!",
"URGENT: %s launching right now!",
"EMERGENCY: %s airborne ASAP!",
"PRIORITY LAUNCH: %s scrambling now!",
"URGENT TANKER: %s departing hot!",
"EMERGENCY: %s wheels up immediately!",
"PRIORITY: %s getting airborne fast!",
"URGENT LAUNCH: %s scrambling ASAP!",
"EMERGENCY TANKER: %s launching on priority!",
"PRIORITY: %s departing right now!",
"URGENT: %s airborne expedited!",
"EMERGENCY: %s scrambling immediately!",
"PRIORITY LAUNCH: %s wheels up NOW!",
"URGENT TANKER: %s launching fast!",
"EMERGENCY: %s departing ASAP!",
"PRIORITY: %s airborne right now!",
"URGENT LAUNCH: %s scrambling hot!",
"EMERGENCY TANKER: %s wheels up expedited!",
"PRIORITY: %s launching immediately!",
"URGENT: %s getting airborne ASAP!",
"EMERGENCY: %s scrambling fast!",
"PRIORITY LAUNCH: %s departing NOW!",
"URGENT TANKER: %s wheels up right now!",
"EMERGENCY: %s airborne hot!",
"PRIORITY: %s scrambling ASAP!",
"URGENT LAUNCH: %s launching immediately!",
"EMERGENCY TANKER: %s departing fast!",
"PRIORITY: %s wheels up expedited!",
"URGENT: %s airborne NOW!",
"EMERGENCY: %s launching hot and fast!",
"PRIORITY LAUNCH: %s scrambling expedited!",
"URGENT TANKER: %s departing immediately!",
"EMERGENCY: %s wheels up ASAP!",
"PRIORITY: %s getting airborne now!",
"URGENT LAUNCH: %s airborne fast!",
"EMERGENCY TANKER: %s scrambling NOW!",
"PRIORITY: %s launching expedited!",
"URGENT: %s departing hot!",
"EMERGENCY: %s airborne immediately unlike Mo!",
"PRIORITY LAUNCH: %s wheels up faster than Mo!",
"URGENT TANKER: %s scrambling (Mo couldn't do this)!",
"EMERGENCY: %s launching while Mo watches!",
"PRIORITY: %s departing - Mo take notes!",
"URGENT LAUNCH: %s airborne (unlike Mo's attempts)!",
"EMERGENCY TANKER: %s scrambling successfully!",
"PRIORITY: %s wheels up for real!",
"URGENT: %s launching like professionals do!",
},
-- Low Fuel Warning
LOW_FUEL = {
"%s reports fuel at %d%%. Recommend expedite refueling.",
"%s low on fuel - %d%% remaining. RTB soon.",
"%s fuel state: %d%%. Time is limited.",
"%s down to %d%% fuel. Get your gas quick.",
"%s running low - %d%% remaining.",
"%s fuel advisory: %d%% left. Don't delay.",
"%s reports %d%% fuel state. Limited time remaining.",
"%s low fuel warning at %d%%. RTB imminent.",
"%s fuel: %d%%. Better hurry up.",
"%s getting thirsty at %d%% fuel remaining.",
"%s fuel down to %d%%. Time's ticking.",
"%s running on fumes at %d%%. Get moving.",
"%s reports %d%% fuel. Clock is running.",
"%s fuel state critical at %d%%.",
"%s getting low at %d%%. Don't dawdle.",
"%s fuel: %d%%. Window is closing.",
"%s reports %d%% remaining. Hurry it up.",
"%s fuel advisory: %d%%. Time's short.",
"%s down to %d%%. Better move fast.",
"%s fuel at %d%%. RTB soon or refuel now.",
"%s running thin at %d%%. Expedite.",
"%s reports %d%% fuel. Not much time left.",
"%s fuel state %d%%. Don't mess around.",
"%s getting low - %d%% and dropping.",
"%s fuel: %d%%. Better get some quick.",
"%s reports %d%%. Running out of time.",
"%s fuel down to %d%%. Tick tock.",
"%s low on gas at %d%%. Move it.",
"%s reports %d%% fuel state. Limited window.",
"%s fuel: %d%%. Don't be slow about it.",
"%s getting thirsty - %d%% remaining.",
"%s reports %d%%. Better hurry your ass up.",
"%s fuel at %d%%. Time ain't on your side.",
"%s running low - %d%%. Get in here.",
"%s fuel state: %d%%. Mo could refuel faster.",
"%s reports %d%%. Don't be a hero, get fuel.",
"%s fuel down to %d%%. Unlike Mo we're warning you.",
"%s getting low at %d%%. Stop screwing around.",
"%s reports %d%% fuel. This isn't a drill.",
"%s fuel: %d%%. Better not screw this up.",
"%s running thin - %d%% remaining.",
"%s reports %d%%. Time to get your ass over here.",
"%s fuel state %d%%. Seriously, hurry up.",
"%s getting thirsty at %d%%. Don't be stupid.",
"%s fuel: %d%%. We're leaving soon.",
"%s reports %d%%. Better expedite refueling.",
"%s fuel down to %d%%. Window closing fast.",
"%s low on juice - %d%% remaining.",
"%s reports %d%% fuel. Get moving or RTB.",
"%s fuel state: %d%%. Don't drag ass.",
"%s getting low at %d%%. Time's running out.",
"%s reports %d%%. Stop dicking around.",
"%s fuel: %d%%. Get in the basket.",
"%s running thin at %d%%. Move faster.",
"%s reports %d%% remaining. Chop chop.",
"%s fuel down to %d%%. Unlike Mo's planning.",
"%s getting thirsty - %d%%. Don't be slow.",
"%s reports %d%% fuel state. Hurry.",
"%s fuel: %d%%. Better not flame out.",
"%s low on gas at %d%%. Get over here.",
"%s reports %d%%. Time to move it.",
"%s fuel state %d%%. We don't have all day.",
"%s getting low - %d%% and dropping fast.",
"%s reports %d%%. Stop being a pussy.",
"%s fuel: %d%%. Refuel or die trying.",
"%s running thin - %d%%. Better hurry.",
"%s reports %d%% fuel. Move your ass.",
"%s fuel down to %d%%. Not kidding here.",
"%s getting thirsty at %d%%. Expedite.",
"%s reports %d%%. Don't be like Mo.",
"%s fuel state: %d%%. Get fuel or get bent.",
"%s low at %d%%. Time's wasting.",
"%s reports %d%% remaining. Hurry up.",
"%s fuel: %d%%. Stop fucking around.",
"%s running low - %d%%. Get here now.",
"%s reports %d%%. We're not waiting forever.",
"%s fuel down to %d%%. Better get moving.",
"%s getting low at %d%%. Tick tock motherfucker.",
"%s reports %d%% fuel state. Move it.",
"%s fuel: %d%%. Don't be a jackass.",
"%s running thin at %d%%. Expedite refuel.",
"%s reports %d%%. Time's running short.",
"%s fuel state %d%%. Get in the pattern.",
"%s getting thirsty - %d%%. Don't delay.",
"%s reports %d%%. Unlike Mo we're still here.",
"%s fuel: %d%%. Better not screw this up.",
"%s low on gas - %d%% remaining.",
"%s reports %d%% fuel. Window closing.",
"%s fuel down to %d%%. Get your shit together.",
"%s getting low at %d%%. Seriously move.",
"%s reports %d%%. Don't make us leave.",
"%s fuel state: %d%%. Better expedite.",
"%s running thin - %d%%. Time's up soon.",
"%s reports %d%% remaining. Get here.",
"%s fuel: %d%%. Stop dragging ass.",
"%s getting thirsty at %d%%. Hurry.",
"%s reports %d%%. Mo would have flamed out by now.",
},
-- Bingo Fuel (RTB)
BINGO_FUEL = {
"%s is BINGO fuel. Returning to base immediately!",
"%s has reached BINGO. RTB in progress!",
"%s calling BINGO fuel. Departing the pattern now!",
"%s is at BINGO state. Returning to base!",
"%s BINGO fuel - heading home now!",
"%s has hit BINGO. No more refueling available!",
"%s fuel critical - RTB initiated!",
"%s at BINGO state. Breaking off now!",
"%s calling BINGO. Pattern is clear!",
"%s BINGO fuel declared. Returning to base!",
"%s is BINGO. Getting the hell out!",
"%s calling BINGO fuel. We're done here!",
"%s has reached BINGO state. Leaving NOW!",
"%s BINGO declared. RTB in progress!",
"%s at BINGO fuel. Heading home!",
"%s calling BINGO. Pattern clear!",
"%s has hit BINGO. See ya!",
"%s BINGO fuel state. Departing!",
"%s is at BINGO. RTB immediately!",
"%s calling BINGO. We're out!",
"%s has reached BINGO. Breaking off!",
"%s BINGO fuel declared. Leaving!",
"%s at BINGO state. Going home!",
"%s calling BINGO. Adios!",
"%s has hit BINGO fuel. Departing now!",
"%s BINGO declared. RTB active!",
"%s is at BINGO. Bye bye!",
"%s calling BINGO fuel. Out of here!",
"%s has reached BINGO state. Later!",
"%s BINGO fuel. Heading back!",
"%s at BINGO. Returning immediately!",
"%s calling BINGO. Pattern's yours!",
"%s has hit BINGO. Going home!",
"%s BINGO declared. Leaving the AO!",
"%s is at BINGO fuel. RTB now!",
"%s calling BINGO. Peace out!",
"%s has reached BINGO. Departing!",
"%s BINGO fuel state. We're done!",
"%s at BINGO. Heading to base!",
"%s calling BINGO. Catch you later!",
"%s has hit BINGO fuel. RTB!",
"%s BINGO declared. Getting out!",
"%s is at BINGO state. Later gator!",
"%s calling BINGO. We out!",
"%s has reached BINGO. Returning!",
"%s BINGO fuel. Leaving now!",
"%s at BINGO. Going home finally!",
"%s calling BINGO. Done pumping gas!",
"%s has hit BINGO. RTB initiated!",
"%s BINGO declared. Out of here!",
"%s is at BINGO fuel. Bye!",
"%s calling BINGO. Pattern clear!",
"%s has reached BINGO state. Departing!",
"%s BINGO fuel. Heading back!",
"%s at BINGO. RTB in progress!",
"%s calling BINGO. See ya later!",
"%s has hit BINGO fuel. Leaving!",
"%s BINGO declared. Going home!",
"%s is at BINGO. Out!",
"%s calling BINGO fuel. We're outta here!",
"%s has reached BINGO. RTB now!",
"%s BINGO fuel state. Later!",
"%s at BINGO. Returning to base!",
"%s calling BINGO. Adios amigos!",
"%s has hit BINGO. Departing!",
"%s BINGO declared. Heading home!",
"%s is at BINGO fuel. Peace!",
"%s calling BINGO. We done!",
"%s has reached BINGO state. Leaving!",
"%s BINGO fuel. RTB active!",
"%s at BINGO. Going back!",
"%s calling BINGO. That's it folks!",
"%s has hit BINGO fuel. Out of here!",
"%s BINGO declared. Returning!",
"%s is at BINGO. Later suckers!",
"%s calling BINGO fuel. Bye!",
"%s has reached BINGO. Heading home!",
"%s BINGO fuel state. Departing!",
"%s at BINGO. RTB initiated!",
"%s calling BINGO. We're gone!",
"%s has hit BINGO. Leaving now!",
"%s BINGO declared. Getting out of dodge!",
"%s is at BINGO fuel. Later!",
"%s calling BINGO. Don't wait up!",
"%s has reached BINGO state. Out!",
"%s BINGO fuel. Going home!",
"%s at BINGO. Returning immediately!",
"%s calling BINGO. Unlike Mo we planned this!",
"%s has hit BINGO fuel. Peace out!",
"%s BINGO declared. Heading back!",
"%s is at BINGO. Bye felicia!",
"%s calling BINGO fuel. That's a wrap!",
"%s has reached BINGO. We're out!",
"%s BINGO fuel state. Later gator!",
"%s at BINGO. RTB right now!",
"%s calling BINGO. Smell ya later!",
"%s has hit BINGO. Departing!",
"%s BINGO declared. Mo would've flamed out!",
"%s is at BINGO fuel. Catch you on the flip side!",
},
-- Tanker Destroyed
DESTROYED = {
"%s has been destroyed!",
"%s is down! Aircraft lost!",
"%s has been shot down!",
"%s destroyed in combat!",
"%s is gone - aircraft destroyed!",
"%s has been lost!",
"We've lost %s!",
"%s destroyed! No survivors!",
"%s is down and out!",
"%s has been eliminated!",
"%s has been blown to hell!",
"%s is toast! Aircraft destroyed!",
"%s went down in flames!",
"%s has been obliterated!",
"%s is scrap metal now!",
"%s got smoked!",
"RIP %s. Aircraft destroyed!",
"%s has been vaporized!",
"%s is no more!",
"%s went down hard!",
"%s has been wasted!",
"%s is KIA! Aircraft lost!",
"%s got shot the fuck down!",
"%s has been annihilated!",
"%s is sleeping with the fishes!",
"%s went boom!",
"%s has been terminated!",
"%s is dead! No survivors!",
"%s got fucked up!",
"%s has ceased to exist!",
"%s went down like Mo's career!",
"%s is destroyed! Total loss!",
"%s got hammered!",
"%s has been neutralized!",
"%s is history!",
"%s went down in a ball of fire!",
"%s has been taken out!",
"%s is gone forever!",
"%s got massacred!",
"%s has been deleted!",
"%s is pushing up daisies!",
"%s went down screaming!",
"%s has been liquidated!",
"%s is scattered across the landscape!",
"%s got wrecked!",
"%s has been erased!",
"%s is no longer operational!",
"%s went down like a brick!",
"%s has been dispatched!",
"%s is gone to the great hangar in the sky!",
"%s got absolutely demolished!",
"%s has been removed from existence!",
"%s is dead as fuck!",
"%s went down faster than Mo!",
"%s has been exterminated!",
"%s is now a smoking crater!",
"%s got absolutely destroyed!",
"%s has been converted to debris!",
"%s is no longer with us!",
"%s went down in a spectacular fashion!",
"%s has been sent to hell!",
"%s is totally fucked!",
"%s got blown out of the sky!",
"%s has been utterly destroyed!",
"%s is burning on the ground!",
"%s went down like a sack of shit!",
"%s has been wiped out!",
"%s is permanently grounded!",
"%s got turned into confetti!",
"%s has been removed from service!",
"%s is now spare parts!",
"%s went down hard and fast!",
"%s has been completely destroyed!",
"%s is toast and then some!",
"%s got absolutely annihilated!",
"%s has been blown to smithereens!",
"%s is no longer flying!",
"%s went down like the Hindenburg!",
"%s has been totally wrecked!",
"%s is deader than dead!",
"%s got straight up murdered!",
"%s has been completely obliterated!",
"%s is scattered across three counties!",
"%s went down in flames like Mo's reputation!",
"%s has been catastrophically destroyed!",
"%s is now a fireball!",
"%s got absolutely smoked!",
"%s has been reduced to atoms!",
"%s is gone gone gone!",
"%s went down and ain't coming back!",
"%s has been utterly annihilated!",
"%s is now a lawn dart!",
"%s got completely fucked!",
"%s has been sent to the shadow realm!",
"%s is now in aircraft heaven!",
"%s went down faster than your hopes and dreams!",
"%s has been totally destroyed!",
"%s is no longer a thing!",
},
-- Hostile Fire
TAKING_FIRE = {
"%s is taking fire!",
"%s under attack!",
"%s receiving hostile fire!",
"%s taking hits!",
"%s is being engaged!",
"%s under hostile fire!",
"Hostile fire on %s!",
"%s taking enemy fire!",
"%s is under attack!",
"%s being fired upon!",
"%s is getting shot at!",
"%s under hostile fire!",
"%s taking incoming!",
"%s is being lit up!",
"%s receiving enemy fire!",
"%s getting hammered!",
"%s under attack right now!",
"%s taking fire from hostiles!",
"%s is being engaged by enemy!",
"%s getting shot to shit!",
"%s under hostile attack!",
"%s taking heavy fire!",
"%s is being targeted!",
"%s receiving hostile rounds!",
"%s getting fucked up!",
"%s under enemy fire!",
"%s taking hits from hostiles!",
"%s is being shot at!",
"%s receiving incoming fire!",
"%s getting attacked!",
"%s under hostile engagement!",
"%s taking enemy rounds!",
"%s is being hit!",
"%s receiving fire!",
"%s getting lit up!",
"%s under attack from enemy!",
"%s taking hostile fire!",
"%s is being engaged!",
"%s receiving enemy rounds!",
"%s getting shot!",
"%s under fire right now!",
"%s taking incoming rounds!",
"%s is being attacked!",
"%s receiving hostile fire!",
"%s getting hammered by hostiles!",
"%s under enemy attack!",
"%s taking fire from below!",
"%s is being targeted by enemy!",
"%s receiving heavy fire!",
"%s getting shot at hard!",
"%s under hostile fire!",
"%s taking enemy fire now!",
"%s is being engaged by hostiles!",
"%s receiving incoming!",
"%s getting attacked by enemy!",
"%s under fire from hostiles!",
"%s taking hits!",
"%s is being shot up!",
"%s receiving hostile rounds!",
"%s getting fucked up by enemy!",
"%s under hostile engagement!",
"%s taking fire!",
"%s is being hammered!",
"%s receiving enemy fire!",
"%s getting shot at!",
"%s under enemy fire!",
"%s taking hostile rounds!",
"%s is being lit up!",
"%s receiving fire from hostiles!",
"%s getting attacked hard!",
"%s under hostile attack!",
"%s taking incoming fire!",
"%s is being engaged!",
"%s receiving hostile fire!",
"%s getting shot to hell!",
"%s under fire!",
"%s taking enemy rounds!",
"%s is being targeted!",
"%s receiving fire!",
"%s getting hammered!",
"%s under attack by hostiles!",
"%s taking fire from enemy!",
"%s is being shot at!",
"%s receiving incoming rounds!",
"%s getting attacked!",
"%s under hostile fire right now!",
"%s taking hits from enemy!",
"%s is being engaged by hostiles!",
"%s receiving hostile fire!",
"%s getting lit up by enemy!",
"%s under fire from below!",
"%s taking enemy fire!",
"%s is being attacked by hostiles!",
"%s receiving fire from enemy!",
"%s getting shot at hard!",
"%s under enemy attack!",
"%s taking hostile fire unlike Mo who'd be dead!",
"%s is being hammered by hostiles!",
"%s receiving enemy rounds!",
},
-- Invalid Waypoint Count (too few)
TOO_FEW_WAYPOINTS = {
"Custom route requires at least %d waypoints!\nPlace markers: %s1, %s2, etc.",
"Not enough waypoints! Need at least %d.\nUse markers: %s1, %s2, etc.",
"Insufficient waypoints - need %d minimum.\nCreate markers: %s1, %s2, etc.",
"Route rejected: need %d waypoints minimum.\nPlace %s1, %s2, etc.",
"At least %d waypoints required!\nDrop markers: %s1, %s2, etc.",
"Need more waypoints - minimum is %d.\nUse: %s1, %s2, etc.",
"Route incomplete. Need %d waypoints.\nCreate: %s1, %s2, etc.",
"Waypoint count too low - need %d.\nPlace: %s1, %s2, etc.",
"Minimum %d waypoints required!\nMark: %s1, %s2, etc.",
"Can't route with less than %d points.\nAdd markers: %s1, %s2, etc.",
},
-- Too Many Waypoints
TOO_MANY_WAYPOINTS = {
"Too many waypoints! Maximum is %d",
"Waypoint limit exceeded. Max: %d",
"Can't route with more than %d waypoints!",
"Route rejected - too many points. Max: %d",
"Waypoint overflow! Maximum is %d",
"Too complex - max %d waypoints allowed!",
"Exceeded waypoint limit of %d!",
"Route too long! Maximum: %d waypoints",
"Cannot accept more than %d waypoints!",
"Waypoint maximum is %d. Route rejected.",
},
-- No RTB Airbase Found
NO_RTB_AIRBASE = {
"No friendly airbase found for RTB!",
"Cannot locate RTB destination!",
"No suitable airbase available for recovery!",
"Unable to find friendly base for RTB!",
"No recovery airfield located!",
"RTB destination unavailable!",
"Cannot identify friendly airbase for return!",
"No airbase in range for RTB!",
"Recovery base not found!",
"Unable to locate RTB airfield!",
},
-- EMERGENCY SPAWN MESSAGES (100 total)
EMERGENCY_SPAWN = {
"EMERGENCY: %s launching immediately!",
"PRIORITY LAUNCH: %s is scrambling now!",
"EMERGENCY TANKER: %s departing expedited!",
"URGENT: %s is launching on priority status!",
"EMERGENCY RESPONSE: %s airborne ASAP!",
"PRIORITY: %s scrambling for emergency fuel!",
"EMERGENCY: %s launching hot!",
"URGENT LAUNCH: %s is wheels up now!",
"EMERGENCY TANKER: %s responding immediately!",
"PRIORITY STATUS: %s emergency launch in progress!",
"EMERGENCY! %s wheels up NOW!",
"SCRAMBLE SCRAMBLE: %s launching immediately!",
"PRIORITY LAUNCH: %s getting airborne right now!",
"EMERGENCY TANKER: %s departing hot and fast!",
"URGENT: %s scrambling for emergency refuel!",
"PRIORITY: %s launching on expedited status!",
"EMERGENCY RESPONSE: %s airborne immediately!",
"URGENT LAUNCH: %s departing NOW!",
"EMERGENCY: %s getting up there ASAP!",
"PRIORITY STATUS: %s scrambling right now!",
"EMERGENCY TANKER: %s wheels up immediately!",
"URGENT: %s launching on priority!",
"SCRAMBLE: %s departing expedited!",
"EMERGENCY: %s getting airborne fast!",
"PRIORITY LAUNCH: %s launching NOW!",
"URGENT TANKER: %s scrambling immediately!",
"EMERGENCY: %s departing hot!",
"PRIORITY: %s wheels up ASAP!",
"URGENT LAUNCH: %s airborne right now!",
"EMERGENCY TANKER: %s launching immediately!",
"SCRAMBLE SCRAMBLE: %s getting up there now!",
"PRIORITY: %s launching on emergency status!",
"URGENT: %s departing immediately!",
"EMERGENCY: %s scrambling for urgent refuel!",
"PRIORITY LAUNCH: %s wheels up hot!",
"URGENT TANKER: %s airborne ASAP!",
"EMERGENCY: %s launching right now!",
"PRIORITY: %s scrambling expedited!",
"URGENT LAUNCH: %s departing NOW NOW NOW!",
"EMERGENCY TANKER: %s getting airborne fast!",
"PRIORITY STATUS: %s launching immediately!",
"URGENT: %s wheels up on priority!",
"EMERGENCY: %s scrambling now!",
"PRIORITY LAUNCH: %s departing fast!",
"URGENT TANKER: %s launching ASAP!",
"EMERGENCY: %s airborne immediately!",
"PRIORITY: %s scrambling hot!",
"URGENT LAUNCH: %s wheels up right now!",
"EMERGENCY TANKER: %s departing expedited!",
"PRIORITY: %s launching on urgent status!",
"URGENT: %s getting airborne now!",
"EMERGENCY SCRAMBLE: %s departing immediately!",
"PRIORITY TANKER: %s wheels up fast!",
"URGENT: %s launching right now!",
"EMERGENCY: %s airborne ASAP!",
"PRIORITY LAUNCH: %s scrambling now!",
"URGENT TANKER: %s departing hot!",
"EMERGENCY: %s wheels up immediately!",
"PRIORITY: %s getting airborne fast!",
"URGENT LAUNCH: %s scrambling ASAP!",
"EMERGENCY TANKER: %s launching on priority!",
"PRIORITY: %s departing right now!",
"URGENT: %s airborne expedited!",
"EMERGENCY: %s scrambling immediately!",
"PRIORITY LAUNCH: %s wheels up NOW!",
"URGENT TANKER: %s launching fast!",
"EMERGENCY: %s departing ASAP!",
"PRIORITY: %s airborne right now!",
"URGENT LAUNCH: %s scrambling hot!",
"EMERGENCY TANKER: %s wheels up expedited!",
"PRIORITY: %s launching immediately!",
"URGENT: %s getting airborne ASAP!",
"EMERGENCY: %s scrambling fast!",
"PRIORITY LAUNCH: %s departing NOW!",
"URGENT TANKER: %s wheels up right now!",
"EMERGENCY: %s airborne hot!",
"PRIORITY: %s scrambling ASAP!",
"URGENT LAUNCH: %s launching immediately!",
"EMERGENCY TANKER: %s departing fast!",
"PRIORITY: %s wheels up expedited!",
"URGENT: %s airborne NOW!",
"EMERGENCY: %s launching hot and fast!",
"PRIORITY LAUNCH: %s scrambling expedited!",
"URGENT TANKER: %s departing immediately!",
"EMERGENCY: %s wheels up ASAP!",
"PRIORITY: %s getting airborne now!",
"URGENT LAUNCH: %s airborne fast!",
"EMERGENCY TANKER: %s scrambling NOW!",
"PRIORITY: %s launching expedited!",
"URGENT: %s departing hot!",
"EMERGENCY: %s airborne immediately unlike Mo!",
"PRIORITY LAUNCH: %s wheels up faster than Mo!",
"URGENT TANKER: %s scrambling (Mo couldn't do this)!",
"EMERGENCY: %s launching while Mo watches!",
"PRIORITY: %s departing - Mo take notes!",
"URGENT LAUNCH: %s airborne (unlike Mo's attempts)!",
"EMERGENCY TANKER: %s scrambling successfully!",
"PRIORITY: %s wheels up for real!",
"URGENT: %s launching like professionals do!",
},
}
--- Get a random message from a category
--- @param category string Message category key
--- @param ... any Format arguments for string.format
--- @return string Formatted message
local function GetRandomMessage(category, ...)
local pool = TANKER_MESSAGES[category]
if not pool or #pool == 0 then
return "Message unavailable"
end
local template = pool[math.random(1, #pool)]
if select("#", ...) > 0 then
return string.format(template, ...)
else
return template
end
end
-- ============================================================================
-- UTILITY FUNCTIONS
-- ============================================================================
local function UpdateTankerMenus()
-- With standard launches removed there is nothing to synchronise yet.
end
--- Announce tanker information to coalition
local function AnnounceTankerInfo(config, spawned)
local msg = GetRandomMessage("SPAWN_SUCCESS", config.displayName) .. "\n"
if config.tacan then
msg = msg .. string.format("TACAN: %s\n", config.tacan)
end
if config.frequency then
msg = msg .. string.format("Radio: %s MHz", config.frequency)
end
MESSAGE:New(msg, 20):ToBlue()
env.info(string.format("[TANKER] %s spawned successfully", config.displayName))
end
--- Monitor tanker fuel levels
local function MonitorTankerFuel(stateKey, config)
return function()
local state = TANKER_STATE[stateKey]
if not state.active or not state.group then
return
end
-- Check if group still exists
if not state.group:IsAlive() then
return
end
local fuelPercent = state.group:GetFuel() * 100
-- Bingo fuel check
if fuelPercent <= config.fuelBingoPercent and not state.bingoWarned then
MESSAGE:New(GetRandomMessage("BINGO_FUEL", config.displayName), 15, "WARNING"):ToBlue()
state.bingoWarned = true
env.info(string.format("[TANKER] %s bingo fuel: %.1f%%", config.displayName, fuelPercent))
-- Low fuel warning
elseif fuelPercent <= config.fuelWarningPercent and not state.fuelWarned then
MESSAGE:New(GetRandomMessage("LOW_FUEL", config.displayName, math.floor(fuelPercent)), 15):ToBlue()
state.fuelWarned = true
env.info(string.format("[TANKER] %s low fuel warning: %.1f%%", config.displayName, fuelPercent))
end
end
end
--- Start (or restart) the fuel monitor scheduler for a tanker
local function StartFuelMonitor(stateKey, config)
local state = TANKER_STATE[stateKey]
if not state then
return
end
if state.fuelMonitor then
state.fuelMonitor:Stop()
state.fuelMonitor = nil
end
state.fuelMonitor = SCHEDULER:New(
nil,
MonitorTankerFuel(stateKey, config),
{},
FUEL_CHECK_INTERVAL,
FUEL_CHECK_INTERVAL
)
end
--- Clean up tanker state
local function CleanupTankerState(stateKey)
local state = TANKER_STATE[stateKey]
state.active = false
state.group = nil
state.dcsGroupName = nil
state.fuelWarned = false
state.bingoWarned = false
if state.fuelMonitor then
state.fuelMonitor:Stop()
state.fuelMonitor = nil
end
if state.respawnScheduler then
state.respawnScheduler:Stop()
state.respawnScheduler = nil
end
end
-- ============================================================================
-- CUSTOM ROUTE FUNCTIONS
-- ============================================================================
--- Parse waypoint marker text for altitude and speed overrides
--- Supports formats: SHELL1, SHELL1:FL220, SHELL1:FL220:SP330, SHELL1::SP300, SHELL1:RTB
--- @param markerText string The text from the map marker
--- @param defaultAlt number Default altitude in feet
--- @param defaultSpeed number Default speed in knots
--- @return table Parsed waypoint data {altitude, speed, rtb, isValid}
local function ParseWaypointMarker(markerText, defaultAlt, defaultSpeed)
local result = {
altitude = defaultAlt,
speed = defaultSpeed,
rtb = false,
isValid = true,
originalText = markerText
}
-- Split by colon
local parts = {}
for part in string.gmatch(markerText, "[^:]+") do
table.insert(parts, part)
end
-- Check for RTB command
for _, part in ipairs(parts) do
if string.upper(part) == "RTB" then
result.rtb = true
return result
end
end
-- Parse FL (Flight Level)
for _, part in ipairs(parts) do
local fl = string.match(part, "FL(%d+)")
if fl then
result.altitude = tonumber(fl) * 100 -- Convert FL to feet
end
end
-- Parse SP (Speed)
for _, part in ipairs(parts) do
local sp = string.match(part, "SP(%d+)")
if sp then
result.speed = tonumber(sp)
end
end
return result
end
--- Scan map for waypoint markers matching callsign pattern
--- @param callsign string The callsign prefix to search for (e.g., "SHELL", "ARCO")
--- @return table Array of waypoint data sorted by sequence number
local function ScanForWaypointMarkers(callsign)
local waypoints = {}
local markerIds = {}
-- Iterate through all possible marker IDs (DCS markers are numbered)
-- We'll scan up to 1000 markers (should be more than enough)
for i = 1, 1000 do
local markerData = world.getMarkPanels()
if markerData and markerData[i] then
local marker = markerData[i]
local markerText = marker.text
if markerText then
-- Check if marker matches pattern: CALLSIGN + number
local upperText = string.upper(markerText)
local upperCallsign = string.upper(callsign)
local sequence = string.match(upperText, "^" .. upperCallsign .. "(%d+)")
if sequence then
local seqNum = tonumber(sequence)
local pos = marker.pos
table.insert(waypoints, {
sequence = seqNum,
coordinate = COORDINATE:NewFromVec3(pos),
markerId = marker.idx,
markerText = markerText
})
table.insert(markerIds, marker.idx)
env.info(string.format("[TANKER] Found waypoint marker: %s at seq %d (ID: %d)",
markerText, seqNum, marker.idx))
end
end
end
end
-- Sort by sequence number
table.sort(waypoints, function(a, b) return a.sequence < b.sequence end)
return waypoints, markerIds
end
-- ============================================================================
--- Spawn a tanker directly from config (no Mission Editor template required)
--- @param config table Tanker configuration
--- @param coord COORDINATE Where to spawn
--- @param heading number Initial heading in degrees
--- @return GROUP|nil The spawned tanker group wrapper
--- @return string|nil The DCS group name that was used
local function SpawnTankerFromConfig(config, coord, heading)
local uniqueIndex = NextUniqueIndex()
local uniqueGroupName = GenerateGroupName(config.groupName, uniqueIndex)
local uniqueUnitName = GenerateUnitName(config.unitName, uniqueIndex)
-- Ensure we have valid altitude (coord.y is altitude in meters MSL)
local spawnAlt = coord.y
env.info(string.format("[TANKER] Spawn altitude: %.1f meters (FL%03d)", spawnAlt, spawnAlt * 3.28084 / 100))
-- Create group structure for coalition.addGroup
local groupData = {
visible = false,
taskSelected = true,
route = {
points = {
{
alt = spawnAlt,
type = "Turning Point",
action = "Turning Point",
alt_type = "BARO",
speed = config.defaultSpeed * 0.514444,
task = {
id = "ComboTask",
params = {
tasks = {
{
id = "Tanker",
params = {}
}
}
}
},
x = coord.x,
y = coord.z,
}
}
},
hidden = false,
units = {
{
alt = spawnAlt,
alt_type = "BARO",
livery_id = config.livery,
skill = "High",
speed = config.defaultSpeed * 0.514444,
type = config.aircraftType,
psi = -heading,
unitName = uniqueUnitName,
x = coord.x,
y = coord.z,
heading = math.rad(heading),
onboard_num = "010",
},
},
y = coord.z,
x = coord.x,
name = uniqueGroupName,
task = "Refueling",
}
local spawnedId = coalition.addGroup(country.id.USA, Group.Category.AIRPLANE, groupData)
if not spawnedId then
env.error(string.format("[TANKER] Failed to spawn %s", config.groupName))
return nil
end
env.info(string.format("[TANKER] Spawned %s as %s", config.groupName, uniqueGroupName))
local mooseGroup = GROUP:FindByName(uniqueGroupName)
if not mooseGroup and Group and Group.getByName then
local dcsGroup = Group.getByName(uniqueGroupName)
if dcsGroup then
mooseGroup = GROUP:Find(dcsGroup)
end
end
if not mooseGroup then
env.warning(string.format("[TANKER] Spawned %s but could not resolve group wrapper", uniqueGroupName))
return nil
end
return mooseGroup, uniqueGroupName
end
--- Ensure default spawns immediately enter a holding pattern so they do not RTB
--- @param group GROUP The spawned tanker group
--- @param coord COORDINATE Center point for the orbit
--- @param config table Tanker configuration for speed/altitude
local function ApplyDefaultOrbitRoute(group, coord, config)
if not group or not coord or not config then
return
end
local orbitCenter = coord:SetAltitude(config.defaultAltitude * 0.3048, true)
local orbitWP = orbitCenter:WaypointAirTurningPoint(
COORDINATE.WaypointAltType.BARO,
config.defaultSpeed * 0.514444,
config.defaultAltitude * 0.3048,
{},
"DEFAULT-ORBIT"
)
orbitWP.task = {
id = "ComboTask",
params = {
tasks = {
{
id = "Tanker",
params = {}
},
{
id = "Orbit",
params = {
pattern = "Circle",
speed = config.defaultSpeed * 0.514444,
altitude = config.defaultAltitude * 0.3048,
point = {
x = orbitCenter.x,
y = orbitCenter.z
}
}
}
}
}
}
group:Route({ orbitWP })
env.info(string.format("[TANKER] Applied default orbit for %s", config.displayName))
end
--- Create custom route tanker spawn from mission editor template
--- Spawns at airbase, taxis, takes off, flies to route waypoints, then loops
--- @param callsign string Callsign prefix used for markers
--- @param config table Tanker configuration
--- @param stateKey string State key for tracking
--- @param isEmergency boolean Whether this is an emergency spawn
--- @return boolean Success status
local function SpawnCustomRouteTanker(callsign, config, stateKey, isEmergency)
local state = TANKER_STATE[stateKey]
-- Check if already active
if state.active then
MESSAGE:New(GetRandomMessage("ALREADY_ACTIVE", config.displayName), 10):ToBlue()
return false
end
-- Scan for waypoint markers
local waypoints, markerIds = ScanForWaypointMarkers(callsign)
-- Validate waypoint count
if #waypoints < ROUTE_CONFIG.minWaypoints then
MESSAGE:New(GetRandomMessage("TOO_FEW_WAYPOINTS",
ROUTE_CONFIG.minWaypoints, callsign, callsign), 15, "ERROR"):ToBlue()
return false
end
if #waypoints > ROUTE_CONFIG.maxWaypoints then
MESSAGE:New(GetRandomMessage("TOO_MANY_WAYPOINTS",
ROUTE_CONFIG.maxWaypoints), 15, "ERROR"):ToBlue()
return false
end
-- Build route description and validate waypoints
local routeDesc = ""
local routePoints = {}
local hasRTB = false
for i, wp in ipairs(waypoints) do
local parsed = ParseWaypointMarker(wp.markerText, config.defaultAltitude, config.defaultSpeed)
if parsed.rtb then
hasRTB = true
-- Find nearest friendly airbase from last waypoint position
local lastPos = #routePoints > 0 and routePoints[#routePoints].coord or wp.coordinate
local nearestAirbase = lastPos:GetClosestAirbase(Airbase.Category.AIRDROME, coalition.side.BLUE)
if nearestAirbase then
local airbaseName = nearestAirbase:GetName()
local airbaseCoord = nearestAirbase:GetCoordinate()
routeDesc = routeDesc .. string.format("\n WP%d: RTB to %s", i, airbaseName)
table.insert(routePoints, {
coord = airbaseCoord,
altitude = 0, -- Will land
speed = parsed.speed,
rtb = true,
airbase = nearestAirbase,
airbaseName = airbaseName
})
env.info(string.format("[TANKER] RTB destination: %s", airbaseName))
else
routeDesc = routeDesc .. string.format("\n WP%d: RTB (no airbase found)", i)
env.warning("[TANKER] No friendly airbase found for RTB")
end
break -- RTB is terminal command
else
routeDesc = routeDesc .. string.format("\n WP%d: FL%03d @ %d kts",
i, math.floor(parsed.altitude / 100), parsed.speed)
table.insert(routePoints, {
coord = wp.coordinate,
altitude = parsed.altitude,
speed = parsed.speed,
rtb = false
})
end
end
-- Confirm route to player
local routeMsg = GetRandomMessage("ROUTE_ACCEPTED", config.displayName, #routePoints, routeDesc)
if isEmergency then
routeMsg = GetRandomMessage("EMERGENCY_SPAWN", config.displayName) .. "\n" .. routeMsg
end
MESSAGE:New(routeMsg, 20):ToBlue()
env.info(string.format("[TANKER] Spawning %s with custom route: %d waypoints",
config.displayName, #routePoints))
-- Debug: log route point data
for i, rp in ipairs(routePoints) do
env.info(string.format("[TANKER] RoutePoint %d: coord=%s, alt=%.0f, spd=%.0f, rtb=%s",
i, tostring(rp.coord), rp.altitude, rp.speed, tostring(rp.rtb)))
end
-- Delete markers if configured
if ROUTE_CONFIG.deleteMarkersAfterUse then
for _, markerId in ipairs(markerIds) do
trigger.action.removeMark(markerId)
end
env.info(string.format("[TANKER] Deleted %d waypoint markers", #markerIds))
end
-- Spawn tanker from mission editor template (at airbase)
-- This will cause the tanker to taxi and takeoff
local tankerSpawn = SPAWN:New(config.groupName)
if not tankerSpawn then
MESSAGE:New(string.format("SPAWN FAILURE: Could not find template '%s' in mission", config.groupName),
15, "ERROR"):ToBlue()
env.error(string.format("[TANKER] Template '%s' not found in mission editor", config.groupName))
return false
end
env.info(string.format("[TANKER] SPAWN object created for template: '%s'", config.groupName))
-- Build complete route from airbase to waypoints
local taskRoute = {}
-- Add all custom route waypoints
for i, rp in ipairs(routePoints) do
local wp
-- RTB waypoint - land at airbase
if rp.rtb and rp.airbase then
wp = rp.coord:WaypointAirLanding(
rp.speed * 0.514444,
rp.airbase:GetDCSObject(),
{},
"RTB"
)
else
-- Normal waypoint
wp = rp.coord:WaypointAirFlyOverPoint(
COORDINATE.WaypointAltType.BARO,
rp.speed * 0.514444, -- Convert knots to m/s
rp.altitude * 0.3048, -- Convert feet to meters
{},
"WP" .. i
)
-- Add tanker task to all waypoints
wp.task = {
id = "ComboTask",
params = {
tasks = {
{
id = "Tanker",
params = {}
}
}
}
}
end
table.insert(taskRoute, wp)
end
-- If last waypoint is not RTB, loop back to first waypoint to create continuous patrol
if not hasRTB and #routePoints > 1 then
local firstPoint = routePoints[1]
local loopWP = firstPoint.coord:WaypointAirFlyOverPoint(
COORDINATE.WaypointAltType.BARO,
firstPoint.speed * 0.514444,
firstPoint.altitude * 0.3048,
{},
"LOOP-WP1"
)
-- Add tanker task to loop waypoint
loopWP.task = {
id = "ComboTask",
params = {
tasks = {
{
id = "Tanker",
params = {}
}
}
}
}
table.insert(taskRoute, loopWP)
env.info(string.format("[TANKER] Added loop waypoint back to WP1 for continuous patrol"))
elseif not hasRTB and #routePoints == 1 then
-- Single waypoint - add circular orbit pattern
local singlePoint = routePoints[1]
local orbitWP = singlePoint.coord:WaypointAirTurningPoint(
COORDINATE.WaypointAltType.BARO,
singlePoint.speed * 0.514444,
singlePoint.altitude * 0.3048,
{},
"ORBIT"
)
orbitWP.task = {
id = "ComboTask",
params = {
tasks = {
{
id = "Tanker",
params = {}
},
{
id = "Orbit",
params = {
pattern = "Circle",
speed = singlePoint.speed * 0.514444,
altitude = singlePoint.altitude * 0.3048
}
}
}
}
}
table.insert(taskRoute, orbitWP)
env.info(string.format("[TANKER] Single waypoint - added circular orbit pattern"))
end
-- Spawn with the custom route
-- Use MOOSE Spawn to spawn from template and immediately route
local spawnedGroup = tankerSpawn:Spawn()
if not spawnedGroup then
MESSAGE:New(GetRandomMessage("SPAWN_FAILURE", config.displayName), 10, "ERROR"):ToBlue()
env.error(string.format("[TANKER] Failed to spawn %s from template", config.displayName))
return false
end
local spawnedGroupName = spawnedGroup:GetName()
env.info(string.format("[TANKER] Spawned group name: '%s' (expected template: '%s')",
spawnedGroupName, config.groupName))
-- Apply the custom route to the spawned group
spawnedGroup:Route(taskRoute)
env.info(string.format("[TANKER] Spawned %s from airbase template, will taxi/takeoff to route",
config.displayName))
-- Set up state tracking for phase announcements
state.active = true
state.group = spawnedGroup
state.dcsGroupName = spawnedGroup:GetName()
state.fuelWarned = false
state.bingoWarned = false
state.hasAnnounced = {
taxi = false,
takeoff = false,
enroute = false,
onStation = false
}
state.routePoints = routePoints -- Store for on-station check
-- Announce taxi phase immediately
MESSAGE:New(GetRandomMessage("TAXI", config.displayName), 10):ToBlue()
state.hasAnnounced.taxi = true
-- Monitor for actual flight phases using continuous checking
SCHEDULER:New(nil, function()
if not state.active or not state.group or not state.group:IsAlive() then
return
end
local velocity = state.group:GetVelocityKMH()
local coord = state.group:GetCoordinate()
local altitudeMSL = coord.y -- MSL altitude
local landHeight = coord:GetLandHeight() -- Ground elevation
local altitudeAGL = altitudeMSL - landHeight -- Above Ground Level
-- Debug current state every check
env.info(string.format("[TANKER] %s status check - AGL: %.0fm (MSL: %.0fm, Ground: %.0fm), Vel: %.0fkm/h",
config.displayName, altitudeAGL, altitudeMSL, landHeight, velocity))
-- TAKEOFF: Airborne detection (AGL > 50m and speed > 150 km/h)
if not state.hasAnnounced.takeoff and altitudeAGL > 50 and velocity > 150 then
MESSAGE:New(GetRandomMessage("TAKEOFF", config.displayName), 10):ToBlue()
state.hasAnnounced.takeoff = true
env.info(string.format("[TANKER] %s TAKEOFF announced (AGL: %.0fm, spd: %.0fkm/h)",
config.displayName, altitudeAGL, velocity))
end
-- ENROUTE: Climbing and away from base (AGL > 300m and speed > 250 km/h)
if state.hasAnnounced.takeoff and not state.hasAnnounced.enroute and altitudeAGL > 300 and velocity > 250 then
MESSAGE:New(GetRandomMessage("ENROUTE", config.displayName), 10):ToBlue()
state.hasAnnounced.enroute = true
env.info(string.format("[TANKER] %s ENROUTE announced (AGL: %.0fm)", config.displayName, altitudeAGL))
end
-- ON_STATION: Within 5nm of WP1 and at reasonable altitude
if state.hasAnnounced.enroute and not state.hasAnnounced.onStation then
local wp1Coord = state.routePoints[1].coord
local currentCoord = state.group:GetCoordinate()
local distance = currentCoord:Get2DDistance(wp1Coord)
local targetAlt = state.routePoints[1].altitude * 0.3048 -- feet to meters
-- Within 5nm and within 2000ft of target altitude
if distance < 9260 and math.abs(altitudeMSL - targetAlt) < 610 then
MESSAGE:New(GetRandomMessage("ON_STATION", config.displayName), 15):ToBlue()
state.hasAnnounced.onStation = true
env.info(string.format("[TANKER] %s ON-STATION announced (distance: %.0fm, alt diff: %.0fm)",
config.displayName, distance, math.abs(altitudeMSL - targetAlt)))
end
end
end, {}, 15, 10) -- Start after 15 seconds, check every 10 seconds
-- Announce TACAN/Freq info (without the "airborne" message)
local msg = string.format("%s assigned:\n", config.displayName)
if config.tacan then
msg = msg .. string.format("TACAN: %s\n", config.tacan)
end
if config.frequency then
msg = msg .. string.format("Radio: %s MHz", config.frequency)
end
MESSAGE:New(msg, 15):ToBlue()
-- Start fuel monitoring
StartFuelMonitor(stateKey, config)
-- Update menus
UpdateTankerMenus()
return true
end
-- ============================================================================
-- EVENT HANDLER
-- ============================================================================
BlueTankerEventHandler = EVENTHANDLER:New()
function BlueTankerEventHandler:OnEventBirth(EventData)
local groupName = EventData.IniDCSGroupName
if groupName and string.find(groupName, "TANKER 135") then
env.info(string.format("[TANKER] Birth event: %s", groupName))
-- Determine which tanker spawned
local stateKey, config
if string.find(groupName, "MPRS") then
stateKey = "KC135_MPRS"
config = TANKER_CONFIG.KC135_MPRS
else
stateKey = "KC135"
config = TANKER_CONFIG.KC135
end
-- Update state
local state = TANKER_STATE[stateKey]
state.active = true
local mooseGroup = GROUP:FindByName(groupName)
if not mooseGroup and Group and Group.getByName then
local dcsGroup = Group.getByName(groupName)
if dcsGroup then
mooseGroup = GROUP:Find(dcsGroup)
end
end
state.group = mooseGroup
state.dcsGroupName = groupName
state.fuelWarned = false
state.bingoWarned = false
-- Announce spawn with details
AnnounceTankerInfo(config, true)
-- Start fuel monitoring
StartFuelMonitor(stateKey, config)
-- Update menus
UpdateTankerMenus()
end
end
function BlueTankerEventHandler:OnEventDead(EventData)
local groupName = EventData.IniDCSGroupName
if groupName and string.find(groupName, "TANKER 135") then
env.info(string.format("[TANKER] Dead event: %s", groupName))
-- Determine which tanker died
local stateKey, config
if string.find(groupName, "MPRS") then
stateKey = "KC135_MPRS"
config = TANKER_CONFIG.KC135_MPRS
else
stateKey = "KC135"
config = TANKER_CONFIG.KC135
end
MESSAGE:New(GetRandomMessage("DESTROYED", config.displayName),
15, "ALERT"):ToBlue()
-- Clean up state
CleanupTankerState(stateKey)
-- Update menus
UpdateTankerMenus()
end
end
function BlueTankerEventHandler:OnEventCrash(EventData)
-- Treat crash same as dead
self:OnEventDead(EventData)
end
function BlueTankerEventHandler:OnEventEngineShutdown(EventData)
local groupName = EventData.IniDCSGroupName
if groupName and string.find(groupName, "TANKER 135") then
env.info(string.format("[TANKER] Engine shutdown event: %s", groupName))
-- Determine which tanker
local stateKey, config
if string.find(groupName, "MPRS") then
stateKey = "KC135_MPRS"
config = TANKER_CONFIG.KC135_MPRS
else
stateKey = "KC135"
config = TANKER_CONFIG.KC135
end
MESSAGE:New(string.format("%s has returned to base", config.displayName),
10):ToBlue()
-- Clean up state
CleanupTankerState(stateKey)
-- Update menus
UpdateTankerMenus()
end
end
function BlueTankerEventHandler:OnEventHit(EventData)
local groupName = EventData.IniDCSGroupName
if groupName and string.find(groupName, "TANKER 135") then
local config = string.find(groupName, "MPRS") and TANKER_CONFIG.KC135_MPRS or TANKER_CONFIG.KC135
MESSAGE:New(GetRandomMessage("TAKING_FIRE", config.displayName),
15, "WARNING"):ToBlue()
env.info(string.format("[TANKER] %s hit by hostile fire", config.displayName))
end
end
-- ============================================================================
-- SPAWN OBJECTS AND FUNCTIONS
-- ============================================================================
-- Function to spawn KC-135 with custom route
function SpawnCustomTanker()
SpawnCustomRouteTanker(
TANKER_CONFIG.KC135.callsign,
TANKER_CONFIG.KC135,
"KC135",
false
)
end
-- Function to spawn KC-135 MPRS with custom route
function SpawnCustomTankerMPRS()
SpawnCustomRouteTanker(
TANKER_CONFIG.KC135_MPRS.callsign,
TANKER_CONFIG.KC135_MPRS,
"KC135_MPRS",
false
)
end
-- ============================================================================
-- EMERGENCY TANKER FUNCTIONS (SPAWN AHEAD OF PLAYER)
-- ============================================================================
-- Function to find emergency marker and associated player
local function FindEmergencyMarkerAndPlayer()
-- Get all markers
local allMarkers = {}
world.getMarkPanels(function(panel)
if panel.coalition == coalition.side.BLUE then
table.insert(allMarkers, {
id = panel.idx,
text = panel.text,
pos = panel.pos
})
end
end)
-- Find emergency markers
local emergencyMarkers = {}
for _, marker in ipairs(allMarkers) do
local markerTextUpper = string.upper(marker.text)
for _, keyword in ipairs(EMERGENCY_CONFIG.markerKeywords) do
if string.find(markerTextUpper, string.upper(keyword)) then
table.insert(emergencyMarkers, marker)
break
end
end
end
if #emergencyMarkers == 0 then
return nil, nil, "No emergency marker found. Place marker with text 'EMERGENCY TANKER'"
end
-- Get all blue human players
local bluePlayers = {}
local allGroups = coalition.getGroups(coalition.side.BLUE, Group.Category.AIRPLANE)
for _, grp in ipairs(allGroups) do
local units = grp:getUnits()
if units then
for _, unit in ipairs(units) do
if unit and unit:isExist() and unit:isActive() then
local playerName = unit:getPlayerName()
if playerName then
local mooseUnit = UNIT:Find(unit)
if mooseUnit and mooseUnit:IsAlive() then
table.insert(bluePlayers, {
unit = mooseUnit,
coord = mooseUnit:GetCoordinate(),
name = playerName
})
end
end
end
end
end
end
if #bluePlayers == 0 then
return nil, nil, "No human players found"
end
-- Find closest player to any emergency marker
local closestPlayer = nil
local closestMarker = nil
local minDistance = math.huge
for _, marker in ipairs(emergencyMarkers) do
local markerCoord = COORDINATE:New(marker.pos.x, marker.pos.y, marker.pos.z)
for _, player in ipairs(bluePlayers) do
local distance = player.coord:Get2DDistance(markerCoord)
if distance < minDistance and distance < EMERGENCY_CONFIG.markerSearchRadius then
minDistance = distance
closestPlayer = player
closestMarker = marker
end
end
end
if not closestPlayer then
return nil, nil, "No player found near emergency marker (must be within 50km)"
end
return closestPlayer, closestMarker, nil
end
-- Function to spawn emergency tanker ahead of player
local function SpawnEmergencyTankerAheadOfPlayer(config, stateKey)
local state = TANKER_STATE[stateKey]
-- Check if already active
if state.active then
MESSAGE:New(GetRandomMessage("ALREADY_ACTIVE", config.displayName), 10):ToBlue()
return false
end
-- Find player and marker
local player, marker, errorMsg = FindEmergencyMarkerAndPlayer()
if not player then
MESSAGE:New(errorMsg or "Could not locate requesting player", 15, "ERROR"):ToBlue()
return false
end
-- Get player info
local playerCoord = player.coord
local playerAltFeet = playerCoord.y * 3.28084 -- Convert meters to feet
local playerHeading = player.unit:GetHeading()
local playerSpeed = player.unit:GetVelocityKNOTS()
-- Validate altitude
if playerAltFeet < EMERGENCY_CONFIG.minAltitudeFeet then
MESSAGE:New(string.format(
"EMERGENCY TANKER DENIED: Altitude too low (%.0f ft). Minimum altitude: %.0f ft",
playerAltFeet, EMERGENCY_CONFIG.minAltitudeFeet
), 15, "ERROR"):ToBlue()
return false
end
if playerAltFeet > EMERGENCY_CONFIG.maxAltitudeFeet then
MESSAGE:New(string.format(
"EMERGENCY TANKER DENIED: Altitude too high (%.0f ft). Maximum altitude: %.0f ft",
playerAltFeet, EMERGENCY_CONFIG.maxAltitudeFeet
), 15, "ERROR"):ToBlue()
return false
end
-- Calculate spawn position (5km ahead of player)
local spawnDistance = EMERGENCY_CONFIG.spawnDistanceKM * 1000 -- Convert to meters
local spawnCoord = playerCoord:Translate(spawnDistance, playerHeading)
-- Calculate egress waypoint (100nm ahead on same heading)
local egressDistance = EMERGENCY_CONFIG.waypointDistanceNM * 1852 -- Convert nm to meters
local egressCoord = spawnCoord:Translate(egressDistance, playerHeading)
-- Announce emergency spawn
local emergencyMsg = GetRandomMessage("EMERGENCY_SPAWN", config.displayName)
emergencyMsg = emergencyMsg .. string.format(
"\n\nSpawning %.1f nm ahead of %s\nAltitude: FL%03d | Heading: %03d°\nEgressing on same heading for %.0f nm then RTB",
EMERGENCY_CONFIG.spawnDistanceKM * 0.539957, -- km to nm
player.name,
math.floor(playerAltFeet / 100),
playerHeading,
EMERGENCY_CONFIG.waypointDistanceNM
)
MESSAGE:New(emergencyMsg, 20):ToBlue()
env.info(string.format(
"[TANKER] Emergency spawn for %s: pos=%s, alt=%.0f ft, hdg=%03d°",
player.name, tostring(spawnCoord), playerAltFeet, playerHeading
))
-- Delete the marker
if marker then
trigger.action.removeMark(marker.id)
env.info(string.format("[TANKER] Deleted emergency marker ID %d", marker.id))
end
-- Spawn tanker at calculated position
local spawnedGroup, spawnedName = SpawnTankerFromConfig(
config,
spawnCoord,
playerHeading
)
if not spawnedGroup then
MESSAGE:New(GetRandomMessage("SPAWN_FAILURE", config.displayName), 10, "ERROR"):ToBlue()
return false
end
-- Build emergency egress route
local taskRoute = {}
-- Waypoint 1: Current spawn position (egress start)
local wp1 = spawnCoord:WaypointAirFlyOverPoint(
COORDINATE.WaypointAltType.BARO,
config.defaultSpeed * 0.514444,
playerAltFeet * 0.3048,
{},
"EGRESS-START"
)
wp1.task = {
id = "ComboTask",
params = {
tasks = {
{
id = "Tanker",
params = {}
}
}
}
}
table.insert(taskRoute, wp1)
-- Waypoint 2: Egress endpoint (100nm ahead)
local wp2 = egressCoord:WaypointAirFlyOverPoint(
COORDINATE.WaypointAltType.BARO,
config.defaultSpeed * 0.514444,
playerAltFeet * 0.3048,
{},
"EGRESS-END"
)
wp2.task = {
id = "ComboTask",
params = {
tasks = {
{
id = "Tanker",
params = {}
}
}
}
}
table.insert(taskRoute, wp2)
-- Waypoint 3: RTB to nearest friendly airbase
local nearestAirbase = egressCoord:GetClosestAirbase(Airbase.Category.AIRDROME, coalition.side.BLUE)
if nearestAirbase then
local airbaseName = nearestAirbase:GetName()
local airbaseCoord = nearestAirbase:GetCoordinate()
local wp3 = airbaseCoord:WaypointAirLanding(
config.defaultSpeed * 0.514444,
nearestAirbase:GetDCSObject(),
{},
"RTB"
)
table.insert(taskRoute, wp3)
env.info(string.format("[TANKER] Emergency tanker will RTB to %s after egress", airbaseName))
else
env.warning("[TANKER] No friendly airbase found for RTB")
end
-- Route the tanker
spawnedGroup:Route(taskRoute)
-- Update state
state.active = true
state.group = spawnedGroup
state.dcsGroupName = spawnedName
state.fuelWarned = false
state.bingoWarned = false
-- Start fuel monitoring
StartFuelMonitoring(config, stateKey)
-- Announce tanker info
AnnounceTankerInfo(config, true)
env.info(string.format("[TANKER] %s emergency spawn complete (egress mode)", config.displayName))
return true
end
-- ============================================================================
-- EMERGENCY TANKER SPAWN FUNCTIONS (PUBLIC)
-- ============================================================================
-- Function to spawn emergency KC-135 ahead of player (NEW METHOD)
function SpawnEmergencyTankerAhead()
return SpawnEmergencyTankerAheadOfPlayer(TANKER_CONFIG.KC135, "KC135")
end
-- Function to spawn emergency KC-135 MPRS ahead of player (NEW METHOD)
function SpawnEmergencyTankerMPRSAhead()
return SpawnEmergencyTankerAheadOfPlayer(TANKER_CONFIG.KC135_MPRS, "KC135_MPRS")
end
-- Function to spawn emergency KC-135 with custom route (OLD METHOD - kept for compatibility)
function SpawnEmergencyTanker()
-- Use emergency respawn delay
local originalDelay = TANKER_CONFIG.KC135.respawnDelay
TANKER_CONFIG.KC135.respawnDelay = TANKER_CONFIG.KC135.emergencyRespawnDelay
local success = SpawnCustomRouteTanker(
TANKER_CONFIG.KC135.callsign,
TANKER_CONFIG.KC135,
"KC135",
true
)
-- Restore original delay
TANKER_CONFIG.KC135.respawnDelay = originalDelay
return success
end
-- Function to spawn emergency KC-135 MPRS with custom route (OLD METHOD - kept for compatibility)
function SpawnEmergencyTankerMPRS()
local originalDelay = TANKER_CONFIG.KC135_MPRS.respawnDelay
TANKER_CONFIG.KC135_MPRS.respawnDelay = TANKER_CONFIG.KC135_MPRS.emergencyRespawnDelay
local success = SpawnCustomRouteTanker(
TANKER_CONFIG.KC135_MPRS.callsign,
TANKER_CONFIG.KC135_MPRS,
"KC135_MPRS",
true
)
TANKER_CONFIG.KC135_MPRS.respawnDelay = originalDelay
return success
end
-- Function to display tanker status
function ShowTankerStatus()
local msg = "=== TANKER STATUS ===\n\n"
-- KC-135 Status
local kc135State = TANKER_STATE.KC135
if kc135State.active and kc135State.group and kc135State.group:IsAlive() then
local fuel = kc135State.group:GetFuel() * 100
local coord = kc135State.group:GetCoordinate()
local alt = coord:GetLandHeight() + coord.y
msg = msg .. string.format("%s: ACTIVE\n", TANKER_CONFIG.KC135.displayName)
msg = msg .. string.format(" Fuel: %.0f%%\n", fuel)
msg = msg .. string.format(" Altitude: FL%03d\n", math.floor(alt * 3.28084 / 100))
if TANKER_CONFIG.KC135.tacan then
msg = msg .. string.format(" TACAN: %s\n", TANKER_CONFIG.KC135.tacan)
end
if TANKER_CONFIG.KC135.frequency then
msg = msg .. string.format(" Radio: %s MHz\n", TANKER_CONFIG.KC135.frequency)
end
else
msg = msg .. string.format("%s: NOT ACTIVE\n", TANKER_CONFIG.KC135.displayName)
end
msg = msg .. "\n"
-- KC-135 MPRS Status
local mprsState = TANKER_STATE.KC135_MPRS
if mprsState.active and mprsState.group and mprsState.group:IsAlive() then
local fuel = mprsState.group:GetFuel() * 100
local coord = mprsState.group:GetCoordinate()
local alt = coord:GetLandHeight() + coord.y
msg = msg .. string.format("%s: ACTIVE\n", TANKER_CONFIG.KC135_MPRS.displayName)
msg = msg .. string.format(" Fuel: %.0f%%\n", fuel)
msg = msg .. string.format(" Altitude: FL%03d\n", math.floor(alt * 3.28084 / 100))
if TANKER_CONFIG.KC135_MPRS.tacan then
msg = msg .. string.format(" TACAN: %s\n", TANKER_CONFIG.KC135_MPRS.tacan)
end
if TANKER_CONFIG.KC135_MPRS.frequency then
msg = msg .. string.format(" Radio: %s MHz\n", TANKER_CONFIG.KC135_MPRS.frequency)
end
else
msg = msg .. string.format("%s: NOT ACTIVE\n", TANKER_CONFIG.KC135_MPRS.displayName)
end
MESSAGE:New(msg, 25):ToBlue()
end
-- Function to show custom route help
-- Function to show main tanker help
function ShowCustomRouteHelp()
local msg = "╔═══════════════════════════════════════════════╗\n"
msg = msg .. "║ TANKER MANAGEMENT SYSTEM - GUIDE ║\n"
msg = msg .. "╚═══════════════════════════════════════════════╝\n\n"
msg = msg .. "━━━ AVAILABLE TANKERS ━━━\n\n"
msg = msg .. string.format("• %s (SHELL)\n", TANKER_CONFIG.KC135.displayName)
msg = msg .. string.format(" TACAN: %s | Radio: %s MHz\n\n",
TANKER_CONFIG.KC135.tacan or "N/A", TANKER_CONFIG.KC135.frequency or "N/A")
msg = msg .. string.format("• %s (ARCO)\n", TANKER_CONFIG.KC135_MPRS.displayName)
msg = msg .. string.format(" TACAN: %s | Radio: %s MHz\n\n",
TANKER_CONFIG.KC135_MPRS.tacan or "N/A", TANKER_CONFIG.KC135_MPRS.frequency or "N/A")
msg = msg .. "━━━ SPAWN OPTIONS ━━━\n\n"
msg = msg .. "CUSTOM ROUTE:\n"
msg = msg .. " • Place numbered markers (SHELL1, SHELL2...)\n"
msg = msg .. " • Tanker spawns at airbase, taxis, takes off\n"
msg = msg .. " • Flies your route, loops back to WP1\n"
msg = msg .. " → See 'Custom Route Guide' for details\n\n"
msg = msg .. "EMERGENCY (Spawn Ahead):\n"
msg = msg .. " • Place marker: 'EMERGENCY TANKER'\n"
msg = msg .. " • Spawns 5km ahead, same altitude/heading\n"
msg = msg .. " • Flies straight 100nm then RTBs\n"
msg = msg .. " → See 'Emergency Guide' for details\n\n"
msg = msg .. "OTHER OPTIONS:\n"
msg = msg .. " • Reroute active tanker mid-flight\n"
msg = msg .. " • Check tanker status (fuel/position)\n"
msg = msg .. " • Auto-respawn if tanker is lost\n\n"
msg = msg .. "For detailed instructions, use:\n"
msg = msg .. " F10 → Tanker Operations → Help\n"
msg = msg .. " → Custom Route Guide\n"
msg = msg .. " → Emergency Tanker Guide\n"
MESSAGE:New(msg, 30):ToBlue()
end
-- Function to show custom route guide
function ShowCustomRouteGuide()
local msg = "╔═══════════════════════════════════════════════╗\n"
msg = msg .. "║ CUSTOM ROUTE TANKER GUIDE ║\n"
msg = msg .. "╚═══════════════════════════════════════════════╝\n\n"
msg = msg .. "━━━ HOW IT WORKS ━━━\n\n"
msg = msg .. "1. Place numbered map markers\n"
msg = msg .. "2. Launch from F10 → Custom Route menu\n"
msg = msg .. "3. Tanker spawns at airbase and taxis\n"
msg = msg .. "4. Takes off and flies to your waypoints\n"
msg = msg .. "5. Loops back to WP1 continuously\n\n"
msg = msg .. "━━━ MARKER SYNTAX ━━━\n\n"
msg = msg .. "Basic: SHELL1, SHELL2, SHELL3\n"
msg = msg .. "Altitude: SHELL1:FL180\n"
msg = msg .. "Speed: SHELL2::SP300\n"
msg = msg .. "Both: SHELL3:FL200:SP280\n"
msg = msg .. "RTB: SHELL4:RTB\n\n"
msg = msg .. "• Min 2 waypoints, max 10\n"
msg = msg .. "• Default: FL220 @ 330 knots\n"
msg = msg .. "• RTB lands at nearest friendly base\n"
msg = msg .. "• Markers auto-deleted after use\n\n"
msg = msg .. "━━━ EXAMPLES ━━━\n\n"
msg = msg .. "3-point circuit:\n"
msg = msg .. " ARCO1, ARCO2, ARCO3\n\n"
msg = msg .. "High altitude with RTB:\n"
msg = msg .. " SHELL1:FL280, SHELL2:FL280, SHELL3:RTB\n\n"
msg = msg .. "━━━ REROUTING ━━━\n\n"
msg = msg .. "Change route mid-flight:\n"
msg = msg .. " 1. Place new markers\n"
msg = msg .. " 2. F10 → Reroute Active Tanker\n"
msg = msg .. " 3. Tanker immediately follows new route\n"
MESSAGE:New(msg, 35):ToBlue()
end
-- Function to show emergency tanker guide
function ShowEmergencyGuide()
local msg = "╔═══════════════════════════════════════════════╗\n"
msg = msg .. "║ EMERGENCY TANKER GUIDE ║\n"
msg = msg .. "╚═══════════════════════════════════════════════╝\n\n"
msg = msg .. "━━━ SPAWN AHEAD (Recommended) ━━━\n\n"
msg = msg .. "For urgent refueling when egressing threats!\n\n"
msg = msg .. "HOW TO USE:\n"
msg = msg .. " 1. Place F10 marker: 'EMERGENCY TANKER' near your position.\n"
msg = msg .. " 2. F10 → Emergency Tanker → Spawn Ahead\n"
msg = msg .. " 3. Tanker spawns 5km in front of you\n"
msg = msg .. " 4. Same altitude & heading as you\n"
msg = msg .. " 5. Flies straight for 100nm\n"
msg = msg .. " 6. Auto RTBs to nearest base\n\n"
msg = msg .. "REQUIREMENTS:\n"
msg = msg .. " • Altitude: 10,000 - 45,000 ft\n"
msg = msg .. " • Marker within 50km of your position\n"
msg = msg .. " • System finds closest player to marker\n\n"
msg = msg .. "PRO TIP:\n"
msg = msg .. " Place marker BEFORE you desperately need it!\n"
msg = msg .. " You can pre-position it during ingress.\n\n"
msg = msg .. "━━━ EMERGENCY CUSTOM ROUTE ━━━\n\n"
msg = msg .. "Same as regular custom route but:\n"
msg = msg .. " • Uses numbered markers (SHELL1, etc.)\n"
msg = msg .. " • Faster respawn if lost (1 min vs 3 min)\n"
msg = msg .. " • Still spawns at airbase (not in-air)\n\n"
msg = msg .. "Use 'Spawn Ahead' for true emergencies!\n"
MESSAGE:New(msg, 35):ToBlue()
end
-- Function to reroute an active tanker with new waypoints
function RerouteTanker()
if not TANKER_STATE.KC135.active or not TANKER_STATE.KC135.group then
MESSAGE:New("KC-135 is not active! Spawn it first.", 10):ToBlue()
return
end
-- Scan for waypoint markers
local waypoints, markerIds = ScanForWaypointMarkers(TANKER_CONFIG.KC135.callsign)
if #waypoints < ROUTE_CONFIG.minWaypoints then
MESSAGE:New(string.format("Reroute requires at least %d waypoints!\nPlace markers: %s1, %s2, etc.",
ROUTE_CONFIG.minWaypoints, TANKER_CONFIG.KC135.callsign, TANKER_CONFIG.KC135.callsign), 15, "ERROR"):ToBlue()
return
end
-- Build new route
local routePoints = {}
local routeDesc = ""
local hasRTB = false
for i, wp in ipairs(waypoints) do
local parsed = ParseWaypointMarker(wp.markerText, TANKER_CONFIG.KC135.defaultAltitude, TANKER_CONFIG.KC135.defaultSpeed)
if parsed.rtb then
hasRTB = true
local lastPos = #routePoints > 0 and routePoints[#routePoints].coord or TANKER_STATE.KC135.group:GetCoordinate()
local nearestAirbase = lastPos:GetClosestAirbase(Airbase.Category.AIRDROME, coalition.side.BLUE)
if nearestAirbase then
local airbaseName = nearestAirbase:GetName()
local airbaseCoord = nearestAirbase:GetCoordinate()
routeDesc = routeDesc .. string.format("\n WP%d: RTB to %s", i, airbaseName)
table.insert(routePoints, {
coord = airbaseCoord,
altitude = 0,
speed = parsed.speed,
rtb = true,
airbase = nearestAirbase,
airbaseName = airbaseName
})
end
break
else
routeDesc = routeDesc .. string.format("\n WP%d: FL%03d @ %d kts",
i, math.floor(parsed.altitude / 100), parsed.speed)
table.insert(routePoints, {
coord = wp.coordinate,
altitude = parsed.altitude,
speed = parsed.speed,
rtb = false
})
end
end
-- Build task route
local taskRoute = {}
for i, rp in ipairs(routePoints) do
local wp
if rp.rtb and rp.airbase then
wp = rp.coord:WaypointAirLanding(
rp.speed * 0.514444,
rp.airbase:GetDCSObject(),
{},
"RTB"
)
else
wp = rp.coord:WaypointAirFlyOverPoint(
COORDINATE.WaypointAltType.BARO,
rp.speed * 0.514444,
rp.altitude * 0.3048,
{},
"WP" .. i
)
if not rp.rtb then
wp.task = {
id = "ComboTask",
params = {
tasks = {
{id = "Tanker", params = {}}
}
}
}
end
end
table.insert(taskRoute, wp)
end
-- Apply new route
TANKER_STATE.KC135.group:Route(taskRoute)
MESSAGE:New(string.format("%s accepting new route with %d waypoints:%s",
TANKER_CONFIG.KC135.displayName, #routePoints, routeDesc), 20):ToBlue()
-- Delete markers
if ROUTE_CONFIG.deleteMarkersAfterUse then
for _, markerId in ipairs(markerIds) do
trigger.action.removeMark(markerId)
end
end
env.info(string.format("[TANKER] Rerouted %s with %d waypoints", TANKER_CONFIG.KC135.displayName, #routePoints))
end
-- Function to reroute KC-135 MPRS
function RerouteTankerMPRS()
if not TANKER_STATE.KC135_MPRS.active or not TANKER_STATE.KC135_MPRS.group then
MESSAGE:New("KC-135 MPRS is not active! Spawn it first.", 10):ToBlue()
return
end
local waypoints, markerIds = ScanForWaypointMarkers(TANKER_CONFIG.KC135_MPRS.callsign)
if #waypoints < ROUTE_CONFIG.minWaypoints then
MESSAGE:New(string.format("Reroute requires at least %d waypoints!\nPlace markers: %s1, %s2, etc.",
ROUTE_CONFIG.minWaypoints, TANKER_CONFIG.KC135_MPRS.callsign, TANKER_CONFIG.KC135_MPRS.callsign), 15, "ERROR"):ToBlue()
return
end
local routePoints = {}
local routeDesc = ""
local hasRTB = false
for i, wp in ipairs(waypoints) do
local parsed = ParseWaypointMarker(wp.markerText, TANKER_CONFIG.KC135_MPRS.defaultAltitude, TANKER_CONFIG.KC135_MPRS.defaultSpeed)
if parsed.rtb then
hasRTB = true
local lastPos = #routePoints > 0 and routePoints[#routePoints].coord or TANKER_STATE.KC135_MPRS.group:GetCoordinate()
local nearestAirbase = lastPos:GetClosestAirbase(Airbase.Category.AIRDROME, coalition.side.BLUE)
if nearestAirbase then
local airbaseName = nearestAirbase:GetName()
local airbaseCoord = nearestAirbase:GetCoordinate()
routeDesc = routeDesc .. string.format("\n WP%d: RTB to %s", i, airbaseName)
table.insert(routePoints, {
coord = airbaseCoord,
altitude = 0,
speed = parsed.speed,
rtb = true,
airbase = nearestAirbase,
airbaseName = airbaseName
})
end
break
else
routeDesc = routeDesc .. string.format("\n WP%d: FL%03d @ %d kts",
i, math.floor(parsed.altitude / 100), parsed.speed)
table.insert(routePoints, {
coord = wp.coordinate,
altitude = parsed.altitude,
speed = parsed.speed,
rtb = false
})
end
end
local taskRoute = {}
for i, rp in ipairs(routePoints) do
local wp
if rp.rtb and rp.airbase then
wp = rp.coord:WaypointAirLanding(
rp.speed * 0.514444,
rp.airbase:GetDCSObject(),
{},
"RTB"
)
else
wp = rp.coord:WaypointAirFlyOverPoint(
COORDINATE.WaypointAltType.BARO,
rp.speed * 0.514444,
rp.altitude * 0.3048,
{},
"WP" .. i
)
if not rp.rtb then
wp.task = {
id = "ComboTask",
params = {
tasks = {
{id = "Tanker", params = {}}
}
}
}
end
end
table.insert(taskRoute, wp)
end
TANKER_STATE.KC135_MPRS.group:Route(taskRoute)
MESSAGE:New(string.format("%s accepting new route with %d waypoints:%s",
TANKER_CONFIG.KC135_MPRS.displayName, #routePoints, routeDesc), 20):ToBlue()
if ROUTE_CONFIG.deleteMarkersAfterUse then
for _, markerId in ipairs(markerIds) do
trigger.action.removeMark(markerId)
end
end
env.info(string.format("[TANKER] Rerouted %s with %d waypoints", TANKER_CONFIG.KC135_MPRS.displayName, #routePoints))
end
-- ============================================================================
-- MISSION MENU SETUP
-- ============================================================================
-- Create mission menu for tanker requests
-- Integrates with MenuManager to place under "Mission Options"
-- This keeps CTLD at F2 and AFAC at F3 as intended
if MenuManager and MenuManager.CreateCoalitionMenu then
-- Use MenuManager to create menu under "Mission Options"
MENU_TANKER_ROOT = MenuManager.CreateCoalitionMenu(coalition.side.BLUE, "Tanker Operations")
env.info("[TANKER] Using MenuManager - menu created under Mission Options")
else
-- Fallback: create root menu if MenuManager not available
MENU_TANKER_ROOT = MENU_COALITION:New(coalition.side.BLUE, "Tanker Operations")
env.warning("[TANKER] MenuManager not found - creating root menu (load MenuManager first!)")
end
-- Help submenu
local MENU_HELP = MENU_COALITION:New(
coalition.side.BLUE,
"Help",
MENU_TANKER_ROOT
)
MENU_COALITION_COMMAND:New(
coalition.side.BLUE,
"Quick Start Guide",
MENU_HELP,
ShowCustomRouteHelp
)
MENU_COALITION_COMMAND:New(
coalition.side.BLUE,
"Custom Route Guide",
MENU_HELP,
ShowCustomRouteGuide
)
MENU_COALITION_COMMAND:New(
coalition.side.BLUE,
"Emergency Tanker Guide",
MENU_HELP,
ShowEmergencyGuide
)
-- Custom route submenu
local MENU_CUSTOM_ROUTE = MENU_COALITION:New(
coalition.side.BLUE,
"Custom Route",
MENU_TANKER_ROOT
)
MENU_COALITION_COMMAND:New(
coalition.side.BLUE,
string.format("Launch %s (%s markers)", TANKER_CONFIG.KC135.displayName, TANKER_CONFIG.KC135.callsign),
MENU_CUSTOM_ROUTE,
SpawnCustomTanker
)
MENU_COALITION_COMMAND:New(
coalition.side.BLUE,
string.format("Launch %s (%s markers)", TANKER_CONFIG.KC135_MPRS.displayName, TANKER_CONFIG.KC135_MPRS.callsign),
MENU_CUSTOM_ROUTE,
SpawnCustomTankerMPRS
)
-- Reroute submenu for changing active tanker routes
local MENU_REROUTE = MENU_COALITION:New(
coalition.side.BLUE,
"Reroute Active Tanker",
MENU_CUSTOM_ROUTE
)
MENU_COALITION_COMMAND:New(
coalition.side.BLUE,
string.format("Reroute %s (%s markers)", TANKER_CONFIG.KC135.displayName, TANKER_CONFIG.KC135.callsign),
MENU_REROUTE,
RerouteTanker
)
MENU_COALITION_COMMAND:New(
coalition.side.BLUE,
string.format("Reroute %s (%s markers)", TANKER_CONFIG.KC135_MPRS.displayName, TANKER_CONFIG.KC135_MPRS.callsign),
MENU_REROUTE,
RerouteTankerMPRS
)
-- Emergency spawns submenu
local MENU_EMERGENCY = MENU_COALITION:New(
coalition.side.BLUE,
"Emergency Tanker",
MENU_TANKER_ROOT
)
-- Emergency spawn ahead of player (NEW PRIMARY METHOD)
MENU_COALITION_COMMAND:New(
coalition.side.BLUE,
string.format("Emergency %s (Spawn Ahead)", TANKER_CONFIG.KC135.displayName),
MENU_EMERGENCY,
SpawnEmergencyTankerAhead
)
MENU_COALITION_COMMAND:New(
coalition.side.BLUE,
string.format("Emergency %s (Spawn Ahead)", TANKER_CONFIG.KC135_MPRS.displayName),
MENU_EMERGENCY,
SpawnEmergencyTankerMPRSAhead
)
-- Emergency spawn with custom route (OLD METHOD - kept for advanced users)
local MENU_EMERGENCY_ROUTE = MENU_COALITION:New(
coalition.side.BLUE,
"Emergency Custom Route",
MENU_EMERGENCY
)
MENU_COALITION_COMMAND:New(
coalition.side.BLUE,
string.format("%s (%s markers)", TANKER_CONFIG.KC135.displayName, TANKER_CONFIG.KC135.callsign),
MENU_EMERGENCY_ROUTE,
SpawnEmergencyTanker
)
MENU_COALITION_COMMAND:New(
coalition.side.BLUE,
string.format("%s (%s markers)", TANKER_CONFIG.KC135_MPRS.displayName, TANKER_CONFIG.KC135_MPRS.callsign),
MENU_EMERGENCY_ROUTE,
SpawnEmergencyTankerMPRS
)
-- Status and info
MENU_COALITION_COMMAND:New(
coalition.side.BLUE,
"Tanker Status Report",
MENU_TANKER_ROOT,
ShowTankerStatus
)
-- ============================================================================
-- EVENT HANDLER REGISTRATION
-- ============================================================================
BlueTankerEventHandler:HandleEvent(EVENTS.Birth)
BlueTankerEventHandler:HandleEvent(EVENTS.Dead)
BlueTankerEventHandler:HandleEvent(EVENTS.Crash)
BlueTankerEventHandler:HandleEvent(EVENTS.EngineShutdown)
BlueTankerEventHandler:HandleEvent(EVENTS.Hit)
env.info("[TANKER] Tanker Management System initialized")
MESSAGE:New("Tanker Management System online - Use F10 menu to request tankers", 15):ToBlue()