@fboes/aerofly-custom-missions 1.12.0 → 1.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -1
- package/dist/dto-flight/AeroflyNavRouteAirports.js +3 -3
- package/dist/dto-flight/AeroflyNavRouteBase.js +63 -11
- package/dist/dto-flight/AeroflyNavRouteBase.test.js +56 -0
- package/dist/dto-flight/AeroflyNavRouteRunway.js +9 -9
- package/dist/dto-flight/AeroflyNavRouteTransition.js +8 -7
- package/dist/dto-flight/AeroflyNavRouteWaypoint.js +17 -9
- package/dist/dto-flight/AeroflyNavigationConfig.js +4 -4
- package/dist/dto-flight/AeroflySettingsFlight.js +16 -15
- package/dist/dto-flight/AeroflySettingsFlight.test.js +0 -22
- package/dist/dto-flight/AeroflySettingsFuelLoad.js +6 -6
- package/dist/node/AeroflyTypes.js +63 -0
- package/dist/node/Convert.js +137 -71
- package/dist/node/Convert.test.js +58 -28
- package/docs/flight.json +13 -10
- package/docs/flight.mcf +14 -10
- package/docs/flight.xml +14 -10
- package/package.json +1 -1
- package/src/dto-flight/AeroflyFlight.ts +0 -0
- package/src/dto-flight/AeroflyNavRouteAirports.ts +3 -3
- package/src/dto-flight/AeroflyNavRouteBase.test.ts +65 -0
- package/src/dto-flight/AeroflyNavRouteBase.ts +75 -13
- package/src/dto-flight/AeroflyNavRouteRunway.ts +9 -9
- package/src/dto-flight/AeroflyNavRouteTransition.ts +8 -7
- package/src/dto-flight/AeroflyNavRouteWaypoint.ts +24 -9
- package/src/dto-flight/AeroflyNavigationConfig.ts +4 -4
- package/src/dto-flight/AeroflySettingsFlight.test.ts +0 -30
- package/src/dto-flight/AeroflySettingsFlight.ts +31 -18
- package/src/dto-flight/AeroflySettingsFuelLoad.ts +11 -7
- package/src/node/AeroflyTypes.ts +74 -0
- package/src/node/Convert.test.ts +129 -42
- package/src/node/Convert.ts +172 -81
- package/types/dto-flight/AeroflyNavRouteBase.d.ts +26 -4
- package/types/dto-flight/AeroflyNavRouteBase.d.ts.map +1 -1
- package/types/dto-flight/AeroflyNavRouteBase.test.d.ts +2 -0
- package/types/dto-flight/AeroflyNavRouteBase.test.d.ts.map +1 -0
- package/types/dto-flight/AeroflyNavRouteRunway.d.ts +1 -1
- package/types/dto-flight/AeroflyNavRouteRunway.d.ts.map +1 -1
- package/types/dto-flight/AeroflyNavRouteTransition.d.ts +1 -1
- package/types/dto-flight/AeroflyNavRouteTransition.d.ts.map +1 -1
- package/types/dto-flight/AeroflyNavRouteWaypoint.d.ts +9 -1
- package/types/dto-flight/AeroflyNavRouteWaypoint.d.ts.map +1 -1
- package/types/dto-flight/AeroflySettingsFlight.d.ts +9 -4
- package/types/dto-flight/AeroflySettingsFlight.d.ts.map +1 -1
- package/types/dto-flight/AeroflySettingsFuelLoad.d.ts +1 -1
- package/types/dto-flight/AeroflySettingsFuelLoad.d.ts.map +1 -1
- package/types/node/AeroflyTypes.d.ts +40 -0
- package/types/node/AeroflyTypes.d.ts.map +1 -0
- package/types/node/Convert.d.ts +78 -16
- package/types/node/Convert.d.ts.map +1 -1
- package/AGENTS.md +0 -43
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,18 @@
|
|
|
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
|
-
## [
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [1.13.1] - 2026-07-24
|
|
8
|
+
|
|
9
|
+
- Fixed orientation / heading parsing
|
|
10
|
+
|
|
11
|
+
## [1.13.0] - 2026-07-23
|
|
12
|
+
|
|
13
|
+
- Made unit conversions more precise
|
|
14
|
+
- Added correct UID generator
|
|
15
|
+
|
|
16
|
+
## [1.12.0] - 2026-07-01
|
|
6
17
|
|
|
7
18
|
- Added unofficial `_cruiseSpeed` property to `AeroflyNavigationConfig`
|
|
8
19
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
2
|
-
import {
|
|
2
|
+
import { convertFeetToMeter, convertMeterToFeet } from "../node/Convert.js";
|
|
3
3
|
import { AeroflyNavRouteBase } from "./AeroflyNavRouteBase.js";
|
|
4
4
|
class AeroflyNavRouteAirport extends AeroflyNavRouteBase {
|
|
5
5
|
/**
|
|
@@ -27,10 +27,10 @@ class AeroflyNavRouteAirport extends AeroflyNavRouteBase {
|
|
|
27
27
|
* @returns {number | null} elevation in feet, null if not set
|
|
28
28
|
*/
|
|
29
29
|
get elevation_ft() {
|
|
30
|
-
return this.elevation !== null ?
|
|
30
|
+
return this.elevation !== null ? convertMeterToFeet(this.elevation) : null;
|
|
31
31
|
}
|
|
32
32
|
set elevation_ft(elevation_ft) {
|
|
33
|
-
this.elevation = elevation_ft !== null ?
|
|
33
|
+
this.elevation = elevation_ft !== null ? convertFeetToMeter(elevation_ft) : null;
|
|
34
34
|
}
|
|
35
35
|
getElement(index = 0) {
|
|
36
36
|
const element = super.getElement(index);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AeroflyConfigurationNode
|
|
1
|
+
import { convertLonLatToVector, convertVectorToLonLat } from "../node/Convert.js";
|
|
2
|
+
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
3
|
+
import { AeroflyVector3Float } from "../node/AeroflyTypes.js";
|
|
3
4
|
export class AeroflyNavRouteBase {
|
|
4
5
|
/**
|
|
5
6
|
* @property {AeroflyNavRouteType} type like "origin", "departure_runway", "departure", "waypoint", "arrival", "approach", "destination_runway" or "destination"
|
|
@@ -18,7 +19,7 @@ export class AeroflyNavRouteBase {
|
|
|
18
19
|
*/
|
|
19
20
|
latitude;
|
|
20
21
|
/**
|
|
21
|
-
* @property {?bigint} uid unique identifier for the waypoint, must match Aerofly FS internal UID if used in an existing mission
|
|
22
|
+
* @property {?bigint} uid unique identifier for the waypoint, must match Aerofly FS internal UID if used in an existing mission. Obviously the UID encodes the geographic position as well as the name of the waypoint.
|
|
22
23
|
*/
|
|
23
24
|
uid;
|
|
24
25
|
/**
|
|
@@ -27,7 +28,7 @@ export class AeroflyNavRouteBase {
|
|
|
27
28
|
* @param {number} longitude WGS84
|
|
28
29
|
* @param {number} latitude WGS84
|
|
29
30
|
* @param {object} [options] additional options for the waypoint
|
|
30
|
-
* @param {?bigint} [options.uid] unique identifier for the waypoint, must match Aerofly FS internal UID if used in an existing mission
|
|
31
|
+
* @param {?bigint} [options.uid] unique identifier for the waypoint, must match Aerofly FS internal UID if used in an existing mission. Obviously the UID encodes the geographic position as well as the name of the waypoint.
|
|
31
32
|
*/
|
|
32
33
|
constructor(type, identifier, longitude, latitude, { uid = null } = {}) {
|
|
33
34
|
this.type = type;
|
|
@@ -40,10 +41,10 @@ export class AeroflyNavRouteBase {
|
|
|
40
41
|
* @returns {AeroflyVector3Float} to use in Aerofly FS4's `main.mcf`
|
|
41
42
|
*/
|
|
42
43
|
get position() {
|
|
43
|
-
return
|
|
44
|
+
return convertLonLatToVector(this.longitude, this.latitude, 0);
|
|
44
45
|
}
|
|
45
46
|
set position(position) {
|
|
46
|
-
const latLonAlt =
|
|
47
|
+
const latLonAlt = convertVectorToLonLat(position);
|
|
47
48
|
this.longitude = latLonAlt.longitude;
|
|
48
49
|
this.latitude = latLonAlt.latitude;
|
|
49
50
|
}
|
|
@@ -53,18 +54,69 @@ export class AeroflyNavRouteBase {
|
|
|
53
54
|
* @returns {this} for chaining
|
|
54
55
|
*/
|
|
55
56
|
setPosition(position) {
|
|
56
|
-
this.position = position;
|
|
57
|
+
this.position = AeroflyVector3Float.fromArray(position);
|
|
57
58
|
return this;
|
|
58
59
|
}
|
|
59
60
|
getElement(index = 0) {
|
|
60
61
|
const element = new AeroflyConfigurationNode("tmnav_route_" + this.type, this.identifier, String(index))
|
|
61
62
|
.appendChild("string8u", "Identifier", this.identifier)
|
|
62
|
-
.appendChild("vector3_float64", "Position", this.position, `Lon ${this.longitude.toFixed(6)}, Lat ${this.latitude.toFixed(6)}`)
|
|
63
|
-
.
|
|
64
|
-
? new AeroflyConfigurationNode("uint64", "Uid", this.uid)
|
|
65
|
-
: new AeroflyConfigurationNodeComment("uint64", "Uid", ""));
|
|
63
|
+
.appendChild("vector3_float64", "Position", this.position.toArray(), `Lon ${this.longitude.toFixed(6)}, Lat ${this.latitude.toFixed(6)}`)
|
|
64
|
+
.appendChild("uint64", "Uid", this.uid ?? this.getUidFallback(), this.uid ? "" : "Fallback UID, not matching Aerofly FS internal UID");
|
|
66
65
|
return element;
|
|
67
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* @returns {bigint} 24 bit longitude
|
|
69
|
+
* @see https://www.aerofly.com/community/forum/index.php?thread/29490-navdata-coordinates-to-uid/
|
|
70
|
+
*/
|
|
71
|
+
encodeLongitude() {
|
|
72
|
+
let scaled = this.longitude / 180;
|
|
73
|
+
scaled = 65536.0 * (0.5 + 0.5 * scaled); // 16 bit
|
|
74
|
+
return BigInt(Math.round(256.0 * scaled + 0.5)); // 24 bit
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* @returns {bigint} 24 bit latitude
|
|
78
|
+
* @see https://www.aerofly.com/community/forum/index.php?thread/29490-navdata-coordinates-to-uid/
|
|
79
|
+
*/
|
|
80
|
+
encodeLatitude() {
|
|
81
|
+
const worldGridConstantA = 2.3311223704144;
|
|
82
|
+
let scaled = this.latitude / 180;
|
|
83
|
+
scaled = Math.tan(worldGridConstantA * scaled) / worldGridConstantA;
|
|
84
|
+
scaled = 65536.0 * (0.5 + 0.5 * scaled); // 16 bit
|
|
85
|
+
return BigInt(Math.round(256.0 * scaled + 0.5)); // 24 bit
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* @returns {number} 16 bit type
|
|
89
|
+
*/
|
|
90
|
+
encodeWaypointType() {
|
|
91
|
+
// Type code (airport (0500) / runway (0800) / SID (4400) / STAR (4800) / Approach (4C00) / RNAV waypoint (C000) / airways (4000)
|
|
92
|
+
switch (this.type) {
|
|
93
|
+
case "origin":
|
|
94
|
+
case "destination":
|
|
95
|
+
return 0x0500;
|
|
96
|
+
case "departure_runway":
|
|
97
|
+
case "destination_runway":
|
|
98
|
+
return 0x0800;
|
|
99
|
+
case "departure":
|
|
100
|
+
return 0x4400;
|
|
101
|
+
case "arrival":
|
|
102
|
+
return 0x4800;
|
|
103
|
+
case "approach":
|
|
104
|
+
return 0x4c00;
|
|
105
|
+
}
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* @returns {bigint} Packs both into a single 64-bit value:
|
|
110
|
+
* - Bits 63..40 → longitude (24 bit)
|
|
111
|
+
* - Bits 39..16 → latitude (24 bit)
|
|
112
|
+
* - Bits 15..0 → payload (16 bit)
|
|
113
|
+
* @see https://www.aerofly.com/community/forum/index.php?thread/29490-navdata-coordinates-to-uid/
|
|
114
|
+
*/
|
|
115
|
+
getUidFallback() {
|
|
116
|
+
return ((this.encodeLongitude() << 40n) |
|
|
117
|
+
(this.encodeLatitude() << 16n) |
|
|
118
|
+
(BigInt(this.encodeWaypointType()) & 0xffffn));
|
|
119
|
+
}
|
|
68
120
|
toJSON() {
|
|
69
121
|
return {
|
|
70
122
|
...this,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import { strict as assert } from "node:assert";
|
|
3
|
+
import { AeroflyNavRouteDestination } from "../index.js";
|
|
4
|
+
describe("AeroflyNavRouteBase", () => {
|
|
5
|
+
it("should create UIDs almost matching the ingame UIDs", () => {
|
|
6
|
+
const testCases = [
|
|
7
|
+
{
|
|
8
|
+
wp: new AeroflyNavRouteDestination("MAX-NE", 179.999, 89.999),
|
|
9
|
+
assertUid: 18446694595666707712n,
|
|
10
|
+
ingameUid: 0n,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
wp: new AeroflyNavRouteDestination("MAX-SW", -179.999, -89.999),
|
|
14
|
+
assertUid: 51677066167552n,
|
|
15
|
+
ingameUid: 0n,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
wp: new AeroflyNavRouteDestination("KEYW", -81.7599558, 24.5561197),
|
|
19
|
+
assertUid: 5033914504046314752n,
|
|
20
|
+
ingameUid: 5033914504046249216n,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
wp: new AeroflyNavRouteDestination("EGLL", -0.45277777, 51.47138888),
|
|
24
|
+
assertUid: 9200173076798702848n,
|
|
25
|
+
ingameUid: 9199731073154483456n,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
wp: new AeroflyNavRouteDestination("YSSY", 151.177, -33.4961),
|
|
29
|
+
assertUid: 16969826619723154688n,
|
|
30
|
+
ingameUid: 16969837613165511936n,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
wp: new AeroflyNavRouteDestination("RJTT", 139.779, 35.709),
|
|
34
|
+
assertUid: 16385782762532177152n,
|
|
35
|
+
ingameUid: 16385890514078663936n,
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
for (const testCase of testCases) {
|
|
39
|
+
assert.strictEqual(testCase.wp.getUidFallback(), testCase.assertUid, `${testCase.wp.identifier} was ${testCase.ingameUid}`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
it("should unpack UIDs for airports to code 1280", () => {
|
|
43
|
+
const unpackPayload = (packed) => {
|
|
44
|
+
return Number(packed & 0xffffn);
|
|
45
|
+
};
|
|
46
|
+
const testCases = [
|
|
47
|
+
{ uid: 5033914504046314752n, code: 1280, airport: "KEYW" },
|
|
48
|
+
{ uid: 9200173076798702848n, code: 1280, airport: "EGLL" },
|
|
49
|
+
{ uid: 16969826619723154688n, code: 1280, airport: "YSSY" },
|
|
50
|
+
{ uid: 16385782762532177152n, code: 1280, airport: "RJTT" },
|
|
51
|
+
];
|
|
52
|
+
for (const testCase of testCases) {
|
|
53
|
+
assert.strictEqual(unpackPayload(testCase.uid), testCase.code, testCase.airport);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { convertDirectionToHeading, convertFeetToMeter, convertMeterToFeet } from "../node/Convert.js";
|
|
2
2
|
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
3
3
|
import { AeroflyNavRouteBase } from "./AeroflyNavRouteBase.js";
|
|
4
|
+
import { AeroflyVector3Float } from "../node/AeroflyTypes.js";
|
|
4
5
|
class AeroflyNavRouteRunway extends AeroflyNavRouteBase {
|
|
5
6
|
/**
|
|
6
7
|
* @property {?number} direction_degree runway direction in degrees, null if not set
|
|
@@ -27,34 +28,33 @@ class AeroflyNavRouteRunway extends AeroflyNavRouteBase {
|
|
|
27
28
|
* @returns {number | null} elevation in feet, null if not set
|
|
28
29
|
*/
|
|
29
30
|
get elevation_ft() {
|
|
30
|
-
return this.elevation !== null ?
|
|
31
|
+
return this.elevation !== null ? convertMeterToFeet(this.elevation) : null;
|
|
31
32
|
}
|
|
32
33
|
set elevation_ft(elevation_ft) {
|
|
33
|
-
this.elevation = elevation_ft !== null ?
|
|
34
|
+
this.elevation = elevation_ft !== null ? convertFeetToMeter(elevation_ft) : null;
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
36
37
|
* @returns {number | null} runway length in feet, null if not set
|
|
37
38
|
*/
|
|
38
39
|
get runwayLength_ft() {
|
|
39
|
-
return this.runwayLength !== null ?
|
|
40
|
+
return this.runwayLength !== null ? convertMeterToFeet(this.runwayLength) : null;
|
|
40
41
|
}
|
|
41
42
|
set runwayLength_ft(runwayLength_ft) {
|
|
42
|
-
this.runwayLength = runwayLength_ft !== null ?
|
|
43
|
+
this.runwayLength = runwayLength_ft !== null ? convertFeetToMeter(runwayLength_ft) : null;
|
|
43
44
|
}
|
|
44
45
|
/**
|
|
45
46
|
* @returns {AeroflyVector3Float | null} runway direction, null if not set
|
|
46
47
|
*/
|
|
47
48
|
get direction() {
|
|
48
|
-
// TODO:
|
|
49
|
-
return this.direction_degree !== null ? [0, 0, 0] : null;
|
|
49
|
+
return this.direction_degree !== null ? new AeroflyVector3Float(0, 0, 0) : null; // TODO: Get direction vector
|
|
50
50
|
}
|
|
51
51
|
set direction(direction) {
|
|
52
|
-
this.direction_degree =
|
|
52
|
+
this.direction_degree = convertDirectionToHeading(direction, this.position);
|
|
53
53
|
}
|
|
54
54
|
getElement(index = 0) {
|
|
55
55
|
const element = super.getElement(index);
|
|
56
56
|
if (this.direction !== null) {
|
|
57
|
-
element.appendChild("vector3_float64", "Direction", this.direction, `Runway direction ${Math.round(this.direction_degree ?? 0)}°`);
|
|
57
|
+
element.appendChild("vector3_float64", "Direction", this.direction.toArray(), `Runway direction ${Math.round(this.direction_degree ?? 0)}°`);
|
|
58
58
|
}
|
|
59
59
|
if (this.elevation !== null) {
|
|
60
60
|
element.appendChild("float64", "Elevation", this.elevation, this.elevation_ft ? `Elevation ${Math.ceil(this.elevation_ft)} ft` : undefined);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { convertFeetToMeter, convertMeterToFeet } from "../node/Convert.js";
|
|
2
2
|
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
3
3
|
import { AeroflyNavRouteBase } from "./AeroflyNavRouteBase.js";
|
|
4
|
+
import { AeroflyVector3Float } from "../node/AeroflyTypes.js";
|
|
4
5
|
class AeroflyNavRouteTransition extends AeroflyNavRouteBase {
|
|
5
6
|
/**
|
|
6
7
|
* @property {string} airport ICAO code of the airport this transition belongs to, e.g. "SEA", "PDX"
|
|
@@ -29,28 +30,28 @@ class AeroflyNavRouteTransition extends AeroflyNavRouteBase {
|
|
|
29
30
|
* @returns {number | null} elevation in feet, null if not set
|
|
30
31
|
*/
|
|
31
32
|
get elevation_ft() {
|
|
32
|
-
return this.elevation !== null ?
|
|
33
|
+
return this.elevation !== null ? convertMeterToFeet(this.elevation) : null;
|
|
33
34
|
}
|
|
34
35
|
set elevation_ft(elevation_ft) {
|
|
35
|
-
this.elevation = elevation_ft !== null ?
|
|
36
|
+
this.elevation = elevation_ft !== null ? convertFeetToMeter(elevation_ft) : null;
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
39
|
* @returns {AeroflyVector3Float} deliberately empty
|
|
39
40
|
*/
|
|
40
41
|
get position() {
|
|
41
|
-
return
|
|
42
|
+
return new AeroflyVector3Float(0, 0, 0); // sic!
|
|
42
43
|
}
|
|
43
44
|
/**
|
|
44
45
|
* @returns {AeroflyVector3Float} deliberately empty
|
|
45
46
|
*/
|
|
46
47
|
get direction() {
|
|
47
|
-
return
|
|
48
|
+
return new AeroflyVector3Float(0, 0, 0); // sic!
|
|
48
49
|
}
|
|
49
50
|
getElement(index = 0) {
|
|
50
51
|
const element = super.getElement(index);
|
|
51
52
|
element
|
|
52
53
|
.appendChild("string8u", "Airport", this.airport)
|
|
53
|
-
.appendChild("vector3_float64", "Direction", this.direction)
|
|
54
|
+
.appendChild("vector3_float64", "Direction", this.direction.toArray())
|
|
54
55
|
.appendChild("float64", "Elevation", this.elevation ?? 0, `Elevation ${this.elevation_ft !== null ? Math.ceil(this.elevation_ft) + " ft" : "unknown"}`)
|
|
55
56
|
.appendChild("string8u", "Transition", this.transition)
|
|
56
57
|
.appendChild("uint64", "TransitionUid", this.transitionUid ?? 0);
|
|
@@ -86,7 +87,7 @@ export class AeroflyNavRouteArrival extends AeroflyNavRouteTransition {
|
|
|
86
87
|
const element = super.getElement(index);
|
|
87
88
|
element
|
|
88
89
|
.appendChild("string8u", "Airport", this.airport)
|
|
89
|
-
.appendChild("vector3_float64", "Direction", this.direction)
|
|
90
|
+
.appendChild("vector3_float64", "Direction", this.direction.toArray())
|
|
90
91
|
.appendChild("float64", "Elevation", this.elevation ?? 0, `Elevation ${this.elevation_ft !== null ? Math.ceil(this.elevation_ft) + " ft" : "unknown"}`);
|
|
91
92
|
return element;
|
|
92
93
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { convertFeetToMeter, convertLonLatToVector, convertMeterToFeet, convertVectorToLonLat, } from "../node/Convert.js";
|
|
2
2
|
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
3
3
|
import { AeroflyNavRouteBase } from "./AeroflyNavRouteBase.js";
|
|
4
4
|
export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
|
|
@@ -9,6 +9,10 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
|
|
|
9
9
|
* @property {?number} navaidFrequency if the waypoint is a navaid, its frequency in Hz
|
|
10
10
|
*/
|
|
11
11
|
navaidFrequency;
|
|
12
|
+
/**
|
|
13
|
+
* @property {?bigint} navaidUid if the waypoint is a navaid, its unique identifier, must match Aerofly FS internal UID if used in an existing mission
|
|
14
|
+
*/
|
|
15
|
+
navaidUid;
|
|
12
16
|
/**
|
|
13
17
|
* @property {number | null} altitude in meter, null if not set
|
|
14
18
|
*/
|
|
@@ -23,17 +27,21 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
|
|
|
23
27
|
* @param {number} latitude WGS84
|
|
24
28
|
* @param {object} [options] additional options for the waypoint
|
|
25
29
|
* @param {?number} [options.navaidFrequency] if the waypoint is a navaid, its frequency in Hz
|
|
30
|
+
* @param {?number} [options.navaidFrequency_khz] if the waypoint is a navaid, its frequency in kHz, will override navaidFrequency in Hz if provided
|
|
31
|
+
* @param {?number} [options.navaidFrequency_mhz] if the waypoint is a navaid, its frequency in MHz, will override navaidFrequency in Hz if provided
|
|
32
|
+
* @param {?bigint} [options.navaidUid] if the waypoint is a navaid, its unique identifier, must match Aerofly FS internal UID if used in an existing mission
|
|
26
33
|
* @param {number} [options.altitude] in meter
|
|
27
34
|
* @param {?number} [options.altitude_ft] altitude in feet, will override altitude in meter if provided
|
|
28
35
|
* @param {boolean} [options.flyOver] if true, the waypoint is meant to be flown over, otherwise it can be used as a fly-by waypoint
|
|
29
36
|
* @param {?bigint} [options.uid] unique identifier for the waypoint, will be generated automatically if not provided
|
|
30
37
|
*/
|
|
31
|
-
constructor(identifier, longitude, latitude, { navaidFrequency = null, navaidFrequency_khz = null, navaidFrequency_mhz = null, altitude = null, altitude_ft = null, flyOver = false, uid = null, } = {}) {
|
|
38
|
+
constructor(identifier, longitude, latitude, { navaidFrequency = null, navaidFrequency_khz = null, navaidFrequency_mhz = null, navaidUid = null, altitude = null, altitude_ft = null, flyOver = false, uid = null, } = {}) {
|
|
32
39
|
super("waypoint", identifier, longitude, latitude, { uid });
|
|
33
40
|
this.identifier = identifier;
|
|
34
41
|
this.longitude = longitude;
|
|
35
42
|
this.latitude = latitude;
|
|
36
43
|
this.navaidFrequency = navaidFrequency;
|
|
44
|
+
this.navaidUid = navaidUid;
|
|
37
45
|
this.altitude = altitude;
|
|
38
46
|
if (altitude_ft !== null) {
|
|
39
47
|
this.altitude_ft = altitude_ft;
|
|
@@ -50,10 +58,10 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
|
|
|
50
58
|
* @returns {number | null} altitude in feet, null if not set
|
|
51
59
|
*/
|
|
52
60
|
get altitude_ft() {
|
|
53
|
-
return this.altitude !== null ?
|
|
61
|
+
return this.altitude !== null ? convertMeterToFeet(this.altitude) : null;
|
|
54
62
|
}
|
|
55
63
|
set altitude_ft(altitude_ft) {
|
|
56
|
-
this.altitude = altitude_ft !== null ?
|
|
64
|
+
this.altitude = altitude_ft !== null ? convertFeetToMeter(altitude_ft) : null;
|
|
57
65
|
}
|
|
58
66
|
get navaidFrequency_khz() {
|
|
59
67
|
return this.navaidFrequency ? this.navaidFrequency / 1000 : null;
|
|
@@ -71,20 +79,19 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
|
|
|
71
79
|
* @returns {AeroflyVector3Float} to use in Aerofly FS4's `main.mcf`
|
|
72
80
|
*/
|
|
73
81
|
get position() {
|
|
74
|
-
return
|
|
82
|
+
return convertLonLatToVector(this.longitude, this.latitude, this.altitude || 0);
|
|
75
83
|
}
|
|
76
84
|
set position(position) {
|
|
77
|
-
const { longitude, latitude, altitude_meter } =
|
|
85
|
+
const { longitude, latitude, altitude_meter } = convertVectorToLonLat(position);
|
|
78
86
|
this.longitude = longitude;
|
|
79
87
|
this.latitude = latitude;
|
|
80
88
|
this.altitude = altitude_meter;
|
|
81
89
|
}
|
|
82
90
|
getElement(index = 0) {
|
|
83
91
|
const element = super.getElement(index);
|
|
84
|
-
if (this.navaidFrequency) {
|
|
85
|
-
element.appendChild("float64", "NavaidFrequency", this.navaidFrequency);
|
|
86
|
-
}
|
|
87
92
|
element
|
|
93
|
+
.appendChild("float64", "NavaidFrequency", this.navaidFrequency ?? 0)
|
|
94
|
+
.appendChild("uint64", "NavaidUid", this.navaidUid ?? BigInt(0))
|
|
88
95
|
.appendChild("vector2_float64", "Altitude", this.altitude !== null && this.altitude > 0 ? [this.altitude, this.altitude] : [-1001, 100001], `${this.altitude_ft !== null && this.altitude_ft > 0 ? Math.ceil(this.altitude_ft) + " ft" : "unrestricted"}`)
|
|
89
96
|
.appendChild("bool", "FlyOver", this.flyOver);
|
|
90
97
|
return element;
|
|
@@ -97,6 +104,7 @@ export class AeroflyNavRouteWaypoint extends AeroflyNavRouteBase {
|
|
|
97
104
|
altitude_ft: this.altitude_ft,
|
|
98
105
|
navaidFrequency: undefined,
|
|
99
106
|
navaidFrequency_khz: this.navaidFrequency_khz,
|
|
107
|
+
navaidUid: this.navaidUid !== null ? this.navaidUid.toString() : null,
|
|
100
108
|
};
|
|
101
109
|
}
|
|
102
110
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
2
|
-
import {
|
|
2
|
+
import { convertFeetToMeter, convertMeterToFeet } from "../node/Convert.js";
|
|
3
3
|
import { AeroflyNavRouteBase } from "./AeroflyNavRouteBase.js";
|
|
4
4
|
export class AeroflyNavigationConfig {
|
|
5
5
|
/**
|
|
@@ -31,16 +31,16 @@ export class AeroflyNavigationConfig {
|
|
|
31
31
|
* @returns {AeroflyNavigationConfig} with cruise altitude converted to meters
|
|
32
32
|
*/
|
|
33
33
|
static createInFeet(cruiseAltitude_ft, waypoints = [], _cruiseSpeed_kts = undefined) {
|
|
34
|
-
return new AeroflyNavigationConfig(
|
|
34
|
+
return new AeroflyNavigationConfig(convertFeetToMeter(cruiseAltitude_ft), waypoints, _cruiseSpeed_kts);
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
37
|
* @returns {number} cruise altitude in feet
|
|
38
38
|
*/
|
|
39
39
|
get cruiseAltitude_ft() {
|
|
40
|
-
return
|
|
40
|
+
return convertMeterToFeet(this.cruiseAltitude);
|
|
41
41
|
}
|
|
42
42
|
set cruiseAltitude_ft(cruiseAltitude_ft) {
|
|
43
|
-
this.cruiseAltitude =
|
|
43
|
+
this.cruiseAltitude = convertFeetToMeter(cruiseAltitude_ft);
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* @returns {AeroflyConfigurationNode[]} indexed checkpoints
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Convert } from "../node/Convert.js";
|
|
2
1
|
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
2
|
+
import { AeroflyMatrix3Float, AeroflyVector3Float, } from "../node/AeroflyTypes.js";
|
|
3
|
+
import { convertFeetToMeter, convertLonLatToVector, convertVectorToLonLat, convertHeadingToOrientation, convertOrientationToHeading, convertMeterToFeet, } from "../node/Convert.js";
|
|
3
4
|
export class AeroflySettingsFlight {
|
|
4
5
|
longitude;
|
|
5
6
|
latitude;
|
|
@@ -52,13 +53,13 @@ export class AeroflySettingsFlight {
|
|
|
52
53
|
this.runway = runway;
|
|
53
54
|
}
|
|
54
55
|
static createInFeet(longitude, latitude, altitude_ft, heading_degree, speed_kts = 0, additionalAttributes = {}) {
|
|
55
|
-
return new AeroflySettingsFlight(longitude, latitude,
|
|
56
|
+
return new AeroflySettingsFlight(longitude, latitude, convertFeetToMeter(altitude_ft), heading_degree, speed_kts, additionalAttributes);
|
|
56
57
|
}
|
|
57
58
|
static createInCartesian(position, velocity, orientation, additionalAttributes = {}) {
|
|
58
59
|
const flight = new AeroflySettingsFlight(0, 0, 0, 0, 0, additionalAttributes);
|
|
59
|
-
flight.position = position;
|
|
60
|
-
flight.velocity = velocity;
|
|
61
|
-
flight.orientation = orientation;
|
|
60
|
+
flight.position = AeroflyVector3Float.fromArray(position);
|
|
61
|
+
flight.velocity = AeroflyVector3Float.fromArray(velocity);
|
|
62
|
+
flight.orientation = AeroflyMatrix3Float.fromArray(orientation);
|
|
62
63
|
return flight;
|
|
63
64
|
}
|
|
64
65
|
/**
|
|
@@ -95,10 +96,10 @@ export class AeroflySettingsFlight {
|
|
|
95
96
|
* @returns {AeroflyVector3Float} position vector to use in Aerofly FS4's `main.mcf`
|
|
96
97
|
*/
|
|
97
98
|
get position() {
|
|
98
|
-
return
|
|
99
|
+
return convertLonLatToVector(this.longitude, this.latitude, this.altitude_meter);
|
|
99
100
|
}
|
|
100
101
|
set position(position) {
|
|
101
|
-
const { longitude, latitude, altitude_meter } =
|
|
102
|
+
const { longitude, latitude, altitude_meter } = convertVectorToLonLat(position);
|
|
102
103
|
this.longitude = longitude;
|
|
103
104
|
this.latitude = latitude;
|
|
104
105
|
this.altitude_meter = altitude_meter;
|
|
@@ -109,26 +110,26 @@ export class AeroflySettingsFlight {
|
|
|
109
110
|
get velocity() {
|
|
110
111
|
const speed = this.speed_ms;
|
|
111
112
|
const heading_rad = this.heading_degree * (Math.PI / 180);
|
|
112
|
-
return
|
|
113
|
+
return new AeroflyVector3Float(Math.cos(heading_rad) * speed, Math.sin(heading_rad) * speed, 0);
|
|
113
114
|
}
|
|
114
115
|
set velocity(velocity) {
|
|
115
116
|
// TODO: implement setting velocity vector and updating speed accordingly
|
|
116
117
|
this.speed_ms = 0;
|
|
117
118
|
}
|
|
118
119
|
get orientation() {
|
|
119
|
-
return
|
|
120
|
+
return convertHeadingToOrientation(this.heading_degree, this.position);
|
|
120
121
|
}
|
|
121
122
|
set orientation(orientation) {
|
|
122
|
-
this.heading_degree =
|
|
123
|
+
this.heading_degree = convertOrientationToHeading(orientation, this.position);
|
|
123
124
|
}
|
|
124
125
|
/**
|
|
125
126
|
* @returns {number} altitude in feet AGL
|
|
126
127
|
*/
|
|
127
128
|
get altitude_ft() {
|
|
128
|
-
return
|
|
129
|
+
return convertMeterToFeet(this.altitude_meter);
|
|
129
130
|
}
|
|
130
131
|
set altitude_ft(altitude_ft) {
|
|
131
|
-
this.altitude_meter =
|
|
132
|
+
this.altitude_meter = convertFeetToMeter(altitude_ft);
|
|
132
133
|
}
|
|
133
134
|
/**
|
|
134
135
|
* @returns {number} speed in meters per seconds
|
|
@@ -141,9 +142,9 @@ export class AeroflySettingsFlight {
|
|
|
141
142
|
}
|
|
142
143
|
getElement() {
|
|
143
144
|
return new AeroflyConfigurationNode("tmsettings_flight", "flight_setting")
|
|
144
|
-
.appendChild("vector3_float64", "position", this.position, `Lon ${this.longitude.toFixed(6)}, Lat ${this.latitude.toFixed(6)}, ${Math.ceil(this.altitude_ft)} ft`)
|
|
145
|
-
.appendChild("vector3_float64", "velocity", this.velocity, `${Math.round(this.speed_kts)} kts`)
|
|
146
|
-
.appendChild("matrix3_float64", "orientation", this.orientation, `${Math.round(this.heading_degree)}° heading`)
|
|
145
|
+
.appendChild("vector3_float64", "position", this.position.toArray(), `Lon ${this.longitude.toFixed(6)}, Lat ${this.latitude.toFixed(6)}, ${Math.ceil(this.altitude_ft)} ft`)
|
|
146
|
+
.appendChild("vector3_float64", "velocity", this.velocity.toArray(), `${Math.round(this.speed_kts)} kts`)
|
|
147
|
+
.appendChild("matrix3_float64", "orientation", this.orientation.toArray(), `${Math.round(this.heading_degree)}° heading`)
|
|
147
148
|
.appendChild("float64", "gear", this.gear)
|
|
148
149
|
.appendChild("float64", "throttle", this.throttle)
|
|
149
150
|
.appendChild("float64", "flaps", this.flaps)
|
|
@@ -27,28 +27,6 @@ describe("AeroflySettingsFlight", () => {
|
|
|
27
27
|
assert.strictEqual(flight.runway, "16L", `Expected runway to be 16L, got ${flight.runway}`);
|
|
28
28
|
assertValidAeroflyStructure(flight.toString());
|
|
29
29
|
});
|
|
30
|
-
it("should use valid orientation from main.mcf", () => {
|
|
31
|
-
const flight = new AeroflySettingsFlight(-122.3088, 47.4502, 1000, 90, 150);
|
|
32
|
-
// Canada
|
|
33
|
-
flight.orientation = [
|
|
34
|
-
-0.763419555334921, 0.645896043270811, 0.00298057365988389, -0.564914354076689, -0.665451427226094,
|
|
35
|
-
-0.487899754622681, -0.313149094027667, -0.374155982565915, 0.872894578754952,
|
|
36
|
-
];
|
|
37
|
-
console.log("Orientation:", flight.heading_degree); // should be 270, is 216
|
|
38
|
-
// South Africa
|
|
39
|
-
flight.orientation = [
|
|
40
|
-
0.324874838801671, -0.945743434574979, 0.0050690306677758, -0.526455468124437, -0.185291526716742,
|
|
41
|
-
-0.829766045466347, 0.785685038164447, 0.266901491350839, -0.5580883574482,
|
|
42
|
-
];
|
|
43
|
-
console.log("Orientation:", flight.heading_degree); // should be 270, is 301
|
|
44
|
-
// Japan
|
|
45
|
-
flight.orientation = [
|
|
46
|
-
0.717429258146935, 0.696613268777701, 0.00502128644729529, -0.393795668419003, 0.411487688662976,
|
|
47
|
-
-0.821950639401925, -0.574647919242008, 0.587714076606685, 0.569536595021937,
|
|
48
|
-
];
|
|
49
|
-
console.log("Orientation:", flight.heading_degree); // should be 270, is 331
|
|
50
|
-
assertValidAeroflyStructure(flight.toString());
|
|
51
|
-
});
|
|
52
30
|
it("should create fallbacks for flight configurations", () => {
|
|
53
31
|
const flight = new AeroflySettingsFlight(-122.3088, 47.4502, 1000, 90, 150, {
|
|
54
32
|
configuration: "OnGround",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AeroflyConfigurationNode } from "../node/AeroflyConfigurationNode.js";
|
|
2
|
-
import {
|
|
2
|
+
import { convertKgToLb, convertLbToKg } from "../node/Convert.js";
|
|
3
3
|
export class AeroflySettingsFuelLoad {
|
|
4
4
|
/**
|
|
5
5
|
* @property {string} aircraft aerofly aircraft name (e.g. "c172")
|
|
@@ -34,20 +34,20 @@ export class AeroflySettingsFuelLoad {
|
|
|
34
34
|
this.configuration = configuration;
|
|
35
35
|
}
|
|
36
36
|
get fuelMass_lb() {
|
|
37
|
-
return
|
|
37
|
+
return convertKgToLb(this.fuelMass);
|
|
38
38
|
}
|
|
39
39
|
set fuelMass_lb(fuelMass_lb) {
|
|
40
|
-
this.fuelMass =
|
|
40
|
+
this.fuelMass = convertLbToKg(fuelMass_lb);
|
|
41
41
|
}
|
|
42
42
|
get payloadMass_lb() {
|
|
43
|
-
return
|
|
43
|
+
return convertKgToLb(this.payloadMass);
|
|
44
44
|
}
|
|
45
45
|
set payloadMass_lb(payloadMass_lb) {
|
|
46
|
-
this.payloadMass =
|
|
46
|
+
this.payloadMass = convertLbToKg(payloadMass_lb);
|
|
47
47
|
}
|
|
48
48
|
getElement() {
|
|
49
49
|
return new AeroflyConfigurationNode("tmsettings_fuel_load", "fuel_load_setting")
|
|
50
|
-
.appendChild("fuel_load_configuration", "configuration", this.fuelMass > 0 ? "
|
|
50
|
+
.appendChild("fuel_load_configuration", "configuration", this.fuelMass > 0 && this.configuration === "Invalid" ? "Custom" : this.configuration)
|
|
51
51
|
.appendChild("string8u", "aircraft", this.aircraft)
|
|
52
52
|
.appendChild("float64", "fuel_mass", this.fuelMass)
|
|
53
53
|
.appendChild("float64", "payload_mass", this.payloadMass);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export class AeroflyVector3Float {
|
|
2
|
+
x;
|
|
3
|
+
y;
|
|
4
|
+
z;
|
|
5
|
+
constructor(x, y, z) {
|
|
6
|
+
this.x = x;
|
|
7
|
+
this.y = y;
|
|
8
|
+
this.z = z;
|
|
9
|
+
}
|
|
10
|
+
normalize() {
|
|
11
|
+
const norm = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
|
|
12
|
+
if (norm === 0) {
|
|
13
|
+
return new AeroflyVector3Float(0, 0, 0);
|
|
14
|
+
}
|
|
15
|
+
return new AeroflyVector3Float(this.x / norm, this.y / norm, this.z / norm);
|
|
16
|
+
}
|
|
17
|
+
cross(b) {
|
|
18
|
+
return new AeroflyVector3Float(this.y * b.z - this.z * b.y, this.z * b.x - this.x * b.z, this.x * b.y - this.y * b.x);
|
|
19
|
+
}
|
|
20
|
+
dot(b) {
|
|
21
|
+
return this.x * b.x + this.y * b.y + this.z * b.z;
|
|
22
|
+
}
|
|
23
|
+
static fromArray(array) {
|
|
24
|
+
return new this(...array);
|
|
25
|
+
}
|
|
26
|
+
toArray() {
|
|
27
|
+
return [this.x, this.y, this.z];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class AeroflyMatrix3Float {
|
|
31
|
+
xx;
|
|
32
|
+
yx;
|
|
33
|
+
zx;
|
|
34
|
+
xy;
|
|
35
|
+
yy;
|
|
36
|
+
zy;
|
|
37
|
+
xz;
|
|
38
|
+
yz;
|
|
39
|
+
zz;
|
|
40
|
+
constructor(xx, yx, zx, xy, yy, zy, xz, yz, zz) {
|
|
41
|
+
this.xx = xx;
|
|
42
|
+
this.yx = yx;
|
|
43
|
+
this.zx = zx;
|
|
44
|
+
this.xy = xy;
|
|
45
|
+
this.yy = yy;
|
|
46
|
+
this.zy = zy;
|
|
47
|
+
this.xz = xz;
|
|
48
|
+
this.yz = yz;
|
|
49
|
+
this.zz = zz;
|
|
50
|
+
}
|
|
51
|
+
transpose() {
|
|
52
|
+
return new AeroflyMatrix3Float(this.xx, this.xy, this.xz, this.yx, this.yy, this.yz, this.zx, this.zy, this.zz);
|
|
53
|
+
}
|
|
54
|
+
multiplyVector(v) {
|
|
55
|
+
return new AeroflyVector3Float(this.xx * v.x + this.xy * v.y + this.xz * v.z, this.yx * v.x + this.yy * v.y + this.yz * v.z, this.zx * v.x + this.zy * v.y + this.zz * v.z);
|
|
56
|
+
}
|
|
57
|
+
static fromArray(array) {
|
|
58
|
+
return new this(...array);
|
|
59
|
+
}
|
|
60
|
+
toArray() {
|
|
61
|
+
return [this.xx, this.yx, this.zx, this.xy, this.yy, this.zy, this.xz, this.yz, this.zz];
|
|
62
|
+
}
|
|
63
|
+
}
|