@fboes/aerofly-custom-missions 1.3.0 → 1.4.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 (170) hide show
  1. package/.editorconfig +4 -0
  2. package/AGENTS.md +43 -0
  3. package/CHANGELOG.md +23 -12
  4. package/LICENSE.txt +1 -1
  5. package/README.md +6 -1
  6. package/dist/check/TestHelpers.js +13 -0
  7. package/dist/check/TestHelpers.test.js +8 -0
  8. package/dist/dto/AeroflyLocalizedText.test.js +16 -0
  9. package/dist/dto/AeroflyMission.js +15 -5
  10. package/dist/dto/AeroflyMission.test.js +166 -0
  11. package/dist/dto/AeroflyMissionCheckpoint.js +1 -1
  12. package/dist/dto/AeroflyMissionCheckpoint.test.js +41 -0
  13. package/dist/dto/AeroflyMissionConditions.js +4 -6
  14. package/dist/dto/AeroflyMissionConditions.test.js +33 -0
  15. package/dist/dto/AeroflyMissionConditionsCloud.js +12 -7
  16. package/dist/dto/AeroflyMissionConditionsCloud.test.js +25 -0
  17. package/dist/dto/AeroflyMissionTargetPlane.js +1 -1
  18. package/dist/dto/AeroflyMissionTargetPlane.test.js +14 -0
  19. package/dist/dto/AeroflyMissionsList.js +2 -6
  20. package/dist/dto/AeroflyMissionsList.test.js +16 -0
  21. package/dist/dto-flight/AeroflyFlight.js +94 -0
  22. package/dist/dto-flight/AeroflyFlight.test.js +69 -0
  23. package/dist/dto-flight/AeroflyNavRouteAirports.js +53 -0
  24. package/dist/dto-flight/AeroflyNavRouteBase.js +54 -0
  25. package/dist/dto-flight/AeroflyNavRouteRunway.js +70 -0
  26. package/dist/dto-flight/AeroflyNavRouteTransition.js +75 -0
  27. package/dist/dto-flight/AeroflyNavRouteWaypoint.js +58 -0
  28. package/dist/dto-flight/AeroflyNavigationConfig.js +50 -0
  29. package/dist/dto-flight/AeroflySettingsAircraft.js +28 -0
  30. package/dist/dto-flight/AeroflySettingsCloud.js +103 -0
  31. package/dist/dto-flight/AeroflySettingsCloud.test.js +53 -0
  32. package/dist/dto-flight/AeroflySettingsFlight.js +95 -0
  33. package/dist/dto-flight/AeroflySettingsFlight.test.js +52 -0
  34. package/dist/dto-flight/AeroflySettingsFuelLoad.js +30 -0
  35. package/dist/dto-flight/AeroflySettingsWind.js +74 -0
  36. package/dist/dto-flight/AeroflyTimeUtc.js +84 -0
  37. package/dist/dto-flight/AeroflyWaypoint.js +85 -0
  38. package/dist/index.js +13 -0
  39. package/dist/node/AeroflyBasicTypes.js +61 -0
  40. package/dist/node/AeroflyConfigurationNode.js +64 -29
  41. package/dist/node/AeroflyConfigurationNode.test.js +28 -0
  42. package/dist/node/Convert copy.js +67 -0
  43. package/dist/node/Convert.js +72 -0
  44. package/dist/node/Convert.test.js +50 -0
  45. package/docs/custom_missions_user.tmc +7 -2
  46. package/docs/custom_missions_user.xml +6 -4
  47. package/docs/flight-mcf.md +7 -0
  48. package/docs/flight.mcf +116 -0
  49. package/docs/flight.xml +116 -0
  50. package/package.json +4 -2
  51. package/src/check/TestHelpers.test.ts +10 -0
  52. package/src/check/TestHelpers.ts +16 -0
  53. package/src/dto/AeroflyLocalizedText.test.ts +20 -0
  54. package/src/dto/AeroflyMission.test.ts +185 -0
  55. package/src/dto/AeroflyMission.ts +29 -5
  56. package/src/dto/AeroflyMissionCheckpoint.test.ts +50 -0
  57. package/src/dto/AeroflyMissionCheckpoint.ts +2 -2
  58. package/src/dto/AeroflyMissionConditions.test.ts +38 -0
  59. package/src/dto/AeroflyMissionConditions.ts +5 -7
  60. package/src/dto/AeroflyMissionConditionsCloud.test.ts +29 -0
  61. package/src/dto/AeroflyMissionConditionsCloud.ts +23 -8
  62. package/src/dto/AeroflyMissionTargetPlane.test.ts +15 -0
  63. package/src/dto/AeroflyMissionTargetPlane.ts +2 -2
  64. package/src/dto/AeroflyMissionsList.test.ts +19 -0
  65. package/src/dto/AeroflyMissionsList.ts +3 -7
  66. package/src/dto-flight/AeroflyFlight.test.ts +87 -0
  67. package/src/dto-flight/AeroflyFlight.ts +180 -0
  68. package/src/dto-flight/AeroflyNavRouteAirports.ts +86 -0
  69. package/src/dto-flight/AeroflyNavRouteBase.ts +109 -0
  70. package/src/dto-flight/AeroflyNavRouteRunway.ts +123 -0
  71. package/src/dto-flight/AeroflyNavRouteTransition.ts +127 -0
  72. package/src/dto-flight/AeroflyNavRouteWaypoint.ts +98 -0
  73. package/src/dto-flight/AeroflyNavigationConfig.ts +79 -0
  74. package/src/dto-flight/AeroflySettingsAircraft.ts +51 -0
  75. package/src/dto-flight/AeroflySettingsCloud.test.ts +68 -0
  76. package/src/dto-flight/AeroflySettingsCloud.ts +137 -0
  77. package/src/dto-flight/AeroflySettingsFlight.test.ts +70 -0
  78. package/src/dto-flight/AeroflySettingsFlight.ts +157 -0
  79. package/src/dto-flight/AeroflySettingsFuelLoad.ts +62 -0
  80. package/src/dto-flight/AeroflySettingsWind.ts +114 -0
  81. package/src/dto-flight/AeroflyTimeUtc.ts +103 -0
  82. package/src/index.ts +23 -0
  83. package/src/node/AeroflyConfigurationNode.test.ts +46 -0
  84. package/src/node/AeroflyConfigurationNode.ts +59 -16
  85. package/src/node/Convert.test.ts +90 -0
  86. package/src/node/Convert.ts +90 -0
  87. package/types/check/TestHelpers.d.ts +3 -0
  88. package/types/check/TestHelpers.d.ts.map +1 -0
  89. package/types/check/TestHelpers.test.d.ts +2 -0
  90. package/types/check/TestHelpers.test.d.ts.map +1 -0
  91. package/types/dto/AeroflyConfigFileSet.d.ts +1 -1
  92. package/types/dto/AeroflyConfiguration.interface.d.ts +1 -1
  93. package/types/dto/AeroflyLocalizedText.d.ts +1 -1
  94. package/types/dto/AeroflyLocalizedText.test.d.ts +2 -0
  95. package/types/dto/AeroflyLocalizedText.test.d.ts.map +1 -0
  96. package/types/dto/AeroflyMission.d.ts +47 -4
  97. package/types/dto/AeroflyMission.d.ts.map +1 -1
  98. package/types/dto/AeroflyMission.test.d.ts +2 -0
  99. package/types/dto/AeroflyMission.test.d.ts.map +1 -0
  100. package/types/dto/AeroflyMissionCheckpoint.d.ts +32 -8
  101. package/types/dto/AeroflyMissionCheckpoint.test.d.ts +2 -0
  102. package/types/dto/AeroflyMissionCheckpoint.test.d.ts.map +1 -0
  103. package/types/dto/AeroflyMissionConditions.d.ts +15 -6
  104. package/types/dto/AeroflyMissionConditions.d.ts.map +1 -1
  105. package/types/dto/AeroflyMissionConditions.test.d.ts +2 -0
  106. package/types/dto/AeroflyMissionConditions.test.d.ts.map +1 -0
  107. package/types/dto/AeroflyMissionConditionsCloud.d.ts +4 -4
  108. package/types/dto/AeroflyMissionConditionsCloud.d.ts.map +1 -1
  109. package/types/dto/AeroflyMissionConditionsCloud.test.d.ts +2 -0
  110. package/types/dto/AeroflyMissionConditionsCloud.test.d.ts.map +1 -0
  111. package/types/dto/AeroflyMissionTargetPlane.d.ts +3 -3
  112. package/types/dto/AeroflyMissionTargetPlane.test.d.ts +2 -0
  113. package/types/dto/AeroflyMissionTargetPlane.test.d.ts.map +1 -0
  114. package/types/dto/AeroflyMissionsList.d.ts +1 -1
  115. package/types/dto/AeroflyMissionsList.d.ts.map +1 -1
  116. package/types/dto/AeroflyMissionsList.test.d.ts +2 -0
  117. package/types/dto/AeroflyMissionsList.test.d.ts.map +1 -0
  118. package/types/dto/index.test.d.ts +2 -0
  119. package/types/dto/index.test.d.ts.map +1 -0
  120. package/types/dto-flight/AeroflyFlight.d.ts +103 -0
  121. package/types/dto-flight/AeroflyFlight.d.ts.map +1 -0
  122. package/types/dto-flight/AeroflyFlight.test.d.ts +2 -0
  123. package/types/dto-flight/AeroflyFlight.test.d.ts.map +1 -0
  124. package/types/dto-flight/AeroflyNavRouteAirports.d.ts +55 -0
  125. package/types/dto-flight/AeroflyNavRouteAirports.d.ts.map +1 -0
  126. package/types/dto-flight/AeroflyNavRouteBase.d.ts +65 -0
  127. package/types/dto-flight/AeroflyNavRouteBase.d.ts.map +1 -0
  128. package/types/dto-flight/AeroflyNavRouteRunway.d.ts +54 -0
  129. package/types/dto-flight/AeroflyNavRouteRunway.d.ts.map +1 -0
  130. package/types/dto-flight/AeroflyNavRouteTransition.d.ts +62 -0
  131. package/types/dto-flight/AeroflyNavRouteTransition.d.ts.map +1 -0
  132. package/types/dto-flight/AeroflyNavRouteWaypoint.d.ts +49 -0
  133. package/types/dto-flight/AeroflyNavRouteWaypoint.d.ts.map +1 -0
  134. package/types/dto-flight/AeroflyNavigationConfig.d.ts +38 -0
  135. package/types/dto-flight/AeroflyNavigationConfig.d.ts.map +1 -0
  136. package/types/dto-flight/AeroflySettingsAircraft.d.ts +32 -0
  137. package/types/dto-flight/AeroflySettingsAircraft.d.ts.map +1 -0
  138. package/types/dto-flight/AeroflySettingsCloud.d.ts +45 -0
  139. package/types/dto-flight/AeroflySettingsCloud.d.ts.map +1 -0
  140. package/types/dto-flight/AeroflySettingsCloud.test.d.ts +2 -0
  141. package/types/dto-flight/AeroflySettingsCloud.test.d.ts.map +1 -0
  142. package/types/dto-flight/AeroflySettingsFlight.d.ts +66 -0
  143. package/types/dto-flight/AeroflySettingsFlight.d.ts.map +1 -0
  144. package/types/dto-flight/AeroflySettingsFlight.test.d.ts +2 -0
  145. package/types/dto-flight/AeroflySettingsFlight.test.d.ts.map +1 -0
  146. package/types/dto-flight/AeroflySettingsFuelLoad.d.ts +42 -0
  147. package/types/dto-flight/AeroflySettingsFuelLoad.d.ts.map +1 -0
  148. package/types/dto-flight/AeroflySettingsWind.d.ts +63 -0
  149. package/types/dto-flight/AeroflySettingsWind.d.ts.map +1 -0
  150. package/types/dto-flight/AeroflyTimeUtc.d.ts +47 -0
  151. package/types/dto-flight/AeroflyTimeUtc.d.ts.map +1 -0
  152. package/types/dto-flight/AeroflyWaypoint.d.ts +68 -0
  153. package/types/dto-flight/AeroflyWaypoint.d.ts.map +1 -0
  154. package/types/index.d.ts +20 -1
  155. package/types/index.d.ts.map +1 -1
  156. package/types/index.test.d.ts +1 -1
  157. package/types/node/AeroflyBasicTypes.d.ts +13 -0
  158. package/types/node/AeroflyBasicTypes.d.ts.map +1 -0
  159. package/types/node/AeroflyConfigurationNode.d.ts +25 -5
  160. package/types/node/AeroflyConfigurationNode.d.ts.map +1 -1
  161. package/types/node/AeroflyConfigurationNode.test.d.ts +2 -0
  162. package/types/node/AeroflyConfigurationNode.test.d.ts.map +1 -0
  163. package/types/node/Convert copy.d.ts +15 -0
  164. package/types/node/Convert copy.d.ts.map +1 -0
  165. package/types/node/Convert.d.ts +15 -0
  166. package/types/node/Convert.d.ts.map +1 -0
  167. package/types/node/Convert.test.d.ts +2 -0
  168. package/types/node/Convert.test.d.ts.map +1 -0
  169. package/dist/index.test.js +0 -258
  170. package/src/index.test.ts +0 -297
package/.editorconfig CHANGED
@@ -10,3 +10,7 @@ charset = utf-8
10
10
  indent_style = space
11
11
  indent_size = 4
12
12
  max_line_length = 120
13
+
14
+ [*.md]
15
+ trim_trailing_whitespace = false
16
+ indent_size = 2
package/AGENTS.md ADDED
@@ -0,0 +1,43 @@
1
+ # AGENTS.md
2
+
3
+ ## Project Overview
4
+
5
+ This project provides custom mission support for Aerofly flight simulators. It includes TypeScript source code, DTOs, configuration files, and supporting documentation for mission creation and management.
6
+
7
+ ## Coding Guidelines
8
+
9
+ - **Explicit Class Properties:**
10
+ - All class properties must be explicitly defined in every class. Avoid using implicit or dynamic property assignment.
11
+ - **JSDoc Comments:**
12
+ - Add descriptive JSDoc comments to every class property and method. This improves code readability and enables rich IntelliSense in VS Code.
13
+ - Example:
14
+ ```typescript
15
+ /**
16
+ * @property {string} name The name of the mission
17
+ */
18
+ name: string;
19
+ ```
20
+ - **TypeScript Usage:**
21
+ - Use TypeScript types and interfaces for all data structures.
22
+ - Keep types up to date in the `types/` directory.
23
+
24
+ ## Contribution
25
+
26
+ - Follow the coding guidelines above for all new code and pull requests.
27
+ - See the `README.md` for setup and usage instructions.
28
+
29
+ ## Testing
30
+
31
+ - TypeScript files have corresponding test files with the same filename and an added `.test` before the extension (e.g., `AeroflyMission.ts` and `AeroflyMission.test.ts`).
32
+ - Run all tests using:
33
+ ```sh
34
+ npm test
35
+ ```
36
+ - To run a single test file, specify its name:
37
+ ```sh
38
+ node --test src/dto/AeroflyMission.test.ts
39
+ ```
40
+ - Create new tests by using the `node:test`
41
+ ```typescript
42
+ import { describe, it } from "node:test";
43
+ ```
package/CHANGELOG.md CHANGED
@@ -2,22 +2,33 @@
2
2
 
3
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
4
 
5
- ## 1.3.0
5
+ ## [1.4.0]
6
+
7
+ - Changed testing to standard Node.js testing suite
8
+ - Added `fuel_mass` and `payload_mass` properties
9
+ - Added `flight.mcf` / `main.mcf` DTO
10
+
11
+ ## [1.3.1]
12
+
13
+ - Re-introduced spacers between exported missions
14
+ - Re-introduced commented out nodes for exported missions
15
+
16
+ ## [1.3.0]
6
17
 
7
18
  - Changed TMC code generation with nodes
8
19
 
9
- ## 1.2.3
20
+ ## [1.2.3]
10
21
 
11
22
  - Internal restructuring of mission generation logic
12
23
  - Optimized waypoint handling and validation
13
24
  - Improved error handling for mission parsing
14
25
 
15
- ## 1.2.2
26
+ ## [1.2.2]
16
27
 
17
28
  - Added altitude constraint property for waypoints
18
29
  - Improved handling of cloud layers with better validation
19
30
 
20
- ## 1.2.1
31
+ ## [1.2.1]
21
32
 
22
33
  - Changed handling of checkpoints to support missions without checkpoints
23
34
  - Improved file generation for programmatic mission creation
@@ -31,7 +42,7 @@ This changelog documents all notable changes to the Aerofly Custom Missions proj
31
42
  - `before_start`
32
43
  - `pushback`
33
44
 
34
- ## 1.2.0
45
+ ## [1.2.0]
35
46
 
36
47
  - Added new cloud level properties:
37
48
  - `cirrus_cover` for high-altitude cloud coverage
@@ -39,13 +50,13 @@ This changelog documents all notable changes to the Aerofly Custom Missions proj
39
50
  - Added new waypoint property `fly_over` for precise waypoint navigation
40
51
  - Added `finish` property to mark mission completion points
41
52
 
42
- ## 1.1.1
53
+ ## [1.1.1]
43
54
 
44
55
  - Fixed styling issues in mission display
45
56
  - Improved UI consistency across different mission types
46
57
  - Enhanced error message formatting
47
58
 
48
- ## 1.1.0
59
+ ## [1.1.0]
49
60
 
50
61
  - Added new mission metadata properties:
51
62
  - `tags` for mission categorization
@@ -59,31 +70,31 @@ This changelog documents all notable changes to the Aerofly Custom Missions proj
59
70
  - `aerotow` for towed aircraft operations
60
71
  - Improved temperature property with better unit handling
61
72
 
62
- ## 1.0.4
73
+ ## [1.0.4]
63
74
 
64
75
  - Added comprehensive documentation for known issues and workarounds
65
76
  - Added `AeroflyMissionConditions.temperature` property with Celsius support
66
77
  - Improved error handling for weather conditions
67
78
 
68
- ## 1.0.3
79
+ ## [1.0.3]
69
80
 
70
81
  - Enhanced API documentation with examples
71
82
  - Added detailed parameter descriptions
72
83
  - Improved code documentation
73
84
 
74
- ## 1.0.2
85
+ ## [1.0.2]
75
86
 
76
87
  - Added shorthand properties for common mission parameters
77
88
  - Improved property access methods
78
89
  - Enhanced mission validation
79
90
 
80
- ## 1.0.1
91
+ ## [1.0.1]
81
92
 
82
93
  - Added initial API documentation
83
94
  - Improved code comments
84
95
  - Added basic usage examples
85
96
 
86
- ## 1.0.0
97
+ ## [1.0.0]
87
98
 
88
99
  - Initial release of Aerofly Custom Missions
89
100
  - Basic mission creation and editing functionality
package/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright © 2024 Frank Boës
3
+ Copyright © 2024-2026 Frank Boës
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
6
 
package/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  [Aerofly Flight Simulator 4](https://www.aerofly.com/) has a custom missions file `custom_missions_user.tmc` with a very unique format. To help you build this custom missions file, this JavaScript / TypeScript library offers Data Objects to create this file programmatically.
6
6
 
7
+ This simulator also defines flight plans and settings in the `main.mcf`, which is automatically loaded on simulator startup. This library contains a proposal for a `flight.mcf`, which is a stripped-down version of the `main.mcf` which only contains all information necessary for flight set-up. See [the documentation for `flight.mcf`](./docs/flight-mcf.md).
8
+
7
9
  This library is intended to work in modern browsers as well as [Node.js](https://nodejs.org/en).
8
10
 
9
11
  ## Installation
@@ -52,6 +54,9 @@ import {
52
54
 
53
55
  You might want to enable TypeScript type checking by adding `// @ts-check` as your first line in your scripts.
54
56
 
57
+ > [!NOTE]
58
+ > This library also contains classes for building the yet unsupported `flight.mcf`. Please refer to the code on how to use this. See [the documentation for `flight.mcf`](./docs/flight-mcf.md).
59
+
55
60
  ### Basic idea
56
61
 
57
62
  All objects are basic structures needed for the missions list. The constructors tell you which properties are required, and will start with sensible defaults for all other properties.
@@ -60,7 +65,7 @@ All objects can be exported as JSON or as string via the `toString()` methods. E
60
65
 
61
66
  ### Building a missions file
62
67
 
63
- A mission file contains one or multiple missions. Building this file starts with the outer container, wich contains the missions:
68
+ A `custom_missions_user.tmc` mission file contains one or multiple missions. Building this file starts with the outer container, wich contains the missions:
64
69
 
65
70
  ```javascript
66
71
  // Build a missions list
@@ -0,0 +1,13 @@
1
+ import { strict as assert } from "node:assert";
2
+ export const assertValidAeroflyStructure = (aeroflyString) => {
3
+ const openingBrackets = aeroflyString.match(/</g);
4
+ const closingBrackets = aeroflyString.match(/>/g);
5
+ const openingBrackets2 = aeroflyString.match(/\[/g);
6
+ const closingBrackets2 = aeroflyString.match(/\]/g);
7
+ assert.ok(openingBrackets?.length ?? 0 > 0, "Has opening <");
8
+ assert.strictEqual(openingBrackets?.length, closingBrackets?.length, "Number of <> matches");
9
+ assert.strictEqual(openingBrackets2?.length, closingBrackets2?.length, "Number of [] matches");
10
+ };
11
+ export const assertIncludes = (string, includes) => {
12
+ assert.ok(string.includes(includes), `Includes "${includes}"`);
13
+ };
@@ -0,0 +1,8 @@
1
+ import { assertValidAeroflyStructure } from "../check/TestHelpers.js";
2
+ import { describe, it } from "node:test";
3
+ describe("TestHelpers", () => {
4
+ it("should validate Aerofly structure correctly", () => {
5
+ const validString = "<>";
6
+ assertValidAeroflyStructure(validString);
7
+ });
8
+ });
@@ -0,0 +1,16 @@
1
+ import { AeroflyLocalizedText } from "./AeroflyLocalizedText.js";
2
+ import { strict as assert } from "node:assert";
3
+ import { assertValidAeroflyStructure } from "../check/TestHelpers.js";
4
+ import { describe, it } from "node:test";
5
+ describe("AeroflyLocalizedText", () => {
6
+ const localizedText = new AeroflyLocalizedText("de", "Test", "Test2");
7
+ it("should correctly map constructor values", () => {
8
+ assert.deepStrictEqual(localizedText.language, "de");
9
+ assert.deepStrictEqual(localizedText.title, "Test");
10
+ assert.deepStrictEqual(localizedText.description, "Test2");
11
+ assertValidAeroflyStructure(localizedText.toString());
12
+ });
13
+ it("should create a valid Aerofly mission", () => {
14
+ assertValidAeroflyStructure(localizedText.toString());
15
+ });
16
+ });
@@ -1,4 +1,4 @@
1
- import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
1
+ import { AeroflyConfigurationNode, AeroflyConfigurationNodeComment } from "../node/AeroflyConfigurationNode.js";
2
2
  import { AeroflyMissionConditions } from "./AeroflyMissionConditions.js";
3
3
  export const feetPerMeter = 3.28084;
4
4
  export const meterPerStatuteMile = 1609.344;
@@ -23,6 +23,8 @@ export class AeroflyMission {
23
23
  * @param {"cold_and_dark"|"before_start"|"taxi"|"takeoff"|"cruise"|"approach"|"landing"|"winch_launch"|"aerotow"|"pushback"} [additionalAttributes.flightSetting] of aircraft, like "taxi", "cruise"
24
24
  * @param {{name:string,livery:string,icao:string}} [additionalAttributes.aircraft] for this mission
25
25
  * @param {string} [additionalAttributes.callsign] of aircraft, uppercased
26
+ * @param {?number} [additionalAttributes.fuelMass] in kg
27
+ * @param {?number} [additionalAttributes.payloadMass] in kg
26
28
  * @param {object} [additionalAttributes.origin] position of aircraft, as well as name of starting airport. Position does not have match airport.
27
29
  * @param {object} [additionalAttributes.destination] position of aircraft, as well as name of destination airport. Position does not have match airport.
28
30
  * @param {?number} [additionalAttributes.distance] in meters
@@ -36,7 +38,7 @@ export class AeroflyMission {
36
38
  name: "c172",
37
39
  icao: "",
38
40
  livery: "",
39
- }, callsign = "", origin = {
41
+ }, callsign = "", fuelMass = null, payloadMass = null, origin = {
40
42
  icao: "",
41
43
  longitude: 0,
42
44
  latitude: 0,
@@ -60,6 +62,8 @@ export class AeroflyMission {
60
62
  this.flightSetting = flightSetting;
61
63
  this.aircraft = aircraft;
62
64
  this.callsign = callsign;
65
+ this.fuelMass = fuelMass;
66
+ this.payloadMass = payloadMass;
63
67
  this.origin = origin;
64
68
  this.destination = destination;
65
69
  this.distance = distance;
@@ -130,11 +134,17 @@ export class AeroflyMission {
130
134
  }
131
135
  element.appendChild("string8", "flight_setting", this.flightSetting);
132
136
  element.appendChild("string8u", "aircraft_name", this.aircraft.name);
133
- /*if (this.aircraft.livery) {
134
- mission.createChild("string8", "aircraft_livery", this.aircraft.livery);
135
- }*/
136
137
  element.appendChild("stringt8c", "aircraft_icao", this.aircraft.icao);
138
+ if (this.aircraft.livery) {
139
+ element.append(new AeroflyConfigurationNodeComment("string8", "aircraft_livery", this.aircraft.livery));
140
+ }
137
141
  element.appendChild("stringt8c", "callsign", this.callsign);
142
+ if (this.fuelMass !== null) {
143
+ element.append(new AeroflyConfigurationNodeComment("float64", "fuel_mass", this.fuelMass, `kg`));
144
+ }
145
+ if (this.payloadMass !== null) {
146
+ element.append(new AeroflyConfigurationNodeComment("float64", "payload_mass", this.payloadMass, `kg`));
147
+ }
138
148
  element.appendChild("stringt8c", "origin_icao", this.origin.icao);
139
149
  element.appendChild("tmvector2d", "origin_lon_lat", [this.origin.longitude, this.origin.latitude]);
140
150
  element.appendChild("float64", "origin_alt", this.origin.alt, `${Math.ceil(this.origin.alt * feetPerMeter)} ft MSL`);
@@ -0,0 +1,166 @@
1
+ import { AeroflyMission } from "./AeroflyMission.js";
2
+ import { strict as assert } from "node:assert";
3
+ import fs from "node:fs";
4
+ import { assertIncludes, assertValidAeroflyStructure } from "../check/TestHelpers.js";
5
+ import { describe, it } from "node:test";
6
+ import { AeroflyLocalizedText } from "./AeroflyLocalizedText.js";
7
+ import { AeroflyMissionCheckpoint } from "./AeroflyMissionCheckpoint.js";
8
+ import { AeroflyMissionConditions } from "./AeroflyMissionConditions.js";
9
+ import { AeroflyMissionConditionsCloud } from "./AeroflyMissionConditionsCloud.js";
10
+ import { AeroflyMissionsList } from "./AeroflyMissionsList.js";
11
+ import { AeroflyMissionTargetPlane } from "./AeroflyMissionTargetPlane.js";
12
+ describe("AeroflyMission", () => {
13
+ const mission = new AeroflyMission("Title", {
14
+ origin: {
15
+ icao: "XXXX",
16
+ longitude: 0,
17
+ latitude: 0,
18
+ dir: 0,
19
+ alt: 0,
20
+ },
21
+ destination: {
22
+ icao: "XXXX",
23
+ longitude: 0,
24
+ latitude: 0,
25
+ dir: 0,
26
+ alt: 0,
27
+ },
28
+ });
29
+ it("should correctly map constructor values", () => {
30
+ assert.strictEqual(mission.title, "Title");
31
+ });
32
+ it("should create a valid Aerofly mission", () => {
33
+ assertValidAeroflyStructure(mission.toString());
34
+ });
35
+ it("should create a valid XML mission", () => {
36
+ assertValidAeroflyStructure(mission.getElement().toXmlString());
37
+ });
38
+ it("should create AeroflyMissionConditions correctly", () => {
39
+ const conditions = new AeroflyMissionConditions({
40
+ time: new Date(Date.UTC(2024, 5, 14, 13, 15, 38)),
41
+ wind: {
42
+ direction: 190,
43
+ speed: 11,
44
+ gusts: 22,
45
+ },
46
+ turbulenceStrength: 1,
47
+ temperature: 21,
48
+ visibility: 14484.096000000001,
49
+ clouds: [
50
+ AeroflyMissionConditionsCloud.createInFeet(0.1, 5000),
51
+ AeroflyMissionConditionsCloud.createInFeet(0.2, 7500),
52
+ AeroflyMissionConditionsCloud.createInFeet(0.1, 9500),
53
+ ],
54
+ });
55
+ assert.strictEqual(conditions.wind.direction, 190);
56
+ assert.strictEqual(conditions.wind.speed, 11);
57
+ assert.strictEqual(conditions.wind.gusts, 22);
58
+ assert.strictEqual(conditions.turbulenceStrength, 1);
59
+ assert.strictEqual(conditions.temperature, 21);
60
+ assert.strictEqual(conditions.visibility, 14484.096000000001);
61
+ const checkpoints = [
62
+ new AeroflyMissionCheckpoint("KCCR", "origin", -122.057, 37.9897, {
63
+ altitude: 8,
64
+ }),
65
+ new AeroflyMissionCheckpoint("19L", "departure_runway", -122.05504061196366, 37.993168229891225, {
66
+ length: 844.2959729825288,
67
+ }),
68
+ new AeroflyMissionCheckpoint("24", "destination_runway", -70.60730234370952, 41.399093035543366, {
69
+ altitude: 20,
70
+ length: 1677.6191463161874,
71
+ frequency: 108700000,
72
+ }),
73
+ new AeroflyMissionCheckpoint("KMVY", "destination", -70.6139, 41.3934, {
74
+ altitude: 20,
75
+ flyOver: false,
76
+ }),
77
+ ];
78
+ const mission = new AeroflyMission("KCCR #1: Concord / Buchanan Field", {
79
+ checkpoints,
80
+ conditions,
81
+ description: `It is a gusty, clear early morning, and you are 8 NM to the north of the towered airport Concord / Buchanan Field (27ft). As the wind is 11 kts from 190°, the main landing runway is 19L (191° / 844m). Fly the pattern and land safely.
82
+
83
+ - Local tower / CTAF frequency: 123.90
84
+ - Local navigational aids: VOR/DME CCR (117.00) 3.4 NM to the north`,
85
+ flightSetting: "cruise",
86
+ aircraft: {
87
+ name: "c172",
88
+ livery: "default",
89
+ icao: "C172",
90
+ },
91
+ callsign: "N51911",
92
+ origin: {
93
+ icao: "KCCR",
94
+ longitude: -122.0736009331662,
95
+ latitude: 38.122300745843944,
96
+ dir: 174.37511511143452,
97
+ alt: 1066.799965862401,
98
+ },
99
+ destination: {
100
+ icao: "KMVY",
101
+ longitude: -70.6139,
102
+ latitude: 41.3934,
103
+ dir: 221,
104
+ alt: 20,
105
+ },
106
+ });
107
+ const missionList = new AeroflyMissionsList([mission]);
108
+ assert.strictEqual(missionList.missions.length, 1);
109
+ assert.strictEqual(missionList.missions[0].aircraft.name, "c172");
110
+ assert.strictEqual(missionList.missions[0].aircraft.icao, "C172");
111
+ let missionListString = missionList.toString();
112
+ assert.strictEqual(missionListString, missionList.toString());
113
+ assertIncludes(missionListString, "[origin]");
114
+ assertIncludes(missionListString, "[tmmission_definition]");
115
+ assertIncludes(missionListString, "[list_tmmission_checkpoint]");
116
+ assertIncludes(missionListString, "[tmmission_checkpoint]");
117
+ assertIncludes(missionListString, "[departure_runway]");
118
+ assertIncludes(missionListString, "[cloud_cover]");
119
+ assertIncludes(missionListString, "[cirrus_cover]");
120
+ assertIncludes(missionListString, "[tmmission_checkpoint][element][0]");
121
+ assertIncludes(missionListString, "[tmmission_checkpoint][element][1]");
122
+ assertIncludes(missionListString, "[tmmission_checkpoint][element][2]");
123
+ assertIncludes(missionListString, "[tmmission_checkpoint][element][3]");
124
+ assertIncludes(missionListString, "<[float64][length][844.2959729825288]>");
125
+ assert.ok(!missionListString.includes("[tags]"));
126
+ assert.ok(!missionListString.includes("[difficulty]"));
127
+ assert.ok(!missionListString.includes("[is_featured]"));
128
+ assert.ok(!missionListString.includes("[tmmission_definition_localized]"));
129
+ assert.ok(!missionListString.includes("[distance]"));
130
+ assert.ok(!missionListString.includes("[duration]"));
131
+ assert.ok(!missionListString.includes("[alt_cst]"));
132
+ assertValidAeroflyStructure(missionListString);
133
+ //console.log(missionListString);
134
+ mission.difficulty = 1.0;
135
+ mission.isFeatured = true;
136
+ mission.localizedTexts.push(new AeroflyLocalizedText("de", "Landeübung #1", "Probier die Landung"));
137
+ mission.distance = 1400;
138
+ mission.duration = 2 * 60 * 60;
139
+ mission.tags.push("approach");
140
+ mission.tags.push("pattern");
141
+ mission.finish = new AeroflyMissionTargetPlane(0, 1, 2);
142
+ mission.tutorialName = "c172";
143
+ mission.isScheduled = true;
144
+ missionListString = missionList.toString();
145
+ assertIncludes(missionListString, "[tags]");
146
+ assertIncludes(missionListString, "[difficulty]");
147
+ assertIncludes(missionListString, "[is_featured]");
148
+ assertIncludes(missionListString, "true");
149
+ assertIncludes(missionListString, "[tmmission_definition_localized]");
150
+ assertIncludes(missionListString, "Landeübung");
151
+ assertIncludes(missionListString, "[distance]");
152
+ assertIncludes(missionListString, "[duration]");
153
+ assertIncludes(missionListString, "[finish]");
154
+ assertIncludes(missionListString, "[tutorial_name]");
155
+ assertIncludes(missionListString, "[is_scheduled]");
156
+ assertValidAeroflyStructure(missionListString);
157
+ //console.dir(missionList.missions[0], { depth: null });
158
+ //console.log(missionListString);
159
+ //console.log(missionList.getElement().toXmlString());
160
+ const file = new AeroflyMissionsList([mission]);
161
+ const fileContent = file.toString();
162
+ fs.writeFileSync("docs/custom_missions_user.tmc", fileContent);
163
+ const xmlContent = file.toXmlString();
164
+ fs.writeFileSync("docs/custom_missions_user.xml", xmlContent);
165
+ });
166
+ });
@@ -15,7 +15,7 @@ export class AeroflyMissionCheckpoint {
15
15
  * @param {"origin"|"departure_runway"|"departure"|"waypoint"|"arrival"|"approach"|"destination_runway"|"destination"} type Type of checkpoint, like "departure_runway"
16
16
  * @param {number} longitude easting, using the World Geodetic
17
17
  * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
18
- * of decimal degrees; -180..180
18
+ * of decimal degrees; [-180,180]
19
19
  * @param {number} latitude northing, using the World Geodetic
20
20
  * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
21
21
  * of decimal degrees; -90..90
@@ -0,0 +1,41 @@
1
+ import { AeroflyMissionCheckpoint } from "./AeroflyMissionCheckpoint.js";
2
+ import { strict as assert } from "node:assert";
3
+ import { assertValidAeroflyStructure, assertIncludes } from "../check/TestHelpers.js";
4
+ import { describe, it } from "node:test";
5
+ describe("AeroflyMissionCheckpoint", () => {
6
+ it("should correctly map constructor values", () => {
7
+ const checkpoint = new AeroflyMissionCheckpoint("TEST", "waypoint", 15, 20, {
8
+ altitude_feet: 1000,
9
+ flyOver: true,
10
+ length_feet: 500,
11
+ });
12
+ assert.strictEqual(checkpoint.name, "TEST");
13
+ assert.strictEqual(checkpoint.type, "waypoint");
14
+ assert.strictEqual(checkpoint.longitude, 15);
15
+ assert.strictEqual(checkpoint.latitude, 20);
16
+ assert.strictEqual(checkpoint.flyOver, true);
17
+ assert.strictEqual(Math.round(checkpoint.altitude_feet), 1000);
18
+ assert.strictEqual(Math.round(checkpoint.altitude), Math.round(1000 * 0.3048));
19
+ assert.strictEqual(Math.round(checkpoint.length_feet), 500);
20
+ assert.strictEqual(Math.round(checkpoint.length ?? 0), Math.round(500 * 0.3048));
21
+ });
22
+ it("should correctly map values from properties", () => {
23
+ const checkpoint = new AeroflyMissionCheckpoint("TEST", "waypoint", 15, 20);
24
+ let checkpointString = checkpoint.toString();
25
+ assertIncludes(checkpointString, "[type]");
26
+ assertIncludes(checkpointString, "[name]");
27
+ assertIncludes(checkpointString, "[lon_lat]");
28
+ assertIncludes(checkpointString, "15 20");
29
+ assertIncludes(checkpointString, "[altitude]");
30
+ assert.ok(!checkpointString.includes("[fly_over]"));
31
+ assert.ok(!checkpointString.includes("[alt_cst]"));
32
+ checkpoint.altitudeConstraint = false;
33
+ checkpoint.flyOver = false;
34
+ checkpoint.altitude_feet = 1000;
35
+ checkpoint.length_feet = 500;
36
+ checkpointString = checkpoint.toString();
37
+ assertIncludes(checkpointString, "[fly_over]");
38
+ assertIncludes(checkpointString, "[alt_cst]");
39
+ assertValidAeroflyStructure(checkpointString);
40
+ });
41
+ });
@@ -15,8 +15,8 @@ export class AeroflyMissionConditions {
15
15
  * @param {Date} [additionalAttributes.time] of flight plan. Relevant is the UTC part, so
16
16
  * consider setting this date in UTC.
17
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
18
+ * @param {number} [additionalAttributes.turbulenceStrength] normalized value [0,1]
19
+ * @param {number} [additionalAttributes.thermalStrength] normalized value [0,1]
20
20
  * @param {number} [additionalAttributes.visibility] in meters
21
21
  * @param {?number} [additionalAttributes.visibility_sm] in statute miles, will overwrite visibility
22
22
  * @param {?number} [additionalAttributes.temperature] in °C, will overwrite thermalStrength
@@ -26,7 +26,7 @@ export class AeroflyMissionConditions {
26
26
  direction: 0,
27
27
  speed: 0,
28
28
  gusts: 0,
29
- }, turbulenceStrength = 0, thermalStrength = 0, visibility = 25000, visibility_sm = 0, temperature = 0, clouds = [], } = {}) {
29
+ }, turbulenceStrength = 0, thermalStrength = 0, visibility = 25_000, visibility_sm = 0, temperature = 0, clouds = [], } = {}) {
30
30
  /**
31
31
  * @property {AeroflyMissionConditionsCloud[]} clouds for the whole flight
32
32
  */
@@ -90,9 +90,7 @@ export class AeroflyMissionConditions {
90
90
  * @returns {AeroflyConfigurationNode[]} cloud elements
91
91
  */
92
92
  getCloudElements() {
93
- return this.clouds
94
- .slice(0, 2) // Aerofly FS4 supports max 2 cloud layers
95
- .flatMap((c, index) => c.getElements(index));
93
+ return this.clouds.flatMap((c, index) => c.getElements(index));
96
94
  }
97
95
  /**
98
96
  * @returns {string} to use in Aerofly FS4's `custom_missions_user.tmc`
@@ -0,0 +1,33 @@
1
+ import { AeroflyMissionConditions } from "./AeroflyMissionConditions.js";
2
+ import { strict as assert } from "node:assert";
3
+ import { assertValidAeroflyStructure } from "../check/TestHelpers.js";
4
+ import { describe, it } from "node:test";
5
+ describe("AeroflyMissionConditions", () => {
6
+ it("should handle visibility in meters correctly when set via property", () => {
7
+ const conditions = new AeroflyMissionConditions();
8
+ conditions.visibility = 15_000;
9
+ assert.deepStrictEqual(conditions.visibility, 15_000);
10
+ });
11
+ it("should handle visibility in statute miles correctly when set via property", () => {
12
+ const conditions = new AeroflyMissionConditions();
13
+ conditions.visibility_sm = 10;
14
+ assert.notDeepStrictEqual(conditions.visibility, 10);
15
+ assert.deepStrictEqual(Math.round(conditions.visibility_sm), 10);
16
+ assertValidAeroflyStructure(conditions.toString());
17
+ });
18
+ it("should handle visibility in meters correctly when set via constructor", () => {
19
+ const conditions = new AeroflyMissionConditions({
20
+ visibility: 15_000,
21
+ });
22
+ assert.deepStrictEqual(conditions.visibility, 15_000);
23
+ assertValidAeroflyStructure(conditions.toString());
24
+ });
25
+ it("should handle visibility in statute miles correctly when set via constructor", () => {
26
+ const conditions = new AeroflyMissionConditions({
27
+ visibility_sm: 10,
28
+ });
29
+ assert.notDeepStrictEqual(conditions.visibility, 10);
30
+ assert.deepStrictEqual(Math.round(conditions.visibility_sm), 10);
31
+ assertValidAeroflyStructure(conditions.toString());
32
+ });
33
+ });
@@ -1,4 +1,4 @@
1
- import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
1
+ import { AeroflyConfigurationNode, AeroflyConfigurationNodeComment } from "../node/AeroflyConfigurationNode.js";
2
2
  import { feetPerMeter } from "./AeroflyMission.js";
3
3
  /**
4
4
  * @class
@@ -10,7 +10,7 @@ import { feetPerMeter } from "./AeroflyMission.js";
10
10
  */
11
11
  export class AeroflyMissionConditionsCloud {
12
12
  /**
13
- * @param {number} cover 0..1, percentage
13
+ * @param {number} cover normalized value [0,1]
14
14
  * @param {number} base altitude in meters AGL
15
15
  */
16
16
  constructor(cover, base) {
@@ -18,7 +18,7 @@ export class AeroflyMissionConditionsCloud {
18
18
  this.base = base;
19
19
  }
20
20
  /**
21
- * @param {number} cover 0..1, percentage
21
+ * @param {number} cover normalized value [0,1]
22
22
  * @param {number} base_feet altitude, but in feet AGL instead of meters AGL
23
23
  * @returns {AeroflyMissionConditionsCloud} self
24
24
  */
@@ -73,10 +73,15 @@ export class AeroflyMissionConditionsCloud {
73
73
  }
74
74
  };
75
75
  const indexString = getIndexString(index);
76
- return [
77
- new AeroflyConfigurationNode("float64", `${indexString}_cover`, this.cover ?? 0, this.cover_code),
78
- new AeroflyConfigurationNode("float64", `${indexString}_base`, this.base, `${this.base_feet} ft AGL`),
79
- ];
76
+ return index <= 1
77
+ ? [
78
+ new AeroflyConfigurationNode("float64", `${indexString}_cover`, this.cover ?? 0, this.cover_code),
79
+ new AeroflyConfigurationNode("float64", `${indexString}_base`, this.base, `${this.base_feet} ft AGL`),
80
+ ]
81
+ : [
82
+ new AeroflyConfigurationNodeComment("float64", `${indexString}_cover`, this.cover ?? 0, this.cover_code),
83
+ new AeroflyConfigurationNodeComment("float64", `${indexString}_base`, this.base, `${this.base_feet} ft AGL`),
84
+ ];
80
85
  }
81
86
  /**
82
87
  * @param {number} index if used in an array will set the array index
@@ -0,0 +1,25 @@
1
+ import { AeroflyMissionConditionsCloud } from "./AeroflyMissionConditionsCloud.js";
2
+ import { strict as assert } from "node:assert";
3
+ import { assertValidAeroflyStructure } from "../check/TestHelpers.js";
4
+ import { describe, it } from "node:test";
5
+ describe("AeroflyMissionConditionsCloud", () => {
6
+ it("should create clouds with indentation", () => {
7
+ const cloud = new AeroflyMissionConditionsCloud(0, 0);
8
+ assert.deepStrictEqual(cloud.cover, 0);
9
+ assert.deepStrictEqual(cloud.base, 0);
10
+ assertValidAeroflyStructure(cloud.toString());
11
+ assertValidAeroflyStructure(cloud.toString(0));
12
+ assertValidAeroflyStructure(cloud.toString(1));
13
+ assertValidAeroflyStructure(cloud.toString(2));
14
+ assertValidAeroflyStructure(cloud.toString(3));
15
+ });
16
+ it("should assign values correctly from constructor and properties", () => {
17
+ const cloud = new AeroflyMissionConditionsCloud(1, 1000);
18
+ assert.deepStrictEqual(cloud.cover, 1);
19
+ assert.deepStrictEqual(cloud.base, 1000);
20
+ cloud.base_feet = 1000;
21
+ assert.notDeepStrictEqual(cloud.base, 1000);
22
+ assert.deepStrictEqual(Math.round(cloud.base_feet), 1000);
23
+ assertValidAeroflyStructure(cloud.toString());
24
+ });
25
+ });
@@ -8,7 +8,7 @@ export class AeroflyMissionTargetPlane {
8
8
  *
9
9
  * @param {number} longitude easting, using the World Geodetic
10
10
  * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
11
- * of decimal degrees; -180..180
11
+ * of decimal degrees; [-180,180]
12
12
  * @param {number}latitude northing, using the World Geodetic
13
13
  * System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
14
14
  * of decimal degrees; -90..90