您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
GPWS and other alerts for GeoFS
// ==UserScript== // @name GeoFS GPWS Alerts // @namespace https://avramovic.info/ // @version 1.0.7 // @description GPWS and other alerts for GeoFS // @author Nemanja Avramovic // @match https://www.geo-fs.com/geofs.php* // @icon https://www.google.com/s2/favicons?sz=64&domain=geo-fs.com // @grant GM.getResourceUrl // @resource stall https://github.com/avramovic/geofs-alerts/raw/master/audio/airbus-stall-warning.mp3 // @resource bankangle https://github.com/avramovic/geofs-alerts/raw/master/audio/bank-angle-bank-angle.mp3 // @resource overspeed https://github.com/avramovic/geofs-alerts/raw/master/audio/md-80-overspeed.mp3 // @resource autopilot https://github.com/avramovic/geofs-alerts/raw/master/audio/airbus-autopilot-off.mp3 // @resource terrain https://github.com/avramovic/geofs-alerts/raw/master/audio/terrain-terrain-pull-up.mp3 // @resource lowgear https://github.com/avramovic/geofs-alerts/raw/master/audio/too-low-gear.mp3 // @resource lowflaps https://github.com/avramovic/geofs-alerts/raw/master/audio/too-low-flaps.mp3 // @resource sinkrate https://github.com/avramovic/geofs-alerts/raw/master/audio/sink-rate.mp3 // @resource minimums https://github.com/avramovic/geofs-alerts/raw/master/audio/minimums.mp3 // @resource approaching https://github.com/avramovic/geofs-alerts/raw/master/audio/approaching-minimums.mp3 // @resource retard https://github.com/avramovic/geofs-alerts/raw/master/audio/airbus-retard.mp3 // @resource h2500 https://github.com/avramovic/geofs-alerts/raw/master/audio/2500.mp3 // @resource h1000 https://github.com/avramovic/geofs-alerts/raw/master/audio/1000.mp3 // @resource h500 https://github.com/avramovic/geofs-alerts/raw/master/audio/500.mp3 // @resource h400 https://github.com/avramovic/geofs-alerts/raw/master/audio/400.mp3 // @resource h300 https://github.com/avramovic/geofs-alerts/raw/master/audio/300.mp3 // @resource h200 https://github.com/avramovic/geofs-alerts/raw/master/audio/200.mp3 // @resource h100 https://github.com/avramovic/geofs-alerts/raw/master/audio/100.mp3 // @resource h50 https://github.com/avramovic/geofs-alerts/raw/master/audio/50.mp3 // @resource h40 https://github.com/avramovic/geofs-alerts/raw/master/audio/40.mp3 // @resource h30 https://github.com/avramovic/geofs-alerts/raw/master/audio/30.mp3 // @resource h20 https://github.com/avramovic/geofs-alerts/raw/master/audio/20.mp3 // @resource h10 https://github.com/avramovic/geofs-alerts/raw/master/audio/10.mp3 // @resource h5 https://github.com/avramovic/geofs-alerts/raw/master/audio/5.mp3 // ==/UserScript== (function () { 'use strict'; // load the audio clips let stickShake; GM.getResourceUrl("stall").then((data) => { stickShake = new Audio('data:audio/mp3;'+data); stickShake.loop = true; }); let bankangle; GM.getResourceUrl("bankangle").then((data) => { bankangle = new Audio('data:audio/mp3;'+data); bankangle.loop = true; }); let overspeed; GM.getResourceUrl("overspeed").then((data) => { overspeed = new Audio('data:audio/mp3;'+data); overspeed.loop = true; }); let autopilot; GM.getResourceUrl("autopilot").then((data) => { autopilot = new Audio('data:audio/mp3;'+data); autopilot.loop = false; }); let terrain; GM.getResourceUrl("terrain").then((data) => { terrain = new Audio('data:audio/mp3;'+data); terrain.loop = true; }); let lowgear; GM.getResourceUrl("lowgear").then((data) => { lowgear = new Audio('data:audio/mp3;'+data); lowgear.loop = true; }); let lowflaps; GM.getResourceUrl("lowflaps").then((data) => { lowflaps = new Audio('data:audio/mp3;'+data); lowflaps.loop = true; }); let sinkrate; GM.getResourceUrl("sinkrate").then((data) => { sinkrate = new Audio('data:audio/mp3;'+data); sinkrate.loop = true; }); let approaching; GM.getResourceUrl("approaching").then((data) => { approaching = new Audio('data:audio/mp3;'+data); approaching.loop = false; }); let h2500; GM.getResourceUrl("h2500").then((data) => { h2500 = new Audio('data:audio/mp3;'+data); h2500.loop = false; }); let h1000; GM.getResourceUrl("h1000").then((data) => { h1000 = new Audio('data:audio/mp3;'+data); h1000.loop = false; }); let h500; GM.getResourceUrl("h500").then((data) => { h500 = new Audio('data:audio/mp3;'+data); h500.loop = false; }); let h400; GM.getResourceUrl("h400").then((data) => { h400 = new Audio('data:audio/mp3;'+data); h400.loop = false; }); let h300; GM.getResourceUrl("h300").then((data) => { h300 = new Audio('data:audio/mp3;'+data); h300.loop = false; }); let h200; GM.getResourceUrl("h200").then((data) => { h200 = new Audio('data:audio/mp3;'+data); h200.loop = false; }); let h100; GM.getResourceUrl("h100").then((data) => { h100 = new Audio('data:audio/mp3;'+data); h100.loop = false; }); let h50; GM.getResourceUrl("h50").then((data) => { h50 = new Audio('data:audio/mp3;'+data); h50.loop = false; }); let h40; GM.getResourceUrl("h40").then((data) => { h40 = new Audio('data:audio/mp3;'+data); h40.loop = false; }); let h30; GM.getResourceUrl("h30").then((data) => { h30 = new Audio('data:audio/mp3;'+data); h30.loop = false; }); let h20; GM.getResourceUrl("h20").then((data) => { h20 = new Audio('data:audio/mp3;'+data); h20.loop = false; }); let h10; GM.getResourceUrl("h10").then((data) => { h10 = new Audio('data:audio/mp3;'+data); h10.loop = false; }); let h5; GM.getResourceUrl("h5").then((data) => { h5 = new Audio('data:audio/mp3;'+data); h5.loop = false; }); let minimums; GM.getResourceUrl("minimums").then((data) => { minimums = new Audio('data:audio/mp3;'+data); minimums.loop = false; }); let retard; GM.getResourceUrl("retard").then((data) => { retard = new Audio('data:audio/mp3;'+data); retard.loop = false; }); let apWasOn = false; let apIsOn = false; let oldAltitude = 0; let altitude = 0; // wait until flight sim is fully loaded let itv = setInterval( function(){ if(unsafeWindow.ui && unsafeWindow.flight && unsafeWindow.geofs){ setInterval(function() { mainLoop(); }, 500); clearInterval(itv); } } ,500); function isGearUp() { return unsafeWindow.geofs.animation.values.gearPosition == 1; } function isGearDown() { return unsafeWindow.geofs.animation.values.gearPosition == 0; } function seaAltitude() { return unsafeWindow.geofs.animation.values.altitude; } function groundAltitude() { return seaAltitude() - unsafeWindow.geofs.animation.values.groundElevationFeet - 50; } function isDescending() { return unsafeWindow.geofs.animation.values.verticalSpeed < -50; } function isSinking() { return unsafeWindow.geofs.animation.values.verticalSpeed < -2500; } function isAscending() { return unsafeWindow.geofs.animation.values.verticalSpeed > 50; } function flapsRetracted() { return unsafeWindow.geofs.animation.values.flapsValue == 0; } function flapsExtended() { return unsafeWindow.geofs.animation.values.flapsValue > 0; } function getLatitude() { return unsafeWindow.geofs.aircraft.instance.lastLlaLocation[0]; } function getLongitude() { return unsafeWindow.geofs.aircraft.instance.lastLlaLocation[1]; } function isOnGround() { return unsafeWindow.geofs.animation.values.groundContact == 1 ? true : false; } function isStalling() { return unsafeWindow.geofs.aircraft.instance.stalling; } function isCrashed() { return unsafeWindow.geofs.aircraft.instance.crashed; } function isEngineOn() { return unsafeWindow.geofs.aircraft.instance.engine.on; } function haversine(lat1, lon1, lat2, lon2) { const R = 6371; // Radius of the Earth in kilometers const toRad = (deg) => deg * (Math.PI / 180); const dLat = toRad(lat2 - lat1); const dLon = toRad(lon2 - lon1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; // Distance in kilometers } function findNearestAirport() { let aircraftPosition = { lat: getLatitude(), lon: getLongitude(), }; let nearestAirport = null; let minDistance = Infinity; for (let i in unsafeWindow.geofs.mainAirportList) { let ap = unsafeWindow.geofs.mainAirportList[i]; let airportPosition = { lat: ap[0], lon: ap[1] }; let distance = haversine( aircraftPosition.lat, aircraftPosition.lon, airportPosition.lat, airportPosition.lon ); if (distance < minDistance) { minDistance = distance; nearestAirport = { airport: i, distanceInKm: distance }; } } return nearestAirport; } Audio.prototype.stop = function() { this.pause(); this.currentTime = 0; }; function mainLoop(){ if (unsafeWindow.geofs.isPaused()) { //paused, do not run the loop return; } // stall alert !isOnGround() && isStalling() ? stickShake.play() : stickShake.stop(); const fastPlanes = ["F-16 Fighting Falcon", "Concorde", "Sukhoi Su-35", "Boeing F/A-18F Super Hornet", "Wingsuit"]; // bank angle alert if (!fastPlanes.includes(unsafeWindow.geofs.aircraft.instance.aircraftRecord.name.trim())) { Math.abs(unsafeWindow.geofs.animation.values.aroll) > 40 ? bankangle.play() : bankangle.stop(); } // indicated airspeed overspeed alert if (!fastPlanes.includes(unsafeWindow.geofs.aircraft.instance.aircraftRecord.name.trim())) { let maxSpeed = unsafeWindow.geofs.animation.values.VNO > 0 ? unsafeWindow.geofs.animation.values.VNO+1 : 350; unsafeWindow.geofs.animation.values.kias > maxSpeed ? overspeed.play() : overspeed.stop(); } // autopilot disconnect alert apIsOn = unsafeWindow.geofs.autopilot.on; if (apWasOn && !apIsOn) { autopilot.play(); } else if (!apWasOn && apIsOn) { autopilot.stop(); } apWasOn = apIsOn; // terrain alert if (isGearUp() && groundAltitude() <= 1000 && !isOnGround() && isEngineOn()) { terrain.play(); } else { terrain.stop(); } let nearestAp = findNearestAirport(); if (isDescending() && groundAltitude() <= 1500 && typeof nearestAp == "object" && nearestAp.distanceInKm < 20) { if (isGearUp()) { lowgear.play(); } else { lowgear.stop(); if (flapsRetracted()) { lowflaps.play(); } else { lowflaps.stop(); } } } else { lowgear.stop(); lowflaps.stop(); } // sink rate isSinking() ? sinkrate.play() : sinkrate.stop(); // height callouts when fully configured for landing and near airport altitude = groundAltitude(); if (isDescending() && isGearDown() && flapsExtended() && typeof nearestAp == "object" && nearestAp.distanceInKm < 20) { if (oldAltitude > 2500 && altitude <= 2500) { h2500.play(); } else if (oldAltitude > 1000 && altitude <= 1000) { h1000.play(); } else if (oldAltitude > 500 && altitude <= 500) { h500.play(); } else if (oldAltitude > 400 && altitude <= 400) { h400.play(); } else if (oldAltitude > 350 && altitude <= 350) { approaching.play(); } else if (oldAltitude > 300 && altitude <= 300) { h300.play(); } else if (oldAltitude > 200 && altitude <= 200) { h200.play(); } else if (oldAltitude > 150 && altitude <= 150) { minimums.play(); } else if (oldAltitude > 100 && altitude <= 100) { h100.play(); } else if (oldAltitude > 50 && altitude <= 50) { h50.play(); } else if (oldAltitude > 40 && altitude <= 40) { h40.play(); } else if (oldAltitude > 30 && altitude <= 30) { h30.play(); } else if (oldAltitude > 20 && altitude <= 20) { h20.play(); if (isEngineOn()) { retard.play(); } } else if (oldAltitude > 10 && altitude <= 10) { h10.play(); } else if (oldAltitude > 5 && altitude <= 5) { h5.play(); } } oldAltitude = altitude; } })();