From e1e15a73bd37e531d51cfea15efdf150472fcfee Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Wed, 21 Dec 2016 20:56:58 -0600 Subject: [PATCH 01/32] feature/ATC-181 - Creates AircraftCommander class to run commands on an AircraftInstance * Removes commands section in AircraftInstanceModel * Adds AircraftCommander instance to App that gets passed into * InputController * Removes no-undef rule from .eslintrc --- .eslintrc | 1 - src/assets/scripts/client/App.js | 6 +- src/assets/scripts/client/InputController.js | 6 +- .../client/aircraft/AircraftCommander.js | 1074 +++++++++++++++++ .../client/aircraft/AircraftInstanceModel.js | 1055 +--------------- 5 files changed, 1086 insertions(+), 1056 deletions(-) create mode 100644 src/assets/scripts/client/aircraft/AircraftCommander.js diff --git a/.eslintrc b/.eslintrc index e3228037..063a5404 100644 --- a/.eslintrc +++ b/.eslintrc @@ -31,7 +31,6 @@ "no-restricted-syntax": 0, "no-shadow": 0, "no-mixed-operators": 0, - "no-undef": 0, "no-underscore-dangle": 0, "object-shorthand": 0 } diff --git a/src/assets/scripts/client/App.js b/src/assets/scripts/client/App.js index 037a718f..37aef17f 100644 --- a/src/assets/scripts/client/App.js +++ b/src/assets/scripts/client/App.js @@ -4,6 +4,7 @@ import LoadingView from './LoadingView'; import AirportController from './airport/AirportController'; import GameController from './game/GameController'; import TutorialView from './tutorial/TutorialView'; +import AircraftCommander from './aircraft/AircraftCommander'; import InputController from './InputController'; import UiController from './UiController'; import CanvasController from './canvas/CanvasController'; @@ -57,6 +58,7 @@ export default class App { this.contentQueue = null; this.airportController = null; this.tutorialView = null; + this.aircraftCommander = null; this.inputController = null; this.uiController = null; this.canvasController = null; @@ -104,7 +106,8 @@ export default class App { this.airportController = new AirportController(airportLoadList, this.updateRun); this.gameController = new GameController(this.getDeltaTime); this.tutorialView = new TutorialView(this.$element); - this.inputController = new InputController(this.$element); + this.aircraftCommander = new AircraftCommander(); + this.inputController = new InputController(this.$element, this.aircraftCommander); this.uiController = new UiController(this.$element); this.canvasController = new CanvasController(this.$element); this.gameClockView = new GameClockView(this.$element); @@ -163,6 +166,7 @@ export default class App { this.airportController = null; this.gameController = null; this.tutorialView = null; + this.aircraftCommander = null; this.inputController = null; this.uiController = null; this.canvasController = null; diff --git a/src/assets/scripts/client/InputController.js b/src/assets/scripts/client/InputController.js index a54a8b20..341619b5 100644 --- a/src/assets/scripts/client/InputController.js +++ b/src/assets/scripts/client/InputController.js @@ -85,13 +85,15 @@ export default class InputController { /** * @constructor */ - constructor($element) { + constructor($element, aircraftCommander) { this.$element = $element; this.$window = null; this.$commandInput = null; this.$canvases = null; this.$sidebar = null; + this._aircraftCommander = aircraftCommander; + this.input = input; this.input.command = ''; this.input.callsign = ''; @@ -880,6 +882,6 @@ export default class InputController { const aircraft = prop.aircraft.list[match]; - return aircraft.runCommands(commandParser.args); + return this._aircraftCommander.runCommands(aircraft, commandParser.args); } } diff --git a/src/assets/scripts/client/aircraft/AircraftCommander.js b/src/assets/scripts/client/aircraft/AircraftCommander.js new file mode 100644 index 00000000..f7e2800c --- /dev/null +++ b/src/assets/scripts/client/aircraft/AircraftCommander.js @@ -0,0 +1,1074 @@ +import _has from 'lodash/has'; +import _isNaN from 'lodash/isNaN'; +import _isNil from 'lodash/isNil'; +import _map from 'lodash/map'; +import Waypoint from './FlightManagementSystem/Waypoint'; +import RouteModel from '../airport/Route/RouteModel'; +import { speech_say } from '../speech'; +import { radians_normalize } from '../math/circle'; +import { round, clamp } from '../math/core'; +import { vradial, vsub } from '../math/vector'; +import { + radio_cardinalDir_names, + groupNumbers, + radio_runway, + radio_heading, + radio_spellOut, + radio_altitude, + radio_trend, + getCardinalDirection +} from '../utilities/radioUtilities'; +import { radiansToDegrees, degreesToRadians, heading_to_string } from '../utilities/unitConverters'; +import { + FLIGHT_MODES, + FLIGHT_CATEGORY, + WAYPOINT_NAV_MODE, + FP_LEG_TYPE +} from '../constants/aircraftConstants'; + + +/** + * Enum of commands and thier corresponding function. + * + * Used to build a call to the correct function when a UI command, or commands, + * for an aircraft have been issued. + * + * @property COMMANDS + * @type {Object} + * @final + */ +const COMMANDS = { + abort: 'runAbort', + altitude: 'runAltitude', + clearedAsFiled: 'runClearedAsFiled', + climbViaSID: 'runClimbViaSID', + debug: 'runDebug', + delete: 'runDelete', + descendViaSTAR: 'runDescendViaSTAR', + direct: 'runDirect', + fix: 'runFix', + flyPresentHeading: 'runFlyPresentHeading', + heading: 'runHeading', + hold: 'runHold', + land: 'runLanding', + moveDataBlock: 'runMoveDataBlock', + route: 'runRoute', + reroute: 'runReroute', + sayRoute: 'runSayRoute', + sid: 'runSID', + speed: 'runSpeed', + star: 'runSTAR', + takeoff: 'runTakeoff', + taxi: 'runTaxi' +}; + +export default class AircraftCommander { + + + // TODO: move aircraftCommands to a new class + /** + * @for AircraftInstanceModel + * @method runCommands + * @param aircraft {AircraftInstanceModel} + * @param commands {CommandParser} + */ + runCommands(aircraft, commands) { + if (!aircraft.inside_ctr) { + return true; + } + + let response = []; + let response_end = ''; + const deferred = []; + + for (let i = 0; i < commands.length; i++) { + const command = commands[i][0]; + const args = commands[i].splice(1); + + if (command === FLIGHT_MODES.TAKEOFF) { + deferred.push([command, args]); + + continue; + } + + let retval = this.run(aircraft, command, args); + + if (retval) { + if (!_has(retval[1], 'log') || !_has(retval[1], 'say')) { + // TODO: reassigning a value using itself is dangerous. this should be re-wroked + retval = [ + retval[0], + { + log: retval[1], + say: retval[1] + } + ]; + } + + response.push(retval[1]); + + if (retval[2]) { + response_end = retval[2]; + } + } + } + + for (let i = 0; i < deferred.length; i += 1) { + const command = deferred[i][0]; + const args = deferred[i][1]; + const retval = this.run(aircraft, command, args); + + if (retval) { + // TODO: fix the logic here this very purposly using `!=`. length is not an object and thus, + // never null but by using coercion it evaluates to falsey if its not an array + // true if array, and not log/say object + if (retval[1].length != null) { + // make into log/say object + retval[1] = { + say: retval[1], + log: retval[1] + }; + } + + response.push(retval[1]); + } + } + + if (commands.length === 0) { + response = [{ + say: 'not understood', + log: 'not understood' + }]; + response_end = 'say again'; + } + + if (response.length >= 1) { + if (response_end) { + response_end = `, ${response_end}`; + } + + const r_log = _map(response, (r) => r.log).join(', '); + const r_say = _map(response, (r) => r.say).join(', '); + + window.uiController.ui_log(`${aircraft.getCallsign()}, ${r_log} ${response_end}`); + speech_say([ + { type: 'callsign', content: this }, + { type: 'text', content: `${r_say} ${response_end}` } + ]); + } + + aircraft.updateStrip(); + + return true; + } + + /** + * @for AircraftInstanceModel + * @method run + * @param command + * @param data + * @return {function} + */ + run(aircraft, command, data) { + let call_func; + + if (COMMANDS[command]) { + call_func = COMMANDS[command]; + } + + if (!call_func) { + return ['fail', 'not understood']; + } + + return this[call_func](aircraft, data); + } + + /** + * @for AircraftInstanceModel + * @method runHeading + * @param data + */ + runHeading(aircraft, data) { + const airport = window.airportController.airport_get(); + const direction = data[0]; + let heading = data[1]; + const incremental = data[2]; + let amount = 0; + let instruction; + + if (_isNaN(heading)) { + return ['fail', 'heading not understood']; + } + + if (incremental) { + amount = heading; + + if (direction === 'left') { + heading = radiansToDegrees(aircraft.heading) - amount; + } else if (direction === 'right') { + heading = radiansToDegrees(aircraft.heading) + amount; + } + } + + // TODO: this probably shouldn't be the AircraftInstanceModel's job. this logic should belong somewhere else. + // Update the FMS + let wp = aircraft.fms.currentWaypoint; + const leg = aircraft.fms.currentLeg; + const f = aircraft.fms.following; + + if (wp.navmode === WAYPOINT_NAV_MODE.RWY) { + aircraft.cancelLanding(); + } + + // already being vectored or holding. Will now just change the assigned heading. + if (wp.navmode === WAYPOINT_NAV_MODE.HEADING) { + aircraft.fms.setCurrent({ + altitude: wp.altitude, + navmode: WAYPOINT_NAV_MODE.HEADING, + heading: degreesToRadians(heading), + speed: wp.speed, + turn: direction, + hold: false + }); + } else if (wp.navmode === WAYPOINT_NAV_MODE.HOLD) { + // in hold. Should leave the hold, and add leg for vectors + const index = aircraft.fms.current[0] + 1; + const waypointToAdd = new Waypoint( + { + altitude: wp.altitude, + navmode: WAYPOINT_NAV_MODE.HEADING, + heading: degreesToRadians(heading), + speed: wp.speed, + turn: direction, + hold: false + }, + airport + ); + + // add new Leg after hold leg + aircraft.fms.insertLeg({ + firstIndex: index, + waypoints: [waypointToAdd] + }); + + // move from hold leg to vector leg. + aircraft.fms.nextWaypoint(); + } else if (f.sid || f.star || f.awy) { + const waypointToAdd = new Waypoint( + { + altitude: wp.altitude, + navmode: WAYPOINT_NAV_MODE.HEADING, + heading: degreesToRadians(heading), + speed: wp.speed, + turn: direction, + hold: false + }, + airport + ); + + // TODO: this should be an FMS class method that accepts a new `waypointToAdd` + // insert wp with heading at current position within the already active leg + leg.waypoints.splice(aircraft.fms.current[1], 0, waypointToAdd); + } else if (leg.route !== '[radar vectors]') { + // needs new leg added + if (aircraft.fms.atLastWaypoint()) { + const waypointToAdd = new Waypoint( + { + altitude: wp.altitude, + navmode: WAYPOINT_NAV_MODE.HEADING, + heading: degreesToRadians(heading), + speed: wp.speed, + turn: direction, + hold: false + }, + airport + ); + + aircraft.fms.appendLeg({ + waypoints: [waypointToAdd] + }); + + aircraft.fms.nextLeg(); + } else { + const waypointToAdd = new Waypoint( + { + altitude: wp.altitude, + navmode: WAYPOINT_NAV_MODE.HEADING, + heading: degreesToRadians(heading), + speed: wp.speed, + turn: direction, + hold: false + }, + airport + ); + + aircraft.fms.insertLegHere({ + waypoints: [waypointToAdd] + }); + } + } + + wp = aircraft.fms.currentWaypoint; // update 'wp' + + // Construct the readback + instruction = 'fly heading'; + if (direction) { + instruction = `turn ${direction} heading`; + } + + const readback = {}; + readback.log = `${instruction} ${heading_to_string(wp.heading)}`; + readback.say = `${instruction} ${radio_heading(heading_to_string(wp.heading))}`; + + if (incremental) { + readback.log = `turn ${amount} degrees ${direction}`; + readback.say = `turn ${groupNumbers(amount)} degrees ${direction}`; + } + + return ['ok', readback]; + } + + /** + * @for AircraftInstanceModel + * @method runAltitude + * @param data + */ + runAltitude(aircraft, data) { + const altitude = data[0]; + const expedite = data[1]; + const airport = window.airportController.airport_get(); + const radioTrendAltitude = radio_trend('altitude', aircraft.altitude, aircraft.fms.altitudeForCurrentWaypoint()); + const currentWaypointRadioAltitude = radio_altitude(aircraft.fms.altitudeForCurrentWaypoint()); + + if ((altitude == null) || isNaN(altitude)) { + // FIXME: move this to it's own command. if expedite can be passed as a sole command it should be its own command + if (expedite) { + aircraft.fms.setCurrent({ expedite: true }); + + return ['ok', `${radioTrendAltitude} ${aircraft.fms.altitudeForCurrentWaypoint()} expedite`]; + } + + return ['fail', 'altitude not understood']; + } + + if (aircraft.mode === FLIGHT_MODES.LANDING) { + aircraft.cancelLanding(); + } + + let ceiling = airport.ctr_ceiling; + if (window.gameController.game.option.get('softCeiling') === 'yes') { + ceiling += 1000; + } + + aircraft.fms.setAll({ + // TODO: enumerate the magic numbers + altitude: clamp(round(airport.elevation / 100) * 100 + 1000, altitude, ceiling), + expedite: expedite + }); + + let isExpeditingString = ''; + if (expedite) { + isExpeditingString = 'and expedite'; + } + + const readback = { + log: `${radioTrendAltitude} ${aircraft.fms.altitudeForCurrentWaypoint()} ${isExpeditingString}`, + say: `${radioTrendAltitude} ${currentWaypointRadioAltitude} ${isExpeditingString}` + }; + + return ['ok', readback]; + } + + /** + * @for AircraftInstanceModel + * @method runClearedAsFiled + * @return {array} + */ + runClearedAsFiled(aircraft) { + if (!aircraft.runSID([aircraft.destination])) { + return [true, 'unable to clear as filed']; + } + + const airport = window.airportController.airport_get(); + const { name: procedureName } = airport.sidCollection.findRouteByIcao(aircraft.destination); + const readback = {}; + + readback.log = `cleared to destination via the ${aircraft.destination} departure, then as filed. Climb and ` + + `maintain ${airport.initial_alt}, expect ${aircraft.fms.fp.altitude} 10 minutes after departure `; + readback.say = `cleared to destination via the ${procedureName} ` + + `departure, then as filed. Climb and maintain ${radio_altitude(airport.initial_alt)}, ` + + `expect ${radio_altitude(aircraft.fms.fp.altitude)}, ${radio_spellOut('10')} minutes after departure'`; + + return ['ok', readback]; + } + + /** + * @for AircraftInstanceModel + * @method runClimbViaSID + */ + runClimbViaSID(aircraft) { + if (aircraft.fms.currentLeg.type !== FP_LEG_TYPE.SID || !aircraft.fms.climbViaSID()) { + const isWarning = true; + + window.uiController.ui_log(`${aircraft.getCallsign()} unable to climb via SID`, isWarning); + + return; + } + + const airport = window.airportController.airport_get(); + const { name: procedureName } = airport.sidCollection.findRouteByIcao(aircraft.fms.currentLeg.route.procedure); + const readback = { + log: `climb via the ${aircraft.fms.currentLeg.route.procedure} departure`, + say: `climb via the ${procedureName} departure` + }; + + return ['ok', readback]; + } + + /** + * @for AircraftInstanceModel + * @method runDescendViaSTAR + * @param data + * @return {boolean|undefined} + */ + runDescendViaSTAR(aircraft) { + if (!aircraft.fms.descendViaSTAR() || !aircraft.fms.following.star) { + const isWarning = true; + window.uiController.ui_log(`${aircraft.getCallsign()}, unable to descend via STAR`, isWarning); + + return; + } + + const airport = window.airportController.airport_get(); + const { name: procedureName } = airport.starCollection.findRouteByIcao(aircraft.fms.currentLeg.route.procedure); + const readback = { + log: `descend via the ${aircraft.fms.following.star} arrival`, + say: `descend via the ${procedureName} arrival` + }; + + return ['ok', readback]; + } + + /** + * @for AircraftInstanceModel + * @method runSpeed + * @param data + */ + runSpeed(aircraft, data) { + const speed = data[0]; + + if (_isNaN(speed)) { + return ['fail', 'speed not understood']; + } + + const clampedSpeed = clamp(aircraft.model.speed.min, speed, aircraft.model.speed.max); + aircraft.fms.setAll({ speed: clampedSpeed }); + + const radioTrendSpeed = radio_trend('speed', aircraft.speed, aircraft.fms.currentWaypoint.speed); + const readback = { + log: `${radioTrendSpeed} ${aircraft.fms.currentWaypoint.speed}`, + say: `${radioTrendSpeed} ${radio_spellOut(aircraft.fms.currentWaypoint.speed)}` + }; + + return ['ok', readback]; + } + + /** + * @for AircraftInstanceModel + * @method runHold + * @param data + */ + runHold(aircraft, data) { + const airport = window.airportController.airport_get(); + let dirTurns = data[0]; + let legLength = data[1]; + let holdFix = data[2]; + let holdFixLocation = null; + let inboundHdg; + // let inboundDir; + + // TODO: this might be better handled from within the parser + if (dirTurns == null) { + // standard for holding patterns is right-turns + dirTurns = 'right'; + } + + // TODO: this might be better handled from within the parser + if (legLength == null) { + legLength = '1min'; + } + + // TODO: simplify this nested if. + if (holdFix !== null) { + holdFix = holdFix.toUpperCase(); + holdFixLocation = airport.getFixPosition(holdFix); + + if (!holdFixLocation) { + return ['fail', `unable to find fix ${holdFix}`]; + } + } + + if (aircraft.isTakeoff() && !holdFix) { + return ['fail', 'where do you want us to hold?']; + } + + // Determine whether or not to enter the hold from present position + if (holdFix) { + // holding over a specific fix (currently only able to do so on inbound course) + inboundHdg = vradial(vsub(aircraft.position, holdFixLocation)); + + if (holdFix !== aircraft.fms.currentWaypoint.fix) { + // not yet headed to the hold fix + aircraft.fms.insertLegHere({ + type: 'fix', + route: '[GPS/RNAV]', + waypoints: [ + // proceed direct to holding fix + new Waypoint( + { + fix: holdFix, + altitude: aircraft.fms.altitudeForCurrentWaypoint(), + speed: aircraft.fms.currentWaypoint.speed + }, + airport + ), + // then enter the hold + new Waypoint( + { + navmode: WAYPOINT_NAV_MODE.HOLD, + speed: aircraft.fms.currentWaypoint.speed, + altitude: aircraft.fms.altitudeForCurrentWaypoint(), + fix: null, + hold: { + fixName: holdFix, + fixPos: holdFixLocation, + dirTurns: dirTurns, + legLength: legLength, + inboundHdg: inboundHdg, + timer: null + } + }, + airport + ) + ] + }); + } else { + // TODO: this should be a `Waypoint` + // already currently going to the hold fix + // Force the initial turn to outbound heading when entering the hold + aircraft.fms.appendWaypoint({ + navmode: WAYPOINT_NAV_MODE.HOLD, + speed: aircraft.fms.currentWaypoint.speed, + altitude: aircraft.fms.altitudeForCurrentWaypoint(), + fix: null, + hold: { + fixName: holdFix, + fixPos: holdFixLocation, + dirTurns: dirTurns, + legLength: legLength, + inboundHdg: inboundHdg, + timer: null + } + }); + } + } else { + // holding over present position (currently only able to do so on present course) + holdFixLocation = aircraft.position; // make a/c hold over their present position + inboundHdg = aircraft.heading; + + // TODO: these aren't `Waypoints` and they should be + aircraft.fms.insertLegHere({ + type: 'fix', + waypoints: [ + { // document the present position as the 'fix' we're holding over + navmode: WAYPOINT_NAV_MODE.FIX, + fix: '[custom]', + location: holdFixLocation, + altitude: aircraft.fms.altitudeForCurrentWaypoint(), + speed: aircraft.fms.currentWaypoint.speed + }, + { // Force the initial turn to outbound heading when entering the hold + navmode: WAYPOINT_NAV_MODE.HOLD, + speed: aircraft.fms.currentWaypoint.speed, + altitude: aircraft.fms.altitudeForCurrentWaypoint(), + fix: null, + hold: { + fixName: holdFix, + fixPos: holdFixLocation, + dirTurns: dirTurns, + legLength: legLength, + inboundHdg: inboundHdg, + timer: null + } + } + ] + }); + } + + // TODO: abstract to method `.getInboundCardinalDirection()` + const inboundDir = radio_cardinalDir_names[getCardinalDirection(radians_normalize(inboundHdg + Math.PI)).toLowerCase()]; + + if (holdFix) { + return ['ok', `proceed direct ${holdFix} and hold inbound, ${dirTurns} turns, ${legLength} legs`]; + } + + return ['ok', `hold ${inboundDir} of present position, ${dirTurns} turns, ${legLength} legs`]; + } + + /** + * @for AircraftInstanceModel + * @method runDirect + * @param data + */ + runDirect(aircraft, data) { + const fixname = data[0].toUpperCase(); + // TODO replace with FixCollection + const fix = window.airportController.airport_get().getFixPosition(fixname); + + if (!fix) { + return ['fail', `unable to find fix called ${fixname}`]; + } + + // remove intermediate fixes + if (aircraft.mode === FLIGHT_MODES.TAKEOFF) { + aircraft.fms.skipToFix(fixname); + } else if (!aircraft.fms.skipToFix(fixname)) { + return ['fail', `${fixname} is not in our flightplan`]; + } + + return ['ok', `proceed direct ${fixname}`]; + } + + runFix(aircraft, data) { + let last_fix; + let fail; + const fixes = _map(data, (fixname) => { + // TODO: this may beed to be the FixCollection + const fix = window.airportController.airport_get().getFixPosition(fixname); + + if (!fix) { + fail = ['fail', `unable to find fix called ${fixname}`]; + + return; + } + + // to avoid repetition, compare name with the previous fix + if (fixname === last_fix) { + return; + } + + last_fix = fixname; + + return fixname; + }); + + if (fail) { + return fail; + } + + for (let i = 0; i < fixes.length; i++) { + // FIXME: use enumerated constant for type + aircraft.fms.insertLegHere({ type: 'fix', route: fixes[i] }); + } + + if (aircraft.mode !== FLIGHT_MODES.WAITING && + aircraft.mode !== FLIGHT_MODES.TAKEOFF && + aircraft.mode !== FLIGHT_MODES.APRON && + aircraft.mode !== FLIGHT_MODES.TAXI + ) { + aircraft.cancelLanding(); + } + + return ['ok', `proceed direct ${fixes.join(', ')}`]; + } + + /** + * @for AircraftInstanceModel + * @method runFlyPresentHeading + * @param data + */ + runFlyPresentHeading(aircraft, data) { + aircraft.cancelFix(); + aircraft.runHeading([null, radiansToDegrees(aircraft.heading)]); + + return ['ok', 'fly present heading']; + } + + /** + * @for AircraftInstanceModel + * @method runSayRoute + * @param data + */ + runSayRoute(aircraft, data) { + return ['ok', { + log: `route: ${aircraft.fms.fp.route.join(' ')}`, + say: 'here\'s our route' + }]; + } + + /** + * @for AircraftInstanceModel + * @method runSID + */ + runSID(aircraft, data) { + const airport = window.airportController.airport_get(); + const { sidCollection } = airport; + const sidId = data[0]; + const standardRouteModel = sidCollection.findRouteByIcao(sidId); + const exit = airport.getSIDExitPoint(sidId); + // TODO: perhaps this should use the `RouteModel`? + const route = `${airport.icao}.${sidId}.${exit}`; + + if (_isNil(standardRouteModel)) { + return ['fail', 'SID name not understood']; + } + + if (aircraft.category !== FLIGHT_CATEGORY.DEPARTURE) { + return ['fail', 'unable to fly SID, we are an inbound']; + } + + if (!aircraft.rwy_dep) { + aircraft.setDepartureRunway(airport.runway); + } + + if (!standardRouteModel.hasFixName(aircraft.rwy_dep)) { + return ['fail', `unable, the ${standardRouteModel.name} departure not valid from Runway ${aircraft.rwy_dep}`]; + } + + // TODO: this is the wrong place for this `.toUpperCase()` + aircraft.fms.followSID(route.toUpperCase()); + + const readback = { + log: `cleared to destination via the ${sidId} departure, then as filed`, + say: `cleared to destination via the ${standardRouteModel.name} departure, then as filed` + }; + + return ['ok', readback]; + } + + /** + * @for AircraftInstanceModel + * @method runSTAR + * @param data {array} a string representation of the STAR, ex: `QUINN.BDEGA2.KSFO` + */ + runSTAR(aircraft, data) { + const routeModel = new RouteModel(data[0]); + const airport = window.airportController.airport_get(); + const { name: starName } = airport.starCollection.findRouteByIcao(routeModel.procedure); + + if (aircraft.category !== FLIGHT_CATEGORY.ARRIVAL) { + return ['fail', 'unable to fly STAR, we are a departure!']; + } + + // TODO: the data[0].length check might not be needed. this is covered via the CommandParser when + // this method runs as the result of a command. + if (data[0].length === 0 || !airport.starCollection.hasRoute(routeModel.procedure)) { + return ['fail', 'STAR name not understood']; + } + + aircraft.fms.followSTAR(routeModel.routeCode); + + // TODO: casing may be an issue here. + const readback = { + log: `cleared to the ${airport.name} via the ${routeModel.procedure} arrival`, + say: `cleared to the ${airport.name} via the ${starName} arrival` + }; + + return ['ok', readback]; + } + + /** + * @for AircraftInstanceModel + * @method runMoveDataBlock + * @param data + */ + runMoveDataBlock(aircraft, dir) { + // TODO: what do all these numbers mean? + const positions = { 8: 360, 9: 45, 6: 90, 3: 135, 2: 180, 1: 225, 4: 270, 7: 315, 5: 'ctr' }; + + if (!_has(positions, dir[0])) { + return; + } + + aircraft.datablockDir = positions[dir[0]]; + } + + /** + * Adds a new Leg to fms with a user specified route + * Note: See notes on 'runReroute' for how to format input for this command + * + * @for AircraftInstanceModel + * @method runRoute + * @param data + */ + runRoute(aircraft, data) { + // capitalize everything + data = data[0].toUpperCase(); + let worked = true; + const route = aircraft.fms.formatRoute(data); + + if (worked && route) { + // Add to fms + worked = aircraft.fms.customRoute(route, false); + } + + if (!route || !data || data.indexOf(' ') > -1) { + worked = false; + } + + // Build the response + if (worked) { + const readback = { + log: `rerouting to :${aircraft.fms.fp.route.join(' ')}`, + say: 'rerouting as requested' + }; + + return ['ok', readback]; + } + + const readback = { + log: `your route "${data}" is invalid!`, + say: 'that route is invalid!' + }; + + return ['fail', readback]; + } + + /** + * Removes all legs, and replaces them with the specified route + * Note: Input data needs to be provided with single dots connecting all + * procedurally-linked points (eg KSFO.OFFSH9.SXC or SGD.V87.MOVER), and + * all other points that will be simply a fix direct to another fix need + * to be connected with double-dots (eg HLI..SQS..BERRA..JAN..KJAN) + * + * @for AircraftInstanceModel + * @method runReroute + * @param data + */ + runReroute(aircraft, data) { + // TODO: capitalize everything? + data = data[0].toUpperCase(); + let worked = true; + const route = aircraft.fms.formatRoute(data); + + if (worked && route) { + // Reset fms + worked = aircraft.fms.customRoute(route, true); + } + + // TODO: what exactly are we checking here? + if (!route || !data || data.indexOf(' ') > -1) { + worked = false; + } + + // Build the response + if (worked) { + const readback = { + log: `rerouting to: ${aircraft.fms.fp.route.join(' ')}`, + say: 'rerouting as requested' + }; + + return ['ok', readback]; + } + + const readback = { + log: `your route "${data}" is invalid!`, + say: 'that route is invalid!' + }; + + return ['fail', readback]; + } + + /** + * @for AircraftInstanceModel + * @method runTaxi + * @param data + */ + runTaxi(aircraft, data) { + // TODO: all this if logic should be simplified or abstracted + if (aircraft.category !== FLIGHT_CATEGORY.DEPARTURE) { + return ['fail', 'inbound']; + } + + if (aircraft.mode === FLIGHT_MODES.TAXI) { + return ['fail', `already taxiing to ${radio_runway(aircraft.rwy_dep)}`]; + } + + if (aircraft.mode === FLIGHT_MODES.WAITING) { + return ['fail', 'already waiting']; + } + + if (aircraft.mode !== FLIGHT_MODES.APRON) { + return ['fail', 'wrong mode']; + } + + // Set the runway to taxi to + if (data[0]) { + if (window.airportController.airport_get().getRunway(data[0].toUpperCase())) { + aircraft.setDepartureRunway(data[0].toUpperCase()); + } else { + return ['fail', `no runway ${data[0].toUpperCase()}`]; + } + } + + // Start the taxi + aircraft.taxi_start = window.gameController.game_time(); + const runway = window.airportController.airport_get().getRunway(aircraft.rwy_dep); + + runway.addQueue(this); + aircraft.mode = FLIGHT_MODES.TAXI; + + const readback = { + log: `taxi to runway ${runway.name}`, + say: `taxi to runway ${radio_runway(runway.name)}` + }; + + return ['ok', readback]; + } + + /** + * @for AircraftInstanceModel + * @method runTakeoff + * @param data + */ + runTakeoff(aircraft, data) { + // TODO: all this if logic should be simplified or abstracted + if (aircraft.category !== 'departure') { + return ['fail', 'inbound']; + } + + if (!aircraft.isOnGround()) { + return ['fail', 'already airborne']; + } + if (aircraft.mode === FLIGHT_MODES.APRON) { + return ['fail', 'unable, we\'re still in the parking area']; + } + if (aircraft.mode === FLIGHT_MODES.TAXI) { + return ['fail', `taxi to runway ${radio_runway(aircraft.rwy_dep)} not yet complete`]; + } + if (aircraft.mode === FLIGHT_MODES.TAKEOFF) { + // FIXME: this is showing immediately after a to clearance. + return ['fail', 'already taking off']; + } + + if (aircraft.fms.altitudeForCurrentWaypoint() <= 0) { + return ['fail', 'no altitude assigned']; + } + + const runway = window.airportController.airport_get().getRunway(aircraft.rwy_dep); + + if (runway.removeQueue(this)) { + aircraft.mode = FLIGHT_MODES.TAKEOFF; + aircraft.scoreWind('taking off'); + aircraft.takeoffTime = window.gameController.game_time(); + + if (aircraft.fms.currentWaypoint.speed == null) { + aircraft.fms.setCurrent({ speed: aircraft.model.speed.cruise }); + } + + const wind = window.airportController.airport_get().getWind(); + const wind_dir = round(radiansToDegrees(wind.angle)); + const readback = { + // TODO: the wind_dir calculation should be abstracted + log: `wind ${round(wind_dir / 10) * 10} ${round(wind.speed)}, runway ${aircraft.rwy_dep} , cleared for takeoff`, + say: `wind ${radio_spellOut(round(wind_dir / 10) * 10)} at ${radio_spellOut(round(wind.speed))}, runway ${radio_runway(aircraft.rwy_dep)}, cleared for takeoff` + }; + + return ['ok', readback]; + } + + const waiting = runway.inQueue(this); + + return ['fail', `number ${waiting} behind ${runway.queue[waiting - 1].getRadioCallsign()}`, '']; + } + + runLanding(aircraft, data) { + const variant = data[0]; + const runway = window.airportController.airport_get().getRunway(data[1]); + + if (!runway) { + return ['fail', `there is no runway ${radio_runway(data[1])}`]; + } + + aircraft.setArrivalRunway(data[1].toUpperCase()); + // tell fms to follow ILS approach + aircraft.fms.followApproach('ils', aircraft.rwy_arr, variant); + + const readback = { + log: `cleared ILS runway ${aircraft.rwy_arr} approach`, + say: `cleared ILS runway ${radio_runway(aircraft.rwy_arr)} approach` + }; + + return ['ok', readback]; + } + + /** + * @for AircraftInstanceModel + * @method runAbort + * @param data + */ + runAbort(aircraft, data) { + // TODO: these ifs on `mode` should be converted to a switch + if (aircraft.mode === FLIGHT_MODES.TAXI) { + aircraft.mode = FLIGHT_MODES.APRON; + aircraft.taxi_start = 0; + + console.log('aborted taxi to runway'); + + const isWarning = true; + window.uiController.ui_log(`${aircraft.getCallsign()} aborted taxi to runway`, isWarning); + + return ['ok', 'taxiing back to terminal']; + } else if (aircraft.mode === FLIGHT_MODES.WAITING) { + return ['fail', 'unable to return to the terminal']; + } else if (aircraft.mode === FLIGHT_MODES.LANDING) { + aircraft.cancelLanding(); + + const readback = { + log: `go around, fly present heading, maintain ${aircraft.fms.altitudeForCurrentWaypoint()}`, + say: `go around, fly present heading, maintain ${radio_altitude(aircraft.fms.altitudeForCurrentWaypoint())}` + }; + + return ['ok', readback]; + } else if (aircraft.mode === FLIGHT_MODES.CRUISE && aircraft.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.RWY) { + aircraft.cancelLanding(); + + const readback = { + log: `cancel approach clearance, fly present heading, maintain ${aircraft.fms.altitudeForCurrentWaypoint()}`, + say: `cancel approach clearance, fly present heading, maintain ${radio_altitude(aircraft.fms.altitudeForCurrentWaypoint())}` + }; + + return ['ok', readback]; + } else if (aircraft.mode === FLIGHT_MODES.CRUISE && aircraft.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.FIX) { + aircraft.cancelFix(); + + if (aircraft.category === FLIGHT_CATEGORY.ARRIVAL) { + return ['ok', 'fly present heading, vector to final approach course']; + } else if (aircraft.category === 'departure') { + return ['ok', 'fly present heading, vector for entrail spacing']; + } + } + + // modes 'apron', 'takeoff', ('cruise' for some navmodes) + return ['fail', 'unable to abort']; + } + + // FIXME: is this in use? + /** + * @for AircraftInstanceModel + * @method runDebug + */ + runDebug() { + window.aircraft = this; + return ['ok', { log: 'in the console, look at the variable ‘aircraft’', say: '' }]; + } + + // FIXME: is this in use? + /** + * @for AircraftInstanceModel + * @method runDelete + */ + runDelete() { + window.aircraftController.aircraft_remove(this); + } +} diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index e32cacf2..40d5cff5 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -4,15 +4,12 @@ import _forEach from 'lodash/forEach'; import _get from 'lodash/get'; import _has from 'lodash/has'; import _isEqual from 'lodash/isEqual'; -import _isNaN from 'lodash/isNaN'; import _isNil from 'lodash/isNil'; import _isString from 'lodash/isString'; import _map from 'lodash/map'; import _without from 'lodash/without'; import AircraftFlightManagementSystem from './FlightManagementSystem/AircraftFlightManagementSystem'; import AircraftStripView from './AircraftStripView'; -import Waypoint from './FlightManagementSystem/Waypoint'; -import RouteModel from '../airport/Route/RouteModel'; import { speech_say } from '../speech'; import { tau, radians_normalize, angle_offset } from '../math/circle'; import { round, abs, sin, cos, extrapolate_range_clamp, clamp } from '../math/core'; @@ -31,61 +28,21 @@ import { point_in_area } from '../math/vector'; import { - radio_cardinalDir_names, digits_decimal, groupNumbers, radio_runway, - radio_heading, radio_spellOut, - radio_altitude, - radio_trend, - getCardinalDirection + radio_altitude } from '../utilities/radioUtilities'; import { km, radiansToDegrees, degreesToRadians, heading_to_string } from '../utilities/unitConverters'; import { FLIGHT_MODES, FLIGHT_CATEGORY, - WAYPOINT_NAV_MODE, - FP_LEG_TYPE + WAYPOINT_NAV_MODE } from '../constants/aircraftConstants'; import { SELECTORS } from '../constants/selectors'; import { GAME_EVENTS } from '../game/GameController'; -/** - * Enum of commands and thier corresponding function. - * - * Used to build a call to the correct function when a UI command, or commands, - * for an aircraft have been issued. - * - * @property COMMANDS - * @type {Object} - * @final - */ -const COMMANDS = { - abort: 'runAbort', - altitude: 'runAltitude', - clearedAsFiled: 'runClearedAsFiled', - climbViaSID: 'runClimbViaSID', - debug: 'runDebug', - delete: 'runDelete', - descendViaSTAR: 'runDescendViaSTAR', - direct: 'runDirect', - fix: 'runFix', - flyPresentHeading: 'runFlyPresentHeading', - heading: 'runHeading', - hold: 'runHold', - land: 'runLanding', - moveDataBlock: 'runMoveDataBlock', - route: 'runRoute', - reroute: 'runReroute', - sayRoute: 'runSayRoute', - sid: 'runSID', - speed: 'runSpeed', - star: 'runSTAR', - takeoff: 'runTakeoff', - taxi: 'runTaxi' -}; - /** * @property FLIGHT_RULES * @type {Object} @@ -603,1012 +560,6 @@ export default class Aircraft { this.$html.hide(600); } - - // TODO: move aircraftCommands to a new class - /** - * @for AircraftInstanceModel - * @method runCommands - * @param commands - */ - runCommands(commands) { - if (!this.inside_ctr) { - return true; - } - - let response = []; - let response_end = ''; - const deferred = []; - - for (let i = 0; i < commands.length; i++) { - const command = commands[i][0]; - const args = commands[i].splice(1); - - if (command === FLIGHT_MODES.TAKEOFF) { - deferred.push([command, args]); - continue; - } - - let retval = this.run(command, args); - - if (retval) { - if (!_has(retval[1], 'log') || !_has(retval[1], 'say')) { - // TODO: reassigning a value using itself is dangerous. this should be re-wroked - retval = [ - retval[0], - { - log: retval[1], - say: retval[1] - } - ]; - } - - response.push(retval[1]); - - if (retval[2]) { - response_end = retval[2]; - } - } - } - - for (let i = 0; i < deferred.length; i += 1) { - const command = deferred[i][0]; - const args = deferred[i][1]; - const retval = this.run(command, args); - - if (retval) { - // TODO: fix the logic here this very purposly using `!=`. length is not an object and thus, - // never null but by using coercion it evaluates to falsey if its not an array - // true if array, and not log/say object - if (retval[1].length != null) { - // make into log/say object - retval[1] = { - say: retval[1], - log: retval[1] - }; - } - - response.push(retval[1]); - } - } - - if (commands.length === 0) { - response = [{ - say: 'not understood', - log: 'not understood' - }]; - response_end = 'say again'; - } - - if (response.length >= 1) { - if (response_end) { - response_end = `, ${response_end}`; - } - - const r_log = _map(response, (r) => r.log).join(', '); - const r_say = _map(response, (r) => r.say).join(', '); - - window.uiController.ui_log(`${this.getCallsign()}, ${r_log} ${response_end}`); - speech_say([ - { type: 'callsign', content: this }, - { type: 'text', content: `${r_say} ${response_end}` } - ]); - } - - this.updateStrip(); - - return true; - } - - /** - * @for AircraftInstanceModel - * @method run - * @param command - * @param data - * @return {function} - */ - run(command, data) { - let call_func; - - if (COMMANDS[command]) { - call_func = COMMANDS[command]; - } - - if (!call_func) { - return ['fail', 'not understood']; - } - - return this[call_func](data); - } - - /** - * @for AircraftInstanceModel - * @method runHeading - * @param data - */ - runHeading(data) { - const airport = window.airportController.airport_get(); - const direction = data[0]; - let heading = data[1]; - const incremental = data[2]; - let amount = 0; - let instruction; - - if (_isNaN(heading)) { - return ['fail', 'heading not understood']; - } - - if (incremental) { - amount = heading; - - if (direction === 'left') { - heading = radiansToDegrees(this.heading) - amount; - } else if (direction === 'right') { - heading = radiansToDegrees(this.heading) + amount; - } - } - - // TODO: this probably shouldn't be the AircraftInstanceModel's job. this logic should belong somewhere else. - // Update the FMS - let wp = this.fms.currentWaypoint; - const leg = this.fms.currentLeg; - const f = this.fms.following; - - if (wp.navmode === WAYPOINT_NAV_MODE.RWY) { - this.cancelLanding(); - } - - // already being vectored or holding. Will now just change the assigned heading. - if (wp.navmode === WAYPOINT_NAV_MODE.HEADING) { - this.fms.setCurrent({ - altitude: wp.altitude, - navmode: WAYPOINT_NAV_MODE.HEADING, - heading: degreesToRadians(heading), - speed: wp.speed, - turn: direction, - hold: false - }); - } else if (wp.navmode === WAYPOINT_NAV_MODE.HOLD) { - // in hold. Should leave the hold, and add leg for vectors - const index = this.fms.current[0] + 1; - const waypointToAdd = new Waypoint( - { - altitude: wp.altitude, - navmode: WAYPOINT_NAV_MODE.HEADING, - heading: degreesToRadians(heading), - speed: wp.speed, - turn: direction, - hold: false - }, - airport - ); - - // add new Leg after hold leg - this.fms.insertLeg({ - firstIndex: index, - waypoints: [waypointToAdd] - }); - - // move from hold leg to vector leg. - this.fms.nextWaypoint(); - } else if (f.sid || f.star || f.awy) { - const waypointToAdd = new Waypoint( - { - altitude: wp.altitude, - navmode: WAYPOINT_NAV_MODE.HEADING, - heading: degreesToRadians(heading), - speed: wp.speed, - turn: direction, - hold: false - }, - airport - ); - - // TODO: this should be an FMS class method that accepts a new `waypointToAdd` - // insert wp with heading at current position within the already active leg - leg.waypoints.splice(this.fms.current[1], 0, waypointToAdd); - } else if (leg.route !== '[radar vectors]') { - // needs new leg added - if (this.fms.atLastWaypoint()) { - const waypointToAdd = new Waypoint( - { - altitude: wp.altitude, - navmode: WAYPOINT_NAV_MODE.HEADING, - heading: degreesToRadians(heading), - speed: wp.speed, - turn: direction, - hold: false - }, - airport - ); - - this.fms.appendLeg({ - waypoints: [waypointToAdd] - }); - - this.fms.nextLeg(); - } else { - const waypointToAdd = new Waypoint( - { - altitude: wp.altitude, - navmode: WAYPOINT_NAV_MODE.HEADING, - heading: degreesToRadians(heading), - speed: wp.speed, - turn: direction, - hold: false - }, - airport - ); - - this.fms.insertLegHere({ - waypoints: [waypointToAdd] - }); - } - } - - wp = this.fms.currentWaypoint; // update 'wp' - - // Construct the readback - instruction = 'fly heading'; - if (direction) { - instruction = `turn ${direction} heading`; - } - - const readback = {}; - readback.log = `${instruction} ${heading_to_string(wp.heading)}`; - readback.say = `${instruction} ${radio_heading(heading_to_string(wp.heading))}`; - - if (incremental) { - readback.log = `turn ${amount} degrees ${direction}`; - readback.say = `turn ${groupNumbers(amount)} degrees ${direction}`; - } - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runAltitude - * @param data - */ - runAltitude(data) { - const altitude = data[0]; - let expedite = data[1]; - const airport = window.airportController.airport_get(); - const radioTrendAltitude = radio_trend('altitude', this.altitude, this.fms.altitudeForCurrentWaypoint()); - const currentWaypointRadioAltitude = radio_altitude(this.fms.altitudeForCurrentWaypoint()); - - if ((altitude == null) || isNaN(altitude)) { - // FIXME: move this to it's own command. if expedite can be passed as a sole command it should be its own command - if (expedite) { - this.fms.setCurrent({ expedite: true }); - - return ['ok', `${radioTrendAltitude} ${this.fms.altitudeForCurrentWaypoint()} expedite`]; - } - - return ['fail', 'altitude not understood']; - } - - if (this.mode === FLIGHT_MODES.LANDING) { - this.cancelLanding(); - } - - let ceiling = airport.ctr_ceiling; - if (window.gameController.game.option.get('softCeiling') === 'yes') { - ceiling += 1000; - } - - this.fms.setAll({ - // TODO: enumerate the magic numbers - altitude: clamp(round(airport.elevation / 100) * 100 + 1000, altitude, ceiling), - expedite: expedite - }); - - let isExpeditingString = ''; - if (expedite) { - isExpeditingString = 'and expedite'; - } - - const readback = { - log: `${radioTrendAltitude} ${this.fms.altitudeForCurrentWaypoint()} ${isExpeditingString}`, - say: `${radioTrendAltitude} ${currentWaypointRadioAltitude} ${isExpeditingString}` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runClearedAsFiled - * @return {array} - */ - runClearedAsFiled() { - if (!this.runSID([this.destination])) { - return [true, 'unable to clear as filed']; - } - - const airport = window.airportController.airport_get(); - const { name: procedureName } = airport.sidCollection.findRouteByIcao(this.destination); - const readback = {}; - - readback.log = `cleared to destination via the ${this.destination} departure, then as filed. Climb and ` + - `maintain ${airport.initial_alt}, expect ${this.fms.fp.altitude} 10 minutes after departure `; - readback.say = `cleared to destination via the ${procedureName} ` + - `departure, then as filed. Climb and maintain ${radio_altitude(airport.initial_alt)}, ` + - `expect ${radio_altitude(this.fms.fp.altitude)}, ${radio_spellOut('10')} minutes after departure'`; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runClimbViaSID - */ - runClimbViaSID() { - if (this.fms.currentLeg.type !== FP_LEG_TYPE.SID || !this.fms.climbViaSID()) { - const isWarning = true; - - window.uiController.ui_log(`${this.getCallsign()} unable to climb via SID`, isWarning); - - return; - } - - const airport = window.airportController.airport_get(); - const { name: procedureName } = airport.sidCollection.findRouteByIcao(this.fms.currentLeg.route.procedure); - const readback = { - log: `climb via the ${this.fms.currentLeg.route.procedure} departure`, - say: `climb via the ${procedureName} departure` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runDescendViaSTAR - * @param data - * @return {boolean|undefined} - */ - runDescendViaSTAR() { - if (!this.fms.descendViaSTAR() || !this.fms.following.star) { - const isWarning = true; - window.uiController.ui_log(`${this.getCallsign()}, unable to descend via STAR`, isWarning); - - return; - } - - const airport = window.airportController.airport_get(); - const { name: procedureName } = airport.starCollection.findRouteByIcao(this.fms.currentLeg.route.procedure); - const readback = { - log: `descend via the ${this.fms.following.star} arrival`, - say: `descend via the ${procedureName} arrival` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runSpeed - * @param data - */ - runSpeed(data) { - const speed = data[0]; - - if (_isNaN(speed)) { - return ['fail', 'speed not understood']; - } - - const clampedSpeed = clamp(this.model.speed.min, speed, this.model.speed.max); - this.fms.setAll({ speed: clampedSpeed }); - - const radioTrendSpeed = radio_trend('speed', this.speed, this.fms.currentWaypoint.speed); - const readback = { - log: `${radioTrendSpeed} ${this.fms.currentWaypoint.speed}`, - say: `${radioTrendSpeed} ${radio_spellOut(this.fms.currentWaypoint.speed)}` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runHold - * @param data - */ - runHold(data) { - const airport = window.airportController.airport_get(); - let dirTurns = data[0]; - let legLength = data[1]; - let holdFix = data[2]; - let holdFixLocation = null; - let inboundHdg; - // let inboundDir; - - // TODO: this might be better handled from within the parser - if (dirTurns == null) { - // standard for holding patterns is right-turns - dirTurns = 'right'; - } - - // TODO: this might be better handled from within the parser - if (legLength == null) { - legLength = '1min'; - } - - // TODO: simplify this nested if. - if (holdFix !== null) { - holdFix = holdFix.toUpperCase(); - holdFixLocation = airport.getFixPosition(holdFix); - - if (!holdFixLocation) { - return ['fail', `unable to find fix ${holdFix}`]; - } - } - - if (this.isTakeoff() && !holdFix) { - return ['fail', 'where do you want us to hold?']; - } - - // Determine whether or not to enter the hold from present position - if (holdFix) { - // holding over a specific fix (currently only able to do so on inbound course) - inboundHdg = vradial(vsub(this.position, holdFixLocation)); - - if (holdFix !== this.fms.currentWaypoint.fix) { - // not yet headed to the hold fix - this.fms.insertLegHere({ - type: 'fix', - route: '[GPS/RNAV]', - waypoints: [ - // proceed direct to holding fix - new Waypoint( - { - fix: holdFix, - altitude: this.fms.altitudeForCurrentWaypoint(), - speed: this.fms.currentWaypoint.speed - }, - airport - ), - // then enter the hold - new Waypoint( - { - navmode: WAYPOINT_NAV_MODE.HOLD, - speed: this.fms.currentWaypoint.speed, - altitude: this.fms.altitudeForCurrentWaypoint(), - fix: null, - hold: { - fixName: holdFix, - fixPos: holdFixLocation, - dirTurns: dirTurns, - legLength: legLength, - inboundHdg: inboundHdg, - timer: null - } - }, - airport - ) - ] - }); - } else { - // TODO: this should be a `Waypoint` - // already currently going to the hold fix - // Force the initial turn to outbound heading when entering the hold - this.fms.appendWaypoint({ - navmode: WAYPOINT_NAV_MODE.HOLD, - speed: this.fms.currentWaypoint.speed, - altitude: this.fms.altitudeForCurrentWaypoint(), - fix: null, - hold: { - fixName: holdFix, - fixPos: holdFixLocation, - dirTurns: dirTurns, - legLength: legLength, - inboundHdg: inboundHdg, - timer: null - } - }); - } - } else { - // holding over present position (currently only able to do so on present course) - holdFixLocation = this.position; // make a/c hold over their present position - inboundHdg = this.heading; - - // TODO: these aren't `Waypoints` and they should be - this.fms.insertLegHere({ - type: 'fix', - waypoints: [ - { // document the present position as the 'fix' we're holding over - navmode: WAYPOINT_NAV_MODE.FIX, - fix: '[custom]', - location: holdFixLocation, - altitude: this.fms.altitudeForCurrentWaypoint(), - speed: this.fms.currentWaypoint.speed - }, - { // Force the initial turn to outbound heading when entering the hold - navmode: WAYPOINT_NAV_MODE.HOLD, - speed: this.fms.currentWaypoint.speed, - altitude: this.fms.altitudeForCurrentWaypoint(), - fix: null, - hold: { - fixName: holdFix, - fixPos: holdFixLocation, - dirTurns: dirTurns, - legLength: legLength, - inboundHdg: inboundHdg, - timer: null - } - } - ] - }); - } - - // TODO: abstract to method `.getInboundCardinalDirection()` - const inboundDir = radio_cardinalDir_names[getCardinalDirection(radians_normalize(inboundHdg + Math.PI)).toLowerCase()]; - - if (holdFix) { - return ['ok', `proceed direct ${holdFix} and hold inbound, ${dirTurns} turns, ${legLength} legs`]; - } - - return ['ok', `hold ${inboundDir} of present position, ${dirTurns} turns, ${legLength} legs`]; - } - - /** - * @for AircraftInstanceModel - * @method runDirect - * @param data - */ - runDirect(data) { - const fixname = data[0].toUpperCase(); - // TODO replace with FixCollection - const fix = window.airportController.airport_get().getFixPosition(fixname); - - if (!fix) { - return ['fail', `unable to find fix called ${fixname}`]; - } - - // remove intermediate fixes - if (this.mode === FLIGHT_MODES.TAKEOFF) { - this.fms.skipToFix(fixname); - } else if (!this.fms.skipToFix(fixname)) { - return ['fail', `${fixname} is not in our flightplan`]; - } - - return ['ok', `proceed direct ${fixname}`]; - } - - runFix(data) { - let last_fix; - let fail; - const fixes = _map(data, (fixname) => { - // TODO: this may beed to be the FixCollection - const fix = window.airportController.airport_get().getFixPosition(fixname); - - if (!fix) { - fail = ['fail', `unable to find fix called ${fixname}`]; - - return; - } - - // to avoid repetition, compare name with the previous fix - if (fixname === last_fix) { - return; - } - - last_fix = fixname; - - return fixname; - }); - - if (fail) { - return fail; - } - - for (let i = 0; i < fixes.length; i++) { - // FIXME: use enumerated constant for type - this.fms.insertLegHere({ type: 'fix', route: fixes[i] }); - } - - if (this.mode !== FLIGHT_MODES.WAITING && - this.mode !== FLIGHT_MODES.TAKEOFF && - this.mode !== FLIGHT_MODES.APRON && - this.mode !== FLIGHT_MODES.TAXI - ) { - this.cancelLanding(); - } - - return ['ok', `proceed direct ${fixes.join(', ')}`]; - } - - /** - * @for AircraftInstanceModel - * @method runFlyPresentHeading - * @param data - */ - runFlyPresentHeading(data) { - this.cancelFix(); - this.runHeading([null, radiansToDegrees(this.heading)]); - - return ['ok', 'fly present heading']; - } - - /** - * @for AircraftInstanceModel - * @method runSayRoute - * @param data - */ - runSayRoute(data) { - return ['ok', { - log: `route: ${this.fms.fp.route.join(' ')}`, - say: 'here\'s our route' - }]; - } - - /** - * @for AircraftInstanceModel - * @method runSID - */ - runSID(data) { - const airport = window.airportController.airport_get(); - const { sidCollection } = airport; - const sidId = data[0]; - const standardRouteModel = sidCollection.findRouteByIcao(sidId); - const exit = airport.getSIDExitPoint(sidId); - // TODO: perhaps this should use the `RouteModel`? - const route = `${airport.icao}.${sidId}.${exit}`; - - if (_isNil(standardRouteModel)) { - return ['fail', 'SID name not understood']; - } - - if (this.category !== FLIGHT_CATEGORY.DEPARTURE) { - return ['fail', 'unable to fly SID, we are an inbound']; - } - - if (!this.rwy_dep) { - this.setDepartureRunway(airportController.airport_get().runway); - } - - if (!standardRouteModel.hasFixName(this.rwy_dep)) { - return ['fail', `unable, the ${standardRouteModel.name} departure not valid from Runway ${this.rwy_dep}`]; - } - - // TODO: this is the wrong place for this `.toUpperCase()` - this.fms.followSID(route.toUpperCase()); - - const readback = { - log: `cleared to destination via the ${sidId} departure, then as filed`, - say: `cleared to destination via the ${standardRouteModel.name} departure, then as filed` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runSTAR - * @param data {array} a string representation of the STAR, ex: `QUINN.BDEGA2.KSFO` - */ - runSTAR(data) { - const routeModel = new RouteModel(data[0]); - const airport = window.airportController.airport_get(); - const { name: starName } = airport.starCollection.findRouteByIcao(routeModel.procedure); - - if (this.category !== FLIGHT_CATEGORY.ARRIVAL) { - return ['fail', 'unable to fly STAR, we are a departure!']; - } - - // TODO: the data[0].length check might not be needed. this is covered via the CommandParser when - // this method runs as the result of a command. - if (data[0].length === 0 || !airport.starCollection.hasRoute(routeModel.procedure)) { - return ['fail', 'STAR name not understood']; - } - - this.fms.followSTAR(routeModel.routeCode); - - // TODO: casing may be an issue here. - const readback = { - log: `cleared to the ${airport.name} via the ${routeModel.procedure} arrival`, - say: `cleared to the ${airport.name} via the ${starName} arrival` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runMoveDataBlock - * @param data - */ - runMoveDataBlock(dir) { - // TODO: what do all these numbers mean? - const positions = { 8: 360, 9: 45, 6: 90, 3: 135, 2: 180, 1: 225, 4: 270, 7: 315, 5: 'ctr' }; - - if (!_has(positions, dir[0])) { - return; - } - - this.datablockDir = positions[dir[0]]; - } - - /** - * Adds a new Leg to fms with a user specified route - * Note: See notes on 'runReroute' for how to format input for this command - * - * @for AircraftInstanceModel - * @method runRoute - * @param data - */ - runRoute(data) { - // capitalize everything - data = data[0].toUpperCase(); - let worked = true; - const route = this.fms.formatRoute(data); - - if (worked && route) { - // Add to fms - worked = this.fms.customRoute(route, false); - } - - if (!route || !data || data.indexOf(' ') > -1) { - worked = false; - } - - // Build the response - if (worked) { - const readback = { - log: `rerouting to :${this.fms.fp.route.join(' ')}`, - say: 'rerouting as requested' - }; - - return ['ok', readback]; - } - - const readback = { - log: `your route "${data}" is invalid!`, - say: 'that route is invalid!' - }; - - return ['fail', readback]; - } - - /** - * Removes all legs, and replaces them with the specified route - * Note: Input data needs to be provided with single dots connecting all - * procedurally-linked points (eg KSFO.OFFSH9.SXC or SGD.V87.MOVER), and - * all other points that will be simply a fix direct to another fix need - * to be connected with double-dots (eg HLI..SQS..BERRA..JAN..KJAN) - * - * @for AircraftInstanceModel - * @method runReroute - * @param data - */ - runReroute(data) { - // TODO: capitalize everything? - data = data[0].toUpperCase(); - let worked = true; - const route = this.fms.formatRoute(data); - - if (worked && route) { - // Reset fms - worked = this.fms.customRoute(route, true); - } - - // TODO: what exactly are we checking here? - if (!route || !data || data.indexOf(' ') > -1) { - worked = false; - } - - // Build the response - if (worked) { - const readback = { - log: `rerouting to: ${this.fms.fp.route.join(' ')}`, - say: 'rerouting as requested' - }; - - return ['ok', readback]; - } - - const readback = { - log: `your route "${data}" is invalid!`, - say: 'that route is invalid!' - }; - - return ['fail', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runTaxi - * @param data - */ - runTaxi(data) { - // TODO: all this if logic should be simplified or abstracted - if (this.category !== FLIGHT_CATEGORY.DEPARTURE) { - return ['fail', 'inbound']; - } - - if (this.mode === FLIGHT_MODES.TAXI) { - return ['fail', `already taxiing to ${radio_runway(this.rwy_dep)}`]; - } - - if (this.mode === FLIGHT_MODES.WAITING) { - return ['fail', 'already waiting']; - } - - if (this.mode !== FLIGHT_MODES.APRON) { - return ['fail', 'wrong mode']; - } - - // Set the runway to taxi to - if (data[0]) { - if (window.airportController.airport_get().getRunway(data[0].toUpperCase())) { - this.setDepartureRunway(data[0].toUpperCase()); - } else { - return ['fail', `no runway ${data[0].toUpperCase()}`]; - } - } - - // Start the taxi - this.taxi_start = window.gameController.game_time(); - const runway = window.airportController.airport_get().getRunway(this.rwy_dep); - - runway.addQueue(this); - this.mode = FLIGHT_MODES.TAXI; - - const readback = { - log: `taxi to runway ${runway.name}`, - say: `taxi to runway ${radio_runway(runway.name)}` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runTakeoff - * @param data - */ - runTakeoff(data) { - // TODO: all this if logic should be simplified or abstracted - if (this.category !== 'departure') { - return ['fail', 'inbound']; - } - - if (!this.isOnGround()) { - return ['fail', 'already airborne']; - } - if (this.mode === FLIGHT_MODES.APRON) { - return ['fail', 'unable, we\'re still in the parking area']; - } - if (this.mode === FLIGHT_MODES.TAXI) { - return ['fail', `taxi to runway ${radio_runway(this.rwy_dep)} not yet complete`]; - } - if (this.mode === FLIGHT_MODES.TAKEOFF) { - // FIXME: this is showing immediately after a to clearance. - return ['fail', 'already taking off']; - } - - if (this.fms.altitudeForCurrentWaypoint() <= 0) { - return ['fail', 'no altitude assigned']; - } - - const runway = window.airportController.airport_get().getRunway(this.rwy_dep); - - if (runway.removeQueue(this)) { - this.mode = FLIGHT_MODES.TAKEOFF; - this.scoreWind('taking off'); - this.takeoffTime = window.gameController.game_time(); - - if (this.fms.currentWaypoint.speed == null) { - this.fms.setCurrent({ speed: this.model.speed.cruise }); - } - - const wind = window.airportController.airport_get().getWind(); - const wind_dir = round(radiansToDegrees(wind.angle)); - const readback = { - // TODO: the wind_dir calculation should be abstracted - log: `wind ${round(wind_dir / 10) * 10} ${round(wind.speed)}, runway ${this.rwy_dep} , cleared for takeoff`, - say: `wind ${radio_spellOut(round(wind_dir / 10) * 10)} at ${radio_spellOut(round(wind.speed))}, runway ${radio_runway(this.rwy_dep)}, cleared for takeoff` - }; - - return ['ok', readback]; - } - - const waiting = runway.inQueue(this); - - return ['fail', `number ${waiting} behind ${runway.queue[waiting - 1].getRadioCallsign()}`, '']; - } - - runLanding(data) { - const variant = data[0]; - const runway = window.airportController.airport_get().getRunway(data[1]); - - if (!runway) { - return ['fail', `there is no runway ${radio_runway(data[1])}`]; - } - - this.setArrivalRunway(data[1].toUpperCase()); - // tell fms to follow ILS approach - this.fms.followApproach('ils', this.rwy_arr, variant); - - const readback = { - log: `cleared ILS runway ${this.rwy_arr} approach`, - say: `cleared ILS runway ${radio_runway(this.rwy_arr)} approach` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runAbort - * @param data - */ - runAbort(data) { - // TODO: these ifs on `mode` should be converted to a switch - if (this.mode === FLIGHT_MODES.TAXI) { - this.mode = FLIGHT_MODES.APRON; - this.taxi_start = 0; - - console.log('aborted taxi to runway'); - - const isWarning = true; - window.uiController.ui_log(`${this.getCallsign()} aborted taxi to runway`, isWarning); - - return ['ok', 'taxiing back to terminal']; - } else if (this.mode === FLIGHT_MODES.WAITING) { - return ['fail', 'unable to return to the terminal']; - } else if (this.mode === FLIGHT_MODES.LANDING) { - this.cancelLanding(); - - const readback = { - log: `go around, fly present heading, maintain ${this.fms.altitudeForCurrentWaypoint()}`, - say: `go around, fly present heading, maintain ${radio_altitude(this.fms.altitudeForCurrentWaypoint())}` - }; - - return ['ok', readback]; - } else if (this.mode === FLIGHT_MODES.CRUISE && this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.RWY) { - this.cancelLanding(); - - const readback = { - log: `cancel approach clearance, fly present heading, maintain ${this.fms.altitudeForCurrentWaypoint()}`, - say: `cancel approach clearance, fly present heading, maintain ${radio_altitude(this.fms.altitudeForCurrentWaypoint())}` - }; - - return ['ok', readback]; - } else if (this.mode === FLIGHT_MODES.CRUISE && this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.FIX) { - this.cancelFix(); - - if (this.category === FLIGHT_CATEGORY.ARRIVAL) { - return ['ok', 'fly present heading, vector to final approach course']; - } else if (this.category === 'departure') { - return ['ok', 'fly present heading, vector for entrail spacing']; - } - } - - // modes 'apron', 'takeoff', ('cruise' for some navmodes) - return ['fail', 'unable to abort']; - } - - // FIXME: is this in use? - /** - * @for AircraftInstanceModel - * @method runDebug - */ - runDebug() { - window.aircraft = this; - return ['ok', { log: 'in the console, look at the variable ‘aircraft’', say: '' }]; - } - - // FIXME: is this in use? - /** - * @for AircraftInstanceModel - * @method runDelete - */ - runDelete() { - window.aircraftController.aircraft_remove(this); - } - // TODO: move to `fms.cancelFix()` /** * @for AircraftInstanceModel @@ -2089,7 +1040,7 @@ export default class Aircraft { const turning_radius = km(this.speed) / 3600 * turning_time; // dist covered in the turn, km const dist_to_localizer = offset[0] / sin(angle_diff); // dist from the localizer intercept point, km const turn_early_km = 1; // start turn 1km early, to avoid overshoots from tailwind - const should_attempt_intercept = (0 < dist_to_localizer && dist_to_localizer <= turning_radius + turn_early_km); + const should_attempt_intercept = (dist_to_localizer > 0 && dist_to_localizer <= turning_radius + turn_early_km); const in_the_window = abs(offset_angle) < degreesToRadians(1.5); // if true, aircraft will move to localizer, regardless of assigned heading if (should_attempt_intercept || in_the_window) { // time to begin turn From 7b38a0a37286264a57b46e520abdc651f570c8da Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Wed, 21 Dec 2016 22:29:10 -0600 Subject: [PATCH 02/32] feature/ATC-181 - Adds window methods as instantiation arguments, adds todos and fixmes to AircraftCommander --- src/assets/scripts/client/App.js | 4 +- .../client/aircraft/AircraftCommander.js | 125 ++++++++++-------- 2 files changed, 74 insertions(+), 55 deletions(-) diff --git a/src/assets/scripts/client/App.js b/src/assets/scripts/client/App.js index 37aef17f..fad23d88 100644 --- a/src/assets/scripts/client/App.js +++ b/src/assets/scripts/client/App.js @@ -106,9 +106,9 @@ export default class App { this.airportController = new AirportController(airportLoadList, this.updateRun); this.gameController = new GameController(this.getDeltaTime); this.tutorialView = new TutorialView(this.$element); - this.aircraftCommander = new AircraftCommander(); - this.inputController = new InputController(this.$element, this.aircraftCommander); this.uiController = new UiController(this.$element); + this.aircraftCommander = new AircraftCommander(this.airportController, this.gameController, this.uiController); + this.inputController = new InputController(this.$element, this.aircraftCommander); this.canvasController = new CanvasController(this.$element); this.gameClockView = new GameClockView(this.$element); diff --git a/src/assets/scripts/client/aircraft/AircraftCommander.js b/src/assets/scripts/client/aircraft/AircraftCommander.js index f7e2800c..f1142f0a 100644 --- a/src/assets/scripts/client/aircraft/AircraftCommander.js +++ b/src/assets/scripts/client/aircraft/AircraftCommander.js @@ -62,12 +62,20 @@ const COMMANDS = { taxi: 'runTaxi' }; +/** + * + * + * @class AircraftCommander + */ export default class AircraftCommander { + constructor(airportController, gameController, uiController) { + this._airportController = airportController; + this._gameController = gameController; + this._uiController = uiController; + } - - // TODO: move aircraftCommands to a new class /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runCommands * @param aircraft {AircraftInstanceModel} * @param commands {CommandParser} @@ -150,7 +158,7 @@ export default class AircraftCommander { const r_log = _map(response, (r) => r.log).join(', '); const r_say = _map(response, (r) => r.say).join(', '); - window.uiController.ui_log(`${aircraft.getCallsign()}, ${r_log} ${response_end}`); + this._uiController.ui_log(`${aircraft.getCallsign()}, ${r_log} ${response_end}`); speech_say([ { type: 'callsign', content: this }, { type: 'text', content: `${r_say} ${response_end}` } @@ -163,10 +171,11 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method run - * @param command - * @param data + * @param aircraft {AircraftInstanceModel} + * @param command {string} + * @param data {array} * @return {function} */ run(aircraft, command, data) { @@ -184,12 +193,12 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runHeading * @param data */ runHeading(aircraft, data) { - const airport = window.airportController.airport_get(); + const airport = this._airportController.airport_get(); const direction = data[0]; let heading = data[1]; const incremental = data[2]; @@ -329,17 +338,19 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runAltitude * @param data */ runAltitude(aircraft, data) { const altitude = data[0]; const expedite = data[1]; - const airport = window.airportController.airport_get(); + const airport = this._airportController.airport_get(); const radioTrendAltitude = radio_trend('altitude', aircraft.altitude, aircraft.fms.altitudeForCurrentWaypoint()); const currentWaypointRadioAltitude = radio_altitude(aircraft.fms.altitudeForCurrentWaypoint()); + // these two conditions should never happen here they will be caught in the `CommandParser` + // FIXME: remove this top level if block if ((altitude == null) || isNaN(altitude)) { // FIXME: move this to it's own command. if expedite can be passed as a sole command it should be its own command if (expedite) { @@ -356,7 +367,7 @@ export default class AircraftCommander { } let ceiling = airport.ctr_ceiling; - if (window.gameController.game.option.get('softCeiling') === 'yes') { + if (this._gameController.game.option.get('softCeiling') === 'yes') { ceiling += 1000; } @@ -380,16 +391,16 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runClearedAsFiled * @return {array} */ runClearedAsFiled(aircraft) { - if (!aircraft.runSID([aircraft.destination])) { + if (!this.runSID(aircraft, [aircraft.destination])) { return [true, 'unable to clear as filed']; } - const airport = window.airportController.airport_get(); + const airport = this._airportController.airport_get(); const { name: procedureName } = airport.sidCollection.findRouteByIcao(aircraft.destination); const readback = {}; @@ -403,19 +414,19 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runClimbViaSID */ runClimbViaSID(aircraft) { if (aircraft.fms.currentLeg.type !== FP_LEG_TYPE.SID || !aircraft.fms.climbViaSID()) { const isWarning = true; - window.uiController.ui_log(`${aircraft.getCallsign()} unable to climb via SID`, isWarning); + this._uiController.ui_log(`${aircraft.getCallsign()} unable to climb via SID`, isWarning); return; } - const airport = window.airportController.airport_get(); + const airport = this._airportController.airport_get(); const { name: procedureName } = airport.sidCollection.findRouteByIcao(aircraft.fms.currentLeg.route.procedure); const readback = { log: `climb via the ${aircraft.fms.currentLeg.route.procedure} departure`, @@ -426,7 +437,7 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runDescendViaSTAR * @param data * @return {boolean|undefined} @@ -434,12 +445,12 @@ export default class AircraftCommander { runDescendViaSTAR(aircraft) { if (!aircraft.fms.descendViaSTAR() || !aircraft.fms.following.star) { const isWarning = true; - window.uiController.ui_log(`${aircraft.getCallsign()}, unable to descend via STAR`, isWarning); + this._uiController.ui_log(`${aircraft.getCallsign()}, unable to descend via STAR`, isWarning); return; } - const airport = window.airportController.airport_get(); + const airport = this._airportController.airport_get(); const { name: procedureName } = airport.starCollection.findRouteByIcao(aircraft.fms.currentLeg.route.procedure); const readback = { log: `descend via the ${aircraft.fms.following.star} arrival`, @@ -450,13 +461,15 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runSpeed * @param data */ runSpeed(aircraft, data) { const speed = data[0]; + // this condition should never happen here it will be caught in the `CommandParser` + // FIXME: remove this if block if (_isNaN(speed)) { return ['fail', 'speed not understood']; } @@ -474,12 +487,12 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runHold * @param data */ runHold(aircraft, data) { - const airport = window.airportController.airport_get(); + const airport = this._airportController.airport_get(); let dirTurns = data[0]; let legLength = data[1]; let holdFix = data[2]; @@ -514,10 +527,14 @@ export default class AircraftCommander { // Determine whether or not to enter the hold from present position if (holdFix) { + // FIXME: replace `vradial(vsub())` with `bearingToPoint()` // holding over a specific fix (currently only able to do so on inbound course) inboundHdg = vradial(vsub(aircraft.position, holdFixLocation)); if (holdFix !== aircraft.fms.currentWaypoint.fix) { + // TODO: break up the inline creation of Waypoints by setting them to constants with meaningful + // names first, then use those consts to send to the fms method + // not yet headed to the hold fix aircraft.fms.insertLegHere({ type: 'fix', @@ -554,6 +571,7 @@ export default class AircraftCommander { }); } else { // TODO: this should be a `Waypoint` + // already currently going to the hold fix // Force the initial turn to outbound heading when entering the hold aircraft.fms.appendWaypoint({ @@ -605,7 +623,7 @@ export default class AircraftCommander { }); } - // TODO: abstract to method `.getInboundCardinalDirection()` + // TODO: abstract to helper function `.getInboundCardinalDirection(inboundHeading)` const inboundDir = radio_cardinalDir_names[getCardinalDirection(radians_normalize(inboundHdg + Math.PI)).toLowerCase()]; if (holdFix) { @@ -616,14 +634,15 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runDirect * @param data */ runDirect(aircraft, data) { + // TODO: maybe handle with parser? const fixname = data[0].toUpperCase(); - // TODO replace with FixCollection - const fix = window.airportController.airport_get().getFixPosition(fixname); + // TODO replace with FixCollection? + const fix = this._airportController.airport_get().getFixPosition(fixname); if (!fix) { return ['fail', `unable to find fix called ${fixname}`]; @@ -644,7 +663,7 @@ export default class AircraftCommander { let fail; const fixes = _map(data, (fixname) => { // TODO: this may beed to be the FixCollection - const fix = window.airportController.airport_get().getFixPosition(fixname); + const fix = this._airportController.airport_get().getFixPosition(fixname); if (!fix) { fail = ['fail', `unable to find fix called ${fixname}`]; @@ -683,7 +702,7 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runFlyPresentHeading * @param data */ @@ -695,7 +714,7 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runSayRoute * @param data */ @@ -707,11 +726,11 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runSID */ runSID(aircraft, data) { - const airport = window.airportController.airport_get(); + const airport = this._airportController.airport_get(); const { sidCollection } = airport; const sidId = data[0]; const standardRouteModel = sidCollection.findRouteByIcao(sidId); @@ -747,13 +766,13 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runSTAR * @param data {array} a string representation of the STAR, ex: `QUINN.BDEGA2.KSFO` */ runSTAR(aircraft, data) { const routeModel = new RouteModel(data[0]); - const airport = window.airportController.airport_get(); + const airport = this._airportController.airport_get(); const { name: starName } = airport.starCollection.findRouteByIcao(routeModel.procedure); if (aircraft.category !== FLIGHT_CATEGORY.ARRIVAL) { @@ -778,7 +797,7 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runMoveDataBlock * @param data */ @@ -797,7 +816,7 @@ export default class AircraftCommander { * Adds a new Leg to fms with a user specified route * Note: See notes on 'runReroute' for how to format input for this command * - * @for AircraftInstanceModel + * @for AircraftCommander * @method runRoute * @param data */ @@ -841,7 +860,7 @@ export default class AircraftCommander { * all other points that will be simply a fix direct to another fix need * to be connected with double-dots (eg HLI..SQS..BERRA..JAN..KJAN) * - * @for AircraftInstanceModel + * @for AircraftCommander * @method runReroute * @param data */ @@ -880,7 +899,7 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runTaxi * @param data */ @@ -904,7 +923,7 @@ export default class AircraftCommander { // Set the runway to taxi to if (data[0]) { - if (window.airportController.airport_get().getRunway(data[0].toUpperCase())) { + if (this._airportController.airport_get().getRunway(data[0].toUpperCase())) { aircraft.setDepartureRunway(data[0].toUpperCase()); } else { return ['fail', `no runway ${data[0].toUpperCase()}`]; @@ -912,8 +931,8 @@ export default class AircraftCommander { } // Start the taxi - aircraft.taxi_start = window.gameController.game_time(); - const runway = window.airportController.airport_get().getRunway(aircraft.rwy_dep); + aircraft.taxi_start = this._gameController.game_time(); + const runway = this._airportController.airport_get().getRunway(aircraft.rwy_dep); runway.addQueue(this); aircraft.mode = FLIGHT_MODES.TAXI; @@ -927,7 +946,7 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runTakeoff * @param data */ @@ -955,18 +974,18 @@ export default class AircraftCommander { return ['fail', 'no altitude assigned']; } - const runway = window.airportController.airport_get().getRunway(aircraft.rwy_dep); + const runway = this._airportController.airport_get().getRunway(aircraft.rwy_dep); if (runway.removeQueue(this)) { aircraft.mode = FLIGHT_MODES.TAKEOFF; aircraft.scoreWind('taking off'); - aircraft.takeoffTime = window.gameController.game_time(); + aircraft.takeoffTime = this._gameController.game_time(); if (aircraft.fms.currentWaypoint.speed == null) { aircraft.fms.setCurrent({ speed: aircraft.model.speed.cruise }); } - const wind = window.airportController.airport_get().getWind(); + const wind = this._airportController.airport_get().getWind(); const wind_dir = round(radiansToDegrees(wind.angle)); const readback = { // TODO: the wind_dir calculation should be abstracted @@ -984,7 +1003,7 @@ export default class AircraftCommander { runLanding(aircraft, data) { const variant = data[0]; - const runway = window.airportController.airport_get().getRunway(data[1]); + const runway = this._airportController.airport_get().getRunway(data[1]); if (!runway) { return ['fail', `there is no runway ${radio_runway(data[1])}`]; @@ -1003,7 +1022,7 @@ export default class AircraftCommander { } /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runAbort * @param data */ @@ -1016,7 +1035,7 @@ export default class AircraftCommander { console.log('aborted taxi to runway'); const isWarning = true; - window.uiController.ui_log(`${aircraft.getCallsign()} aborted taxi to runway`, isWarning); + this._uiController.ui_log(`${aircraft.getCallsign()} aborted taxi to runway`, isWarning); return ['ok', 'taxiing back to terminal']; } else if (aircraft.mode === FLIGHT_MODES.WAITING) { @@ -1055,17 +1074,17 @@ export default class AircraftCommander { // FIXME: is this in use? /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runDebug */ - runDebug() { - window.aircraft = this; + runDebug(aircraft) { + window.aircraft = aircraft; return ['ok', { log: 'in the console, look at the variable ‘aircraft’', say: '' }]; } // FIXME: is this in use? /** - * @for AircraftInstanceModel + * @for AircraftCommander * @method runDelete */ runDelete() { From 6d253bf30fbd4b4254161e75db5a3b4611904967 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Wed, 21 Dec 2016 22:35:23 -0600 Subject: [PATCH 03/32] feature/ATC-181 - changes ref from aircraft to this --- src/assets/scripts/client/aircraft/AircraftCommander.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/scripts/client/aircraft/AircraftCommander.js b/src/assets/scripts/client/aircraft/AircraftCommander.js index f1142f0a..36fcd1a0 100644 --- a/src/assets/scripts/client/aircraft/AircraftCommander.js +++ b/src/assets/scripts/client/aircraft/AircraftCommander.js @@ -708,7 +708,7 @@ export default class AircraftCommander { */ runFlyPresentHeading(aircraft, data) { aircraft.cancelFix(); - aircraft.runHeading([null, radiansToDegrees(aircraft.heading)]); + this.runHeading(aircraft, [null, radiansToDegrees(aircraft.heading)]); return ['ok', 'fly present heading']; } From ed4b04dd792c1ccee1eafba918b4e459a650207c Mon Sep 17 00:00:00 2001 From: Abraham Anderson Date: Tue, 27 Dec 2016 06:57:46 -0600 Subject: [PATCH 04/32] Replaced string sid with enums value --- .../scripts/client/aircraft/AircraftInstanceModel.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 40d5cff5..3df34379 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -38,7 +38,8 @@ import { km, radiansToDegrees, degreesToRadians, heading_to_string } from '../ut import { FLIGHT_MODES, FLIGHT_CATEGORY, - WAYPOINT_NAV_MODE + WAYPOINT_NAV_MODE, + FP_LEG_TYPE } from '../constants/aircraftConstants'; import { SELECTORS } from '../constants/selectors'; import { GAME_EVENTS } from '../game/GameController'; @@ -302,7 +303,7 @@ export default class Aircraft { // TODO: this should return early // TODO: use existing enumeration for `sid` - if (leg.type === 'sid') { + if (leg.type === FP_LEG_TYPE.SID) { const a = _map(leg.waypoints, (v) => v.altitude); const cvs = !a.every((v) => v === window.airportController.airport_get().initial_alt); this.fms.followSID(leg.route.routeCode); @@ -396,7 +397,7 @@ export default class Aircraft { // TODO: if we just need the last fix in the list, why loop through all the legs? _forEach(this.fms.legs, (leg) => { - if (leg.type === 'sid') { + if (leg.type === FP_LEG_TYPE.SID) { // TODO: use lodash `_last()` here exit = leg.waypoints[leg.waypoints.length - 1].fix; return; From 5da479f27f596f2597ad2999dc89043100a79197 Mon Sep 17 00:00:00 2001 From: Abraham Anderson Date: Sun, 1 Jan 2017 20:27:39 -0600 Subject: [PATCH 05/32] simplifed logic in AircraftInstanceModel.js for cancelLanding --- .../client/aircraft/AircraftInstanceModel.js | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 3df34379..bc18cf6a 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -593,31 +593,30 @@ export default class Aircraft { */ cancelLanding() { // TODO: this logic could be simplified. do an early return instead of wrapping the entire function in an if. - if (this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.RWY) { - const runway = window.airportController.airport_get().getRunway(this.rwy_arr); + if (this.fms.currentWaypoint.navmode !== WAYPOINT_NAV_MODE.RWY) { + this.fms.setCurrent({ runway: null }); + return false; + } - if (this.mode === FLIGHT_MODES.LANDING) { - // TODO: enumerate the magic numbers - this.fms.setCurrent({ - altitude: Math.max(2000, round((this.altitude / 1000)) * 1000), - heading: runway.angle - }); - } + const runway = window.airportController.airport_get().getRunway(this.rwy_arr); + if (this.mode === FLIGHT_MODES.LANDING) { + // TODO: enumerate the magic numbers this.fms.setCurrent({ - navmode: WAYPOINT_NAV_MODE.HEADING, - runway: null + altitude: Math.max(2000, round((this.altitude / 1000)) * 1000), + heading: runway.angle }); - - this.mode = FLIGHT_MODES.CRUISE; - this.updateStrip(); - - return true; } - this.fms.setCurrent({ runway: null }); + this.fms.setCurrent({ + navmode: WAYPOINT_NAV_MODE.HEADING, + runway: null + }); - return false; + this.mode = FLIGHT_MODES.CRUISE; + this.updateStrip(); + + return true; } // FIXME: is this method still in use? From a5d582fb1a666ed75e90dedd75caee33eabacb46 Mon Sep 17 00:00:00 2001 From: Abraham Anderson Date: Mon, 2 Jan 2017 02:54:47 -0600 Subject: [PATCH 06/32] removed TODO message for simplifing logic --- .../FlightManagementSystem/AircraftFlightManagementSystem.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/assets/scripts/client/aircraft/FlightManagementSystem/AircraftFlightManagementSystem.js b/src/assets/scripts/client/aircraft/FlightManagementSystem/AircraftFlightManagementSystem.js index 1fa72fa7..b41518e8 100644 --- a/src/assets/scripts/client/aircraft/FlightManagementSystem/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/client/aircraft/FlightManagementSystem/AircraftFlightManagementSystem.js @@ -1005,4 +1005,8 @@ export default class AircraftFlightManagementSystem { altitudeForCurrentWaypoint() { return this.currentWaypoint.altitude; } + + + + } From 8122c0cd74dff829a6482ccd520a02928a1fef56 Mon Sep 17 00:00:00 2001 From: Abraham Anderson Date: Mon, 2 Jan 2017 05:30:11 -0600 Subject: [PATCH 07/32] simplified updatePhysics with the addition of updateSpeedPhysics --- .../client/aircraft/AircraftInstanceModel.js | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index bc18cf6a..eec36f95 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1282,28 +1282,7 @@ export default class Aircraft { } // SPEED - let difference = null; - - if (this.target.speed < this.speed - 0.01) { - difference = -this.model.rate.decelerate * window.gameController.game_delta() / 2; - - if (this.isOnGround()) { - difference *= 3.5; - } - } else if (this.target.speed > this.speed + 0.01) { - difference = this.model.rate.accelerate * window.gameController.game_delta() / 2; - difference *= extrapolate_range_clamp(0, this.speed, this.model.speed.min, 2, 1); - } - - if (difference) { - const offset = this.speed - this.target.speed; - - if (abs(offset) < abs(difference)) { - this.speed = this.target.speed; - } else { - this.speed += difference; - } - } + this.updateSpeedPhysics(); if (!this.position) { return; @@ -1381,6 +1360,37 @@ export default class Aircraft { } } + /** + * @for updateSpeedPhysics + * @method updateWarning + * This updates the speed for the instance of the aircraft by checking the difference between current speed and requested speed + */ + updateSpeedPhysics() { + let difference = null; + + if (this.target.speed < this.speed - 0.01) { + difference = -this.model.rate.decelerate * window.gameController.game_delta() / 2; + + if (this.isOnGround()) { + // What is 3.5 is this restiance/breaking power? + difference *= 3.5; + } + } else if (this.target.speed > this.speed + 0.01) { + difference = this.model.rate.accelerate * window.gameController.game_delta() / 2; + difference *= extrapolate_range_clamp(0, this.speed, this.model.speed.min, 2, 1); + } + + if (difference) { + const offset = this.speed - this.target.speed; + + if (abs(offset) < abs(difference)) { + this.speed = this.target.speed; + } else { + this.speed += difference; + } + } + } + // TODO: this method needs a lot of love. its much too long with waaay too many nested if/else ifs. /** * @for AircraftInstanceModel From 692e5d67f2b5723657b8e54f6b00a2ecd9abac90 Mon Sep 17 00:00:00 2001 From: Abraham Anderson Date: Wed, 4 Jan 2017 03:53:41 -0600 Subject: [PATCH 08/32] simplifed updatePhysics by adding aircraftTurnPhysics() --- .../client/aircraft/AircraftInstanceModel.js | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index eec36f95..c7ee1586 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1220,24 +1220,7 @@ export default class Aircraft { return; } - // TURNING - // this.target.heading = radians_normalize(this.target.heading); - if (!this.isOnGround() && this.heading !== this.target.heading) { - // Perform standard turns 3 deg/s or 25 deg bank, whichever - // requires less bank angle. - // Formula based on http://aviation.stackexchange.com/a/8013 - const turn_rate = clamp(0, 1 / (this.speed / 8.883031), 0.0523598776); - const turn_amount = turn_rate * window.gameController.game_delta(); - const offset = angle_offset(this.target.heading, this.heading); - - if (abs(offset) < turn_amount) { - this.heading = this.target.heading; - } else if ((offset < 0 && this.target.turn === null) || this.target.turn === 'left') { - this.heading -= turn_amount; - } else if ((offset > 0 && this.target.turn === null) || this.target.turn === 'right') { - this.heading += turn_amount; - } - } + this.aircraftTurnPsyics(); // ALTITUDE let distance = null; @@ -1391,6 +1374,34 @@ export default class Aircraft { } } + /** + * @for AircraftInstanceModel + * @method aircraftTurnPsyics + * This turns the aircraft if it is not on the ground and has not arived at its destenation + */ + aircraftTurnPsyics() { + // Exits eary if the airplane is on the ground or at its destenation + if (this.isOnGround() && this.heading === this.target.heading) { + return; + } + // TURNING + // this.target.heading = radians_normalize(this.target.heading); + // Perform standard turns 3 deg/s or 25 deg bank, whichever + // requires less bank angle. + // Formula based on http://aviation.stackexchange.com/a/8013 + const turn_rate = clamp(0, 1 / (this.speed / 8.883031), 0.0523598776); + const turn_amount = turn_rate * window.gameController.game_delta(); + const offset = angle_offset(this.target.heading, this.heading); + + if (abs(offset) < turn_amount) { + this.heading = this.target.heading; + } else if ((offset < 0 && this.target.turn === null) || this.target.turn === 'left') { + this.heading -= turn_amount; + } else if ((offset > 0 && this.target.turn === null) || this.target.turn === 'right') { + this.heading += turn_amount; + } + } + // TODO: this method needs a lot of love. its much too long with waaay too many nested if/else ifs. /** * @for AircraftInstanceModel From 27d93fd7a6b1e2f6e1f01ebcc9ff8e02ffaad6c6 Mon Sep 17 00:00:00 2001 From: Abraham Anderson Date: Wed, 4 Jan 2017 05:13:46 -0600 Subject: [PATCH 09/32] simplified updatePhysics() by adding updateAltitudePhysics() --- .../client/aircraft/AircraftInstanceModel.js | 143 ++++++++++-------- 1 file changed, 76 insertions(+), 67 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index c7ee1586..15830e19 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1220,45 +1220,9 @@ export default class Aircraft { return; } - this.aircraftTurnPsyics(); + this.aircraftTurnPhysics(); - // ALTITUDE - let distance = null; - const expedite_factor = 1.5; - this.trend = 0; - - if (this.target.altitude < this.altitude - 0.02) { - distance = -this.model.rate.descent / 60 * window.gameController.game_delta(); - - if (this.mode === FLIGHT_MODES.LANDING) { - distance *= 3; - } - - this.trend -= 1; - } else if (this.target.altitude > this.altitude + 0.02) { - const climbrate = this.getClimbRate(); - distance = climbrate / 60 * window.gameController.game_delta(); - - if (this.mode === FLIGHT_MODES.LANDING) { - distance *= 1.5; - } - - this.trend = 1; - } - - if (distance) { - if (this.target.expedite) { - distance *= expedite_factor; - } - - const offset = this.altitude - this.target.altitude; - - if (abs(offset) < abs(distance)) { - this.altitude = this.target.altitude; - } else { - this.altitude += distance; - } - } + this.updateAltitudePhysics(); if (this.isOnGround()) { this.trend = 0; @@ -1344,7 +1308,80 @@ export default class Aircraft { } /** - * @for updateSpeedPhysics + * @for AircraftInstanceModel + * @method aircraftTurnPhysics + * This turns the aircraft if it is not on the ground and has not arived at its destenation + */ + aircraftTurnPhysics() { + // Exits eary if the airplane is on the ground or at its destenation + if (this.isOnGround() && this.heading === this.target.heading) { + return; + } + // TURNING + // this.target.heading = radians_normalize(this.target.heading); + // Perform standard turns 3 deg/s or 25 deg bank, whichever + // requires less bank angle. + // Formula based on http://aviation.stackexchange.com/a/8013 + const turn_rate = clamp(0, 1 / (this.speed / 8.883031), 0.0523598776); + const turn_amount = turn_rate * window.gameController.game_delta(); + const offset = angle_offset(this.target.heading, this.heading); + + if (abs(offset) < turn_amount) { + this.heading = this.target.heading; + } else if ((offset < 0 && this.target.turn === null) || this.target.turn === 'left') { + this.heading -= turn_amount; + } else if ((offset > 0 && this.target.turn === null) || this.target.turn === 'right') { + this.heading += turn_amount; + } + } + + /** + * @for AircraftInstanceModel + * @method updateAltitudePhysics + * This updates the Altitude for the instance of the aircraft by checking the difference between current Altitude and requested Altitude + */ + updateAltitudePhysics() { + // ALTITUDE + let distance = null; + const expedite_factor = 1.5; + this.trend = 0; + + if (this.target.altitude < this.altitude - 0.02) { + distance = -this.model.rate.descent / 60 * window.gameController.game_delta(); + + if (this.mode === FLIGHT_MODES.LANDING) { + distance *= 3; + } + + this.trend -= 1; + } else if (this.target.altitude > this.altitude + 0.02) { + const climbrate = this.getClimbRate(); + distance = climbrate / 60 * window.gameController.game_delta(); + + if (this.mode === FLIGHT_MODES.LANDING) { + distance *= 1.5; + } + + this.trend = 1; + } + + if (distance) { + if (this.target.expedite) { + distance *= expedite_factor; + } + + const offset = this.altitude - this.target.altitude; + + if (abs(offset) < abs(distance)) { + this.altitude = this.target.altitude; + } else { + this.altitude += distance; + } + } + } + + /** + * @for AircraftInstanceModel * @method updateWarning * This updates the speed for the instance of the aircraft by checking the difference between current speed and requested speed */ @@ -1374,34 +1411,6 @@ export default class Aircraft { } } - /** - * @for AircraftInstanceModel - * @method aircraftTurnPsyics - * This turns the aircraft if it is not on the ground and has not arived at its destenation - */ - aircraftTurnPsyics() { - // Exits eary if the airplane is on the ground or at its destenation - if (this.isOnGround() && this.heading === this.target.heading) { - return; - } - // TURNING - // this.target.heading = radians_normalize(this.target.heading); - // Perform standard turns 3 deg/s or 25 deg bank, whichever - // requires less bank angle. - // Formula based on http://aviation.stackexchange.com/a/8013 - const turn_rate = clamp(0, 1 / (this.speed / 8.883031), 0.0523598776); - const turn_amount = turn_rate * window.gameController.game_delta(); - const offset = angle_offset(this.target.heading, this.heading); - - if (abs(offset) < turn_amount) { - this.heading = this.target.heading; - } else if ((offset < 0 && this.target.turn === null) || this.target.turn === 'left') { - this.heading -= turn_amount; - } else if ((offset > 0 && this.target.turn === null) || this.target.turn === 'right') { - this.heading += turn_amount; - } - } - // TODO: this method needs a lot of love. its much too long with waaay too many nested if/else ifs. /** * @for AircraftInstanceModel From 367185d73ec83b8bddf6aaf89e92ff0f46c5db3a Mon Sep 17 00:00:00 2001 From: Dabea Date: Sat, 7 Jan 2017 23:21:44 -0600 Subject: [PATCH 10/32] simplified updateAltituePhysics by creating increaseAorcraftA;totdie amd decreaseAircraftAltitude --- .../client/aircraft/AircraftInstanceModel.js | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 15830e19..20a5010e 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1347,22 +1347,13 @@ export default class Aircraft { this.trend = 0; if (this.target.altitude < this.altitude - 0.02) { - distance = -this.model.rate.descent / 60 * window.gameController.game_delta(); - if (this.mode === FLIGHT_MODES.LANDING) { - distance *= 3; - } + this.decreaseAircraftAltitude(); - this.trend -= 1; } else if (this.target.altitude > this.altitude + 0.02) { - const climbrate = this.getClimbRate(); - distance = climbrate / 60 * window.gameController.game_delta(); - if (this.mode === FLIGHT_MODES.LANDING) { - distance *= 1.5; - } + this.increaseAircraftAltitude(); - this.trend = 1; } if (distance) { @@ -1380,6 +1371,36 @@ export default class Aircraft { } } + /** + * @for AircraftInstanceModel + * @method decreaseAircraftAltitude + * Eecreases the aircrafts altitude + */ +decreaseAircraftAltitude() { + distance = -this.model.rate.descent / 60 * window.gameController.game_delta(); + + if (this.mode === FLIGHT_MODES.LANDING) { + distance *= 3; + } + + this.trend -= 1; +} +/** + * @for AircraftInstanceModel + * @method increaseAircraftAltitude + * Increases the aircrafts altitude + */ +increaseAircraftAltitude() { + const climbrate = this.getClimbRate(); + distance = climbrate / 60 * window.gameController.game_delta(); + + if (this.mode === FLIGHT_MODES.LANDING) { + distance *= 1.5; + } + + this.trend = 1; +} + /** * @for AircraftInstanceModel * @method updateWarning From ac7d2fe11444d3b61f78dc447a89967dd6e72a69 Mon Sep 17 00:00:00 2001 From: Dabea Date: Sun, 8 Jan 2017 00:17:32 -0600 Subject: [PATCH 11/32] fixed spaceing and simplied more logic --- .../client/aircraft/AircraftInstanceModel.js | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 20a5010e..d42ba775 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1341,21 +1341,30 @@ export default class Aircraft { * This updates the Altitude for the instance of the aircraft by checking the difference between current Altitude and requested Altitude */ updateAltitudePhysics() { - // ALTITUDE - let distance = null; - const expedite_factor = 1.5; this.trend = 0; if (this.target.altitude < this.altitude - 0.02) { - this.decreaseAircraftAltitude(); - } else if (this.target.altitude > this.altitude + 0.02) { + this.increaseAircraftAltitude(); + } + } - this.increaseAircraftAltitude(); + /** + * @for AircraftInstanceModel + * @method decreaseAircraftAltitude + * Eecreases the aircrafts altitude + */ + decreaseAircraftAltitude() { + let distance = -this.model.rate.descent / 60 * window.gameController.game_delta(); + const expedite_factor = 1.5; + if (this.mode === FLIGHT_MODES.LANDING) { + distance *= 3; } + this.trend -= 1; + if (distance) { if (this.target.expedite) { distance *= expedite_factor; @@ -1372,34 +1381,35 @@ export default class Aircraft { } /** - * @for AircraftInstanceModel - * @method decreaseAircraftAltitude - * Eecreases the aircrafts altitude - */ -decreaseAircraftAltitude() { - distance = -this.model.rate.descent / 60 * window.gameController.game_delta(); + * @for AircraftInstanceModel + * @method increaseAircraftAltitude + * Increases the aircrafts altitude + */ + increaseAircraftAltitude() { + const climbrate = this.getClimbRate(); + const expedite_factor = 1.5; + let distance = climbrate / 60 * window.gameController.game_delta(); - if (this.mode === FLIGHT_MODES.LANDING) { - distance *= 3; - } + if (this.mode === FLIGHT_MODES.LANDING) { + distance *= 1.5; + } - this.trend -= 1; -} -/** - * @for AircraftInstanceModel - * @method increaseAircraftAltitude - * Increases the aircrafts altitude - */ -increaseAircraftAltitude() { - const climbrate = this.getClimbRate(); - distance = climbrate / 60 * window.gameController.game_delta(); + this.trend = 1; - if (this.mode === FLIGHT_MODES.LANDING) { - distance *= 1.5; - } + if (distance) { + if (this.target.expedite) { + distance *= expedite_factor; + } - this.trend = 1; -} + const offset = this.altitude - this.target.altitude; + + if (abs(offset) < abs(distance)) { + this.altitude = this.target.altitude; + } else { + this.altitude += distance; + } + } + } /** * @for AircraftInstanceModel From 3916a4ef73bc150c5f6f4d46f643d1aeea2e6cc0 Mon Sep 17 00:00:00 2001 From: Dabea Date: Sun, 8 Jan 2017 00:21:49 -0600 Subject: [PATCH 12/32] added TODO for increaseAircraftAltitue and decreaseAircraftAlititude --- src/assets/scripts/client/aircraft/AircraftInstanceModel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index d42ba775..3437ab51 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1364,7 +1364,7 @@ export default class Aircraft { } this.trend -= 1; - + //TODO: This might be able to become its own function since it is repeated again in the increaseAircraftAltitude() if (distance) { if (this.target.expedite) { distance *= expedite_factor; @@ -1395,7 +1395,7 @@ export default class Aircraft { } this.trend = 1; - + //TODO: This might be able to become its own function since it is repeated in the decreaseAircraftAltitude() Also I think the outer If() might not be needed if (distance) { if (this.target.expedite) { distance *= expedite_factor; From be945bbe0ba55c6b0f90fb3a4270e148f7c3c27a Mon Sep 17 00:00:00 2001 From: Dabea Date: Sun, 8 Jan 2017 00:30:02 -0600 Subject: [PATCH 13/32] fixed doc block descriptions --- .../scripts/client/aircraft/AircraftInstanceModel.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 3437ab51..4061c072 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1308,9 +1308,9 @@ export default class Aircraft { } /** + * This turns the aircraft if it is not on the ground and has not arived at its destenation * @for AircraftInstanceModel * @method aircraftTurnPhysics - * This turns the aircraft if it is not on the ground and has not arived at its destenation */ aircraftTurnPhysics() { // Exits eary if the airplane is on the ground or at its destenation @@ -1336,9 +1336,9 @@ export default class Aircraft { } /** + * This updates the Altitude for the instance of the aircraft by checking the difference between current Altitude and requested Altitude * @for AircraftInstanceModel * @method updateAltitudePhysics - * This updates the Altitude for the instance of the aircraft by checking the difference between current Altitude and requested Altitude */ updateAltitudePhysics() { this.trend = 0; @@ -1351,9 +1351,9 @@ export default class Aircraft { } /** + * Decreases the aircrafts altitude * @for AircraftInstanceModel * @method decreaseAircraftAltitude - * Eecreases the aircrafts altitude */ decreaseAircraftAltitude() { let distance = -this.model.rate.descent / 60 * window.gameController.game_delta(); @@ -1381,9 +1381,9 @@ export default class Aircraft { } /** + * Increases the aircrafts altitude * @for AircraftInstanceModel * @method increaseAircraftAltitude - * Increases the aircrafts altitude */ increaseAircraftAltitude() { const climbrate = this.getClimbRate(); @@ -1412,9 +1412,9 @@ export default class Aircraft { } /** + * This updates the speed for the instance of the aircraft by checking the difference between current speed and requested speed * @for AircraftInstanceModel * @method updateWarning - * This updates the speed for the instance of the aircraft by checking the difference between current speed and requested speed */ updateSpeedPhysics() { let difference = null; From b5a090e15678a5f75d95f8960be9ed49047a2b3b Mon Sep 17 00:00:00 2001 From: Dabea Date: Sun, 8 Jan 2017 00:39:03 -0600 Subject: [PATCH 14/32] updated aicraftTurnPhysics to updateeAircraftTurnPhysics for more consistent nameing --- src/assets/scripts/client/aircraft/AircraftInstanceModel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 4061c072..a5a5b1a9 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1220,7 +1220,7 @@ export default class Aircraft { return; } - this.aircraftTurnPhysics(); + this.updateAircraftTurnPhysics(); this.updateAltitudePhysics(); @@ -1310,9 +1310,9 @@ export default class Aircraft { /** * This turns the aircraft if it is not on the ground and has not arived at its destenation * @for AircraftInstanceModel - * @method aircraftTurnPhysics + * @method updateAircraftTurnPhysics */ - aircraftTurnPhysics() { + updateAircraftTurnPhysics() { // Exits eary if the airplane is on the ground or at its destenation if (this.isOnGround() && this.heading === this.target.heading) { return; From 1fb3c0167cd1e8e48531587b026cc52981992df6 Mon Sep 17 00:00:00 2001 From: Dabea Date: Tue, 10 Jan 2017 01:20:29 -0600 Subject: [PATCH 15/32] added TODO --- src/assets/scripts/client/aircraft/AircraftInstanceModel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index a5a5b1a9..b01f99ec 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1300,6 +1300,7 @@ export default class Aircraft { this.radial += tau(); } + //TODO: I am not sure what this has to do with aircraft Physics const isInsideAirspace = this.isInsideAirspace(window.airportController.airport_get()); if (isInsideAirspace !== this.inside_ctr) { From abb566efcb7d84c7b9cc6f058e004c58dc06e39d Mon Sep 17 00:00:00 2001 From: Dabea Date: Tue, 10 Jan 2017 02:01:02 -0600 Subject: [PATCH 16/32] simplified cancelFix logic by returning early --- .../scripts/client/aircraft/AircraftInstanceModel.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index b01f99ec..68590b48 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -567,8 +567,10 @@ export default class Aircraft { * @method cancelFix */ cancelFix() { - // TODO: this logic could be simplified. do an early return instead of wrapping the entire function in an if. - if (this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.FIX) { + if (this.fms.currentWaypoint.navmode !== WAYPOINT_NAV_MODE.FIX) { + return false; + } + const curr = this.fms.currentWaypoint; this.fms.appendLeg({ @@ -582,9 +584,6 @@ export default class Aircraft { this.updateStrip(); return true; - } - - return false; } /** From 1a4fe49677b43ed28f6872fb9ea73e1201116384 Mon Sep 17 00:00:00 2001 From: Dabea Date: Tue, 10 Jan 2017 02:03:40 -0600 Subject: [PATCH 17/32] fixed spaceing on cancelFix --- .../client/aircraft/AircraftInstanceModel.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 68590b48..746d5239 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -571,19 +571,19 @@ export default class Aircraft { return false; } - const curr = this.fms.currentWaypoint; + const curr = this.fms.currentWaypoint; - this.fms.appendLeg({ - altitude: curr.altitude, - navmode: WAYPOINT_NAV_MODE.HEADING, - heading: this.heading, - speed: curr.speed - }); + this.fms.appendLeg({ + altitude: curr.altitude, + navmode: WAYPOINT_NAV_MODE.HEADING, + heading: this.heading, + speed: curr.speed + }); - this.fms.nextLeg(); - this.updateStrip(); + this.fms.nextLeg(); + this.updateStrip(); - return true; + return true; } /** From a8680253d382969e39f3bcb36dc1121e9de7b6f4 Mon Sep 17 00:00:00 2001 From: Dabea Date: Tue, 10 Jan 2017 03:21:54 -0600 Subject: [PATCH 18/32] Simplified updatePhysics with the addtion of updateGroundPhyics and updateSimpleGroundSpeedPhyics --- .../client/aircraft/AircraftInstanceModel.js | 100 +++++++++++------- 1 file changed, 61 insertions(+), 39 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 746d5239..c2d8ef5e 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1246,50 +1246,13 @@ export default class Aircraft { this.position_history.push([this.position[0], this.position[1], window.gameController.game_time() / window.gameController.game_speedup()]); } - const angle = this.heading; // FIXME: is this ratio correct? is it 0.000514444 or 0.514444? let scaleSpeed = this.speed * 0.000514444 * window.gameController.game_delta(); // knots to m/s if (window.gameController.game.option.get('simplifySpeeds') === 'no') { - // TODO: this should be abstracted to a helper function - // Calculate the true air speed as indicated airspeed * 1.6% per 1000' - scaleSpeed *= 1 + (this.altitude * 0.000016); - - // Calculate movement including wind assuming wind speed - // increases 2% per 1000' - const wind = window.airportController.airport_get().wind; - let vector; - - if (this.isOnGround()) { - vector = vscale([sin(angle), cos(angle)], scaleSpeed); - } else { - let crab_angle = 0; - - // Compensate for crosswind while tracking a fix or on ILS - if (this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.FIX || this.mode === FLIGHT_MODES.LANDING) { - // TODO: this should be abstracted to a helper function - const offset = angle_offset(this.heading, wind.angle + Math.PI); - crab_angle = Math.asin((wind.speed * sin(offset)) / this.speed); - } - - // TODO: this should be abstracted to a helper function - vector = vadd(vscale( - vturn(wind.angle + Math.PI), - wind.speed * 0.000514444 * window.gameController.game_delta()), - vscale(vturn(angle + crab_angle), scaleSpeed) - ); - } - - this.ds = vlen(vector); - // TODO: this should be abstracted to a helper function - this.groundSpeed = this.ds / 0.000514444 / window.gameController.game_delta(); - this.groundTrack = vradial(vector); - this.position = vadd(this.position, vector); + this.updatecGroundSpeedPhysics(scaleSpeed); } else { - this.ds = scaleSpeed; - this.groundSpeed = this.speed; - this.groundTrack = this.heading; - this.position = vadd(this.position, vscale([sin(angle), cos(angle)], scaleSpeed)); + this.updatecSimpleGroundSpeedPhysics(scaleSpeed); } this.distance = vlen(this.position); @@ -1442,6 +1405,65 @@ export default class Aircraft { } } + /** + * This calculates the ground speed + * @for AircraftInstanceModel + * @method updateVectorPhysics + * @param scaleSpeed + */ + updatecGroundSpeedPhysics(scaleSpeed) { + // TODO: this should be abstracted to a helper function + // Calculate the true air speed as indicated airspeed * 1.6% per 1000' + const trueAirSpeed = scaleSpeed * (1 + this.altitude * 0.000016); + + // Calculate movement including wind assuming wind speed + // increases 2% per 1000' + const wind = window.airportController.airport_get().wind; + const angle = this.heading; + let vector; + + if (this.isOnGround()) { + vector = vscale([sin(angle), cos(angle)], trueAirSpeed); + } else { + let crab_angle = 0; + + // Compensate for crosswind while tracking a fix or on ILS + if (this.fms.currentWaypoint.navmode === WAYPOINT_NAV_MODE.FIX || this.mode === FLIGHT_MODES.LANDING) { + // TODO: this should be abstracted to a helper function + const offset = angle_offset(this.heading, wind.angle + Math.PI); + crab_angle = Math.asin((wind.speed * sin(offset)) / this.speed); + } + + // TODO: this should be abstracted to a helper function + vector = vadd(vscale( + vturn(wind.angle + Math.PI), + wind.speed * 0.000514444 * window.gameController.game_delta()), + vscale(vturn(angle + crab_angle), trueAirSpeed) + ); + } + + this.ds = vlen(vector); + // TODO: this should be abstracted to a helper function + this.groundSpeed = this.ds / 0.000514444 / window.gameController.game_delta(); + this.groundTrack = vradial(vector); + this.position = vadd(this.position, vector); + } + + /** + * This calculates the simplify ground speed + * @for AircraftInstanceModel + * @method updatecSimpleGroundSpeedPhysics + * @param scaleSpeed + */ + updatecSimpleGroundSpeedPhysics(scaleSpeed) { + const angle = this.heading; + + this.ds = scaleSpeed; + this.groundSpeed = this.speed; + this.groundTrack = this.heading; + this.position = vadd(this.position, vscale([sin(angle), cos(angle)], scaleSpeed)); + } + // TODO: this method needs a lot of love. its much too long with waaay too many nested if/else ifs. /** * @for AircraftInstanceModel From 2c2fcfbde7cdc7bc052cfc53b57d84f6f01c3aef Mon Sep 17 00:00:00 2001 From: Dabea Date: Tue, 10 Jan 2017 03:52:08 -0600 Subject: [PATCH 19/32] removed unused variable and change scaleSpeed to a const --- src/assets/scripts/client/aircraft/AircraftInstanceModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index c2d8ef5e..b40a5368 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1247,7 +1247,7 @@ export default class Aircraft { } // FIXME: is this ratio correct? is it 0.000514444 or 0.514444? - let scaleSpeed = this.speed * 0.000514444 * window.gameController.game_delta(); // knots to m/s + const scaleSpeed = this.speed * 0.000514444 * window.gameController.game_delta(); // knots to m/s if (window.gameController.game.option.get('simplifySpeeds') === 'no') { this.updatecGroundSpeedPhysics(scaleSpeed); From 72d6f2c9359a34ddd16790fdda48e522319dde23 Mon Sep 17 00:00:00 2001 From: Abraham Anderson Date: Wed, 11 Jan 2017 02:34:55 -0600 Subject: [PATCH 20/32] fixed spelling --- .../scripts/client/aircraft/AircraftInstanceModel.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index b40a5368..bb3b70b6 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1250,9 +1250,9 @@ export default class Aircraft { const scaleSpeed = this.speed * 0.000514444 * window.gameController.game_delta(); // knots to m/s if (window.gameController.game.option.get('simplifySpeeds') === 'no') { - this.updatecGroundSpeedPhysics(scaleSpeed); + this.updateGroundSpeedPhysics(scaleSpeed); } else { - this.updatecSimpleGroundSpeedPhysics(scaleSpeed); + this.updateSimpleGroundSpeedPhysics(scaleSpeed); } this.distance = vlen(this.position); @@ -1411,7 +1411,7 @@ export default class Aircraft { * @method updateVectorPhysics * @param scaleSpeed */ - updatecGroundSpeedPhysics(scaleSpeed) { + updateGroundSpeedPhysics(scaleSpeed) { // TODO: this should be abstracted to a helper function // Calculate the true air speed as indicated airspeed * 1.6% per 1000' const trueAirSpeed = scaleSpeed * (1 + this.altitude * 0.000016); @@ -1452,10 +1452,10 @@ export default class Aircraft { /** * This calculates the simplify ground speed * @for AircraftInstanceModel - * @method updatecSimpleGroundSpeedPhysics + * @method updateSimpleGroundSpeedPhysics * @param scaleSpeed */ - updatecSimpleGroundSpeedPhysics(scaleSpeed) { + updateSimpleGroundSpeedPhysics(scaleSpeed) { const angle = this.heading; this.ds = scaleSpeed; From 4254f7434c7ba951d1613abe3fc0b545e8c0dfa5 Mon Sep 17 00:00:00 2001 From: Dabea Date: Thu, 12 Jan 2017 16:47:44 -0600 Subject: [PATCH 21/32] created warnInterceptAngle function to simplifiy updateTarget and reduce repeat code --- .../client/aircraft/AircraftInstanceModel.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index bb3b70b6..a8e95b19 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1011,13 +1011,9 @@ export default class Aircraft { // TODO: Abstraction on the below, to remove duplicate code // Intercept Angle if (!assignedHdg && courseDifference > maxInterceptAngle) { // intercept via fixes - const isWarning = true; - window.uiController.ui_log(`${this.getCallsign()} approach course intercept angle was greater than 30 degrees`, isWarning); - window.gameController.events_recordNew(GAME_EVENTS.ILLEGAL_APPROACH_CLEARANCE); + this.warnInterceptAngle(); } else if (interceptAngle > maxInterceptAngle) { // intercept via vectors - const isWarning = true; - window.uiController.ui_log(`${this.getCallsign()} approach course intercept angle was greater than 30 degrees`, isWarning); - window.gameController.events_recordNew(GAME_EVENTS.ILLEGAL_APPROACH_CLEARANCE); + this.warnInterceptAngle(); } // Glideslope intercept @@ -1201,6 +1197,17 @@ export default class Aircraft { } } + /** + * This will display a waring and record an illegal approach event + * @for warnInterceptAngle + * @method updatePhysics + */ + warnInterceptAngle() { + const isWarning = true; + window.uiController.ui_log(`${this.getCallsign()} approach course intercept angle was greater than 30 degrees`, isWarning); + window.gameController.events_recordNew(GAME_EVENTS.ILLEGAL_APPROACH_CLEARANCE); + } + // TODO: this method needs a lot of love. its much too long with waaay too many nested if/else ifs. /** * @for AircraftInstanceModel From 99ba21897769d628d431e2b2f841986e071cf457 Mon Sep 17 00:00:00 2001 From: Dabea Date: Thu, 12 Jan 2017 16:49:15 -0600 Subject: [PATCH 22/32] Removed TODO that was fixed --- src/assets/scripts/client/aircraft/AircraftInstanceModel.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index a8e95b19..6266d340 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1008,7 +1008,6 @@ export default class Aircraft { this.target.heading = angle; // Check legality of localizer interception if (!this.projected) { // do not give penalty during a future projection - // TODO: Abstraction on the below, to remove duplicate code // Intercept Angle if (!assignedHdg && courseDifference > maxInterceptAngle) { // intercept via fixes this.warnInterceptAngle(); From e5b547a926c4501ee2a17f3d56798b8893a5cc77 Mon Sep 17 00:00:00 2001 From: Dabea Date: Thu, 12 Jan 2017 17:02:55 -0600 Subject: [PATCH 23/32] simplifed updateTarget by adding updateFixTarget --- .../client/aircraft/AircraftInstanceModel.js | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 6266d340..8ba6b3da 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1198,8 +1198,8 @@ export default class Aircraft { /** * This will display a waring and record an illegal approach event - * @for warnInterceptAngle - * @method updatePhysics + * @for AircraftInstanceModel + * @method warnInterceptAngle */ warnInterceptAngle() { const isWarning = true; @@ -1207,6 +1207,40 @@ export default class Aircraft { window.gameController.events_recordNew(GAME_EVENTS.ILLEGAL_APPROACH_CLEARANCE); } + /** + * This will display update the fix for the aircraft + * @for AircraftInstanceModel + * @method updateFixTarget + */ + updateFixTarget() { + const fix = this.fms.currentWaypoint.location; + if (!fix) { + console.error(`${this.getCallsign()} using "fix" navmode, but no fix location!`); + console.log(this.fms); + console.log(this.fms.currentWaypoint); + } + + const vector_to_fix = vsub(this.position, fix); + const distance_to_fix = distance2d(this.position, fix); + + if ((distance_to_fix < 1) || + ((distance_to_fix < 10) && + (distance_to_fix < window.aircraftController.aircraft_turn_initiation_distance(this, fix))) + ) { + // if there are more waypoints available + if (!this.fms.atLastWaypoint()) { + this.fms.nextWaypoint(); + } else { + this.cancelFix(); + } + + this.updateStrip(); + } else { + this.target.heading = vradial(vector_to_fix) - Math.PI; + this.target.turn = null; + } + } + // TODO: this method needs a lot of love. its much too long with waaay too many nested if/else ifs. /** * @for AircraftInstanceModel From f1c6218dffe411bd5da4bc01f50f60402dae1f24 Mon Sep 17 00:00:00 2001 From: Dabea Date: Sun, 15 Jan 2017 00:54:13 -0600 Subject: [PATCH 24/32] 'Completed Merge from devlo' --- .../client/aircraft/AircraftInstanceModel.js | 989 ------------------ 1 file changed, 989 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 05244d7b..3d3b8d87 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -493,997 +493,8 @@ export default class Aircraft { let heavy = ''; if (this.model.weightclass === 'H') { -<<<<<<< HEAD heavy = ' heavy'; } -======= - heavy = 'heavy'; - } - - if (this.model.weightclass === 'U') { - heavy = 'super'; - } - - let callsign = this.callsign; - if (condensed) { - const length = 2; - callsign = callsign.substr(callsign.length - length); - } - - let cs = window.airlineController.airline_get(this.airline).callsign; - - if (cs === 'November') { - cs += ` ${radio_spellOut(callsign)} ${heavy}`; - } else { - cs += ` ${groupNumbers(callsign, this.airline)} ${heavy}`; - } - - return cs; - } - - /** - * @for AircraftInstanceModel - * @method getClimbRate - * @return {number} - */ - getClimbRate() { - const altitude = this.altitude; - const rate = this.model.rate.climb; - const ceiling = this.model.ceiling; - let serviceCeilingClimbRate; - let cr_uncorr; - let cr_current; - - if (this.model.engines.type === 'J') { - serviceCeilingClimbRate = 500; - } else { - serviceCeilingClimbRate = 100; - } - - // TODO: enumerate the magic number - // in troposphere - if (this.altitude < 36152) { - // TODO: break this assignemnt up into smaller parts and holy magic numbers! enumerate the magic numbers - cr_uncorr = rate * 420.7 * ((1.232 * Math.pow((518.6 - 0.00356 * altitude) / 518.6, 5.256)) / (518.6 - 0.00356 * altitude)); - cr_current = cr_uncorr - (altitude / ceiling * cr_uncorr) + (altitude / ceiling * serviceCeilingClimbRate); - } else { - // in lower stratosphere - // re-do for lower stratosphere - // Reference: https://www.grc.nasa.gov/www/k-12/rocket/atmos.html - // also recommend using graphing calc from desmos.com - return this.model.rate.climb; // <-- NOT VALID! Just a placeholder! - } - - return cr_current; - } - - /** - * @for AircraftInstanceModel - * @method hideStrip - */ - hideStrip() { - this.$html.hide(600); - } - - - // TODO: move aircraftCommands to a new class - /** - * @for AircraftInstanceModel - * @method runCommands - * @param commands - */ - runCommands(commands) { - if (!this.inside_ctr) { - return true; - } - - let response = []; - let response_end = ''; - const deferred = []; - - for (let i = 0; i < commands.length; i++) { - const command = commands[i][0]; - const args = commands[i].splice(1); - - if (command === FLIGHT_MODES.TAKEOFF) { - deferred.push([command, args]); - continue; - } - - let retval = this.run(command, args); - - if (retval) { - if (!_has(retval[1], 'log') || !_has(retval[1], 'say')) { - // TODO: reassigning a value using itself is dangerous. this should be re-wroked - retval = [ - retval[0], - { - log: retval[1], - say: retval[1] - } - ]; - } - - response.push(retval[1]); - - if (retval[2]) { - response_end = retval[2]; - } - } - } - - for (let i = 0; i < deferred.length; i += 1) { - const command = deferred[i][0]; - const args = deferred[i][1]; - const retval = this.run(command, args); - - if (retval) { - // TODO: fix the logic here this very purposly using `!=`. length is not an object and thus, - // never null but by using coercion it evaluates to falsey if its not an array - // true if array, and not log/say object - if (retval[1].length != null) { - // make into log/say object - retval[1] = { - say: retval[1], - log: retval[1] - }; - } - - response.push(retval[1]); - } - } - - if (commands.length === 0) { - response = [{ - say: 'not understood', - log: 'not understood' - }]; - response_end = 'say again'; - } - - if (response.length >= 1) { - if (response_end) { - response_end = `, ${response_end}`; - } - - const r_log = _map(response, (r) => r.log).join(', '); - const r_say = _map(response, (r) => r.say).join(', '); - - window.uiController.ui_log(`${this.getCallsign()}, ${r_log} ${response_end}`); - speech_say([ - { type: 'callsign', content: this }, - { type: 'text', content: `${r_say} ${response_end}` } - ]); - } - - this.updateStrip(); - - return true; - } - - /** - * @for AircraftInstanceModel - * @method run - * @param command - * @param data - * @return {function} - */ - run(command, data) { - let call_func; - - if (COMMANDS[command]) { - call_func = COMMANDS[command]; - } - - if (!call_func) { - return ['fail', 'not understood']; - } - - return this[call_func](data); - } - - /** - * @for AircraftInstanceModel - * @method runHeading - * @param data - */ - runHeading(data) { - const airport = window.airportController.airport_get(); - const direction = data[0]; - let heading = data[1]; - const incremental = data[2]; - let amount = 0; - let instruction; - - if (_isNaN(heading)) { - return ['fail', 'heading not understood']; - } - - if (incremental) { - amount = heading; - - if (direction === 'left') { - heading = radiansToDegrees(this.heading) - amount; - } else if (direction === 'right') { - heading = radiansToDegrees(this.heading) + amount; - } - } - - // TODO: this probably shouldn't be the AircraftInstanceModel's job. this logic should belong somewhere else. - // Update the FMS - let wp = this.fms.currentWaypoint; - const leg = this.fms.currentLeg; - const f = this.fms.following; - - if (wp.navmode === WAYPOINT_NAV_MODE.RWY) { - this.cancelLanding(); - } - - // already being vectored or holding. Will now just change the assigned heading. - if (wp.navmode === WAYPOINT_NAV_MODE.HEADING) { - this.fms.setCurrent({ - altitude: wp.altitude, - navmode: WAYPOINT_NAV_MODE.HEADING, - heading: degreesToRadians(heading), - speed: wp.speed, - turn: direction, - hold: false - }); - } else if (wp.navmode === WAYPOINT_NAV_MODE.HOLD) { - // in hold. Should leave the hold, and add leg for vectors - const index = this.fms.current[0] + 1; - const waypointToAdd = new Waypoint( - { - altitude: wp.altitude, - navmode: WAYPOINT_NAV_MODE.HEADING, - heading: degreesToRadians(heading), - speed: wp.speed, - turn: direction, - hold: false - }, - airport - ); - - // add new Leg after hold leg - this.fms.insertLeg({ - firstIndex: index, - waypoints: [waypointToAdd] - }); - - // move from hold leg to vector leg. - this.fms.nextWaypoint(); - } else if (f.sid || f.star || f.awy) { - const waypointToAdd = new Waypoint( - { - altitude: wp.altitude, - navmode: WAYPOINT_NAV_MODE.HEADING, - heading: degreesToRadians(heading), - speed: wp.speed, - turn: direction, - hold: false - }, - airport - ); - - // TODO: this should be an FMS class method that accepts a new `waypointToAdd` - // insert wp with heading at current position within the already active leg - leg.waypoints.splice(this.fms.current[1], 0, waypointToAdd); - } else if (leg.route !== '[radar vectors]') { - // needs new leg added - if (this.fms.atLastWaypoint()) { - const waypointToAdd = new Waypoint( - { - altitude: wp.altitude, - navmode: WAYPOINT_NAV_MODE.HEADING, - heading: degreesToRadians(heading), - speed: wp.speed, - turn: direction, - hold: false - }, - airport - ); - - this.fms.appendLeg({ - waypoints: [waypointToAdd] - }); - - this.fms.nextLeg(); - } else { - const waypointToAdd = new Waypoint( - { - altitude: wp.altitude, - navmode: WAYPOINT_NAV_MODE.HEADING, - heading: degreesToRadians(heading), - speed: wp.speed, - turn: direction, - hold: false - }, - airport - ); - - this.fms.insertLegHere({ - waypoints: [waypointToAdd] - }); - } - } - - wp = this.fms.currentWaypoint; // update 'wp' - - // Construct the readback - instruction = 'fly heading'; - if (direction) { - instruction = `turn ${direction} heading`; - } - - const readback = {}; - readback.log = `${instruction} ${heading_to_string(wp.heading)}`; - readback.say = `${instruction} ${radio_heading(heading_to_string(wp.heading))}`; - - if (incremental) { - readback.log = `turn ${amount} degrees ${direction}`; - readback.say = `turn ${groupNumbers(amount)} degrees ${direction}`; - } - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runAltitude - * @param data - */ - runAltitude(data) { - const altitude = data[0]; - let expedite = data[1]; - const airport = window.airportController.airport_get(); - - if ((altitude == null) || isNaN(altitude)) { - // FIXME: move this to it's own command. if expedite can be passed as a sole command it should be its own command - if (expedite) { - this.fms.setCurrent({ expedite: true }); - const radioTrendAltitude = radio_trend('altitude', this.altitude, this.fms.altitudeForCurrentWaypoint()); - - return ['ok', `${radioTrendAltitude} ${this.fms.altitudeForCurrentWaypoint()} expedite`]; - } - - return ['fail', 'altitude not understood']; - } - - if (this.mode === FLIGHT_MODES.LANDING) { - this.cancelLanding(); - } - - let ceiling = airport.ctr_ceiling; - if (window.gameController.game.option.get('softCeiling') === 'yes') { - ceiling += 1000; - } - - this.fms.setAll({ - // TODO: enumerate the magic numbers - altitude: clamp(round(airport.elevation / 100) * 100 + 1000, altitude, ceiling), - expedite: expedite - }); - - const newAltitude = this.fms.altitudeForCurrentWaypoint(); - const instruction = radio_trend('altitude', this.altitude, newAltitude); - const readback_text = `${instruction} ${newAltitude}`; - const readback_verbal = `${instruction} ${radio_altitude(newAltitude)}`; - - const readback = { - log: readback_text, - say: readback_verbal - }; - - if (expedite) { - readback.log = `${readback_text} and expedite`; - readback.say = `${readback_verbal} and expedite`; - } - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runClearedAsFiled - * @return {array} - */ - runClearedAsFiled() { - if (!this.runSID([this.destination])) { - return [true, 'unable to clear as filed']; - } - - const airport = window.airportController.airport_get(); - const { name: procedureName } = airport.sidCollection.findRouteByIcao(this.destination); - const readback = {}; - - readback.log = `cleared to destination via the ${this.destination} departure, then as filed. Climb and ` + - `maintain ${airport.initial_alt}, expect ${this.fms.fp.altitude} 10 minutes after departure`; - readback.say = `cleared to destination via the ${procedureName} ` + - `departure, then as filed. Climb and maintain ${radio_altitude(airport.initial_alt)}, ` + - `expect ${radio_altitude(this.fms.fp.altitude)}, ${radio_spellOut('10')} minutes after departure'`; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runClimbViaSID - */ - runClimbViaSID() { - if (this.fms.currentLeg.type !== FP_LEG_TYPE.SID || !this.fms.climbViaSID()) { - const isWarning = true; - - window.uiController.ui_log(`${this.getCallsign()} unable to climb via SID`, isWarning); - - return; - } - - const airport = window.airportController.airport_get(); - const { name: procedureName } = airport.sidCollection.findRouteByIcao(this.fms.currentLeg.route.procedure); - const readback = { - log: `climb via the ${this.fms.currentLeg.route.procedure} departure`, - say: `climb via the ${procedureName} departure` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runDescendViaSTAR - * @param data - * @return {boolean|undefined} - */ - runDescendViaSTAR() { - if (!this.fms.descendViaSTAR() || !this.fms.following.star) { - const isWarning = true; - window.uiController.ui_log(`${this.getCallsign()}, unable to descend via STAR`, isWarning); - - return; - } - - const airport = window.airportController.airport_get(); - const { name: procedureName } = airport.starCollection.findRouteByIcao(this.fms.currentLeg.route.procedure); - const readback = { - log: `descend via the ${this.fms.following.star} arrival`, - say: `descend via the ${procedureName} arrival` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runSpeed - * @param data - */ - runSpeed(data) { - const speed = data[0]; - - if (_isNaN(speed)) { - return ['fail', 'speed not understood']; - } - - const clampedSpeed = clamp(this.model.speed.min, speed, this.model.speed.max); - this.fms.setAll({ speed: clampedSpeed }); - - const radioTrendSpeed = radio_trend('speed', this.speed, this.fms.currentWaypoint.speed); - const readback = { - log: `${radioTrendSpeed} ${this.fms.currentWaypoint.speed}`, - say: `${radioTrendSpeed} ${radio_spellOut(this.fms.currentWaypoint.speed)}` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runHold - * @param data - */ - runHold(data) { - const airport = window.airportController.airport_get(); - let dirTurns = data[0]; - let legLength = data[1]; - let holdFix = data[2]; - let holdFixLocation = null; - let inboundHdg; - // let inboundDir; - - // TODO: this might be better handled from within the parser - if (dirTurns == null) { - // standard for holding patterns is right-turns - dirTurns = 'right'; - } - - // TODO: this might be better handled from within the parser - if (legLength == null) { - legLength = '1min'; - } - - // TODO: simplify this nested if. - if (holdFix !== null) { - holdFix = holdFix.toUpperCase(); - holdFixLocation = airport.getFixPosition(holdFix); - - if (!holdFixLocation) { - return ['fail', `unable to find fix ${holdFix}`]; - } - } - - if (this.isTakeoff() && !holdFix) { - return ['fail', 'where do you want us to hold?']; - } - - // Determine whether or not to enter the hold from present position - if (holdFix) { - // holding over a specific fix (currently only able to do so on inbound course) - inboundHdg = vradial(vsub(this.position, holdFixLocation)); - - if (holdFix !== this.fms.currentWaypoint.fix) { - // not yet headed to the hold fix - this.fms.insertLegHere({ - type: 'fix', - route: '[GPS/RNAV]', - waypoints: [ - // proceed direct to holding fix - new Waypoint( - { - fix: holdFix, - altitude: this.fms.altitudeForCurrentWaypoint(), - speed: this.fms.currentWaypoint.speed - }, - airport - ), - // then enter the hold - new Waypoint( - { - navmode: WAYPOINT_NAV_MODE.HOLD, - speed: this.fms.currentWaypoint.speed, - altitude: this.fms.altitudeForCurrentWaypoint(), - fix: null, - hold: { - fixName: holdFix, - fixPos: holdFixLocation, - dirTurns: dirTurns, - legLength: legLength, - inboundHdg: inboundHdg, - timer: null - } - }, - airport - ) - ] - }); - } else { - // TODO: this should be a `Waypoint` - // already currently going to the hold fix - // Force the initial turn to outbound heading when entering the hold - this.fms.appendWaypoint({ - navmode: WAYPOINT_NAV_MODE.HOLD, - speed: this.fms.currentWaypoint.speed, - altitude: this.fms.altitudeForCurrentWaypoint(), - fix: null, - hold: { - fixName: holdFix, - fixPos: holdFixLocation, - dirTurns: dirTurns, - legLength: legLength, - inboundHdg: inboundHdg, - timer: null - } - }); - } - } else { - // holding over present position (currently only able to do so on present course) - holdFixLocation = this.position; // make a/c hold over their present position - inboundHdg = this.heading; - - // TODO: these aren't `Waypoints` and they should be - this.fms.insertLegHere({ - type: 'fix', - waypoints: [ - { // document the present position as the 'fix' we're holding over - navmode: WAYPOINT_NAV_MODE.FIX, - fix: '[custom]', - location: holdFixLocation, - altitude: this.fms.altitudeForCurrentWaypoint(), - speed: this.fms.currentWaypoint.speed - }, - { // Force the initial turn to outbound heading when entering the hold - navmode: WAYPOINT_NAV_MODE.HOLD, - speed: this.fms.currentWaypoint.speed, - altitude: this.fms.altitudeForCurrentWaypoint(), - fix: null, - hold: { - fixName: holdFix, - fixPos: holdFixLocation, - dirTurns: dirTurns, - legLength: legLength, - inboundHdg: inboundHdg, - timer: null - } - } - ] - }); - } - - // TODO: abstract to method `.getInboundCardinalDirection()` - const inboundDir = radio_cardinalDir_names[getCardinalDirection(radians_normalize(inboundHdg + Math.PI)).toLowerCase()]; - - if (holdFix) { - return ['ok', `proceed direct ${holdFix} and hold inbound, ${dirTurns} turns, ${legLength} legs`]; - } - - return ['ok', `hold ${inboundDir} of present position, ${dirTurns} turns, ${legLength} legs`]; - } - - /** - * @for AircraftInstanceModel - * @method runDirect - * @param data - */ - runDirect(data) { - const fixname = data[0].toUpperCase(); - // TODO replace with FixCollection - const fix = window.airportController.airport_get().getFixPosition(fixname); - - if (!fix) { - return ['fail', `unable to find fix called ${fixname}`]; - } - - // remove intermediate fixes - if (this.mode === FLIGHT_MODES.TAKEOFF) { - this.fms.skipToFix(fixname); - } else if (!this.fms.skipToFix(fixname)) { - return ['fail', `${fixname} is not in our flightplan`]; - } - - return ['ok', `proceed direct ${fixname}`]; - } - - runFix(data) { - let last_fix; - let fail; - const fixes = _map(data, (fixname) => { - // TODO: this may beed to be the FixCollection - const fix = window.airportController.airport_get().getFixPosition(fixname); - - if (!fix) { - fail = ['fail', `unable to find fix called ${fixname}`]; - - return; - } - - // to avoid repetition, compare name with the previous fix - if (fixname === last_fix) { - return; - } - - last_fix = fixname; - - return fixname; - }); - - if (fail) { - return fail; - } - - for (let i = 0; i < fixes.length; i++) { - // FIXME: use enumerated constant for type - this.fms.insertLegHere({ type: 'fix', route: fixes[i] }); - } - - if (this.mode !== FLIGHT_MODES.WAITING && - this.mode !== FLIGHT_MODES.TAKEOFF && - this.mode !== FLIGHT_MODES.APRON && - this.mode !== FLIGHT_MODES.TAXI - ) { - this.cancelLanding(); - } - - return ['ok', `proceed direct ${fixes.join(', ')}`]; - } - - /** - * @for AircraftInstanceModel - * @method runFlyPresentHeading - * @param data - */ - runFlyPresentHeading(data) { - this.cancelFix(); - this.runHeading([null, radiansToDegrees(this.heading)]); - - return ['ok', 'fly present heading']; - } - - /** - * @for AircraftInstanceModel - * @method runSayRoute - * @param data - */ - runSayRoute(data) { - return ['ok', { - log: `route: ${this.fms.fp.route.join(' ')}`, - say: 'here\'s our route' - }]; - } - - /** - * @for AircraftInstanceModel - * @method runSID - */ - runSID(data) { - const airport = window.airportController.airport_get(); - const { sidCollection } = airport; - const sidId = data[0]; - const standardRouteModel = sidCollection.findRouteByIcao(sidId); - const exit = airport.getSIDExitPoint(sidId); - // TODO: perhaps this should use the `RouteModel`? - const route = `${airport.icao}.${sidId}.${exit}`; - - if (_isNil(standardRouteModel)) { - return ['fail', 'SID name not understood']; - } - - if (this.category !== FLIGHT_CATEGORY.DEPARTURE) { - return ['fail', 'unable to fly SID, we are an inbound']; - } - - if (!this.rwy_dep) { - this.setDepartureRunway(airportController.airport_get().runway); - } - - if (!standardRouteModel.hasFixName(this.rwy_dep)) { - return ['fail', `unable, the ${standardRouteModel.name} departure not valid from Runway ${this.rwy_dep}`]; - } - - // TODO: this is the wrong place for this `.toUpperCase()` - this.fms.followSID(route.toUpperCase()); - - const readback = { - log: `cleared to destination via the ${sidId} departure, then as filed`, - say: `cleared to destination via the ${standardRouteModel.name} departure, then as filed` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runSTAR - * @param data {array} a string representation of the STAR, ex: `QUINN.BDEGA2.KSFO` - */ - runSTAR(data) { - const routeModel = new RouteModel(data[0]); - const airport = window.airportController.airport_get(); - const { name: starName } = airport.starCollection.findRouteByIcao(routeModel.procedure); - if (this.category !== FLIGHT_CATEGORY.ARRIVAL) { - return ['fail', 'unable to fly STAR, we are a departure!']; - } - - // TODO: the data[0].length check might not be needed. this is covered via the CommandParser when - // this method runs as the result of a command. - if (data[0].length === 0 || !airport.starCollection.hasRoute(routeModel.procedure)) { - return ['fail', 'STAR name not understood']; - } - - this.fms.followSTAR(routeModel.routeCode); - - // TODO: casing may be an issue here. - const readback = { - log: `cleared to the ${airport.name} via the ${routeModel.procedure} arrival`, - say: `cleared to the ${airport.name} via the ${starName} arrival` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runMoveDataBlock - * @param data - */ - runMoveDataBlock(dir) { - // TODO: what do all these numbers mean? - const positions = { 8: 360, 9: 45, 6: 90, 3: 135, 2: 180, 1: 225, 4: 270, 7: 315, 5: 'ctr' }; - - if (!_has(positions, dir[0])) { - return; - } - - this.datablockDir = positions[dir[0]]; - } - - /** - * Adds a new Leg to fms with a user specified route - * Note: See notes on 'runReroute' for how to format input for this command - * - * @for AircraftInstanceModel - * @method runRoute - * @param data - */ - runRoute(data) { - // capitalize everything - data = data[0].toUpperCase(); - let worked = true; - const route = this.fms.formatRoute(data); - - if (worked && route) { - // Add to fms - worked = this.fms.customRoute(route, false); - } - - if (!route || !data || data.indexOf(' ') > -1) { - worked = false; - } - - // Build the response - if (worked) { - const readback = { - log: `rerouting to :${this.fms.fp.route.join(' ')}`, - say: 'rerouting as requested' - }; - - return ['ok', readback]; - } - - const readback = { - log: `your route "${data}" is invalid!`, - say: 'that route is invalid!' - }; - - return ['fail', readback]; - } - - /** - * Removes all legs, and replaces them with the specified route - * Note: Input data needs to be provided with single dots connecting all - * procedurally-linked points (eg KSFO.OFFSH9.SXC or SGD.V87.MOVER), and - * all other points that will be simply a fix direct to another fix need - * to be connected with double-dots (eg HLI..SQS..BERRA..JAN..KJAN) - * - * @for AircraftInstanceModel - * @method runReroute - * @param data - */ - runReroute(data) { - // TODO: capitalize everything? - data = data[0].toUpperCase(); - let worked = true; - const route = this.fms.formatRoute(data); - - if (worked && route) { - // Reset fms - worked = this.fms.customRoute(route, true); - } - - // TODO: what exactly are we checking here? - if (!route || !data || data.indexOf(' ') > -1) { - worked = false; - } - - // Build the response - if (worked) { - const readback = { - log: `rerouting to: ${this.fms.fp.route.join(' ')}`, - say: 'rerouting as requested' - }; - - return ['ok', readback]; - } - - const readback = { - log: `your route "${data}" is invalid!`, - say: 'that route is invalid!' - }; - - return ['fail', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runTaxi - * @param data - */ - runTaxi(data) { - // TODO: all this if logic should be simplified or abstracted - if (this.category !== FLIGHT_CATEGORY.DEPARTURE) { - return ['fail', 'inbound']; - } - - if (this.mode === FLIGHT_MODES.TAXI) { - return ['fail', `already taxiing to ${radio_runway(this.rwy_dep)}`]; - } - - if (this.mode === FLIGHT_MODES.WAITING) { - return ['fail', 'already waiting']; - } - - if (this.mode !== FLIGHT_MODES.APRON) { - return ['fail', 'wrong mode']; - } - - // Set the runway to taxi to - if (data[0]) { - if (window.airportController.airport_get().getRunway(data[0].toUpperCase())) { - this.setDepartureRunway(data[0].toUpperCase()); - } else { - return ['fail', `no runway ${data[0].toUpperCase()}`]; - } - } - - // Start the taxi - this.taxi_start = window.gameController.game_time(); - const runway = window.airportController.airport_get().getRunway(this.rwy_dep); - - runway.addQueue(this); - this.mode = FLIGHT_MODES.TAXI; - - const readback = { - log: `taxi to runway ${runway.name}`, - say: `taxi to runway ${radio_runway(runway.name)}` - }; - - return ['ok', readback]; - } - - /** - * @for AircraftInstanceModel - * @method runTakeoff - * @param data - */ - runTakeoff(data) { - // TODO: all this if logic should be simplified or abstracted - if (this.category !== 'departure') { - return ['fail', 'inbound']; - } - - if (!this.isOnGround()) { - return ['fail', 'already airborne']; - } - if (this.mode === FLIGHT_MODES.APRON) { - return ['fail', 'unable, we\'re still in the parking area']; - } - if (this.mode === FLIGHT_MODES.TAXI) { - return ['fail', `taxi to runway ${radio_runway(this.rwy_dep)} not yet complete`]; - } - if (this.mode === FLIGHT_MODES.TAKEOFF) { - // FIXME: this is showing immediately after a to clearance. - return ['fail', 'already taking off']; - } - - if (this.fms.altitudeForCurrentWaypoint() <= 0) { - return ['fail', 'no altitude assigned']; - } - - const runway = window.airportController.airport_get().getRunway(this.rwy_dep); - - if (runway.removeQueue(this)) { - this.mode = FLIGHT_MODES.TAKEOFF; - this.scoreWind('taking off'); - this.takeoffTime = window.gameController.game_time(); - - if (this.fms.currentWaypoint.speed == null) { - this.fms.setCurrent({ speed: this.model.speed.cruise }); - } - - const wind = window.airportController.airport_get().getWind(); - const wind_dir = round(radiansToDegrees(wind.angle)); - const readback = { - // TODO: the wind_dir calculation should be abstracted - log: `wind ${round(wind_dir / 10) * 10} ${round(wind.speed)}, runway ${this.rwy_dep}, cleared for takeoff`, - say: `wind ${radio_spellOut(round(wind_dir / 10) * 10)} at ${radio_spellOut(round(wind.speed))}, runway ${radio_runway(this.rwy_dep)}, cleared for takeoff` - }; - - return ['ok', readback]; - } - - const waiting = runway.inQueue(this); - - return ['fail', `number ${waiting} behind ${runway.queue[waiting - 1].getRadioCallsign()}`, '']; - } ->>>>>>> 26165f952c23b91392f8de5ad4a86947c5c8180f if (this.model.weightclass === 'U') { heavy = ' super'; From 2c26e0d487049bf712e50ac513e53f5a5c2d9ab7 Mon Sep 17 00:00:00 2001 From: Dabea Date: Wed, 18 Jan 2017 03:27:25 -0600 Subject: [PATCH 25/32] resolves both n8rzz/atc#254 and zlsa/atc#777 by removeing -+.02 variance --- src/assets/scripts/client/aircraft/AircraftInstanceModel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 3d3b8d87..47c2d271 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1346,9 +1346,9 @@ export default class Aircraft { updateAltitudePhysics() { this.trend = 0; - if (this.target.altitude < this.altitude - 0.02) { + if (this.target.altitude < this.altitude) { this.decreaseAircraftAltitude(); - } else if (this.target.altitude > this.altitude + 0.02) { + } else if (this.target.altitude > this.altitude) { this.increaseAircraftAltitude(); } } From d7b348dcbb6ce28bff3af455befc4e7cc4ffe583 Mon Sep 17 00:00:00 2001 From: Dabea Date: Wed, 18 Jan 2017 03:56:50 -0600 Subject: [PATCH 26/32] updated comments to make more sense and added and removed spaces where needed --- .../client/aircraft/AircraftInstanceModel.js | 16 +++++++++++++--- .../AircraftFlightManagementSystem.js | 4 ---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 47c2d271..4c1e0fe4 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -594,6 +594,7 @@ export default class Aircraft { // TODO: this logic could be simplified. do an early return instead of wrapping the entire function in an if. if (this.fms.currentWaypoint.navmode !== WAYPOINT_NAV_MODE.RWY) { this.fms.setCurrent({ runway: null }); + return false; } @@ -677,6 +678,7 @@ export default class Aircraft { /** * Aircraft has "weight-on-wheels" (on the ground) + * * @for AircraftInstanceModel * @method isOnGround */ @@ -1208,7 +1210,8 @@ export default class Aircraft { } /** - * This will display update the fix for the aircraft + * This will display update the FIX for the aircraft and will change the aircrafts heading + * * @for AircraftInstanceModel * @method updateFixTarget */ @@ -1260,7 +1263,6 @@ export default class Aircraft { } this.updateAircraftTurnPhysics(); - this.updateAltitudePhysics(); if (this.isOnGround()) { @@ -1274,6 +1276,7 @@ export default class Aircraft { return; } + //TODO: abstract to AircraftPositionHistory class // Trailling if (this.position_history.length === 0) { this.position_history.push([ @@ -1312,6 +1315,7 @@ export default class Aircraft { /** * This turns the aircraft if it is not on the ground and has not arived at its destenation + * * @for AircraftInstanceModel * @method updateAircraftTurnPhysics */ @@ -1340,6 +1344,7 @@ export default class Aircraft { /** * This updates the Altitude for the instance of the aircraft by checking the difference between current Altitude and requested Altitude + * * @for AircraftInstanceModel * @method updateAltitudePhysics */ @@ -1355,6 +1360,7 @@ export default class Aircraft { /** * Decreases the aircrafts altitude + * * @for AircraftInstanceModel * @method decreaseAircraftAltitude */ @@ -1385,6 +1391,7 @@ export default class Aircraft { /** * Increases the aircrafts altitude + * * @for AircraftInstanceModel * @method increaseAircraftAltitude */ @@ -1416,6 +1423,7 @@ export default class Aircraft { /** * This updates the speed for the instance of the aircraft by checking the difference between current speed and requested speed + * * @for AircraftInstanceModel * @method updateWarning */ @@ -1447,6 +1455,7 @@ export default class Aircraft { /** * This calculates the ground speed + * * @for AircraftInstanceModel * @method updateVectorPhysics * @param scaleSpeed @@ -1490,7 +1499,8 @@ export default class Aircraft { } /** - * This calculates the simplify ground speed + * This uses the current speed infomation to update the ground speed and position + * * @for AircraftInstanceModel * @method updateSimpleGroundSpeedPhysics * @param scaleSpeed diff --git a/src/assets/scripts/client/aircraft/FlightManagementSystem/AircraftFlightManagementSystem.js b/src/assets/scripts/client/aircraft/FlightManagementSystem/AircraftFlightManagementSystem.js index 3d321075..45c403e9 100644 --- a/src/assets/scripts/client/aircraft/FlightManagementSystem/AircraftFlightManagementSystem.js +++ b/src/assets/scripts/client/aircraft/FlightManagementSystem/AircraftFlightManagementSystem.js @@ -1010,8 +1010,4 @@ export default class AircraftFlightManagementSystem { altitudeForCurrentWaypoint() { return this.currentWaypoint.altitude; } - - - - } From cb598fa1491b9067a84f4651b03e9571aec10c24 Mon Sep 17 00:00:00 2001 From: Dabea Date: Wed, 18 Jan 2017 04:15:02 -0600 Subject: [PATCH 27/32] fixed spelling issue on comment --- src/assets/scripts/client/aircraft/AircraftInstanceModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 4c1e0fe4..6fe5b2dd 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1499,7 +1499,7 @@ export default class Aircraft { } /** - * This uses the current speed infomation to update the ground speed and position + * This uses the current speed information to update the ground speed and position * * @for AircraftInstanceModel * @method updateSimpleGroundSpeedPhysics From 0d58fcd97a5de349391a61f47a9914f32b8d2bc3 Mon Sep 17 00:00:00 2001 From: Dabea Date: Wed, 18 Jan 2017 04:19:24 -0600 Subject: [PATCH 28/32] improved comment --- src/assets/scripts/client/aircraft/AircraftInstanceModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 6fe5b2dd..5f738a70 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1210,7 +1210,7 @@ export default class Aircraft { } /** - * This will display update the FIX for the aircraft and will change the aircrafts heading + * This will update the FIX for the aircraft and will change the aircraft's heading * * @for AircraftInstanceModel * @method updateFixTarget From 95c9d3d293f80abd106af9d79df286235248b24b Mon Sep 17 00:00:00 2001 From: Dabea Date: Wed, 18 Jan 2017 04:54:18 -0600 Subject: [PATCH 29/32] removed console.log messages --- src/assets/scripts/client/aircraft/AircraftInstanceModel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js index 5f738a70..6df5076f 100644 --- a/src/assets/scripts/client/aircraft/AircraftInstanceModel.js +++ b/src/assets/scripts/client/aircraft/AircraftInstanceModel.js @@ -1217,10 +1217,9 @@ export default class Aircraft { */ updateFixTarget() { const fix = this.fms.currentWaypoint.location; + if (!fix) { console.error(`${this.getCallsign()} using "fix" navmode, but no fix location!`); - console.log(this.fms); - console.log(this.fms.currentWaypoint); } const vector_to_fix = vsub(this.position, fix); From 2a2355c7525161ddc5869c0adfbba220cb5a6be7 Mon Sep 17 00:00:00 2001 From: Dabea Date: Sun, 22 Jan 2017 01:07:33 -0600 Subject: [PATCH 30/32] fixed bug where aircraft info box dose not apear when given taxi command --- src/assets/scripts/client/aircraft/AircraftCommander.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftCommander.js b/src/assets/scripts/client/aircraft/AircraftCommander.js index 36fcd1a0..6a038855 100644 --- a/src/assets/scripts/client/aircraft/AircraftCommander.js +++ b/src/assets/scripts/client/aircraft/AircraftCommander.js @@ -934,7 +934,7 @@ export default class AircraftCommander { aircraft.taxi_start = this._gameController.game_time(); const runway = this._airportController.airport_get().getRunway(aircraft.rwy_dep); - runway.addQueue(this); + runway.addQueue(aircraft); aircraft.mode = FLIGHT_MODES.TAXI; const readback = { @@ -976,7 +976,7 @@ export default class AircraftCommander { const runway = this._airportController.airport_get().getRunway(aircraft.rwy_dep); - if (runway.removeQueue(this)) { + if (runway.removeQueue(aircraft)) { aircraft.mode = FLIGHT_MODES.TAKEOFF; aircraft.scoreWind('taking off'); aircraft.takeoffTime = this._gameController.game_time(); @@ -996,7 +996,7 @@ export default class AircraftCommander { return ['ok', readback]; } - const waiting = runway.inQueue(this); + const waiting = runway.inQueue(aircraft); return ['fail', `number ${waiting} behind ${runway.queue[waiting - 1].getRadioCallsign()}`, '']; } From f0d7f7870de1d8c5126ab362a82a1bc00b3f2417 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sun, 22 Jan 2017 22:50:35 -0600 Subject: [PATCH 31/32] feature/ATC-181 - adds changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fca9f47a..58190c82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Major - Restructures `src` files into `client` and `server` folders. [#220](https://github.com/n8rzz/atc/issues/220) - Updates Node to version 7.0.0 [#184](https://github.com/n8rzz/atc/issues/184) +- Moves aircraft command logic from `AircraftInstanceModel` to new `AircraftCommander` class [#181](https://github.com/n8rzz/atc/issues/181) ### Minor - Changes `AircraftStripView` text outputs to be all uppercase [#193](https://github.com/n8rzz/atc/issues/193) From 43b2269863315536b17f9abca24b400c05d8da33 Mon Sep 17 00:00:00 2001 From: Nate Geslin Date: Sun, 22 Jan 2017 23:20:13 -0600 Subject: [PATCH 32/32] feature/ATC-181 - updates runwayModel method name that was changed --- src/assets/scripts/client/aircraft/AircraftCommander.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/assets/scripts/client/aircraft/AircraftCommander.js b/src/assets/scripts/client/aircraft/AircraftCommander.js index 6a038855..40cabe0e 100644 --- a/src/assets/scripts/client/aircraft/AircraftCommander.js +++ b/src/assets/scripts/client/aircraft/AircraftCommander.js @@ -934,7 +934,7 @@ export default class AircraftCommander { aircraft.taxi_start = this._gameController.game_time(); const runway = this._airportController.airport_get().getRunway(aircraft.rwy_dep); - runway.addQueue(aircraft); + runway.addAircraftToQueue(aircraft); aircraft.mode = FLIGHT_MODES.TAXI; const readback = { @@ -1024,6 +1024,7 @@ export default class AircraftCommander { /** * @for AircraftCommander * @method runAbort + * @param aircraft {AircraftInstanceModel} * @param data */ runAbort(aircraft, data) { @@ -1076,6 +1077,7 @@ export default class AircraftCommander { /** * @for AircraftCommander * @method runDebug + * * @param aircraft {AircraftInstanceModel} */ runDebug(aircraft) { window.aircraft = aircraft; @@ -1086,8 +1088,9 @@ export default class AircraftCommander { /** * @for AircraftCommander * @method runDelete + * @param aircraft {AircraftInstanceModel} */ - runDelete() { - window.aircraftController.aircraft_remove(this); + runDelete(aircraft) { + window.aircraftController.aircraft_remove(aircraft); } }