@fboes/aerofly-custom-missions 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.editorconfig +1 -5
  2. package/.eslintrc.json +24 -0
  3. package/CHANGELOG.md +59 -21
  4. package/dist/dto/AeroflyLocalizedText.js +44 -0
  5. package/dist/dto/AeroflyMission.js +173 -0
  6. package/dist/dto/AeroflyMissionCheckpoint.js +127 -0
  7. package/dist/dto/AeroflyMissionConditions.js +124 -0
  8. package/dist/dto/AeroflyMissionConditionsCloud.js +90 -0
  9. package/dist/dto/AeroflyMissionTargetPlane.js +35 -0
  10. package/dist/dto/AeroflyMissionsList.js +36 -0
  11. package/dist/index.js +7 -634
  12. package/dist/index.test.js +17 -11
  13. package/dist/node/AeroflyConfigurationNode.js +82 -0
  14. package/docs/custom_missions_user.tmc +3 -6
  15. package/docs/custom_missions_user.xml +100 -0
  16. package/package.json +9 -10
  17. package/src/dto/AeroflyLocalizedText.ts +74 -0
  18. package/src/dto/AeroflyMission.ts +372 -0
  19. package/src/dto/AeroflyMissionCheckpoint.ts +234 -0
  20. package/src/dto/AeroflyMissionConditions.ts +189 -0
  21. package/src/dto/AeroflyMissionConditionsCloud.ts +111 -0
  22. package/src/dto/AeroflyMissionTargetPlane.ts +62 -0
  23. package/src/dto/AeroflyMissionsList.ts +52 -0
  24. package/src/index.test.ts +22 -25
  25. package/src/index.ts +7 -1099
  26. package/src/node/AeroflyConfigurationNode.ts +89 -0
  27. package/types/AeroflyMissionCheckpoint.d.ts +140 -0
  28. package/types/AeroflyMissionTargetPlane.d.ts +40 -0
  29. package/types/dto/AeroflyConfigFileSet.d.ts +10 -0
  30. package/types/dto/AeroflyConfigFileSet.d.ts.map +1 -0
  31. package/types/dto/AeroflyConfiguration.interface.d.ts +4 -0
  32. package/types/dto/AeroflyConfiguration.interface.d.ts.map +1 -0
  33. package/types/dto/AeroflyLocalizedText.d.ts +58 -0
  34. package/types/dto/AeroflyLocalizedText.d.ts.map +1 -0
  35. package/types/dto/AeroflyMission.d.ts +163 -0
  36. package/types/dto/AeroflyMission.d.ts.map +1 -0
  37. package/types/dto/AeroflyMissionCheckpoint.d.ts +126 -0
  38. package/types/dto/AeroflyMissionCheckpoint.d.ts.map +1 -0
  39. package/types/dto/AeroflyMissionConditions.d.ts +102 -0
  40. package/types/dto/AeroflyMissionConditions.d.ts.map +1 -0
  41. package/types/dto/AeroflyMissionConditionsCloud.d.ts +57 -0
  42. package/types/dto/AeroflyMissionConditionsCloud.d.ts.map +1 -0
  43. package/types/dto/AeroflyMissionTargetPlane.d.ts +45 -0
  44. package/types/dto/AeroflyMissionTargetPlane.d.ts.map +1 -0
  45. package/types/dto/AeroflyMissionsList.d.ts +30 -0
  46. package/types/dto/AeroflyMissionsList.d.ts.map +1 -0
  47. package/types/dto/basicTypes.d.ts +1 -0
  48. package/types/index.d.ts +7 -585
  49. package/types/index.d.ts.map +1 -1
  50. package/types/node/AeroflyConfigurationNode.d.ts +14 -0
  51. package/types/node/AeroflyConfigurationNode.d.ts.map +1 -0
  52. package/eslint.config.js +0 -29
package/.editorconfig CHANGED
@@ -8,9 +8,5 @@ insert_final_newline = true
8
8
  trim_trailing_whitespace = true
9
9
  charset = utf-8
10
10
  indent_style = space
11
- indent_size = 2
12
- max_line_length = 120
13
-
14
- # To accomodate TypeScript compiler
15
- [*.{js,ts}]
16
11
  indent_size = 4
12
+ max_line_length = 120
package/.eslintrc.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "env": {
3
+ "browser": true,
4
+ "es2021": true,
5
+ "node": true
6
+ },
7
+ "extends": [
8
+ "eslint:recommended",
9
+ "plugin:@typescript-eslint/recommended",
10
+ "eslint-config-prettier",
11
+ "plugin:jsdoc/recommended-error"
12
+ ],
13
+ "parser": "@typescript-eslint/parser",
14
+ "parserOptions": {
15
+ "ecmaVersion": "latest",
16
+ "sourceType": "module"
17
+ },
18
+ "plugins": [
19
+ "@typescript-eslint",
20
+ "jsdoc"
21
+ ],
22
+ "rules": {
23
+ }
24
+ }
package/CHANGELOG.md CHANGED
@@ -1,52 +1,90 @@
1
1
  # Changelog
2
2
 
3
+ This changelog documents all notable changes to the Aerofly Custom Missions project. Each version entry includes a list of changes, with the most recent version at the top.
4
+
5
+ ## 1.3.0
6
+
7
+ - Changed TMC code generation with nodes
8
+
9
+ ## 1.2.3
10
+
11
+ - Internal restructuring of mission generation logic
12
+ - Optimized waypoint handling and validation
13
+ - Improved error handling for mission parsing
14
+
3
15
  ## 1.2.2
4
16
 
5
- - Added altitude constraint property
6
- - Improved handling of cloud layers
17
+ - Added altitude constraint property for waypoints
18
+ - Improved handling of cloud layers with better validation
7
19
 
8
20
  ## 1.2.1
9
21
 
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
22
+ - Changed handling of checkpoints to support missions without checkpoints
23
+ - Improved file generation for programmatic mission creation
24
+ - Added new properties:
25
+ - `is_scheduled` for mission scheduling
26
+ - `tutorial_name` for tutorial identification
27
+ - Enhanced cloud handling with better validation
28
+ - Improved handling of unset values with default fallbacks
29
+ - Added new flight settings:
30
+ - `cold_and_dark`
31
+ - `before_start`
32
+ - `pushback`
16
33
 
17
34
  ## 1.2.0
18
35
 
19
- - Adding new cloud level `cirrus_cover` / `cirrus_base`
20
- - Adding new waypoint property `fly_over`
21
- - Adding `finish` property
36
+ - Added new cloud level properties:
37
+ - `cirrus_cover` for high-altitude cloud coverage
38
+ - `cirrus_base` for cirrus cloud base altitude
39
+ - Added new waypoint property `fly_over` for precise waypoint navigation
40
+ - Added `finish` property to mark mission completion points
22
41
 
23
42
  ## 1.1.1
24
43
 
25
- - Minor styling fixes
44
+ - Fixed styling issues in mission display
45
+ - Improved UI consistency across different mission types
46
+ - Enhanced error message formatting
26
47
 
27
48
  ## 1.1.0
28
49
 
29
- - Added new mission properties `tags`, `isFeatured`, `difficulty`, `distance`, `duration` and translations
30
- - Added new flight settings `winch_launch`, `aerotow`
31
- - Improved temperature property
50
+ - Added new mission metadata properties:
51
+ - `tags` for mission categorization
52
+ - `isFeatured` for highlighting special missions
53
+ - `difficulty` for mission complexity rating
54
+ - `distance` for mission length in kilometers
55
+ - `duration` for estimated completion time
56
+ - Multi-language support for mission descriptions
57
+ - Added new flight settings:
58
+ - `winch_launch` for glider operations
59
+ - `aerotow` for towed aircraft operations
60
+ - Improved temperature property with better unit handling
32
61
 
33
62
  ## 1.0.4
34
63
 
35
- - Added documentation for known issues
36
- - Added property `AeroflyMissionConditions.temperature`
64
+ - Added comprehensive documentation for known issues and workarounds
65
+ - Added `AeroflyMissionConditions.temperature` property with Celsius support
66
+ - Improved error handling for weather conditions
37
67
 
38
68
  ## 1.0.3
39
69
 
40
- - Improved documentation
70
+ - Enhanced API documentation with examples
71
+ - Added detailed parameter descriptions
72
+ - Improved code documentation
41
73
 
42
74
  ## 1.0.2
43
75
 
44
- - Added more short hand properties
76
+ - Added shorthand properties for common mission parameters
77
+ - Improved property access methods
78
+ - Enhanced mission validation
45
79
 
46
80
  ## 1.0.1
47
81
 
48
- - Improved documentation
82
+ - Added initial API documentation
83
+ - Improved code comments
84
+ - Added basic usage examples
49
85
 
50
86
  ## 1.0.0
51
87
 
52
- - Initial commit
88
+ - Initial release of Aerofly Custom Missions
89
+ - Basic mission creation and editing functionality
90
+ - Support for essential mission parameters
@@ -0,0 +1,44 @@
1
+ import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
2
+ /**
3
+ * @class
4
+ * A translation for the mission title and description.
5
+ */
6
+ export class AeroflyLocalizedText {
7
+ /**
8
+ * @param {string} language ISO 639-1 like
9
+ * - br
10
+ * - cn
11
+ * - de
12
+ * - es
13
+ * - fr
14
+ * - id
15
+ * - it
16
+ * - jp
17
+ * - kr
18
+ * - pl
19
+ * - sv
20
+ * - tr
21
+ * @param {string} title of this flight plan
22
+ * @param {string} description text, mission briefing, etc
23
+ */
24
+ constructor(language, title, description) {
25
+ this.language = language;
26
+ this.title = title;
27
+ this.description = description;
28
+ }
29
+ /**
30
+ * @returns {AeroflyConfigurationNode} to use in Aerofly FS4's `custom_missions_user.tmc`
31
+ */
32
+ getElement() {
33
+ return new AeroflyConfigurationNode("tmmission_definition_localized", "element")
34
+ .appendChild("string8u", "language", this.language)
35
+ .appendChild("string8", "title", this.title)
36
+ .appendChild("string8", "description", this.description);
37
+ }
38
+ /**
39
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
40
+ */
41
+ toString() {
42
+ return this.getElement().toString();
43
+ }
44
+ }
@@ -0,0 +1,173 @@
1
+ import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
2
+ import { AeroflyMissionConditions } from "./AeroflyMissionConditions.js";
3
+ export const feetPerMeter = 3.28084;
4
+ export const meterPerStatuteMile = 1609.344;
5
+ /**
6
+ * @class
7
+ * A single flighplan, containing aircraft and weather data as well.
8
+ *
9
+ * The purpose of this class is to collect data needed for Aerofly FS4's
10
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
11
+ * for this file via the `toString()` method.
12
+ */
13
+ export class AeroflyMission {
14
+ /**
15
+ * @param {string} title of this flight plan
16
+ * @param {object} [additionalAttributes] allows to set additional attributes on creation
17
+ * @param {string} [additionalAttributes.description] text, mission briefing, etc
18
+ * @param {AeroflyLocalizedText[]} [additionalAttributes.localizedTexts] translations for title and description
19
+ * @param {?string} [additionalAttributes.tutorialName] will create a link to a tutorial page at https://www.aerofly.com/aircraft-tutorials/
20
+ * @param {string[]} [additionalAttributes.tags] free-text tags
21
+ * @param {?boolean} [additionalAttributes.isFeatured] makes this mission pop up in "Challenges"
22
+ * @param {?number} [additionalAttributes.difficulty] values between 0.00 and 2.00 have been encountered, but they seem to be without limit
23
+ * @param {"cold_and_dark"|"before_start"|"taxi"|"takeoff"|"cruise"|"approach"|"landing"|"winch_launch"|"aerotow"|"pushback"} [additionalAttributes.flightSetting] of aircraft, like "taxi", "cruise"
24
+ * @param {{name:string,livery:string,icao:string}} [additionalAttributes.aircraft] for this mission
25
+ * @param {string} [additionalAttributes.callsign] of aircraft, uppercased
26
+ * @param {object} [additionalAttributes.origin] position of aircraft, as well as name of starting airport. Position does not have match airport.
27
+ * @param {object} [additionalAttributes.destination] position of aircraft, as well as name of destination airport. Position does not have match airport.
28
+ * @param {?number} [additionalAttributes.distance] in meters
29
+ * @param {?number} [additionalAttributes.duration] in seconds
30
+ * @param {?boolean} [additionalAttributes.isScheduled] marks this flight as "Scheduled flight".
31
+ * @param {?AeroflyMissionTargetPlane} [additionalAttributes.finish] as finish condition
32
+ * @param {AeroflyMissionConditions} [additionalAttributes.conditions] like time and weather for mission
33
+ * @param {AeroflyMissionCheckpoint[]} [additionalAttributes.checkpoints] form the actual flight plan
34
+ */
35
+ constructor(title, { tutorialName = null, description = "", localizedTexts = [], tags = [], isFeatured = null, difficulty = null, flightSetting = "taxi", aircraft = {
36
+ name: "c172",
37
+ icao: "",
38
+ livery: "",
39
+ }, callsign = "", origin = {
40
+ icao: "",
41
+ longitude: 0,
42
+ latitude: 0,
43
+ dir: 0,
44
+ alt: 0,
45
+ }, destination = {
46
+ icao: "",
47
+ longitude: 0,
48
+ latitude: 0,
49
+ dir: 0,
50
+ alt: 0,
51
+ }, distance = null, duration = null, isScheduled = null, finish = null, conditions = new AeroflyMissionConditions(), checkpoints = [], } = {}) {
52
+ this.tutorialName = tutorialName;
53
+ this.title = title;
54
+ this.checkpoints = checkpoints;
55
+ this.description = description;
56
+ this.localizedTexts = localizedTexts;
57
+ this.tags = tags;
58
+ this.isFeatured = isFeatured;
59
+ this.difficulty = difficulty;
60
+ this.flightSetting = flightSetting;
61
+ this.aircraft = aircraft;
62
+ this.callsign = callsign;
63
+ this.origin = origin;
64
+ this.destination = destination;
65
+ this.distance = distance;
66
+ this.duration = duration;
67
+ this.isScheduled = isScheduled;
68
+ this.finish = finish;
69
+ this.conditions = conditions;
70
+ }
71
+ /**
72
+ * @returns {AeroflyConfigurationNode[]} indexed checkpoints
73
+ */
74
+ getCheckpointElements() {
75
+ return this.checkpoints.map((c, index) => {
76
+ return c.getElement(index);
77
+ });
78
+ }
79
+ /**
80
+ * @returns {AeroflyConfigurationNode[]} indexed checkpoints
81
+ */
82
+ getLocalizedTextElements() {
83
+ return this.localizedTexts.map((c, index) => {
84
+ const el = c.getElement();
85
+ el.value = String(index);
86
+ return el;
87
+ });
88
+ }
89
+ /**
90
+ * @returns {AeroflyConfigurationNode} for this mission
91
+ */
92
+ getElement() {
93
+ if (!this.origin.icao) {
94
+ const firstCheckpoint = this.checkpoints[0];
95
+ this.origin = {
96
+ icao: firstCheckpoint.name,
97
+ longitude: firstCheckpoint.longitude,
98
+ latitude: firstCheckpoint.latitude,
99
+ dir: this.origin.dir,
100
+ alt: firstCheckpoint.altitude,
101
+ };
102
+ }
103
+ if (!this.destination.icao) {
104
+ const lastCheckpoint = this.checkpoints[this.checkpoints.length - 1];
105
+ this.destination = {
106
+ icao: lastCheckpoint.name,
107
+ longitude: lastCheckpoint.longitude,
108
+ latitude: lastCheckpoint.latitude,
109
+ dir: lastCheckpoint.direction ?? 0,
110
+ alt: lastCheckpoint.altitude,
111
+ };
112
+ }
113
+ const element = new AeroflyConfigurationNode("tmmission_definition", "mission");
114
+ element.appendChild("string8", "title", this.title);
115
+ element.appendChild("string8", "description", this.description);
116
+ if (this.tutorialName !== null) {
117
+ element.appendChild("string8", "tutorial_name", this.tutorialName, `Opens https://www.aerofly.com/aircraft-tutorials/${this.tutorialName}`);
118
+ }
119
+ if (this.localizedTexts.length > 0) {
120
+ element.append(new AeroflyConfigurationNode("list_tmmission_definition_localized", "localized_text").append(...this.getLocalizedTextElements()));
121
+ }
122
+ if (this.tags.length > 0) {
123
+ element.appendChild("string8u", "tags", this.tags);
124
+ }
125
+ if (this.difficulty !== null) {
126
+ element.appendChild("float64", "difficulty", this.difficulty);
127
+ }
128
+ if (this.isFeatured !== null) {
129
+ element.appendChild("bool", "is_featured", this.isFeatured);
130
+ }
131
+ element.appendChild("string8", "flight_setting", this.flightSetting);
132
+ element.appendChild("string8u", "aircraft_name", this.aircraft.name);
133
+ /*if (this.aircraft.livery) {
134
+ mission.createChild("string8", "aircraft_livery", this.aircraft.livery);
135
+ }*/
136
+ element.appendChild("stringt8c", "aircraft_icao", this.aircraft.icao);
137
+ element.appendChild("stringt8c", "callsign", this.callsign);
138
+ element.appendChild("stringt8c", "origin_icao", this.origin.icao);
139
+ element.appendChild("tmvector2d", "origin_lon_lat", [this.origin.longitude, this.origin.latitude]);
140
+ element.appendChild("float64", "origin_alt", this.origin.alt, `${Math.ceil(this.origin.alt * feetPerMeter)} ft MSL`);
141
+ element.appendChild("float64", "origin_dir", this.origin.dir);
142
+ element.appendChild("stringt8c", "destination_icao", this.destination.icao);
143
+ element.appendChild("tmvector2d", "destination_lon_lat", [
144
+ this.destination.longitude,
145
+ this.destination.latitude,
146
+ ]);
147
+ element.appendChild("float64", "destination_alt", this.destination.alt, `${Math.ceil(this.destination.alt * feetPerMeter)} ft MSL`);
148
+ element.appendChild("float64", "destination_dir", this.destination.dir);
149
+ if (this.distance !== null) {
150
+ element.appendChild("float64", "distance", this.distance, `${Math.round(this.distance / 1000)} km`);
151
+ }
152
+ if (this.duration !== null) {
153
+ element.appendChild("float64", "duration", this.duration, `${Math.round(this.duration / 60)} min`);
154
+ }
155
+ if (this.isScheduled !== null) {
156
+ element.appendChild("bool", "is_scheduled", this.isScheduled);
157
+ }
158
+ if (this.finish !== null) {
159
+ element.append(this.finish.getElement());
160
+ }
161
+ element.append(this.conditions.getElement());
162
+ if (this.checkpoints.length > 0) {
163
+ element.append(new AeroflyConfigurationNode("list_tmmission_checkpoint", "checkpoints").append(...this.getCheckpointElements()));
164
+ }
165
+ return element;
166
+ }
167
+ /**
168
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
169
+ */
170
+ toString() {
171
+ return this.getElement().toString();
172
+ }
173
+ }
@@ -0,0 +1,127 @@
1
+ import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
2
+ import { feetPerMeter } from "./AeroflyMission.js";
3
+ /**
4
+ * @class
5
+ * A single way point for the given flight plan
6
+ *
7
+ * The purpose of this class is to collect data needed for Aerofly FS4's
8
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
9
+ * for this file via the `toString()` method.
10
+ */
11
+ export class AeroflyMissionCheckpoint {
12
+ /**
13
+ * @param {string} name ICAO code for airport, runway designator, navaid
14
+ * designator, fix name, or custom name
15
+ * @param {"origin"|"departure_runway"|"departure"|"waypoint"|"arrival"|"approach"|"destination_runway"|"destination"} type Type of checkpoint, like "departure_runway"
16
+ * @param {number} longitude easting, using the World Geodetic
17
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
18
+ * of decimal degrees; -180..180
19
+ * @param {number} latitude northing, using the World Geodetic
20
+ * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
21
+ * of decimal degrees; -90..90
22
+ * @param {object} additionalAttributes allows to set additional attributes on creation
23
+ * @param {number} [additionalAttributes.altitude] The height in meters above or below the WGS
24
+ * 84 reference ellipsoid
25
+ * @param {?number} [additionalAttributes.altitude_feet] The height in feet above or below the WGS
26
+ * 84 reference ellipsoid. Will overwrite altitude
27
+ * @param {number} [additionalAttributes.altitudeConstraint] The altitude given in `altitude`
28
+ * will be interpreted as mandatory flight plan altitude instead of
29
+ * suggestion.
30
+ * @param {boolean} [additionalAttributes.direction] of runway, in degree
31
+ * @param {?number} [additionalAttributes.slope] of runway
32
+ * @param {?number} [additionalAttributes.length] of runway, in meters
33
+ * @param {?number} [additionalAttributes.length_feet] of runway, in feet. Will overwrite length
34
+ * @param {?number} [additionalAttributes.frequency] of runways or navigational aids, in Hz; multiply by 1000 for kHz, 1_000_000 for MHz
35
+ * @param {?boolean} [additionalAttributes.flyOver] if waypoint is meant to be flown over
36
+ */
37
+ constructor(name, type, longitude, latitude, { altitude = 0, altitude_feet = 0, altitudeConstraint = null, direction = null, slope = null, length = null, length_feet = 0, frequency = null, flyOver = null, } = {}) {
38
+ this.type = type;
39
+ this.name = name;
40
+ this.longitude = longitude;
41
+ this.latitude = latitude;
42
+ this.altitude = altitude;
43
+ this.altitudeConstraint = altitudeConstraint;
44
+ this.direction = direction;
45
+ this.slope = slope;
46
+ this.length = length;
47
+ this.frequency = frequency;
48
+ this.flyOver = flyOver;
49
+ if (altitude_feet) {
50
+ this.altitude_feet = altitude_feet;
51
+ }
52
+ if (length_feet) {
53
+ this.length_feet = length_feet;
54
+ }
55
+ }
56
+ /**
57
+ * @param {number} altitude_feet in feet
58
+ */
59
+ set altitude_feet(altitude_feet) {
60
+ this.altitude = altitude_feet / feetPerMeter;
61
+ }
62
+ /**
63
+ * @returns {number} altitude_feet
64
+ */
65
+ get altitude_feet() {
66
+ return this.altitude * feetPerMeter;
67
+ }
68
+ /**
69
+ * @param {number} length_feet in feet
70
+ */
71
+ set length_feet(length_feet) {
72
+ this.length = length_feet / feetPerMeter;
73
+ }
74
+ /**
75
+ * @returns {number} length_feet
76
+ */
77
+ get length_feet() {
78
+ return (this.length ?? 0) * feetPerMeter;
79
+ }
80
+ /**
81
+ * @returns {string} with MHz / kHz attached
82
+ */
83
+ get frequency_string() {
84
+ if (!this.frequency) {
85
+ return "None";
86
+ }
87
+ if (this.frequency > 1000000) {
88
+ return String(this.frequency / 1000000) + " MHz";
89
+ }
90
+ if (this.frequency > 1000) {
91
+ return String(this.frequency / 1000) + " kHz";
92
+ }
93
+ return String(this.frequency) + " Hz";
94
+ }
95
+ /**
96
+ * @param {number} index default: 0
97
+ * @returns {AeroflyConfigurationNode} to use in Aerofly FS4's `custom_missions_user.tmc`
98
+ */
99
+ getElement(index = 0) {
100
+ const element = new AeroflyConfigurationNode("tmmission_checkpoint", "element", String(index))
101
+ .appendChild("string8u", "type", this.type)
102
+ .appendChild("string8u", "name", this.name)
103
+ .appendChild("vector2_float64", "lon_lat", [this.longitude, this.latitude])
104
+ .appendChild("float64", "altitude", this.altitude, `${Math.ceil(this.altitude_feet)} ft`)
105
+ .appendChild("float64", "direction", this.direction ?? (index === 0 ? -1 : 0))
106
+ .appendChild("float64", "slope", this.slope ?? 0);
107
+ if (this.altitudeConstraint !== null) {
108
+ element.appendChild("bool", "alt_cst", this.altitudeConstraint);
109
+ }
110
+ if (this.length) {
111
+ element.appendChild("float64", "length", this.length ?? 0, `${Math.floor(this.length_feet)} ft`);
112
+ }
113
+ if (this.frequency) {
114
+ element.appendChild("float64", "frequency", this.frequency ?? 0, `${this.frequency_string}`);
115
+ }
116
+ if (this.flyOver !== null) {
117
+ element.appendChild("bool", "fly_over", this.flyOver);
118
+ }
119
+ return element;
120
+ }
121
+ /**
122
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
123
+ */
124
+ toString() {
125
+ return this.getElement().toString();
126
+ }
127
+ }
@@ -0,0 +1,124 @@
1
+ import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
2
+ import { meterPerStatuteMile } from "./AeroflyMission.js";
3
+ import { AeroflyMissionConditionsCloud } from "./AeroflyMissionConditionsCloud.js";
4
+ /**
5
+ * @class
6
+ * Time and weather data for the given flight plan
7
+ *
8
+ * The purpose of this class is to collect data needed for Aerofly FS4's
9
+ * `custom_missions_user.tmc` flight plan file format, and export the structure
10
+ * for this file via the `toString()` method.
11
+ */
12
+ export class AeroflyMissionConditions {
13
+ /**
14
+ * @param {object} additionalAttributes allows to set additional attributes on creation
15
+ * @param {Date} [additionalAttributes.time] of flight plan. Relevant is the UTC part, so
16
+ * consider setting this date in UTC.
17
+ * @param {{direction: number, speed: number, gusts: number}} [additionalAttributes.wind] state
18
+ * @param {number} [additionalAttributes.turbulenceStrength] 0..1, percentage
19
+ * @param {number} [additionalAttributes.thermalStrength] 0..1, percentage
20
+ * @param {number} [additionalAttributes.visibility] in meters
21
+ * @param {?number} [additionalAttributes.visibility_sm] in statute miles, will overwrite visibility
22
+ * @param {?number} [additionalAttributes.temperature] in °C, will overwrite thermalStrength
23
+ * @param {AeroflyMissionConditionsCloud[]} [additionalAttributes.clouds] for the whole flight
24
+ */
25
+ constructor({ time = new Date(), wind = {
26
+ direction: 0,
27
+ speed: 0,
28
+ gusts: 0,
29
+ }, turbulenceStrength = 0, thermalStrength = 0, visibility = 25000, visibility_sm = 0, temperature = 0, clouds = [], } = {}) {
30
+ /**
31
+ * @property {AeroflyMissionConditionsCloud[]} clouds for the whole flight
32
+ */
33
+ this.clouds = [];
34
+ this.time = time;
35
+ this.wind = wind;
36
+ this.turbulenceStrength = turbulenceStrength;
37
+ this.thermalStrength = thermalStrength;
38
+ this.visibility = visibility;
39
+ this.clouds = clouds;
40
+ if (visibility_sm) {
41
+ this.visibility_sm = visibility_sm;
42
+ }
43
+ if (temperature) {
44
+ this.temperature = temperature;
45
+ }
46
+ }
47
+ /**
48
+ * @returns {number} the Aerofly value for UTC hours + minutes/60 + seconds/3600. Ignores milliseconds ;)
49
+ */
50
+ get time_hours() {
51
+ return this.time.getUTCHours() + this.time.getUTCMinutes() / 60 + this.time.getUTCSeconds() / 3600;
52
+ }
53
+ /**
54
+ * @returns {string} Time representation like "20:15:00"
55
+ */
56
+ get time_presentational() {
57
+ return [this.time.getUTCHours(), this.time.getUTCMinutes(), this.time.getUTCSeconds()]
58
+ .map((t) => {
59
+ return String(t).padStart(2, "0");
60
+ })
61
+ .join(":");
62
+ }
63
+ /**
64
+ * @param {number} visibility_sm `this.visibility` in statute miles instead of meters
65
+ */
66
+ set visibility_sm(visibility_sm) {
67
+ this.visibility = visibility_sm * meterPerStatuteMile;
68
+ }
69
+ /**
70
+ * @returns {number} `this.visibility` in statute miles instead of meters
71
+ */
72
+ get visibility_sm() {
73
+ return this.visibility / meterPerStatuteMile;
74
+ }
75
+ /**
76
+ * Will set `this.thermalStrength`
77
+ * @param {number} temperature in °C
78
+ */
79
+ set temperature(temperature) {
80
+ // Range from -15°C to 35°C
81
+ this.thermalStrength = Math.max(0, (temperature + 15) / 50) ** 2;
82
+ }
83
+ /**
84
+ * @returns {number} in °C
85
+ */
86
+ get temperature() {
87
+ return Math.sqrt(this.thermalStrength) * 50 - 15;
88
+ }
89
+ /**
90
+ * @returns {AeroflyConfigurationNode[]} cloud elements
91
+ */
92
+ getCloudElements() {
93
+ return this.clouds
94
+ .slice(0, 2) // Aerofly FS4 supports max 2 cloud layers
95
+ .flatMap((c, index) => c.getElements(index));
96
+ }
97
+ /**
98
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
99
+ */
100
+ getElement() {
101
+ if (this.clouds.length < 1) {
102
+ this.clouds = [new AeroflyMissionConditionsCloud(0, 0)];
103
+ }
104
+ return new AeroflyConfigurationNode("tmmission_conditions", "conditions")
105
+ .append(new AeroflyConfigurationNode("tm_time_utc", "time")
106
+ .appendChild("int32", "time_year", this.time.getUTCFullYear())
107
+ .appendChild("int32", "time_month", this.time.getUTCMonth() + 1)
108
+ .appendChild("int32", "time_day", this.time.getUTCDate())
109
+ .appendChild("float64", "time_hours", this.time_hours, `${this.time_presentational} UTC`))
110
+ .appendChild("float64", "wind_direction", this.wind.direction)
111
+ .appendChild("float64", "wind_speed", this.wind.speed, "kts")
112
+ .appendChild("float64", "wind_gusts", this.wind.gusts, "kts")
113
+ .appendChild("float64", "turbulence_strength", this.turbulenceStrength)
114
+ .appendChild("float64", "thermal_strength", this.thermalStrength, `${this.temperature} °C`)
115
+ .appendChild("float64", "visibility", this.visibility, `${this.visibility_sm} SM`)
116
+ .append(...this.getCloudElements());
117
+ }
118
+ /**
119
+ * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
120
+ */
121
+ toString() {
122
+ return this.getElement().toString();
123
+ }
124
+ }