@fboes/aerofly-custom-missions 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.eslintrc.json +24 -0
  2. package/CHANGELOG.md +9 -0
  3. package/dist/dto/AeroflyConfigFileSet.js +43 -0
  4. package/dist/dto/AeroflyLocalizedText.js +40 -0
  5. package/dist/dto/AeroflyMission.js +171 -0
  6. package/dist/dto/AeroflyMissionCheckpoint.js +121 -0
  7. package/dist/dto/AeroflyMissionConditions.js +122 -0
  8. package/dist/dto/AeroflyMissionConditionsCloud.js +80 -0
  9. package/dist/dto/AeroflyMissionTargetPlane.js +30 -0
  10. package/dist/dto/AeroflyMissionsList.js +28 -0
  11. package/dist/index.js +7 -628
  12. package/dist/index.test.js +34 -5
  13. package/package.json +9 -10
  14. package/src/dto/AeroflyConfigFileSet.ts +35 -0
  15. package/src/dto/AeroflyLocalizedText.ts +69 -0
  16. package/src/dto/AeroflyMission.ts +377 -0
  17. package/src/dto/AeroflyMissionCheckpoint.ts +235 -0
  18. package/src/dto/AeroflyMissionConditions.ts +196 -0
  19. package/src/dto/AeroflyMissionConditionsCloud.ts +100 -0
  20. package/src/dto/AeroflyMissionTargetPlane.ts +56 -0
  21. package/src/dto/AeroflyMissionsList.ts +36 -0
  22. package/src/index.test.ts +36 -10
  23. package/src/index.ts +7 -1084
  24. package/types/AeroflyMissionCheckpoint.d.ts +140 -0
  25. package/types/AeroflyMissionTargetPlane.d.ts +40 -0
  26. package/types/dto/AeroflyConfigFileSet.d.ts +10 -0
  27. package/types/dto/AeroflyConfigFileSet.d.ts.map +1 -0
  28. package/types/dto/AeroflyLocalizedText.d.ts +54 -0
  29. package/types/dto/AeroflyLocalizedText.d.ts.map +1 -0
  30. package/types/dto/AeroflyMission.d.ts +208 -0
  31. package/types/dto/AeroflyMission.d.ts.map +1 -0
  32. package/types/dto/AeroflyMissionCheckpoint.d.ts +152 -0
  33. package/types/dto/AeroflyMissionCheckpoint.d.ts.map +1 -0
  34. package/types/dto/AeroflyMissionConditions.d.ts +116 -0
  35. package/types/dto/AeroflyMissionConditions.d.ts.map +1 -0
  36. package/types/dto/AeroflyMissionConditionsCloud.d.ts +51 -0
  37. package/types/dto/AeroflyMissionConditionsCloud.d.ts.map +1 -0
  38. package/types/dto/AeroflyMissionTargetPlane.d.ts +40 -0
  39. package/types/dto/AeroflyMissionTargetPlane.d.ts.map +1 -0
  40. package/types/dto/AeroflyMissionsList.d.ts +24 -0
  41. package/types/dto/AeroflyMissionsList.d.ts.map +1 -0
  42. package/types/dto/basicTypes.d.ts +1 -0
  43. package/types/index.d.ts +7 -638
  44. package/types/index.d.ts.map +1 -1
  45. package/eslint.config.js +0 -29
package/.eslintrc.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "env": {
3
+ "browser": true,
4
+ "es2021": true,
5
+ "node": true
6
+ },
7
+ "extends": [
8
+ "eslint:recommended",
9
+ "plugin:@typescript-eslint/recommended",
10
+ "eslint-config-prettier",
11
+ "plugin:jsdoc/recommended-error"
12
+ ],
13
+ "parser": "@typescript-eslint/parser",
14
+ "parserOptions": {
15
+ "ecmaVersion": "latest",
16
+ "sourceType": "module"
17
+ },
18
+ "plugins": [
19
+ "@typescript-eslint",
20
+ "jsdoc"
21
+ ],
22
+ "rules": {
23
+ }
24
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.3
4
+
5
+ - Internal restructuring
6
+
7
+ ## 1.2.2
8
+
9
+ - Added altitude constraint property
10
+ - Improved handling of cloud layers
11
+
3
12
  ## 1.2.1
4
13
 
5
14
  - Changed handling of checkpoints, as it is obviously possible to have missions without checkpoints
@@ -0,0 +1,43 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _AeroflyConfigFileSet_indent;
13
+ export class AeroflyConfigFileSet {
14
+ constructor(indent, type, name, value = "") {
15
+ _AeroflyConfigFileSet_indent.set(this, void 0);
16
+ __classPrivateFieldSet(this, _AeroflyConfigFileSet_indent, indent, "f");
17
+ this.elements = [`${this.indent}<[${type}][${name}][${value}]`];
18
+ }
19
+ get indent() {
20
+ return " ".repeat(__classPrivateFieldGet(this, _AeroflyConfigFileSet_indent, "f"));
21
+ }
22
+ push(type, name, value, comment = "") {
23
+ if (value instanceof Array) {
24
+ value = value.join(" ");
25
+ }
26
+ else if (typeof value === "boolean") {
27
+ value = value ? "true" : "false";
28
+ }
29
+ let tag = `${this.indent} <[${type}][${name}][${value}]>`;
30
+ if (comment) {
31
+ tag += ` // ${comment}`;
32
+ }
33
+ return this.pushRaw(tag);
34
+ }
35
+ pushRaw(s) {
36
+ this.elements.push(s);
37
+ return this;
38
+ }
39
+ toString() {
40
+ return this.elements.join("\n") + "\n" + `${this.indent}>`;
41
+ }
42
+ }
43
+ _AeroflyConfigFileSet_indent = new WeakMap();
@@ -0,0 +1,40 @@
1
+ import { AeroflyConfigFileSet } from "./AeroflyConfigFileSet.js";
2
+ /**
3
+ * @class
4
+ * A translation for the mission title and description.
5
+ */
6
+ export class AeroflyLocalizedText {
7
+ /**
8
+ * @param {string} language ISO 639-1 like
9
+ * - br
10
+ * - cn
11
+ * - de
12
+ * - es
13
+ * - fr
14
+ * - id
15
+ * - it
16
+ * - jp
17
+ * - kr
18
+ * - pl
19
+ * - sv
20
+ * - tr
21
+ * @param {string} title of this flight plan
22
+ * @param {string} description text, mission briefing, etc
23
+ */
24
+ constructor(language, title, description) {
25
+ this.language = language;
26
+ this.title = title;
27
+ this.description = description;
28
+ }
29
+ /**
30
+ * @param {number} index if used in an array will se the array index
31
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
32
+ */
33
+ toString(index = 0) {
34
+ return new AeroflyConfigFileSet(4, "tmmission_definition_localized", "element", String(index))
35
+ .push("string8u", "language", this.language)
36
+ .push("string8", "title", this.title)
37
+ .push("string8", "description", this.description)
38
+ .toString();
39
+ }
40
+ }
@@ -0,0 +1,171 @@
1
+ import { AeroflyConfigFileSet } from "./AeroflyConfigFileSet.js";
2
+ import { AeroflyMissionConditions } from "./AeroflyMissionConditions.js";
3
+ export const feetPerMeter = 3.28084;
4
+ export const meterPerStatuteMile = 1609.344;
5
+ /**
6
+ * @class
7
+ * A single flighplan, containing aircraft and weather data as well.
8
+ *
9
+ * The purpose of this class is to collect data needed for Aerofly FS4's
10
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
11
+ * for this file via the `toString()` method.
12
+ */
13
+ export class AeroflyMission {
14
+ /**
15
+ * @param {string} title of this flight plan
16
+ * @param {object} [additionalAttributes] allows to set additional attributes on creation
17
+ * @param {string} [additionalAttributes.description] text, mission briefing, etc
18
+ * @param {AeroflyLocalizedText[]} [additionalAttributes.localizedTexts] translations for title and description
19
+ * @param {?string} [additionalAttributes.tutorialName] will create a link to a tutorial page at https://www.aerofly.com/aircraft-tutorials/
20
+ * @param {string[]} [additionalAttributes.tags]
21
+ * @param {?boolean} [additionalAttributes.isFeatured] makes this mission pop up in "Challenges"
22
+ * @param {?number} [additionalAttributes.difficulty] values between 0.00 and 2.00 have been encountered, but they seem to be without limit
23
+ * @param {"cold_and_dark"|"before_start"|"taxi"|"takeoff"|"cruise"|"approach"|"landing"|"winch_launch"|"aerotow"|"pushback"} [additionalAttributes.flightSetting] of aircraft, like "taxi", "cruise"
24
+ * @param {{name:string,livery:string,icao:string}} [additionalAttributes.aircraft] for this mission
25
+ * @param {string} [additionalAttributes.callsign] of aircraft, uppercased
26
+ * @param {object} [additionalAttributes.origin] position of aircraft, as well as name of starting airport. Position does not have match airport.
27
+ * @param {object} [additionalAttributes.destination] position of aircraft, as well as name of destination airport. Position does not have match airport.
28
+ * @param {?number} [additionalAttributes.distance] in meters
29
+ * @param {?number} [additionalAttributes.duration] in seconds
30
+ * @param {?boolean} [additionalAttributes.isScheduled] marks this flight as "Scheduled flight".
31
+ * @param {?AeroflyMissionTargetPlane} [additionalAttributes.finish] as finish condition
32
+ * @param {AeroflyMissionConditions} [additionalAttributes.conditions] like time and weather for mission
33
+ * @param {AeroflyMissionCheckpoint[]} [additionalAttributes.checkpoints] form the actual flight plan
34
+ */
35
+ constructor(title, { tutorialName = null, description = "", localizedTexts = [], tags = [], isFeatured = null, difficulty = null, flightSetting = "taxi", aircraft = {
36
+ name: "c172",
37
+ icao: "",
38
+ livery: "",
39
+ }, callsign = "", origin = {
40
+ icao: "",
41
+ longitude: 0,
42
+ latitude: 0,
43
+ dir: 0,
44
+ alt: 0,
45
+ }, destination = {
46
+ icao: "",
47
+ longitude: 0,
48
+ latitude: 0,
49
+ dir: 0,
50
+ alt: 0,
51
+ }, distance = null, duration = null, isScheduled = null, finish = null, conditions = new AeroflyMissionConditions(), checkpoints = [], } = {}) {
52
+ this.tutorialName = tutorialName;
53
+ this.title = title;
54
+ this.checkpoints = checkpoints;
55
+ this.description = description;
56
+ this.localizedTexts = localizedTexts;
57
+ this.tags = tags;
58
+ this.isFeatured = isFeatured;
59
+ this.difficulty = difficulty;
60
+ this.flightSetting = flightSetting;
61
+ this.aircraft = aircraft;
62
+ this.callsign = callsign;
63
+ this.origin = origin;
64
+ this.destination = destination;
65
+ this.distance = distance;
66
+ this.duration = duration;
67
+ this.isScheduled = isScheduled;
68
+ this.finish = finish;
69
+ this.conditions = conditions;
70
+ }
71
+ /**
72
+ * @returns {string} indexed checkpoints
73
+ */
74
+ getCheckpointsString() {
75
+ return this.checkpoints
76
+ .map((c, index) => {
77
+ return c.toString(index);
78
+ })
79
+ .join("\n");
80
+ }
81
+ /**
82
+ * @returns {string} indexed checkpoints
83
+ */
84
+ getLocalizedTextsString() {
85
+ return this.localizedTexts
86
+ .map((c, index) => {
87
+ return c.toString(index);
88
+ })
89
+ .join("\n");
90
+ }
91
+ /**
92
+ * @throws {Error} on missing waypoints
93
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
94
+ */
95
+ toString() {
96
+ if (!this.origin.icao) {
97
+ const firstCheckpoint = this.checkpoints[0];
98
+ this.origin = {
99
+ icao: firstCheckpoint.name,
100
+ longitude: firstCheckpoint.longitude,
101
+ latitude: firstCheckpoint.latitude,
102
+ dir: this.origin.dir,
103
+ alt: firstCheckpoint.altitude,
104
+ };
105
+ }
106
+ if (!this.destination.icao) {
107
+ const lastCheckpoint = this.checkpoints[this.checkpoints.length - 1];
108
+ this.destination = {
109
+ icao: lastCheckpoint.name,
110
+ longitude: lastCheckpoint.longitude,
111
+ latitude: lastCheckpoint.latitude,
112
+ dir: lastCheckpoint.direction ?? 0,
113
+ alt: lastCheckpoint.altitude,
114
+ };
115
+ }
116
+ const fileSet = new AeroflyConfigFileSet(3, "tmmission_definition", "mission");
117
+ fileSet.push("string8", "title", this.title);
118
+ fileSet.push("string8", "description", this.description);
119
+ if (this.tutorialName !== null) {
120
+ fileSet.push("string8", "tutorial_name", this.tutorialName, `Opens https://www.aerofly.com/aircraft-tutorials/${this.tutorialName}`);
121
+ }
122
+ if (this.localizedTexts.length > 0) {
123
+ fileSet.pushRaw(new AeroflyConfigFileSet(4, "list_tmmission_definition_localized", "localized_text")
124
+ .pushRaw(this.getLocalizedTextsString())
125
+ .toString());
126
+ }
127
+ if (this.tags.length > 0) {
128
+ fileSet.push("string8u", "tags", this.tags.join(" "));
129
+ }
130
+ if (this.difficulty !== null) {
131
+ fileSet.push("float64", "difficulty", this.difficulty);
132
+ }
133
+ if (this.isFeatured !== null) {
134
+ fileSet.push("bool", "is_featured", this.isFeatured);
135
+ }
136
+ fileSet.push("string8", "flight_setting", this.flightSetting);
137
+ fileSet.push("string8u", "aircraft_name", this.aircraft.name);
138
+ /*if (this.aircraft.livery) {
139
+ fileSet.push("string8", "aircraft_livery", this.aircraft.livery);
140
+ }*/
141
+ fileSet.push("stringt8c", "aircraft_icao", this.aircraft.icao);
142
+ fileSet.push("stringt8c", "callsign", this.callsign);
143
+ fileSet.push("stringt8c", "origin_icao", this.origin.icao);
144
+ fileSet.push("tmvector2d", "origin_lon_lat", [this.origin.longitude, this.origin.latitude]);
145
+ fileSet.push("float64", "origin_alt", this.origin.alt, `${Math.ceil(this.origin.alt * feetPerMeter)} ft MSL`);
146
+ fileSet.push("float64", "origin_dir", this.origin.dir);
147
+ fileSet.push("stringt8c", "destination_icao", this.destination.icao);
148
+ fileSet.push("tmvector2d", "destination_lon_lat", [this.destination.longitude, this.destination.latitude]);
149
+ fileSet.push("float64", "destination_alt", this.destination.alt, `${Math.ceil(this.destination.alt * feetPerMeter)} ft MSL`);
150
+ fileSet.push("float64", "destination_dir", this.destination.dir);
151
+ if (this.distance !== null) {
152
+ fileSet.push("float64", "distance", this.distance, `${Math.round(this.distance / 1000)} km`);
153
+ }
154
+ if (this.duration !== null) {
155
+ fileSet.push("float64", "duration", this.duration, `${Math.round(this.duration / 60)} min`);
156
+ }
157
+ if (this.isScheduled !== null) {
158
+ fileSet.push("bool", "is_scheduled", this.isScheduled ? "true" : "false");
159
+ }
160
+ if (this.finish !== null) {
161
+ fileSet.pushRaw(this.finish.toString());
162
+ }
163
+ fileSet.pushRaw(this.conditions.toString());
164
+ if (this.checkpoints.length > 0) {
165
+ fileSet.pushRaw(new AeroflyConfigFileSet(4, "list_tmmission_checkpoint", "checkpoints")
166
+ .pushRaw(this.getCheckpointsString())
167
+ .toString());
168
+ }
169
+ return fileSet.toString();
170
+ }
171
+ }
@@ -0,0 +1,121 @@
1
+ import { AeroflyConfigFileSet } from "./AeroflyConfigFileSet.js";
2
+ import { feetPerMeter } from "./AeroflyMission.js";
3
+ /**
4
+ * @class
5
+ * A single way point for the given flight plan
6
+ *
7
+ * The purpose of this class is to collect data needed for Aerofly FS4's
8
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
9
+ * for this file via the `toString()` method.
10
+ */
11
+ export class AeroflyMissionCheckpoint {
12
+ /**
13
+ * @param {string} name ICAO code for airport, runway designator, navaid
14
+ * designator, fix name, or custom name
15
+ * @param {"origin"|"departure_runway"|"departure"|"waypoint"|"arrival"|"approach"|"destination_runway"|"destination"} type Type of checkpoint, like "departure_runway"
16
+ * @param {number} longitude easting, using the World Geodetic
17
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
18
+ * of decimal degrees; -180..180
19
+ * @param {number} latitude northing, using the World Geodetic
20
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
21
+ * of decimal degrees; -90..90
22
+ * @param {object} additionalAttributes allows to set additional attributes on creation
23
+ * @param {number} [additionalAttributes.altitude] The height in meters above or below the WGS
24
+ * 84 reference ellipsoid
25
+ * @param {?number} [additionalAttributes.altitude_feet] The height in feet above or below the WGS
26
+ * 84 reference ellipsoid. Will overwrite altitude
27
+ * @param {number} [additionalAttributes.altitudeConstraint] The altitude given in `altitude`
28
+ * will be interpreted as mandatory flight plan altitude instead of
29
+ * suggestion.
30
+ * @param {boolean} [additionalAttributes.direction] of runway, in degree
31
+ * @param {?number} [additionalAttributes.slope] of runway
32
+ * @param {?number} [additionalAttributes.length] of runway, in meters
33
+ * @param {?number} [additionalAttributes.length_feet] of runway, in feet. Will overwrite length
34
+ * @param {?number} [additionalAttributes.frequency] of runways or navigational aids, in Hz; multiply by 1000 for kHz, 1_000_000 for MHz
35
+ * @param {?boolean} [additionalAttributes.flyOver] if waypoint is meant to be flown over
36
+ */
37
+ constructor(name, type, longitude, latitude, { altitude = 0, altitude_feet = null, altitudeConstraint = null, direction = null, slope = null, length = null, length_feet = null, frequency = null, flyOver = null, } = {}) {
38
+ this.type = type;
39
+ this.name = name;
40
+ this.longitude = longitude;
41
+ this.latitude = latitude;
42
+ this.altitude = altitude;
43
+ this.altitudeConstraint = altitudeConstraint;
44
+ this.direction = direction;
45
+ this.slope = slope;
46
+ this.length = length;
47
+ this.frequency = frequency;
48
+ this.flyOver = flyOver;
49
+ if (altitude_feet) {
50
+ this.altitude_feet = altitude_feet;
51
+ }
52
+ if (length_feet) {
53
+ this.length_feet = length_feet;
54
+ }
55
+ }
56
+ /**
57
+ * @param {number} altitude_feet
58
+ */
59
+ set altitude_feet(altitude_feet) {
60
+ this.altitude = altitude_feet / feetPerMeter;
61
+ }
62
+ /**
63
+ * @returns {number} altitude_feet
64
+ */
65
+ get altitude_feet() {
66
+ return this.altitude * feetPerMeter;
67
+ }
68
+ /**
69
+ * @param {number} length_feet
70
+ */
71
+ set length_feet(length_feet) {
72
+ this.length = length_feet / feetPerMeter;
73
+ }
74
+ /**
75
+ * @returns {number} length_feet
76
+ */
77
+ get length_feet() {
78
+ return (this.length ?? 0) * feetPerMeter;
79
+ }
80
+ /**
81
+ * @returns {string}
82
+ */
83
+ get frequency_string() {
84
+ if (!this.frequency) {
85
+ return "None";
86
+ }
87
+ if (this.frequency > 1000000) {
88
+ return String(this.frequency / 1000000) + " MHz";
89
+ }
90
+ if (this.frequency > 1000) {
91
+ return String(this.frequency / 1000) + " kHz";
92
+ }
93
+ return String(this.frequency) + " Hz";
94
+ }
95
+ /**
96
+ * @param {number} index if used in an array will se the array index
97
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
98
+ */
99
+ toString(index = 0) {
100
+ const fileSet = new AeroflyConfigFileSet(5, "tmmission_checkpoint", "element", String(index))
101
+ .push("string8u", "type", this.type)
102
+ .push("string8u", "name", this.name)
103
+ .push("vector2_float64", "lon_lat", [this.longitude, this.latitude])
104
+ .push("float64", "altitude", this.altitude, `${Math.ceil(this.altitude_feet)} ft`)
105
+ .push("float64", "direction", this.direction ?? (index === 0 ? -1 : 0))
106
+ .push("float64", "slope", this.slope ?? 0);
107
+ if (this.altitudeConstraint !== null) {
108
+ fileSet.push("bool", "alt_cst", this.altitudeConstraint);
109
+ }
110
+ if (this.length) {
111
+ fileSet.push("float64", "length", this.length ?? 0, `${Math.floor(this.length_feet)} ft`);
112
+ }
113
+ if (this.frequency) {
114
+ fileSet.push("float64", "frequency", this.frequency ?? 0, `${this.frequency_string}`);
115
+ }
116
+ if (this.flyOver !== null) {
117
+ fileSet.push("bool", "fly_over", this.flyOver);
118
+ }
119
+ return fileSet.toString();
120
+ }
121
+ }
@@ -0,0 +1,122 @@
1
+ import { AeroflyConfigFileSet } from "./AeroflyConfigFileSet.js";
2
+ import { meterPerStatuteMile } from "./AeroflyMission.js";
3
+ import { AeroflyMissionConditionsCloud } from "./AeroflyMissionConditionsCloud.js";
4
+ /**
5
+ * @class
6
+ * Time and weather data for the given flight plan
7
+ *
8
+ * The purpose of this class is to collect data needed for Aerofly FS4's
9
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
10
+ * for this file via the `toString()` method.
11
+ */
12
+ export class AeroflyMissionConditions {
13
+ /**
14
+ * @param {object} additionalAttributes allows to set additional attributes on creation
15
+ * @param {Date} [additionalAttributes.time] of flight plan. Relevant is the UTC part, so
16
+ * consider setting this date in UTC.
17
+ * @param {{direction: number, speed: number, gusts: number}} [additionalAttributes.wind] state
18
+ * @param {number} [additionalAttributes.turbulenceStrength] 0..1, percentage
19
+ * @param {number} [additionalAttributes.thermalStrength] 0..1, percentage
20
+ * @param {number} [additionalAttributes.visibility] in meters
21
+ * @param {?number} [additionalAttributes.visibility_sm] in statute miles, will overwrite visibility
22
+ * @param {?number} [additionalAttributes.temperature] in °C, will overwrite thermalStrength
23
+ * @param {AeroflyMissionConditionsCloud[]} [additionalAttributes.clouds] for the whole flight
24
+ */
25
+ constructor({ time = new Date(), wind = {
26
+ direction: 0,
27
+ speed: 0,
28
+ gusts: 0,
29
+ }, turbulenceStrength = 0, thermalStrength = 0, visibility = 25_000, visibility_sm = null, temperature = null, clouds = [], } = {}) {
30
+ /**
31
+ * @property {AeroflyMissionConditionsCloud[]} clouds for the whole flight
32
+ */
33
+ this.clouds = [];
34
+ this.time = time;
35
+ this.wind = wind;
36
+ this.turbulenceStrength = turbulenceStrength;
37
+ this.thermalStrength = thermalStrength;
38
+ this.visibility = visibility;
39
+ this.clouds = clouds;
40
+ if (visibility_sm) {
41
+ this.visibility_sm = visibility_sm;
42
+ }
43
+ if (temperature) {
44
+ this.temperature = temperature;
45
+ }
46
+ }
47
+ /**
48
+ * @returns {number} the Aerofly value for UTC hours + minutes/60 + seconds/3600. Ignores milliseconds ;)
49
+ */
50
+ get time_hours() {
51
+ return this.time.getUTCHours() + this.time.getUTCMinutes() / 60 + this.time.getUTCSeconds() / 3600;
52
+ }
53
+ /**
54
+ * @returns {string} Time representation like "20:15:00"
55
+ */
56
+ get time_presentational() {
57
+ return [this.time.getUTCHours(), this.time.getUTCMinutes(), this.time.getUTCSeconds()]
58
+ .map((t) => {
59
+ return String(t).padStart(2, "0");
60
+ })
61
+ .join(":");
62
+ }
63
+ /**
64
+ * @param {number} visibility_sm `this.visibility` in statute miles instead of meters
65
+ */
66
+ set visibility_sm(visibility_sm) {
67
+ this.visibility = visibility_sm * meterPerStatuteMile;
68
+ }
69
+ /**
70
+ * @returns {number} `this.visibility` in statute miles instead of meters
71
+ */
72
+ get visibility_sm() {
73
+ return this.visibility / meterPerStatuteMile;
74
+ }
75
+ /**
76
+ * Will set `this.thermalStrength`
77
+ * @param {number} temperature in °C
78
+ */
79
+ set temperature(temperature) {
80
+ // Range from -15°C to 35°C
81
+ this.thermalStrength = Math.max(0, (temperature + 15) / 50) ** 2;
82
+ }
83
+ /**
84
+ * @returns {number} in °C
85
+ */
86
+ get temperature() {
87
+ return Math.sqrt(this.thermalStrength) * 50 - 15;
88
+ }
89
+ /**
90
+ * @returns {string}
91
+ */
92
+ getCloudsString() {
93
+ return this.clouds
94
+ .map((c, index) => {
95
+ return c.toString(index);
96
+ })
97
+ .join("\n");
98
+ }
99
+ /**
100
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
101
+ */
102
+ toString() {
103
+ if (this.clouds.length < 1) {
104
+ this.clouds = [new AeroflyMissionConditionsCloud(0, 0)];
105
+ }
106
+ return new AeroflyConfigFileSet(4, "tmmission_conditions", "conditions")
107
+ .pushRaw(new AeroflyConfigFileSet(5, "tm_time_utc", "time")
108
+ .push("int32", "time_year", this.time.getUTCFullYear())
109
+ .push("int32", "time_month", this.time.getUTCMonth() + 1)
110
+ .push("int32", "time_day", this.time.getUTCDate())
111
+ .push("float64", "time_hours", this.time_hours, `${this.time_presentational} UTC`)
112
+ .toString())
113
+ .push("float64", "wind_direction", this.wind.direction)
114
+ .push("float64", "wind_speed", this.wind.speed, "kts")
115
+ .push("float64", "wind_gusts", this.wind.gusts, "kts")
116
+ .push("float64", "turbulence_strength", this.turbulenceStrength)
117
+ .push("float64", "thermal_strength", this.thermalStrength, `${this.temperature} °C`)
118
+ .push("float64", "visibility", this.visibility, `${this.visibility_sm} SM`)
119
+ .pushRaw(this.getCloudsString())
120
+ .toString();
121
+ }
122
+ }
@@ -0,0 +1,80 @@
1
+ import { feetPerMeter } from "./AeroflyMission.js";
2
+ /**
3
+ * @class
4
+ * A cloud layer for the current flight plan's weather data
5
+ *
6
+ * The purpose of this class is to collect data needed for Aerofly FS4's
7
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
8
+ * for this file via the `toString()` method.
9
+ */
10
+ export class AeroflyMissionConditionsCloud {
11
+ /**
12
+ * @param {number} cover 0..1, percentage
13
+ * @param {number} base altitude in meters AGL
14
+ */
15
+ constructor(cover, base) {
16
+ this.cover = cover;
17
+ this.base = base;
18
+ }
19
+ /**
20
+ * @param {number} cover 0..1, percentage
21
+ * @param {number} base_feet altitude, but in feet AGL instead of meters AGL
22
+ * @returns {AeroflyMissionConditionsCloud}
23
+ */
24
+ static createInFeet(cover, base_feet) {
25
+ return new AeroflyMissionConditionsCloud(cover, base_feet / feetPerMeter);
26
+ }
27
+ /**
28
+ * @param {number} base_feet `this.base` in feet instead of meters
29
+ */
30
+ set base_feet(base_feet) {
31
+ this.base = base_feet / feetPerMeter;
32
+ }
33
+ /**
34
+ * @returns {number} `this.base` in feet instead of meters
35
+ */
36
+ get base_feet() {
37
+ return this.base * feetPerMeter;
38
+ }
39
+ /**
40
+ * @returns {string} Cloud coverage as text representation like "OVC" for `this.cover`
41
+ */
42
+ get cover_code() {
43
+ if (this.cover < 1 / 8) {
44
+ return "CLR";
45
+ }
46
+ else if (this.cover <= 2 / 8) {
47
+ return "FEW";
48
+ }
49
+ else if (this.cover <= 4 / 8) {
50
+ return "SCT";
51
+ }
52
+ else if (this.cover <= 7 / 8) {
53
+ return "BKN";
54
+ }
55
+ return "OVC";
56
+ }
57
+ /**
58
+ * @param {number} index if used in an array will se the array index
59
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
60
+ */
61
+ toString(index = 0) {
62
+ const getIndexString = (index) => {
63
+ switch (index) {
64
+ case 0:
65
+ return "cloud";
66
+ case 1:
67
+ return "cirrus";
68
+ case 2:
69
+ return "cumulus_mediocris";
70
+ default:
71
+ return "more_clouds";
72
+ }
73
+ };
74
+ const indexString = getIndexString(index);
75
+ const comment = index > 1 ? "//" : "";
76
+ return `\
77
+ ${comment}<[float64][${indexString}_cover][${this.cover ?? 0}]> // ${this.cover_code}
78
+ ${comment}<[float64][${indexString}_base][${this.base}]> // ${this.base_feet} ft AGL`;
79
+ }
80
+ }
@@ -0,0 +1,30 @@
1
+ import { AeroflyConfigFileSet } from "./AeroflyConfigFileSet.js";
2
+ /**
3
+ * @class
4
+ * A target plane which the aircraft needs to cross.
5
+ */
6
+ export class AeroflyMissionTargetPlane {
7
+ /**
8
+ *
9
+ * @param {number} longitude easting, using the World Geodetic
10
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
11
+ * of decimal degrees; -180..180
12
+ * @param {number}latitude northing, using the World Geodetic
13
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
14
+ * of decimal degrees; -90..90
15
+ * @param {number} dir in degree
16
+ * @param {string} name of property
17
+ */
18
+ constructor(longitude, latitude, dir, name = "finish") {
19
+ this.longitude = longitude;
20
+ this.latitude = latitude;
21
+ this.dir = dir;
22
+ this.name = name;
23
+ }
24
+ toString() {
25
+ return new AeroflyConfigFileSet(4, "tmmission_target_plane", this.name)
26
+ .push("vector2_float64", "lon_lat", [this.longitude, this.latitude])
27
+ .push("float64", "direction", this.dir)
28
+ .toString();
29
+ }
30
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @class
3
+ * A list of flight plans.
4
+ *
5
+ * The purpose of this class is to collect data needed for Aerofly FS4's
6
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
7
+ * for this file via the `toString()` method.
8
+ */
9
+ export class AeroflyMissionsList {
10
+ /**
11
+ * @param {AeroflyMission[]} missions in this mission list
12
+ */
13
+ constructor(missions = []) {
14
+ this.missions = missions;
15
+ }
16
+ /**
17
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
18
+ */
19
+ toString() {
20
+ const separator = "\n// -----------------------------------------------------------------------------\n";
21
+ return `\
22
+ <[file][][]
23
+ <[tmmissions_list][][]
24
+ <[list_tmmission_definition][missions][]${separator + this.missions.join(separator) + separator} >
25
+ >
26
+ >`;
27
+ }
28
+ }