GeoFS GPWS Alerts

GPWS and other alerts for GeoFS

目前为 2025-01-14 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name GeoFS GPWS Alerts
  3. // @namespace https://avramovic.info/
  4. // @version 1.0.6
  5. // @description GPWS and other alerts for GeoFS
  6. // @author Nemanja Avramovic
  7. // @match https://www.geo-fs.com/geofs.php*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=geo-fs.com
  9. // @grant GM.getResourceUrl
  10. // @resource stall https://github.com/avramovic/geofs-alerts/raw/master/audio/airbus-stall-warning.mp3
  11. // @resource bankangle https://github.com/avramovic/geofs-alerts/raw/master/audio/bank-angle-bank-angle.mp3
  12. // @resource overspeed https://github.com/avramovic/geofs-alerts/raw/master/audio/md-80-overspeed.mp3
  13. // @resource autopilot https://github.com/avramovic/geofs-alerts/raw/master/audio/airbus-autopilot-off.mp3
  14. // @resource terrain https://github.com/avramovic/geofs-alerts/raw/master/audio/terrain-terrain-pull-up.mp3
  15. // @resource lowgear https://github.com/avramovic/geofs-alerts/raw/master/audio/too-low-gear.mp3
  16. // @resource lowflaps https://github.com/avramovic/geofs-alerts/raw/master/audio/too-low-flaps.mp3
  17. // @resource sinkrate https://github.com/avramovic/geofs-alerts/raw/master/audio/sink-rate.mp3
  18. // @resource minimums https://github.com/avramovic/geofs-alerts/raw/master/audio/minimums.mp3
  19. // @resource approaching https://github.com/avramovic/geofs-alerts/raw/master/audio/approaching-minimums.mp3
  20. // @resource retard https://github.com/avramovic/geofs-alerts/raw/master/audio/airbus-retard.mp3
  21. // @resource h2500 https://github.com/avramovic/geofs-alerts/raw/master/audio/2500.mp3
  22. // @resource h1000 https://github.com/avramovic/geofs-alerts/raw/master/audio/1000.mp3
  23. // @resource h500 https://github.com/avramovic/geofs-alerts/raw/master/audio/500.mp3
  24. // @resource h400 https://github.com/avramovic/geofs-alerts/raw/master/audio/400.mp3
  25. // @resource h300 https://github.com/avramovic/geofs-alerts/raw/master/audio/300.mp3
  26. // @resource h200 https://github.com/avramovic/geofs-alerts/raw/master/audio/200.mp3
  27. // @resource h100 https://github.com/avramovic/geofs-alerts/raw/master/audio/100.mp3
  28. // @resource h50 https://github.com/avramovic/geofs-alerts/raw/master/audio/50.mp3
  29. // @resource h40 https://github.com/avramovic/geofs-alerts/raw/master/audio/40.mp3
  30. // @resource h30 https://github.com/avramovic/geofs-alerts/raw/master/audio/30.mp3
  31. // @resource h20 https://github.com/avramovic/geofs-alerts/raw/master/audio/20.mp3
  32. // @resource h10 https://github.com/avramovic/geofs-alerts/raw/master/audio/10.mp3
  33. // @resource h5 https://github.com/avramovic/geofs-alerts/raw/master/audio/5.mp3
  34. // ==/UserScript==
  35.  
  36. (function () {
  37. 'use strict';
  38. // load the audio clips
  39. let stickShake;
  40. GM.getResourceUrl("stall").then((data) => {
  41. stickShake = new Audio('data:audio/mp3;'+data);
  42. stickShake.loop = true;
  43. });
  44.  
  45. let bankangle;
  46. GM.getResourceUrl("bankangle").then((data) => {
  47. bankangle = new Audio('data:audio/mp3;'+data);
  48. bankangle.loop = true;
  49. });
  50.  
  51. let overspeed;
  52. GM.getResourceUrl("overspeed").then((data) => {
  53. overspeed = new Audio('data:audio/mp3;'+data);
  54. overspeed.loop = true;
  55. });
  56.  
  57. let autopilot;
  58. GM.getResourceUrl("autopilot").then((data) => {
  59. autopilot = new Audio('data:audio/mp3;'+data);
  60. autopilot.loop = false;
  61. });
  62.  
  63. let terrain;
  64. GM.getResourceUrl("terrain").then((data) => {
  65. terrain = new Audio('data:audio/mp3;'+data);
  66. terrain.loop = true;
  67. });
  68.  
  69. let lowgear;
  70. GM.getResourceUrl("lowgear").then((data) => {
  71. lowgear = new Audio('data:audio/mp3;'+data);
  72. lowgear.loop = true;
  73. });
  74.  
  75. let lowflaps;
  76. GM.getResourceUrl("lowflaps").then((data) => {
  77. lowflaps = new Audio('data:audio/mp3;'+data);
  78. lowflaps.loop = true;
  79. });
  80.  
  81. let sinkrate;
  82. GM.getResourceUrl("sinkrate").then((data) => {
  83. sinkrate = new Audio('data:audio/mp3;'+data);
  84. sinkrate.loop = true;
  85. });
  86.  
  87. let approaching;
  88. GM.getResourceUrl("approaching").then((data) => {
  89. approaching = new Audio('data:audio/mp3;'+data);
  90. approaching.loop = false;
  91. });
  92.  
  93. let h2500;
  94. GM.getResourceUrl("h2500").then((data) => {
  95. h2500 = new Audio('data:audio/mp3;'+data);
  96. h2500.loop = false;
  97. });
  98.  
  99. let h1000;
  100. GM.getResourceUrl("h1000").then((data) => {
  101. h1000 = new Audio('data:audio/mp3;'+data);
  102. h1000.loop = false;
  103. });
  104.  
  105. let h500;
  106. GM.getResourceUrl("h500").then((data) => {
  107. h500 = new Audio('data:audio/mp3;'+data);
  108. h500.loop = false;
  109. });
  110.  
  111. let h400;
  112. GM.getResourceUrl("h400").then((data) => {
  113. h400 = new Audio('data:audio/mp3;'+data);
  114. h400.loop = false;
  115. });
  116.  
  117. let h300;
  118. GM.getResourceUrl("h300").then((data) => {
  119. h300 = new Audio('data:audio/mp3;'+data);
  120. h300.loop = false;
  121. });
  122.  
  123. let h200;
  124. GM.getResourceUrl("h200").then((data) => {
  125. h200 = new Audio('data:audio/mp3;'+data);
  126. h200.loop = false;
  127. });
  128.  
  129. let h100;
  130. GM.getResourceUrl("h100").then((data) => {
  131. h100 = new Audio('data:audio/mp3;'+data);
  132. h100.loop = false;
  133. });
  134.  
  135. let h50;
  136. GM.getResourceUrl("h50").then((data) => {
  137. h50 = new Audio('data:audio/mp3;'+data);
  138. h50.loop = false;
  139. });
  140.  
  141. let h40;
  142. GM.getResourceUrl("h40").then((data) => {
  143. h40 = new Audio('data:audio/mp3;'+data);
  144. h40.loop = false;
  145. });
  146.  
  147. let h30;
  148. GM.getResourceUrl("h30").then((data) => {
  149. h30 = new Audio('data:audio/mp3;'+data);
  150. h30.loop = false;
  151. });
  152.  
  153. let h20;
  154. GM.getResourceUrl("h20").then((data) => {
  155. h20 = new Audio('data:audio/mp3;'+data);
  156. h20.loop = false;
  157. });
  158.  
  159. let h10;
  160. GM.getResourceUrl("h10").then((data) => {
  161. h10 = new Audio('data:audio/mp3;'+data);
  162. h10.loop = false;
  163. });
  164.  
  165. let h5;
  166. GM.getResourceUrl("h5").then((data) => {
  167. h5 = new Audio('data:audio/mp3;'+data);
  168. h5.loop = false;
  169. });
  170.  
  171. let minimums;
  172. GM.getResourceUrl("minimums").then((data) => {
  173. minimums = new Audio('data:audio/mp3;'+data);
  174. minimums.loop = false;
  175. });
  176.  
  177. let retard;
  178. GM.getResourceUrl("retard").then((data) => {
  179. retard = new Audio('data:audio/mp3;'+data);
  180. retard.loop = false;
  181. });
  182.  
  183.  
  184. let apWasOn = false;
  185. let apIsOn = false;
  186. let oldAltitude = 0;
  187. let altitude = 0;
  188. let wasOnGround = true;
  189.  
  190.  
  191. // wait until flight sim is fully loaded
  192. let itv = setInterval(
  193. function(){
  194. if(unsafeWindow.ui && unsafeWindow.flight && unsafeWindow.geofs){
  195. setInterval(function() { mainLoop(); }, 500);
  196. clearInterval(itv);
  197. }
  198. }
  199. ,500);
  200.  
  201. function isGearUp() {
  202. return unsafeWindow.geofs.animation.values.gearPosition == 1;
  203. }
  204.  
  205. function isGearDown() {
  206. return unsafeWindow.geofs.animation.values.gearPosition == 0;
  207. }
  208.  
  209. function seaAltitude() {
  210. return unsafeWindow.geofs.animation.values.altitude;
  211. }
  212.  
  213. function groundAltitude() {
  214. return seaAltitude() - unsafeWindow.geofs.animation.values.groundElevationFeet - 50;
  215. }
  216.  
  217. function isDescending() {
  218. return unsafeWindow.geofs.animation.values.verticalSpeed < -50;
  219. }
  220.  
  221. function isSinking() {
  222. return unsafeWindow.geofs.animation.values.verticalSpeed < -2500;
  223. }
  224.  
  225. function isAscending() {
  226. return unsafeWindow.geofs.animation.values.verticalSpeed > 50;
  227. }
  228.  
  229. function flapsRetracted() {
  230. return unsafeWindow.geofs.animation.values.flapsValue == 0;
  231. }
  232.  
  233. function flapsExtended() {
  234. return unsafeWindow.geofs.animation.values.flapsValue > 0;
  235. }
  236.  
  237. function getLatitude() {
  238. return unsafeWindow.geofs.aircraft.instance.lastLlaLocation[0];
  239. }
  240.  
  241. function getLongitude() {
  242. return unsafeWindow.geofs.aircraft.instance.lastLlaLocation[1];
  243. }
  244.  
  245. function isOnGround() {
  246. return unsafeWindow.geofs.animation.values.groundContact == 1 ? true : false;
  247. }
  248.  
  249. function isStalling() {
  250. return unsafeWindow.geofs.aircraft.instance.stalling;
  251. }
  252.  
  253. function isCrashed() {
  254. return unsafeWindow.geofs.aircraft.instance.crashed;
  255. }
  256.  
  257. function isEngineOn() {
  258. return unsafeWindow.geofs.aircraft.instance.engine.on;
  259. }
  260.  
  261. function haversine(lat1, lon1, lat2, lon2) {
  262. const R = 6371; // Radius of the Earth in kilometers
  263. const toRad = (deg) => deg * (Math.PI / 180);
  264.  
  265. const dLat = toRad(lat2 - lat1);
  266. const dLon = toRad(lon2 - lon1);
  267.  
  268. const a =
  269. Math.sin(dLat / 2) * Math.sin(dLat / 2) +
  270. Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
  271. const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  272. return R * c; // Distance in kilometers
  273. }
  274.  
  275. function findNearestAirport() {
  276. let aircraftPosition = {
  277. lat: getLatitude(),
  278. lon: getLongitude(),
  279. };
  280.  
  281. let nearestAirport = null;
  282. let minDistance = Infinity;
  283.  
  284. for (let i in unsafeWindow.geofs.mainAirportList) {
  285. let ap = unsafeWindow.geofs.mainAirportList[i];
  286. let airportPosition = {
  287. lat: ap[0],
  288. lon: ap[1]
  289. };
  290.  
  291. let distance = haversine(
  292. aircraftPosition.lat,
  293. aircraftPosition.lon,
  294. airportPosition.lat,
  295. airportPosition.lon
  296. );
  297.  
  298. if (distance < minDistance) {
  299. minDistance = distance;
  300. nearestAirport = {
  301. airport: i,
  302. distanceInKm: distance
  303. };
  304. }
  305.  
  306. }
  307.  
  308. return nearestAirport;
  309. }
  310.  
  311.  
  312. function mainLoop(){
  313.  
  314. if (unsafeWindow.geofs.isPaused()) {
  315. //paused, do not run the loop
  316. return;
  317. }
  318.  
  319. // stall alert
  320. !isOnGround() && isStalling() ? stickShake.play() : stickShake.pause();
  321.  
  322. const fastPlanes = ["F-16 Fighting Falcon", "Concorde", "Sukhoi Su-35", "Boeing F/A-18F Super Hornet", "Wingsuit"];
  323.  
  324. // bank angle alert
  325. if (!fastPlanes.includes(unsafeWindow.geofs.aircraft.instance.aircraftRecord.name.trim())) {
  326. Math.abs(unsafeWindow.geofs.animation.values.aroll) > 40 ? bankangle.play() : bankangle.pause();
  327. }
  328.  
  329. // indicated airspeed overspeed alert
  330. if (!fastPlanes.includes(unsafeWindow.geofs.aircraft.instance.aircraftRecord.name.trim())) {
  331. let maxSpeed = unsafeWindow.geofs.animation.values.VNO > 0 ? unsafeWindow.geofs.animation.values.VNO+1 : 350;
  332. unsafeWindow.geofs.animation.values.kias > maxSpeed ? overspeed.play() : overspeed.pause();
  333. }
  334.  
  335. // autopilot disconnect alert
  336. apIsOn = unsafeWindow.geofs.autopilot.on;
  337. if (apWasOn && !apIsOn) {
  338. autopilot.play();
  339. } else if (!apWasOn && apIsOn) {
  340. autopilot.pause();
  341. autopilot.currentTime = 0;
  342.  
  343. }
  344. apWasOn = apIsOn;
  345.  
  346. // terrain alert
  347. if (isGearUp() && groundAltitude() <= 1000 && !isOnGround() && isEngineOn()) {
  348. terrain.play();
  349. } else {
  350. terrain.pause();
  351. terrain.currentTime = 0;
  352. }
  353.  
  354.  
  355. let nearestAp = findNearestAirport();
  356. if (isDescending() && groundAltitude() <= 1500 && typeof nearestAp == "object" && nearestAp.distanceInKm < 20) {
  357. if (isGearUp()) {
  358. lowgear.play();
  359. } else {
  360. lowgear.pause();
  361. lowgear.currentTime = 0;
  362.  
  363. if (flapsRetracted()) {
  364. lowflaps.play();
  365. } else {
  366. lowflaps.pause();
  367. lowflaps.currentTime = 0;
  368. }
  369. }
  370.  
  371. } else {
  372. lowgear.pause();
  373. lowgear.currentTime = 0;
  374. lowflaps.pause();
  375. lowflaps.currentTime = 0;
  376. }
  377.  
  378.  
  379. // sink rate
  380. isSinking() ? sinkrate.play() : sinkrate.pause();
  381.  
  382. // height callouts when fully configured for landing and near airport
  383. altitude = groundAltitude();
  384.  
  385. if (isDescending() && isGearDown() && flapsExtended() && typeof nearestAp == "object" && nearestAp.distanceInKm < 20) {
  386. if (oldAltitude > 2500 && altitude <= 2500) {
  387. h2500.play();
  388. } else if (oldAltitude > 1000 && altitude <= 1000) {
  389. h1000.play();
  390. } else if (oldAltitude > 500 && altitude <= 500) {
  391. h500.play();
  392. } else if (oldAltitude > 400 && altitude <= 400) {
  393. h400.play();
  394. } else if (oldAltitude > 350 && altitude <= 350) {
  395. approaching.play();
  396. } else if (oldAltitude > 300 && altitude <= 300) {
  397. h300.play();
  398. } else if (oldAltitude > 200 && altitude <= 200) {
  399. h200.play();
  400. } else if (oldAltitude > 150 && altitude <= 150) {
  401. minimums.play();
  402. } else if (oldAltitude > 100 && altitude <= 100) {
  403. h100.play();
  404. } else if (oldAltitude > 50 && altitude <= 50) {
  405. h50.play();
  406. } else if (oldAltitude > 40 && altitude <= 40) {
  407. h40.play();
  408. } else if (oldAltitude > 30 && altitude <= 30) {
  409. h30.play();
  410. } else if (oldAltitude > 20 && altitude <= 20) {
  411. h20.play();
  412. if (isEngineOn()) {
  413. retard.play();
  414. }
  415. } else if (oldAltitude > 10 && altitude <= 10) {
  416. h10.play();
  417. } else if (oldAltitude > 5 && altitude <= 5) {
  418. h5.play();
  419. }
  420. }
  421.  
  422.  
  423. oldAltitude = altitude;
  424. wasOnGround = isOnGround();
  425. }
  426. })();