2324 lines
73 KiB
C++
2324 lines
73 KiB
C++
// NasalFlightPlan.cxx -- expose FlightPlan classes to Nasal
|
|
//
|
|
// Written by James Turner, started 2020.
|
|
//
|
|
// Copyright (C) 2020 James Turner
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License as
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
#include "config.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <optional>
|
|
|
|
#include "NasalFlightPlan.hxx"
|
|
|
|
#include "NasalPositioned.hxx"
|
|
|
|
#include <Airports/airport.hxx>
|
|
#include <Airports/dynamics.hxx>
|
|
#include <Airports/parking.hxx>
|
|
#include <Airports/runways.hxx>
|
|
#include <Autopilot/route_mgr.hxx>
|
|
#include <Main/fg_props.hxx>
|
|
#include <Main/globals.hxx>
|
|
#include <Navaids/FlightPlan.hxx>
|
|
#include <Navaids/NavDataCache.hxx>
|
|
#include <Navaids/airways.hxx>
|
|
#include <Navaids/fix.hxx>
|
|
#include <Navaids/navrecord.hxx>
|
|
#include <Navaids/procedure.hxx>
|
|
#include <Navaids/routePath.hxx>
|
|
#include <Navaids/waypoint.hxx>
|
|
#include <Scenery/scenery.hxx>
|
|
#include <Scripting/NasalSys.hxx>
|
|
|
|
using namespace flightgear;
|
|
|
|
static void wayptGhostDestroy(void* g);
|
|
static void legGhostDestroy(void* g);
|
|
static void routeBaseGhostDestroy(void* g);
|
|
|
|
static const char* wayptGhostGetMember(naContext c, void* g, naRef field, naRef* out);
|
|
static void waypointGhostSetMember(naContext c, void* g, naRef field, naRef value);
|
|
|
|
static naGhostType WayptGhostType = {wayptGhostDestroy,
|
|
"waypoint",
|
|
wayptGhostGetMember,
|
|
waypointGhostSetMember};
|
|
|
|
static const char* legGhostGetMember(naContext c, void* g, naRef field, naRef* out);
|
|
static void legGhostSetMember(naContext c, void* g, naRef field, naRef value);
|
|
|
|
static naGhostType FPLegGhostType = {legGhostDestroy,
|
|
"flightplan-leg",
|
|
legGhostGetMember,
|
|
legGhostSetMember};
|
|
|
|
static const char* flightplanGhostGetMember(naContext c, void* g, naRef field, naRef* out);
|
|
static void flightplanGhostSetMember(naContext c, void* g, naRef field, naRef value);
|
|
|
|
static naGhostType FlightPlanGhostType = {routeBaseGhostDestroy,
|
|
"flightplan",
|
|
flightplanGhostGetMember,
|
|
flightplanGhostSetMember};
|
|
|
|
static const char* procedureGhostGetMember(naContext c, void* g, naRef field, naRef* out);
|
|
static naGhostType ProcedureGhostType = {routeBaseGhostDestroy,
|
|
"procedure",
|
|
procedureGhostGetMember,
|
|
0};
|
|
|
|
static const char* airwayGhostGetMember(naContext c, void* g, naRef field, naRef* out);
|
|
static naGhostType AirwayGhostType = {routeBaseGhostDestroy,
|
|
"airway",
|
|
airwayGhostGetMember,
|
|
0};
|
|
|
|
static void hashset(naContext c, naRef hash, const char* key, naRef val)
|
|
{
|
|
naRef s = naNewString(c);
|
|
naStr_fromdata(s, (char*)key, strlen(key));
|
|
naHash_set(hash, s, val);
|
|
}
|
|
|
|
static naRef stringToNasal(naContext c, const std::string& s)
|
|
{
|
|
return naStr_fromdata(naNewString(c),
|
|
const_cast<char*>(s.c_str()),
|
|
s.length());
|
|
}
|
|
|
|
static bool convertToNum(naRef v, double& result)
|
|
{
|
|
naRef n = naNumValue(v);
|
|
if (naIsNil(n)) {
|
|
return false; // couldn't convert
|
|
}
|
|
|
|
result = n.num;
|
|
return true;
|
|
}
|
|
|
|
static WayptFlag wayptFlagFromString(const char* s)
|
|
{
|
|
if (!strcmp(s, "sid")) return WPT_DEPARTURE;
|
|
if (!strcmp(s, "star")) return WPT_ARRIVAL;
|
|
if (!strcmp(s, "approach")) return WPT_APPROACH;
|
|
if (!strcmp(s, "missed")) return WPT_MISS;
|
|
if (!strcmp(s, "pseudo")) return WPT_PSEUDO;
|
|
|
|
return (WayptFlag)0;
|
|
}
|
|
|
|
static naRef wayptFlagToNasal(naContext c, unsigned int flags)
|
|
{
|
|
if (flags & WPT_PSEUDO) return stringToNasal(c, "pseudo");
|
|
if (flags & WPT_DEPARTURE) return stringToNasal(c, "sid");
|
|
if (flags & WPT_ARRIVAL) return stringToNasal(c, "star");
|
|
if (flags & WPT_MISS) return stringToNasal(c, "missed");
|
|
if (flags & WPT_APPROACH) return stringToNasal(c, "approach");
|
|
return naNil();
|
|
}
|
|
|
|
static std::optional<Airway::Level> airwayLevelFromNasal(naRef na)
|
|
{
|
|
if (naIsString(na)) {
|
|
const char* s = naStr_data(na);
|
|
if (!strcmp(s, "both")) return {Airway::Both};
|
|
if (!strcmp(s, "high")) return {Airway::HighLevel};
|
|
if (!strcmp(s, "low")) return {Airway::LowLevel};
|
|
return {};
|
|
}
|
|
|
|
if (!naIsNum(na)) {
|
|
return {};
|
|
}
|
|
|
|
const int num = static_cast<int>(na.num);
|
|
switch (num) {
|
|
case Airway::HighLevel: return {Airway::HighLevel};
|
|
case Airway::LowLevel: return {Airway::LowLevel};
|
|
case Airway::Both: return {Airway::Both};
|
|
default:
|
|
break;
|
|
// fall through
|
|
}
|
|
|
|
return {}; // fail
|
|
}
|
|
|
|
Waypt* wayptGhost(naRef r)
|
|
{
|
|
if (naGhost_type(r) == &WayptGhostType)
|
|
return (Waypt*)naGhost_ptr(r);
|
|
|
|
if (naGhost_type(r) == &FPLegGhostType) {
|
|
FlightPlan::Leg* leg = (FlightPlan::Leg*)naGhost_ptr(r);
|
|
return leg->waypoint();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void wayptGhostDestroy(void* g)
|
|
{
|
|
Waypt* wpt = (Waypt*)g;
|
|
if (!Waypt::put(wpt)) // unref
|
|
delete wpt;
|
|
}
|
|
|
|
static void legGhostDestroy(void* g)
|
|
{
|
|
FlightPlan::Leg* leg = (FlightPlan::Leg*)g;
|
|
if (!FlightPlan::Leg::put(leg)) // unref
|
|
delete leg;
|
|
}
|
|
|
|
|
|
FlightPlan::Leg* fpLegGhost(naRef r)
|
|
{
|
|
if (naGhost_type(r) == &FPLegGhostType)
|
|
return (FlightPlan::Leg*)naGhost_ptr(r);
|
|
return 0;
|
|
}
|
|
|
|
Procedure* procedureGhost(naRef r)
|
|
{
|
|
if (naGhost_type(r) == &ProcedureGhostType)
|
|
return (Procedure*)naGhost_ptr(r);
|
|
return 0;
|
|
}
|
|
|
|
static FlightPlan* flightplanGhost(naRef r)
|
|
{
|
|
if (naGhost_type(r) == &FlightPlanGhostType)
|
|
return (FlightPlan*)naGhost_ptr(r);
|
|
return 0;
|
|
}
|
|
|
|
static Airway* airwayGhost(naRef r)
|
|
{
|
|
if (naGhost_type(r) == &AirwayGhostType)
|
|
return (Airway*)naGhost_ptr(r);
|
|
return 0;
|
|
}
|
|
|
|
static void routeBaseGhostDestroy(void* g)
|
|
{
|
|
RouteBase* r = (RouteBase*)g;
|
|
if (!RouteBase::put(r)) // unref
|
|
delete r;
|
|
}
|
|
|
|
static naRef flightplanPrototype;
|
|
static naRef fpLegPrototype;
|
|
static naRef procedurePrototype;
|
|
static naRef airwayPrototype;
|
|
|
|
naRef ghostForWaypt(naContext c, const Waypt* wpt)
|
|
{
|
|
if (!wpt) {
|
|
return naNil();
|
|
}
|
|
|
|
Waypt::get(wpt); // take a ref
|
|
return naNewGhost2(c, &WayptGhostType, (void*)wpt);
|
|
}
|
|
|
|
naRef ghostForLeg(naContext c, const FlightPlan::Leg* leg)
|
|
{
|
|
if (!leg) {
|
|
return naNil();
|
|
}
|
|
|
|
FlightPlan::Leg::get(leg); // take a ref
|
|
return naNewGhost2(c, &FPLegGhostType, (void*)leg);
|
|
}
|
|
|
|
naRef ghostForFlightPlan(naContext c, const FlightPlan* fp)
|
|
{
|
|
if (!fp) {
|
|
return naNil();
|
|
}
|
|
|
|
FlightPlan::get(fp); // take a ref
|
|
return naNewGhost2(c, &FlightPlanGhostType, (void*)fp);
|
|
}
|
|
|
|
naRef ghostForProcedure(naContext c, const Procedure* proc)
|
|
{
|
|
if (!proc) {
|
|
return naNil();
|
|
}
|
|
|
|
FlightPlan::get(proc); // take a ref
|
|
return naNewGhost2(c, &ProcedureGhostType, (void*)proc);
|
|
}
|
|
|
|
naRef ghostForAirway(naContext c, const Airway* awy)
|
|
{
|
|
if (!awy) {
|
|
return naNil();
|
|
}
|
|
|
|
Airway::get(awy); // take a ref
|
|
return naNewGhost2(c, &AirwayGhostType, (void*)awy);
|
|
}
|
|
|
|
// Return the navaid ghost associated with a waypoint of navaid type.
|
|
static naRef waypointNavaid(naContext c, Waypt* wpt)
|
|
{
|
|
FGPositioned* pos = wpt->source();
|
|
if (!pos || (!FGNavRecord::isNavaidType(pos) && !fgpositioned_cast<FGFix>(pos))) {
|
|
return naNil();
|
|
}
|
|
|
|
return ghostForPositioned(c, wpt->source());
|
|
}
|
|
|
|
// Return the airport ghost associated with a waypoint of airport or runway
|
|
// type.
|
|
static naRef waypointAirport(naContext c, Waypt* wpt)
|
|
{
|
|
FGPositioned* pos = wpt->source();
|
|
|
|
if (FGPositioned::isRunwayType(pos)) {
|
|
pos = static_cast<FGRunway*>(pos)->airport();
|
|
} else if (!FGPositioned::isAirportType(pos)) {
|
|
return naNil();
|
|
}
|
|
|
|
return ghostForAirport(c, static_cast<FGAirport*>(pos));
|
|
}
|
|
|
|
// Return the runway ghost associated with a waypoint of runway type.
|
|
static naRef waypointRunway(naContext c, Waypt* wpt)
|
|
{
|
|
FGPositioned* pos = wpt->source();
|
|
|
|
if (!FGPositioned::isRunwayType(pos)) {
|
|
return naNil();
|
|
}
|
|
|
|
return ghostForRunway(c, static_cast<FGRunway*>(pos));
|
|
}
|
|
|
|
static const char* waypointCommonGetMember(naContext c, Waypt* wpt, const char* fieldName, naRef* out)
|
|
{
|
|
if (!strcmp(fieldName, "wp_name") || !strcmp(fieldName, "id"))
|
|
*out = stringToNasal(c, wpt->ident());
|
|
else if (!strcmp(fieldName, "wp_type"))
|
|
*out = stringToNasal(c, wpt->type());
|
|
else if (!strcmp(fieldName, "wp_role"))
|
|
*out = wayptFlagToNasal(c, wpt->flags());
|
|
else if (!strcmp(fieldName, "wp_lat") || !strcmp(fieldName, "lat"))
|
|
*out = naNum(wpt->position().getLatitudeDeg());
|
|
else if (!strcmp(fieldName, "wp_lon") || !strcmp(fieldName, "lon"))
|
|
*out = naNum(wpt->position().getLongitudeDeg());
|
|
else if (!strcmp(fieldName, "wp_parent_name")) {
|
|
if (wpt->owner()) {
|
|
*out = stringToNasal(c, wpt->owner()->ident());
|
|
} else {
|
|
*out = naNil();
|
|
}
|
|
} else if (!strcmp(fieldName, "wp_parent")) {
|
|
// TODO add ghostForRouteElement to cover all this
|
|
Procedure* proc = dynamic_cast<Procedure*>(wpt->owner());
|
|
if (proc) {
|
|
*out = ghostForProcedure(c, proc);
|
|
} else {
|
|
Airway* airway = dynamic_cast<Airway*>(wpt->owner());
|
|
if (airway) {
|
|
*out = ghostForAirway(c, airway);
|
|
} else {
|
|
*out = naNil();
|
|
}
|
|
}
|
|
} else if (!strcmp(fieldName, "fly_type")) {
|
|
if (wpt->type() == "hold") {
|
|
*out = stringToNasal(c, "Hold");
|
|
} else {
|
|
*out = stringToNasal(c, wpt->flag(WPT_OVERFLIGHT) ? "flyOver" : "flyBy");
|
|
}
|
|
} else if (!strcmp(fieldName, "heading_course")) {
|
|
*out = naNum(wpt->headingRadialDeg());
|
|
} else if (!strcmp(fieldName, "navaid")) {
|
|
*out = waypointNavaid(c, wpt);
|
|
} else if (!strcmp(fieldName, "airport")) {
|
|
*out = waypointAirport(c, wpt);
|
|
} else if (!strcmp(fieldName, "runway")) {
|
|
*out = waypointRunway(c, wpt);
|
|
} else if (!strcmp(fieldName, "airway")) {
|
|
if (wpt->type() == "via") {
|
|
AirwayRef awy = static_cast<Via*>(wpt)->airway();
|
|
assert(awy);
|
|
*out = ghostForAirway(c, awy);
|
|
} else {
|
|
Airway* airway = dynamic_cast<Airway*>(wpt->owner());
|
|
if (airway) {
|
|
*out = ghostForAirway(c, airway);
|
|
} else {
|
|
*out = naNil();
|
|
}
|
|
}
|
|
} else if (!strcmp(fieldName, "hidden")) {
|
|
*out = naNum(wpt->flag(WPT_HIDDEN));
|
|
} else if (wpt->type() == "hold") {
|
|
// hold-specific properties
|
|
const auto hold = static_cast<Hold*>(wpt);
|
|
if (!strcmp(fieldName, "hold_is_left_handed")) {
|
|
*out = naNum(hold->isLeftHanded());
|
|
} else if (!strcmp(fieldName, "hold_is_distance")) {
|
|
*out = naNum(hold->isDistance());
|
|
} else if (!strcmp(fieldName, "hold_is_time")) {
|
|
*out = naNum(!hold->isDistance());
|
|
} else if (!strcmp(fieldName, "hold_inbound_radial")) {
|
|
*out = naNum(hold->inboundRadial());
|
|
} else if (!strcmp(fieldName, "hold_heading_radial_deg")) {
|
|
*out = naNum(hold->inboundRadial());
|
|
} else if (!strcmp(fieldName, "hold_time_or_distance")) {
|
|
// This is the leg length, defined either as a time in seconds, or a
|
|
// distance in nm.
|
|
*out = naNum(hold->timeOrDistance());
|
|
} else {
|
|
return nullptr; // member not found
|
|
}
|
|
} else {
|
|
return nullptr; // member not found
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
static bool waypointCommonSetMember(naContext c, Waypt* wpt, const char* fieldName, naRef value)
|
|
{
|
|
if (!strcmp(fieldName, "wp_role")) {
|
|
if (!naIsString(value)) naRuntimeError(c, "wp_role must be a string");
|
|
if (wpt->owner() != NULL) naRuntimeError(c, "cannot override wp_role on waypoint with parent");
|
|
WayptFlag f = wayptFlagFromString(naStr_data(value));
|
|
if (f == 0) {
|
|
naRuntimeError(c, "unrecognized wp_role value %s", naStr_data(value));
|
|
}
|
|
|
|
wpt->setFlag(f, true);
|
|
} else if (!strcmp(fieldName, "fly_type")) {
|
|
if (!naIsString(value)) naRuntimeError(c, "fly_type must be a string");
|
|
bool flyOver = (strcmp(naStr_data(value), "flyOver") == 0);
|
|
wpt->setFlag(WPT_OVERFLIGHT, flyOver);
|
|
} else if (!strcmp(fieldName, "hidden")) {
|
|
if (!naIsNum(value)) naRuntimeError(c, "wpt.hidden must be a number");
|
|
wpt->setFlag(WPT_HIDDEN, static_cast<int>(value.num) != 0);
|
|
} else if (wpt->type() == "hold") {
|
|
const auto hold = static_cast<Hold*>(wpt);
|
|
if (!strcmp(fieldName, "hold_heading_radial_deg")) {
|
|
if (!naIsNum(value)) naRuntimeError(c, "set hold_heading_radial_deg: invalid hold radial");
|
|
hold->setHoldRadial(value.num);
|
|
} else if (!strcmp("hold_is_left_handed", fieldName)) {
|
|
bool leftHanded = static_cast<int>(value.num) > 0;
|
|
if (leftHanded) {
|
|
hold->setLeftHanded();
|
|
} else {
|
|
hold->setRightHanded();
|
|
}
|
|
}
|
|
} else {
|
|
// nothing changed
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static const char* wayptGhostGetMember(naContext c, void* g, naRef field, naRef* out)
|
|
{
|
|
const char* fieldName = naStr_data(field);
|
|
Waypt* wpt = (flightgear::Waypt*)g;
|
|
return waypointCommonGetMember(c, wpt, fieldName, out);
|
|
}
|
|
|
|
static RouteRestriction routeRestrictionFromArg(naRef arg)
|
|
{
|
|
if (naIsNil(arg) || !naIsString(arg)) {
|
|
return RESTRICT_NONE;
|
|
}
|
|
|
|
const std::string u = simgear::strutils::lowercase(naStr_data(arg));
|
|
if (u == "computed") return RESTRICT_COMPUTED;
|
|
if (u == "at") return RESTRICT_AT;
|
|
if (u == "above") return RESTRICT_ABOVE;
|
|
if (u == "below") return RESTRICT_BELOW;
|
|
if (u == "mach") return SPEED_RESTRICT_MACH;
|
|
if (u == "computed-mach") return SPEED_COMPUTED_MACH;
|
|
if (u == "delete") return RESTRICT_DELETE;
|
|
return RESTRICT_NONE;
|
|
};
|
|
|
|
naRef routeRestrictionToNasal(naContext c, RouteRestriction rr)
|
|
{
|
|
switch (rr) {
|
|
case RESTRICT_NONE: return naNil();
|
|
case RESTRICT_AT: return stringToNasal(c, "at");
|
|
case RESTRICT_ABOVE: return stringToNasal(c, "above");
|
|
case RESTRICT_BELOW: return stringToNasal(c, "below");
|
|
case SPEED_RESTRICT_MACH: return stringToNasal(c, "mach");
|
|
case RESTRICT_COMPUTED: return stringToNasal(c, "computed");
|
|
case SPEED_COMPUTED_MACH: return stringToNasal(c, "computed-mach");
|
|
case RESTRICT_DELETE: return stringToNasal(c, "delete");
|
|
}
|
|
|
|
return naNil();
|
|
}
|
|
|
|
// navaid() method of FPLeg ghosts
|
|
static naRef f_fpLeg_navaid(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
flightgear::Waypt* w = wayptGhost(me);
|
|
if (!w) {
|
|
naRuntimeError(c,
|
|
"flightplan-leg.navaid() called, but can't find the "
|
|
"underlying waypoint for the flightplan-leg object");
|
|
}
|
|
|
|
return waypointNavaid(c, w);
|
|
}
|
|
|
|
// airport() method of FPLeg ghosts
|
|
static naRef f_fpLeg_airport(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
flightgear::Waypt* w = wayptGhost(me);
|
|
if (!w) {
|
|
naRuntimeError(c,
|
|
"flightplan-leg.airport() called, but can't find the "
|
|
"underlying waypoint for the flightplan-leg object");
|
|
}
|
|
|
|
return waypointAirport(c, w);
|
|
}
|
|
|
|
// runway() method of FPLeg ghosts
|
|
static naRef f_fpLeg_runway(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
flightgear::Waypt* w = wayptGhost(me);
|
|
if (!w) {
|
|
naRuntimeError(c,
|
|
"flightplan-leg.runway() called, but can't find the "
|
|
"underlying waypoint for the flightplan-leg object");
|
|
}
|
|
|
|
return waypointRunway(c, w);
|
|
}
|
|
|
|
static const char* legGhostGetMember(naContext c, void* g, naRef field, naRef* out)
|
|
{
|
|
const char* fieldName = naStr_data(field);
|
|
FlightPlan::Leg* leg = (FlightPlan::Leg*)g;
|
|
if (!leg) {
|
|
*out = naNil();
|
|
naRuntimeError(c, "leg ghost member fetched, but no associated leg object found");
|
|
return "";
|
|
}
|
|
|
|
Waypt* wpt = leg->waypoint();
|
|
|
|
if (!strcmp(fieldName, "parents")) {
|
|
*out = naNewVector(c);
|
|
naVec_append(*out, fpLegPrototype);
|
|
} else if (!strcmp(fieldName, "index")) {
|
|
*out = naNum(leg->index());
|
|
} else if (!strcmp(fieldName, "alt_cstr")) {
|
|
*out = naNum(leg->altitudeFt());
|
|
} else if (!strcmp(fieldName, "alt_cstr_type")) {
|
|
*out = routeRestrictionToNasal(c, leg->altitudeRestriction());
|
|
} else if (!strcmp(fieldName, "speed_cstr")) {
|
|
double s = isMachRestrict(leg->speedRestriction()) ? leg->speedMach() : leg->speedKts();
|
|
*out = naNum(s);
|
|
} else if (!strcmp(fieldName, "speed_cstr_type")) {
|
|
*out = routeRestrictionToNasal(c, leg->speedRestriction());
|
|
} else if (!strcmp(fieldName, "leg_distance")) {
|
|
*out = naNum(leg->distanceNm());
|
|
} else if (!strcmp(fieldName, "leg_bearing")) {
|
|
*out = naNum(leg->courseDeg());
|
|
} else if (!strcmp(fieldName, "distance_along_route")) {
|
|
*out = naNum(leg->distanceAlongRoute());
|
|
} else if (!strcmp(fieldName, "airport")) {
|
|
*out = naNewFunc(c, naNewCCode(c, f_fpLeg_airport));
|
|
} else if (!strcmp(fieldName, "navaid")) {
|
|
*out = naNewFunc(c, naNewCCode(c, f_fpLeg_navaid));
|
|
} else if (!strcmp(fieldName, "runway")) {
|
|
*out = naNewFunc(c, naNewCCode(c, f_fpLeg_runway));
|
|
} else if (!strcmp(fieldName, "hold_count")) {
|
|
*out = naNum(leg->holdCount());
|
|
} else { // check for fields defined on the underlying waypoint
|
|
if (wpt) { // FIXME null check shouldn't be required, check refcount
|
|
return waypointCommonGetMember(c, wpt, fieldName, out);
|
|
} else {
|
|
naRuntimeError(c, "leg ghost member fetched, but no underlying waypoint object found");
|
|
}
|
|
}
|
|
|
|
return ""; // success
|
|
}
|
|
|
|
static void waypointGhostSetMember(naContext c, void* g, naRef field, naRef value)
|
|
{
|
|
const char* fieldName = naStr_data(field);
|
|
Waypt* wpt = (Waypt*)g;
|
|
waypointCommonSetMember(c, wpt, fieldName, value);
|
|
}
|
|
|
|
static void legGhostSetMember(naContext c, void* g, naRef field, naRef value)
|
|
{
|
|
const char* fieldName = naStr_data(field);
|
|
FlightPlan::Leg* leg = (FlightPlan::Leg*)g;
|
|
|
|
bool didChange = false;
|
|
if (!strcmp(fieldName, "hold_count")) {
|
|
const int count = static_cast<int>(value.num);
|
|
// this may upgrade the waypoint to a hold
|
|
if (!leg->setHoldCount(count))
|
|
naRuntimeError(c, "unable to set hold on leg waypoint: maybe unsuitable waypt type?");
|
|
} else if (!strcmp(fieldName, "hold_heading_radial_deg")) {
|
|
if (!leg->convertWaypointToHold())
|
|
naRuntimeError(c, "couldn't convert leg waypoint into a hold");
|
|
|
|
// now we can call the base method
|
|
didChange = waypointCommonSetMember(c, leg->waypoint(), fieldName, value);
|
|
} else {
|
|
didChange = waypointCommonSetMember(c, leg->waypoint(), fieldName, value);
|
|
}
|
|
|
|
if (didChange) {
|
|
leg->markWaypointDirty();
|
|
}
|
|
}
|
|
|
|
static const char* flightplanGhostGetMember(naContext c, void* g, naRef field, naRef* out)
|
|
{
|
|
const char* fieldName = naStr_data(field);
|
|
FlightPlan* fp = static_cast<FlightPlan*>(g);
|
|
|
|
if (!strcmp(fieldName, "parents")) {
|
|
*out = naNewVector(c);
|
|
naVec_append(*out, flightplanPrototype);
|
|
} else if (!strcmp(fieldName, "id"))
|
|
*out = stringToNasal(c, fp->ident());
|
|
else if (!strcmp(fieldName, "departure"))
|
|
*out = ghostForAirport(c, fp->departureAirport());
|
|
else if (!strcmp(fieldName, "destination"))
|
|
*out = ghostForAirport(c, fp->destinationAirport());
|
|
else if (!strcmp(fieldName, "departure_runway"))
|
|
*out = ghostForRunway(c, fp->departureRunway());
|
|
else if (!strcmp(fieldName, "destination_runway"))
|
|
*out = ghostForRunway(c, fp->destinationRunway());
|
|
else if (!strcmp(fieldName, "sid"))
|
|
*out = ghostForProcedure(c, fp->sid());
|
|
else if (!strcmp(fieldName, "sid_trans"))
|
|
*out = ghostForProcedure(c, fp->sidTransition());
|
|
else if (!strcmp(fieldName, "star"))
|
|
*out = ghostForProcedure(c, fp->star());
|
|
else if (!strcmp(fieldName, "star_trans"))
|
|
*out = ghostForProcedure(c, fp->starTransition());
|
|
else if (!strcmp(fieldName, "approach"))
|
|
*out = ghostForProcedure(c, fp->approach());
|
|
else if (!strcmp(fieldName, "approach_trans"))
|
|
*out = ghostForProcedure(c, fp->approachTransition());
|
|
else if (!strcmp(fieldName, "current"))
|
|
*out = naNum(fp->currentIndex());
|
|
else if (!strcmp(fieldName, "aircraftCategory"))
|
|
*out = stringToNasal(c, fp->icaoAircraftCategory());
|
|
else if (!strcmp(fieldName, "followLegTrackToFix"))
|
|
*out = naNum(fp->followLegTrackToFixes());
|
|
else if (!strcmp(fieldName, "active"))
|
|
*out = naNum(fp->isActive());
|
|
else if (!strcmp(fieldName, "cruiseAltitudeFt"))
|
|
*out = naNum(fp->cruiseAltitudeFt());
|
|
else if (!strcmp(fieldName, "cruiseFlightLevel"))
|
|
*out = naNum(fp->cruiseFlightLevel());
|
|
else if (!strcmp(fieldName, "cruiseAltitudeM"))
|
|
*out = naNum(fp->cruiseAltitudeM());
|
|
else if (!strcmp(fieldName, "cruiseSpeedKt"))
|
|
*out = naNum(fp->cruiseSpeedKnots());
|
|
else if (!strcmp(fieldName, "cruiseSpeedMach"))
|
|
*out = naNum(fp->cruiseSpeedMach());
|
|
else if (!strcmp(fieldName, "cruiseSpeedKPH"))
|
|
*out = naNum(fp->cruiseSpeedKPH());
|
|
else if (!strcmp(fieldName, "remarks"))
|
|
*out = stringToNasal(c, fp->remarks());
|
|
else if (!strcmp(fieldName, "callsign"))
|
|
*out = stringToNasal(c, fp->callsign());
|
|
else if (!strcmp(fieldName, "estimatedDurationMins"))
|
|
*out = naNum(fp->estimatedDurationMinutes());
|
|
else if (!strcmp(fieldName, "firstNonDepartureLeg"))
|
|
*out = naNum(fp->indexOfFirstNonDepartureWaypoint());
|
|
else if (!strcmp(fieldName, "firstArrivalLeg"))
|
|
*out = naNum(fp->indexOfFirstArrivalWaypoint());
|
|
else if (!strcmp(fieldName, "firstApproachLeg"))
|
|
*out = naNum(fp->indexOfFirstApproachWaypoint());
|
|
else if (!strcmp(fieldName, "destination_runway_leg"))
|
|
*out = naNum(fp->indexOfDestinationRunwayWaypoint());
|
|
else if (!strcmp(fieldName, "totalDistanceNm"))
|
|
*out = naNum(fp->totalDistanceNm());
|
|
else if (!strcmp(fieldName, "isRoute"))
|
|
*out = naNum(fp->isRoute());
|
|
|
|
else {
|
|
return nullptr;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
static void flightplanGhostSetMember(naContext c, void* g, naRef field, naRef value)
|
|
{
|
|
const char* fieldName = naStr_data(field);
|
|
FlightPlan* fp = static_cast<FlightPlan*>(g);
|
|
|
|
if (!strcmp(fieldName, "id")) {
|
|
if (!naIsString(value)) naRuntimeError(c, "flightplan.id must be a string");
|
|
fp->setIdent(naStr_data(value));
|
|
} else if (!strcmp(fieldName, "current")) {
|
|
int index = static_cast<int>(value.num);
|
|
if ((index < -1) || (index >= fp->numLegs())) {
|
|
naRuntimeError(c, "flightplan.current must be a valid index or -1");
|
|
}
|
|
fp->setCurrentIndex(index);
|
|
} else if (!strcmp(fieldName, "departure")) {
|
|
FGAirport* apt = airportGhost(value);
|
|
if (apt) {
|
|
fp->setDeparture(apt);
|
|
return;
|
|
}
|
|
|
|
FGRunway* rwy = runwayGhost(value);
|
|
if (rwy) {
|
|
fp->setDeparture(rwy);
|
|
return;
|
|
}
|
|
|
|
if (naIsNil(value)) {
|
|
fp->clearDeparture();
|
|
return;
|
|
}
|
|
|
|
naRuntimeError(c, "bad argument type setting departure");
|
|
} else if (!strcmp(fieldName, "destination")) {
|
|
FGAirport* apt = airportGhost(value);
|
|
if (apt) {
|
|
fp->setDestination(apt);
|
|
return;
|
|
}
|
|
|
|
FGRunway* rwy = runwayGhost(value);
|
|
if (rwy) {
|
|
fp->setDestination(rwy);
|
|
return;
|
|
}
|
|
|
|
if (naIsNil(value)) {
|
|
fp->clearDestination();
|
|
return;
|
|
}
|
|
|
|
naRuntimeError(c, "bad argument type setting destination");
|
|
} else if (!strcmp(fieldName, "departure_runway")) {
|
|
FGRunway* rwy = runwayGhost(value);
|
|
if (rwy) {
|
|
fp->setDeparture(rwy);
|
|
return;
|
|
}
|
|
|
|
naRuntimeError(c, "bad argument type setting departure runway");
|
|
} else if (!strcmp(fieldName, "destination_runway")) {
|
|
if (naIsNil(value)) {
|
|
fp->setDestination(static_cast<FGRunway*>(nullptr));
|
|
return;
|
|
}
|
|
|
|
FGRunway* rwy = runwayGhost(value);
|
|
if (rwy) {
|
|
fp->setDestination(rwy);
|
|
return;
|
|
}
|
|
|
|
naRuntimeError(c, "bad argument type setting destination runway");
|
|
} else if (!strcmp(fieldName, "sid")) {
|
|
Procedure* proc = procedureGhost(value);
|
|
if (proc && (proc->type() == PROCEDURE_SID)) {
|
|
fp->setSID((flightgear::SID*)proc);
|
|
return;
|
|
}
|
|
// allow a SID transition to be set, implicitly include the SID itself
|
|
if (proc && (proc->type() == PROCEDURE_TRANSITION)) {
|
|
fp->setSID((Transition*)proc);
|
|
return;
|
|
}
|
|
|
|
if (naIsString(value)) {
|
|
const std::string s(naStr_data(value));
|
|
FGAirport* apt = fp->departureAirport();
|
|
auto sid = apt->findSIDWithIdent(s);
|
|
if (!sid) {
|
|
naRuntimeError(c, "Unknown SID %s at %s", s.c_str(), apt->ident().c_str());
|
|
}
|
|
|
|
fp->setSID(sid);
|
|
return;
|
|
}
|
|
|
|
if (naIsNil(value)) {
|
|
fp->clearSID();
|
|
return;
|
|
}
|
|
|
|
naRuntimeError(c, "bad argument type setting SID");
|
|
} else if (!strcmp(fieldName, "sid_trans")) {
|
|
Procedure* proc = procedureGhost(value);
|
|
if (proc && (proc->type() == PROCEDURE_TRANSITION)) {
|
|
fp->setSID((Transition*)proc);
|
|
return;
|
|
}
|
|
|
|
if (naIsNil(value)) {
|
|
fp->setSID(fp->sid(), string{});
|
|
return;
|
|
}
|
|
|
|
if (naIsString(value)) {
|
|
const std::string s(naStr_data(value));
|
|
Transition* trans = nullptr;
|
|
|
|
if (fp->sid()) {
|
|
trans = fp->sid()->findTransitionByName(s);
|
|
if (!trans) {
|
|
naRuntimeError(c, "No such transition %s for SID %s at %s",
|
|
s.c_str(),
|
|
fp->sid()->ident().c_str(),
|
|
fp->departureAirport()->ident().c_str());
|
|
}
|
|
} else {
|
|
trans = fp->departureAirport()->selectSIDByTransition(fp->departureRunway(), s);
|
|
if (!trans) {
|
|
naRuntimeError(c, "Couldn't find SID transition to %s at %s",
|
|
s.c_str(),
|
|
fp->departureAirport()->ident().c_str());
|
|
}
|
|
}
|
|
|
|
if (trans) {
|
|
fp->setSID(trans);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
naRuntimeError(c, "bad argument type setting sid_trans");
|
|
} else if (!strcmp(fieldName, "star")) {
|
|
Procedure* proc = procedureGhost(value);
|
|
if (proc && (proc->type() == PROCEDURE_STAR)) {
|
|
fp->setSTAR((STAR*)proc);
|
|
return;
|
|
}
|
|
|
|
if (proc && (proc->type() == PROCEDURE_TRANSITION)) {
|
|
fp->setSTAR((Transition*)proc);
|
|
return;
|
|
}
|
|
|
|
if (naIsString(value)) {
|
|
const std::string s(naStr_data(value));
|
|
FGAirport* apt = fp->destinationAirport();
|
|
auto star = apt->findSTARWithIdent(s);
|
|
if (!star) {
|
|
naRuntimeError(c, "Unknown SID %s at %s", s.c_str(), apt->ident().c_str());
|
|
}
|
|
fp->setSTAR(star);
|
|
return;
|
|
}
|
|
|
|
if (naIsNil(value)) {
|
|
fp->clearSTAR();
|
|
return;
|
|
}
|
|
|
|
naRuntimeError(c, "bad argument type setting STAR");
|
|
} else if (!strcmp(fieldName, "star_trans")) {
|
|
Procedure* proc = procedureGhost(value);
|
|
if (proc && (proc->type() == PROCEDURE_TRANSITION)) {
|
|
fp->setSTAR((Transition*)proc);
|
|
return;
|
|
}
|
|
|
|
if (naIsNil(value)) {
|
|
fp->setSTAR(fp->star(), string{});
|
|
return;
|
|
}
|
|
|
|
if (naIsString(value)) {
|
|
const std::string s(naStr_data(value));
|
|
Transition* trans = nullptr;
|
|
|
|
if (fp->star()) {
|
|
trans = fp->star()->findTransitionByName(s);
|
|
if (!trans) {
|
|
naRuntimeError(c, "No such transition %s for STAR %s at %s",
|
|
s.c_str(),
|
|
fp->star()->ident().c_str(),
|
|
fp->destinationAirport()->ident().c_str());
|
|
}
|
|
} else {
|
|
trans = fp->destinationAirport()->selectSTARByTransition(fp->destinationRunway(), s);
|
|
if (!trans) {
|
|
naRuntimeError(c, "Couldn't find STAR transition to %s at %s",
|
|
s.c_str(),
|
|
fp->destinationAirport()->ident().c_str());
|
|
}
|
|
}
|
|
|
|
if (trans) {
|
|
fp->setSTAR(trans);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
naRuntimeError(c, "bad argument type setting star_trans");
|
|
} else if (!strcmp(fieldName, "approach")) {
|
|
Procedure* proc = procedureGhost(value);
|
|
if (proc && Approach::isApproach(proc->type())) {
|
|
fp->setApproach((Approach*)proc);
|
|
return;
|
|
}
|
|
|
|
if (proc && (proc->type() == PROCEDURE_TRANSITION)) {
|
|
fp->setApproach((Transition*)proc);
|
|
return;
|
|
}
|
|
|
|
if (naIsString(value)) {
|
|
FGAirport* apt = fp->destinationAirport();
|
|
fp->setApproach(apt->findApproachWithIdent(naStr_data(value)));
|
|
return;
|
|
}
|
|
|
|
if (naIsNil(value)) {
|
|
fp->setApproach(static_cast<Approach*>(nullptr));
|
|
return;
|
|
}
|
|
|
|
naRuntimeError(c, "bad argument type setting approach");
|
|
} else if (!strcmp(fieldName, "approach_trans")) {
|
|
Procedure* proc = procedureGhost(value);
|
|
if (proc && (proc->type() == PROCEDURE_TRANSITION)) {
|
|
fp->setApproach((Transition*)proc);
|
|
return;
|
|
}
|
|
|
|
if (naIsNil(value)) {
|
|
fp->setApproach(fp->approach(), string{});
|
|
return;
|
|
}
|
|
|
|
if (naIsString(value)) {
|
|
const std::string s(naStr_data(value));
|
|
Transition* trans = nullptr;
|
|
|
|
if (fp->approach()) {
|
|
trans = fp->approach()->findTransitionByName(s);
|
|
if (!trans) {
|
|
naRuntimeError(c, "No such transition %s for approach %s at %s",
|
|
s.c_str(),
|
|
fp->approach()->ident().c_str(),
|
|
fp->destinationAirport()->ident().c_str());
|
|
}
|
|
} else {
|
|
naRuntimeError(c, "No approach selected, can't set approach_trans");
|
|
}
|
|
|
|
if (trans) {
|
|
fp->setApproach(trans);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
naRuntimeError(c, "bad argument type setting approach_trans");
|
|
} else if (!strcmp(fieldName, "aircraftCategory")) {
|
|
if (!naIsString(value)) naRuntimeError(c, "aircraftCategory must be a string");
|
|
fp->setIcaoAircraftCategory(naStr_data(value));
|
|
} else if (!strcmp(fieldName, "followLegTrackToFix")) {
|
|
fp->setFollowLegTrackToFixes(static_cast<bool>(value.num));
|
|
} else if (!strcmp(fieldName, "cruiseAltitudeFt")) {
|
|
fp->setCruiseAltitudeFt(static_cast<int>(value.num));
|
|
} else if (!strcmp(fieldName, "cruiseAltitudeM")) {
|
|
fp->setCruiseAltitudeM(static_cast<int>(value.num));
|
|
} else if (!strcmp(fieldName, "cruiseFlightLevel")) {
|
|
fp->setCruiseFlightLevel(static_cast<int>(value.num));
|
|
} else if (!strcmp(fieldName, "cruiseSpeedKt")) {
|
|
fp->setCruiseSpeedKnots(static_cast<int>(value.num));
|
|
} else if (!strcmp(fieldName, "cruiseSpeedKPH")) {
|
|
fp->setCruiseSpeedKPH(static_cast<int>(value.num));
|
|
} else if (!strcmp(fieldName, "cruiseSpeedMach")) {
|
|
fp->setCruiseSpeedMach(value.num);
|
|
} else if (!strcmp(fieldName, "callsign")) {
|
|
if (!naIsString(value)) naRuntimeError(c, "flightplan.callsign must be a string");
|
|
fp->setCallsign(naStr_data(value));
|
|
} else if (!strcmp(fieldName, "remarks")) {
|
|
if (!naIsString(value)) naRuntimeError(c, "flightplan.remarks must be a string");
|
|
fp->setRemarks(naStr_data(value));
|
|
} else if (!strcmp(fieldName, "estimatedDurationMins")) {
|
|
fp->setEstimatedDurationMinutes(static_cast<int>(value.num));
|
|
}
|
|
}
|
|
|
|
static naRef procedureTpType(naContext c, ProcedureType ty)
|
|
{
|
|
switch (ty) {
|
|
case PROCEDURE_SID: return stringToNasal(c, "sid");
|
|
case PROCEDURE_STAR: return stringToNasal(c, "star");
|
|
case PROCEDURE_TRANSITION: return stringToNasal(c, "transition");
|
|
case PROCEDURE_RUNWAY_TRANSITION: return stringToNasal(c, "rwy_transition");
|
|
case PROCEDURE_APPROACH_VOR:
|
|
case PROCEDURE_APPROACH_ILS:
|
|
case PROCEDURE_APPROACH_RNAV:
|
|
case PROCEDURE_APPROACH_NDB:
|
|
return stringToNasal(c, "IAP");
|
|
default:
|
|
return naNil();
|
|
}
|
|
}
|
|
|
|
static naRef procedureRadioType(naContext c, ProcedureType ty)
|
|
{
|
|
switch (ty) {
|
|
case PROCEDURE_APPROACH_VOR: return stringToNasal(c, "VOR");
|
|
case PROCEDURE_APPROACH_ILS: return stringToNasal(c, "ILS");
|
|
case PROCEDURE_APPROACH_RNAV: return stringToNasal(c, "RNAV");
|
|
case PROCEDURE_APPROACH_NDB: return stringToNasal(c, "NDB");
|
|
default:
|
|
return naNil();
|
|
}
|
|
}
|
|
|
|
static const char* procedureGhostGetMember(naContext c, void* g, naRef field, naRef* out)
|
|
{
|
|
const char* fieldName = naStr_data(field);
|
|
Procedure* proc = (Procedure*)g;
|
|
|
|
if (!strcmp(fieldName, "parents")) {
|
|
*out = naNewVector(c);
|
|
naVec_append(*out, procedurePrototype);
|
|
} else if (!strcmp(fieldName, "id"))
|
|
*out = stringToNasal(c, proc->ident());
|
|
else if (!strcmp(fieldName, "airport"))
|
|
*out = ghostForAirport(c, proc->airport());
|
|
else if (!strcmp(fieldName, "tp_type"))
|
|
*out = procedureTpType(c, proc->type());
|
|
else if (!strcmp(fieldName, "radio"))
|
|
*out = procedureRadioType(c, proc->type());
|
|
else if (!strcmp(fieldName, "runways")) {
|
|
*out = naNewVector(c);
|
|
for (FGRunwayRef rwy : proc->runways()) {
|
|
naVec_append(*out, stringToNasal(c, rwy->ident()));
|
|
}
|
|
} else if (!strcmp(fieldName, "transitions")) {
|
|
const auto ty = proc->type();
|
|
string_list idents;
|
|
if (Approach::isApproach(ty)) {
|
|
const auto app = static_cast<Approach*>(proc);
|
|
idents = app->transitionIdents();
|
|
} else if ((ty == PROCEDURE_SID) || (ty == PROCEDURE_STAR)) {
|
|
ArrivalDeparture* ad = static_cast<ArrivalDeparture*>(proc);
|
|
idents = ad->transitionIdents();
|
|
} else {
|
|
*out = naNil();
|
|
return "";
|
|
}
|
|
|
|
*out = naNewVector(c);
|
|
for (std::string id : idents) {
|
|
naVec_append(*out, stringToNasal(c, id));
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
static const char* airwayGhostGetMember(naContext c, void* g, naRef field, naRef* out)
|
|
{
|
|
const char* fieldName = naStr_data(field);
|
|
Airway* awy = (Airway*)g;
|
|
|
|
if (!strcmp(fieldName, "parents")) {
|
|
*out = naNewVector(c);
|
|
naVec_append(*out, airwayPrototype);
|
|
} else if (!strcmp(fieldName, "id"))
|
|
*out = stringToNasal(c, awy->ident());
|
|
else if (!strcmp(fieldName, "level")) {
|
|
// James was dumb, returning a string here since we didn't have the
|
|
// constant values exposed.
|
|
const auto level = awy->level();
|
|
switch (level) {
|
|
case Airway::HighLevel: *out = stringToNasal(c, "high"); break;
|
|
case Airway::LowLevel: *out = stringToNasal(c, "low"); break;
|
|
case Airway::Both: *out = stringToNasal(c, "both"); break;
|
|
default: *out = naNil();
|
|
}
|
|
} else if (!strcmp(fieldName, "level_code")) {
|
|
// so expose the numerical values here
|
|
const auto level = awy->level();
|
|
*out = naNum(static_cast<int>(level));
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
static naRef f_createFlightplan(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
bool asRoute = false; // default to a regular Flight-Plan
|
|
if (argc == 1) {
|
|
if (naIsNum(args[0])) {
|
|
asRoute = args[0].num != 0.0;
|
|
}
|
|
} else if ((argc == 2) && naIsNum(args[1])) {
|
|
// args[0] is a string file name, args[1] is our flag
|
|
asRoute = args[1].num != 0.0;
|
|
}
|
|
|
|
flightgear::FlightPlanRef fp(asRoute ? FlightPlan::createRoute() : FlightPlan::create());
|
|
|
|
if ((argc > 0) && naIsString(args[0])) {
|
|
SGPath path(naStr_data(args[0]));
|
|
if (!path.exists()) {
|
|
std::string pdata = path.utf8Str();
|
|
naRuntimeError(c, "createFlightplan, no file at path %s", pdata.c_str());
|
|
}
|
|
|
|
if (!fp->load(path)) {
|
|
SG_LOG(SG_NASAL, SG_WARN, "failed to load flight-plan from " << path);
|
|
return naNil();
|
|
}
|
|
}
|
|
|
|
return ghostForFlightPlan(c, fp.get());
|
|
}
|
|
|
|
static naRef f_flightplan(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
if (argc == 0) {
|
|
FGRouteMgr* rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
|
|
return ghostForFlightPlan(c, rm->flightPlan());
|
|
}
|
|
|
|
if ((argc > 0) && naIsString(args[0])) {
|
|
return f_createFlightplan(c, me, argc, args);
|
|
}
|
|
|
|
naRuntimeError(c, "bad arguments to flightplan()");
|
|
return naNil();
|
|
}
|
|
|
|
class NasalFPDelegate : public FlightPlan::Delegate
|
|
{
|
|
public:
|
|
NasalFPDelegate(FlightPlan* fp, FGNasalSys* sys, naRef ins) : _nasal(sys),
|
|
_plan(fp),
|
|
_instance(ins)
|
|
{
|
|
assert(fp);
|
|
assert(sys);
|
|
_gcSaveKey = _nasal->gcSave(ins);
|
|
}
|
|
|
|
~NasalFPDelegate() override
|
|
{
|
|
_nasal->gcRelease(_gcSaveKey);
|
|
}
|
|
|
|
void departureChanged() override
|
|
{
|
|
callDelegateMethod("departureChanged");
|
|
}
|
|
|
|
void arrivalChanged() override
|
|
{
|
|
callDelegateMethod("arrivalChanged");
|
|
}
|
|
|
|
void waypointsChanged() override
|
|
{
|
|
callDelegateMethod("waypointsChanged");
|
|
}
|
|
|
|
void currentWaypointChanged() override
|
|
{
|
|
callDelegateMethod("currentWaypointChanged");
|
|
}
|
|
|
|
void cleared() override
|
|
{
|
|
callDelegateMethod("cleared");
|
|
}
|
|
|
|
void endOfFlightPlan() override
|
|
{
|
|
callDelegateMethod("endOfFlightPlan");
|
|
}
|
|
|
|
void activated() override
|
|
{
|
|
callDelegateMethod("activated");
|
|
}
|
|
|
|
void sequence() override
|
|
{
|
|
callDelegateMethod("sequence");
|
|
}
|
|
|
|
void loaded() override
|
|
{
|
|
callDelegateMethod("loaded");
|
|
}
|
|
|
|
private:
|
|
void callDelegateMethod(const char* method)
|
|
{
|
|
naRef f;
|
|
naContext ctx = naNewContext();
|
|
|
|
if (naMember_cget(ctx, _instance, method, &f) != 0) {
|
|
naRef arg[1];
|
|
arg[0] = ghostForFlightPlan(ctx, _plan);
|
|
_nasal->callMethod(f, _instance, 1, arg, naNil());
|
|
}
|
|
|
|
naFreeContext(ctx);
|
|
}
|
|
|
|
FGNasalSys* _nasal;
|
|
FlightPlan* _plan;
|
|
naRef _instance;
|
|
int _gcSaveKey;
|
|
};
|
|
|
|
|
|
class NasalFPDelegateFactory : public FlightPlan::DelegateFactory
|
|
{
|
|
public:
|
|
NasalFPDelegateFactory(naRef code, const std::string& id) : _id(id)
|
|
{
|
|
_nasal = globals->get_subsystem<FGNasalSys>();
|
|
_func = code;
|
|
_gcSaveKey = _nasal->gcSave(_func);
|
|
}
|
|
|
|
virtual ~NasalFPDelegateFactory()
|
|
{
|
|
_nasal->gcRelease(_gcSaveKey);
|
|
}
|
|
|
|
FlightPlan::Delegate* createFlightPlanDelegate(FlightPlan* fp) override
|
|
{
|
|
naRef args[1];
|
|
naContext ctx = naNewContext();
|
|
args[0] = ghostForFlightPlan(ctx, fp);
|
|
naRef instance = _nasal->call(_func, 1, args, naNil());
|
|
|
|
FlightPlan::Delegate* result = nullptr;
|
|
if (!naIsNil(instance)) {
|
|
// will GC-save instance
|
|
result = new NasalFPDelegate(fp, _nasal, instance);
|
|
}
|
|
|
|
naFreeContext(ctx);
|
|
return result;
|
|
}
|
|
|
|
void destroyFlightPlanDelegate(FlightPlan* fp, FlightPlan::Delegate* d) override
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
const std::string& id() const
|
|
{
|
|
return _id;
|
|
}
|
|
|
|
private:
|
|
const std::string _id;
|
|
FGNasalSys* _nasal;
|
|
naRef _func;
|
|
int _gcSaveKey;
|
|
};
|
|
|
|
static std::vector<FlightPlan::DelegateFactoryRef> static_nasalDelegateFactories;
|
|
|
|
void shutdownNasalFlightPlan()
|
|
{
|
|
for (auto f : static_nasalDelegateFactories) {
|
|
FlightPlan::unregisterDelegateFactory(f);
|
|
}
|
|
static_nasalDelegateFactories.clear();
|
|
}
|
|
|
|
static naRef f_registerFPDelegate(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
if ((argc < 1) || !naIsFunc(args[0])) {
|
|
naRuntimeError(c, "non-function argument to registerFlightPlanDelegate");
|
|
}
|
|
|
|
const std::string delegateId = (argc > 1) ? naStr_data(args[1]) : std::string{};
|
|
if (!delegateId.empty()) {
|
|
auto it = std::find_if(static_nasalDelegateFactories.begin(), static_nasalDelegateFactories.end(),
|
|
[delegateId](FlightPlan::DelegateFactoryRef factory) {
|
|
auto nfpd = static_cast<NasalFPDelegateFactory*>(factory.get());
|
|
return nfpd->id() == delegateId;
|
|
});
|
|
if (it != static_nasalDelegateFactories.end()) {
|
|
naRuntimeError(c, "duplicate delegate ID at registerFlightPlanDelegate: %s", delegateId.c_str());
|
|
}
|
|
}
|
|
|
|
auto factory = std::make_shared<NasalFPDelegateFactory>(args[0], delegateId);
|
|
FlightPlan::registerDelegateFactory(factory);
|
|
static_nasalDelegateFactories.push_back(factory);
|
|
return naNil();
|
|
}
|
|
|
|
static naRef f_unregisterFPDelegate(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
if ((argc < 1) || !naIsString(args[0])) {
|
|
naRuntimeError(c, "non-string argument to unregisterFlightPlanDelegate");
|
|
}
|
|
|
|
const std::string delegateId = naStr_data(args[0]);
|
|
auto it = std::find_if(static_nasalDelegateFactories.begin(), static_nasalDelegateFactories.end(),
|
|
[delegateId](FlightPlan::DelegateFactoryRef factory) {
|
|
auto nfpd = static_cast<NasalFPDelegateFactory*>(factory.get());
|
|
return nfpd->id() == delegateId;
|
|
});
|
|
|
|
if (it == static_nasalDelegateFactories.end()) {
|
|
SG_LOG(SG_NASAL, SG_DEV_WARN, "f_unregisterFPDelegate: no delegate with ID:" << delegateId);
|
|
return naNil();
|
|
}
|
|
|
|
FlightPlan::unregisterDelegateFactory(*it);
|
|
static_nasalDelegateFactories.erase(it);
|
|
|
|
return naNil();
|
|
}
|
|
|
|
static WayptRef wayptFromArg(naRef arg)
|
|
{
|
|
WayptRef r = wayptGhost(arg);
|
|
if (r.valid()) {
|
|
return r;
|
|
}
|
|
|
|
FGPositioned* pos = positionedGhost(arg);
|
|
if (!pos) {
|
|
// let's check if the arg is hash, coudl extra a geod and hence build
|
|
// a simple waypoint
|
|
|
|
return WayptRef();
|
|
}
|
|
|
|
// special-case for runways
|
|
if (pos->type() == FGPositioned::RUNWAY) {
|
|
return new RunwayWaypt((FGRunway*)pos, NULL);
|
|
}
|
|
|
|
return new NavaidWaypoint(pos, NULL);
|
|
}
|
|
|
|
static naRef convertWayptVecToNasal(naContext c, const WayptVec& wps)
|
|
{
|
|
naRef result = naNewVector(c);
|
|
for (WayptRef wpt : wps) {
|
|
naVec_append(result, ghostForWaypt(c, wpt.get()));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static naRef f_airwaySearch(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
if (argc < 2) {
|
|
naRuntimeError(c, "airwaysSearch needs at least two arguments");
|
|
}
|
|
|
|
WayptRef start = wayptFromArg(args[0]),
|
|
end = wayptFromArg(args[1]);
|
|
|
|
if (!start || !end) {
|
|
SG_LOG(SG_NASAL, SG_WARN, "airwaysSearch: start or end points are invalid");
|
|
return naNil();
|
|
}
|
|
|
|
bool highLevel = true;
|
|
if ((argc > 2) && naIsString(args[2])) {
|
|
if (!strcmp(naStr_data(args[2]), "lowlevel")) {
|
|
highLevel = false;
|
|
}
|
|
}
|
|
|
|
WayptVec route;
|
|
if (highLevel) {
|
|
Airway::highLevel()->route(start, end, route);
|
|
} else {
|
|
Airway::lowLevel()->route(start, end, route);
|
|
}
|
|
|
|
return convertWayptVecToNasal(c, route);
|
|
}
|
|
|
|
static naRef f_findAirway(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
if ((argc < 1) || !naIsString(args[0])) {
|
|
naRuntimeError(c, "findAirway needs at least one string arguments");
|
|
}
|
|
|
|
std::string ident = naStr_data(args[0]);
|
|
FGPositionedRef pos;
|
|
Airway::Level level = Airway::Both;
|
|
if (argc >= 2) {
|
|
int posArgIndex = 1;
|
|
|
|
// try next arg as a level specifier first: this means you can't use
|
|
// a string navaid ident which is 'high', 'low' or 'both'.
|
|
auto maybeLevel = airwayLevelFromNasal(args[1]);
|
|
if (maybeLevel.has_value()) {
|
|
level = maybeLevel.value_or(Airway::Both);
|
|
++posArgIndex; // worked, so increment index
|
|
}
|
|
|
|
if (argc > posArgIndex) {
|
|
pos = positionedFromArg(args[posArgIndex]);
|
|
}
|
|
}
|
|
|
|
AirwayRef awy;
|
|
if (pos) {
|
|
SG_LOG(SG_NASAL, SG_INFO, "Pevious navaid for airway():" << pos->ident());
|
|
awy = Airway::findByIdentAndNavaid(ident, pos);
|
|
} else {
|
|
awy = Airway::findByIdent(ident, level);
|
|
}
|
|
|
|
if (!awy)
|
|
return naNil();
|
|
|
|
return ghostForAirway(c, awy.get());
|
|
}
|
|
|
|
|
|
static naRef f_createWP(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
SGGeod pos;
|
|
int argOffset = geodFromArgs(args, 0, argc, pos);
|
|
|
|
if (((argc - argOffset) < 1) || !naIsString(args[argOffset])) {
|
|
naRuntimeError(c, "createWP: no identifier supplied");
|
|
}
|
|
|
|
std::string ident = naStr_data(args[argOffset++]);
|
|
WayptRef wpt = new BasicWaypt(pos, ident, NULL);
|
|
|
|
// set waypt flags - approach, departure, pseudo, etc
|
|
if (argc > argOffset) {
|
|
WayptFlag f = wayptFlagFromString(naStr_data(args[argOffset++]));
|
|
if (f == 0) {
|
|
naRuntimeError(c, "createWP: bad waypoint role");
|
|
}
|
|
|
|
wpt->setFlag(f);
|
|
}
|
|
|
|
return ghostForWaypt(c, wpt);
|
|
}
|
|
|
|
static naRef f_createWPFrom(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
if (argc < 1) {
|
|
naRuntimeError(c, "createWPFrom: need at least one argument");
|
|
}
|
|
|
|
FGPositioned* positioned = positionedGhost(args[0]);
|
|
if (!positioned) {
|
|
naRuntimeError(c, "createWPFrom: couldn't convert arg[0] to FGPositioned");
|
|
}
|
|
|
|
WayptRef wpt;
|
|
if (positioned->type() == FGPositioned::RUNWAY) {
|
|
wpt = new RunwayWaypt((FGRunway*)positioned, NULL);
|
|
} else {
|
|
wpt = new NavaidWaypoint(positioned, NULL);
|
|
}
|
|
|
|
// set waypt flags - approach, departure, pseudo, etc
|
|
if (argc > 1) {
|
|
WayptFlag f = wayptFlagFromString(naStr_data(args[1]));
|
|
if (f == 0) {
|
|
naRuntimeError(c, "createWPFrom: bad waypoint role");
|
|
}
|
|
wpt->setFlag(f);
|
|
}
|
|
|
|
return ghostForWaypt(c, wpt);
|
|
}
|
|
|
|
static naRef f_createViaTo(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
if ((argc < 2) || (argc > 3)) {
|
|
naRuntimeError(c, "createViaTo: needs two or three arguments");
|
|
}
|
|
|
|
AirwayRef airway = airwayGhost(args[0]);
|
|
naRef toArg = args[1];
|
|
if (!airway && naIsString(args[0])) {
|
|
std::string airwayName = naStr_data(args[0]);
|
|
auto level = Airway::Both;
|
|
if (argc == 3) {
|
|
// this means second arg is high / low select
|
|
auto l = airwayLevelFromNasal(args[1]);
|
|
toArg = args[2];
|
|
if (!l) {
|
|
naRuntimeError(c, "createViaTo: level argument is not accepted");
|
|
}
|
|
|
|
level = l.value_or(Airway::Both);
|
|
}
|
|
|
|
|
|
airway = Airway::findByIdent(airwayName, level);
|
|
if (!airway) {
|
|
naRuntimeError(c, "createViaTo: couldn't find airway with provided name: %s",
|
|
naStr_data(args[0]));
|
|
}
|
|
}
|
|
|
|
if (!airway) {
|
|
naRuntimeError(c, "createViaTo: invalid airway");
|
|
}
|
|
|
|
FGPositionedRef nav;
|
|
if (naIsString(toArg)) {
|
|
WayptRef enroute = airway->findEnroute(naStr_data(toArg));
|
|
if (!enroute) {
|
|
naRuntimeError(c, "unknown waypoint on airway %s: %s",
|
|
naStr_data(args[0]), naStr_data(toArg));
|
|
}
|
|
|
|
nav = enroute->source();
|
|
} else {
|
|
nav = positionedGhost(toArg);
|
|
if (!nav) {
|
|
naRuntimeError(c, "createViaTo: arg[1] is not a navaid");
|
|
}
|
|
}
|
|
|
|
if (!airway->containsNavaid(nav)) {
|
|
naRuntimeError(c, "createViaTo: navaid not on airway");
|
|
}
|
|
|
|
Via* via = new Via(nullptr, airway, nav);
|
|
return ghostForWaypt(c, via);
|
|
}
|
|
|
|
static naRef f_createViaFromTo(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
if ((argc < 3) || (argc > 4)) {
|
|
naRuntimeError(c, "createViaFromTo: needs three or four arguments");
|
|
}
|
|
|
|
auto from = positionedFromArg(args[0]);
|
|
if (!from) {
|
|
naRuntimeError(c, "createViaFromTo: from wp not found");
|
|
}
|
|
|
|
AirwayRef airway = airwayGhost(args[1]);
|
|
naRef toArg = args[2];
|
|
if (!airway && naIsString(args[1])) {
|
|
std::string airwayName = naStr_data(args[1]);
|
|
auto level = Airway::Both;
|
|
if (argc == 4) {
|
|
// this means third arg is high / low select
|
|
auto l = airwayLevelFromNasal(args[2]);
|
|
toArg = args[3];
|
|
if (!l) {
|
|
naRuntimeError(c, "createViaFromTo: level argument is not accepted");
|
|
}
|
|
|
|
level = l.value_or(Airway::Both);
|
|
}
|
|
|
|
|
|
airway = Airway::findByIdent(airwayName, level);
|
|
if (!airway) {
|
|
naRuntimeError(c, "createViaFromTo: couldn't find airway with provided name: %s",
|
|
naStr_data(args[1]));
|
|
}
|
|
}
|
|
|
|
if (!airway) {
|
|
naRuntimeError(c, "createViaFromTo: invalid airway");
|
|
}
|
|
|
|
FGPositionedRef nav;
|
|
if (naIsString(toArg)) {
|
|
WayptRef enroute = airway->findEnroute(naStr_data(toArg));
|
|
if (!enroute) {
|
|
naRuntimeError(c, "unknown waypoint on airway %s: %s",
|
|
naStr_data(args[1]), naStr_data(toArg));
|
|
}
|
|
|
|
nav = enroute->source();
|
|
} else {
|
|
nav = positionedFromArg(toArg);
|
|
if (!nav) {
|
|
naRuntimeError(c, "createViaFromTo: final arg is not a navaid");
|
|
}
|
|
}
|
|
|
|
if (!airway->containsNavaid(nav)) {
|
|
naRuntimeError(c, "createViaFromTo: navaid not on airway");
|
|
}
|
|
|
|
Via* via = new Via(nullptr, airway, nav);
|
|
return ghostForWaypt(c, via);
|
|
}
|
|
|
|
static naRef f_createDiscontinuity(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
return ghostForWaypt(c, new Discontinuity(NULL));
|
|
}
|
|
|
|
static naRef f_flightplan_getWP(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.getWP called on non-flightplan object");
|
|
}
|
|
|
|
int index;
|
|
if (argc == 0) {
|
|
index = fp->currentIndex();
|
|
} else {
|
|
index = (int)naNumValue(args[0]).num;
|
|
}
|
|
|
|
if ((index < 0) || (index >= fp->numLegs())) {
|
|
return naNil();
|
|
}
|
|
|
|
return ghostForLeg(c, fp->legAtIndex(index));
|
|
}
|
|
|
|
static naRef f_flightplan_currentWP(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.currentWP called on non-flightplan object");
|
|
}
|
|
|
|
int index = fp->currentIndex();
|
|
if (argc > 0) {
|
|
index += static_cast<int>(naNumValue(args[0]).num);
|
|
}
|
|
|
|
if ((index < 0) || (index >= fp->numLegs())) {
|
|
return naNil();
|
|
}
|
|
|
|
return ghostForLeg(c, fp->legAtIndex(index));
|
|
}
|
|
|
|
static naRef f_flightplan_nextWP(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.nextWP called on non-flightplan object");
|
|
}
|
|
return ghostForLeg(c, fp->nextLeg());
|
|
}
|
|
|
|
static naRef f_flightplan_numWaypoints(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.numWaypoints called on non-flightplan object");
|
|
}
|
|
return naNum(fp->numLegs());
|
|
}
|
|
|
|
static naRef f_flightplan_numRemainingWaypoints(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.f_flightplan_numRemainingWaypoints called on non-flightplan object");
|
|
}
|
|
|
|
// for an inactive flightplan, just reutnr the total number of WPs
|
|
if (fp->currentIndex() < 0) {
|
|
return naNum(fp->numLegs());
|
|
}
|
|
|
|
return naNum(fp->numLegs() - fp->currentIndex());
|
|
}
|
|
|
|
static naRef f_flightplan_appendWP(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.appendWP called on non-flightplan object");
|
|
}
|
|
|
|
WayptRef wp = wayptGhost(args[0]);
|
|
int index = fp->numLegs();
|
|
fp->insertWayptAtIndex(wp.get(), index);
|
|
return naNum(index);
|
|
}
|
|
|
|
static naRef f_flightplan_insertWP(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.insertWP called on non-flightplan object");
|
|
}
|
|
|
|
WayptRef wp = wayptGhost(args[0]);
|
|
int index = -1; // append
|
|
if ((argc > 1) && naIsNum(args[1])) {
|
|
index = (int)args[1].num;
|
|
}
|
|
|
|
auto leg = fp->insertWayptAtIndex(wp.get(), index);
|
|
return ghostForLeg(c, leg);
|
|
}
|
|
|
|
static naRef f_flightplan_insertWPAfter(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.insertWPAfter called on non-flightplan object");
|
|
}
|
|
|
|
WayptRef wp = wayptGhost(args[0]);
|
|
int index = -1; // append
|
|
if ((argc > 1) && naIsNum(args[1])) {
|
|
index = (int)args[1].num;
|
|
}
|
|
|
|
auto leg = fp->insertWayptAtIndex(wp.get(), index + 1);
|
|
return ghostForLeg(c, leg);
|
|
}
|
|
|
|
static naRef f_flightplan_insertWaypoints(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.insertWaypoints called on non-flightplan object");
|
|
}
|
|
|
|
// don't warn when passing a nil to this, which can happen in certain
|
|
// procedure construction situations
|
|
if (naIsNil(args[0])) {
|
|
return naNil();
|
|
}
|
|
|
|
WayptVec wps;
|
|
if (!naIsVector(args[0])) {
|
|
naRuntimeError(c, "flightplan.insertWaypoints expects vector as first arg");
|
|
}
|
|
|
|
int count = naVec_size(args[0]);
|
|
for (int i = 0; i < count; ++i) {
|
|
Waypt* wp = wayptGhost(naVec_get(args[0], i));
|
|
if (wp) {
|
|
wps.push_back(wp);
|
|
}
|
|
}
|
|
|
|
int index = -1; // append
|
|
if ((argc > 1) && naIsNum(args[1])) {
|
|
index = (int)args[1].num;
|
|
}
|
|
|
|
fp->insertWayptsAtIndex(wps, index);
|
|
return naNil();
|
|
}
|
|
|
|
static naRef f_flightplan_deleteWP(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.deleteWP called on non-flightplan object");
|
|
}
|
|
|
|
if ((argc < 1) || !naIsNum(args[0])) {
|
|
naRuntimeError(c, "bad argument to flightplan.deleteWP");
|
|
}
|
|
|
|
int index = (int)args[0].num;
|
|
fp->deleteIndex(index);
|
|
return naNil();
|
|
}
|
|
|
|
static naRef f_flightplan_clearLegs(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.clearLegs called on non-flightplan object");
|
|
}
|
|
|
|
fp->clearLegs();
|
|
return naNil();
|
|
}
|
|
|
|
static naRef f_flightplan_clearAll(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.clearAll called on non-flightplan object");
|
|
}
|
|
|
|
fp->clearAll();
|
|
return naNil();
|
|
}
|
|
|
|
|
|
static naRef f_flightplan_clearWPType(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.clearWPType called on non-flightplan object");
|
|
}
|
|
|
|
if (argc < 1) {
|
|
naRuntimeError(c, "insufficent args to flightplan.clearWPType");
|
|
}
|
|
|
|
WayptFlag flag = wayptFlagFromString(naStr_data(args[0]));
|
|
if (flag == 0) {
|
|
naRuntimeError(c, "clearWPType: bad waypoint role");
|
|
}
|
|
|
|
fp->clearWayptsWithFlag(flag);
|
|
return naNil();
|
|
}
|
|
|
|
static naRef f_flightplan_clone(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.clone called on non-flightplan object");
|
|
}
|
|
|
|
const bool convertRouteToFP = (argc > 0) && naIsNum(args[0]) && (args[0].num != 0.0);
|
|
return ghostForFlightPlan(c, fp->clone(fp->ident(), convertRouteToFP));
|
|
}
|
|
|
|
static naRef f_flightplan_pathGeod(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.pathGeod called on non-flightplan object");
|
|
}
|
|
|
|
if ((argc < 1) || !naIsNum(args[0])) {
|
|
naRuntimeError(c, "bad argument to flightplan.pathGeod");
|
|
}
|
|
|
|
if ((argc > 1) && !naIsNum(args[1])) {
|
|
naRuntimeError(c, "bad argument to flightplan.pathGeod");
|
|
}
|
|
|
|
int index = (int)args[0].num;
|
|
double offset = (argc > 1) ? args[1].num : 0.0;
|
|
naRef result = naNewHash(c);
|
|
SGGeod g = fp->pointAlongRoute(index, offset);
|
|
hashset(c, result, "lat", naNum(g.getLatitudeDeg()));
|
|
hashset(c, result, "lon", naNum(g.getLongitudeDeg()));
|
|
return result;
|
|
}
|
|
|
|
static naRef f_flightplan_finish(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.finish called on non-flightplan object");
|
|
}
|
|
|
|
// forbid on isRoute FPs?
|
|
|
|
fp->finish();
|
|
return naNil();
|
|
}
|
|
|
|
static naRef f_flightplan_activate(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "activate called on non-flightplan object");
|
|
}
|
|
|
|
if (fp->isRoute()) {
|
|
naRuntimeError(c, "activate called on isRoute flightplan");
|
|
}
|
|
|
|
fp->activate();
|
|
return naNil();
|
|
}
|
|
|
|
|
|
static naRef f_flightplan_indexOfWp(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "flightplan.indexOfWP called on non-flightplan object");
|
|
}
|
|
|
|
FGPositioned* positioned = positionedGhost(args[0]);
|
|
if (positioned) {
|
|
return naNum(fp->findWayptIndex(positioned));
|
|
}
|
|
|
|
FlightPlan::Leg* leg = fpLegGhost(args[0]);
|
|
if (leg) {
|
|
if (leg->owner() == fp) {
|
|
return naNum(leg->index());
|
|
}
|
|
|
|
naRuntimeError(c, "flightplan.indexOfWP called on leg from different flightplan");
|
|
}
|
|
|
|
SGGeod pos;
|
|
int argOffset = geodFromArgs(args, 0, argc, pos);
|
|
if (argOffset > 0) {
|
|
return naNum(fp->findWayptIndex(pos));
|
|
}
|
|
|
|
return naNum(-1);
|
|
}
|
|
|
|
static naRef f_flightplan_save(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "save called on non-flightplan object");
|
|
}
|
|
|
|
if ((argc < 1) || !naIsString(args[0])) {
|
|
naRuntimeError(c, "flightplan.save, no file path argument");
|
|
}
|
|
|
|
const SGPath raw_path(naStr_data(args[0]));
|
|
const SGPath validated_path = SGPath(raw_path).validate(true);
|
|
if (validated_path.isNull()) {
|
|
naRuntimeError(c, "flightplan.save, writing to path is not permitted");
|
|
}
|
|
|
|
bool ok = fp->save(validated_path);
|
|
return naNum(ok);
|
|
}
|
|
|
|
static naRef f_flightplan_parseICAORoute(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "parseICAORoute called on non-flightplan object");
|
|
}
|
|
|
|
if ((argc < 1) || !naIsString(args[0])) {
|
|
naRuntimeError(c, "flightplan.parseICAORoute, no route argument");
|
|
}
|
|
|
|
bool ok = fp->parseICAORouteString(naStr_data(args[0]));
|
|
return naNum(ok);
|
|
}
|
|
|
|
static naRef f_flightplan_toICAORoute(naContext c, naRef me, int, naRef*)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "toICAORoute called on non-flightplan object");
|
|
}
|
|
|
|
return stringToNasal(c, fp->asICAORouteString());
|
|
}
|
|
|
|
static naRef f_flightplan_computeDuration(naContext c, naRef me, int, naRef*)
|
|
{
|
|
FlightPlan* fp = flightplanGhost(me);
|
|
if (!fp) {
|
|
naRuntimeError(c, "computeDuration called on non-flightplan object");
|
|
}
|
|
|
|
fp->computeDurationMinutes();
|
|
return naNum(fp->estimatedDurationMinutes());
|
|
}
|
|
|
|
static naRef f_leg_setSpeed(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan::Leg* leg = fpLegGhost(me);
|
|
if (!leg) {
|
|
naRuntimeError(c, "leg.setSpeed called on non-flightplan-leg object");
|
|
}
|
|
|
|
double speed = 0.0;
|
|
RouteRestriction rr = RESTRICT_AT;
|
|
if (argc > 0) {
|
|
if (naIsNil(args[0])) {
|
|
// clear the restriction to NONE
|
|
rr = RESTRICT_NONE;
|
|
} else if (convertToNum(args[0], speed)) {
|
|
if ((argc > 1) && naIsString(args[1])) {
|
|
rr = routeRestrictionFromArg(args[1]);
|
|
} else {
|
|
naRuntimeError(c, "bad arguments to setSpeed");
|
|
}
|
|
}
|
|
|
|
leg->setSpeed(rr, speed);
|
|
} else {
|
|
naRuntimeError(c, "bad arguments to setSpeed");
|
|
}
|
|
|
|
return naNil();
|
|
}
|
|
|
|
static naRef f_leg_setAltitude(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan::Leg* leg = fpLegGhost(me);
|
|
if (!leg) {
|
|
naRuntimeError(c, "leg.setAltitude called on non-flightplan-leg object");
|
|
}
|
|
|
|
double altitude = 0.0;
|
|
RouteRestriction rr = RESTRICT_AT;
|
|
if (argc > 0) {
|
|
if (naIsNil(args[0])) {
|
|
// clear the restriction to NONE
|
|
rr = RESTRICT_NONE;
|
|
} else if (convertToNum(args[0], altitude)) {
|
|
if (argc > 1) {
|
|
rr = routeRestrictionFromArg(args[1]);
|
|
} else {
|
|
naRuntimeError(c, "bad arguments to leg.setAltitude");
|
|
}
|
|
}
|
|
|
|
leg->setAltitude(rr, altitude);
|
|
} else {
|
|
naRuntimeError(c, "bad arguments to setleg.setAltitude");
|
|
}
|
|
|
|
return naNil();
|
|
}
|
|
|
|
static naRef f_leg_courseAndDistanceFrom(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan::Leg* leg = fpLegGhost(me);
|
|
if (!leg || !leg->owner()) {
|
|
naRuntimeError(c, "leg.courseAndDistanceFrom called on non-flightplan-leg object");
|
|
}
|
|
|
|
SGGeod pos;
|
|
geodFromArgs(args, 0, argc, pos);
|
|
|
|
RoutePath path(leg->owner());
|
|
SGGeod wpPos = path.positionForIndex(leg->index());
|
|
double courseDeg, az2, distanceM;
|
|
SGGeodesy::inverse(pos, wpPos, courseDeg, az2, distanceM);
|
|
|
|
naRef result = naNewVector(c);
|
|
naVec_append(result, naNum(courseDeg));
|
|
naVec_append(result, naNum(distanceM * SG_METER_TO_NM));
|
|
return result;
|
|
}
|
|
|
|
static naRef f_leg_path(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
FlightPlan::Leg* leg = fpLegGhost(me);
|
|
if (!leg || !leg->owner()) {
|
|
naRuntimeError(c, "leg.setAltitude called on non-flightplan-leg object");
|
|
}
|
|
|
|
RoutePath path(leg->owner());
|
|
SGGeodVec gv(path.pathForIndex(leg->index()));
|
|
|
|
naRef result = naNewVector(c);
|
|
for (SGGeod p : gv) {
|
|
// construct a geo.Coord!
|
|
naRef coord = naNewHash(c);
|
|
hashset(c, coord, "lat", naNum(p.getLatitudeDeg()));
|
|
hashset(c, coord, "lon", naNum(p.getLongitudeDeg()));
|
|
naVec_append(result, coord);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static naRef f_procedure_transition(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
Procedure* proc = procedureGhost(me);
|
|
if (!proc) {
|
|
naRuntimeError(c, "procedure.transition called on non-procedure object");
|
|
}
|
|
|
|
const string ident{naStr_data(args[0])};
|
|
const auto ty = proc->type();
|
|
if (Approach::isApproach(ty)) {
|
|
const auto app = static_cast<Approach*>(proc);
|
|
const auto trans = app->findTransitionByName(ident);
|
|
return ghostForProcedure(c, trans);
|
|
}
|
|
|
|
if ((ty != PROCEDURE_SID) && (ty != PROCEDURE_STAR)) {
|
|
naRuntimeError(c, "procedure.transition called on non-SID or -STAR");
|
|
}
|
|
|
|
ArrivalDeparture* ad = (ArrivalDeparture*)proc;
|
|
Transition* trans = ad->findTransitionByName(ident);
|
|
|
|
return ghostForProcedure(c, trans);
|
|
}
|
|
|
|
static naRef approachRoute(Approach* app, naContext c, int argc, naRef* args)
|
|
{
|
|
int argOffset = 0;
|
|
FGRunway* rwy = runwayGhost(args[0]);
|
|
if (rwy) ++argOffset;
|
|
|
|
WayptVec r;
|
|
Transition* trans = nullptr;
|
|
WayptRef iaf;
|
|
|
|
if (argOffset < argc) {
|
|
// one of these might work, never both
|
|
trans = (Transition*)procedureGhost(args[argOffset]);
|
|
iaf = wayptFromArg(args[argOffset]);
|
|
|
|
if (trans) {
|
|
bool ok = app->routeWithTransition(rwy, trans, r);
|
|
if (!ok)
|
|
return naNil();
|
|
|
|
return convertWayptVecToNasal(c, r);
|
|
}
|
|
}
|
|
|
|
bool ok = app->route(rwy, iaf, r);
|
|
if (!ok)
|
|
return naNil();
|
|
|
|
return convertWayptVecToNasal(c, r);
|
|
}
|
|
|
|
static naRef f_procedure_route(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
Procedure* proc = procedureGhost(me);
|
|
if (!proc) {
|
|
naRuntimeError(c, "procedure.route called on non-procedure object");
|
|
}
|
|
|
|
// wrapping up tow different routines here - approach routing
|
|
// to the associated runway, and SID/STAR routing via an enroute transition
|
|
// and possibly a runway transition or not.
|
|
if (Approach::isApproach(proc->type())) {
|
|
return approachRoute(static_cast<Approach*>(proc), c, argc, args);
|
|
} else if ((proc->type() != PROCEDURE_SID) && (proc->type() != PROCEDURE_STAR)) {
|
|
naRuntimeError(c, "procedure.route called on unsuitable procedure type");
|
|
}
|
|
|
|
int argOffset = 0;
|
|
FGRunway* rwy = runwayGhost(args[0]);
|
|
if (rwy) ++argOffset;
|
|
|
|
ArrivalDeparture* ad = (ArrivalDeparture*)proc;
|
|
Transition* trans = NULL;
|
|
if (argOffset < argc) {
|
|
trans = (Transition*)procedureGhost(args[argOffset]);
|
|
}
|
|
|
|
// note either runway or trans may be NULL - that's ok
|
|
WayptVec r;
|
|
if (!ad->route(rwy, trans, r)) {
|
|
SG_LOG(SG_NASAL, SG_WARN, "procedure.route failed for ArrivalDeparture somehow");
|
|
return naNil();
|
|
}
|
|
|
|
return convertWayptVecToNasal(c, r);
|
|
}
|
|
|
|
static naRef f_airway_contains(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
Airway* awy = airwayGhost(me);
|
|
if (!awy) {
|
|
naRuntimeError(c, "airway.contains called on non-airway object");
|
|
}
|
|
|
|
if (argc < 1) {
|
|
naRuntimeError(c, "missing arg to airway.contains");
|
|
}
|
|
|
|
auto pos = positionedFromArg(args[0]);
|
|
if (!pos) {
|
|
return naNum(0);
|
|
}
|
|
|
|
return naNum(awy->containsNavaid(pos));
|
|
}
|
|
|
|
static naRef f_airway_viaWaypoints(naContext c, naRef me, int argc, naRef* args)
|
|
{
|
|
Airway* awy = airwayGhost(me);
|
|
if (!awy) {
|
|
naRuntimeError(c, "airway.viaWaypoints called on non-airway object");
|
|
}
|
|
|
|
if ((argc < 1) || (argc > 2)) {
|
|
naRuntimeError(c, "Airway.viaWaypoints: needs one ro two arguments");
|
|
}
|
|
|
|
auto from = positionedFromArg(args[0]);
|
|
auto fromWp = awy->findEnroute(from);
|
|
if (!fromWp) {
|
|
naRuntimeError(c, "Airway.viaWaypoints: from wp not found");
|
|
}
|
|
|
|
naRef toArg = args[1];
|
|
FGPositionedRef nav;
|
|
if (naIsString(toArg)) {
|
|
WayptRef enroute = awy->findEnroute(naStr_data(toArg));
|
|
if (!enroute) {
|
|
naRuntimeError(c, "unknown waypoint on airway %s: %s",
|
|
awy->ident().c_str(), naStr_data(toArg));
|
|
}
|
|
|
|
nav = enroute->source();
|
|
} else {
|
|
nav = positionedFromArg(toArg);
|
|
if (!nav) {
|
|
naRuntimeError(c, "Airway.viaWaypoints: final arg is not a navaid");
|
|
}
|
|
}
|
|
|
|
auto toWaypt = awy->findEnroute(nav);
|
|
if (!toWaypt) {
|
|
naRuntimeError(c, "Airway.viaWaypoints: navaid not on airway");
|
|
}
|
|
|
|
auto wps = awy->via(fromWp, toWaypt);
|
|
return convertWayptVecToNasal(c, wps);
|
|
}
|
|
|
|
// Table of extension functions. Terminate with zeros.
|
|
static struct {
|
|
const char* name;
|
|
naCFunction func;
|
|
} funcs[] = {
|
|
{"flightplan", f_flightplan},
|
|
{"createFlightplan", f_createFlightplan},
|
|
{"registerFlightPlanDelegate", f_registerFPDelegate},
|
|
{"unregisterFlightPlanDelegate", f_unregisterFPDelegate},
|
|
{"createWP", f_createWP},
|
|
{"createWPFrom", f_createWPFrom},
|
|
{"createViaTo", f_createViaTo},
|
|
{"createViaFromTo", f_createViaFromTo},
|
|
{"createDiscontinuity", f_createDiscontinuity},
|
|
{"airwaysRoute", f_airwaySearch},
|
|
{"airway", f_findAirway},
|
|
{nullptr, nullptr}};
|
|
|
|
|
|
naRef initNasalFlightPlan(naRef globals, naContext c)
|
|
{
|
|
flightplanPrototype = naNewHash(c);
|
|
naSave(c, flightplanPrototype);
|
|
|
|
hashset(c, flightplanPrototype, "getWP", naNewFunc(c, naNewCCode(c, f_flightplan_getWP)));
|
|
hashset(c, flightplanPrototype, "currentWP", naNewFunc(c, naNewCCode(c, f_flightplan_currentWP)));
|
|
hashset(c, flightplanPrototype, "nextWP", naNewFunc(c, naNewCCode(c, f_flightplan_nextWP)));
|
|
hashset(c, flightplanPrototype, "getPlanSize", naNewFunc(c, naNewCCode(c, f_flightplan_numWaypoints)));
|
|
// alias to this name also
|
|
hashset(c, flightplanPrototype, "numWaypoints", naNewFunc(c, naNewCCode(c, f_flightplan_numWaypoints)));
|
|
hashset(c, flightplanPrototype, "numRemainingWaypoints", naNewFunc(c, naNewCCode(c, f_flightplan_numRemainingWaypoints)));
|
|
|
|
hashset(c, flightplanPrototype, "appendWP", naNewFunc(c, naNewCCode(c, f_flightplan_appendWP)));
|
|
hashset(c, flightplanPrototype, "insertWP", naNewFunc(c, naNewCCode(c, f_flightplan_insertWP)));
|
|
hashset(c, flightplanPrototype, "deleteWP", naNewFunc(c, naNewCCode(c, f_flightplan_deleteWP)));
|
|
hashset(c, flightplanPrototype, "insertWPAfter", naNewFunc(c, naNewCCode(c, f_flightplan_insertWPAfter)));
|
|
hashset(c, flightplanPrototype, "insertWaypoints", naNewFunc(c, naNewCCode(c, f_flightplan_insertWaypoints)));
|
|
|
|
auto f = naNewFunc(c, naNewCCode(c, f_flightplan_clearLegs));
|
|
hashset(c, flightplanPrototype, "cleanPlan", f); // original name for compat
|
|
// alias to a better name
|
|
hashset(c, flightplanPrototype, "clearLegs", f);
|
|
|
|
|
|
hashset(c, flightplanPrototype, "clearAll", naNewFunc(c, naNewCCode(c, f_flightplan_clearAll)));
|
|
|
|
hashset(c, flightplanPrototype, "clearWPType", naNewFunc(c, naNewCCode(c, f_flightplan_clearWPType)));
|
|
hashset(c, flightplanPrototype, "clone", naNewFunc(c, naNewCCode(c, f_flightplan_clone)));
|
|
|
|
hashset(c, flightplanPrototype, "pathGeod", naNewFunc(c, naNewCCode(c, f_flightplan_pathGeod)));
|
|
// this is a clearer name than pathGeod
|
|
hashset(c, flightplanPrototype, "pointAlongRoute", naNewFunc(c, naNewCCode(c, f_flightplan_pathGeod)));
|
|
|
|
hashset(c, flightplanPrototype, "finish", naNewFunc(c, naNewCCode(c, f_flightplan_finish)));
|
|
hashset(c, flightplanPrototype, "activate", naNewFunc(c, naNewCCode(c, f_flightplan_activate)));
|
|
hashset(c, flightplanPrototype, "indexOfWP", naNewFunc(c, naNewCCode(c, f_flightplan_indexOfWp)));
|
|
hashset(c, flightplanPrototype, "computeDuration", naNewFunc(c, naNewCCode(c, f_flightplan_computeDuration)));
|
|
hashset(c, flightplanPrototype, "parseICAORoute", naNewFunc(c, naNewCCode(c, f_flightplan_parseICAORoute)));
|
|
hashset(c, flightplanPrototype, "toICAORoute", naNewFunc(c, naNewCCode(c, f_flightplan_toICAORoute)));
|
|
|
|
hashset(c, flightplanPrototype, "save", naNewFunc(c, naNewCCode(c, f_flightplan_save)));
|
|
|
|
procedurePrototype = naNewHash(c);
|
|
naSave(c, procedurePrototype);
|
|
hashset(c, procedurePrototype, "transition", naNewFunc(c, naNewCCode(c, f_procedure_transition)));
|
|
hashset(c, procedurePrototype, "route", naNewFunc(c, naNewCCode(c, f_procedure_route)));
|
|
|
|
fpLegPrototype = naNewHash(c);
|
|
naSave(c, fpLegPrototype);
|
|
hashset(c, fpLegPrototype, "setSpeed", naNewFunc(c, naNewCCode(c, f_leg_setSpeed)));
|
|
hashset(c, fpLegPrototype, "setAltitude", naNewFunc(c, naNewCCode(c, f_leg_setAltitude)));
|
|
hashset(c, fpLegPrototype, "path", naNewFunc(c, naNewCCode(c, f_leg_path)));
|
|
hashset(c, fpLegPrototype, "courseAndDistanceFrom", naNewFunc(c, naNewCCode(c, f_leg_courseAndDistanceFrom)));
|
|
|
|
airwayPrototype = naNewHash(c);
|
|
naSave(c, airwayPrototype);
|
|
hashset(c, airwayPrototype, "contains", naNewFunc(c, naNewCCode(c, f_airway_contains)));
|
|
hashset(c, airwayPrototype, "viaWaypoints", naNewFunc(c, naNewCCode(c, f_airway_viaWaypoints)));
|
|
hashset(c, airwayPrototype, "HIGH", naNum(Airway::HighLevel));
|
|
hashset(c, airwayPrototype, "LOW", naNum(Airway::LowLevel));
|
|
hashset(c, airwayPrototype, "BOTH", naNum(Airway::Both));
|
|
|
|
hashset(c, globals, "Airway", airwayPrototype);
|
|
|
|
for (int i = 0; funcs[i].name; i++) {
|
|
hashset(c, globals, funcs[i].name,
|
|
naNewFunc(c, naNewCCode(c, funcs[i].func)));
|
|
}
|
|
|
|
return naNil();
|
|
}
|