@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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.2
4
+
5
+ - Added altitude constraint property
6
+ - Improved handling of cloud layers
7
+
8
+ ## 1.2.1
9
+
10
+ - Changed handling of checkpoints, as it is obviously possible to have missions without checkpoints
11
+ - Changed actual file generation to improve programmatic adding of entries to file
12
+ - Adding `is_scheduled` and `tutorial_name` property
13
+ - Improved cloud handling
14
+ - Improved handling off unset values
15
+ - Added `cold_and_dark`, `before_start`, `pushback"` as flight setting
16
+
3
17
  ## 1.2.0
4
18
 
5
19
  - Adding new cloud level `cirrus_cover` / `cirrus_base`
@@ -13,7 +27,7 @@
13
27
  ## 1.1.0
14
28
 
15
29
  - Added new mission properties `tags`, `isFeatured`, `difficulty`, `distance`, `duration` and translations
16
- - Added new flight settings `"winch_launch`, `aerotow`
30
+ - Added new flight settings `winch_launch`, `aerotow`
17
31
  - Improved temperature property
18
32
 
19
33
  ## 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,17 +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";
368
- switch (index) {
369
- case 1:
370
- indexString = "cirrus";
371
- break;
372
- case 2:
373
- indexString = "cumulus_mediocris";
374
- break;
375
- }
376
- return ` <[float64][${indexString}_cover][${this.cover ?? 0}]> // ${this.cover_code}
377
- <[float64][${indexString}_base][${this.base}]> // ${this.base_feet} ft AGL`;
420
+ const getIndexString = (index) => {
421
+ switch (index) {
422
+ case 0:
423
+ return "cloud";
424
+ case 1:
425
+ return "cirrus";
426
+ case 2:
427
+ return "cumulus_mediocris";
428
+ default:
429
+ return "more_clouds";
430
+ }
431
+ };
432
+ const indexString = getIndexString(index);
433
+ const comment = index > 1 ? '//' : '';
434
+ return `\
435
+ ${comment}<[float64][${indexString}_cover][${this.cover ?? 0}]> // ${this.cover_code}
436
+ ${comment}<[float64][${indexString}_base][${this.base}]> // ${this.base_feet} ft AGL`;
378
437
  }
379
438
  }
380
439
  /**
@@ -399,21 +458,25 @@ export class AeroflyMissionCheckpoint {
399
458
  * @param {object} additionalAttributes allows to set additional attributes on creation
400
459
  * @param {number} [additionalAttributes.altitude] The height in meters above or below the WGS
401
460
  * 84 reference ellipsoid
402
- * @param {number?} [additionalAttributes.altitude_feet] The height in feet above or below the WGS
461
+ * @param {?number} [additionalAttributes.altitude_feet] The height in feet above or below the WGS
403
462
  * 84 reference ellipsoid. Will overwrite altitude
404
- * @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
463
+ * @param {number} [additionalAttributes.altitudeConstraint] The altitude given in `altitude`
464
+ * will be interpreted as mandatory flight plan altitude instead of
465
+ * suggestion.
466
+ * @param {boolean} [additionalAttributes.direction] of runway, in degree
467
+ * @param {?number} [additionalAttributes.slope] of runway
468
+ * @param {?number} [additionalAttributes.length] of runway, in meters
469
+ * @param {?number} [additionalAttributes.length_feet] of runway, in feet. Will overwrite length
470
+ * @param {?number} [additionalAttributes.frequency] of runways or navigational aids, in Hz; multiply by 1000 for kHz, 1_000_000 for MHz
471
+ * @param {?boolean} [additionalAttributes.flyOver] if waypoint is meant to be flown over
410
472
  */
411
- constructor(name, type, longitude, latitude, { altitude = 0, altitude_feet = null, direction = null, slope = null, length = null, length_feet = null, frequency = null, flyOver = undefined, } = {}) {
473
+ 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, } = {}) {
412
474
  this.type = type;
413
475
  this.name = name;
414
476
  this.longitude = longitude;
415
477
  this.latitude = latitude;
416
478
  this.altitude = altitude;
479
+ this.altitudeConstraint = altitudeConstraint;
417
480
  this.direction = direction;
418
481
  this.slope = slope;
419
482
  this.length = length;
@@ -457,11 +520,11 @@ export class AeroflyMissionCheckpoint {
457
520
  if (!this.frequency) {
458
521
  return "None";
459
522
  }
460
- if (this.frequency > 1_000_000) {
461
- return String(this.frequency / 1_000_000) + " MHz";
523
+ if (this.frequency > 1000000) {
524
+ return String(this.frequency / 1000000) + " MHz";
462
525
  }
463
- if (this.frequency > 1_000) {
464
- return String(this.frequency / 1_000) + " kHz";
526
+ if (this.frequency > 1000) {
527
+ return String(this.frequency / 1000) + " kHz";
465
528
  }
466
529
  return String(this.frequency) + " Hz";
467
530
  }
@@ -470,26 +533,32 @@ export class AeroflyMissionCheckpoint {
470
533
  * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
471
534
  */
472
535
  toString(index = 0) {
473
- const optionalProperties = [];
536
+ const fileSet = new AeroflyConfigFileSet(5, "tmmission_checkpoint", "element", String(index))
537
+ .push("string8u", "type", this.type)
538
+ .push("string8u", "name", this.name)
539
+ .push("vector2_float64", "lon_lat", [this.longitude, this.latitude])
540
+ .push("float64", "altitude", this.altitude, `${Math.ceil(this.altitude_feet)} ft`)
541
+ .push("float64", "direction", this.direction ?? (index === 0 ? -1 : 0))
542
+ .push("float64", "slope", this.slope ?? 0);
543
+ if (this.altitudeConstraint !== null) {
544
+ fileSet.push("bool", "alt_cst", this.altitudeConstraint);
545
+ }
474
546
  if (this.length) {
475
- optionalProperties.push(` <[float64][length][${this.length ?? 0}]> // ${Math.floor(this.length_feet)} ft`);
547
+ fileSet.push("float64", "length", this.length ?? 0, `${Math.floor(this.length_feet)} ft`);
476
548
  }
477
549
  if (this.frequency) {
478
- optionalProperties.push(` <[float64][frequency][${this.frequency ?? 0}]> // ${this.frequency_string}`);
550
+ fileSet.push("float64", "frequency", this.frequency ?? 0, `${this.frequency_string}`);
479
551
  }
480
- if (this.flyOver !== undefined) {
481
- optionalProperties.push(` <[bool][fly_over][${this.flyOver ? "true" : "false"}]>`);
552
+ if (this.flyOver !== null) {
553
+ fileSet.push("bool", "fly_over", this.flyOver);
482
554
  }
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
- >`;
555
+ return fileSet.toString();
491
556
  }
492
557
  }
558
+ /**
559
+ * @class
560
+ * A translation for the mission title and description.
561
+ */
493
562
  export class AeroflyLocalizedText {
494
563
  /**
495
564
  * @param {string} language ISO 639-1 like
@@ -518,13 +587,17 @@ export class AeroflyLocalizedText {
518
587
  * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
519
588
  */
520
589
  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
- >`;
590
+ return new AeroflyConfigFileSet(4, "tmmission_definition_localized", "element", String(index))
591
+ .push("string8u", "language", this.language)
592
+ .push("string8", "title", this.title)
593
+ .push("string8", "description", this.description)
594
+ .toString();
526
595
  }
527
596
  }
597
+ /**
598
+ * @class
599
+ * A target plane which the aircraft needs to cross.
600
+ */
528
601
  export class AeroflyMissionTargetPlane {
529
602
  /**
530
603
  *
@@ -544,10 +617,10 @@ export class AeroflyMissionTargetPlane {
544
617
  this.name = name;
545
618
  }
546
619
  toString() {
547
- return ` <[tmmission_target_plane][${this.name}][]
548
- <[vector2_float64][lon_lat][${this.longitude} ${this.latitude}]>
549
- <[float64][direction][${this.dir}]>
550
- >`;
620
+ return new AeroflyConfigFileSet(4, "tmmission_target_plane", this.name)
621
+ .push("vector2_float64", "lon_lat", [this.longitude, this.latitude])
622
+ .push("float64", "direction", this.dir)
623
+ .toString();
551
624
  }
552
625
  }
553
626
  export default {