Skip to content

Commit

Permalink
Improved handling of and documentation for MSFS / X-Plane flight plan…
Browse files Browse the repository at this point in the history
… files
  • Loading branch information
fboes committed Feb 6, 2024
1 parent a3eeea3 commit d591f76
Show file tree
Hide file tree
Showing 17 changed files with 111 additions and 21 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
/.vscode
*.code-workspace
/tmp
.DS_Store
@eaDir/
4 changes: 2 additions & 2 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
The MIT License (MIT)

Copyright © 2022 Frank Boës
Copyright © 2022-2024 Frank Boës

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:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# ![](docs/favicon-64x64.png) Aerofly Missionsgerät

The Aerofly Missionsgerät converts [Aerofly FS 4's](https://www.aerofly.com/) `main.mcf` and other flight plan file formats into a shareable mission file, using your current flight plan and other settings.
The Aerofly Missionsgerät converts simulator flight plan files for [Aerofly FS 4](https://www.aerofly.com/) (`main.mcf` and `tmc`), Microsoft Flight Simulator (`pln`) and X-Plane (`fms`). It also imports Garmin flight plan files (`fpl`).

There are two versions of this tool:
Head over to [https://fboes.github.io/aerofly-missions/dist/](https://fboes.github.io/aerofly-missions/dist/) to get started.

1. **Web version**: Use the Missionsgerät right out of your browser by visiting [https://fboes.github.io/aerofly-missions/dist/](https://fboes.github.io/aerofly-missions/dist/).
2. **Local Node.js CLI tool**: If you want some automation for your workflow.
There is also a local Node.js CLI tool, if you want some automation for your workflow.

![Flight plan as text briefing](docs/flightplan.png)

Expand Down
2 changes: 2 additions & 0 deletions dist/Aerofly/Mission.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ export class Mission {
if (gpl.cruisingAlt) {
this.cruise_altitude_ft = gpl.cruisingAlt;
}
// Assuming non AFS4 flight plans to start on the ground ;)
this.flight_setting = Mission.FLIGHT_SETTING_TAXI;
this.checkpoints = gpl.waypoints.map((w, i) => {
var _a;
let cp = new MissionCheckpoint();
Expand Down
2 changes: 1 addition & 1 deletion dist/Import/MsfsPln.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class MsfsPln extends GarminFpl {
const coords = this.convertCoordinate(worldPosition);
if (index === 0 || index === waypointsXml.length - 1) {
const runwayNumberFP = this.getXmlNode(xml, "RunwayNumberFP");
let runwayDesignatorFP = this.getXmlNode(xml, "RunwayNumberFP");
let runwayDesignatorFP = this.getXmlNode(xml, "RunwayDesignatorFP");
const rw = runwayNumberFP + (runwayDesignatorFP === "NONE" ? "" : runwayDesignatorFP.substring(0, 1));
if (runwayNumberFP) {
if (index === 0) {
Expand Down
13 changes: 13 additions & 0 deletions dist/Tests/MsfsPlnTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class MsfsPlnTest extends Test {
this.testEGOV();
this.testLittleNavMap();
this.testGarminParse();
this.testRunway();
}
testEGOV() {
// Parse PLN
Expand All @@ -33,6 +34,7 @@ export class MsfsPlnTest extends Test {
this.group(MsfsPln.name + ": Mission conversion");
{
this.assertEquals(mission.checkpoints.length, 16);
this.assertEquals(mission.flight_setting, Mission.FLIGHT_SETTING_TAXI);
}
// Export Mission to XML
const exportPln = new MsfsPlnExport(mission);
Expand Down Expand Up @@ -82,10 +84,21 @@ export class MsfsPlnTest extends Test {
this.group(MsfsPln.name + ": Mission conversion");
{
this.assertEquals(mission.checkpoints.length, 11);
this.assertEquals(mission.flight_setting, Mission.FLIGHT_SETTING_TAXI);
this.assertEquals(mission.checkpoints[0].type, MissionCheckpoint.TYPE_ORIGIN);
this.assertEquals(mission.checkpoints[1].type, MissionCheckpoint.TYPE_DEPARTURE_RUNWAY);
this.assertEquals(mission.checkpoints[9].type, MissionCheckpoint.TYPE_DESTINATION_RUNWAY);
this.assertEquals(mission.checkpoints[10].type, MissionCheckpoint.TYPE_DESTINATION);
}
}
testRunway() {
const pln = new MsfsPln(fs.readFileSync("./src/Tests/cases/ENHD_local_flight.pln", "utf8"));
this.group(MsfsPln.name + ": Runway check");
{
this.assertEquals(pln.departureRunway, "13");
this.assertEquals(pln.destinationRunway, "31");
const mission = new Mission("", "").fromGarminFpl(pln);
this.assertEquals(mission.checkpoints.length, 9);
}
}
}
13 changes: 13 additions & 0 deletions dist/Tests/XplaneFmsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class XplaneFmsTest extends Test {
this.testEGOV();
this.testLittleNavMap();
this.testGarminParse();
this.testRunway();
}
testEGOV() {
const fms = new XplaneFms(fs.readFileSync("./src/Tests/cases/EGCC-EDDF.fms", "utf8"));
Expand All @@ -33,6 +34,7 @@ export class XplaneFmsTest extends Test {
this.group(XplaneFms.name + ": Mission conversion");
{
this.assertEquals(mission.checkpoints.length, 17);
this.assertEquals(mission.flight_setting, Mission.FLIGHT_SETTING_TAXI);
}
// Export Mission to XML
const exportFms = new XplaneFmsExport(mission);
Expand Down Expand Up @@ -85,10 +87,21 @@ export class XplaneFmsTest extends Test {
this.group(XplaneFms.name + ": Mission conversion");
{
this.assertEquals(mission.checkpoints.length, 11);
this.assertEquals(mission.flight_setting, Mission.FLIGHT_SETTING_TAXI);
this.assertEquals(mission.checkpoints[0].type, MissionCheckpoint.TYPE_ORIGIN);
this.assertEquals(mission.checkpoints[1].type, MissionCheckpoint.TYPE_DEPARTURE_RUNWAY);
this.assertEquals(mission.checkpoints[9].type, MissionCheckpoint.TYPE_DESTINATION_RUNWAY);
this.assertEquals(mission.checkpoints[10].type, MissionCheckpoint.TYPE_DESTINATION);
}
}
testRunway() {
const pln = new XplaneFms(fs.readFileSync("./src/Tests/cases/ENHD_local_flight.fms", "utf8"));
this.group(XplaneFms.name + ": Runway check");
{
this.assertEquals(pln.departureRunway, "13");
this.assertEquals(pln.destinationRunway, "31");
const mission = new Mission("", "").fromGarminFpl(pln);
this.assertEquals(mission.checkpoints.length, 9);
}
}
}
8 changes: 5 additions & 3 deletions dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<meta property="og:title" name="title" content="Aerofly Missionsgerät" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://fboes.github.io/aerofly-missions/dist/" />
<meta property="og:locale" content="en_GB" />
<meta property="og:description" name="description" content="The Aerofly Missionsgerät converts Aerofly FS 4's `main.mcf` and other flight plan file formats into a shareable mission file, using your current flight plan and other settings." />
<meta property="og:locale" content="en_US" />
<meta property="og:description" name="description" content="The Aerofly Missionsgerät converts simulator flight plan files for Aerofly FS 4, Microsoft Flight Simulator and X-Plane. It also imports Garmin flight plan files." />
<meta property="og:site_name" name="application-name" content="Aerofly Missionsgerät" />
<meta property="og:image" content="https://fboes.github.io/aerofly-missions/dist/social-1200x630.png" />
<link rel="icon" sizes="180x180" href="favicon-180x180.png" />
Expand All @@ -34,7 +34,7 @@ <h1>
</h1>
</header>
<main class="is-simple-mode">
<p>The Aerofly Missionsgerät converts <a href="https://www.aerofly.com/">Aerofly FS 4's</a> <code>main.mcf</code> (and other flight plan file formats) into a <code>custom_missions_user.tmc</code> file (and other flight plan file formats), using your current flight plan and other settings to generate a shareable mission.</p>
<p>The Aerofly Missionsgerät converts simulator flight plan files for <a href="https://www.aerofly.com/">Aerofly FS 4</a> (<code>main.mcf</code> and <code>tmc</code>), Microsoft Flight Simulator (<code>pln</code>) and X-Plane (<code>fms</code>). It also imports Garmin flight plan files (<code>fpl</code>).<br />For more information see the <a href="https://fboes.github.io/aerofly-missions/docs/">Missionsgerät's instructions</a>.</p>

<section class="no-print">
<h2>Step 1: Load flight plan file</h2>
Expand Down Expand Up @@ -305,13 +305,15 @@ <h2 class="no-print">Step 3: Check flight plan</h2>
<p>By activating the Mapbox map you consent to the integration probably sending personal data across the internet. Please reload the page after activating Mapbox.</p>
<button onclick="localStorage.setItem('mapbox.consent', '1'); window.location.reload()">Activate Mapbox map</button>
</div>
<p>Use <kbd>Shift</kbd> + <kbd>Click</kbd> on the map to move waypoints.</p>

<button data-handler="modal-open" type="button" data-modal="help-flightplan-modal" class="icon help second" title="Open flight plan help">? <span>Open flight plan help</span></button>
<button data-handler="reset" type="button" class="icon reset" id="reset-flightplan" title="Reset flight plan"><span>Reset flight plan</span></button>
</section>
<section class="no-print">
<h2>Step 4: Save converted files</h2>
<p>The Missionsgerät exports Aerofly FS <code>tmc</code>, Microsoft FS <code>pln</code> or X-Plane <code>fms</code> flight plan files.</p>
<p>See the <a href="https://fboes.github.io/aerofly-missions/docs/generic-installation.html">Missionsgerät's instructions on how to install the mission files into Aerofly FS4</a>.</p>
<p class="expert-mode">It also generates a Markdown file containing a textual description of the flight including weather data, as well as a <a href="https://geojson.org/">GeoJSON</a> file.</p>

<missionsgeraet-buttons id="download-buttons"></missionsgeraet-buttons>
Expand Down
2 changes: 1 addition & 1 deletion dist/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "Aerofly Missionsgerät",
"short_name": "Missionsgerät",
"description": "The Aerofly Missionsgerät converts Aerofly FS 4's `main.mcf` and other flight plan file formats into a shareable mission file, using your current flight plan and other settings.",
"description": "The Aerofly Missionsgerät converts simulator flight plan files for Aerofly FS 4, Microsoft Flight Simulator and X-Plane. It also imports Garmin flight plan files.",
"icons": [
{
"src": "./favicon-180x180.png",
Expand Down
17 changes: 15 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
# ![](favicon-64x64.png) Additional documents
# ![](favicon-64x64.png) Documentation

The basic steps to work with the Missionsgerät are straightforward:

1. The Aerofly Missionsgerät converts simulator flight plan files for [Aerofly FS 4](https://www.aerofly.com/) (`main.mcf` and `tmc`), Microsoft Flight Simulator (`pln`) and X-Plane (`fms`). It also imports Garmin flight plan files (`fpl`).
2. Upload the flight plan file to the Missionsgerät.
3. You may want to add settings like the aircraft used, time and weather. Only Aerofly FS 4 flight plan files contain these information, so other flight plan files need to add these manually. You may also want to modify your starting position and heading by using the Missionsgerät's map.
4. Download the converted flight plan files file from the Missionsgerät.

If you want to use the flight plan file in Aerofly FS 4, check the [generic `custom_missions_user.tmc` installation instructions](./generic-installation.md).

## Additional documents

1. [Building a custom missions file](./custom-missions.md)
1. [Flight plan output](./flightplan.md)
Expand All @@ -9,7 +20,9 @@ On a more theoretical level:

1. [How to build a flight plan](./build-flightplan.md)

These mission files are included:
## Included mission files

These missions are ready to download:

1. [Carl's Homecoming](./carls_homecoming/README.md)
Take a seat in Carl's Pitts S-2, and starting in Hamburg travel up to Denmark, Sweden and finally Finnland.
Expand Down
13 changes: 8 additions & 5 deletions docs/generic-installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

To install a custom missions file in Aerofly FS 4:

1. Open a file explorer in your Aerofly FS 4 user folder. Usually this is located at `C:\Users\%USERNAME%\Documents\Aerofly FS 4`.
2. Open up the `missions` folder.
3. Put the new `.tmc` file into the `missions` folder,
1. Open a file explorer in your Aerofly FS 4 user folder. Usually this is located at
`C:\Users\%USERNAME%\Documents\Aerofly FS 4`.
2. Open up the `missions` folder. If this folder does not exists, create it.
3. Put the new `.tmc` file into the `missions` folder,
and rename it to `custom_missions_user.tmc`.
4. On starting Aerofly FS 4, your custom missions will be loaded from the
new `custom_missions_user.tmc`.
4. On starting Aerofly FS 4, your custom missions will be loaded from the new
`custom_missions_user.tmc`.
The custom missions can be found in Aerofly FS 4 in
["Main Menu" > "Missions" > "Challenges"](https://www.aerofly.com/tutorials/missions/).

Be aware that there is only one `custom_missions_user.tmc` at any given time.
If you have multiple mission files, rename them accordingly and keep backups of the original files.
Expand Down
2 changes: 1 addition & 1 deletion docs/reno_air_races/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Welcome to the [Reno Air Races](https://airrace.org/). Make yourself familiar wi
Please note: You will have the exact positions of the poles marked in your map for the jet course (with a MB-339) and bi-plane course (with a Pitts S-2).

The flight plan itself will not show up properly in Aerofly FS4, as the waypoints are way too close - and sadly the poles are not modelled. But glancing on the moving map gives you an idea where the poles are supposed to be, and you may have a glimpse as some funny spots on the aerial images, which are the poles.
The flight plan itself will not show up properly in Aerofly FS 4, as the waypoints are way too close - and sadly the poles are not modeled. But glancing on the moving map gives you an idea where the poles are supposed to be, and you may have a glimpse as some funny spots on the aerial images, which are the poles.

You may also want to take a look at [Krzysk's Reno Course](https://github.com/krzysk1/reno_course) and [ApfelFliegers's installation instructions](https://www.aerofly.com/community/forum/index.php?thread/19105-user-created-custom-missions/&postID=121141#post121141).

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"name": "aerofly-missions",
"author": "Frank Boës <[email protected]> (https://3960.org/)",
"license": "MIT",
"description": "The Aerofly Missionsgerät converts Aerofly FS 4's `main.mcf` and other flight plan file formats into a shareable mission file, using your current flight plan and other settings.",
"description": "The Aerofly Missionsgerät converts simulator flight plan files for Aerofly FS 4, Microsoft Flight Simulator and X-Plane. It also imports Garmin flight plan files.",
"version": "2.19.0",
"exports": "./dist/aerofly-missions.js",
"bin": {
Expand Down
13 changes: 13 additions & 0 deletions src/Aerofly/Mission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,9 @@ export class Mission {
this.cruise_altitude_ft = gpl.cruisingAlt;
}

// Assuming non AFS4 flight plans to start on the ground ;)
this.flight_setting = Mission.FLIGHT_SETTING_TAXI;

this.checkpoints = gpl.waypoints.map((w, i) => {
let cp = new MissionCheckpoint();
cp.lon_lat.lat = w.lat;
Expand All @@ -439,8 +442,18 @@ export class Mission {
) {
cp.type = i === 1 ? MissionCheckpoint.TYPE_DEPARTURE_RUNWAY : MissionCheckpoint.TYPE_DESTINATION_RUNWAY;
cp.name = cp.name.replace(/^(RW)/, "");

/*if (cp.type === MissionCheckpoint.TYPE_DEPARTURE_RUNWAY) {
gpl.departureRunway = undefined;
} else {
gpl.destinationRunway = undefined;
}*/
}

//this.addCheckpointAfter(0, 0.5);
// if (gpl.departureRunway) {
// if (gpl.destinationRunway) {

return cp;
});

Expand Down
2 changes: 1 addition & 1 deletion src/Import/MsfsPln.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class MsfsPln extends GarminFpl {

if (index === 0 || index === waypointsXml.length - 1) {
const runwayNumberFP = this.getXmlNode(xml, "RunwayNumberFP");
let runwayDesignatorFP = this.getXmlNode(xml, "RunwayNumberFP") as MsfsPlnRunwayDesignator | "";
let runwayDesignatorFP = this.getXmlNode(xml, "RunwayDesignatorFP") as MsfsPlnRunwayDesignator | "";
const rw = runwayNumberFP + (runwayDesignatorFP === "NONE" ? "" : runwayDesignatorFP.substring(0, 1));
if (runwayNumberFP) {
if (index === 0) {
Expand Down
15 changes: 15 additions & 0 deletions src/Tests/MsfsPlnTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class MsfsPlnTest extends Test {
this.testEGOV();
this.testLittleNavMap();
this.testGarminParse();
this.testRunway();
}

testEGOV() {
Expand All @@ -34,6 +35,7 @@ export class MsfsPlnTest extends Test {
this.group(MsfsPln.name + ": Mission conversion");
{
this.assertEquals(mission.checkpoints.length, 16);
this.assertEquals(mission.flight_setting, Mission.FLIGHT_SETTING_TAXI);
}

// Export Mission to XML
Expand Down Expand Up @@ -88,10 +90,23 @@ export class MsfsPlnTest extends Test {
this.group(MsfsPln.name + ": Mission conversion");
{
this.assertEquals(mission.checkpoints.length, 11);
this.assertEquals(mission.flight_setting, Mission.FLIGHT_SETTING_TAXI);
this.assertEquals(mission.checkpoints[0].type, MissionCheckpoint.TYPE_ORIGIN);
this.assertEquals(mission.checkpoints[1].type, MissionCheckpoint.TYPE_DEPARTURE_RUNWAY);
this.assertEquals(mission.checkpoints[9].type, MissionCheckpoint.TYPE_DESTINATION_RUNWAY);
this.assertEquals(mission.checkpoints[10].type, MissionCheckpoint.TYPE_DESTINATION);
}
}

testRunway() {
const pln = new MsfsPln(fs.readFileSync("./src/Tests/cases/ENHD_local_flight.pln", "utf8"));
this.group(MsfsPln.name + ": Runway check");
{
this.assertEquals(pln.departureRunway, "13");
this.assertEquals(pln.destinationRunway, "31");

const mission = new Mission("", "").fromGarminFpl(pln);
this.assertEquals(mission.checkpoints.length, 9);
}
}
}
Loading

0 comments on commit d591f76

Please sign in to comment.