@fboes/aerofly-custom-missions 1.2.2 → 1.3.0

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 (52) hide show
  1. package/.editorconfig +1 -5
  2. package/.eslintrc.json +24 -0
  3. package/CHANGELOG.md +59 -21
  4. package/dist/dto/AeroflyLocalizedText.js +44 -0
  5. package/dist/dto/AeroflyMission.js +173 -0
  6. package/dist/dto/AeroflyMissionCheckpoint.js +127 -0
  7. package/dist/dto/AeroflyMissionConditions.js +124 -0
  8. package/dist/dto/AeroflyMissionConditionsCloud.js +90 -0
  9. package/dist/dto/AeroflyMissionTargetPlane.js +35 -0
  10. package/dist/dto/AeroflyMissionsList.js +36 -0
  11. package/dist/index.js +7 -634
  12. package/dist/index.test.js +17 -11
  13. package/dist/node/AeroflyConfigurationNode.js +82 -0
  14. package/docs/custom_missions_user.tmc +3 -6
  15. package/docs/custom_missions_user.xml +100 -0
  16. package/package.json +9 -10
  17. package/src/dto/AeroflyLocalizedText.ts +74 -0
  18. package/src/dto/AeroflyMission.ts +372 -0
  19. package/src/dto/AeroflyMissionCheckpoint.ts +234 -0
  20. package/src/dto/AeroflyMissionConditions.ts +189 -0
  21. package/src/dto/AeroflyMissionConditionsCloud.ts +111 -0
  22. package/src/dto/AeroflyMissionTargetPlane.ts +62 -0
  23. package/src/dto/AeroflyMissionsList.ts +52 -0
  24. package/src/index.test.ts +22 -25
  25. package/src/index.ts +7 -1099
  26. package/src/node/AeroflyConfigurationNode.ts +89 -0
  27. package/types/AeroflyMissionCheckpoint.d.ts +140 -0
  28. package/types/AeroflyMissionTargetPlane.d.ts +40 -0
  29. package/types/dto/AeroflyConfigFileSet.d.ts +10 -0
  30. package/types/dto/AeroflyConfigFileSet.d.ts.map +1 -0
  31. package/types/dto/AeroflyConfiguration.interface.d.ts +4 -0
  32. package/types/dto/AeroflyConfiguration.interface.d.ts.map +1 -0
  33. package/types/dto/AeroflyLocalizedText.d.ts +58 -0
  34. package/types/dto/AeroflyLocalizedText.d.ts.map +1 -0
  35. package/types/dto/AeroflyMission.d.ts +163 -0
  36. package/types/dto/AeroflyMission.d.ts.map +1 -0
  37. package/types/dto/AeroflyMissionCheckpoint.d.ts +126 -0
  38. package/types/dto/AeroflyMissionCheckpoint.d.ts.map +1 -0
  39. package/types/dto/AeroflyMissionConditions.d.ts +102 -0
  40. package/types/dto/AeroflyMissionConditions.d.ts.map +1 -0
  41. package/types/dto/AeroflyMissionConditionsCloud.d.ts +57 -0
  42. package/types/dto/AeroflyMissionConditionsCloud.d.ts.map +1 -0
  43. package/types/dto/AeroflyMissionTargetPlane.d.ts +45 -0
  44. package/types/dto/AeroflyMissionTargetPlane.d.ts.map +1 -0
  45. package/types/dto/AeroflyMissionsList.d.ts +30 -0
  46. package/types/dto/AeroflyMissionsList.d.ts.map +1 -0
  47. package/types/dto/basicTypes.d.ts +1 -0
  48. package/types/index.d.ts +7 -585
  49. package/types/index.d.ts.map +1 -1
  50. package/types/node/AeroflyConfigurationNode.d.ts +14 -0
  51. package/types/node/AeroflyConfigurationNode.d.ts.map +1 -0
  52. package/eslint.config.js +0 -29
@@ -0,0 +1,372 @@
1
+ import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
2
+ import { AeroflyLocalizedText } from "./AeroflyLocalizedText.js";
3
+ import { AeroflyMissionCheckpoint } from "./AeroflyMissionCheckpoint.js";
4
+ import { AeroflyMissionConditions } from "./AeroflyMissionConditions.js";
5
+ import { AeroflyMissionTargetPlane } from "./AeroflyMissionTargetPlane.js";
6
+
7
+ export const feetPerMeter = 3.28084;
8
+ export const meterPerStatuteMile = 1609.344;
9
+
10
+ /**
11
+ * Data for the aircraft to use on this mission
12
+ * @property {string} name lowercase Aerofly aircraft ID
13
+ * @property {string} icao ICAO aircraft code
14
+ * @property {string} livery (not used yet)
15
+ */
16
+ export type AeroflyMissionAircraft = {
17
+ name: string;
18
+ icao: string;
19
+ livery: string;
20
+ };
21
+
22
+ /**
23
+ * State of aircraft systems. Configures power settings, flap positions etc
24
+ */
25
+ export type AeroflyMissionSetting =
26
+ | "cold_and_dark"
27
+ | "before_start"
28
+ | "taxi"
29
+ | "takeoff"
30
+ | "cruise"
31
+ | "approach"
32
+ | "landing"
33
+ | "winch_launch"
34
+ | "aerotow"
35
+ | "pushback";
36
+
37
+ /**
38
+ * Represents origin or destination conditions for flight
39
+ * @property {string} icao uppercase ICAO airport ID
40
+ * @property {number} longitude easting, using the World Geodetic
41
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
42
+ * of decimal degrees; -180..180
43
+ * @property {number} latitude northing, using the World Geodetic
44
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
45
+ * of decimal degrees; -90..90
46
+ * @property {number} dir in degree
47
+ * @property {number} alt the height in meters above or below the WGS
48
+ * 84 reference ellipsoid
49
+ */
50
+ export type AeroflyMissionPosition = {
51
+ icao: string;
52
+ longitude: number;
53
+ latitude: number;
54
+ dir: number;
55
+ alt: number;
56
+ };
57
+
58
+ /**
59
+ * @class
60
+ * A single flighplan, containing aircraft and weather data as well.
61
+ *
62
+ * The purpose of this class is to collect data needed for Aerofly FS4's
63
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
64
+ * for this file via the `toString()` method.
65
+ */
66
+ export class AeroflyMission {
67
+ /**
68
+ * @property {?string} tutorialName will create a link to a tutorial page at https://www.aerofly.com/aircraft-tutorials/
69
+ */
70
+ tutorialName: string | null;
71
+
72
+ /**
73
+ * @property {string} title of this flight plan
74
+ */
75
+ title: string;
76
+
77
+ /**
78
+ * @property {string} description text, mission briefing, etc
79
+ */
80
+ description: string;
81
+
82
+ /**
83
+ * @property {AeroflyLocalizedText[]} localizedTexts for title and description
84
+ */
85
+ localizedTexts: AeroflyLocalizedText[];
86
+
87
+ /**
88
+ * @property {string[]} tags free-text tags
89
+ */
90
+ tags: string[];
91
+
92
+ /**
93
+ * @property {?boolean} isFeatured makes this mission pop up in "Challenges"
94
+ */
95
+ isFeatured: boolean | null;
96
+
97
+ /**
98
+ * @property {?number} difficulty values between 0.00 and 2.00 have been encountered, but they seem to be without limit
99
+ */
100
+ difficulty: number | null;
101
+
102
+ /**
103
+ * @property {"cold_and_dark"|"before_start"|"taxi"|"takeoff"|"cruise"|"approach"|"landing"|"winch_launch"|"aerotow"|"pushback"} flightSetting of aircraft, like "taxi", "cruise"
104
+ */
105
+ flightSetting: AeroflyMissionSetting;
106
+
107
+ /**
108
+ * @property {object} aircraft for this mission
109
+ */
110
+ aircraft: AeroflyMissionAircraft;
111
+
112
+ /**
113
+ * @property {string} callsign of aircraft, uppercased
114
+ */
115
+ callsign: string;
116
+
117
+ /**
118
+ * @property {object} origin position of aircraft, as well as name of starting airport. Position does not have match airport.
119
+ */
120
+ origin: AeroflyMissionPosition;
121
+
122
+ /**
123
+ * @property {object} destination position of aircraft, as well as name of destination airport. Position does not have match airport.
124
+ */
125
+ destination: AeroflyMissionPosition;
126
+
127
+ /**
128
+ * @property {?number} distance in meters
129
+ */
130
+ distance: number | null;
131
+
132
+ /**
133
+ * @property {?number} duration in seconds
134
+ */
135
+ duration: number | null;
136
+
137
+ /**
138
+ * @property {?boolean} isScheduled marks this flight as "Scheduled flight".
139
+ */
140
+ isScheduled: boolean | null;
141
+
142
+ /**
143
+ * @property {?AeroflyMissionTargetPlane} finish as finish condition
144
+ */
145
+ finish: AeroflyMissionTargetPlane | null;
146
+
147
+ /**
148
+ * @property {AeroflyMissionConditions} conditions like time and weather for mission
149
+ */
150
+ conditions: AeroflyMissionConditions;
151
+
152
+ /**
153
+ * @property {AeroflyMissionConditions} checkpoints form the actual flight plan
154
+ */
155
+ checkpoints: AeroflyMissionCheckpoint[];
156
+
157
+ /**
158
+ * @param {string} title of this flight plan
159
+ * @param {object} [additionalAttributes] allows to set additional attributes on creation
160
+ * @param {string} [additionalAttributes.description] text, mission briefing, etc
161
+ * @param {AeroflyLocalizedText[]} [additionalAttributes.localizedTexts] translations for title and description
162
+ * @param {?string} [additionalAttributes.tutorialName] will create a link to a tutorial page at https://www.aerofly.com/aircraft-tutorials/
163
+ * @param {string[]} [additionalAttributes.tags] free-text tags
164
+ * @param {?boolean} [additionalAttributes.isFeatured] makes this mission pop up in "Challenges"
165
+ * @param {?number} [additionalAttributes.difficulty] values between 0.00 and 2.00 have been encountered, but they seem to be without limit
166
+ * @param {"cold_and_dark"|"before_start"|"taxi"|"takeoff"|"cruise"|"approach"|"landing"|"winch_launch"|"aerotow"|"pushback"} [additionalAttributes.flightSetting] of aircraft, like "taxi", "cruise"
167
+ * @param {{name:string,livery:string,icao:string}} [additionalAttributes.aircraft] for this mission
168
+ * @param {string} [additionalAttributes.callsign] of aircraft, uppercased
169
+ * @param {object} [additionalAttributes.origin] position of aircraft, as well as name of starting airport. Position does not have match airport.
170
+ * @param {object} [additionalAttributes.destination] position of aircraft, as well as name of destination airport. Position does not have match airport.
171
+ * @param {?number} [additionalAttributes.distance] in meters
172
+ * @param {?number} [additionalAttributes.duration] in seconds
173
+ * @param {?boolean} [additionalAttributes.isScheduled] marks this flight as "Scheduled flight".
174
+ * @param {?AeroflyMissionTargetPlane} [additionalAttributes.finish] as finish condition
175
+ * @param {AeroflyMissionConditions} [additionalAttributes.conditions] like time and weather for mission
176
+ * @param {AeroflyMissionCheckpoint[]} [additionalAttributes.checkpoints] form the actual flight plan
177
+ */
178
+ constructor(
179
+ title: string,
180
+ {
181
+ tutorialName = null,
182
+ description = "",
183
+ localizedTexts = [],
184
+ tags = [],
185
+ isFeatured = null,
186
+ difficulty = null,
187
+ flightSetting = "taxi",
188
+ aircraft = {
189
+ name: "c172",
190
+ icao: "",
191
+ livery: "",
192
+ },
193
+ callsign = "",
194
+ origin = {
195
+ icao: "",
196
+ longitude: 0,
197
+ latitude: 0,
198
+ dir: 0,
199
+ alt: 0,
200
+ },
201
+ destination = {
202
+ icao: "",
203
+ longitude: 0,
204
+ latitude: 0,
205
+ dir: 0,
206
+ alt: 0,
207
+ },
208
+ distance = null,
209
+ duration = null,
210
+ isScheduled = null,
211
+ finish = null,
212
+ conditions = new AeroflyMissionConditions(),
213
+ checkpoints = [],
214
+ }: Partial<AeroflyMission> = {},
215
+ ) {
216
+ this.tutorialName = tutorialName;
217
+ this.title = title;
218
+ this.checkpoints = checkpoints;
219
+ this.description = description;
220
+ this.localizedTexts = localizedTexts;
221
+ this.tags = tags;
222
+ this.isFeatured = isFeatured;
223
+ this.difficulty = difficulty;
224
+ this.flightSetting = flightSetting;
225
+ this.aircraft = aircraft;
226
+ this.callsign = callsign;
227
+ this.origin = origin;
228
+ this.destination = destination;
229
+ this.distance = distance;
230
+ this.duration = duration;
231
+ this.isScheduled = isScheduled;
232
+ this.finish = finish;
233
+ this.conditions = conditions;
234
+ }
235
+
236
+ /**
237
+ * @returns {AeroflyConfigurationNode[]} indexed checkpoints
238
+ */
239
+ getCheckpointElements(): AeroflyConfigurationNode[] {
240
+ return this.checkpoints.map((c: AeroflyMissionCheckpoint, index: number): AeroflyConfigurationNode => {
241
+ return c.getElement(index);
242
+ });
243
+ }
244
+
245
+ /**
246
+ * @returns {AeroflyConfigurationNode[]} indexed checkpoints
247
+ */
248
+ getLocalizedTextElements(): AeroflyConfigurationNode[] {
249
+ return this.localizedTexts.map((c: AeroflyLocalizedText, index: number): AeroflyConfigurationNode => {
250
+ const el = c.getElement();
251
+ el.value = String(index);
252
+ return el;
253
+ });
254
+ }
255
+
256
+ /**
257
+ * @returns {AeroflyConfigurationNode} for this mission
258
+ */
259
+ getElement(): AeroflyConfigurationNode {
260
+ if (!this.origin.icao) {
261
+ const firstCheckpoint = this.checkpoints[0];
262
+ this.origin = {
263
+ icao: firstCheckpoint.name,
264
+ longitude: firstCheckpoint.longitude,
265
+ latitude: firstCheckpoint.latitude,
266
+ dir: this.origin.dir,
267
+ alt: firstCheckpoint.altitude,
268
+ };
269
+ }
270
+
271
+ if (!this.destination.icao) {
272
+ const lastCheckpoint = this.checkpoints[this.checkpoints.length - 1];
273
+ this.destination = {
274
+ icao: lastCheckpoint.name,
275
+ longitude: lastCheckpoint.longitude,
276
+ latitude: lastCheckpoint.latitude,
277
+ dir: lastCheckpoint.direction ?? 0,
278
+ alt: lastCheckpoint.altitude,
279
+ };
280
+ }
281
+
282
+ const element = new AeroflyConfigurationNode("tmmission_definition", "mission");
283
+
284
+ element.appendChild("string8", "title", this.title);
285
+ element.appendChild("string8", "description", this.description);
286
+
287
+ if (this.tutorialName !== null) {
288
+ element.appendChild(
289
+ "string8",
290
+ "tutorial_name",
291
+ this.tutorialName,
292
+ `Opens https://www.aerofly.com/aircraft-tutorials/${this.tutorialName}`,
293
+ );
294
+ }
295
+ if (this.localizedTexts.length > 0) {
296
+ element.append(
297
+ new AeroflyConfigurationNode("list_tmmission_definition_localized", "localized_text").append(
298
+ ...this.getLocalizedTextElements(),
299
+ ),
300
+ );
301
+ }
302
+ if (this.tags.length > 0) {
303
+ element.appendChild("string8u", "tags", this.tags);
304
+ }
305
+ if (this.difficulty !== null) {
306
+ element.appendChild("float64", "difficulty", this.difficulty);
307
+ }
308
+ if (this.isFeatured !== null) {
309
+ element.appendChild("bool", "is_featured", this.isFeatured);
310
+ }
311
+
312
+ element.appendChild("string8", "flight_setting", this.flightSetting);
313
+ element.appendChild("string8u", "aircraft_name", this.aircraft.name);
314
+ /*if (this.aircraft.livery) {
315
+ mission.createChild("string8", "aircraft_livery", this.aircraft.livery);
316
+ }*/
317
+ element.appendChild("stringt8c", "aircraft_icao", this.aircraft.icao);
318
+ element.appendChild("stringt8c", "callsign", this.callsign);
319
+ element.appendChild("stringt8c", "origin_icao", this.origin.icao);
320
+ element.appendChild("tmvector2d", "origin_lon_lat", [this.origin.longitude, this.origin.latitude]);
321
+ element.appendChild(
322
+ "float64",
323
+ "origin_alt",
324
+ this.origin.alt,
325
+ `${Math.ceil(this.origin.alt * feetPerMeter)} ft MSL`,
326
+ );
327
+ element.appendChild("float64", "origin_dir", this.origin.dir);
328
+ element.appendChild("stringt8c", "destination_icao", this.destination.icao);
329
+ element.appendChild("tmvector2d", "destination_lon_lat", [
330
+ this.destination.longitude,
331
+ this.destination.latitude,
332
+ ]);
333
+ element.appendChild(
334
+ "float64",
335
+ "destination_alt",
336
+ this.destination.alt,
337
+ `${Math.ceil(this.destination.alt * feetPerMeter)} ft MSL`,
338
+ );
339
+ element.appendChild("float64", "destination_dir", this.destination.dir);
340
+
341
+ if (this.distance !== null) {
342
+ element.appendChild("float64", "distance", this.distance, `${Math.round(this.distance / 1000)} km`);
343
+ }
344
+ if (this.duration !== null) {
345
+ element.appendChild("float64", "duration", this.duration, `${Math.round(this.duration / 60)} min`);
346
+ }
347
+ if (this.isScheduled !== null) {
348
+ element.appendChild("bool", "is_scheduled", this.isScheduled);
349
+ }
350
+ if (this.finish !== null) {
351
+ element.append(this.finish.getElement());
352
+ }
353
+
354
+ element.append(this.conditions.getElement());
355
+ if (this.checkpoints.length > 0) {
356
+ element.append(
357
+ new AeroflyConfigurationNode("list_tmmission_checkpoint", "checkpoints").append(
358
+ ...this.getCheckpointElements(),
359
+ ),
360
+ );
361
+ }
362
+
363
+ return element;
364
+ }
365
+
366
+ /**
367
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
368
+ */
369
+ toString(): string {
370
+ return this.getElement().toString();
371
+ }
372
+ }
@@ -0,0 +1,234 @@
1
+ import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
2
+ import { feetPerMeter } from "./AeroflyMission.js";
3
+
4
+ /**
5
+ * Types of checkpoints. Required are usually "origin", "departure_runway" at the start and "destination_runway", "destination" at the end.
6
+ */
7
+ export type AeroflyMissionCheckpointType =
8
+ | "origin"
9
+ | "departure_runway"
10
+ | "departure"
11
+ | "waypoint"
12
+ | "arrival"
13
+ | "approach"
14
+ | "destination_runway"
15
+ | "destination";
16
+
17
+ /**
18
+ * @class
19
+ * A single way point for the given flight plan
20
+ *
21
+ * The purpose of this class is to collect data needed for Aerofly FS4's
22
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
23
+ * for this file via the `toString()` method.
24
+ */
25
+ export class AeroflyMissionCheckpoint {
26
+ /**
27
+ * @property {"origin"|"departure_runway"|"departure"|"waypoint"|"arrival"|"approach"|"destination_runway"|"destination"} type of checkpoint, like "departure_runway"
28
+ */
29
+ type: AeroflyMissionCheckpointType;
30
+
31
+ /**
32
+ * @property {string} name ICAO code for airport, runway designator, navaid
33
+ * designator, fix name, or custom name
34
+ */
35
+ name: string;
36
+
37
+ /**
38
+ * @property {number} longitude easting, using the World Geodetic
39
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
40
+ * of decimal degrees; -180..180
41
+ */
42
+ longitude: number;
43
+
44
+ /**
45
+ * @property {number} latitude northing, using the World Geodetic
46
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
47
+ * of decimal degrees; -90..90
48
+ */
49
+ latitude: number;
50
+
51
+ /**
52
+ * @property {number} altitude The height in meters above or below the WGS
53
+ * 84 reference ellipsoid
54
+ */
55
+ altitude: number;
56
+
57
+ /**
58
+ * @property {?boolean} altitudeConstraint The altitude given in `altitude`
59
+ * will be interpreted as mandatory flight plan altitude instead of
60
+ * suggestion.
61
+ */
62
+ altitudeConstraint: boolean | null;
63
+
64
+ /**
65
+ * @property {?number} direction of runway, in degree
66
+ */
67
+ direction: number | null;
68
+
69
+ /**
70
+ * @property {?number} slope of runway
71
+ */
72
+ slope: number | null;
73
+
74
+ /**
75
+ * @property {?number} length of runway, in meters
76
+ */
77
+ length: number | null;
78
+
79
+ /**
80
+ * @property {?number} frequency of runways or navigational aids, in Hz; multiply by 1000 for kHz, 1_000_000 for MHz
81
+ */
82
+ frequency: number | null;
83
+
84
+ /**
85
+ * @property {?boolean} flyOver if waypoint is meant to be flown over
86
+ */
87
+ flyOver: boolean | null;
88
+
89
+ /**
90
+ * @param {string} name ICAO code for airport, runway designator, navaid
91
+ * designator, fix name, or custom name
92
+ * @param {"origin"|"departure_runway"|"departure"|"waypoint"|"arrival"|"approach"|"destination_runway"|"destination"} type Type of checkpoint, like "departure_runway"
93
+ * @param {number} longitude easting, using the World Geodetic
94
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
95
+ * of decimal degrees; -180..180
96
+ * @param {number} latitude northing, using the World Geodetic
97
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
98
+ * of decimal degrees; -90..90
99
+ * @param {object} additionalAttributes allows to set additional attributes on creation
100
+ * @param {number} [additionalAttributes.altitude] The height in meters above or below the WGS
101
+ * 84 reference ellipsoid
102
+ * @param {?number} [additionalAttributes.altitude_feet] The height in feet above or below the WGS
103
+ * 84 reference ellipsoid. Will overwrite altitude
104
+ * @param {number} [additionalAttributes.altitudeConstraint] The altitude given in `altitude`
105
+ * will be interpreted as mandatory flight plan altitude instead of
106
+ * suggestion.
107
+ * @param {boolean} [additionalAttributes.direction] of runway, in degree
108
+ * @param {?number} [additionalAttributes.slope] of runway
109
+ * @param {?number} [additionalAttributes.length] of runway, in meters
110
+ * @param {?number} [additionalAttributes.length_feet] of runway, in feet. Will overwrite length
111
+ * @param {?number} [additionalAttributes.frequency] of runways or navigational aids, in Hz; multiply by 1000 for kHz, 1_000_000 for MHz
112
+ * @param {?boolean} [additionalAttributes.flyOver] if waypoint is meant to be flown over
113
+ */
114
+ constructor(
115
+ name: string,
116
+ type: AeroflyMissionCheckpointType,
117
+ longitude: number,
118
+ latitude: number,
119
+ {
120
+ altitude = 0,
121
+ altitude_feet = 0,
122
+ altitudeConstraint = null,
123
+ direction = null,
124
+ slope = null,
125
+ length = null,
126
+ length_feet = 0,
127
+ frequency = null,
128
+ flyOver = null,
129
+ }: Partial<AeroflyMissionCheckpoint> & {
130
+ altitude_feet?: number;
131
+ length_feet?: number;
132
+ } = {},
133
+ ) {
134
+ this.type = type;
135
+ this.name = name;
136
+ this.longitude = longitude;
137
+ this.latitude = latitude;
138
+ this.altitude = altitude;
139
+ this.altitudeConstraint = altitudeConstraint;
140
+ this.direction = direction;
141
+ this.slope = slope;
142
+ this.length = length;
143
+ this.frequency = frequency;
144
+ this.flyOver = flyOver;
145
+
146
+ if (altitude_feet) {
147
+ this.altitude_feet = altitude_feet;
148
+ }
149
+ if (length_feet) {
150
+ this.length_feet = length_feet;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * @param {number} altitude_feet in feet
156
+ */
157
+ set altitude_feet(altitude_feet: number) {
158
+ this.altitude = altitude_feet / feetPerMeter;
159
+ }
160
+
161
+ /**
162
+ * @returns {number} altitude_feet
163
+ */
164
+ get altitude_feet(): number {
165
+ return this.altitude * feetPerMeter;
166
+ }
167
+
168
+ /**
169
+ * @param {number} length_feet in feet
170
+ */
171
+ set length_feet(length_feet: number) {
172
+ this.length = length_feet / feetPerMeter;
173
+ }
174
+
175
+ /**
176
+ * @returns {number} length_feet
177
+ */
178
+ get length_feet(): number {
179
+ return (this.length ?? 0) * feetPerMeter;
180
+ }
181
+
182
+ /**
183
+ * @returns {string} with MHz / kHz attached
184
+ */
185
+ get frequency_string(): string {
186
+ if (!this.frequency) {
187
+ return "None";
188
+ }
189
+ if (this.frequency > 1000000) {
190
+ return String(this.frequency / 1000000) + " MHz";
191
+ }
192
+ if (this.frequency > 1000) {
193
+ return String(this.frequency / 1000) + " kHz";
194
+ }
195
+
196
+ return String(this.frequency) + " Hz";
197
+ }
198
+
199
+ /**
200
+ * @param {number} index default: 0
201
+ * @returns {AeroflyConfigurationNode} to use in Aerofly FS4's `custom_missions_user.tmc`
202
+ */
203
+ getElement(index: number = 0): AeroflyConfigurationNode {
204
+ const element = new AeroflyConfigurationNode("tmmission_checkpoint", "element", String(index))
205
+ .appendChild("string8u", "type", this.type)
206
+ .appendChild("string8u", "name", this.name)
207
+ .appendChild("vector2_float64", "lon_lat", [this.longitude, this.latitude])
208
+ .appendChild("float64", "altitude", this.altitude, `${Math.ceil(this.altitude_feet)} ft`)
209
+ .appendChild("float64", "direction", this.direction ?? (index === 0 ? -1 : 0))
210
+ .appendChild("float64", "slope", this.slope ?? 0);
211
+
212
+ if (this.altitudeConstraint !== null) {
213
+ element.appendChild("bool", "alt_cst", this.altitudeConstraint);
214
+ }
215
+ if (this.length) {
216
+ element.appendChild("float64", "length", this.length ?? 0, `${Math.floor(this.length_feet)} ft`);
217
+ }
218
+ if (this.frequency) {
219
+ element.appendChild("float64", "frequency", this.frequency ?? 0, `${this.frequency_string}`);
220
+ }
221
+ if (this.flyOver !== null) {
222
+ element.appendChild("bool", "fly_over", this.flyOver);
223
+ }
224
+
225
+ return element;
226
+ }
227
+
228
+ /**
229
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
230
+ */
231
+ toString(): string {
232
+ return this.getElement().toString();
233
+ }
234
+ }