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;
}
}
|