@fboes/aerofly-custom-missions 1.2.0 → 1.2.2

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.
package/src/index.ts CHANGED
@@ -25,7 +25,17 @@ export type AeroflyMissionPosition = {
25
25
  /**
26
26
  * State of aircraft systems. Configures power settings, flap positions etc
27
27
  */
28
- export type AeroflyMissionSetting = "taxi" | "takeoff" | "cruise" | "approach" | "landing" | "winch_launch" | "aerotow";
28
+ export type AeroflyMissionSetting =
29
+ | "cold_and_dark"
30
+ | "before_start"
31
+ | "taxi"
32
+ | "takeoff"
33
+ | "cruise"
34
+ | "approach"
35
+ | "landing"
36
+ | "winch_launch"
37
+ | "aerotow"
38
+ | "pushback";
29
39
 
30
40
  /**
31
41
  * Types of checkpoints. Required are usually "origin", "departure_runway" at the start and "destination_runway", "destination" at the end.
@@ -69,6 +79,42 @@ export type AeroflyMissionConditionsWind = {
69
79
  */
70
80
  export type AeroflyMissionConditionsCloudCoverCode = "CLR" | "FEW" | "SCT" | "BKN" | "OVC";
71
81
 
82
+ export class AeroflyConfigFileSet {
83
+ #indent: number;
84
+ elements: string[];
85
+
86
+ constructor(indent: number, type: string, name: string, value: string = "") {
87
+ this.#indent = indent;
88
+ this.elements = [`${this.indent}<[${type}][${name}][${value}]`];
89
+ }
90
+
91
+ get indent(): string {
92
+ return " ".repeat(this.#indent);
93
+ }
94
+
95
+ push(type: string, name: string, value: string | number | string[] | number[] | boolean, comment: string = "") {
96
+ if (value instanceof Array) {
97
+ value = value.join(" ");
98
+ } else if (typeof value === "boolean") {
99
+ value = value ? "true" : "false";
100
+ }
101
+ let tag = `${this.indent} <[${type}][${name}][${value}]>`;
102
+ if (comment) {
103
+ tag += ` // ${comment}`;
104
+ }
105
+ return this.pushRaw(tag);
106
+ }
107
+
108
+ pushRaw(s: string) {
109
+ this.elements.push(s);
110
+ return this;
111
+ }
112
+
113
+ toString() {
114
+ return this.elements.join("\n") + "\n" + `${this.indent}>`;
115
+ }
116
+ }
117
+
72
118
  /**
73
119
  * @class
74
120
  * A list of flight plans.
@@ -95,7 +141,8 @@ export class AeroflyMissionsList {
95
141
  */
96
142
  toString(): string {
97
143
  const separator = "\n// -----------------------------------------------------------------------------\n";
98
- return `<[file][][]
144
+ return `\
145
+ <[file][][]
99
146
  <[tmmissions_list][][]
100
147
  <[list_tmmission_definition][missions][]${separator + this.missions.join(separator) + separator} >
101
148
  >
@@ -112,7 +159,10 @@ export class AeroflyMissionsList {
112
159
  * for this file via the `toString()` method.
113
160
  */
114
161
  export class AeroflyMission {
115
- // tutorial_name
162
+ /**
163
+ * @property {?string} tutorialName will create a link to a tutorial page at https://www.aerofly.com/aircraft-tutorials/
164
+ */
165
+ tutorialName: string | null;
116
166
 
117
167
  /**
118
168
  * @property {string} title of this flight plan
@@ -135,17 +185,17 @@ export class AeroflyMission {
135
185
  tags: string[];
136
186
 
137
187
  /**
138
- * @property {boolean} isFeatured makes this mission pop up in "Challenges"
188
+ * @property {?boolean} isFeatured makes this mission pop up in "Challenges"
139
189
  */
140
- isFeatured: boolean;
190
+ isFeatured: boolean | null;
141
191
 
142
192
  /**
143
- * @property {number|undefined} difficulty values between 0.00 and 2.00 have been encountered, but they seem to be without limit
193
+ * @property {?number} difficulty values between 0.00 and 2.00 have been encountered, but they seem to be without limit
144
194
  */
145
- difficulty: number | undefined;
195
+ difficulty: number | null;
146
196
 
147
197
  /**
148
- * @property {"taxi"|"takeoff"|"cruise"|"approach"|"landing"|"winch_launch"|"aerotow"} flightSetting of aircraft, like "taxi", "cruise"
198
+ * @property {"cold_and_dark"|"before_start"|"taxi"|"takeoff"|"cruise"|"approach"|"landing"|"winch_launch"|"aerotow"|"pushback"} flightSetting of aircraft, like "taxi", "cruise"
149
199
  */
150
200
  flightSetting: AeroflyMissionSetting;
151
201
 
@@ -170,14 +220,19 @@ export class AeroflyMission {
170
220
  destination: AeroflyMissionPosition;
171
221
 
172
222
  /**
173
- * @property {number} in meters
223
+ * @property {?number} distance in meters
224
+ */
225
+ distance: number | null;
226
+
227
+ /**
228
+ * @property {?number} duration in seconds
174
229
  */
175
- distance: number;
230
+ duration: number | null;
176
231
 
177
232
  /**
178
- * @property {number} in seconds
233
+ * @property {?boolean} isScheduled marks this flight as "Scheduled flight".
179
234
  */
180
- duration: number;
235
+ isScheduled: boolean | null;
181
236
 
182
237
  /**
183
238
  * @property {?AeroflyMissionTargetPlane} finish as finish condition
@@ -199,16 +254,18 @@ export class AeroflyMission {
199
254
  * @param {object} [additionalAttributes] allows to set additional attributes on creation
200
255
  * @param {string} [additionalAttributes.description] text, mission briefing, etc
201
256
  * @param {AeroflyLocalizedText[]} [additionalAttributes.localizedTexts] translations for title and description
257
+ * @param {?string} [additionalAttributes.tutorialName] will create a link to a tutorial page at https://www.aerofly.com/aircraft-tutorials/
202
258
  * @param {string[]} [additionalAttributes.tags]
203
- * @param {boolean} [additionalAttributes.isFeatured] makes this mission pop up in "Challenges"
204
- * @param {number|undefined} [additionalAttributes.difficulty] values between 0.00 and 2.00 have been encountered, but they seem to be without limit
205
- * @param {"taxi"|"takeoff"|"cruise"|"approach"|"landing"|"winch_launch"|"aerotow"} [additionalAttributes.flightSetting] of aircraft, like "taxi", "cruise"
259
+ * @param {?boolean} [additionalAttributes.isFeatured] makes this mission pop up in "Challenges"
260
+ * @param {?number} [additionalAttributes.difficulty] values between 0.00 and 2.00 have been encountered, but they seem to be without limit
261
+ * @param {"cold_and_dark"|"before_start"|"taxi"|"takeoff"|"cruise"|"approach"|"landing"|"winch_launch"|"aerotow"|"pushback"} [additionalAttributes.flightSetting] of aircraft, like "taxi", "cruise"
206
262
  * @param {{name:string,livery:string,icao:string}} [additionalAttributes.aircraft] for this mission
207
263
  * @param {string} [additionalAttributes.callsign] of aircraft, uppercased
208
264
  * @param {object} [additionalAttributes.origin] position of aircraft, as well as name of starting airport. Position does not have match airport.
209
265
  * @param {object} [additionalAttributes.destination] position of aircraft, as well as name of destination airport. Position does not have match airport.
210
- * @param {number} [additionalAttributes.distance] in meters
211
- * @param {number} [additionalAttributes.duration] in seconds
266
+ * @param {?number} [additionalAttributes.distance] in meters
267
+ * @param {?number} [additionalAttributes.duration] in seconds
268
+ * @param {?boolean} [additionalAttributes.isScheduled] marks this flight as "Scheduled flight".
212
269
  * @param {?AeroflyMissionTargetPlane} [additionalAttributes.finish] as finish condition
213
270
  * @param {AeroflyMissionConditions} [additionalAttributes.conditions] like time and weather for mission
214
271
  * @param {AeroflyMissionCheckpoint[]} [additionalAttributes.checkpoints] form the actual flight plan
@@ -216,11 +273,12 @@ export class AeroflyMission {
216
273
  constructor(
217
274
  title: string,
218
275
  {
276
+ tutorialName = null,
219
277
  description = "",
220
278
  localizedTexts = [],
221
279
  tags = [],
222
- isFeatured = false,
223
- difficulty = undefined,
280
+ isFeatured = null,
281
+ difficulty = null,
224
282
  flightSetting = "taxi",
225
283
  aircraft = {
226
284
  name: "c172",
@@ -242,29 +300,33 @@ export class AeroflyMission {
242
300
  dir: 0,
243
301
  alt: 0,
244
302
  },
245
- distance = 0,
246
- duration = 0,
303
+ distance = null,
304
+ duration = null,
305
+ isScheduled = null,
247
306
  finish = null,
248
307
  conditions = new AeroflyMissionConditions(),
249
308
  checkpoints = [],
250
309
  }: {
310
+ tutorialName?: string | null;
251
311
  description?: string;
252
312
  localizedTexts?: AeroflyLocalizedText[];
253
313
  tags?: string[];
254
- isFeatured?: boolean;
255
- difficulty?: number | undefined;
314
+ isFeatured?: boolean | null;
315
+ difficulty?: number | null;
256
316
  flightSetting?: AeroflyMissionSetting;
257
317
  aircraft?: AeroflyMissionAircraft;
258
318
  callsign?: string;
259
319
  origin?: AeroflyMissionPosition;
260
320
  destination?: AeroflyMissionPosition;
261
- distance?: number;
262
- duration?: number;
321
+ distance?: number | null;
322
+ duration?: number | null;
323
+ isScheduled?: boolean | null;
263
324
  finish?: AeroflyMissionTargetPlane | null;
264
325
  conditions?: AeroflyMissionConditions;
265
326
  checkpoints?: AeroflyMissionCheckpoint[];
266
327
  } = {},
267
328
  ) {
329
+ this.tutorialName = tutorialName;
268
330
  this.title = title;
269
331
  this.checkpoints = checkpoints;
270
332
  this.description = description;
@@ -279,6 +341,7 @@ export class AeroflyMission {
279
341
  this.destination = destination;
280
342
  this.distance = distance;
281
343
  this.duration = duration;
344
+ this.isScheduled = isScheduled;
282
345
  this.finish = finish;
283
346
  this.conditions = conditions;
284
347
  }
@@ -310,10 +373,6 @@ export class AeroflyMission {
310
373
  * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
311
374
  */
312
375
  toString(): string {
313
- if (this.checkpoints.length < 2) {
314
- throw Error("this.checkpoints.length < 2");
315
- }
316
-
317
376
  if (!this.origin.icao) {
318
377
  const firstCheckpoint = this.checkpoints[0];
319
378
  this.origin = {
@@ -336,60 +395,79 @@ export class AeroflyMission {
336
395
  };
337
396
  }
338
397
 
339
- const optionalProperties = [];
398
+ const fileSet = new AeroflyConfigFileSet(3, "tmmission_definition", "mission");
399
+ fileSet.push("string8", "title", this.title);
400
+ fileSet.push("string8", "description", this.description);
401
+
402
+ if (this.tutorialName !== null) {
403
+ fileSet.push(
404
+ "string8",
405
+ "tutorial_name",
406
+ this.tutorialName,
407
+ `Opens https://www.aerofly.com/aircraft-tutorials/${this.tutorialName}`,
408
+ );
409
+ }
340
410
  if (this.localizedTexts.length > 0) {
341
- optionalProperties.push(` <[list_tmmission_definition_localized][localized_text][]
342
- ${this.getLocalizedTextsString()}
343
- >`);
411
+ fileSet.pushRaw(
412
+ new AeroflyConfigFileSet(4, "list_tmmission_definition_localized", "localized_text")
413
+ .pushRaw(this.getLocalizedTextsString())
414
+ .toString(),
415
+ );
344
416
  }
345
417
  if (this.tags.length > 0) {
346
- optionalProperties.push(` <[string8u][tags][ ${this.tags.join(" ")} ]>`);
418
+ fileSet.push("string8u", "tags", this.tags.join(" "));
347
419
  }
348
- if (this.difficulty !== undefined) {
349
- optionalProperties.push(` <[float64] [difficulty] [${this.difficulty}]>`);
420
+ if (this.difficulty !== null) {
421
+ fileSet.push("float64", "difficulty", this.difficulty);
350
422
  }
351
- if (this.isFeatured) {
352
- optionalProperties.push(
353
- ` <[bool] [is_featured] [${this.isFeatured ? "true" : "false"}]>`,
354
- );
423
+ if (this.isFeatured !== null) {
424
+ fileSet.push("bool", "is_featured", this.isFeatured);
355
425
  }
356
426
 
357
- const moreOptionalProperties = [];
358
- if (this.distance) {
359
- moreOptionalProperties.push(
360
- ` <[float64] [distance] [${this.distance}]> // ${Math.ceil(this.distance / 1000)} km`,
361
- );
427
+ fileSet.push("string8", "flight_setting", this.flightSetting);
428
+ fileSet.push("string8u", "aircraft_name", this.aircraft.name);
429
+ /*if (this.aircraft.livery) {
430
+ fileSet.push("string8", "aircraft_livery", this.aircraft.livery);
431
+ }*/
432
+ fileSet.push("stringt8c", "aircraft_icao", this.aircraft.icao);
433
+ fileSet.push("stringt8c", "callsign", this.callsign);
434
+ fileSet.push("stringt8c", "origin_icao", this.origin.icao);
435
+ fileSet.push("tmvector2d", "origin_lon_lat", [this.origin.longitude, this.origin.latitude]);
436
+ fileSet.push("float64", "origin_alt", this.origin.alt, `${Math.ceil(this.origin.alt * feetPerMeter)} ft MSL`);
437
+ fileSet.push("float64", "origin_dir", this.origin.dir);
438
+ fileSet.push("stringt8c", "destination_icao", this.destination.icao);
439
+ fileSet.push("tmvector2d", "destination_lon_lat", [this.destination.longitude, this.destination.latitude]);
440
+ fileSet.push(
441
+ "float64",
442
+ "destination_alt",
443
+ this.destination.alt,
444
+ `${Math.ceil(this.destination.alt * feetPerMeter)} ft MSL`,
445
+ );
446
+ fileSet.push("float64", "destination_dir", this.destination.dir);
447
+
448
+ if (this.distance !== null) {
449
+ fileSet.push("float64", "distance", this.distance, `${Math.round(this.distance / 1000)} km`);
362
450
  }
363
- if (this.duration) {
364
- moreOptionalProperties.push(
365
- ` <[float64] [duration] [${this.duration}]> // ${Math.ceil(this.duration / 60)} min`,
366
- );
451
+ if (this.duration !== null) {
452
+ fileSet.push("float64", "duration", this.duration, `${Math.round(this.duration / 60)} min`);
453
+ }
454
+ if (this.isScheduled !== null) {
455
+ fileSet.push("bool", "is_scheduled", this.isScheduled ? "true" : "false");
367
456
  }
368
- if (this.finish) {
369
- moreOptionalProperties.push(this.finish.toString());
457
+ if (this.finish !== null) {
458
+ fileSet.pushRaw(this.finish.toString());
370
459
  }
371
460
 
372
- return ` <[tmmission_definition][mission][]
373
- <[string8][title][${this.title}]>
374
- <[string8][description][${this.description}]>${optionalProperties.length > 0 ? "\n" + optionalProperties.join("\n") : ""}
375
- <[string8] [flight_setting] [${this.flightSetting}]>
376
- <[string8u] [aircraft_name] [${this.aircraft.name}]>
377
- //<[string8u][aircraft_livery] [${this.aircraft.livery}]>
378
- <[stringt8c] [aircraft_icao] [${this.aircraft.icao}]>
379
- <[stringt8c] [callsign] [${this.callsign}]>
380
- <[stringt8c] [origin_icao] [${this.origin.icao}]>
381
- <[tmvector2d][origin_lon_lat] [${this.origin.longitude} ${this.origin.latitude}]>
382
- <[float64] [origin_alt] [${this.origin.alt}]> // ${Math.ceil(this.origin.alt * feetPerMeter)} ft MSL
383
- <[float64] [origin_dir] [${this.origin.dir}]>
384
- <[stringt8c] [destination_icao] [${this.destination.icao}]>
385
- <[tmvector2d][destination_lon_lat][${this.destination.longitude} ${this.destination.latitude}]>
386
- <[float64] [destination_alt] [${this.destination.alt}]> // ${Math.ceil(this.destination.alt * feetPerMeter)} ft MSL
387
- <[float64] [destination_dir] [${this.destination.dir}]>${moreOptionalProperties.length > 0 ? "\n" + moreOptionalProperties.join("\n") : ""}
388
- ${this.conditions}
389
- <[list_tmmission_checkpoint][checkpoints][]
390
- ${this.getCheckpointsString()}
391
- >
392
- >`;
461
+ fileSet.pushRaw(this.conditions.toString());
462
+ if (this.checkpoints.length > 0) {
463
+ fileSet.pushRaw(
464
+ new AeroflyConfigFileSet(4, "list_tmmission_checkpoint", "checkpoints")
465
+ .pushRaw(this.getCheckpointsString())
466
+ .toString(),
467
+ );
468
+ }
469
+
470
+ return fileSet.toString();
393
471
  }
394
472
  }
395
473
 
@@ -441,8 +519,8 @@ export class AeroflyMissionConditions {
441
519
  * @param {number} [additionalAttributes.turbulenceStrength] 0..1, percentage
442
520
  * @param {number} [additionalAttributes.thermalStrength] 0..1, percentage
443
521
  * @param {number} [additionalAttributes.visibility] in meters
444
- * @param {number?} [additionalAttributes.visibility_sm] in statute miles, will overwrite visibility
445
- * @param {number?} [additionalAttributes.temperature] in °C, will overwrite thermalStrength
522
+ * @param {?number} [additionalAttributes.visibility_sm] in statute miles, will overwrite visibility
523
+ * @param {?number} [additionalAttributes.temperature] in °C, will overwrite thermalStrength
446
524
  * @param {AeroflyMissionConditionsCloud[]} [additionalAttributes.clouds] for the whole flight
447
525
  */
448
526
  constructor({
@@ -554,21 +632,23 @@ export class AeroflyMissionConditions {
554
632
  this.clouds = [new AeroflyMissionConditionsCloud(0, 0)];
555
633
  }
556
634
 
557
- return ` <[tmmission_conditions][conditions][]
558
- <[tm_time_utc][time][]
559
- <[int32][time_year][${this.time.getUTCFullYear()}]>
560
- <[int32][time_month][${this.time.getUTCMonth() + 1}]>
561
- <[int32][time_day][${this.time.getUTCDate()}]>
562
- <[float64][time_hours][${this.time_hours}]> // ${this.time_presentational} UTC
563
- >
564
- <[float64][wind_direction][${this.wind.direction}]>
565
- <[float64][wind_speed][${this.wind.speed}]> // kts
566
- <[float64][wind_gusts][${this.wind.gusts}]> // kts
567
- <[float64][turbulence_strength][${this.turbulenceStrength}]>
568
- <[float64][thermal_strength][${this.thermalStrength}]> // ${this.temperature} °C
569
- <[float64][visibility][${this.visibility}]> // ${this.visibility_sm} SM
570
- ${this.getCloudsString()}
571
- >`;
635
+ return new AeroflyConfigFileSet(4, "tmmission_conditions", "conditions")
636
+ .pushRaw(
637
+ new AeroflyConfigFileSet(5, "tm_time_utc", "time")
638
+ .push("int32", "time_year", this.time.getUTCFullYear())
639
+ .push("int32", "time_month", this.time.getUTCMonth() + 1)
640
+ .push("int32", "time_day", this.time.getUTCDate())
641
+ .push("float64", "time_hours", this.time_hours, `${this.time_presentational} UTC`)
642
+ .toString(),
643
+ )
644
+ .push("float64", "wind_direction", this.wind.direction)
645
+ .push("float64", "wind_speed", this.wind.speed, "kts")
646
+ .push("float64", "wind_gusts", this.wind.gusts, "kts")
647
+ .push("float64", "turbulence_strength", this.turbulenceStrength)
648
+ .push("float64", "thermal_strength", this.thermalStrength, `${this.temperature} °C`)
649
+ .push("float64", "visibility", this.visibility, `${this.visibility_sm} SM`)
650
+ .pushRaw(this.getCloudsString())
651
+ .toString();
572
652
  }
573
653
  }
574
654
 
@@ -644,19 +724,25 @@ export class AeroflyMissionConditionsCloud {
644
724
  * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
645
725
  */
646
726
  toString(index: number = 0): string {
647
- let indexString = "cloud";
648
-
649
- switch (index) {
650
- case 1:
651
- indexString = "cirrus";
652
- break;
653
- case 2:
654
- indexString = "cumulus_mediocris";
655
- break;
656
- }
727
+ const getIndexString = (index: number) => {
728
+ switch (index) {
729
+ case 0:
730
+ return "cloud";
731
+ case 1:
732
+ return "cirrus";
733
+ case 2:
734
+ return "cumulus_mediocris";
735
+ default:
736
+ return "more_clouds";
737
+ }
738
+ };
739
+
740
+ const indexString = getIndexString(index);
741
+ const comment = index > 1 ? '//' : '';
657
742
 
658
- return ` <[float64][${indexString}_cover][${this.cover ?? 0}]> // ${this.cover_code}
659
- <[float64][${indexString}_base][${this.base}]> // ${this.base_feet} ft AGL`;
743
+ return `\
744
+ ${comment}<[float64][${indexString}_cover][${this.cover ?? 0}]> // ${this.cover_code}
745
+ ${comment}<[float64][${indexString}_base][${this.base}]> // ${this.base_feet} ft AGL`;
660
746
  }
661
747
  }
662
748
 
@@ -700,6 +786,13 @@ export class AeroflyMissionCheckpoint {
700
786
  */
701
787
  altitude: number;
702
788
 
789
+ /**
790
+ * @property {?boolean} altitudeConstraint The altitude given in `altitude`
791
+ * will be interpreted as mandatory flight plan altitude instead of
792
+ * suggestion.
793
+ */
794
+ altitudeConstraint: boolean | null;
795
+
703
796
  /**
704
797
  * @property {?number} direction of runway, in degree
705
798
  */
@@ -721,9 +814,9 @@ export class AeroflyMissionCheckpoint {
721
814
  frequency: number | null;
722
815
 
723
816
  /**
724
- * @property {boolean|undefined} flyOver if waypoint is meant to be flown over
817
+ * @property {?boolean} flyOver if waypoint is meant to be flown over
725
818
  */
726
- flyOver: boolean | undefined;
819
+ flyOver: boolean | null;
727
820
 
728
821
  /**
729
822
  * @param {string} name ICAO code for airport, runway designator, navaid
@@ -738,14 +831,17 @@ export class AeroflyMissionCheckpoint {
738
831
  * @param {object} additionalAttributes allows to set additional attributes on creation
739
832
  * @param {number} [additionalAttributes.altitude] The height in meters above or below the WGS
740
833
  * 84 reference ellipsoid
741
- * @param {number?} [additionalAttributes.altitude_feet] The height in feet above or below the WGS
834
+ * @param {?number} [additionalAttributes.altitude_feet] The height in feet above or below the WGS
742
835
  * 84 reference ellipsoid. Will overwrite altitude
743
- * @param {number} [additionalAttributes.direction] of runway, in degree
744
- * @param {number?} [additionalAttributes.slope] of runway
745
- * @param {number?} [additionalAttributes.length] of runway, in meters
746
- * @param {number?} [additionalAttributes.length_feet] of runway, in feet. Will overwrite length
747
- * @param {number?} [additionalAttributes.frequency] of runways or navigational aids, in Hz; multiply by 1000 for kHz, 1_000_000 for MHz
748
- * @param {boolean|undefined} [additionalAttributes.flyOver] if waypoint is meant to be flown over
836
+ * @param {number} [additionalAttributes.altitudeConstraint] The altitude given in `altitude`
837
+ * will be interpreted as mandatory flight plan altitude instead of
838
+ * suggestion.
839
+ * @param {boolean} [additionalAttributes.direction] of runway, in degree
840
+ * @param {?number} [additionalAttributes.slope] of runway
841
+ * @param {?number} [additionalAttributes.length] of runway, in meters
842
+ * @param {?number} [additionalAttributes.length_feet] of runway, in feet. Will overwrite length
843
+ * @param {?number} [additionalAttributes.frequency] of runways or navigational aids, in Hz; multiply by 1000 for kHz, 1_000_000 for MHz
844
+ * @param {?boolean} [additionalAttributes.flyOver] if waypoint is meant to be flown over
749
845
  */
750
846
  constructor(
751
847
  name: string,
@@ -755,21 +851,23 @@ export class AeroflyMissionCheckpoint {
755
851
  {
756
852
  altitude = 0,
757
853
  altitude_feet = null,
854
+ altitudeConstraint = null,
758
855
  direction = null,
759
856
  slope = null,
760
857
  length = null,
761
858
  length_feet = null,
762
859
  frequency = null,
763
- flyOver = undefined,
860
+ flyOver = null,
764
861
  }: {
765
862
  altitude?: number;
766
863
  altitude_feet?: number | null;
864
+ altitudeConstraint?: boolean | null;
767
865
  direction?: number | null;
768
866
  slope?: number | null;
769
867
  length?: number | null;
770
868
  length_feet?: number | null;
771
869
  frequency?: number | null;
772
- flyOver?: boolean | undefined;
870
+ flyOver?: boolean | null;
773
871
  } = {},
774
872
  ) {
775
873
  this.type = type;
@@ -777,6 +875,7 @@ export class AeroflyMissionCheckpoint {
777
875
  this.longitude = longitude;
778
876
  this.latitude = latitude;
779
877
  this.altitude = altitude;
878
+ this.altitudeConstraint = altitudeConstraint;
780
879
  this.direction = direction;
781
880
  this.slope = slope;
782
881
  this.length = length;
@@ -841,32 +940,35 @@ export class AeroflyMissionCheckpoint {
841
940
  * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
842
941
  */
843
942
  toString(index: number = 0): string {
844
- const optionalProperties = [];
943
+ const fileSet = new AeroflyConfigFileSet(5, "tmmission_checkpoint", "element", String(index))
944
+ .push("string8u", "type", this.type)
945
+ .push("string8u", "name", this.name)
946
+ .push("vector2_float64", "lon_lat", [this.longitude, this.latitude])
947
+ .push("float64", "altitude", this.altitude, `${Math.ceil(this.altitude_feet)} ft`)
948
+ .push("float64", "direction", this.direction ?? (index === 0 ? -1 : 0))
949
+ .push("float64", "slope", this.slope ?? 0);
950
+
951
+ if (this.altitudeConstraint !== null) {
952
+ fileSet.push("bool", "alt_cst", this.altitudeConstraint);
953
+ }
845
954
  if (this.length) {
846
- optionalProperties.push(
847
- ` <[float64][length][${this.length ?? 0}]> // ${Math.floor(this.length_feet)} ft`,
848
- );
955
+ fileSet.push("float64", "length", this.length ?? 0, `${Math.floor(this.length_feet)} ft`);
849
956
  }
850
957
  if (this.frequency) {
851
- optionalProperties.push(
852
- ` <[float64][frequency][${this.frequency ?? 0}]> // ${this.frequency_string}`,
853
- );
958
+ fileSet.push("float64", "frequency", this.frequency ?? 0, `${this.frequency_string}`);
854
959
  }
855
- if (this.flyOver !== undefined) {
856
- optionalProperties.push(` <[bool][fly_over][${this.flyOver ? "true" : "false"}]>`);
960
+ if (this.flyOver !== null) {
961
+ fileSet.push("bool", "fly_over", this.flyOver);
857
962
  }
858
963
 
859
- return ` <[tmmission_checkpoint][element][${index}]
860
- <[string8u][type][${this.type}]>
861
- <[string8u][name][${this.name}]>
862
- <[vector2_float64][lon_lat][${this.longitude} ${this.latitude}]>
863
- <[float64][altitude][${this.altitude}]> // ${Math.ceil(this.altitude_feet)} ft
864
- <[float64][direction][${this.direction ?? (index === 0 ? -1 : 0)}]>
865
- <[float64][slope][${this.slope ?? 0}]>${optionalProperties.length > 0 ? "\n" + optionalProperties.join("\n") : ""}
866
- >`;
964
+ return fileSet.toString();
867
965
  }
868
966
  }
869
967
 
968
+ /**
969
+ * @class
970
+ * A translation for the mission title and description.
971
+ */
870
972
  export class AeroflyLocalizedText {
871
973
  /**
872
974
  * @property {string} language ISO 639-1 like
@@ -923,14 +1025,18 @@ export class AeroflyLocalizedText {
923
1025
  * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
924
1026
  */
925
1027
  toString(index: number = 0): string {
926
- return ` <[tmmission_definition_localized][element][${index}]
927
- <[string8u][language][${this.language}]>
928
- <[string8][title][${this.title}]>
929
- <[string8][description][${this.description}]>
930
- >`;
1028
+ return new AeroflyConfigFileSet(4, "tmmission_definition_localized", "element", String(index))
1029
+ .push("string8u", "language", this.language)
1030
+ .push("string8", "title", this.title)
1031
+ .push("string8", "description", this.description)
1032
+ .toString();
931
1033
  }
932
1034
  }
933
1035
 
1036
+ /**
1037
+ * @class
1038
+ * A target plane which the aircraft needs to cross.
1039
+ */
934
1040
  export class AeroflyMissionTargetPlane {
935
1041
  /**
936
1042
  * @property {number} longitude easting, using the World Geodetic
@@ -975,10 +1081,10 @@ export class AeroflyMissionTargetPlane {
975
1081
  }
976
1082
 
977
1083
  toString(): string {
978
- return ` <[tmmission_target_plane][${this.name}][]
979
- <[vector2_float64][lon_lat][${this.longitude} ${this.latitude}]>
980
- <[float64][direction][${this.dir}]>
981
- >`;
1084
+ return new AeroflyConfigFileSet(4, "tmmission_target_plane", this.name)
1085
+ .push("vector2_float64", "lon_lat", [this.longitude, this.latitude])
1086
+ .push("float64", "direction", this.dir)
1087
+ .toString();
982
1088
  }
983
1089
  }
984
1090