DH2 Fixed

Improve Diamond Hunt 2

目前为 2017-02-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name DH2 Fixed
  3. // @namespace FileFace
  4. // @description Improve Diamond Hunt 2
  5. // @version 0.28.1
  6. // @author Zorbing
  7. // @grant none
  8. // @run-at document-start
  9. // @include http://www.diamondhunt.co/game.php
  10. // ==/UserScript==
  11.  
  12. (function ()
  13. {
  14. 'use strict';
  15.  
  16.  
  17.  
  18. /**
  19. * observer
  20. */
  21.  
  22. let observedKeys = new Map();
  23. /**
  24. * Observes the given key for change
  25. *
  26. * @param {string} key The name of the variable
  27. * @param {Function} fn The function which is called on change
  28. */
  29. function observe(key, fn)
  30. {
  31. if (key instanceof Array)
  32. {
  33. for (let k of key)
  34. {
  35. observe(k, fn);
  36. }
  37. }
  38. else
  39. {
  40. if (!observedKeys.has(key))
  41. {
  42. observedKeys.set(key, new Set());
  43. }
  44. observedKeys.get(key).add(fn);
  45. }
  46. return fn;
  47. }
  48. function unobserve(key, fn)
  49. {
  50. if (key instanceof Array)
  51. {
  52. let ret = [];
  53. for (let k of key)
  54. {
  55. ret.push(unobserve(k, fn));
  56. }
  57. return ret;
  58. }
  59. if (!observedKeys.has(key))
  60. {
  61. return false;
  62. }
  63. return observedKeys.get(key).delete(fn);
  64. }
  65. function updateValue(key, newValue)
  66. {
  67. if (window[key] === newValue)
  68. {
  69. return false;
  70. }
  71.  
  72. const oldValue = window[key];
  73. window[key] = newValue;
  74. (observedKeys.get(key) || []).forEach(fn => fn(key, oldValue, newValue));
  75. return true;
  76. }
  77.  
  78.  
  79.  
  80. /**
  81. * global constants
  82. */
  83.  
  84. const tierLevels = ['empty', 'sapphire', 'emerald', 'ruby', 'diamond'];
  85. const furnaceLevels = ['stone', 'bronze', 'iron', 'silver', 'gold'];
  86. const furnaceCapacity = [10, 30, 75, 150, 300];
  87. const ovenLevels = ['bronze', 'iron', 'silver', 'gold'];
  88. const maxOilStorageLevel = 3; // 7
  89. const oilStorageSize = [10e3, 50e3, 100e3];
  90.  
  91.  
  92.  
  93. /**
  94. * general functions
  95. */
  96.  
  97. let styleElement = null;
  98. function addStyle(styleCode)
  99. {
  100. if (styleElement === null)
  101. {
  102. styleElement = document.createElement('style');
  103. document.head.appendChild(styleElement);
  104. }
  105. styleElement.innerHTML += styleCode;
  106. }
  107. function getBoundKey(key)
  108. {
  109. return 'bound' + key[0].toUpperCase() + key.substr(1);
  110. }
  111. function getTierKey(key, tierLevel)
  112. {
  113. return tierLevels[tierLevel] + key[0].toUpperCase() + key.substr(1);
  114. }
  115. function formatNumber(num)
  116. {
  117. return parseFloat(num).toLocaleString('en');
  118. }
  119. function formatNumbersInText(text)
  120. {
  121. return text.replace(/\d(?:[\d',\.]*\d)?/g, (numStr) =>
  122. {
  123. return formatNumber(parseInt(numStr.replace(/\D/g, ''), 10));
  124. });
  125. }
  126. function now()
  127. {
  128. return (new Date()).getTime();
  129. }
  130. function padLeft(num, padChar)
  131. {
  132. return (num < 10 ? padChar : '') + num;
  133. }
  134. function formatTimer(timer)
  135. {
  136. timer = parseInt(timer, 10);
  137. const hours = Math.floor(timer / 3600);
  138. const minutes = Math.floor((timer % 3600) / 60);
  139. const seconds = timer % 60;
  140. return padLeft(hours, '0') + ':' + padLeft(minutes, '0') + ':' + padLeft(seconds, '0');
  141. }
  142.  
  143.  
  144.  
  145. /**
  146. * hide crafting recipes of lower tiers or of maxed machines
  147. */
  148.  
  149. function hideTierRecipes(max, getKey, init)
  150. {
  151. const keys2Observe = [];
  152. let maxLevel = 0;
  153. for (let i = max-1; i >= 0; i--)
  154. {
  155. const level = i+1;
  156. const key = getKey(i);
  157. const boundKey = getBoundKey(key);
  158. keys2Observe.push(key);
  159. keys2Observe.push(boundKey);
  160. if (window[key] > 0 || window[boundKey] > 0)
  161. {
  162. maxLevel = Math.max(maxLevel, level);
  163. }
  164.  
  165. const recipeRow = document.getElementById('crafting-' + key);
  166. if (recipeRow)
  167. {
  168. const hide = level <= maxLevel;
  169. recipeRow.style.display = hide ? 'none' : '';
  170. }
  171. }
  172.  
  173. if (init)
  174. {
  175. observe(keys2Observe, () => hideTierRecipes(max, getKey, false));
  176. }
  177. }
  178. function hideRecipe(key, max, init)
  179. {
  180. const maxValue = typeof max === 'function' ? max() : max;
  181. const boundKey = getBoundKey(key);
  182. const unbound = parseInt(window[key], 10);
  183. const bound = parseInt(window[boundKey], 10);
  184.  
  185. const recipeRow = document.getElementById('crafting-' + key);
  186. if (recipeRow)
  187. {
  188. const hide = (bound + unbound) >= maxValue;
  189. recipeRow.style.display = hide ? 'none' : '';
  190. }
  191.  
  192. if (init)
  193. {
  194. observe([key, boundKey], () => hideRecipe(key, max, false));
  195. }
  196. }
  197. function hideCraftedRecipes()
  198. {
  199. function processRecipes(init)
  200. {
  201. // furnace
  202. hideTierRecipes(
  203. furnaceLevels.length
  204. , i => furnaceLevels[i] + 'Furnace'
  205. , init
  206. );
  207. // oil storage
  208. hideTierRecipes(
  209. 7
  210. , i => 'oilStorage' + (i+1)
  211. , init
  212. );
  213. // oven recipes
  214. hideTierRecipes(
  215. ovenLevels.length
  216. , i => ovenLevels[i] + 'Oven'
  217. , init
  218. );
  219. // drills
  220. hideRecipe('drills', 10, init);
  221. // crushers
  222. hideRecipe('crushers', 10, init);
  223. // oil pipe
  224. hideRecipe('oilPipe', 1, init);
  225. // row boat
  226. hideRecipe('rowBoat', 1, init);
  227. }
  228. processRecipes(true);
  229.  
  230. const oldProcessCraftingTab = window.processCraftingTab;
  231. window.processCraftingTab = () =>
  232. {
  233. const reinit = !!window.refreshLoadCraftingTable;
  234. oldProcessCraftingTab();
  235.  
  236. if (reinit)
  237. {
  238. processRecipes(true);
  239. }
  240. };
  241. }
  242.  
  243.  
  244.  
  245. /**
  246. * improve item boxes
  247. */
  248.  
  249. function hideNumberInItemBox(key, setVisibility)
  250. {
  251. const itemBox = document.getElementById('item-box-' + key);
  252. const numberElement = itemBox.lastElementChild;
  253. if (setVisibility)
  254. {
  255. numberElement.style.visibility = 'hidden';
  256. }
  257. else
  258. {
  259. numberElement.style.display = 'none';
  260. }
  261. }
  262. function addSpan2ItemBox(key)
  263. {
  264. hideNumberInItemBox(key);
  265.  
  266. const itemBox = document.getElementById('item-box-' + key);
  267. const span = document.createElement('span');
  268. itemBox.appendChild(span);
  269. return span;
  270. }
  271. function setOilPerSecond(span, oil)
  272. {
  273. span.innerHTML = `+ ${formatNumber(oil)} L/s <img src="images/oil.png" class="image-icon-20" style="margin-top: -2px;">`;
  274. }
  275. function improveItemBoxes()
  276. {
  277. // show capacity of furnace
  278. for (let i = 0; i < furnaceLevels.length; i++)
  279. {
  280. const key = furnaceLevels[i] + 'Furnace';
  281. const capacitySpan = addSpan2ItemBox(getBoundKey(key));
  282. capacitySpan.className = 'capacity';
  283. capacitySpan.textContent = 'Capacity: ' + formatNumber(furnaceCapacity[i]);
  284. }
  285.  
  286. // show oil cap of oil storage
  287. for (let i = 0; i < maxOilStorageLevel; i++)
  288. {
  289. const key = 'oilStorage' + (i+1);
  290. const capSpan = addSpan2ItemBox(getBoundKey(key));
  291. capSpan.className = 'oil-cap';
  292. capSpan.textContent = 'Oil cap: ' + formatNumber(oilStorageSize[i]);
  293. }
  294.  
  295. // show oil per second
  296. const handheldOilSpan = addSpan2ItemBox('handheldOilPump');
  297. setOilPerSecond(handheldOilSpan, 1*window.miner);
  298. observe('miner', () => setOilPerSecond(handheldOilSpan, 1*window.miner));
  299. const oilPipeSpan = addSpan2ItemBox('boundOilPipe');
  300. setOilPerSecond(oilPipeSpan, 50);
  301.  
  302. // show current tier
  303. hideNumberInItemBox('emptyAnvil', true);
  304. hideNumberInItemBox('farmer', true);
  305. hideNumberInItemBox('planter', true);
  306. const tierItemList = ['pickaxe', 'shovel', 'hammer', 'axe', 'rake', 'fishingRod'];
  307. for (let tierItem of tierItemList)
  308. {
  309. for (let i = 0; i < tierLevels.length; i++)
  310. {
  311. const key = getTierKey(tierItem, i);
  312. const tierSpan = addSpan2ItemBox(tierItem == 'rake' ? key : getBoundKey(key));
  313. tierSpan.className = 'tier';
  314. tierSpan.textContent = 'Tier: ' + i;
  315. }
  316. }
  317.  
  318. // show boat progress
  319. function setTransitText(span, isInTransit)
  320. {
  321. span.textContent = isInTransit ? 'In transit' : 'Ready';
  322. }
  323. const boatSpan = addSpan2ItemBox('boundRowBoat');
  324. setTransitText(boatSpan, window.rowBoatTimer > 0);
  325. observe('rowBoatTimer', () => setTransitText(boatSpan, window.rowBoatTimer > 0));
  326. const canoeSpan = addSpan2ItemBox('boundCanoe');
  327. setTransitText(canoeSpan, window.canoeTimer > 0);
  328. observe('canoeTimer', () => setTransitText(canoeSpan, window.canoeTimer > 0));
  329. }
  330.  
  331.  
  332.  
  333. /**
  334. * fix wood cutting
  335. */
  336.  
  337. function fixWoodcutting()
  338. {
  339. addStyle(`
  340. img.woodcutting-tree-img
  341. {
  342. border: 1px solid transparent;
  343. }
  344. `);
  345. }
  346.  
  347.  
  348.  
  349. /**
  350. * fix chat
  351. */
  352.  
  353. const lastMsg = new Map();
  354. function isMuted(user)
  355. {
  356. // return window.mutedPeople.some((name) => user.indexOf(name) > -1);
  357. return window.mutedPeople.includes(user);
  358. }
  359. function isSpam(user, msg)
  360. {
  361. return lastMsg.has(user) &&
  362. lastMsg.get(user).msg == msg &&
  363. // last message in the last 30 seconds?
  364. (now() - lastMsg.get(user).time) < 30e3;
  365. }
  366. function handleSpam(user, msg)
  367. {
  368. const msgObj = lastMsg.get(user);
  369. msgObj.time = now();
  370. msgObj.repeat++;
  371. // a user is allowed to repeat a message twice (write it 3 times in total)
  372. if (msgObj.repeat > 1)
  373. {
  374. window.mutedPeople.push(user);
  375. }
  376. }
  377. function fixChat()
  378. {
  379. const oldAddToChatBox = window.addToChatBox;
  380. window.addToChatBox = (userChatting, iconSet, tagSet, msg, isPM) =>
  381. {
  382. if (isMuted(userChatting))
  383. {
  384. return;
  385. }
  386. if (isSpam(userChatting, msg))
  387. {
  388. return handleSpam(userChatting, msg);
  389. }
  390. lastMsg.set(userChatting, {
  391. time: now()
  392. , msg: msg
  393. , repeat: 0
  394. });
  395.  
  396. // add clickable links
  397. msg = msg.replace(/(https?:\/\/[^\s]+)/g, '<a target="_blank" href="$1">$1</a>');
  398.  
  399. oldAddToChatBox(userChatting, iconSet, tagSet, msg, isPM);
  400. };
  401.  
  402. // add checkbox instead of button for toggling auto scrolling
  403. const btn = document.querySelector('input[value="Toggle Autoscroll"]');
  404. const checkboxId = 'chat-toggle-autoscroll';
  405. // create checkbox
  406. const toggleCheckbox = document.createElement('input');
  407. toggleCheckbox.type = 'checkbox';
  408. toggleCheckbox.id = checkboxId;
  409. toggleCheckbox.checked = true;
  410. // create label
  411. const toggleLabel = document.createElement('label');
  412. toggleLabel.htmlFor = checkboxId;
  413. toggleLabel.textContent = 'Autoscroll';
  414. btn.parentNode.insertBefore(toggleCheckbox, btn);
  415. btn.parentNode.insertBefore(toggleLabel, btn);
  416. btn.style.display = 'none';
  417.  
  418. toggleCheckbox.addEventListener('change', function ()
  419. {
  420. window.isAutoScrolling = this.checked;
  421. window.scrollText('none', this.checked ? 'lime' : 'red', (this.checked ? 'En' : 'Dis') + 'abled');
  422. });
  423. }
  424.  
  425.  
  426.  
  427. /**
  428. * hopefully only temporary fixes
  429. */
  430.  
  431. function temporaryFixes()
  432. {
  433. // fix recipe of oil storage 3
  434. const oldProcessCraftingTab = window.processCraftingTab;
  435. window.processCraftingTab = () =>
  436. {
  437. const reinit = !!window.refreshLoadCraftingTable;
  438. oldProcessCraftingTab();
  439.  
  440. if (reinit)
  441. {
  442. // 200 instead of 100 gold bars
  443. window.craftingRecipes['oilStorage3'].recipeCost[2] = 200;
  444. document.getElementById('recipe-cost-oilStorage3-2').textContent = 200;
  445. window.showMateriesNeededAndLevelLabels('oilStorage3');
  446. }
  447. };
  448.  
  449. // fix burn rate of ovens
  450. window.getOvenBurnRate = () =>
  451. {
  452. if (boundBronzeOven == 1)
  453. {
  454. return .5;
  455. }
  456. else if (boundIronOven == 1)
  457. {
  458. return .4;
  459. }
  460. else if (boundSilverOven == 1)
  461. {
  462. return .3;
  463. }
  464. else if (boundGoldOven == 1)
  465. {
  466. return .2;
  467. }
  468. return 1;
  469. };
  470.  
  471. // fix grow time of some seeds
  472. const seeds = {
  473. 'limeLeafSeeds': '1 hour and 30 minutes'
  474. , 'greenLeafSeeds': '45 minutes'
  475. };
  476. for (let seedName in seeds)
  477. {
  478. const tooltip = document.getElementById('tooltip-' + seedName);
  479. tooltip.lastElementChild.lastChild.textContent = seeds[seedName];
  480. }
  481.  
  482. // fix exhaustion timer
  483. const oldClientGameLoop = window.clientGameLoop;
  484. window.clientGameLoop = () =>
  485. {
  486. oldClientGameLoop();
  487. if (document.getElementById('tab-container-combat').style.display != 'none')
  488. {
  489. combatNotFightingTick();
  490. }
  491. };
  492.  
  493. // fix scrollText (e.g. when joining the game and receiving xp at that moment)
  494. window.mouseX = window.innerWidth / 2;
  495. window.mouseY = window.innerHeight / 2;
  496. }
  497.  
  498.  
  499.  
  500. /**
  501. * improve timer
  502. */
  503.  
  504. function improveTimer()
  505. {
  506. window.formatTime = (seconds) =>
  507. {
  508. return formatTimer(seconds);
  509. };
  510. window.formatTimeShort2 = (seconds) =>
  511. {
  512. return formatTimer(seconds);
  513. };
  514.  
  515. const barInfo = {
  516. 1: {
  517. name: 'bronze'
  518. , timePerBar: 1
  519. }
  520. , 2: {
  521. name: 'iron'
  522. , timePerBar: 5
  523. }
  524. , 3: {
  525. name: 'silver'
  526. , timePerBar: 10
  527. }
  528. , 4: {
  529. name: 'gold'
  530. , timePerBar: 30
  531. }
  532. , 5: {
  533. name: 'glass'
  534. , timePerBar: 1
  535. }
  536. };
  537. const smeltingPercEl = document.querySelector('span[data-item-display="smeltingPerc"]');
  538. const smeltingTimerEl = document.createElement('span');
  539. smeltingPercEl.style.display = 'none';
  540. smeltingPercEl.parentNode.lastElementChild.style.display = 'none';
  541. smeltingPercEl.parentNode.appendChild(smeltingTimerEl);
  542. let interval = null;
  543. let barName = '';
  544. let remainingTime = 0;
  545. function setRemainingTime()
  546. {
  547. smeltingTimerEl.textContent = formatTimer(Math.max(remainingTime, 0));
  548. }
  549. function updateSmeltingPerc()
  550. {
  551. const info = barInfo[window.smeltingBarType];
  552. if (info)
  553. {
  554. const totalTime = info.timePerBar * window.smeltingTotalAmount;
  555. const time = Math.round(totalTime * (1 - window.smeltingPerc / 100));
  556. if (interval == null || barName != info.name)
  557. {
  558. barName = info.name;
  559. remainingTime = time;
  560. interval = window.setInterval(() =>
  561. {
  562. remainingTime--;
  563. setRemainingTime();
  564. }, 1000);
  565. setRemainingTime();
  566. }
  567. // tolerate up to 2 seconds deviation (to make it smoother for the user)
  568. else if (Math.abs(remainingTime - time) > 2)
  569. {
  570. remainingTime = time;
  571. setRemainingTime();
  572. }
  573. }
  574. else if (interval)
  575. {
  576. window.clearInterval(interval);
  577. barName = '';
  578. interval = null;
  579. }
  580. }
  581. observe('smeltingPerc', () => updateSmeltingPerc());
  582. updateSmeltingPerc();
  583.  
  584. // add tree grow timer
  585. const treeInfo = {
  586. 1: {
  587. name: 'Normal tree'
  588. // 3h = 10800s
  589. , growTime: 3 * 60 * 60
  590. }
  591. , 2: {
  592. name: 'Oak tree'
  593. // 6h = 21600s
  594. , growTime: 6 * 60 * 60
  595. }
  596. , 3: {
  597. name: 'Willow tree'
  598. // 8h = 28800s
  599. , growTime: 8 * 60 * 60
  600. }
  601. };
  602. function updateTreeInfo(place, nameEl, timerEl, init)
  603. {
  604. const idKey = 'treeId' + place;
  605. const growTimerKey = 'treeGrowTimer' + place;
  606. const lockedKey = 'treeUnlocked' + place;
  607.  
  608. const info = treeInfo[window[idKey]];
  609. if (!info)
  610. {
  611. const isLocked = place > 4 && window[lockedKey] == 0;
  612. nameEl.textContent = isLocked ? 'Locked' : 'Nothing';
  613. timerEl.textContent = '';
  614. }
  615. else
  616. {
  617. nameEl.textContent = info.name;
  618. const remainingTime = info.growTime - parseInt(window[growTimerKey], 10);
  619. timerEl.textContent = remainingTime > 0 ? '(' + formatTimer(remainingTime) + ')' : 'Fully grown';
  620. }
  621.  
  622. if (init)
  623. {
  624. observe(
  625. [idKey, growTimerKey, lockedKey]
  626. , () => updateTreeInfo(place, nameEl, timerEl, false)
  627. );
  628. }
  629. }
  630. for (let i = 0; i < 6; i++)
  631. {
  632. const treePlace = i+1;
  633. const treeContainer = document.getElementById('wc-div-tree-' + treePlace);
  634. treeContainer.style.position = 'relative';
  635. const infoEl = document.createElement('div');
  636. infoEl.setAttribute('style', 'position: absolute; top: 0; left: 0; right: 0; pointer-events: none; margin-top: 5px; color: white;');
  637. const treeName = document.createElement('div');
  638. treeName.style.fontSize = '1.2rem';
  639. infoEl.appendChild(treeName);
  640. const treeTimer = document.createElement('div');
  641. infoEl.appendChild(treeTimer);
  642. treeContainer.appendChild(infoEl);
  643.  
  644. updateTreeInfo(treePlace, treeName, treeTimer, true);
  645. }
  646. }
  647.  
  648.  
  649.  
  650. /**
  651. * improve smelting dialog
  652. */
  653.  
  654. const smeltingRequirements = {
  655. 'glass': {
  656. sand: 1
  657. , oil: 10
  658. }
  659. , 'bronzeBar': {
  660. copper: 1
  661. , tin: 1
  662. , oil: 10
  663. }
  664. , 'ironBar': {
  665. iron: 1
  666. , oil: 100
  667. }
  668. , 'silverBar': {
  669. silver: 1
  670. , oil: 300
  671. }
  672. , 'goldBar': {
  673. gold: 1
  674. , oil: 1e3
  675. }
  676. };
  677. function improveSmelting()
  678. {
  679. const amountInput = document.getElementById('input-smelt-bars-amount');
  680. amountInput.type = 'number';
  681. amountInput.min = 0;
  682. amountInput.step = 5;
  683. function onValueChange(event)
  684. {
  685. smeltingValue = null;
  686. window.selectBar('', '', amountInput, document.getElementById('smelting-furnace-capacity').value);
  687. }
  688. amountInput.addEventListener('mouseup', onValueChange);
  689. amountInput.addEventListener('keyup', onValueChange);
  690. amountInput.setAttribute('onkeyup', '');
  691.  
  692. const oldSelectBar = window.selectBar;
  693. let smeltingValue = null;
  694. window.selectBar = (bar, inputElement, inputBarsAmountEl, capacity) =>
  695. {
  696. const requirements = smeltingRequirements[bar];
  697. let maxAmount = capacity;
  698. for (let key in requirements)
  699. {
  700. maxAmount = Math.min(Math.floor(window[key] / requirements[key]), maxAmount);
  701. }
  702. if (amountInput.value > maxAmount)
  703. {
  704. smeltingValue = amountInput.value;
  705. amountInput.value = maxAmount;
  706. }
  707. else if (smeltingValue != null)
  708. {
  709. amountInput.value = Math.min(smeltingValue, maxAmount);
  710. if (smeltingValue <= maxAmount)
  711. {
  712. smeltingValue = null;
  713. }
  714. }
  715. return oldSelectBar(bar, inputElement, inputBarsAmountEl, capacity);
  716. };
  717.  
  718. const oldOpenFurnaceDialogue = window.openFurnaceDialogue;
  719. window.openFurnaceDialogue = (furnace) =>
  720. {
  721. if (smeltingBarType == 0)
  722. {
  723. amountInput.max = getFurnaceCapacity(furnace);
  724. }
  725. return oldOpenFurnaceDialogue(furnace);
  726. };
  727. }
  728.  
  729.  
  730.  
  731. /**
  732. * init
  733. */
  734.  
  735. function init()
  736. {
  737. temporaryFixes();
  738.  
  739. hideCraftedRecipes();
  740. improveItemBoxes();
  741. fixWoodcutting();
  742. fixChat();
  743. improveTimer();
  744. improveSmelting();
  745. }
  746. document.addEventListener('DOMContentLoaded', () =>
  747. {
  748. const oldDoCommand = window.doCommand;
  749. window.doCommand = (data) =>
  750. {
  751. if (data.startsWith('REFRESH_ITEMS='))
  752. {
  753. const itemDataValues = data.split('=')[1].split(';');
  754. const itemArray = [];
  755. for (var i = 0; i < itemDataValues.length; i++)
  756. {
  757. const [key, newValue] = itemDataValues[i].split('~');
  758. if (updateValue(key, newValue))
  759. {
  760. itemArray.push(key);
  761. }
  762. }
  763.  
  764. window.refreshItemValues(itemArray, false);
  765.  
  766. if (window.firstLoadGame)
  767. {
  768. window.loadInitial();
  769. window.firstLoadGame = false;
  770. init();
  771. }
  772. else
  773. {
  774. window.clientGameLoop();
  775. }
  776. return;
  777. }
  778. return oldDoCommand(data);
  779. };
  780. });
  781.  
  782.  
  783.  
  784. /**
  785. * fix web socket errors
  786. */
  787.  
  788. function webSocketLoaded(event)
  789. {
  790. if (window.webSocket == null)
  791. {
  792. console.error('no webSocket instance found!');
  793. return;
  794. }
  795.  
  796. const messageQueue = [];
  797. const oldOnMessage = webSocket.onmessage;
  798. webSocket.onmessage = (event) => messageQueue.push(event);
  799. document.addEventListener('DOMContentLoaded', () =>
  800. {
  801. messageQueue.forEach(event => onMessage(event));
  802. webSocket.onmessage = oldOnMessage;
  803. });
  804.  
  805. const commandQueue = [];
  806. const oldSendBytes = window.sendBytes;
  807. window.sendBytes = (command) => commandQueue.push(command);
  808. const oldOnOpen = webSocket.onopen;
  809. webSocket.onopen = (event) =>
  810. {
  811. window.sendBytes = oldSendBytes;
  812. commandQueue.forEach(command => window.sendBytes(command));
  813. return oldOnOpen(event);
  814. };
  815. }
  816. function isWebSocketScript(script)
  817. {
  818. return script.src.includes('socket.js');
  819. }
  820. function fixWebSocketScript()
  821. {
  822. if (!document.head)
  823. {
  824. return;
  825. }
  826.  
  827. const scripts = document.head.querySelectorAll('script');
  828. let found = false;
  829. for (let i = 0; i < scripts.length; i++)
  830. {
  831. if (isWebSocketScript(scripts[i]))
  832. {
  833. // does this work?
  834. scripts[i].onload = webSocketLoaded;
  835. return;
  836. }
  837. }
  838.  
  839. // create an observer instance
  840. const mutationObserver = new MutationObserver((mutationList) =>
  841. {
  842. mutationList.forEach((mutation) =>
  843. {
  844. if (mutation.addedNodes.length === 0)
  845. {
  846. return;
  847. }
  848.  
  849. for (let i = 0; i < mutation.addedNodes.length; i++)
  850. {
  851. const node = mutation.addedNodes[i];
  852. if (node.tagName == 'SCRIPT' && isWebSocketScript(node))
  853. {
  854. mutationObserver.disconnect();
  855. node.onload = webSocketLoaded;
  856. return;
  857. }
  858. }
  859. });
  860. });
  861. mutationObserver.observe(document.head, {
  862. childList: true
  863. });
  864. }
  865. fixWebSocketScript();
  866. })();