@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.
- package/.editorconfig +1 -5
- package/.eslintrc.json +24 -0
- package/CHANGELOG.md +59 -21
- package/dist/dto/AeroflyLocalizedText.js +44 -0
- package/dist/dto/AeroflyMission.js +173 -0
- package/dist/dto/AeroflyMissionCheckpoint.js +127 -0
- package/dist/dto/AeroflyMissionConditions.js +124 -0
- package/dist/dto/AeroflyMissionConditionsCloud.js +90 -0
- package/dist/dto/AeroflyMissionTargetPlane.js +35 -0
- package/dist/dto/AeroflyMissionsList.js +36 -0
- package/dist/index.js +7 -634
- package/dist/index.test.js +17 -11
- package/dist/node/AeroflyConfigurationNode.js +82 -0
- package/docs/custom_missions_user.tmc +3 -6
- package/docs/custom_missions_user.xml +100 -0
- package/package.json +9 -10
- package/src/dto/AeroflyLocalizedText.ts +74 -0
- package/src/dto/AeroflyMission.ts +372 -0
- package/src/dto/AeroflyMissionCheckpoint.ts +234 -0
- package/src/dto/AeroflyMissionConditions.ts +189 -0
- package/src/dto/AeroflyMissionConditionsCloud.ts +111 -0
- package/src/dto/AeroflyMissionTargetPlane.ts +62 -0
- package/src/dto/AeroflyMissionsList.ts +52 -0
- package/src/index.test.ts +22 -25
- package/src/index.ts +7 -1099
- package/src/node/AeroflyConfigurationNode.ts +89 -0
- package/types/AeroflyMissionCheckpoint.d.ts +140 -0
- package/types/AeroflyMissionTargetPlane.d.ts +40 -0
- package/types/dto/AeroflyConfigFileSet.d.ts +10 -0
- package/types/dto/AeroflyConfigFileSet.d.ts.map +1 -0
- package/types/dto/AeroflyConfiguration.interface.d.ts +4 -0
- package/types/dto/AeroflyConfiguration.interface.d.ts.map +1 -0
- package/types/dto/AeroflyLocalizedText.d.ts +58 -0
- package/types/dto/AeroflyLocalizedText.d.ts.map +1 -0
- package/types/dto/AeroflyMission.d.ts +163 -0
- package/types/dto/AeroflyMission.d.ts.map +1 -0
- package/types/dto/AeroflyMissionCheckpoint.d.ts +126 -0
- package/types/dto/AeroflyMissionCheckpoint.d.ts.map +1 -0
- package/types/dto/AeroflyMissionConditions.d.ts +102 -0
- package/types/dto/AeroflyMissionConditions.d.ts.map +1 -0
- package/types/dto/AeroflyMissionConditionsCloud.d.ts +57 -0
- package/types/dto/AeroflyMissionConditionsCloud.d.ts.map +1 -0
- package/types/dto/AeroflyMissionTargetPlane.d.ts +45 -0
- package/types/dto/AeroflyMissionTargetPlane.d.ts.map +1 -0
- package/types/dto/AeroflyMissionsList.d.ts +30 -0
- package/types/dto/AeroflyMissionsList.d.ts.map +1 -0
- package/types/dto/basicTypes.d.ts +1 -0
- package/types/index.d.ts +7 -585
- package/types/index.d.ts.map +1 -1
- package/types/node/AeroflyConfigurationNode.d.ts +14 -0
- package/types/node/AeroflyConfigurationNode.d.ts.map +1 -0
- package/eslint.config.js +0 -29
package/.editorconfig
CHANGED
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
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
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
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
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
|
-
-
|
|
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
|
|
30
|
-
-
|
|
31
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
+
}
|