Back to Index Page generated: Nov 12, 2024, 11:02:04 PM

Expansion Traffic Control

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Introduces the friendly voice of the main station traffic controller, who welcomes ships approaching the planet, guides them into the correct docking maneuvers and even says goodbye when they leave. Just don't linger in the station approach lane after you leave and get in the way, or else they won't be best pleased... Recommended for new players learning how to dock manually, and for experienced players who want a bit of extra ambiance around main stations. Most messages from this OXP appear only if your ship does not have functional docking computers, but a few appear regardless, for example: Clear the lane, Commander! If you see a message that starts with 'Traffic Control:' then it is probably from this OXP. This OXP is neutral for beginners and slightly biased against the experienced player, because it enforces (with fines and small but escalating bounties) a sensible rule against the use of cloaking devices in proximity to main stations... however, to be fair, it will not impose penalties if you pass undetected (or at least unidentified). Of course, requesting docking clearance or activating docking computers will transmit your identity to the station... This OXP is compatible with all other OXPs, and includes considerations for Buoy Repair, ILS and AutoDock if those OXPs are also installed. Introduces the friendly voice of the main station traffic controller, who welcomes ships approaching the planet, guides them into the correct docking maneuvers and even says goodbye when they leave. Just don't linger in the station approach lane after you leave and get in the way, or else they won't be best pleased... Recommended for new players learning how to dock manually, and for experienced players who want a bit of extra ambiance around main stations. Most messages from this OXP appear only if your ship does not have functional docking computers, but a few appear regardless, for example: Clear the lane, Commander! If you see a message that starts with 'Traffic Control:' then it is probably from this OXP. This OXP is neutral for beginners and slightly biased against the experienced player, because it enforces (with fines and small but escalating bounties) a sensible rule against the use of cloaking devices in proximity to main stations... however, to be fair, it will not impose penalties if you pass undetected (or at least unidentified). Of course, requesting docking clearance or activating docking computers will transmit your identity to the station... This OXP is compatible with all other OXPs, and includes considerations for Buoy Repair, ILS and AutoDock if those OXPs are also installed.
Identifier oolite.oxp.Thargoid.TrafficControl oolite.oxp.Thargoid.TrafficControl
Title Traffic Control Traffic Control
Category Mechanics Mechanics
Author Thargoid, Milo Thargoid, Milo
Version 2.02 2.02
Tags mechanics mechanics
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL http://wiki.alioth.net/index.php/Traffic_Control_OXP n/a
Download URL https://wiki.alioth.net/img_auth.php/2/22/Oolite.oxp.Thargoid.TrafficControl.2.02.oxz n/a
License Creative Commons Attribution - Non-Commercial - Share Alike 4.0 license with clauses - see readme file Creative Commons Attribution - Non-Commercial - Share Alike 4.0 license with clauses - see readme file
File Size n/a
Upload date 1626493229

Documentation

Also read http://wiki.alioth.net/index.php/Traffic%20Control

Traffic Control Readme & License.txt

Traffic Control OXP by Thargoid, now maintained by Milo

A small script to help new Jamesons, plus the occasional old timer who's been relying on docking computers for years, and suddenly finds out how much when they get broken.

Introduces the friendly voice of the main station traffic controller, who welcomes ships approaching the planet, guides them into the correct docking maneuvers and even says goodbye when they leave. Just don't linger in the station approach lane after you leave and get in the way, or else they won't be best pleased...

Available to everyone, although as they're busy people if they detect docking computers fitted to your ship they'll assume those will be used and won't bother you.

Recommended for new players learning how to dock manually, and for experienced players who want a bit of extra ambiance around main stations. Most messages from this OXP appear only if your ship does not have functional docking computers, but a few appear regardless, for example: Clear the lane, Commander! If you see a message that starts with 'Traffic Control:' then it is probably from this OXP.

This OXP is neutral for beginners and slightly biased against the experienced player, because it enforces (with fines and small but escalating bounties) a sensible rule against the use of cloaking devices in proximity to main stations... however, to be fair, it will not impose penalties if you pass undetected (or at least unidentified). Of course, requesting docking clearance or activating docking computers will transmit your identity to the station...

This OXP is compatible with all other OXPs, and includes considerations for Buoy Repair, ILS and AutoDock if those OXPs are also installed.

--------------------------------------------------------------

License:

This OXP is released under the Creative Commons Attribution - Non-Commercial - Share Alike 4.0 license with the following clauses:

* Whilst you are free (and encouraged) to re-use any of the scripting, models or texturing in this OXP, the usage must be distinct from that within this OXP. Unique identifiers such as (but not limited to) unique shipdata.plist entity keys, mission variables, script names (this.name), equipment identity strings (EQ_), description list arrays and entity roles must not be re-used without prior agreement. Basically if it's unique or would identify or overwrite anything in the original OXP, then you may not re-use it (for obvious compatibility reasons).
* rebundling of this OXP within another distribution is permitted as long as it is unchanged. The following derivates however are permitted and except from the above:
	* the conversion of files between XML and openStep.
	* the merging of files with other files of the same type from other OXPs.
* The license information (either as this file or merged into a larger one) must be included in the OXP.
* Even though it is not compulsory, if you are re-using any sizable or recognisable piece of this OXP, please let me know :)

--------------------------------------------------------------

Instructions:

Move the file "oolite.oxp.Thargoid.TrafficControl.2.02.oxz" to the AddOns directory of your Oolite installation.

Remove any older versions of this OXP from the AddOns directory.

Then start the game up whilst holding down the shift key (until the spinning Cobra Mk II screen appears).

--------------------------------------------------------------

Version history:

09/10/2008 - Version 1.00, Initial release.
04/11/2008 - Version 1.01, scripting update for v1.72+ compatibility.
24/05/2010 - Version 1.10, fortified equipment damage checking for 1.74
13/02/2011 - Version 1.11, removal of upper limit, to allow running with 1.75
08/07/2020 - Version 2.00, updated by Milo; set minimum Oolite version to 1.88; fixed errors if navigation buoy is destroyed; corrected planet approach guidance (follow green circle, not square, if ship has a basic compass); added additional hints for new players (until they buy docking computers); set up recurring reminders to "clear the lane" when not supposed to be there; integrated docking advice with docking clearance protocol for main stations that require clearance; added warning messages to fugitive players entering station aegis or requesting docking clearance; added responses to being detected using cloaking devices in the station aegis (this OXP now declares it illegal to cloak within main station aegis and introduces a small bounty for the first offence, which will be increased for non-fugitive players each time they are detected still cloaked after the initial offence; however, entering the aegis cloaked and never interacting with the station will not impose a penalty as the traffic controllers are unaware of the trespass); added a 1000 credit fine (or equivalent bounty) to the arrival report when docking at main stations while cloaked (even if fast-docking is used); added considerations for nova systems (traffic control will leave a recording when sun has gone nova) and for Buoy Repair, ILS and AutoDock OXPs
07/11/2021 - Version 2.01, updated by Milo; fix bug reported by dybal (docking clearance "granted" status was not correctly detected, resulting in unintended behavior, particularly noticeable when combined with ILS OXP)
07/16/2021 - Version 2.02, updated by Milo; reduced the frequency of some messages, changed one word to be more situationally neutral, and fixed a log warning involving percent symbols; updated license from CC 3.0 to CC 4.0

Equipment

This expansion declares no equipment.

Ships

This expansion declares no ships.

Models

This expansion declares no models.

Scripts

Path
Config/script.js
this.name					= "TrafficControl";
this.author					= "Thargoid, Milo";
this.copyright				= "Creative Commons Attribution - Non-Commercial - Share Alike 3.0 listationPose with clauses - see readme.txt.";
this.description			= "Advice from station for docking, reminders to clear the lane, and penalties for using cloaking devices in or near main stations";
this.version				= "2.02";

this.startUp = function() {
	// initialize all variables
	this.shipWillEnterWitchspace();
}

this.shipExitedStationAegis = this.shipDied = function() {
	// stop timers
	this._stopTrafficControlTimers();

	// reset most variables when leaving station aegis
	this.$hintRequest = false;                   // reset request docking clearance hint do-once flag
	this.$hintExtension = false;                 // reset request clearance extension hint do-once flag
	this.$buoyDistance = 0;                      // clear saved buoy distance (used to suppress message repetition while player is moving towards the buoy as instructed)
	this.$undetected = false;                    // clear flag (set to true if the player arrives in the aegis cloaked and has not yet been detected -- also treated as criminal)
	this.$hintILS = false;                       // reset using ILS flag (used to suppress message repetition when player ship is being auto-steered by ILS)
}

this.shipWillEnterWitchspace = function() {
	this.shipExitedStationAegis();               // make sure we do all of the resets (necessary here because the player could initiate a jump while still inside the aegis)
	this.$autopilotStationForDocking = null;     // clear variable set by this.playerStartedAutoPilot to be checked by this.playerCancelledAutoPilot

	// reset this flag only when leaving system
	this.$cloakFirstOffence = false;             // clear status flag (set to true if player cloaks within aegis after being seen by traffic control -- treated as a criminal offence)
}

this._stopTrafficControlTimers = function() { // called in variety of circumstances to halt traffic control guidance
	if ( this.$approachBuoyTimer )      this.$approachBuoyTimer.stop();
	if ( this.$aimAtStationTimer )      this.$aimAtStationTimer.stop();
	if ( this.$approachStationTimer )   this.$approachStationTimer.stop();
	if ( this.$clearDockingLaneTimer )  this.$clearDockingLaneTimer.stop(); // this is done in this._buoyApproach instead
}

this.shipWillDockWithStation = function(station) { // separated out from above event handlers to apply penalties when docking with cloak enabled
	var sun = system.sun;
	// internal function handleAutopilotOn in playerEntityControls handles fast docking, and sets ship_clock_adjust += 1200.0 before calling enterDock, which calls shipWillDockWithStation
	//if ( clock.isAdjusting && (clock.adjustedSeconds - clock.seconds) === 1200.0 ) // we are fast docking ... but it actually does not matter for what we are currently doing, hence commented this out
	if ( station !== system.mainStation ) {
		return; // traffic control only at main station
	} else if ( sun && sun.isValid && sun.hasGoneNova ) {
		return; // traffic control absent due to nova
	} else if ( station.suppressArrivalReports ) {
		return; // don't apply penalty if we can't inform the player
	} else if ( player.ship.isCloaked ) {
		if ( player.credits >= 1000 ) {
			player.credits -= 1000;
			player.addMessageToArrivalReport(expandDescription("For docking with a cloaking device activated in violation of GalCop Traffic Control regulations, you have been fined [amount|icr].", { amount : 1000 }));
		} else {
			player.addMessageToArrivalReport(expandDescription("For docking with a cloaking device activated in violation of GalCop Traffic Control regulations, you have been fined [amount|icr]. As you were unable to pay in full, a bounty has been placed on you for the remainder.", { amount : 1000 }));
			player.bounty += 1000 - Math.floor(player.credits);
			player.credits = 0;
		}
	}
	this.shipExitedStationAegis(station);        // reset most variables and stop timers when docking with station
	this.$autopilotStationForDocking = null;     // clear variable set by this.playerStartedAutoPilot to be checked by this.playerCancelledAutoPilot
}

this.shipEnteredPlanetaryVicinity = function(planet) { // called by the game, could be before or after this.shipEnteredStationAegis or not at all (if planet is small and player approaches station)
	var ps = player.ship, ms = system.mainStation, sun = system.sun;
	if ( ps.isCloaked ) {
		return; // traffic control cannot see cloaked ships, so no guidance can be provided
	} else if ( planet.isMainPlanet && ms && ms.isValid ) { // also check that there is a main station, just in case ...
		if ( sun && sun.isValid && sun.hasGoneNova ) {
			player.commsMessage(system.name + " Traffic Control: This is a recorded message. Due to inhospitable solar activity, we are unable to assist you.", 5);
			return; // no guidance available
		// otherwise, provide guidance based on compass type, not specific equipment (the following message is one of the few that will always be shown with this OXP)
		} else if ( ps.compassType === "OO_COMPASSTYPE_ADVANCED" ) {
			player.commsMessage(system.name + " Traffic Control: For station approach, please select and follow the beacon N on your compass.", 20);
		} else {
			player.commsMessage(system.name + " Traffic Control: For station approach, please follow the green solid circle on your compass.", 20);
		}
	}
}

this.shipEnteredStationAegis = function(station) { // called either by the game or by this.playerRequestedDockingClearance (if player arrived cloaked, de-cloaked, then requested clearance)
	var ps = player.ship, sun = system.sun;
	// extra traffic control instructions are only enabled at main stations when the player doesn't have a docking computer
	if ( ps.isCloaked ) {
		this.$undetected = true; // checked in this.playerRequestedDockingClearance, this.playerStartedAutoPilot
		return; // traffic control cannot see cloaked ships, so none of the timers will be started if the player enters the aegis already cloaked
	} else if ( !station.isMainStation ) {
		return; // traffic control is only at main stations
	} else if ( player.alertCondition === 3 ) { // red alert
		return; // no traffic control during combat
	} else if ( sun && sun.isValid && sun.hasGoneNova ) {
		player.commsMessage(system.name + " Traffic Control: This is a recorded message. Due to inhospitable solar activity, we are unable to assist you.", 5);
		return; // no guidance available
	} else if ( player.bounty > 50 ) { // Fugitive, not cloaked (based on earlier check), this is another scenario where we always send a message (not restricted to ships without docking computers)
		player.commsMessage(system.name + " Traffic Control: Your ship has been flagged for criminal violations. Docking here is not authorized.", 15); // show this even if they have a docking computer
		return; // no guidance available
	} else if ( ps.equipmentStatus("EQ_DOCK_COMP") !== "EQUIPMENT_OK" ) { // player does not have a docking computer, so traffic control will assist
		if ( sun && sun.isValid && sun.isGoingNova ) {
			player.commsMessage(system.name + " Traffic Control: Better move fast, Commander. Our solar readings are heating up.", 5);
		}
		var buoysNearStation = system.entitiesWithScanClass("CLASS_BUOY", station, 15000);
		if ( buoysNearStation.length === 1 ) { // check that the buoy exists
			player.commsMessage(system.name + " Traffic Control: Please fly to the station buoy to begin docking maneuvers.", 15);
			if ( this.$approachBuoyTimer ) { // reuse the timer if we created it earlier in the same game session
				this.$approachBuoyTimer.start();
			} else { // otherwise, once per game session, make a new timer to direct the player to fly to the buoy
				this.$approachBuoyTimer = new Timer(this, this._buoyApproach, 16, 8); // delay 16 seconds, then trigger this._buoyApproach every 8 seconds
			}
		} else { // no buoy there, or more than one (any OXPs that add extra buoys near the main station would be a conflict for now)
			player.commsMessage(system.name + " Traffic Control: we are unable to assist you.", 15);
			if ( buoysNearStation.length === 0 && worldScripts.buoyRepair ) { // no buoy, but a replacement will come
				var keyToPress = expandDescription("[oolite_key_docking_clearance_request]");
				player.consoleMessage("(A replacement buoy has been ordered. To request docking assistance after it arrives, target the station and press '" + (keyToPress === "L" ? "Shift-L" : keyToPress) + "').", 10);
			}
		}
	}
}

this.playerRequestedDockingClearance = function(message) { // called by the game - the player can request or cancel clearance at any time, with various prior conditions
	this.$hintExtension = false; // reset dock clearance request extension hint
	var ps = player.ship, station = ps.target, ms = system.mainStation, sun = system.sun; // must be a valid station to have called this event handler
	if ( !ms || !ms.isValid ) {
		return; // main station doesn't exist (interstellar) or was destroyed (request presumably was directed to another station so traffic control is not involved)
	} else if ( sun && sun.isValid && sun.hasGoneNova ) {
		player.commsMessage(system.name + " Traffic Control: This is a recorded message. Due to inhospitable solar activity, we are unable to assist you.", 5);
		this._stopTrafficControlTimers();
		return; // no guidance available
	} else if ( !ps.withinStationAegis ) { // withinStationAegis checks specifically the main station aegis, no other stations qualify
		return; // no main station in the system or player ship is too far away, so traffic control is not involved
	} else if ( !station || !station.isValid || !station.isStation ) { // sanity check, this shouldn't happen unless Oolite internal code changes
		log(this.name, "playerRequestedDockingClearance event handler called but player.ship.target is not a station, Traffic Control aborted.");
		this._stopTrafficControlTimers();
		return; // no guidance available
	} else if ( !station.isMainStation ) {
		return; // clearance request was not directed to the main station
	// conditions below handle a docking request that was directed to the main station
	} else if ( message === "DOCKING_CLEARANCE_DENIED_SHIP_HOSTILE" ) { // player.dockingClearanceStatus will be "DOCKING_CLEARANCE_STATUS_NONE"
		this._stopTrafficControlTimers(); // no more unprompted traffic control messages if the player is hostile to the station
		return;
	} else if ( message === "DOCKING_CLEARANCE_DENIED_SHIP_FUGITIVE" ) { // player.dockingClearanceStatus will be "DOCKING_CLEARANCE_STATUS_NONE"
		// the game itself imposes an unauthorized docking penalty of 5% of credits capped at 5000 for docking without clearance, and fugitives never receive clearance
		player.commsMessage("Unauthorized docking will incur a fine of up to 5000 credits.", 5); // supplement the game's refusal message from the station with extra info for the benefit of new players (don't restrict message to show only for ships without docking computers because new players may not become a fugitive for the first time until after they've acquired a docking computer, and a supplemental message here shouldn't bother experienced players who know already that it's pointless to request docking clearance as a fugitive)
		if ( ps.isCloaked || this.$undetected ) { // special treatment for cloaked fugitives; cloaked ships still interact normally with docking protocol as of Oolite 1.89, so we assume they transmit their identity
			if ( !this.$cloakFirstOffence ) { // this variable serves as a do-once switch
				player.commsMessage(system.name + " Traffic Control: Fugitive " + player.name + ", using a cloaking device near a GalCop station is illegal! This offence has been added to your record.", 15);
				player.bounty += 13;
				this.$cloakFirstOffence = true;
				this.$undetected = false;
			} else { // not the first time detected using cloak within the aegis
				player.commsMessage(system.name + " Traffic Control: Fugitive " + player.name + ", deactivate your cloaking device immediately!", 5);
				// do not increase the bounty for fugitives because we don't want this to be used as an exploit to reach high levels of infamy
			}
		}
		this._stopTrafficControlTimers(); // no more unprompted traffic control messages if the player is a fugitive
		return;
	} else if ( ps.isCloaked || this.$undetected ) { // cloaked ships still interact normally with docking protocol as of Oolite 1.89, so we assume they transmit their identity (and this is not a fugitive because they are handled above)
		if ( !this.$cloakFirstOffence ) { // this variable serves as a do-once switch
			player.commsMessage(system.name + " Traffic Control: Using a cloaking device within the station aegis is against GalCop regulations! This offence has been noted on your record.", 15);
			player.bounty += 13;
			this.$cloakFirstOffence = true;
			this.$undetected = false;
		} else { // not the first time detected using cloak within the aegis
			player.commsMessage(system.name + " Traffic Control: Deactivate your cloaking device immediately! Your continued non-compliance has been reported.", 15);
			player.bounty += 13; // increase each time non-fugitive player requests clearance while cloaked (from 0 bounty, 3 cloaked requests would reach 52; max would be 49 + 13 = 62, because > 50 is fugitive, handled earlier)
		}
		if ( !ps.isCloaked ) { // non-fugitive who is no longer cloaked, requesting docking clearance after arriving in the main station aegis already cloaked
			this.shipEnteredStationAegis(ms); // behave as if they just arrived for the first time, as we did not see them before
		}
		return; // otherwise, traffic control cannot see cloaked ships, so we can't reach the below conditions for re-activating docking guidance
	} else if ( message === "DOCKING_CLEARANCE_CANCELLED" ) { // player cancelled clearance request, player.dockingClearanceStatus will be "DOCKING_CLEARANCE_STATUS_NONE"
		// this condition is handled in the timer callbacks (this._aimAtStation and this._approachStation) ... but if they were in this._buoyApproach, stop it now
		if ( this.$approachBuoyTimer ) this.$approachBuoyTimer.stop(); // they evidently know how to request clearance again if they want it
		return;
	} else if ( message === "DOCKING_CLEARANCE_DENIED_NO_DOCKS" ) { // player.dockingClearanceStatus will be "DOCKING_CLEARANCE_STATUS_NONE"
		this._stopTrafficControlTimers(); // no extra messages from traffic control are needed, the request rejection message is enough
		// start timer to keep reminding them to stay out of the lane until they leave the aegis
		if ( this.$clearDockingLaneTimer ) { // reuse the timer if we created it earlier in the same game session
			this.$clearDockingLaneTimer.start();
		} else { // otherwise, once per game session, make a new timer to remind to leave the docking area if they are blocking it
			this.$clearDockingLaneTimer = new Timer(this, this._laneClearReminder, 15, 15);
		}
		return;
	} else if ( ps.equipmentStatus("EQ_DOCK_COMP") === "EQUIPMENT_OK" ) {
		return; // no docking guidance if player has a working docking computer (might have been repaired in-flight)
	} else if ( message === "DOCKING_CLEARANCE_EXTENDED" ) { // extension granted by main station, player.dockingClearanceStatus will be "DOCKING_CLEARANCE_STATUS_GRANTED"
		return; // this condition is handled in the timer callbacks (this._aimAtStation and this._approachStation)
	//} else if ( message === "DOCKING_CLEARANCE_NOT_REQUIRED" ) {
		// player.dockingClearanceStatus will be "DOCKING_CLEARANCE_STATUS_NOT_REQUIRED"
	//} else if ( message === "DOCKING_CLEARANCE_DENIED_TRAFFIC_INBOUND" || message === "DOCKING_CLEARANCE_DENIED_TRAFFIC_OUTBOUND" ) {
		// not really refused, still in queue, player.dockingClearanceStatus will be "DOCKING_CLEARANCE_STATUS_REQUESTED"
	//} else if ( message === "DOCKING_CLEARANCE_GRANTED" ) {
		// player.dockingClearanceStatus will be "DOCKING_CLEARANCE_STATUS_GRANTED"
	} else if ( (!this.$approachBuoyTimer    || !this.$approachBuoyTimer.isRunning) &&
				(!this.$aimAtStationTimer    || !this.$aimAtStationTimer.isRunning) &&
				(!this.$approachStationTimer || !this.$approachStationTimer.isRunning) ) {
		// traffic control is not currently assisting, and clearance is not required, has been granted, or player is queued (one of the above commented-out conditions)
		if ( this.$approachBuoyTimer ) { // reuse the timer if we created it earlier in the same game session
			this.$approachBuoyTimer.start();
		} else { // otherwise, once per game session, make a new timer to direct the player to fly to the buoy
			this.$approachBuoyTimer = new Timer(this, this._buoyApproach, 4, 8); // delay 4 seconds, then trigger this._buoyApproach every 8 seconds
		}
		return;
	}
}

this._buoyApproach = function() { // timer callback
	var ps = player.ship, ms = system.mainStation, sun = system.sun;
	if ( this.$clearDockingLaneTimer ) this.$clearDockingLaneTimer.stop(); // don't give conflicting guidance!
	if ( !ps.isValid || !ms.isValid ) {
		this.$approachBuoyTimer.stop(); // player ship or main station was destroyed
		return;
	} else if ( sun && sun.isValid && sun.hasGoneNova ) {
		player.commsMessage(system.name + " Traffic Control: This is a recorded message. Due to inhospitable solar activity, we are unable to assist you.", 5);
		this.$approachBuoyTimer.stop();
		return; // no guidance available
	} else if ( ps.isCloaked ) { // we are in a timer callback that can only start if the player requested docking clearance or was visible when they entered the aegis (or launched from the station)
		if ( !this.$cloakFirstOffence ) { // traffic control saw the player before they cloaked (after launch, or when they first arrived in the aegis) - this variable also serves as a do-once switch
			player.commsMessage(system.name + " Traffic Control: Using a cloaking device within the station aegis is against GalCop regulations! This offence has been noted.", 15);
			player.bounty += 13;
			this.$cloakFirstOffence = true;
		}
		return; // traffic control cannot see cloaked ships, so unsure if the player is still around (but keep the timer running in case they de-cloak before leaving the aegis)
	} else if ( player.alertCondition === 3 ) { // red alert
		return; // no traffic control during combat (but keep the timer running)
	} else if ( this.$autopilotStationForDocking !== null ) {
		return; // no traffic control while autopilot is engaged (for compatibility with AutoDock and other autopilot OXPs), but keep timer running in case they cancel it
	} else { // instruct to approach the buoy, or hand off to _aimAtStation if they arrived or they are using ILS and have (or don't need) clearance
		var buoysNearStation = system.entitiesWithScanClass("CLASS_BUOY", ms, 15000);
		if ( buoysNearStation.length === 1 ) {
			this.$stationBuoy = buoysNearStation[0]; // take the first (and only) buoy -- we need a buoy to determine where the approach lane is
			var buoyDistance = ps.position.distanceTo(this.$stationBuoy); // how far the player ship is from the buoy
			if ( ps.speed > 0 && !ps.missilesOnline && ps.target === ms && ps.equipmentStatus("EQ_ILS") === "EQUIPMENT_OK" ) {
				// has ILS, is moving, is not in missile targeting mode (which disables ILS), and is targeting the main station -- ILS will auto-steer to dock
				if ( ms.requiresDockingClearance === true && player.dockingClearanceStatus !== "DOCKING_CLEARANCE_STATUS_GRANTED" && player.dockingClearanceStatus !== "DOCKING_CLEARANCE_STATUS_TIMING_OUT" ) {
					ps.target = this.$stationBuoy; // if not cleared to dock, target the buoy (not null, because telescope in grav mode would re-target) to stop ILS unauthorized docking
					return;
				}
				// else, player has or doesn't need clearance, continue with ILS, fall through and transition to next timer
			} else if ( buoyDistance < 1000 ) { // player has arrived at the buoy
				if ( ps.position.distanceTo(ms) > ms.position.distanceTo(this.$stationBuoy) ) {
					player.commsMessage(system.name + " Traffic Control: Please navigate around to the station-facing side of the buoy.", 4);
					return; // wait for compliance (go around)
				} else if ( ps.speed > 0 ) { // suppress the following message if the player is not moving
					player.commsMessage(system.name + " Traffic Control: Hold position and re-orient towards the station docking bay.", 4);
					return; // wait for compliance (hold position)
				}
				// fall through and transition to next timer (if passed above checks)
			} else if ( this.$buoyDistance && buoyDistance >= this.$buoyDistance ) { // player is not approaching
				player.commsMessage(system.name + " Traffic Control: Please navigate towards the station-facing side of the buoy.", 4);
				this.$buoyDistance = buoyDistance; // save current distance for comparison on the next timer update
				return; // wait for compliance
			}
			// transition to next timer (_aimAtStation)
			this.$hintRequest = this.$hintExtension = false; // reset hint displayed flags for next timer
			this.$approachBuoyTimer.stop();
			this.$buoyDistance = 0; // clear saved buoy distance
			if ( this.$aimAtStationTimer ) { // reuse the timer if we created it earlier in the same game session
				this.$aimAtStationTimer.start();
			} else { // otherwise, once per game session, make a new timer to direct the player to aim at the station
				this.$aimAtStationTimer = new Timer(this, this._aimAtStation, 4, 8); // delay 4 seconds, then trigger this._aimAtStation every 8 seconds
			}
			return;
		} else { // the nav buoy has been destroyed since initial check
			this.$approachBuoyTimer.stop();
			player.commsMessage(system.name + " Traffic Control: Assistance is not available at this time.", 15);
			if ( worldScripts.buoyRepair ) {
				var keyToPress = expandDescription("[oolite_key_docking_clearance_request]");
				player.consoleMessage("(A replacement buoy has been ordered. To request docking assistance after it arrives, target the station and press '" + (keyToPress === "L" ? "Shift-L" : keyToPress) + "').", 10);
			}
			return;
		}
	}
}

this._aimAtStation = function() { // timer callback
	var ps = player.ship, ms = system.mainStation, sun = system.sun;
	if ( this.$clearDockingLaneTimer ) this.$clearDockingLaneTimer.stop(); // don't give conflicting guidance!
	if ( sun && sun.isValid && sun.hasGoneNova ) {
		player.commsMessage(system.name + " Traffic Control: This is a recorded message. Due to inhospitable solar activity, we are unable to assist you.", 5);
		this.$aimAtStationTimer.stop();
		return; // no guidance available
	} else if ( !this._getVectors() ) {
		this._stopTrafficControlTimers(); // player ship, nav buoy or main station was destroyed
		return;
	} else if ( ps.isCloaked ) { // we are in a timer callback that can only start if the player requested docking clearance or was visible when they entered the aegis (or launched from the station)
		if ( !this.$cloakFirstOffence ) { // this variable serves as a do-once switch
			player.commsMessage(system.name + " Traffic Control: Using a cloaking device within the station aegis is against GalCop regulations! This offence has been noted.", 15);
			player.bounty += 13;
			this.$cloakFirstOffence = true;
		}
		return; // traffic control cannot see cloaked ships, so unsure if the player is still around (but keep the timer running in case they de-cloak before leaving the aegis)
	} else if ( player.alertCondition === 3 ) { // red alert
		return; // no traffic control during combat (but keep the timer running)
	} else if ( this.$autopilotStationForDocking !== null ) {
		return; // no traffic control while autopilot is engaged (for compatibility with AutoDock and other autopilot OXPs), but keep timer running in case they cancel it
	} else if ( ms.requiresDockingClearance === true ) {
		switch (player.dockingClearanceStatus) {
			case "DOCKING_CLEARANCE_STATUS_NONE": // could be because the player never asked or because they cancelled; if they cancelled, they know how to ask again, but in case they never asked, give a hint
				if ( !this.$hintRequest ) {
					this.$hintRequest = true; // suppress repetition
					var keyToPress = expandDescription("[oolite_key_docking_clearance_request]");
					if ( ps.target === ms ) {
						player.commsMessage(system.name + " Traffic Control: Docking clearance is required (you have the station targeted, so just press '" + (keyToPress === "L" ? "Shift-L" : keyToPress) + "').", 15);
					} else {
						player.commsMessage(system.name + " Traffic Control: Docking clearance is required (target the station and press '" + (keyToPress === "L" ? "Shift-L" : keyToPress) + "').", 15);
						if ( ps.missiles.length && ps.target && ps.target.isValid && ps.target.isShip && ps.equipmentStatus("EQ_ILS") === "EQUIPMENT_OK" ) {
							player.consoleMessage(expandDescription("(If ILS redirects your target to a ship, you can target the station with missile lock '[oolite_key_target_missile]' instead of ident '[oolite_key_ident_system]'.)"), 10);
						}
					}
				} else if ( ps.speed > 0 && !ps.missilesOnline && ps.target === ms && ps.equipmentStatus("EQ_ILS") === "EQUIPMENT_OK" ) {
					// has ILS, is moving, is not in missile targeting mode (which disables ILS), and is targeting the main station -- ILS will auto-steer to dock, ignoring lack of clearance
					ps.target = this.$stationBuoy; // if not cleared to dock, target the buoy (not null, because telescope in grav mode would re-target) to stop ILS unauthorized docking
				}
				return; // stay in this timer, waiting for them to request clearance or fly out of the aegis
			case "DOCKING_CLEARANCE_STATUS_REQUESTED": // they might have cancelled and re-requested between timer updates here, but we don't do anything different in that case
				var buoyDistance = ps.position.distanceTo(this.$stationBuoy); // how far the player ship is from the buoy
				if ( ps.speed > 0 && !ps.missilesOnline && ps.target === ms && ps.equipmentStatus("EQ_ILS") === "EQUIPMENT_OK" ) {
					// has ILS, is moving, is not in missile targeting mode (which disables ILS), and is targeting the main station -- ILS will auto-steer to dock, ignoring lack of clearance
					ps.target = this.$stationBuoy; // if not cleared to dock, target the buoy (not null, because telescope in grav mode would re-target) to stop ILS unauthorized docking
				}
				if ( buoyDistance > 1000 ) { //  player is not within 1km of the buoy
					player.commsMessage(system.name + " Traffic Control: Please fly to the station-facing side of the buoy and wait for clearance.", 3);
				} else if ( ps.speed > 0 ) { // if player is within 1km, just wait
					player.commsMessage(system.name + " Traffic Control: Please hold position and wait for clearance.", 4);
				}
				return; // wait for clearance
			case "DOCKING_CLEARANCE_STATUS_TIMING_OUT":
				if ( !this.$hintExtension ) {
					this.$hintExtension = true; // suppress repetition
					var keyToPress = expandDescription("[oolite_key_docking_clearance_request]");
					player.consoleMessage("(To request an extension, target the station and press '" + (keyToPress === "L" ? "Shift-L" : keyToPress) + "'.)", 10);
					if ( ps.target !== ms && ps.missiles.length && ps.equipmentStatus("EQ_ILS") === "EQUIPMENT_OK" ) {
						player.consoleMessage(expandDescription("(If ILS redirects your target to a docking ship, try missile lock '[oolite_key_target_missile]' instead of ident lock '[oolite_key_ident_system]'.)"), 10);
					}
				}
				// fall through, timing out is still an "allowed to dock" status
			case "DOCKING_CLEARANCE_STATUS_GRANTED":
				// fall through
		} // end switch
	}
	// no docking clearance required or clearance granted
	if ( !this.$shipInDockingArea ) {
		player.commsMessage(system.name + " Traffic Control: Please enter the docking lane between the station and the buoy.", 4);
		return; // wait for compliance
	} else if ( ps.speed > 0 && !ps.missilesOnline && ps.target === ms && ps.equipmentStatus("EQ_ILS") === "EQUIPMENT_OK" ) {
		// has ILS, is moving, is not in missile targeting mode (which disables ILS), and is targeting the main station -- ILS will auto-steer to dock
		// fall through to transition to next timer
	} else if ( this.$shipUD < 0.05 && this.$shipLR < 0.05 && this.$shipAngle < 0.01 ) { // player ship is in the approach lane and aimed at the docking bay, transition to the next timer
		player.commsMessage(system.name + " Traffic Control: Approach vector looks good. Please proceed.", 4);
		// fall through to transition to next timer
	} else if ( ps.position.distanceTo(this.$stationBuoy) > 1000 ) { // approach vector is not ideal and already left the buoy (before being instructed to do so)
		player.commsMessage(system.name + " Traffic Control: Please fly towards the station-facing side of the buoy and re-align with the station.", 4);
		return; // wait for correction
	} else { // at the buoy but station is off-centre
		player.commsMessage(system.name + " Traffic Control: Please line up with the centre of the docking bay (hold Control for smaller movements).", 4);
		return; // wait for correction
	}
	// transition to next timer (_approachStation)
	this.$hintExtension = this.$hintILS = false; // reset hint displayed flags for next timer
	this.$aimAtStationTimer.stop();
	if ( this.$approachStationTimer ) { // reuse the timer if we created it earlier in the same game session
		this.$approachStationTimer.start();
	} else { // otherwise, once per game session, make a new timer to direct the player to approach the station
		this.$approachStationTimer = new Timer(this, this._approachStation, 4, 4); // delay 4 seconds, then trigger this._approachStation every 4 seconds
	}
}

this._approachStation = function() { // timer callback
	var ps = player.ship, ms = system.mainStation, sun = system.sun;
	if ( this.$clearDockingLaneTimer ) this.$clearDockingLaneTimer.stop(); // don't give conflicting guidance!
	if ( sun && sun.isValid && sun.hasGoneNova ) {
		player.commsMessage(system.name + " Traffic Control: This is a recorded message. Due to inhospitable solar activity, we are unable to assist you.", 5);
		this.$approachStationTimer.stop();
		return; // no guidance available
	} else if ( !this._getVectors() ) {
		this._stopTrafficControlTimers(); // player ship, nav buoy or main station was destroyed
		return;
	} else if ( ps.isCloaked ) { // we are in a timer callback that can only start if the player was visible when they entered the aegis (or launched from the station), so the player cloaking was observed
		if ( !this.$cloakFirstOffence ) { // this variable serves as a do-once switch
			player.commsMessage(system.name + " Traffic Control: Using a cloaking device within the station aegis is against GalCop regulations! This offence has been noted.", 15);
			player.bounty += 13;
			this.$cloakFirstOffence = true;
		}
		return; // traffic control cannot see cloaked ships, so unsure if the player is still around (but keep the timer running in case they de-cloak before leaving the aegis)
	} else if ( player.alertCondition === 3 ) { // red alert
		return; // no traffic control during combat (but keep the timer running)
	} else if ( this.$autopilotStationForDocking !== null ) {
		return; // no traffic control while autopilot is engaged (for compatibility with AutoDock and other autopilot OXPs), but keep timer running in case they cancel it
	} else if ( ms.requiresDockingClearance === true ) {
		switch (player.dockingClearanceStatus) {
			case "DOCKING_CLEARANCE_STATUS_NONE": // since we don't enter this timer until clearance is granted, this means clearance timed out or was cancelled by the player
				if ( ps.speed > 0 && !ps.missilesOnline && ps.target === ms && ps.equipmentStatus("EQ_ILS") === "EQUIPMENT_OK" ) {
					// has ILS, is moving, is not in missile targeting mode (which disables ILS), and is targeting the main station -- ILS will auto-steer to dock, ignoring lack of clearance
					ps.target = this.$stationBuoy; // if not cleared to dock, target the buoy (not null, because telescope in grav mode would re-target) to stop ILS unauthorized docking
				}
				// start timer to keep reminding them to stay out of the lane until they leave the aegis or request docking permission again (handled by this.playerRequestedDockingClearance)
				if ( this.$clearDockingLaneTimer ) { // reuse the timer if we created it earlier in the same game session
					this.$clearDockingLaneTimer.start();
				} else { // otherwise, once per game session, make a new timer to remind to leave the docking area if they are blocking it
					this.$clearDockingLaneTimer = new Timer(this, this._laneClearReminder, 15, 15);
				}
				this.$approachStationTimer.stop();
				return;
			case "DOCKING_CLEARANCE_STATUS_REQUESTED": // timed out or cancelled and player re-requested between timer updates
				player.commsMessage(system.name + " Traffic Control: Please wait near the navigation buoy until you are cleared.", 5);
				this.$approachStationTimer.stop();
				this.$approachBuoyTimer.start(); // go back to the earlier phase
				return;
			case "DOCKING_CLEARANCE_STATUS_TIMING_OUT":
				if ( ps.speed > 0 || !this.$hintExtension ) { // repeat reminders to renew if moving, in case it gets lost amidst other messages
					this.$hintExtension = true; // suppress repetition
					var keyToPress = expandDescription("[oolite_key_docking_clearance_request]");
					player.consoleMessage("(To extend clearance, target the station and press '" + (keyToPress === "L" ? "Shift-L" : keyToPress) + "'.)", 10);
				}
				// fall through, timing out is an "allowed to dock" status
			case "DOCKING_CLEARANCE_STATUS_GRANTED":
				// fall through
		} // end switch
	}
	// no docking clearance required or clearance granted
	if ( !ps.missilesOnline && ps.target === ms && ps.equipmentStatus("EQ_ILS") === "EQUIPMENT_OK" ) {
		// has ILS, is not in missile targeting mode (which disables ILS), and is targeting the main station -- ILS will auto-steer to dock
		if ( !this.$hintILS ) {
			this.$hintILS = true; // suppress repetition
			player.commsMessage(expandDescription(system.name + " Traffic Control: Docking instructions have been sent to your ILS, Commander. (If you want to stop ILS, press '[oolite_key_untarget_missile]'.)"), 15);
		}
		return; // don't stop the timer, in case the player stops using ILS
	} else if ( !this.$shipInDockingArea ) {
		player.commsMessage(system.name + " Traffic Control: Please re-enter the docking lane between the station and the buoy.", 4);
		return; // wait for compliance
	} else if ( this.$shipInDockingArea && this.$shipUD < 0.05 && this.$shipLR < 0.05 && this.$shipAngle < 0.02 ) { // in the correct docking lane, no ILS
		var rollmsg = ps.position.distanceTo(ms) < 3000 ? ", and match your roll to the docking bay." : (ps.roll ? ". Don't worry about rotation yet." : ".");
		if ( ps.speed < ps.maxSpeed * 0.1 || ps.speed > ps.maxSpeed * 0.4 ) {
			player.commsMessage(system.name + " Traffic Control: Please set speed between 10%% and 40%%" + rollmsg, 3);
		} else {
			player.commsMessage(system.name + " Traffic Control: Approach is good. Slow if needed" + rollmsg, 3);
		}
	} else if ( ps.speed > 0 && ps.position.distanceTo(ms) < 1000 && this.$shipAngle < 1.5 ) { // incorrect position or orientation, no ILS, within 1km, moving towards station
		player.commsMessage(system.name + " Traffic Control: Abort approach! You are off-course and too close to the station!", 2);
		this.$approachStationTimer.stop();
		this.$approachBuoyTimer.start(); // go back to the earlier phase
	} else if ( ps.speed > 10 ) { // not in correct docking lane, no ILS, not too close, moving fast
		player.commsMessage(system.name + " Traffic Control: Please reduce throttle to minimum while correcting your course. Your view of the sides of the docking bay should be symmetrical.", 3);
	} else { // not in correct docking lane, no ILS, not too close to the station, not moving fast
		player.commsMessage(expandDescription(system.name + " Traffic Control: Match rotation, pitch ('[oolite_key_pitch_forward]' or '[oolite_key_pitch_back]') or yaw ('[oolite_key_yaw_left]' or '[oolite_key_yaw_right]') towards the most visible side of the docking bay, trickle power to thrusters, then stop and re-centre (hold Control for smaller movements)."), 3);
		this.$hintExtension = false; // repeat extension hint if needed while not moving during repositioning
	}
}

this._getVectors = function() {
	var ps = player.ship, ms = system.mainStation;
	if ( !this.$stationBuoy || !this.$stationBuoy.isValid || !ps.isValid || !ms.isValid )
	{
		log(this.name, "_getVectors validity check failed, stopping Traffic Control");
		this._stopTrafficControlTimers(); // player ship, nav buoy or main station was destroyed
		return false;
	}
	// imagine a cylinder extending from the centre of the station towards the buoy - this is the "docking lane" (alternatively, it could be represented as a cone widening towards the buoy, but a cylinder is simple)
	// we want to check two independent things: is the centre of the ship inside that cylinder, and is the ship pointing towards the docking bay?
	// what is the position of the centre of the docking bay? we could check each of the subentities of the station, find the docks (.isDock === true), take their positions and boundingBox, etc.
	// however, if a station has multiple docks, we don't have a way to check which dock the player has been assigned to use
	// to simplify, we assume that the buoy is positioned on a vector projected out from the centre of the docking bay, and if there are multiple buoys, we don't provide assistance
	// we also don't care how deep the docking bay is, or if it is not circular, because the player is instructed to rotate manually and will be docked before they go "too deep"
	// so, we project a cylinder from the centre of the station, instead of from the centre of the docking bay
	// let's define the cylinder as having radius R (the radius of the station) and height H (distance from station centre to buoy centre)
	// so we have A = (x1, y1, z1) at the centre of the station, and B = (x2, y2, z2) at the buoy, and vector AB along central axis of the cylinder (from A to B) and length H
	let playerPos = ps.position, stationPos = ms.position, buoyPos = this.$stationBuoy.position;
	let stationBuoyVector = stationPos.subtract(buoyPos); // vector from the station centre to the buoy centre (magnitude of this vector is the length of the cylinder)
	let stationPlayerVector = stationPos.subtract(playerPos); // vector from player ship to station centre
	// next project PlayerStationVector onto stationBuoyVector, and split it into a parallel vector (how close we are to the station vs. the buoy) and a perpendicular part (how close we are to the line between station and buoy)
	// to project the ship to station vector, the math is simpler if we use a unit vector instead of PlayerStationVector
	let stationBuoyDirection = stationBuoyVector.direction(); // direction unit vector between station and buoy (vector divided by its magnitude, resulting in vector of length 1)
	let where = stationBuoyDirection.dot(stationPlayerVector)/stationBuoyVector.magnitude(); // value representing where the player ship is in relation to the station and the buoy
	// the above dot product is equivalent to Math.cos(stationBuoyVector.angleTo(stationPlayerVector)) because both vectors in the dot product are unit vectors (length 1), leaving only the cosine of the angle between them
	// doing it as dot product is more efficient than using angleTo, because angleTo performs the dot product and then takes the arc-cosine of it, so we would unnecessarily go from cosine to arc-cosine then back to cosine
	if ( where < 0 || where > 1 ) { // player ship to station line is beyond/behind the station (< 0) or beyond/behind the buoy (> 1) from perspective of someone viewing the ship from between the station and the buoy
		this.$shipInDockingArea = false;
	} else { // player ship is between the buoy and the station parallel to their connecting line, but might be far away... check if its distance to the connecting axis is less than the station's radius (we first need the above check to make sure the player is parallel to or on the line segment between the station and the buoy, otherwise the following maths wouldn't be correct)
		// dist(playerPos, stationBuoyVector) = stationPlayerVector - (stationPlayerVector dot StationBuoyDirection) * stationBuoyDirection
		// we can find the point on the station-buoy line closest to the ship by projecting a triangle with a right-angle at that point, connecting the line to the ship and another line (hypotenuse) connecting the ship to the station
		// by viewing the distance from that point to the ship as a height of a parallelogram and knowing that the magnitude of the cross product of stationPlayerVector and stationBuoyVector gives the area of the parallelogram ...
		// we can derive the distance from the parallelogram area equation: area = base x height, rearranged as height = area (the cross product) / base (the length of the station-buoy line that forms the base of the parallelogram)
		let area_of_parallelogram = stationPlayerVector.cross(stationBuoyVector).magnitude();
		let base_of_parallelogram = stationBuoyVector.magnitude(); // length of the "docking lane" cylinder
		let distance_from_station_buoy_line_to_ship = area_of_parallelogram / base_of_parallelogram; // this is the height of parallelogram and also the distance we want to know
		if ( distance_from_station_buoy_line_to_ship < ms.collisionRadius ) { // collisionRadius might be imprecise? we could instead use the larger of the x and y coordinates of the station's boundingBox
			this.$shipInDockingArea = true;
		} else {
			this.$shipInDockingArea = false;
		}
	}
	// now see if the ship is pointing in the right direction
	let stationPlayerDirection = stationPlayerVector.direction(); // direction unit vector between station and player
	let shipOrientation = ps.orientation;
	if ( 0 < oolite.compareVersion("1.72") ) { // Correction for internal trunk error
		shipOrientation.w = -shipOrientation.w;
	}
	let shipHeading = shipOrientation.vectorForward().direction(); // the way the ship is pointing (Z-axis)
	let shipPitch = shipOrientation.vectorUp(); // ship's up and down (Y-axis) vector
	let shipYaw = shipOrientation.vectorRight(); // ship's left and right (X-axis) vector
	this.$shipAngle = shipHeading.angleTo(stationPlayerDirection); // how accurately ship is pointing towards the station centre (aligned with the vector extending from the station centre to the ship)
	this.$shipUD = Math.abs(shipPitch.dot(stationBuoyDirection)); // how far off-axis we are in the Y direction (0 is exact alignment with station, 1 is exact alignment with buoy)
	this.$shipLR = Math.abs(shipYaw.dot(stationBuoyDirection)); // how far off-axis we are in the X direction (0 is exact alignment with station, 1 is exact alignment with buoy)
	return true; // successfully updated vectors
}

this.shipLaunchedFromStation = function(stationLaunchedFrom) {
	var ps = player.ship, ms = system.mainStation, sun = system.sun;
	if ( sun && sun.isValid && sun.hasGoneNova ) {
		player.commsMessage(system.name + " Traffic Control: This is a recorded message. Due to inhospitable solar activity, we are unable to assist you.", 5);
		return; // no guidance available
	} else if ( stationLaunchedFrom === ms ) { // the station the player launched from is the main one, so traffic control takes an interest
		player.commsMessage(system.name + " Traffic Control: Bon voyage Commander " + player.name + ". Please clear the approach lane as soon as possible.", 5); 
		var buoysNearStation = system.entitiesWithScanClass("CLASS_BUOY", ms, 15000);
		if ( buoysNearStation.length === 1 ) {
			this.$stationBuoy = buoysNearStation[0]; // take the first (and only) buoy -- we need a buoy to determine where the approach lane is
			if ( this.$clearDockingLaneTimer ) { // reuse the timer if we created it earlier in the same game session
				this.$clearDockingLaneTimer.start();
			} else { // otherwise, once per game session, make a new timer to remind to leave the docking area
				this.$clearDockingLaneTimer = new Timer(this, this._laneClearReminder, 15, 15);
			}
		}
	}
}

this._laneClearReminder = function() { // timer callback 
	var ps = player.ship, ms = system.mainStation, sun = system.sun;
	if ( sun && sun.isValid && sun.hasGoneNova ) {
		player.commsMessage(system.name + " Traffic Control: This is a recorded message. Due to inhospitable solar activity, we are unable to assist you.", 5);
		this.$clearDockingLaneTimer.stop();
		return; // no guidance available
	} else if ( !this._getVectors() ) {
		this._stopTrafficControlTimers(); // player ship, nav buoy or main station was destroyed
		return;
	} else if ( ps.isCloaked ) { // we are in a timer callback that can only start if the player was visible when they entered the aegis (or launched from the station), so the player cloaking was observed
		if ( !this.$cloakFirstOffence ) { // this variable serves as a do-once switch
			player.commsMessage(system.name + " Traffic Control: Using a cloaking device within the station aegis is against GalCop regulations! This offence has been noted.", 15);
			player.bounty += 13;
			this.$cloakFirstOffence = true;
		}
		return; // traffic control cannot see cloaked ships, so unsure if the player is still around (but keep the timer running in case they de-cloak before leaving the aegis)
	} else if ( player.alertCondition === 3 ) { // red alert
		return; // no traffic control during combat (but keep the timer running)
	} else if ( this.$autopilotStationForDocking !== null ) {
		return; // no traffic control while autopilot is engaged (for compatibility with AutoDock and other autopilot OXPs), but keep timer running in case they cancel it
	} else if ( (ms.requiresDockingClearance === false || ps.dockingClearanceStatus === "DOCKING_CLEARANCE_STATUS_GRANTED") && (ps.speed > 0 && !ps.missilesOnline && ps.target === ms && ps.equipmentStatus("EQ_ILS") === "EQUIPMENT_OK") ) {
		return; // they have or don't need docking clearance and they are moving and ILS is auto-steering them (keep timer running in case they stop using ILS)
	} else if ( this.$shipInDockingArea ) { // player is in the docking area
		player.commsMessage(system.name + " Traffic Control: Please clear the docking area!", 5);
		if ( ps.speed > 0 && !ps.missilesOnline && ps.target === ms && ps.equipmentStatus("EQ_ILS") === "EQUIPMENT_OK" ) {
			// has ILS, is moving, is not in missile targeting mode (which disables ILS), and is targeting the main station -- ILS will auto-steer to dock
			ps.target = this.$stationBuoy; // if not cleared to dock, target the buoy (not null, because telescope in grav mode will re-target) to stop ILS unauthorized docking
		}
//	} else {
//		this.$clearDockingLaneTimer.stop(); // player left the lane, but keep the timer running (until they leave the station aegis) in case they come back
	}
}

// handle regular docking computers or AutoDock OXP, and more cloaking checks, below

this.playerStartedAutoPilot = function(stationForDocking) { // handler function started when autopilot is engaged for docking (AutoDock OXP service, or regular docking computer)
	var ps = player.ship, ms = system.mainStation, sun = system.sun;
	this.$autopilotStationForDocking = stationForDocking; // record the destination so we can check it in this.playerCancelledAutoPilot
	if ( sun && sun.isValid && sun.hasGoneNova ) {
		player.commsMessage(system.name + " Traffic Control: This is a recorded message. Due to inhospitable solar activity, we are unable to assist you.", 5);
		return; // no guidance available
	} else if ( stationForDocking === ms ) { // traffic control only takes an interest if autopilot destination is the main station
		if ( ps.isCloaked || this.$undetected ) { // cloaked ships still interact normally with docking protocol as of Oolite 1.89, so we assume the docking computer transmits their identity
			// autopilot docking actually bypasses the docking protocol and instantly sets player.dockingClearanceStatus to "DOCKING_CLEARANCE_STATUS_GRANTED" but we'll infer that their computer requested it
			if ( !this.$cloakFirstOffence ) { // this variable serves as a do-once switch
				player.commsMessage(system.name + " Traffic Control: Using a cloaking device within the station aegis is against GalCop regulations! This offence has been noted on your record.", 15);
				player.bounty += 13;
				this.$cloakFirstOffence = true;
				this.$undetected = false;
			} else { // not the first time detected using cloak within the aegis
				player.commsMessage(system.name + " Traffic Control: Deactivate your cloaking device immediately! Your continued non-compliance has been reported.", 15);
				if (player.bounty < 50) player.bounty += 13; // increase bounty for non-fugitive player each time they (re)start the docking autopilot while cloaked
				// the game will not cancel the autopilot for fugitives until after this event, and we don't want this to be used as an exploit to reach high levels of infamy
			}
			if ( !ps.isCloaked ) { // this.$undetected was true ... a non-fugitive who is no longer cloaked, requesting docking clearance after arriving cloaked into the main station aegis
				this.shipEnteredStationAegis(ms); // behave as if they just arrived for the first time, as we did not see them before
			}
		}
	}
}

this.playerCancelledAutoPilot = function() { // called by the game because the player manually stopped autopilot or because the station refused docking (possible reasons include no dock available or fugitive status, see http://wiki.alioth.net/index.php/Docking_Instructions and the internal game function dockingInstructionsForShip in StationEntity.m and in DockEntity.m)
	var ps = player.ship, ms = system.mainStation, sun = system.sun;
	if ( sun && sun.isValid && sun.hasGoneNova ) {
		player.commsMessage(system.name + " Traffic Control: This is a recorded message. Due to inhospitable solar activity, we are unable to assist you.", 5);
		return;
	} else if ( this.$autopilotStationForDocking === ms || ps.withinStationAegis ) { // traffic control only cares about docking with main station or activity within aegis (another dockable)
		// if autopilot was cancelled due to fugitive status, an appropriate message was already displayed to the player (by the game) about that
		// for cloaking checks, we don't care what equipment the ship has - they were somehow able to initiate docking, so the station has their identity
		if ( ps.isCloaked ) { // cloaked ships still interact normally with docking protocol as of Oolite 1.89, so we assume the docking computer transmits their identity
			// autopilot docking actually bypasses the docking protocol and instantly sets player.dockingClearanceStatus to "DOCKING_CLEARANCE_STATUS_GRANTED" but we'll infer that their computer requested it
			// they can't still be undetected because this.playerStartedAutoPilot handled that case and set this.$undetected = false, so we don't need to check that again here
			// however, it is possible that they activated a cloaking device only after they started the autopilot, which we now detect here
			if ( !this.$cloakFirstOffence ) { // this variable serves as a do-once switch
				player.commsMessage(system.name + " Traffic Control: Using a cloaking device within the station aegis is against GalCop regulations! This offence has been noted on your record.", 15);
				player.bounty += 13;
				this.$cloakFirstOffence = true;
				this.$undetected = false;
			} else { // not the first time detected using cloak within the aegis
				player.commsMessage(system.name + " Traffic Control: Deactivate your cloaking device immediately! Your continued non-compliance has been reported.", 15);
				if (player.bounty < 50) player.bounty += 13; // increase bounty for non-fugitive player each time they (re)start the docking autopilot while cloaked
				// the game will not cancel the autopilot for fugitives until after this event, and we don't want this to be used as an exploit to reach high levels of infamy
			}
			if ( !ps.isCloaked ) { // this.$undetected was true ... a non-fugitive who is no longer cloaked, requesting docking clearance after arriving cloaked into the main station aegis
				this.shipEnteredStationAegis(ms); // behave as if they just arrived for the first time, as we did not see them before
			}
		}
		// otherwise, whatever timers were previously running (if any) can continue now that we are no longer in autopilot mode
		this.$autopilotStationForDocking = null;
	}
}