DH2 Fixed

Improve Diamond Hunt 2

当前为 2017-03-02 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name DH2 Fixed
  3. // @namespace FileFace
  4. // @description Improve Diamond Hunt 2
  5. // @version 0.41.0
  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 tierNames = ['Standard', 'Sapphire', 'Emerald', 'Ruby', 'Diamond'];
  86. const tierItemList = ['pickaxe', 'shovel', 'hammer', 'axe', 'rake', 'fishingRod'];
  87. const furnaceLevels = ['stone', 'bronze', 'iron', 'silver', 'gold'];
  88. const furnaceCapacity = [10, 30, 75, 150, 300];
  89. const ovenLevels = ['bronze', 'iron', 'silver', 'gold'];
  90. const maxOilStorageLevel = 4; // 7
  91. const oilStorageSize = [10e3, 50e3, 100e3, 300e3];
  92.  
  93.  
  94.  
  95. /**
  96. * general functions
  97. */
  98.  
  99. let styleElement = null;
  100. function addStyle(styleCode)
  101. {
  102. if (styleElement === null)
  103. {
  104. styleElement = document.createElement('style');
  105. document.head.appendChild(styleElement);
  106. }
  107. styleElement.innerHTML += styleCode;
  108. }
  109. function getBoundKey(key)
  110. {
  111. return 'bound' + key[0].toUpperCase() + key.substr(1);
  112. }
  113. function getTierKey(key, tierLevel)
  114. {
  115. return tierLevels[tierLevel] + key[0].toUpperCase() + key.substr(1);
  116. }
  117. function formatNumber(num)
  118. {
  119. return parseFloat(num).toLocaleString('en');
  120. }
  121. function formatNumbersInText(text)
  122. {
  123. return text.replace(/\d(?:[\d',\.]*\d)?/g, (numStr) =>
  124. {
  125. return formatNumber(parseInt(numStr.replace(/\D/g, ''), 10));
  126. });
  127. }
  128. function now()
  129. {
  130. return (new Date()).getTime();
  131. }
  132. function padLeft(num, padChar)
  133. {
  134. return (num < 10 ? padChar : '') + num;
  135. }
  136. function formatTimer(timer)
  137. {
  138. timer = parseInt(timer, 10);
  139. const hours = Math.floor(timer / 3600);
  140. const minutes = Math.floor((timer % 3600) / 60);
  141. const seconds = timer % 60;
  142. return padLeft(hours, '0') + ':' + padLeft(minutes, '0') + ':' + padLeft(seconds, '0');
  143. }
  144. const timeSteps = [
  145. {
  146. threshold: 1
  147. , name: 'second'
  148. , short: 'sec'
  149. , padp: 0
  150. }
  151. , {
  152. threshold: 60
  153. , name: 'minute'
  154. , short: 'min'
  155. , padp: 0
  156. }
  157. , {
  158. threshold: 3600
  159. , name: 'hour'
  160. , short: 'h'
  161. , padp: 1
  162. }
  163. , {
  164. threshold: 86400
  165. , name: 'day'
  166. , short: 'd'
  167. , padp: 2
  168. }
  169. ];
  170. function formatTime2NearestUnit(time, long = false)
  171. {
  172. let step = timeSteps[0];
  173. for (let i = timeSteps.length-1; i > 0; i--)
  174. {
  175. if (time >= timeSteps[i].threshold)
  176. {
  177. step = timeSteps[i];
  178. break;
  179. }
  180. }
  181. const factor = Math.pow(10, step.padp);
  182. const num = Math.round(time / step.threshold * factor) / factor;
  183. const unit = long ? step.name + (num === 1 ? '' : 's') : step.short;
  184. return num + ' ' + unit;
  185. }
  186.  
  187.  
  188.  
  189. /**
  190. * hide crafting recipes of lower tiers or of maxed machines
  191. */
  192.  
  193. function setRecipeVisibility(key, visible)
  194. {
  195. const recipeRow = document.getElementById('crafting-' + key);
  196. if (recipeRow)
  197. {
  198. recipeRow.style.display = visible ? '' : 'none';
  199. }
  200. }
  201. function hideLeveledRecipes(max, getKey, init)
  202. {
  203. const keys2Observe = [];
  204. let maxLevel = 0;
  205. for (let i = max-1; i >= 0; i--)
  206. {
  207. const level = i+1;
  208. const key = getKey(i);
  209. const boundKey = getBoundKey(key);
  210. keys2Observe.push(key);
  211. keys2Observe.push(boundKey);
  212. if (window[key] > 0 || window[boundKey] > 0)
  213. {
  214. maxLevel = Math.max(maxLevel, level);
  215. }
  216.  
  217. setRecipeVisibility(key, level > maxLevel);
  218. }
  219.  
  220. if (init)
  221. {
  222. observe(keys2Observe, () => hideLeveledRecipes(max, getKey, false));
  223. }
  224. }
  225. function hideToolRecipe(key, init)
  226. {
  227. const emptyKey = getTierKey(key, 0);
  228. const keys2Observe = [emptyKey];
  229. let hasTool = window[emptyKey] > 0;
  230. for (let i = 0; i < tierLevels.length; i++)
  231. {
  232. const boundKey = getBoundKey(getTierKey(key, i));
  233. hasTool = hasTool || window[boundKey] > 0;
  234. keys2Observe.push(boundKey);
  235. }
  236.  
  237. setRecipeVisibility(emptyKey, !hasTool);
  238.  
  239. if (init)
  240. {
  241. observe(keys2Observe, () => hideToolRecipe(key, false));
  242. }
  243. }
  244. function hideRecipe(key, max, init)
  245. {
  246. const maxValue = typeof max === 'function' ? max() : max;
  247. const boundKey = getBoundKey(key);
  248. const unbound = parseInt(window[key], 10);
  249. const bound = parseInt(window[boundKey], 10);
  250.  
  251. setRecipeVisibility(key, (bound + unbound) < maxValue);
  252.  
  253. if (init)
  254. {
  255. observe([key, boundKey], () => hideRecipe(key, max, false));
  256. }
  257. }
  258. function hideCraftedRecipes()
  259. {
  260. function processRecipes(init)
  261. {
  262. // furnace
  263. hideLeveledRecipes(
  264. furnaceLevels.length
  265. , i => furnaceLevels[i] + 'Furnace'
  266. , init
  267. );
  268. // oil storage
  269. hideLeveledRecipes(
  270. 7
  271. , i => 'oilStorage' + (i+1)
  272. , init
  273. );
  274. // oven recipes
  275. hideLeveledRecipes(
  276. ovenLevels.length
  277. , i => ovenLevels[i] + 'Oven'
  278. , init
  279. );
  280. // tools
  281. hideToolRecipe('axe', init);
  282. hideToolRecipe('hammer', init);
  283. hideToolRecipe('shovel', init);
  284. hideToolRecipe('pickaxe', init);
  285. hideToolRecipe('fishingRod', init);
  286. // drills
  287. hideRecipe('drills', 10, init);
  288. // crushers
  289. hideRecipe('crushers', 10, init);
  290. // oil pipe
  291. hideRecipe('oilPipe', 1, init);
  292. // boats
  293. hideRecipe('rowBoat', 1, init);
  294. hideRecipe('canoe', 1, init);
  295. }
  296. processRecipes(true);
  297.  
  298. const _processCraftingTab = window.processCraftingTab;
  299. window.processCraftingTab = () =>
  300. {
  301. const reinit = !!window.refreshLoadCraftingTable;
  302. _processCraftingTab();
  303.  
  304. if (reinit)
  305. {
  306. processRecipes(true);
  307. }
  308. };
  309. }
  310.  
  311.  
  312.  
  313. /**
  314. * improve item boxes
  315. */
  316.  
  317. function hideNumberInItemBox(key, setVisibility)
  318. {
  319. const itemBox = document.getElementById('item-box-' + key);
  320. const numberElement = itemBox.lastElementChild;
  321. if (setVisibility)
  322. {
  323. numberElement.style.visibility = 'hidden';
  324. }
  325. else
  326. {
  327. numberElement.style.display = 'none';
  328. }
  329. }
  330. function addSpan2ItemBox(key)
  331. {
  332. hideNumberInItemBox(key);
  333.  
  334. const itemBox = document.getElementById('item-box-' + key);
  335. const span = document.createElement('span');
  336. itemBox.appendChild(span);
  337. return span;
  338. }
  339. function setOilPerSecond(span, oil)
  340. {
  341. span.innerHTML = `+ ${formatNumber(oil)} L/s <img src="images/oil.png" class="image-icon-20" style="margin-top: -2px;">`;
  342. }
  343. function improveItemBoxes()
  344. {
  345. // show capacity of furnace
  346. for (let i = 0; i < furnaceLevels.length; i++)
  347. {
  348. const key = furnaceLevels[i] + 'Furnace';
  349. const capacitySpan = addSpan2ItemBox(getBoundKey(key));
  350. capacitySpan.className = 'capacity';
  351. capacitySpan.textContent = 'Capacity: ' + formatNumber(furnaceCapacity[i]);
  352. }
  353.  
  354. // show oil cap of oil storage
  355. for (let i = 0; i < maxOilStorageLevel; i++)
  356. {
  357. const key = 'oilStorage' + (i+1);
  358. const capSpan = addSpan2ItemBox(getBoundKey(key));
  359. capSpan.className = 'oil-cap';
  360. capSpan.textContent = 'Oil cap: ' + formatNumber(oilStorageSize[i]);
  361. }
  362.  
  363. // show oil per second
  364. const handheldOilSpan = addSpan2ItemBox('handheldOilPump');
  365. setOilPerSecond(handheldOilSpan, 1*window.miner);
  366. observe('miner', () => setOilPerSecond(handheldOilSpan, 1*window.miner));
  367. const oilPipeSpan = addSpan2ItemBox('boundOilPipe');
  368. setOilPerSecond(oilPipeSpan, 50);
  369.  
  370. // show current tier
  371. hideNumberInItemBox('emptyAnvil', true);
  372. hideNumberInItemBox('farmer', true);
  373. hideNumberInItemBox('planter', true);
  374. for (let tierItem of tierItemList)
  375. {
  376. for (let i = 0; i < tierLevels.length; i++)
  377. {
  378. const key = getTierKey(tierItem, i);
  379. const toolKey = tierItem == 'rake' ? key : getBoundKey(key);
  380. const tierSpan = addSpan2ItemBox(toolKey);
  381. tierSpan.className = 'tier';
  382. tierSpan.textContent = tierNames[i];
  383. }
  384. }
  385.  
  386. // show boat progress
  387. function setTransitText(span, isInTransit)
  388. {
  389. span.textContent = isInTransit ? 'In transit' : 'Ready';
  390. }
  391. const boatSpan = addSpan2ItemBox('boundRowBoat');
  392. setTransitText(boatSpan, window.rowBoatTimer > 0);
  393. observe('rowBoatTimer', () => setTransitText(boatSpan, window.rowBoatTimer > 0));
  394. const canoeSpan = addSpan2ItemBox('boundCanoe');
  395. setTransitText(canoeSpan, window.canoeTimer > 0);
  396. observe('canoeTimer', () => setTransitText(canoeSpan, window.canoeTimer > 0));
  397. }
  398.  
  399.  
  400.  
  401. /**
  402. * fix wood cutting
  403. */
  404.  
  405. function fixWoodcutting()
  406. {
  407. addStyle(`
  408. img.woodcutting-tree-img
  409. {
  410. border: 1px solid transparent;
  411. }
  412. `);
  413. }
  414.  
  415.  
  416.  
  417. /**
  418. * fix chat
  419. */
  420.  
  421. const lastMsg = new Map();
  422. function isMuted(user)
  423. {
  424. // return window.mutedPeople.some((name) => user.indexOf(name) > -1);
  425. return window.mutedPeople.includes(user);
  426. }
  427. function isSpam(user, msg)
  428. {
  429. return lastMsg.has(user) &&
  430. lastMsg.get(user).msg == msg &&
  431. // last message in the last 30 seconds?
  432. (now() - lastMsg.get(user).time) < 30e3;
  433. }
  434. function handleSpam(user, msg)
  435. {
  436. const msgObj = lastMsg.get(user);
  437. msgObj.time = now();
  438. msgObj.repeat++;
  439. // a user is allowed to repeat a message twice (write it 3 times in total)
  440. if (msgObj.repeat > 1)
  441. {
  442. window.mutedPeople.push(user);
  443. }
  444. }
  445. function fixChat()
  446. {
  447. const _addToChatBox = window.addToChatBox;
  448. window.addToChatBox = (userChatting, iconSet, tagSet, msg, isPM) =>
  449. {
  450. if (isMuted(userChatting))
  451. {
  452. return;
  453. }
  454. if (isSpam(userChatting, msg))
  455. {
  456. return handleSpam(userChatting, msg);
  457. }
  458. lastMsg.set(userChatting, {
  459. time: now()
  460. , msg: msg
  461. , repeat: 0
  462. });
  463.  
  464. // add clickable links
  465. msg = msg.replace(/(https?:\/\/[^\s"<>]+)/g, '<a target="_blank" href="$1">$1</a>');
  466.  
  467. _addToChatBox(userChatting, iconSet, tagSet, msg, isPM);
  468. };
  469.  
  470. // add checkbox instead of button for toggling auto scrolling
  471. const btn = document.querySelector('input[value="Toggle Autoscroll"]');
  472. const checkboxId = 'chat-toggle-autoscroll';
  473. // create checkbox
  474. const toggleCheckbox = document.createElement('input');
  475. toggleCheckbox.type = 'checkbox';
  476. toggleCheckbox.id = checkboxId;
  477. toggleCheckbox.checked = true;
  478. // create label
  479. const toggleLabel = document.createElement('label');
  480. toggleLabel.htmlFor = checkboxId;
  481. toggleLabel.textContent = 'Autoscroll';
  482. btn.parentNode.insertBefore(toggleCheckbox, btn);
  483. btn.parentNode.insertBefore(toggleLabel, btn);
  484. btn.style.display = 'none';
  485.  
  486. toggleCheckbox.addEventListener('change', function ()
  487. {
  488. window.isAutoScrolling = this.checked;
  489. window.scrollText('none', this.checked ? 'lime' : 'red', (this.checked ? 'En' : 'Dis') + 'abled');
  490. });
  491. }
  492.  
  493.  
  494.  
  495. /**
  496. * hopefully only temporary fixes
  497. */
  498.  
  499. function temporaryFixes()
  500. {
  501. // fix recipe of oil storage 3
  502. const _processCraftingTab = window.processCraftingTab;
  503. window.processCraftingTab = () =>
  504. {
  505. const reinit = !!window.refreshLoadCraftingTable;
  506. _processCraftingTab();
  507.  
  508. if (reinit)
  509. {
  510. // 200 instead of 100 gold bars
  511. window.craftingRecipes['oilStorage3'].recipeCost[2] = 200;
  512. document.getElementById('recipe-cost-oilStorage3-2').textContent = 200;
  513. window.showMateriesNeededAndLevelLabels('oilStorage3');
  514. }
  515. };
  516.  
  517. // fix burn rate of ovens
  518. window.getOvenBurnRate = () =>
  519. {
  520. if (boundBronzeOven == 1)
  521. {
  522. return .5;
  523. }
  524. else if (boundIronOven == 1)
  525. {
  526. return .4;
  527. }
  528. else if (boundSilverOven == 1)
  529. {
  530. return .3;
  531. }
  532. else if (boundGoldOven == 1)
  533. {
  534. return .2;
  535. }
  536. return 1;
  537. };
  538.  
  539. // fix grow time of some seeds
  540. const seeds = {
  541. 'limeLeafSeeds': '1 hour and 30 minutes'
  542. , 'greenLeafSeeds': '45 minutes'
  543. };
  544. for (let seedName in seeds)
  545. {
  546. const tooltip = document.getElementById('tooltip-' + seedName);
  547. tooltip.lastElementChild.lastChild.textContent = seeds[seedName];
  548. }
  549.  
  550. // fix exhaustion timer and updating brewing recipes
  551. const _clientGameLoop = window.clientGameLoop;
  552. window.clientGameLoop = () =>
  553. {
  554. _clientGameLoop();
  555. if (document.getElementById('tab-container-combat').style.display != 'none')
  556. {
  557. window.combatNotFightingTick();
  558. }
  559. if (currentOpenTab == 'brewing')
  560. {
  561. window.processBrewingTab();
  562. }
  563. };
  564.  
  565. // fix elements of scrollText (e.g. when joining the game and receiving xp at that moment)
  566. const textEls = document.querySelectorAll('div.scroller');
  567. for (let i = 0; i < textEls.length; i++)
  568. {
  569. const scroller = textEls[i];
  570. if (scroller.style.position != 'absolute')
  571. {
  572. scroller.style.display = 'none';
  573. }
  574. }
  575.  
  576. // fix style of tooltips
  577. addStyle(`
  578. body > div.tooltip > h2:first-child
  579. {
  580. margin-top: 0;
  581. font-size: 20pt;
  582. font-weight: normal;
  583. }
  584. `);
  585. }
  586.  
  587.  
  588.  
  589. /**
  590. * improve timer
  591. */
  592.  
  593. function improveTimer()
  594. {
  595. window.formatTime = (seconds) =>
  596. {
  597. return formatTimer(seconds);
  598. };
  599. window.formatTimeShort2 = (seconds) =>
  600. {
  601. return formatTimer(seconds);
  602. };
  603.  
  604. addStyle(`
  605. #notif-smelting > span:not(.timer)
  606. {
  607. display: none;
  608. }
  609. `);
  610. const smeltingNotifBox = document.getElementById('notif-smelting');
  611. const smeltingTimerEl = document.createElement('span');
  612. smeltingTimerEl.className = 'timer';
  613. smeltingNotifBox.appendChild(smeltingTimerEl);
  614. function updateSmeltingTimer()
  615. {
  616. const totalTime = parseInt(window.smeltingPercD, 10);
  617. const elapsedTime = parseInt(window.smeltingPercN, 10);
  618. smeltingTimerEl.textContent = formatTimer(Math.max(totalTime - elapsedTime, 0));
  619. }
  620. observe('smeltingPercD', () => updateSmeltingTimer());
  621. observe('smeltingPercN', () => updateSmeltingTimer());
  622. updateSmeltingTimer();
  623.  
  624. // add tree grow timer
  625. const treeInfo = {
  626. 1: {
  627. name: 'Normal tree'
  628. // 3h = 10800s
  629. , growTime: 3 * 60 * 60
  630. }
  631. , 2: {
  632. name: 'Oak tree'
  633. // 6h = 21600s
  634. , growTime: 6 * 60 * 60
  635. }
  636. , 3: {
  637. name: 'Willow tree'
  638. // 8h = 28800s
  639. , growTime: 8 * 60 * 60
  640. }
  641. };
  642. function updateTreeInfo(place, nameEl, timerEl, init)
  643. {
  644. const idKey = 'treeId' + place;
  645. const growTimerKey = 'treeGrowTimer' + place;
  646. const lockedKey = 'treeUnlocked' + place;
  647.  
  648. const info = treeInfo[window[idKey]];
  649. if (!info)
  650. {
  651. const isLocked = place > 4 && window[lockedKey] == 0;
  652. nameEl.textContent = isLocked ? 'Locked' : 'Empty';
  653. timerEl.textContent = '';
  654. }
  655. else
  656. {
  657. nameEl.textContent = info.name;
  658. const remainingTime = info.growTime - parseInt(window[growTimerKey], 10);
  659. timerEl.textContent = remainingTime > 0 ? '(' + formatTimer(remainingTime) + ')' : 'Fully grown';
  660. }
  661.  
  662. if (init)
  663. {
  664. observe(
  665. [idKey, growTimerKey, lockedKey]
  666. , () => updateTreeInfo(place, nameEl, timerEl, false)
  667. );
  668. }
  669. }
  670. for (let i = 0; i < 6; i++)
  671. {
  672. const treePlace = i+1;
  673. const treeContainer = document.getElementById('wc-div-tree-' + treePlace);
  674. treeContainer.style.position = 'relative';
  675. const infoEl = document.createElement('div');
  676. infoEl.setAttribute('style', 'position: absolute; top: 0; left: 0; right: 0; pointer-events: none; margin-top: 5px; color: white;');
  677. const treeName = document.createElement('div');
  678. treeName.style.fontSize = '1.2rem';
  679. infoEl.appendChild(treeName);
  680. const treeTimer = document.createElement('div');
  681. infoEl.appendChild(treeTimer);
  682. treeContainer.appendChild(infoEl);
  683.  
  684. updateTreeInfo(treePlace, treeName, treeTimer, true);
  685. }
  686. }
  687.  
  688.  
  689.  
  690. /**
  691. * improve smelting dialog
  692. */
  693.  
  694. const smeltingRequirements = {
  695. 'glass': {
  696. sand: 1
  697. , oil: 10
  698. }
  699. , 'bronzeBar': {
  700. copper: 1
  701. , tin: 1
  702. , oil: 10
  703. }
  704. , 'ironBar': {
  705. iron: 1
  706. , oil: 100
  707. }
  708. , 'silverBar': {
  709. silver: 1
  710. , oil: 300
  711. }
  712. , 'goldBar': {
  713. gold: 1
  714. , oil: 1e3
  715. }
  716. };
  717. function improveSmelting()
  718. {
  719. const amountInput = document.getElementById('input-smelt-bars-amount');
  720. amountInput.type = 'number';
  721. amountInput.min = 0;
  722. amountInput.step = 5;
  723. function onValueChange(event)
  724. {
  725. smeltingValue = null;
  726. window.selectBar('', '', amountInput, document.getElementById('smelting-furnace-capacity').value);
  727. }
  728. amountInput.addEventListener('mouseup', onValueChange);
  729. amountInput.addEventListener('keyup', onValueChange);
  730. amountInput.setAttribute('onkeyup', '');
  731.  
  732. const _selectBar = window.selectBar;
  733. let smeltingValue = null;
  734. window.selectBar = (bar, inputElement, inputBarsAmountEl, capacity) =>
  735. {
  736. const requirements = smeltingRequirements[bar];
  737. let maxAmount = capacity;
  738. for (let key in requirements)
  739. {
  740. maxAmount = Math.min(Math.floor(window[key] / requirements[key]), maxAmount);
  741. }
  742. const value = parseInt(amountInput.value, 10);
  743. if (value > maxAmount)
  744. {
  745. smeltingValue = value;
  746. amountInput.value = maxAmount;
  747. }
  748. else if (smeltingValue != null)
  749. {
  750. amountInput.value = Math.min(smeltingValue, maxAmount);
  751. if (smeltingValue <= maxAmount)
  752. {
  753. smeltingValue = null;
  754. }
  755. }
  756. return _selectBar(bar, inputElement, inputBarsAmountEl, capacity);
  757. };
  758.  
  759. const _openFurnaceDialogue = window.openFurnaceDialogue;
  760. window.openFurnaceDialogue = (furnace) =>
  761. {
  762. if (smeltingBarType == 0)
  763. {
  764. amountInput.max = getFurnaceCapacity(furnace);
  765. }
  766. return _openFurnaceDialogue(furnace);
  767. };
  768. }
  769.  
  770.  
  771.  
  772. /**
  773. * add chance to time calculator
  774. */
  775.  
  776. /**
  777. * calculates the number of seconds until the event with the given chance happened at least once with the given
  778. * probability p (in percent)
  779. */
  780. function calcSecondsTillP(chancePerSecond, p)
  781. {
  782. return Math.round(Math.log(1 - p/100) / Math.log(1 - chancePerSecond));
  783. }
  784. function addChanceTooltip(headline, chancePerSecond, elId, targetEl)
  785. {
  786. // ensure element existence
  787. const tooltipElId = 'tooltip-chance-' + elId;
  788. let tooltipEl = document.getElementById(tooltipElId);
  789. if (!tooltipEl)
  790. {
  791. tooltipEl = document.createElement('div');
  792. tooltipEl.id = tooltipElId;
  793. tooltipEl.style.display = 'none';
  794. document.getElementById('tooltip-list').appendChild(tooltipEl);
  795. }
  796.  
  797. // set elements content
  798. const percValues = [1, 10, 20, 50, 80, 90, 99];
  799. let percRows = '';
  800. for (let p of percValues)
  801. {
  802. percRows += `
  803. <tr>
  804. <td>${p}%</td>
  805. <td>${formatTime2NearestUnit(calcSecondsTillP(chancePerSecond, p), true)}</td>
  806. </tr>`;
  807. }
  808. tooltipEl.innerHTML = `<h2>${headline}</h2>
  809. <table class="chance">
  810. <tr>
  811. <th>Probability</th>
  812. <th>Time</th>
  813. </tr>
  814. ${percRows}
  815. </table>
  816. `;
  817.  
  818. // ensure binded events to show the tooltip
  819. if (targetEl.dataset.tooltipId == null)
  820. {
  821. targetEl.setAttribute('data-tooltip-id', tooltipElId);
  822. window.$(targetEl).bind({
  823. mousemove: window.changeTooltipPosition
  824. , mouseenter: window.showTooltip
  825. , mouseleave: window.hideTooltip
  826. });
  827. }
  828. }
  829. function chance2TimeCalculator()
  830. {
  831. addStyle(`
  832. table.chance
  833. {
  834. border-spacing: 0;
  835. }
  836. table.chance th
  837. {
  838. border-bottom: 1px solid gray;
  839. }
  840. table.chance td:first-child
  841. {
  842. border-right: 1px solid gray;
  843. text-align: center;
  844. }
  845. table.chance th,
  846. table.chance td
  847. {
  848. padding: 4px 8px;
  849. }
  850. table.chance tr:nth-child(2n) td
  851. {
  852. background-color: white;
  853. }
  854. `);
  855.  
  856. const _clicksShovel = window.clicksShovel;
  857. window.clicksShovel = () =>
  858. {
  859. _clicksShovel();
  860.  
  861. const shovelChance = document.getElementById('dialogue-shovel-chance');
  862. const titleEl = shovelChance.parentElement;
  863. const chance = 1/window.getChanceOfDiggingSand();
  864. addChanceTooltip('One sand every:', chance, 'shovel', titleEl);
  865. };
  866.  
  867. // depends on fishingXp
  868. const _clicksFishingRod = window.clicksFishingRod;
  869. window.clicksFishingRod = () =>
  870. {
  871. _clicksFishingRod();
  872.  
  873. const fishList = ['shrimp', 'sardine', 'tuna', 'swordfish', 'shark'];
  874. for (let fish of fishList)
  875. {
  876. const rawFish = 'raw' + fish[0].toUpperCase() + fish.substr(1);
  877. const row = document.getElementById('dialogue-fishing-rod-tr-' + rawFish);
  878. const chance = row.cells[4].textContent
  879. .replace(/[^\d\/]/g, '')
  880. .split('/')
  881. .reduce((p, c) => p / parseInt(c, 10), 1)
  882. ;
  883. addChanceTooltip(`One raw ${fish} every:`, chance, rawFish, row);
  884. }
  885. };
  886. }
  887.  
  888.  
  889.  
  890. /**
  891. * add notification boxes
  892. */
  893.  
  894. function addNotifBox(id, icon)
  895. {
  896. const notificationArea = document.getElementById('notifaction-area');
  897. const notifBox = document.createElement('span');
  898. notifBox.className = 'notif-box';
  899. notifBox.id = 'notif-' + id;
  900. notifBox.innerHTML = `<img src="images/${icon}" class="image-icon-50" id="notif-${id}-img">`;
  901. notificationArea.appendChild(notifBox);
  902. return notifBox;
  903. }
  904. function addClickableNotifBox(id, icon, tabName)
  905. {
  906. const notifBox = addNotifBox(id, icon);
  907. notifBox.style.cursor = 'pointer';
  908. notifBox.addEventListener('click', () => window.openTab(tabName));
  909. return notifBox;
  910. }
  911. function showStageNotification(stagePrefix, notifBox, init)
  912. {
  913. const keys2Observe = [];
  914. let show = false;
  915. for (let i = 1; i <= 6; i++)
  916. {
  917. const key = stagePrefix + 'Stage' + i;
  918. keys2Observe.push(key);
  919. show = show || window[key] == 4;
  920. }
  921. notifBox.style.display = show ? '' : 'none';
  922.  
  923. if (init)
  924. {
  925. observe(keys2Observe, () => showStageNotification(stagePrefix, notifBox, false));
  926. }
  927. }
  928. function addNotificationBoxes()
  929. {
  930. // tree / wood cutting notification
  931. const treeNotifBox = addClickableNotifBox('woodCutter', 'icons/woodcutting.png', 'woodcutting');
  932. treeNotifBox.title = 'There is some wood to chop';
  933. window.$(treeNotifBox).tooltip();
  934. showStageNotification('tree', treeNotifBox, true);
  935.  
  936. // farming notification
  937. const harvestNotifBox = addClickableNotifBox('farming', 'icons/watering-can.png', 'farming');
  938. harvestNotifBox.title = 'Some plants are ready for harvest';
  939. window.$(harvestNotifBox).tooltip();
  940. showStageNotification('farmingPatch', harvestNotifBox, true);
  941.  
  942. // combat cooldown timer
  943. const combatNotifBox = addNotifBox('combatCooldown', 'icons/combat.png');
  944. // const combatNotifBox = addNotifBox('combatCooldown', 'icons/hourglass.png');
  945. const combatTimer = document.createElement('span');
  946. combatNotifBox.appendChild(combatTimer);
  947. function updateCombatTimer()
  948. {
  949. const cooldown = parseInt(window.combatGlobalCooldown, 10);
  950. const show = cooldown > 0;
  951. combatNotifBox.style.display = show ? '' : 'none';
  952. combatTimer.textContent = formatTimer(cooldown);
  953. }
  954. observe('combatGlobalCooldown', () => updateCombatTimer());
  955. updateCombatTimer();
  956. }
  957.  
  958.  
  959.  
  960. /**
  961. * add tooltips for recipes
  962. */
  963.  
  964. function updateRecipeTooltips(recipeKey, recipes)
  965. {
  966. const table = document.getElementById('table-' + recipeKey + '-recipe');
  967. const rows = table.rows;
  968. for (let i = 1; i < rows.length; i++)
  969. {
  970. const row = rows[i];
  971. const key = row.id.replace(recipeKey + '-', '');
  972. const recipe = recipes[key];
  973. const requirementCell = row.cells[3];
  974. requirementCell.title = recipe.recipe
  975. .map((name, i) =>
  976. {
  977. return formatNumber(recipe.recipeCost[i]) + ' '
  978. + name.replace(/[A-Z]/g, (match) => ' ' + match.toLowerCase())
  979. ;
  980. })
  981. .join(' + ')
  982. ;
  983. window.$(requirementCell).tooltip();
  984. }
  985. }
  986. function addRecipeTooltips()
  987. {
  988. const _processCraftingTab = window.processCraftingTab;
  989. window.processCraftingTab = () =>
  990. {
  991. const reinit = !!window.refreshLoadCraftingTable;
  992. _processCraftingTab();
  993.  
  994. if (reinit)
  995. {
  996. updateRecipeTooltips('crafting', window.craftingRecipes);
  997. }
  998. };
  999.  
  1000. const _processBrewingTab = window.processBrewingTab;
  1001. window.processBrewingTab = () =>
  1002. {
  1003. const reinit = !!window.refreshLoadBrewingTable;
  1004. _processBrewingTab();
  1005.  
  1006. if (reinit)
  1007. {
  1008. updateRecipeTooltips('brewing', window.brewingRecipes);
  1009. }
  1010. }
  1011.  
  1012. const _processMagicTab = window.processMagicTab;
  1013. window.processMagicTab = () =>
  1014. {
  1015. const reinit = !!window.refreshLoadCraftingTable;
  1016. _processMagicTab();
  1017.  
  1018. if (reinit)
  1019. {
  1020. updateRecipeTooltips('magic', window.magicRecipes);
  1021. }
  1022. }
  1023. }
  1024.  
  1025.  
  1026.  
  1027. /**
  1028. * fix formatting of numbers
  1029. */
  1030.  
  1031. function prepareRecipeForTable(recipe)
  1032. {
  1033. // create a copy of the recipe to prevent requirement check from failing
  1034. const newRecipe = JSON.parse(JSON.stringify(recipe));
  1035. newRecipe.recipeCost = recipe.recipeCost.map(cost => formatNumber(cost));
  1036. newRecipe.xp = formatNumber(recipe.xp);
  1037. return newRecipe;
  1038. }
  1039. function fixNumberFormat()
  1040. {
  1041. const _addRecipeToBrewingTable = window.addRecipeToBrewingTable;
  1042. window.addRecipeToBrewingTable = (brewingRecipe) =>
  1043. {
  1044. _addRecipeToBrewingTable(prepareRecipeForTable(brewingRecipe));
  1045. };
  1046.  
  1047. const _addRecipeToMagicTable = window.addRecipeToMagicTable;
  1048. window.addRecipeToMagicTable = (magicRecipe) =>
  1049. {
  1050. _addRecipeToMagicTable(prepareRecipeForTable(magicRecipe));
  1051. };
  1052. }
  1053.  
  1054.  
  1055.  
  1056. /**
  1057. * style tweaks
  1058. */
  1059.  
  1060. function addTweakStyle(setting, style)
  1061. {
  1062. const prefix = 'body.' + setting;
  1063. addStyle(
  1064. style
  1065. .replace(/(^\s*|,\s*|\}\s*)([^\{\},]+)(,|\s*\{)/g, '$1' + prefix + ' $2$3')
  1066. );
  1067. document.body.classList.add(setting);
  1068. }
  1069. function tweakStyle()
  1070. {
  1071. // tweak oil production/consumption
  1072. addTweakStyle('tweak-oil', `
  1073. span#oil-flow-values
  1074. {
  1075. position: relative;
  1076. margin-left: .5em;
  1077. }
  1078. #oil-flow-values > span
  1079. {
  1080. font-size: 0px;
  1081. position: absolute;
  1082. top: -0.75rem;
  1083. visibility: hidden;
  1084. }
  1085. #oil-flow-values > span > span
  1086. {
  1087. font-size: 1rem;
  1088. visibility: visible;
  1089. }
  1090. #oil-flow-values > span:last-child
  1091. {
  1092. top: 0.75rem;
  1093. }
  1094. #oil-flow-values span[data-item-display="oilIn"]::before
  1095. {
  1096. content: '+';
  1097. }
  1098. #oil-flow-values span[data-item-display="oilOut"]::before
  1099. {
  1100. content: '-';
  1101. }
  1102. `);
  1103.  
  1104. addTweakStyle('no-select', `
  1105. table.tab-bar,
  1106. span.item-box,
  1107. div.farming-patch,
  1108. div.farming-patch-locked,
  1109. div.tab-sub-container-combat,
  1110. table.top-links a
  1111. {
  1112. -webkit-user-select: none;
  1113. -moz-user-select: none;
  1114. -ms-user-select: none;
  1115. user-select: none;
  1116. }
  1117. `);
  1118. }
  1119.  
  1120.  
  1121.  
  1122. /**
  1123. * init
  1124. */
  1125.  
  1126. function init()
  1127. {
  1128. temporaryFixes();
  1129.  
  1130. hideCraftedRecipes();
  1131. improveItemBoxes();
  1132. fixWoodcutting();
  1133. fixChat();
  1134. improveTimer();
  1135. improveSmelting();
  1136. chance2TimeCalculator();
  1137. addNotificationBoxes();
  1138. addRecipeTooltips();
  1139.  
  1140. fixNumberFormat();
  1141. tweakStyle();
  1142. }
  1143. document.addEventListener('DOMContentLoaded', () =>
  1144. {
  1145. const _doCommand = window.doCommand;
  1146. window.doCommand = (data) =>
  1147. {
  1148. if (data.startsWith('REFRESH_ITEMS='))
  1149. {
  1150. const itemDataValues = data.split('=')[1].split(';');
  1151. const itemArray = [];
  1152. for (var i = 0; i < itemDataValues.length; i++)
  1153. {
  1154. const [key, newValue] = itemDataValues[i].split('~');
  1155. if (updateValue(key, newValue))
  1156. {
  1157. itemArray.push(key);
  1158. }
  1159. }
  1160.  
  1161. window.refreshItemValues(itemArray, false);
  1162.  
  1163. if (window.firstLoadGame)
  1164. {
  1165. window.loadInitial();
  1166. window.firstLoadGame = false;
  1167. init();
  1168. }
  1169. else
  1170. {
  1171. window.clientGameLoop();
  1172. }
  1173. return;
  1174. }
  1175. return _doCommand(data);
  1176. };
  1177. });
  1178.  
  1179.  
  1180.  
  1181. /**
  1182. * fix web socket errors
  1183. */
  1184.  
  1185. function webSocketLoaded(event)
  1186. {
  1187. if (window.webSocket == null)
  1188. {
  1189. console.error('no webSocket instance found!');
  1190. return;
  1191. }
  1192.  
  1193. const messageQueue = [];
  1194. const _onMessage = webSocket.onmessage;
  1195. webSocket.onmessage = (event) => messageQueue.push(event);
  1196. document.addEventListener('DOMContentLoaded', () =>
  1197. {
  1198. messageQueue.forEach(event => onMessage(event));
  1199. webSocket.onmessage = _onMessage;
  1200. });
  1201.  
  1202. const commandQueue = [];
  1203. const _sendBytes = window.sendBytes;
  1204. window.sendBytes = (command) => commandQueue.push(command);
  1205. const _onOpen = webSocket.onopen;
  1206. webSocket.onopen = (event) =>
  1207. {
  1208. window.sendBytes = _sendBytes;
  1209. commandQueue.forEach(command => window.sendBytes(command));
  1210. return _onOpen(event);
  1211. };
  1212. }
  1213. function isWebSocketScript(script)
  1214. {
  1215. return script.src.includes('socket.js');
  1216. }
  1217. function fixWebSocketScript()
  1218. {
  1219. if (!document.head)
  1220. {
  1221. return;
  1222. }
  1223.  
  1224. const scripts = document.head.querySelectorAll('script');
  1225. let found = false;
  1226. for (let i = 0; i < scripts.length; i++)
  1227. {
  1228. if (isWebSocketScript(scripts[i]))
  1229. {
  1230. // does this work?
  1231. scripts[i].onload = webSocketLoaded;
  1232. return;
  1233. }
  1234. }
  1235.  
  1236. // create an observer instance
  1237. const mutationObserver = new MutationObserver((mutationList) =>
  1238. {
  1239. mutationList.forEach((mutation) =>
  1240. {
  1241. if (mutation.addedNodes.length === 0)
  1242. {
  1243. return;
  1244. }
  1245.  
  1246. for (let i = 0; i < mutation.addedNodes.length; i++)
  1247. {
  1248. const node = mutation.addedNodes[i];
  1249. if (node.tagName == 'SCRIPT' && isWebSocketScript(node))
  1250. {
  1251. mutationObserver.disconnect();
  1252. node.onload = webSocketLoaded;
  1253. return;
  1254. }
  1255. }
  1256. });
  1257. });
  1258. mutationObserver.observe(document.head, {
  1259. childList: true
  1260. });
  1261. }
  1262. fixWebSocketScript();
  1263.  
  1264. // fix scrollText (e.g. when joining the game and receiving xp at that moment)
  1265. window.mouseX = window.innerWidth / 2;
  1266. window.mouseY = window.innerHeight / 2;
  1267. })();