bk117.nas 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972
  1. # Melchior FRANZ, < mfranz # aon : at >
  2. if (!contains(globals, "cprint"))
  3. var cprint = func nil;
  4. var devel = !!getprop("devel");
  5. var quickstart = !!getprop("quickstart");
  6. var sin = func(a) math.sin(a * D2R);
  7. var cos = func(a) math.cos(a * D2R);
  8. var pow = func(v, w) math.exp(math.ln(v) * w);
  9. var npow = func(v, w) v ? math.exp(math.ln(abs(v)) * w) * (v < 0 ? -1 : 1) : 0;
  10. var clamp = func(v, min = 0, max = 1) v < min ? min : v > max ? max : v;
  11. var normatan = func(x, slope = 1) math.atan2(x, slope) * 2 / math.pi;
  12. var bell = func(x, spread = 2) pow(math.e, -(x * x) / spread);
  13. var max = func(a, b) a > b ? a : b;
  14. var min = func(a, b) a < b ? a : b;
  15. # timers ============================================================
  16. aircraft.timer.new("/sim/time/hobbs/helicopter", nil).start();
  17. # strobes ===========================================================
  18. var strobe_switch = props.globals.initNode("controls/lighting/strobe", 1, "BOOL");
  19. aircraft.light.new("sim/model/bk117/lighting/strobe-top", [0.05, 1.00], strobe_switch);
  20. aircraft.light.new("sim/model/bk117/lighting/strobe-bottom", [0.05, 1.03], strobe_switch);
  21. # beacons ===========================================================
  22. var beacon_switch = props.globals.initNode("controls/lighting/beacon", 1, "BOOL");
  23. aircraft.light.new("sim/model/bk117/lighting/beacon-top", [0.62, 0.62], beacon_switch);
  24. aircraft.light.new("sim/model/bk117/lighting/beacon-bottom", [0.63, 0.63], beacon_switch);
  25. # nav lights ========================================================
  26. var nav_light_switch = props.globals.initNode("controls/lighting/nav-lights", 1, "BOOL");
  27. var visibility = props.globals.getNode("environment/visibility-m", 1);
  28. var sun_angle = props.globals.getNode("sim/time/sun-angle-rad", 1);
  29. var nav_lights = props.globals.getNode("sim/model/bk117/lighting/nav-lights", 1);
  30. var nav_light_loop = func {
  31. if (nav_light_switch.getValue())
  32. nav_lights.setValue(visibility.getValue() < 5000 or sun_angle.getValue() > 1.4);
  33. else
  34. nav_lights.setValue(0);
  35. settimer(nav_light_loop, 3);
  36. }
  37. nav_light_loop();
  38. # fuel ==============================================================
  39. # density = 6.682 lb/gal [Flight Manual Section 9.2]
  40. # avtur/JET A-1/JP-8
  41. var FUEL_DENSITY = getprop("/consumables/fuel/tank/density-ppg"); # pound per gallon
  42. var GAL2LB = FUEL_DENSITY;
  43. var LB2GAL = 1 / GAL2LB;
  44. var KG2GAL = KG2LB * LB2GAL;
  45. var GAL2KG = 1 / KG2GAL;
  46. var Tank = {
  47. new: func(n) {
  48. var m = { parents: [Tank] };
  49. m.capacity = n.getNode("capacity-gal_us").getValue();
  50. m.level_galN = n.initNode("level-gal_us", m.capacity);
  51. m.level_lbN = n.getNode("level-lbs");
  52. m.consume(0);
  53. return m;
  54. },
  55. level: func {
  56. return me.level_galN.getValue();
  57. },
  58. consume: func(amount) { # US gal (neg. values for feeding)
  59. var level = me.level();
  60. if (amount > level)
  61. amount = level;
  62. level -= amount;
  63. if (level > me.capacity)
  64. level = me.capacity;
  65. me.level_galN.setDoubleValue(level);
  66. me.level_lbN.setDoubleValue(level * GAL2LB);
  67. return amount;
  68. },
  69. };
  70. var fuel = {
  71. init: func {
  72. var fuel = props.globals.getNode("/consumables/fuel");
  73. me.pump_capacity = 6.6 * L2GAL / 60; # same pumps for transfer and supply; from ec135: 6.6 l/min
  74. me.total_galN = fuel.getNode("total-fuel-gals", 1);
  75. me.total_lbN = fuel.getNode("total-fuel-lbs", 1);
  76. me.total_normN = fuel.getNode("total-fuel-norm", 1);
  77. me.main = Tank.new(fuel.getNode("tank[0]"));
  78. me.supply = Tank.new(fuel.getNode("tank[1]"));
  79. var sw = props.globals.getNode("/controls/switches");
  80. setlistener(sw.initNode("fuel/transfer-pump[0]", 1, "BOOL"), func(n) me.trans1 = n.getValue(), 1);
  81. setlistener(sw.initNode("fuel/transfer-pump[1]", 1, "BOOL"), func(n) me.trans2 = n.getValue(), 1);
  82. setlistener("/sim/freeze/fuel", func(n) me.freeze = n.getBoolValue(), 1);
  83. me.capacity = me.main.capacity + me.supply.capacity;
  84. me.warntime = 0;
  85. me.update(0);
  86. },
  87. update: func(dt) {
  88. # transfer pumps (feed supply from main)
  89. var free = me.supply.capacity - me.supply.level();
  90. if (free > 0) {
  91. var trans_flow = (me.trans1 + me.trans2) * me.pump_capacity;
  92. me.supply.consume(-me.main.consume(min(trans_flow * dt, free)));
  93. }
  94. # low fuel warning [POH "General Description" 0.28a]
  95. var time = elapsedN.getValue();
  96. if (time > me.warntime and me.supply.level() * GAL2KG < 60) {
  97. screen.log.write("LOW FUEL WARNING", 1, 0, 0);
  98. me.warntime = time + screen.log.autoscroll * 2;
  99. }
  100. var level = me.main.level() + me.supply.level();
  101. me.total_galN.setDoubleValue(level);
  102. me.total_lbN.setDoubleValue(level * GAL2LB);
  103. me.total_normN.setDoubleValue(level / me.capacity);
  104. },
  105. level: func {
  106. return me.supply.level();
  107. },
  108. consume: func(amount) {
  109. return me.freeze ? 0 : me.supply.consume(amount);
  110. },
  111. };
  112. # engines/rotor =====================================================
  113. var rotor_rpm = props.globals.getNode("rotors/main/rpm");
  114. var torque = props.globals.getNode("rotors/gear/total-torque", 1);
  115. var collective = props.globals.getNode("controls/engines/engine[0]/throttle");
  116. var turbine = props.globals.getNode("sim/model/bk117/turbine-rpm-pct", 1);
  117. var torque_pct = props.globals.getNode("sim/model/bk117/torque-pct", 1);
  118. var target_rel_rpm = props.globals.getNode("controls/rotor/reltarget", 1);
  119. var max_rel_torque = props.globals.getNode("controls/rotor/maxreltorque", 1);
  120. var Engine = {
  121. new: func(n) {
  122. var m = { parents: [Engine] };
  123. m.in = props.globals.getNode("controls/engines", 1).getChild("engine", n, 1);
  124. m.out = props.globals.getNode("engines", 1).getChild("engine", n, 1);
  125. m.airtempN = props.globals.getNode("/environment/temperature-degc");
  126. # input
  127. m.ignitionN = m.in.initNode("ignition", 0, "BOOL");
  128. m.starterN = m.in.initNode("starter", 0, "BOOL");
  129. m.powerN = m.in.initNode("power", 0);
  130. m.magnetoN = m.in.initNode("magnetos", 1, "INT");
  131. # output
  132. m.runningN = m.out.initNode("running", 0, "BOOL");
  133. m.n1_pctN = m.out.initNode("n1-pct", 0);
  134. m.n2_pctN = m.out.initNode("n2-pct", 0);
  135. m.n1N = m.out.initNode("n1-rpm", 0);
  136. m.n2N = m.out.initNode("n2-rpm", 0);
  137. m.totN = m.out.initNode("tot-degc", m.airtempN.getValue());
  138. m.starterLP = aircraft.lowpass.new(3);
  139. m.n1LP = aircraft.lowpass.new(4);
  140. m.n2LP = aircraft.lowpass.new(4);
  141. setlistener("/sim/signals/reinit", func(n) n.getValue() or m.reset(), 1);
  142. m.timer = aircraft.timer.new("/sim/time/hobbs/turbines[" ~ n ~ "]", 10);
  143. m.running = 0;
  144. m.fuelflow = 0;
  145. m.n1 = -1;
  146. m.up = -1;
  147. return m;
  148. },
  149. reset: func {
  150. me.magnetoN.setIntValue(1);
  151. me.ignitionN.setBoolValue(0);
  152. me.starterN.setBoolValue(0);
  153. me.powerN.setDoubleValue(0);
  154. me.runningN.setBoolValue(me.running = 0);
  155. me.starterLP.set(0);
  156. me.n1LP.set(me.n1 = 0);
  157. me.n2LP.set(me.n2 = 0);
  158. },
  159. update: func(dt, trim = 0) {
  160. var starter = me.starterLP.filter(me.starterN.getValue() * 0.19); # starter 15-20% N1max
  161. me.powerN.setValue(me.power = clamp(me.powerN.getValue()));
  162. var power = me.power * 0.97 + trim; # 97% = N2% in flight position
  163. if (me.running)
  164. power += (1 - collective.getValue()) * 0.03; # droop compensator
  165. if (power > 1.12)
  166. power = 1.12; # overspeed restrictor
  167. me.fuelflow = 0;
  168. if (!me.running) {
  169. if (me.n1 > 0.05 and power > 0.05 and me.ignitionN.getValue()) {
  170. me.runningN.setBoolValue(me.running = 1);
  171. me.timer.start();
  172. }
  173. } elsif (power < 0.05 or !fuel.level()) {
  174. me.runningN.setBoolValue(me.running = 0);
  175. me.timer.stop();
  176. } else {
  177. me.fuelflow = power;
  178. }
  179. var lastn1 = me.n1;
  180. me.n1 = me.n1LP.filter(max(me.fuelflow, starter));
  181. me.n2 = me.n2LP.filter(me.n1);
  182. me.up = me.n1 - lastn1;
  183. # temperature
  184. if (me.fuelflow > me.pos.idle)
  185. var target = 440 + (779 - 440) * (0.03 + me.fuelflow - me.pos.idle) / (me.pos.flight - me.pos.idle);
  186. else
  187. var target = 440 * (0.03 + me.fuelflow) / me.pos.idle;
  188. if (me.n1 < 0.4 and me.fuelflow - me.n1 > 0.001) {
  189. target += (me.fuelflow - me.n1) * 7000;
  190. if (target > 980)
  191. target = 980;
  192. }
  193. var airtemp = me.airtempN.getValue();
  194. if (target < airtemp)
  195. target = airtemp;
  196. var decay = (me.up > 0 ? 10 : me.n1 > 0.02 ? 0.01 : 0.001) * dt;
  197. me.totN.setValue((me.totN.getValue() + decay * target) / (1 + decay));
  198. # constant 150 kg/h for now (both turbines)
  199. fuel.consume(75 * KG2GAL * me.fuelflow * dt / 3600);
  200. # derived gauge values
  201. me.n1_pctN.setDoubleValue(me.n1 * 100);
  202. me.n2_pctN.setDoubleValue(me.n2 * 100);
  203. me.n1N.setDoubleValue(me.n1 * 50970);
  204. me.n2N.setDoubleValue(me.n2 * 33290);
  205. },
  206. setpower: func(v) {
  207. var target = (int((me.power + 0.15) * 3) + v) / 3;
  208. var time = abs(me.power - target) * 4;
  209. interpolate(me.powerN, target, time);
  210. },
  211. adjust_power: func(delta, mode = 0) {
  212. if (delta) {
  213. var power = me.powerN.getValue();
  214. if (me.power_min == nil) {
  215. if (delta > 0) {
  216. if (power < me.pos.idle) {
  217. me.power_min = me.pos.cutoff;
  218. me.power_max = me.pos.idle;
  219. } else {
  220. me.power_min = me.pos.idle;
  221. me.power_max = me.pos.flight;
  222. }
  223. } else {
  224. if (power > me.pos.idle) {
  225. me.power_max = me.pos.flight;
  226. me.power_min = me.pos.idle;
  227. } else {
  228. me.power_max = me.pos.idle;
  229. me.power_min = me.pos.cutoff;
  230. }
  231. }
  232. }
  233. me.powerN.setValue(power = clamp(power + delta, me.power_min, me.power_max));
  234. return power;
  235. } elsif (mode) {
  236. me.power_min = me.power_max = nil;
  237. }
  238. },
  239. pos: { cutoff: 0, idle: 0.63, flight: 1 },
  240. };
  241. var engines = {
  242. init: func {
  243. me.engine = [Engine.new(0), Engine.new(1)];
  244. me.trimN = props.globals.initNode("/controls/engines/power-trim");
  245. me.balanceN = props.globals.initNode("/controls/engines/power-balance");
  246. me.commonrpmN = props.globals.initNode("/engines/engine/rpm");
  247. },
  248. reset: func {
  249. me.engine[0].reset();
  250. me.engine[1].reset();
  251. },
  252. update: func(dt) {
  253. # each starter button disables ignition switch of opposite engine
  254. if (me.engine[0].starterN.getValue())
  255. me.engine[1].ignitionN.setBoolValue(0);
  256. if (me.engine[1].starterN.getValue())
  257. me.engine[0].ignitionN.setBoolValue(0);
  258. # update engines
  259. var trim = me.trimN.getValue() * 0.1;
  260. var balance = me.balanceN.getValue() * 0.1;
  261. me.engine[0].update(dt, trim - balance);
  262. me.engine[1].update(dt, trim + balance);
  263. # set rotor
  264. var n2max = max(me.engine[0].n2, me.engine[1].n2);
  265. target_rel_rpm.setValue(n2max);
  266. max_rel_torque.setValue(n2max);
  267. me.commonrpmN.setValue(n2max * 33290); # attitude indicator needs pressure
  268. # Warning Box Type K-DW02/01
  269. if (n2max > 0.67) { # 0.63?
  270. setprop("sim/sound/warn2600", n2max > 1.08);
  271. setprop("sim/sound/warn650", abs(me.engine[0].n2 - me.engine[1].n2) > 0.12
  272. or n2max > 0.75 and n2max < 0.95);
  273. } else {
  274. setprop("sim/sound/warn2600", 0);
  275. setprop("sim/sound/warn650", 0);
  276. }
  277. },
  278. adjust_power: func(delta, mode = 0) {
  279. if (!delta) {
  280. engines.engine[0].adjust_power(0, mode);
  281. engines.engine[1].adjust_power(0, mode);
  282. } else {
  283. var p = [0, 0];
  284. for (var i = 0; i < 2; i += 1)
  285. if (controls.engines[i].selected.getValue())
  286. p[i] = engines.engine[i].adjust_power(delta);
  287. gui.popupTip(sprintf("power lever %d%%", 100 * max(p[0], p[1])));
  288. }
  289. },
  290. quickstart: func { # development only
  291. me.engine[0].n1LP.set(1);
  292. me.engine[0].n2LP.set(1);
  293. me.engine[1].n1LP.set(1);
  294. me.engine[1].n2LP.set(1);
  295. procedure.step = 1;
  296. procedure.next();
  297. },
  298. };
  299. var vert_speed_fpm = props.globals.initNode("/velocities/vertical-speed-fpm");
  300. if (devel) {
  301. setprop("/instrumentation/altimeter/setting-inhg", getprop("/environment/pressure-inhg"));
  302. setlistener("/sim/signals/fdm-initialized", func {
  303. settimer(func {
  304. screen.property_display.x = 760;
  305. screen.property_display.y = 200;
  306. screen.property_display.format = "%.3g";
  307. screen.property_display.add(
  308. rotor_rpm,
  309. torque_pct,
  310. target_rel_rpm,
  311. max_rel_torque,
  312. "/controls/engines/power-trim",
  313. "/controls/engines/power-balance",
  314. "/consumables/fuel/total-fuel-gals",
  315. "L",
  316. engines.engine[0].runningN,
  317. engines.engine[0].ignitionN,
  318. "/controls/engines/engine[0]/power",
  319. engines.engine[0].n1_pctN,
  320. engines.engine[0].n2_pctN,
  321. engines.engine[0].totN,
  322. #engines.engine[0].n1N,
  323. #engines.engine[0].n2N,
  324. "R",
  325. engines.engine[1].runningN,
  326. engines.engine[1].ignitionN,
  327. "/controls/engines/engine[1]/power",
  328. engines.engine[1].n1_pctN,
  329. engines.engine[1].n2_pctN,
  330. engines.engine[1].totN,
  331. #engines.engine[1].n1N,
  332. #engines.engine[1].n2N,
  333. "X",
  334. "/sim/model/gross-weight-kg",
  335. "/position/altitude-ft",
  336. "/position/altitude-agl-ft",
  337. "/instrumentation/altimeter/indicated-altitude-ft",
  338. "/environment/temperature-degc",
  339. vert_speed_fpm,
  340. "/velocities/airspeed-kt",
  341. );
  342. }, 1);
  343. });
  344. }
  345. var mouse = {
  346. init: func {
  347. me.x = me.y = nil;
  348. me.savex = nil;
  349. me.savey = nil;
  350. setlistener("/sim/startup/xsize", func(n) me.centerx = int(n.getValue() / 2), 1);
  351. setlistener("/sim/startup/ysize", func(n) me.centery = int(n.getValue() / 2), 1);
  352. setlistener("/devices/status/mice/mouse/mode", func(n) me.mode = n.getValue(), 1);
  353. setlistener("/devices/status/mice/mouse/button[1]", func(n) {
  354. me.mmb = n.getValue();
  355. if (me.mode)
  356. return;
  357. if (me.mmb) {
  358. engines.adjust_power(0, 1);
  359. me.savex = me.x;
  360. me.savey = me.y;
  361. gui.setCursor(me.centerx, me.centery, "none");
  362. } else {
  363. gui.setCursor(me.savex, me.savey, "pointer");
  364. }
  365. }, 1);
  366. setlistener("/devices/status/mice/mouse/x", func(n) me.x = n.getValue(), 1);
  367. setlistener("/devices/status/mice/mouse/y", func(n) me.update(me.y = n.getValue()), 1);
  368. },
  369. update: func {
  370. if (me.mode or !me.mmb)
  371. return;
  372. if (var dy = -me.y + me.centery)
  373. engines.adjust_power(dy * 0.005);
  374. gui.setCursor(me.centerx, me.centery);
  375. },
  376. };
  377. var power = func(v) {
  378. if (controls.engines[0].selected.getValue())
  379. engines.engine[0].setpower(v);
  380. if (controls.engines[1].selected.getValue())
  381. engines.engine[1].setpower(v);
  382. }
  383. var startup = func {
  384. if (procedure.stage < 0) {
  385. procedure.step = 1;
  386. procedure.next();
  387. }
  388. }
  389. var shutdown = func {
  390. if (procedure.stage > 0) {
  391. procedure.step = -1;
  392. procedure.next();
  393. }
  394. }
  395. var procedure = {
  396. stage: -999,
  397. step: nil,
  398. loopid: 0,
  399. reset: func {
  400. me.loopid += 1;
  401. me.stage = -999;
  402. step = nil;
  403. engines.reset();
  404. },
  405. next: func(delay = 0) {
  406. if (crashed)
  407. return;
  408. if (me.stage < 0 and me.step > 0 or me.stage > 0 and me.step < 0)
  409. me.stage = 0;
  410. settimer(func { me.stage += me.step; me.process(me.loopid) }, delay * !quickstart);
  411. },
  412. process: func(id) {
  413. id == me.loopid or return;
  414. # startup
  415. if (me.stage == 1) {
  416. cprint("", "1: press start button #1 -> spool up turbine #1 to N1 8.6--15%");
  417. setprop("/controls/rotor/brake", 0);
  418. engines.engine[0].ignitionN.setValue(1);
  419. engines.engine[0].starterN.setValue(1);
  420. me.next(4);
  421. } elsif (me.stage == 2) {
  422. cprint("", "2: move power lever #1 forward -> fuel injection");
  423. engines.engine[0].powerN.setValue(0.13);
  424. me.next(2.5);
  425. } elsif (me.stage == 3) {
  426. cprint("", "3: turbine #1 ignition (wait for EGT stabilization)");
  427. me.next(4.5);
  428. } elsif (me.stage == 4) {
  429. cprint("", "4: move power lever #1 to idle position -> engine #1 spools up to N1 63%");
  430. engines.engine[0].powerN.setValue(0.63);
  431. me.next(5);
  432. } elsif (me.stage == 5) {
  433. cprint("", "5: release start button #1\n");
  434. engines.engine[0].starterN.setValue(0);
  435. engines.engine[0].ignitionN.setValue(0);
  436. me.next(3);
  437. } elsif (me.stage == 6) {
  438. cprint("", "6: press start button #2 -> spool up turbine #2 to N1 8.6--15%");
  439. engines.engine[1].ignitionN.setValue(1);
  440. engines.engine[1].starterN.setValue(1);
  441. me.next(5);
  442. } elsif (me.stage == 7) {
  443. cprint("", "7: move power lever #2 forward -> fuel injection");
  444. engines.engine[1].powerN.setValue(0.13);
  445. me.next(2);
  446. } elsif (me.stage == 8) {
  447. cprint("", "8: turbine #2 ignition (wait for EGT stabilization)");
  448. me.next(5);
  449. } elsif (me.stage == 9) {
  450. cprint("", "9: move power lever #2 to idle position -> engine #2 spools up to N1 63%");
  451. engines.engine[1].powerN.setValue(0.63);
  452. me.next(8);
  453. } elsif (me.stage == 10) {
  454. cprint("", "10: release start button #2\n");
  455. engines.engine[1].starterN.setValue(0);
  456. engines.engine[1].ignitionN.setValue(0);
  457. me.next(1);
  458. } elsif (me.stage == 11) {
  459. cprint("", "11: move both power levers forward -> turbines spool up to 100%");
  460. engines.engine[0].powerN.setValue(1);
  461. engines.engine[1].powerN.setValue(1);
  462. # shutdown
  463. } elsif (me.stage == -1) {
  464. cprint("", "-1: power lever in idle position; cool engines");
  465. engines.engine[0].starterN.setValue(0);
  466. engines.engine[1].starterN.setValue(0);
  467. engines.engine[0].ignitionN.setValue(0);
  468. engines.engine[1].ignitionN.setValue(0);
  469. engines.engine[0].powerN.setValue(0.63);
  470. engines.engine[1].powerN.setValue(0.63);
  471. me.next(40);
  472. } elsif (me.stage == -2) {
  473. cprint("", "-2: engines shut down");
  474. engines.engine[0].powerN.setValue(0);
  475. engines.engine[1].powerN.setValue(0);
  476. me.next(40);
  477. } elsif (me.stage == -3) {
  478. cprint("", "-3: rotor brake\n");
  479. setprop("/controls/rotor/brake", 1);
  480. }
  481. },
  482. };
  483. # torquemeter
  484. var torque_val = 0;
  485. torque.setDoubleValue(0);
  486. var update_torque = func(dt) {
  487. var f = dt / (0.2 + dt);
  488. torque_val = torque.getValue() * f + torque_val * (1 - f);
  489. torque_pct.setDoubleValue(torque_val / 5300);
  490. }
  491. # blade vibration absorber pendulum
  492. var pendulum = props.globals.getNode("/sim/model/bk117/absorber-angle-deg", 1);
  493. var update_absorber = func {
  494. pendulum.setDoubleValue(90 * clamp(abs(rotor_rpm.getValue()) / 90));
  495. }
  496. var vibration = { # and noise ...
  497. init: func {
  498. me.lonN = props.globals.initNode("/rotors/main/vibration/longitudinal");
  499. me.latN = props.globals.initNode("/rotors/main/vibration/lateral");
  500. me.soundN = props.globals.initNode("/sim/sound/vibration");
  501. me.airspeedN = props.globals.getNode("/velocities/airspeed-kt");
  502. me.vertspeedN = props.globals.getNode("/velocities/vertical-speed-fps");
  503. me.groundspeedN = props.globals.getNode("/velocities/groundspeed-kt");
  504. me.speeddownN = props.globals.getNode("/velocities/speed-down-fps");
  505. me.angleN = props.globals.initNode("/velocities/descent-angle-deg");
  506. me.dir = 0;
  507. },
  508. update: func(dt) {
  509. var airspeed = me.airspeedN.getValue();
  510. if (airspeed > 120) { # overspeed vibration
  511. var frequency = 2000 + 500 * rand();
  512. var v = 0.49 + 0.5 * normatan(airspeed - 160, 10);
  513. var intensity = v;
  514. var noise = v * internal;
  515. } elsif (airspeed > 30) { # Blade Vortex Interaction (BVI) 8 deg, 65 kts max?
  516. var frequency = rotor_rpm.getValue() * 4 * 60;
  517. var down = me.speeddownN.getValue() * FT2M;
  518. var level = me.groundspeedN.getValue() * NM2M / 3600;
  519. me.angleN.setDoubleValue(var angle = math.atan2(down, level) * R2D);
  520. var speed = math.sqrt(level * level + down * down) * MPS2KT;
  521. angle = bell(angle - 9, 13);
  522. speed = bell(speed - 65, 450);
  523. var v = angle * speed;
  524. var intensity = v * 0.10;
  525. var noise = v * (1 - internal * 0.4);
  526. } else { # hover
  527. var rpm = rotor_rpm.getValue();
  528. var frequency = rpm * 4 * 60;
  529. var coll = bell(collective.getValue(), 0.5);
  530. var ias = bell(airspeed, 600);
  531. var vert = bell(me.vertspeedN.getValue() * 0.5, 400);
  532. var rpm = 0.477 + 0.5 * normatan(rpm - 350, 30) * 1.025;
  533. var v = coll * ias * vert * rpm;
  534. var intensity = v * 0.10;
  535. var noise = v * (1 - internal * 0.4);
  536. }
  537. me.dir += dt * frequency;
  538. me.lonN.setValue(cos(me.dir) * intensity);
  539. me.latN.setValue(sin(me.dir) * intensity);
  540. me.soundN.setValue(noise);
  541. },
  542. };
  543. # sound =============================================================
  544. # stall sound
  545. var stall = props.globals.getNode("rotors/main/stall", 1);
  546. var stall_filtered = props.globals.getNode("rotors/main/stall-filtered", 1);
  547. var stall_val = 0;
  548. stall.setDoubleValue(0);
  549. var update_stall = func(dt) {
  550. var s = stall.getValue();
  551. if (s < stall_val) {
  552. var f = dt / (0.3 + dt);
  553. stall_val = s * f + stall_val * (1 - f);
  554. } else {
  555. stall_val = s;
  556. }
  557. var c = collective.getValue();
  558. stall_filtered.setDoubleValue(stall_val + 0.006 * (1 - c));
  559. }
  560. # skid slide sound
  561. var Skid = {
  562. new: func(n) {
  563. var m = { parents: [Skid] };
  564. var soundN = props.globals.getNode("sim/model/bk117/sound", 1).getChild("slide", n, 1);
  565. var gearN = props.globals.getNode("gear", 1).getChild("gear", n, 1);
  566. m.compressionN = gearN.getNode("compression-norm", 1);
  567. m.rollspeedN = gearN.getNode("rollspeed-ms", 1);
  568. m.frictionN = gearN.getNode("ground-friction-factor", 1);
  569. m.wowN = gearN.getNode("wow", 1);
  570. m.volumeN = soundN.getNode("volume", 1);
  571. m.pitchN = soundN.getNode("pitch", 1);
  572. m.compressionN.setDoubleValue(0);
  573. m.rollspeedN.setDoubleValue(0);
  574. m.frictionN.setDoubleValue(0);
  575. m.volumeN.setDoubleValue(0);
  576. m.pitchN.setDoubleValue(0);
  577. m.wowN.setBoolValue(1);
  578. m.self = n;
  579. return m;
  580. },
  581. update: func {
  582. me.wow = me.wowN.getValue();
  583. if (me.wow < 0.5)
  584. return me.volumeN.setDoubleValue(0);
  585. var rollspeed = abs(me.rollspeedN.getValue());
  586. me.pitchN.setDoubleValue(rollspeed * 0.6);
  587. var s = normatan(20 * rollspeed);
  588. var f = clamp((me.frictionN.getValue() - 0.5) * 2);
  589. var c = clamp(me.compressionN.getValue() * 2);
  590. var vol = s * f * c;
  591. me.volumeN.setDoubleValue(vol > 0.1 ? vol : 0);
  592. #if (!me.self) {
  593. # cprint("33;1", sprintf("S=%0.3f F=%0.3f C=%0.3f >> %0.3f", s, f, c, s * f * c));
  594. #}
  595. },
  596. };
  597. var skids = [];
  598. for (var i = 0; i < 4; i += 1)
  599. append(skids, Skid.new(i));
  600. #var antislide = props.globals.initNode("/gear/antislide");
  601. var update_slide = func {
  602. var wow = 0;
  603. foreach (var s; skids) {
  604. s.update();
  605. wow += s.wow;
  606. }
  607. #antislide.setDoubleValue(wow > 0 ? 1 - rotor_rpm.getValue() / 10 : 0);
  608. }
  609. var internal = 1;
  610. setlistener("sim/current-view/view-number", func {
  611. internal = getprop("sim/current-view/internal");
  612. }, 1);
  613. # crash handler =====================================================
  614. var crash = func {
  615. if (arg[0]) {
  616. # crash
  617. setprop("sim/model/bk117/tail-angle-deg", 35);
  618. setprop("sim/model/bk117/shadow", 0);
  619. setprop("sim/model/bk117/doors/door[0]/position-norm", 0.2);
  620. setprop("sim/model/bk117/doors/door[1]/position-norm", 0.9);
  621. setprop("sim/model/bk117/doors/door[2]/position-norm", 0.2);
  622. setprop("sim/model/bk117/doors/door[3]/position-norm", 0.6);
  623. setprop("sim/model/bk117/doors/door[4]/position-norm", 0.1);
  624. setprop("sim/model/bk117/doors/door[5]/position-norm", 0.05);
  625. setprop("rotors/main/rpm", 0);
  626. setprop("rotors/main/blade[0]/flap-deg", -60);
  627. setprop("rotors/main/blade[1]/flap-deg", -50);
  628. setprop("rotors/main/blade[2]/flap-deg", -40);
  629. setprop("rotors/main/blade[3]/flap-deg", -30);
  630. setprop("rotors/main/blade[0]/incidence-deg", -30);
  631. setprop("rotors/main/blade[1]/incidence-deg", -20);
  632. setprop("rotors/main/blade[2]/incidence-deg", -50);
  633. setprop("rotors/main/blade[3]/incidence-deg", -55);
  634. setprop("rotors/tail/rpm", 0);
  635. strobe_switch.setValue(0);
  636. beacon_switch.setValue(0);
  637. nav_light_switch.setValue(0);
  638. engines.engine[0].n2_pctN.setValue(0);
  639. engines.engine[1].n2_pctN.setValue(0);
  640. torque_pct.setValue(torque_val = 0);
  641. stall_filtered.setValue(stall_val = 0);
  642. } else {
  643. # uncrash (for replay)
  644. setprop("sim/model/bk117/tail-angle-deg", 0);
  645. setprop("sim/model/bk117/shadow", 1);
  646. doors.reset();
  647. setprop("rotors/tail/rpm", 2219);
  648. setprop("rotors/main/rpm", 442);
  649. for (i = 0; i < 4; i += 1) {
  650. setprop("rotors/main/blade[" ~ i ~ "]/flap-deg", 0);
  651. setprop("rotors/main/blade[" ~ i ~ "]/incidence-deg", 0);
  652. }
  653. strobe_switch.setValue(1);
  654. beacon_switch.setValue(1);
  655. engines.engine[0].n2_pct.setValue(100);
  656. engines.engine[1].n2_pct.setValue(100);
  657. }
  658. }
  659. # "manual" rotor animation for flight data recorder replay ============
  660. var rotor_step = props.globals.getNode("sim/model/bk117/rotor-step-deg");
  661. var blade1_pos = props.globals.getNode("rotors/main/blade[0]/position-deg", 1);
  662. var blade2_pos = props.globals.getNode("rotors/main/blade[1]/position-deg", 1);
  663. var blade3_pos = props.globals.getNode("rotors/main/blade[2]/position-deg", 1);
  664. var blade4_pos = props.globals.getNode("rotors/main/blade[3]/position-deg", 1);
  665. var rotorangle = 0;
  666. var rotoranim_loop = func {
  667. var i = rotor_step.getValue();
  668. if (i >= 0.0) {
  669. blade1_pos.setValue(rotorangle);
  670. blade2_pos.setValue(rotorangle + 90);
  671. blade3_pos.setValue(rotorangle + 180);
  672. blade4_pos.setValue(rotorangle + 270);
  673. rotorangle += i;
  674. settimer(rotoranim_loop, 0.1);
  675. }
  676. }
  677. var init_rotoranim = func {
  678. if (rotor_step.getValue() >= 0.0)
  679. settimer(rotoranim_loop, 0.1);
  680. }
  681. # view management ===================================================
  682. var elapsedN = props.globals.getNode("/sim/time/elapsed-sec", 1);
  683. var flap_mode = 0;
  684. var down_time = 0;
  685. controls.flapsDown = func(v) {
  686. if (!flap_mode) {
  687. if (v < 0) {
  688. down_time = elapsedN.getValue();
  689. flap_mode = 1;
  690. dynamic_view.lookat(
  691. 5, # heading left
  692. -20, # pitch up
  693. 0, # roll right
  694. 0.2, # right
  695. 0.6, # up
  696. 0.85, # back
  697. 0.2, # time
  698. 55, # field of view
  699. );
  700. } elsif (v > 0) {
  701. flap_mode = 2;
  702. gui.popupTip("AUTOTRIM", 1e10);
  703. aircraft.autotrim.start();
  704. }
  705. } else {
  706. if (flap_mode == 1) {
  707. if (elapsedN.getValue() < down_time + 0.2)
  708. return;
  709. dynamic_view.resume();
  710. } elsif (flap_mode == 2) {
  711. aircraft.autotrim.stop();
  712. gui.popdown();
  713. }
  714. flap_mode = 0;
  715. }
  716. }
  717. # register function that may set me.heading_offset, me.pitch_offset, me.roll_offset,
  718. # me.x_offset, me.y_offset, me.z_offset, and me.fov_offset
  719. #
  720. dynamic_view.register(func {
  721. var lowspeed = 1 - normatan(me.speedN.getValue() / 50);
  722. var r = sin(me.roll) * cos(me.pitch);
  723. me.heading_offset = # heading change due to
  724. (me.roll < 0 ? -50 : -30) * r * abs(r); # roll left/right
  725. me.pitch_offset = # pitch change due to
  726. (me.pitch < 0 ? -50 : -50) * sin(me.pitch) * lowspeed # pitch down/up
  727. + 15 * sin(me.roll) * sin(me.roll); # roll
  728. me.roll_offset = # roll change due to
  729. -15 * r * lowspeed; # roll
  730. });
  731. # livery/configuration ==============================================
  732. #aircraft.livery.init("Aircraft/bk117/Models/Variants", "sim/model/bk117/name");
  733. # main() ============================================================
  734. var delta_time = props.globals.getNode("/sim/time/delta-sec", 1);
  735. var hi_heading = props.globals.getNode("/instrumentation/heading-indicator/indicated-heading-deg", 1);
  736. var vertspeed = props.globals.initNode("/velocities/vertical-speed-fps");
  737. var gross_weight_lb = props.globals.initNode("/yasim/gross-weight-lbs");
  738. var gross_weight_kg = props.globals.initNode("/sim/model/gross-weight-kg");
  739. props.globals.getNode("/instrumentation/adf/rotation-deg", 1).alias(hi_heading);
  740. var main_loop = func {
  741. props.globals.removeChild("autopilot");
  742. if (replay)
  743. setprop("/position/gear-agl-m", getprop("/position/altitude-agl-ft") * 0.3 - 1.2);
  744. vert_speed_fpm.setDoubleValue(vertspeed.getValue() * 60);
  745. gross_weight_kg.setDoubleValue(gross_weight_lb.getValue() * LB2KG);
  746. var dt = delta_time.getValue();
  747. update_torque(dt);
  748. update_stall(dt);
  749. update_slide();
  750. #update_volume();
  751. update_absorber();
  752. fuel.update(dt);
  753. engines.update(dt);
  754. vibration.update(dt);
  755. settimer(main_loop, 0);
  756. }
  757. var replay = 0;
  758. var crashed = 0;
  759. setlistener("/sim/signals/fdm-initialized", func {
  760. gui.menuEnable("autopilot", 0);
  761. #init_rotoranim();
  762. vibration.init();
  763. engines.init();
  764. fuel.init();
  765. mouse.init();
  766. #init_weapons();
  767. #setlistener("/sim/model/livery/file", reconfigure, 1);
  768. collective.setDoubleValue(1);
  769. setlistener("/sim/signals/reinit", func(n) {
  770. n.getBoolValue() and return;
  771. cprint("32;1", "reinit");
  772. procedure.reset();
  773. collective.setDoubleValue(1);
  774. aircraft.livery.rescan();
  775. reconfigure();
  776. crashed = 0;
  777. });
  778. setlistener("sim/crashed", func(n) {
  779. cprint("31;1", "crashed ", n.getValue());
  780. engines.engine[0].timer.stop();
  781. engines.engine[1].timer.stop();
  782. if (n.getBoolValue())
  783. crash(crashed = 1);
  784. });
  785. setlistener("/sim/freeze/replay-state", func(n) {
  786. replay = n.getValue();
  787. cprint("33;1", replay ? "replay" : "pause");
  788. if (crashed)
  789. crash(!n.getBoolValue())
  790. });
  791. main_loop();
  792. if (devel and quickstart)
  793. engines.quickstart();
  794. });