@fboes/aerofly-custom-missions 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.eslintrc.json +24 -0
  2. package/CHANGELOG.md +4 -0
  3. package/dist/dto/AeroflyConfigFileSet.js +43 -0
  4. package/dist/dto/AeroflyLocalizedText.js +40 -0
  5. package/dist/dto/AeroflyMission.js +171 -0
  6. package/dist/dto/AeroflyMissionCheckpoint.js +121 -0
  7. package/dist/dto/AeroflyMissionConditions.js +122 -0
  8. package/dist/dto/AeroflyMissionConditionsCloud.js +80 -0
  9. package/dist/dto/AeroflyMissionTargetPlane.js +30 -0
  10. package/dist/dto/AeroflyMissionsList.js +28 -0
  11. package/dist/index.js +7 -634
  12. package/dist/index.test.js +12 -5
  13. package/package.json +9 -10
  14. package/src/dto/AeroflyConfigFileSet.ts +35 -0
  15. package/src/dto/AeroflyLocalizedText.ts +69 -0
  16. package/src/dto/AeroflyMission.ts +377 -0
  17. package/src/dto/AeroflyMissionCheckpoint.ts +235 -0
  18. package/src/dto/AeroflyMissionConditions.ts +196 -0
  19. package/src/dto/AeroflyMissionConditionsCloud.ts +100 -0
  20. package/src/dto/AeroflyMissionTargetPlane.ts +56 -0
  21. package/src/dto/AeroflyMissionsList.ts +36 -0
  22. package/src/index.test.ts +8 -10
  23. package/src/index.ts +7 -1099
  24. package/types/AeroflyMissionCheckpoint.d.ts +140 -0
  25. package/types/AeroflyMissionTargetPlane.d.ts +40 -0
  26. package/types/dto/AeroflyConfigFileSet.d.ts +10 -0
  27. package/types/dto/AeroflyConfigFileSet.d.ts.map +1 -0
  28. package/types/dto/AeroflyLocalizedText.d.ts +54 -0
  29. package/types/dto/AeroflyLocalizedText.d.ts.map +1 -0
  30. package/types/dto/AeroflyMission.d.ts +208 -0
  31. package/types/dto/AeroflyMission.d.ts.map +1 -0
  32. package/types/dto/AeroflyMissionCheckpoint.d.ts +152 -0
  33. package/types/dto/AeroflyMissionCheckpoint.d.ts.map +1 -0
  34. package/types/dto/AeroflyMissionConditions.d.ts +116 -0
  35. package/types/dto/AeroflyMissionConditions.d.ts.map +1 -0
  36. package/types/dto/AeroflyMissionConditionsCloud.d.ts +51 -0
  37. package/types/dto/AeroflyMissionConditionsCloud.d.ts.map +1 -0
  38. package/types/dto/AeroflyMissionTargetPlane.d.ts +40 -0
  39. package/types/dto/AeroflyMissionTargetPlane.d.ts.map +1 -0
  40. package/types/dto/AeroflyMissionsList.d.ts +24 -0
  41. package/types/dto/AeroflyMissionsList.d.ts.map +1 -0
  42. package/types/dto/basicTypes.d.ts +1 -0
  43. package/types/index.d.ts +8 -586
  44. package/types/index.d.ts.map +1 -1
  45. package/types/index.test.d.ts +1 -1
  46. package/eslint.config.js +0 -29
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@fboes/aerofly-custom-missions",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Builder for Aerofly FS4 Custom Missions Files",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
7
  "test": "node ./dist/index.test.js",
8
8
  "start": "node ./dist/index.js",
9
9
  "prettier": "npx prettier --cache --write .",
10
- "eslint": "npx eslint **/*.js --fix",
10
+ "eslint": "npx eslint src/*.ts --fix",
11
11
  "tsc": "npx tsc --build",
12
12
  "tsc-watch": "npx tsc --watch",
13
13
  "build": "npm run tsc && npm run eslint && npm run prettier"
@@ -21,15 +21,14 @@
21
21
  "license": "MIT",
22
22
  "type": "module",
23
23
  "devDependencies": {
24
- "@eslint/js": "^9.5.0",
25
- "@types/node": "^20.14.2",
26
- "eslint": "^9.5.0",
24
+ "@types/node": "^20.11.19",
25
+ "@typescript-eslint/eslint-plugin": "^7.0.1",
26
+ "@typescript-eslint/parser": "^7.0.1",
27
+ "eslint": "^8.56.0",
27
28
  "eslint-config-prettier": "^9.1.0",
28
- "eslint-plugin-jsdoc": "^48.2.12",
29
- "eslint-plugin-prettier": "^5.1.3",
30
- "globals": "^15.4.0",
31
- "prettier": "3.3.2",
32
- "typescript": "^5.4.5"
29
+ "eslint-plugin-jsdoc": "^48.1.0",
30
+ "prettier": "3.2.4",
31
+ "typescript": "^5.3.3"
33
32
  },
34
33
  "types": "./types/index.d.ts"
35
34
  }
@@ -0,0 +1,35 @@
1
+ export class AeroflyConfigFileSet {
2
+ #indent: number;
3
+ elements: string[];
4
+
5
+ constructor(indent: number, type: string, name: string, value: string = "") {
6
+ this.#indent = indent;
7
+ this.elements = [`${this.indent}<[${type}][${name}][${value}]`];
8
+ }
9
+
10
+ get indent(): string {
11
+ return " ".repeat(this.#indent);
12
+ }
13
+
14
+ push(type: string, name: string, value: string | number | string[] | number[] | boolean, comment: string = "") {
15
+ if (value instanceof Array) {
16
+ value = value.join(" ");
17
+ } else if (typeof value === "boolean") {
18
+ value = value ? "true" : "false";
19
+ }
20
+ let tag = `${this.indent} <[${type}][${name}][${value}]>`;
21
+ if (comment) {
22
+ tag += ` // ${comment}`;
23
+ }
24
+ return this.pushRaw(tag);
25
+ }
26
+
27
+ pushRaw(s: string) {
28
+ this.elements.push(s);
29
+ return this;
30
+ }
31
+
32
+ toString() {
33
+ return this.elements.join("\n") + "\n" + `${this.indent}>`;
34
+ }
35
+ }
@@ -0,0 +1,69 @@
1
+ import { AeroflyConfigFileSet } from "./AeroflyConfigFileSet.js";
2
+
3
+ /**
4
+ * @class
5
+ * A translation for the mission title and description.
6
+ */
7
+ export class AeroflyLocalizedText {
8
+ /**
9
+ * @property {string} language ISO 639-1 like
10
+ * - br
11
+ * - cn
12
+ * - de
13
+ * - es
14
+ * - fr
15
+ * - id
16
+ * - it
17
+ * - jp
18
+ * - kr
19
+ * - pl
20
+ * - sv
21
+ * - tr
22
+ */
23
+ language: string;
24
+
25
+ /**
26
+ * @property {string} title of this flight plan
27
+ */
28
+ title: string;
29
+
30
+ /**
31
+ * @property {string} description text, mission briefing, etc
32
+ */
33
+ description: string;
34
+
35
+ /**
36
+ * @param {string} language ISO 639-1 like
37
+ * - br
38
+ * - cn
39
+ * - de
40
+ * - es
41
+ * - fr
42
+ * - id
43
+ * - it
44
+ * - jp
45
+ * - kr
46
+ * - pl
47
+ * - sv
48
+ * - tr
49
+ * @param {string} title of this flight plan
50
+ * @param {string} description text, mission briefing, etc
51
+ */
52
+ constructor(language: string, title: string, description: string) {
53
+ this.language = language;
54
+ this.title = title;
55
+ this.description = description;
56
+ }
57
+
58
+ /**
59
+ * @param {number} index if used in an array will se the array index
60
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
61
+ */
62
+ toString(index: number = 0): string {
63
+ return new AeroflyConfigFileSet(4, "tmmission_definition_localized", "element", String(index))
64
+ .push("string8u", "language", this.language)
65
+ .push("string8", "title", this.title)
66
+ .push("string8", "description", this.description)
67
+ .toString();
68
+ }
69
+ }
@@ -0,0 +1,377 @@
1
+ import { AeroflyConfigFileSet } from "./AeroflyConfigFileSet.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 name lowercase Aerofly aircraft ID
13
+ * @property icao ICAO aircraft code
14
+ * @property 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 icao uppercase ICAO airport ID
40
+ * @property 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 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 dir in degree
47
+ * @property 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
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]
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
+ }: {
215
+ tutorialName?: string | null;
216
+ description?: string;
217
+ localizedTexts?: AeroflyLocalizedText[];
218
+ tags?: string[];
219
+ isFeatured?: boolean | null;
220
+ difficulty?: number | null;
221
+ flightSetting?: AeroflyMissionSetting;
222
+ aircraft?: AeroflyMissionAircraft;
223
+ callsign?: string;
224
+ origin?: AeroflyMissionPosition;
225
+ destination?: AeroflyMissionPosition;
226
+ distance?: number | null;
227
+ duration?: number | null;
228
+ isScheduled?: boolean | null;
229
+ finish?: AeroflyMissionTargetPlane | null;
230
+ conditions?: AeroflyMissionConditions;
231
+ checkpoints?: AeroflyMissionCheckpoint[];
232
+ } = {},
233
+ ) {
234
+ this.tutorialName = tutorialName;
235
+ this.title = title;
236
+ this.checkpoints = checkpoints;
237
+ this.description = description;
238
+ this.localizedTexts = localizedTexts;
239
+ this.tags = tags;
240
+ this.isFeatured = isFeatured;
241
+ this.difficulty = difficulty;
242
+ this.flightSetting = flightSetting;
243
+ this.aircraft = aircraft;
244
+ this.callsign = callsign;
245
+ this.origin = origin;
246
+ this.destination = destination;
247
+ this.distance = distance;
248
+ this.duration = duration;
249
+ this.isScheduled = isScheduled;
250
+ this.finish = finish;
251
+ this.conditions = conditions;
252
+ }
253
+
254
+ /**
255
+ * @returns {string} indexed checkpoints
256
+ */
257
+ getCheckpointsString(): string {
258
+ return this.checkpoints
259
+ .map((c: AeroflyMissionCheckpoint, index: number): string => {
260
+ return c.toString(index);
261
+ })
262
+ .join("\n");
263
+ }
264
+
265
+ /**
266
+ * @returns {string} indexed checkpoints
267
+ */
268
+ getLocalizedTextsString(): string {
269
+ return this.localizedTexts
270
+ .map((c: AeroflyLocalizedText, index: number): string => {
271
+ return c.toString(index);
272
+ })
273
+ .join("\n");
274
+ }
275
+
276
+ /**
277
+ * @throws {Error} on missing waypoints
278
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
279
+ */
280
+ toString(): string {
281
+ if (!this.origin.icao) {
282
+ const firstCheckpoint = this.checkpoints[0];
283
+ this.origin = {
284
+ icao: firstCheckpoint.name,
285
+ longitude: firstCheckpoint.longitude,
286
+ latitude: firstCheckpoint.latitude,
287
+ dir: this.origin.dir,
288
+ alt: firstCheckpoint.altitude,
289
+ };
290
+ }
291
+
292
+ if (!this.destination.icao) {
293
+ const lastCheckpoint = this.checkpoints[this.checkpoints.length - 1];
294
+ this.destination = {
295
+ icao: lastCheckpoint.name,
296
+ longitude: lastCheckpoint.longitude,
297
+ latitude: lastCheckpoint.latitude,
298
+ dir: lastCheckpoint.direction ?? 0,
299
+ alt: lastCheckpoint.altitude,
300
+ };
301
+ }
302
+
303
+ const fileSet = new AeroflyConfigFileSet(3, "tmmission_definition", "mission");
304
+ fileSet.push("string8", "title", this.title);
305
+ fileSet.push("string8", "description", this.description);
306
+
307
+ if (this.tutorialName !== null) {
308
+ fileSet.push(
309
+ "string8",
310
+ "tutorial_name",
311
+ this.tutorialName,
312
+ `Opens https://www.aerofly.com/aircraft-tutorials/${this.tutorialName}`,
313
+ );
314
+ }
315
+ if (this.localizedTexts.length > 0) {
316
+ fileSet.pushRaw(
317
+ new AeroflyConfigFileSet(4, "list_tmmission_definition_localized", "localized_text")
318
+ .pushRaw(this.getLocalizedTextsString())
319
+ .toString(),
320
+ );
321
+ }
322
+ if (this.tags.length > 0) {
323
+ fileSet.push("string8u", "tags", this.tags.join(" "));
324
+ }
325
+ if (this.difficulty !== null) {
326
+ fileSet.push("float64", "difficulty", this.difficulty);
327
+ }
328
+ if (this.isFeatured !== null) {
329
+ fileSet.push("bool", "is_featured", this.isFeatured);
330
+ }
331
+
332
+ fileSet.push("string8", "flight_setting", this.flightSetting);
333
+ fileSet.push("string8u", "aircraft_name", this.aircraft.name);
334
+ /*if (this.aircraft.livery) {
335
+ fileSet.push("string8", "aircraft_livery", this.aircraft.livery);
336
+ }*/
337
+ fileSet.push("stringt8c", "aircraft_icao", this.aircraft.icao);
338
+ fileSet.push("stringt8c", "callsign", this.callsign);
339
+ fileSet.push("stringt8c", "origin_icao", this.origin.icao);
340
+ fileSet.push("tmvector2d", "origin_lon_lat", [this.origin.longitude, this.origin.latitude]);
341
+ fileSet.push("float64", "origin_alt", this.origin.alt, `${Math.ceil(this.origin.alt * feetPerMeter)} ft MSL`);
342
+ fileSet.push("float64", "origin_dir", this.origin.dir);
343
+ fileSet.push("stringt8c", "destination_icao", this.destination.icao);
344
+ fileSet.push("tmvector2d", "destination_lon_lat", [this.destination.longitude, this.destination.latitude]);
345
+ fileSet.push(
346
+ "float64",
347
+ "destination_alt",
348
+ this.destination.alt,
349
+ `${Math.ceil(this.destination.alt * feetPerMeter)} ft MSL`,
350
+ );
351
+ fileSet.push("float64", "destination_dir", this.destination.dir);
352
+
353
+ if (this.distance !== null) {
354
+ fileSet.push("float64", "distance", this.distance, `${Math.round(this.distance / 1000)} km`);
355
+ }
356
+ if (this.duration !== null) {
357
+ fileSet.push("float64", "duration", this.duration, `${Math.round(this.duration / 60)} min`);
358
+ }
359
+ if (this.isScheduled !== null) {
360
+ fileSet.push("bool", "is_scheduled", this.isScheduled ? "true" : "false");
361
+ }
362
+ if (this.finish !== null) {
363
+ fileSet.pushRaw(this.finish.toString());
364
+ }
365
+
366
+ fileSet.pushRaw(this.conditions.toString());
367
+ if (this.checkpoints.length > 0) {
368
+ fileSet.pushRaw(
369
+ new AeroflyConfigFileSet(4, "list_tmmission_checkpoint", "checkpoints")
370
+ .pushRaw(this.getCheckpointsString())
371
+ .toString(),
372
+ );
373
+ }
374
+
375
+ return fileSet.toString();
376
+ }
377
+ }