9aba24978b
FlyWithLua auto-install: bridge drops fms-sync/ui-sync/terrain-probe into X-Plane's FlyWithLua Scripts dir on startup and self-updates (content-compare). Graceful when no X-Plane / no FlyWithLua. /api/lua/install + status in health. Desktop app bundles the scripts and passes LUA_SRC_DIR to the sidecar. Smoothing: shared useEased/useEasedAngle hook (api/ease.js) with render-bail on settle. VFR steam gauges now interpolate to 60fps instead of stepping at the ~10Hz value stream. MFD ownship no longer vibrates — position/heading eased in a single rAF loop, follow-pan without animated-panTo pile-up (pauses on range zoom). Airspace overlay: server/airspace.js loads per-region GeoJSON, classifies (B/C/D/TMA/CTR/MOA/Restricted/Prohibited/Danger), bbox query, and downloads regions on demand — FAA (US, key-free) and OpenAIP (Europe, user key). New AIRSPACE softkey draws chart-coloured boundaries (B blue, C magenta, D dashed), non-interactive so map-clicks still drop waypoints. Launcher gains a "Lufträume" section to pick/download regions via the running bridge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
89 lines
4.0 KiB
JavaScript
89 lines
4.0 KiB
JavaScript
// Auto-installs the FlyWithLua companion scripts into X-Plane on bridge start.
|
|
//
|
|
// The web cockpit needs three .lua helpers running INSIDE X-Plane (they have the
|
|
// FMS / scenery SDK the Web API lacks — see plugins/*.lua). Rather than make the
|
|
// user copy files by hand, the bridge drops them into the sim's FlyWithLua
|
|
// Scripts folder on startup and keeps them up to date (content-compare, so a new
|
|
// build self-updates the installed copy; unchanged files are left alone).
|
|
//
|
|
// Everything degrades gracefully: no X-Plane found, no FlyWithLua installed, or
|
|
// the script sources missing → we log a hint and carry on. We never create the
|
|
// FlyWithLua folder ourselves (its absence means the user must install
|
|
// FlyWithLua NG+ first; making an empty folder would only hide that).
|
|
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
// The companion scripts to install, in load-independent order.
|
|
const SCRIPTS = ['fms-sync.lua', 'ui-sync.lua', 'terrain-probe.lua'];
|
|
|
|
// Where do the canonical .lua sources live? plugins/ sits next to server/ in the
|
|
// repo; in the packaged desktop app it's bundled as a Tauri resource. Probe a
|
|
// few locations so both `node server/bridge.js` and the compiled sidecar work.
|
|
function sourceDir() {
|
|
const candidates = [
|
|
process.env.LUA_SRC_DIR,
|
|
path.join(__dirname, '..', 'plugins'), // repo: server/ -> ../plugins
|
|
path.join(process.cwd(), 'plugins'), // run from repo root
|
|
path.join(path.dirname(process.execPath), 'plugins'),
|
|
path.join(path.dirname(process.execPath), '..', 'Resources', 'plugins'),
|
|
].filter(Boolean);
|
|
for (const dir of candidates) {
|
|
try {
|
|
if (fs.existsSync(path.join(dir, SCRIPTS[0]))) return dir;
|
|
} catch { /* ignore */ }
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Install / update the scripts under <root>. Returns a report object; never throws.
|
|
export function installLuaScripts(root, log = console.log) {
|
|
if (!root) {
|
|
return { ok: false, reason: 'no-xplane', installed: [], updated: [], unchanged: [] };
|
|
}
|
|
const fwl = path.join(root, 'Resources', 'plugins', 'FlyWithLua');
|
|
if (!fs.existsSync(fwl)) {
|
|
log('lua-install: FlyWithLua not found — install FlyWithLua NG+ into ' +
|
|
`${path.join(root, 'Resources', 'plugins')} to enable FMS/terrain sync`);
|
|
return { ok: false, reason: 'no-flywithlua', installed: [], updated: [], unchanged: [] };
|
|
}
|
|
const src = sourceDir();
|
|
if (!src) {
|
|
log('lua-install: companion script sources not found (set LUA_SRC_DIR)');
|
|
return { ok: false, reason: 'no-source', installed: [], updated: [], unchanged: [] };
|
|
}
|
|
|
|
const dest = path.join(fwl, 'Scripts');
|
|
const report = { ok: true, reason: 'ok', dir: dest, installed: [], updated: [], unchanged: [], failed: [] };
|
|
try { fs.mkdirSync(dest, { recursive: true }); } catch { /* ignore */ }
|
|
|
|
for (const name of SCRIPTS) {
|
|
const from = path.join(src, name);
|
|
const to = path.join(dest, name);
|
|
try {
|
|
if (!fs.existsSync(from)) { report.failed.push(name); continue; }
|
|
const want = fs.readFileSync(from, 'utf8');
|
|
const have = fs.existsSync(to) ? fs.readFileSync(to, 'utf8') : null;
|
|
if (have === null) { fs.writeFileSync(to, want); report.installed.push(name); }
|
|
else if (have !== want) { fs.writeFileSync(to, want); report.updated.push(name); }
|
|
else { report.unchanged.push(name); }
|
|
} catch (e) {
|
|
report.failed.push(name);
|
|
log(`lua-install: ${name} failed: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
const parts = [];
|
|
if (report.installed.length) parts.push(`installed ${report.installed.join(', ')}`);
|
|
if (report.updated.length) parts.push(`updated ${report.updated.join(', ')}`);
|
|
if (report.unchanged.length) parts.push(`${report.unchanged.length} up to date`);
|
|
log(`lua-install: ${parts.join('; ') || 'nothing to do'} → ${dest}`);
|
|
if (report.installed.length || report.updated.length) {
|
|
log('lua-install: reload in X-Plane — "FlyWithLua > Reload all Lua script files" (or restart)');
|
|
}
|
|
return report;
|
|
}
|