Files
xplane-cockpit/server/luainstall.js
T
karim 9aba24978b Auto-install Lua, smooth all panels, airspace overlay + launcher region picker
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>
2026-06-02 13:57:50 +02:00

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;
}