@fboes/aerofly-custom-missions 1.2.0 → 1.2.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.1
4
+
5
+ - Changed handling of checkpoints, as it is obviously possible to have missions without checkpoints
6
+ - Changed actual file generation to improve programmatic adding of entries to file
7
+ - Adding `is_scheduled` and `tutorial_name` property
8
+ - Improved cloud handling
9
+ - Improved handling off unset values
10
+ - Added `cold_and_dark`, `before_start`, `pushback"` as flight setting
11
+
3
12
  ## 1.2.0
4
13
 
5
14
  - Adding new cloud level `cirrus_cover` / `cirrus_base`
@@ -13,7 +22,7 @@
13
22
  ## 1.1.0
14
23
 
15
24
  - Added new mission properties `tags`, `isFeatured`, `difficulty`, `distance`, `duration` and translations
16
- - Added new flight settings `"winch_launch`, `aerotow`
25
+ - Added new flight settings `winch_launch`, `aerotow`
17
26
  - Improved temperature property
18
27
 
19
28
  ## 1.0.4
package/README.md CHANGED
@@ -171,19 +171,14 @@ The `tags` property of a single mission can contain multiple values. The followi
171
171
  - `destination`
172
172
  - Be aware that all units for altitude, elevation or distance are measured in meters! In most cases there will be helper functions for defining these values in feet (for length, altitude, elevation) or statute miles (for visibility).
173
173
 
174
- There are also some unsupported properties, which are present in Aerofly FS4, but as of yet will not be generated by this library:
175
-
176
- ```
177
- <[string8][tutorial_name][c172]> // Opens https://www.aerofly.com/aircraft-tutorials/${tutorial_name}
178
- <[bool][is_scheduled][true]>
179
- ```
180
-
181
174
  ### Known issues
182
175
 
183
176
  - `AeroflyMissionConditions.time`: Even though a date property is available in Aerofly FS 4, [the custom missions cannot change the date in Aerofly FS 4](https://www.aerofly.com/community/forum/index.php?thread/22487-more-settings-for-environment-conditions/&pageNo=1). This is obviously as the date cannot be changed inside the simulation.
184
177
  - `AeroflyMissionConditions.clouds`: Even though Aerofly FS 4 is able to handle three levels of clouds, the custom missions can only set two levels in Aerofly FS 4. This is obviously as the third cloud layer cannot be changed inside the simulation.
185
178
  - `AeroflyMissionCheckpoint.type`: Even though internal flight plans of Aerofly FS 4 have types `"departure"|"arrival"|"approach"`, [Aerofly FS 4 dumps SIDs and STARs on loading a custom missions](https://www.aerofly.com/community/forum/index.php?thread/22156-flight-plans/).
186
179
  - `AeroflyMission.aircraft.livery`: Even though Aerofly FS 4 knows multiple liveries per plane, [the custom missions file is not able to set liveries to any other but the standard livery](https://www.aerofly.com/community/forum/index.php?thread/19105-user-created-custom-missions/).
180
+ - `AeroflyMission.isScheduled`: Even though for regular missions this will mark these as a "Scheduled flight", [custom missions cannot be created as "Scheduled flights"](https://www.aerofly.com/community/forum/index.php?thread/23101-challenge-guides/&postID=147354#post147432).
181
+ - `AeroflyMission.isFeatured`: Even though for regular missions this will mark these as a featured missions, custom missions cannot be created as featured missions.
187
182
 
188
183
  ## Status
189
184
 
package/dist/index.js CHANGED
@@ -1,5 +1,48 @@
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;
1
13
  const feetPerMeter = 3.28084;
2
14
  const meterPerStatuteMile = 1609.344;
15
+ export class AeroflyConfigFileSet {
16
+ constructor(indent, type, name, value = "") {
17
+ _AeroflyConfigFileSet_indent.set(this, void 0);
18
+ __classPrivateFieldSet(this, _AeroflyConfigFileSet_indent, indent, "f");
19
+ this.elements = [`${this.indent}<[${type}][${name}][${value}]`];
20
+ }
21
+ get indent() {
22
+ return " ".repeat(__classPrivateFieldGet(this, _AeroflyConfigFileSet_indent, "f"));
23
+ }
24
+ push(type, name, value, comment = "") {
25
+ if (value instanceof Array) {
26
+ value = value.join(" ");
27
+ }
28
+ else if (typeof value === "boolean") {
29
+ value = value ? "true" : "false";
30
+ }
31
+ let tag = `${this.indent} <[${type}][${name}][${value}]>`;
32
+ if (comment) {
33
+ tag += ` // ${comment}`;
34
+ }
35
+ return this.pushRaw(tag);
36
+ }
37
+ pushRaw(s) {
38
+ this.elements.push(s);
39
+ return this;
40
+ }
41
+ toString() {
42
+ return this.elements.join("\n") + "\n" + `${this.indent}>`;
43
+ }
44
+ }
45
+ _AeroflyConfigFileSet_indent = new WeakMap();
3
46
  /**
4
47
  * @class
5
48
  * A list of flight plans.
@@ -20,7 +63,8 @@ export class AeroflyMissionsList {
20
63
  */
21
64
  toString() {
22
65
  const separator = "\n// -----------------------------------------------------------------------------\n";
23
- return `<[file][][]
66
+ return `\
67
+ <[file][][]
24
68
  <[tmmissions_list][][]
25
69
  <[list_tmmission_definition][missions][]${separator + this.missions.join(separator) + separator} >
26
70
  >
@@ -41,21 +85,23 @@ export class AeroflyMission {
41
85
  * @param {object} [additionalAttributes] allows to set additional attributes on creation
42
86
  * @param {string} [additionalAttributes.description] text, mission briefing, etc
43
87
  * @param {AeroflyLocalizedText[]} [additionalAttributes.localizedTexts] translations for title and description
88
+ * @param {?string} [additionalAttributes.tutorialName] will create a link to a tutorial page at https://www.aerofly.com/aircraft-tutorials/
44
89
  * @param {string[]} [additionalAttributes.tags]
45
- * @param {boolean} [additionalAttributes.isFeatured] makes this mission pop up in "Challenges"
46
- * @param {number|undefined} [additionalAttributes.difficulty] values between 0.00 and 2.00 have been encountered, but they seem to be without limit
47
- * @param {"taxi"|"takeoff"|"cruise"|"approach"|"landing"|"winch_launch"|"aerotow"} [additionalAttributes.flightSetting] of aircraft, like "taxi", "cruise"
90
+ * @param {?boolean} [additionalAttributes.isFeatured] makes this mission pop up in "Challenges"
91
+ * @param {?number} [additionalAttributes.difficulty] values between 0.00 and 2.00 have been encountered, but they seem to be without limit
92
+ * @param {"cold_and_dark"|"before_start"|"taxi"|"takeoff"|"cruise"|"approach"|"landing"|"winch_launch"|"aerotow"|"pushback"} [additionalAttributes.flightSetting] of aircraft, like "taxi", "cruise"
48
93
  * @param {{name:string,livery:string,icao:string}} [additionalAttributes.aircraft] for this mission
49
94
  * @param {string} [additionalAttributes.callsign] of aircraft, uppercased
50
95
  * @param {object} [additionalAttributes.origin] position of aircraft, as well as name of starting airport. Position does not have match airport.
51
96
  * @param {object} [additionalAttributes.destination] position of aircraft, as well as name of destination airport. Position does not have match airport.
52
- * @param {number} [additionalAttributes.distance] in meters
53
- * @param {number} [additionalAttributes.duration] in seconds
97
+ * @param {?number} [additionalAttributes.distance] in meters
98
+ * @param {?number} [additionalAttributes.duration] in seconds
99
+ * @param {?boolean} [additionalAttributes.isScheduled] marks this flight as "Scheduled flight".
54
100
  * @param {?AeroflyMissionTargetPlane} [additionalAttributes.finish] as finish condition
55
101
  * @param {AeroflyMissionConditions} [additionalAttributes.conditions] like time and weather for mission
56
102
  * @param {AeroflyMissionCheckpoint[]} [additionalAttributes.checkpoints] form the actual flight plan
57
103
  */
58
- constructor(title, { description = "", localizedTexts = [], tags = [], isFeatured = false, difficulty = undefined, flightSetting = "taxi", aircraft = {
104
+ constructor(title, { tutorialName = null, description = "", localizedTexts = [], tags = [], isFeatured = null, difficulty = null, flightSetting = "taxi", aircraft = {
59
105
  name: "c172",
60
106
  icao: "",
61
107
  livery: "",
@@ -71,7 +117,8 @@ export class AeroflyMission {
71
117
  latitude: 0,
72
118
  dir: 0,
73
119
  alt: 0,
74
- }, distance = 0, duration = 0, finish = null, conditions = new AeroflyMissionConditions(), checkpoints = [], } = {}) {
120
+ }, distance = null, duration = null, isScheduled = null, finish = null, conditions = new AeroflyMissionConditions(), checkpoints = [], } = {}) {
121
+ this.tutorialName = tutorialName;
75
122
  this.title = title;
76
123
  this.checkpoints = checkpoints;
77
124
  this.description = description;
@@ -86,6 +133,7 @@ export class AeroflyMission {
86
133
  this.destination = destination;
87
134
  this.distance = distance;
88
135
  this.duration = duration;
136
+ this.isScheduled = isScheduled;
89
137
  this.finish = finish;
90
138
  this.conditions = conditions;
91
139
  }
@@ -114,9 +162,6 @@ export class AeroflyMission {
114
162
  * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
115
163
  */
116
164
  toString() {
117
- if (this.checkpoints.length < 2) {
118
- throw Error("this.checkpoints.length < 2");
119
- }
120
165
  if (!this.origin.icao) {
121
166
  const firstCheckpoint = this.checkpoints[0];
122
167
  this.origin = {
@@ -137,52 +182,60 @@ export class AeroflyMission {
137
182
  alt: lastCheckpoint.altitude,
138
183
  };
139
184
  }
140
- const optionalProperties = [];
185
+ const fileSet = new AeroflyConfigFileSet(3, "tmmission_definition", "mission");
186
+ fileSet.push("string8", "title", this.title);
187
+ fileSet.push("string8", "description", this.description);
188
+ if (this.tutorialName !== null) {
189
+ fileSet.push("string8", "tutorial_name", this.tutorialName, `Opens https://www.aerofly.com/aircraft-tutorials/${this.tutorialName}`);
190
+ }
141
191
  if (this.localizedTexts.length > 0) {
142
- optionalProperties.push(` <[list_tmmission_definition_localized][localized_text][]
143
- ${this.getLocalizedTextsString()}
144
- >`);
192
+ fileSet.pushRaw(new AeroflyConfigFileSet(4, "list_tmmission_definition_localized", "localized_text")
193
+ .pushRaw(this.getLocalizedTextsString())
194
+ .toString());
145
195
  }
146
196
  if (this.tags.length > 0) {
147
- optionalProperties.push(` <[string8u][tags][ ${this.tags.join(" ")} ]>`);
197
+ fileSet.push("string8u", "tags", this.tags.join(" "));
198
+ }
199
+ if (this.difficulty !== null) {
200
+ fileSet.push("float64", "difficulty", this.difficulty);
201
+ }
202
+ if (this.isFeatured !== null) {
203
+ fileSet.push("bool", "is_featured", this.isFeatured);
148
204
  }
149
- if (this.difficulty !== undefined) {
150
- optionalProperties.push(` <[float64] [difficulty] [${this.difficulty}]>`);
205
+ fileSet.push("string8", "flight_setting", this.flightSetting);
206
+ fileSet.push("string8u", "aircraft_name", this.aircraft.name);
207
+ /*if (this.aircraft.livery) {
208
+ fileSet.push("string8", "aircraft_livery", this.aircraft.livery);
209
+ }*/
210
+ fileSet.push("stringt8c", "aircraft_icao", this.aircraft.icao);
211
+ fileSet.push("stringt8c", "callsign", this.callsign);
212
+ fileSet.push("stringt8c", "origin_icao", this.origin.icao);
213
+ fileSet.push("tmvector2d", "origin_lon_lat", [this.origin.longitude, this.origin.latitude]);
214
+ fileSet.push("float64", "origin_alt", this.origin.alt, `${Math.ceil(this.origin.alt * feetPerMeter)} ft MSL`);
215
+ fileSet.push("float64", "origin_dir", this.origin.dir);
216
+ fileSet.push("stringt8c", "destination_icao", this.destination.icao);
217
+ fileSet.push("tmvector2d", "destination_lon_lat", [this.destination.longitude, this.destination.latitude]);
218
+ fileSet.push("float64", "destination_alt", this.destination.alt, `${Math.ceil(this.destination.alt * feetPerMeter)} ft MSL`);
219
+ fileSet.push("float64", "destination_dir", this.destination.dir);
220
+ if (this.distance !== null) {
221
+ fileSet.push("float64", "distance", this.distance, `${Math.round(this.distance / 1000)} km`);
151
222
  }
152
- if (this.isFeatured) {
153
- optionalProperties.push(` <[bool] [is_featured] [${this.isFeatured ? "true" : "false"}]>`);
223
+ if (this.duration !== null) {
224
+ fileSet.push("float64", "duration", this.duration, `${Math.round(this.duration / 60)} min`);
154
225
  }
155
- const moreOptionalProperties = [];
156
- if (this.distance) {
157
- moreOptionalProperties.push(` <[float64] [distance] [${this.distance}]> // ${Math.ceil(this.distance / 1000)} km`);
226
+ if (this.isScheduled !== null) {
227
+ fileSet.push("bool", "is_scheduled", this.isScheduled ? "true" : "false");
158
228
  }
159
- if (this.duration) {
160
- moreOptionalProperties.push(` <[float64] [duration] [${this.duration}]> // ${Math.ceil(this.duration / 60)} min`);
229
+ if (this.finish !== null) {
230
+ fileSet.pushRaw(this.finish.toString());
161
231
  }
162
- if (this.finish) {
163
- moreOptionalProperties.push(this.finish.toString());
232
+ fileSet.pushRaw(this.conditions.toString());
233
+ if (this.checkpoints.length > 0) {
234
+ fileSet.pushRaw(new AeroflyConfigFileSet(4, "list_tmmission_checkpoint", "checkpoints")
235
+ .pushRaw(this.getCheckpointsString())
236
+ .toString());
164
237
  }
165
- return ` <[tmmission_definition][mission][]
166
- <[string8][title][${this.title}]>
167
- <[string8][description][${this.description}]>${optionalProperties.length > 0 ? "\n" + optionalProperties.join("\n") : ""}
168
- <[string8] [flight_setting] [${this.flightSetting}]>
169
- <[string8u] [aircraft_name] [${this.aircraft.name}]>
170
- //<[string8u][aircraft_livery] [${this.aircraft.livery}]>
171
- <[stringt8c] [aircraft_icao] [${this.aircraft.icao}]>
172
- <[stringt8c] [callsign] [${this.callsign}]>
173
- <[stringt8c] [origin_icao] [${this.origin.icao}]>
174
- <[tmvector2d][origin_lon_lat] [${this.origin.longitude} ${this.origin.latitude}]>
175
- <[float64] [origin_alt] [${this.origin.alt}]> // ${Math.ceil(this.origin.alt * feetPerMeter)} ft MSL
176
- <[float64] [origin_dir] [${this.origin.dir}]>
177
- <[stringt8c] [destination_icao] [${this.destination.icao}]>
178
- <[tmvector2d][destination_lon_lat][${this.destination.longitude} ${this.destination.latitude}]>
179
- <[float64] [destination_alt] [${this.destination.alt}]> // ${Math.ceil(this.destination.alt * feetPerMeter)} ft MSL
180
- <[float64] [destination_dir] [${this.destination.dir}]>${moreOptionalProperties.length > 0 ? "\n" + moreOptionalProperties.join("\n") : ""}
181
- ${this.conditions}
182
- <[list_tmmission_checkpoint][checkpoints][]
183
- ${this.getCheckpointsString()}
184
- >
185
- >`;
238
+ return fileSet.toString();
186
239
  }
187
240
  }
188
241
  /**
@@ -202,15 +255,15 @@ export class AeroflyMissionConditions {
202
255
  * @param {number} [additionalAttributes.turbulenceStrength] 0..1, percentage
203
256
  * @param {number} [additionalAttributes.thermalStrength] 0..1, percentage
204
257
  * @param {number} [additionalAttributes.visibility] in meters
205
- * @param {number?} [additionalAttributes.visibility_sm] in statute miles, will overwrite visibility
206
- * @param {number?} [additionalAttributes.temperature] in °C, will overwrite thermalStrength
258
+ * @param {?number} [additionalAttributes.visibility_sm] in statute miles, will overwrite visibility
259
+ * @param {?number} [additionalAttributes.temperature] in °C, will overwrite thermalStrength
207
260
  * @param {AeroflyMissionConditionsCloud[]} [additionalAttributes.clouds] for the whole flight
208
261
  */
209
262
  constructor({ time = new Date(), wind = {
210
263
  direction: 0,
211
264
  speed: 0,
212
265
  gusts: 0,
213
- }, turbulenceStrength = 0, thermalStrength = 0, visibility = 25_000, visibility_sm = null, temperature = null, clouds = [], } = {}) {
266
+ }, turbulenceStrength = 0, thermalStrength = 0, visibility = 25000, visibility_sm = null, temperature = null, clouds = [], } = {}) {
214
267
  /**
215
268
  * @property {AeroflyMissionConditionsCloud[]} clouds for the whole flight
216
269
  */
@@ -287,21 +340,21 @@ export class AeroflyMissionConditions {
287
340
  if (this.clouds.length < 1) {
288
341
  this.clouds = [new AeroflyMissionConditionsCloud(0, 0)];
289
342
  }
290
- return ` <[tmmission_conditions][conditions][]
291
- <[tm_time_utc][time][]
292
- <[int32][time_year][${this.time.getUTCFullYear()}]>
293
- <[int32][time_month][${this.time.getUTCMonth() + 1}]>
294
- <[int32][time_day][${this.time.getUTCDate()}]>
295
- <[float64][time_hours][${this.time_hours}]> // ${this.time_presentational} UTC
296
- >
297
- <[float64][wind_direction][${this.wind.direction}]>
298
- <[float64][wind_speed][${this.wind.speed}]> // kts
299
- <[float64][wind_gusts][${this.wind.gusts}]> // kts
300
- <[float64][turbulence_strength][${this.turbulenceStrength}]>
301
- <[float64][thermal_strength][${this.thermalStrength}]> // ${this.temperature} °C
302
- <[float64][visibility][${this.visibility}]> // ${this.visibility_sm} SM
303
- ${this.getCloudsString()}
304
- >`;
343
+ return new AeroflyConfigFileSet(4, "tmmission_conditions", "conditions")
344
+ .pushRaw(new AeroflyConfigFileSet(5, "tm_time_utc", "time")
345
+ .push("int32", "time_year", this.time.getUTCFullYear())
346
+ .push("int32", "time_month", this.time.getUTCMonth() + 1)
347
+ .push("int32", "time_day", this.time.getUTCDate())
348
+ .push("float64", "time_hours", this.time_hours, `${this.time_presentational} UTC`)
349
+ .toString())
350
+ .push("float64", "wind_direction", this.wind.direction)
351
+ .push("float64", "wind_speed", this.wind.speed, "kts")
352
+ .push("float64", "wind_gusts", this.wind.gusts, "kts")
353
+ .push("float64", "turbulence_strength", this.turbulenceStrength)
354
+ .push("float64", "thermal_strength", this.thermalStrength, `${this.temperature} °C`)
355
+ .push("float64", "visibility", this.visibility, `${this.visibility_sm} SM`)
356
+ .pushRaw(this.getCloudsString())
357
+ .toString();
305
358
  }
306
359
  }
307
360
  /**
@@ -364,16 +417,23 @@ export class AeroflyMissionConditionsCloud {
364
417
  * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
365
418
  */
366
419
  toString(index = 0) {
367
- let indexString = "cloud";
420
+ let indexString = "";
368
421
  switch (index) {
422
+ case 0:
423
+ indexString = "cloud";
424
+ break;
369
425
  case 1:
370
426
  indexString = "cirrus";
371
427
  break;
372
428
  case 2:
373
429
  indexString = "cumulus_mediocris";
374
430
  break;
431
+ default:
432
+ return "";
375
433
  }
376
- return ` <[float64][${indexString}_cover][${this.cover ?? 0}]> // ${this.cover_code}
434
+ // TODO
435
+ return `\
436
+ <[float64][${indexString}_cover][${this.cover ?? 0}]> // ${this.cover_code}
377
437
  <[float64][${indexString}_base][${this.base}]> // ${this.base_feet} ft AGL`;
378
438
  }
379
439
  }
@@ -399,16 +459,16 @@ export class AeroflyMissionCheckpoint {
399
459
  * @param {object} additionalAttributes allows to set additional attributes on creation
400
460
  * @param {number} [additionalAttributes.altitude] The height in meters above or below the WGS
401
461
  * 84 reference ellipsoid
402
- * @param {number?} [additionalAttributes.altitude_feet] The height in feet above or below the WGS
462
+ * @param {?number} [additionalAttributes.altitude_feet] The height in feet above or below the WGS
403
463
  * 84 reference ellipsoid. Will overwrite altitude
404
464
  * @param {number} [additionalAttributes.direction] of runway, in degree
405
- * @param {number?} [additionalAttributes.slope] of runway
406
- * @param {number?} [additionalAttributes.length] of runway, in meters
407
- * @param {number?} [additionalAttributes.length_feet] of runway, in feet. Will overwrite length
408
- * @param {number?} [additionalAttributes.frequency] of runways or navigational aids, in Hz; multiply by 1000 for kHz, 1_000_000 for MHz
409
- * @param {boolean|undefined} [additionalAttributes.flyOver] if waypoint is meant to be flown over
465
+ * @param {?number} [additionalAttributes.slope] of runway
466
+ * @param {?number} [additionalAttributes.length] of runway, in meters
467
+ * @param {?number} [additionalAttributes.length_feet] of runway, in feet. Will overwrite length
468
+ * @param {?number} [additionalAttributes.frequency] of runways or navigational aids, in Hz; multiply by 1000 for kHz, 1_000_000 for MHz
469
+ * @param {?boolean} [additionalAttributes.flyOver] if waypoint is meant to be flown over
410
470
  */
411
- constructor(name, type, longitude, latitude, { altitude = 0, altitude_feet = null, direction = null, slope = null, length = null, length_feet = null, frequency = null, flyOver = undefined, } = {}) {
471
+ constructor(name, type, longitude, latitude, { altitude = 0, altitude_feet = null, direction = null, slope = null, length = null, length_feet = null, frequency = null, flyOver = null, } = {}) {
412
472
  this.type = type;
413
473
  this.name = name;
414
474
  this.longitude = longitude;
@@ -457,11 +517,11 @@ export class AeroflyMissionCheckpoint {
457
517
  if (!this.frequency) {
458
518
  return "None";
459
519
  }
460
- if (this.frequency > 1_000_000) {
461
- return String(this.frequency / 1_000_000) + " MHz";
520
+ if (this.frequency > 1000000) {
521
+ return String(this.frequency / 1000000) + " MHz";
462
522
  }
463
- if (this.frequency > 1_000) {
464
- return String(this.frequency / 1_000) + " kHz";
523
+ if (this.frequency > 1000) {
524
+ return String(this.frequency / 1000) + " kHz";
465
525
  }
466
526
  return String(this.frequency) + " Hz";
467
527
  }
@@ -470,26 +530,29 @@ export class AeroflyMissionCheckpoint {
470
530
  * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
471
531
  */
472
532
  toString(index = 0) {
473
- const optionalProperties = [];
533
+ const fileSet = new AeroflyConfigFileSet(5, "tmmission_checkpoint", "element", String(index))
534
+ .push("string8u", "type", this.type)
535
+ .push("string8u", "name", this.name)
536
+ .push("vector2_float64", "lon_lat", [this.longitude, this.latitude])
537
+ .push("float64", "altitude", this.altitude, `${Math.ceil(this.altitude_feet)} ft`)
538
+ .push("float64", "direction", this.direction ?? (index === 0 ? -1 : 0))
539
+ .push("float64", "slope", this.slope ?? 0);
474
540
  if (this.length) {
475
- optionalProperties.push(` <[float64][length][${this.length ?? 0}]> // ${Math.floor(this.length_feet)} ft`);
541
+ fileSet.push("float64", "length", this.length ?? 0, `${Math.floor(this.length_feet)} ft`);
476
542
  }
477
543
  if (this.frequency) {
478
- optionalProperties.push(` <[float64][frequency][${this.frequency ?? 0}]> // ${this.frequency_string}`);
544
+ fileSet.push("float64", "frequency", this.frequency ?? 0, `${this.frequency_string}`);
479
545
  }
480
- if (this.flyOver !== undefined) {
481
- optionalProperties.push(` <[bool][fly_over][${this.flyOver ? "true" : "false"}]>`);
546
+ if (this.flyOver !== null) {
547
+ fileSet.push("bool", "fly_over", this.flyOver);
482
548
  }
483
- return ` <[tmmission_checkpoint][element][${index}]
484
- <[string8u][type][${this.type}]>
485
- <[string8u][name][${this.name}]>
486
- <[vector2_float64][lon_lat][${this.longitude} ${this.latitude}]>
487
- <[float64][altitude][${this.altitude}]> // ${Math.ceil(this.altitude_feet)} ft
488
- <[float64][direction][${this.direction ?? (index === 0 ? -1 : 0)}]>
489
- <[float64][slope][${this.slope ?? 0}]>${optionalProperties.length > 0 ? "\n" + optionalProperties.join("\n") : ""}
490
- >`;
549
+ return fileSet.toString();
491
550
  }
492
551
  }
552
+ /**
553
+ * @class
554
+ * A translation for the mission title and description.
555
+ */
493
556
  export class AeroflyLocalizedText {
494
557
  /**
495
558
  * @param {string} language ISO 639-1 like
@@ -518,13 +581,17 @@ export class AeroflyLocalizedText {
518
581
  * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
519
582
  */
520
583
  toString(index = 0) {
521
- return ` <[tmmission_definition_localized][element][${index}]
522
- <[string8u][language][${this.language}]>
523
- <[string8][title][${this.title}]>
524
- <[string8][description][${this.description}]>
525
- >`;
584
+ return new AeroflyConfigFileSet(4, "tmmission_definition_localized", "element", String(index))
585
+ .push("string8u", "language", this.language)
586
+ .push("string8", "title", this.title)
587
+ .push("string8", "description", this.description)
588
+ .toString();
526
589
  }
527
590
  }
591
+ /**
592
+ * @class
593
+ * A target plane which the aircraft needs to cross.
594
+ */
528
595
  export class AeroflyMissionTargetPlane {
529
596
  /**
530
597
  *
@@ -544,10 +611,10 @@ export class AeroflyMissionTargetPlane {
544
611
  this.name = name;
545
612
  }
546
613
  toString() {
547
- return ` <[tmmission_target_plane][${this.name}][]
548
- <[vector2_float64][lon_lat][${this.longitude} ${this.latitude}]>
549
- <[float64][direction][${this.dir}]>
550
- >`;
614
+ return new AeroflyConfigFileSet(4, "tmmission_target_plane", this.name)
615
+ .push("vector2_float64", "lon_lat", [this.longitude, this.latitude])
616
+ .push("float64", "direction", this.dir)
617
+ .toString();
551
618
  }
552
619
  }
553
620
  export default {