DH2 Fixed

Improve Diamond Hunt 2

目前为 2017-03-04 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name DH2 Fixed
  3. // @namespace FileFace
  4. // @description Improve Diamond Hunt 2
  5. // @version 0.51.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 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. // use time format established in DHQoL (https://greasyfork.org/scripts/16041-dhqol)
  137. function formatTimer(timer)
  138. {
  139. timer = parseInt(timer, 10);
  140. const hours = Math.floor(timer / 3600);
  141. const minutes = Math.floor((timer % 3600) / 60);
  142. const seconds = timer % 60;
  143. return padLeft(hours, '0') + ':' + padLeft(minutes, '0') + ':' + padLeft(seconds, '0');
  144. }
  145. const timeSteps = [
  146. {
  147. threshold: 1
  148. , name: 'second'
  149. , short: 'sec'
  150. , padp: 0
  151. }
  152. , {
  153. threshold: 60
  154. , name: 'minute'
  155. , short: 'min'
  156. , padp: 0
  157. }
  158. , {
  159. threshold: 3600
  160. , name: 'hour'
  161. , short: 'h'
  162. , padp: 1
  163. }
  164. , {
  165. threshold: 86400
  166. , name: 'day'
  167. , short: 'd'
  168. , padp: 2
  169. }
  170. ];
  171. function formatTime2NearestUnit(time, long = false)
  172. {
  173. let step = timeSteps[0];
  174. for (let i = timeSteps.length-1; i > 0; i--)
  175. {
  176. if (time >= timeSteps[i].threshold)
  177. {
  178. step = timeSteps[i];
  179. break;
  180. }
  181. }
  182. const factor = Math.pow(10, step.padp);
  183. const num = Math.round(time / step.threshold * factor) / factor;
  184. const unit = long ? step.name + (num === 1 ? '' : 's') : step.short;
  185. return num + ' ' + unit;
  186. }
  187. const storePrefix = 'dh2-';
  188. const store = {
  189. get: (key) =>
  190. {
  191. const value = localStorage.getItem(storePrefix + key);
  192. try
  193. {
  194. return JSON.parse(value);
  195. }
  196. catch (e) {}
  197. return value;
  198. }
  199. , has: (key) =>
  200. {
  201. return localStorage.hasOwnProperty(storePrefix + key);
  202. }
  203. , persist: (key, value) =>
  204. {
  205. localStorage.setItem(storePrefix + key, JSON.stringify(value));
  206. }
  207. , remove: (key) =>
  208. {
  209. localStorage.removeItem(storePrefix + key);
  210. }
  211. };
  212.  
  213.  
  214.  
  215. /**
  216. * hide crafting recipes of lower tiers or of maxed machines
  217. */
  218.  
  219. function setRecipeVisibility(key, visible)
  220. {
  221. const recipeRow = document.getElementById('crafting-' + key);
  222. if (recipeRow)
  223. {
  224. recipeRow.style.display = visible ? '' : 'none';
  225. }
  226. }
  227. function hideLeveledRecipes(max, getKey, init)
  228. {
  229. const keys2Observe = [];
  230. let maxLevel = 0;
  231. for (let i = max-1; i >= 0; i--)
  232. {
  233. const level = i+1;
  234. const key = getKey(i);
  235. const boundKey = getBoundKey(key);
  236. keys2Observe.push(key);
  237. keys2Observe.push(boundKey);
  238. if (window[key] > 0 || window[boundKey] > 0)
  239. {
  240. maxLevel = Math.max(maxLevel, level);
  241. }
  242.  
  243. setRecipeVisibility(key, level > maxLevel);
  244. }
  245.  
  246. if (init)
  247. {
  248. observe(keys2Observe, () => hideLeveledRecipes(max, getKey, false));
  249. }
  250. }
  251. function hideToolRecipe(key, init)
  252. {
  253. const emptyKey = getTierKey(key, 0);
  254. const keys2Observe = [emptyKey];
  255. let hasTool = window[emptyKey] > 0;
  256. for (let i = 0; i < tierLevels.length; i++)
  257. {
  258. const boundKey = getBoundKey(getTierKey(key, i));
  259. hasTool = hasTool || window[boundKey] > 0;
  260. keys2Observe.push(boundKey);
  261. }
  262.  
  263. setRecipeVisibility(emptyKey, !hasTool);
  264.  
  265. if (init)
  266. {
  267. observe(keys2Observe, () => hideToolRecipe(key, false));
  268. }
  269. }
  270. function hideRecipe(key, max, init)
  271. {
  272. const maxValue = typeof max === 'function' ? max() : max;
  273. const boundKey = getBoundKey(key);
  274. const unbound = parseInt(window[key], 10);
  275. const bound = parseInt(window[boundKey], 10);
  276.  
  277. setRecipeVisibility(key, (bound + unbound) < maxValue);
  278.  
  279. if (init)
  280. {
  281. observe([key, boundKey], () => hideRecipe(key, max, false));
  282. }
  283. }
  284. function hideCraftedRecipes()
  285. {
  286. function processRecipes(init)
  287. {
  288. // furnace
  289. hideLeveledRecipes(
  290. furnaceLevels.length
  291. , i => furnaceLevels[i] + 'Furnace'
  292. , init
  293. );
  294. // oil storage
  295. hideLeveledRecipes(
  296. 7
  297. , i => 'oilStorage' + (i+1)
  298. , init
  299. );
  300. // oven recipes
  301. hideLeveledRecipes(
  302. ovenLevels.length
  303. , i => ovenLevels[i] + 'Oven'
  304. , init
  305. );
  306. // tools
  307. hideToolRecipe('axe', init);
  308. hideToolRecipe('hammer', init);
  309. hideToolRecipe('shovel', init);
  310. hideToolRecipe('pickaxe', init);
  311. hideToolRecipe('fishingRod', init);
  312. // drills
  313. hideRecipe('drills', 10, init);
  314. // crushers
  315. hideRecipe('crushers', 10, init);
  316. // oil pipe
  317. hideRecipe('oilPipe', 1, init);
  318. // boats
  319. hideRecipe('rowBoat', 1, init);
  320. hideRecipe('canoe', 1, init);
  321. }
  322. processRecipes(true);
  323.  
  324. const _processCraftingTab = window.processCraftingTab;
  325. window.processCraftingTab = () =>
  326. {
  327. const reinit = !!window.refreshLoadCraftingTable;
  328. _processCraftingTab();
  329.  
  330. if (reinit)
  331. {
  332. processRecipes(true);
  333. }
  334. };
  335. }
  336.  
  337.  
  338.  
  339. /**
  340. * improve item boxes
  341. */
  342.  
  343. function hideNumberInItemBox(key, setVisibility)
  344. {
  345. const itemBox = document.getElementById('item-box-' + key);
  346. const numberElement = itemBox.lastElementChild;
  347. if (setVisibility)
  348. {
  349. numberElement.style.visibility = 'hidden';
  350. }
  351. else
  352. {
  353. numberElement.style.display = 'none';
  354. }
  355. }
  356. function addSpan2ItemBox(key)
  357. {
  358. hideNumberInItemBox(key);
  359.  
  360. const itemBox = document.getElementById('item-box-' + key);
  361. const span = document.createElement('span');
  362. itemBox.appendChild(span);
  363. return span;
  364. }
  365. function setOilPerSecond(span, oil)
  366. {
  367. span.innerHTML = `+ ${formatNumber(oil)} L/s <img src="images/oil.png" class="image-icon-20" style="margin-top: -2px;">`;
  368. }
  369. function improveItemBoxes()
  370. {
  371. // show capacity of furnace
  372. for (let i = 0; i < furnaceLevels.length; i++)
  373. {
  374. const key = furnaceLevels[i] + 'Furnace';
  375. const capacitySpan = addSpan2ItemBox(getBoundKey(key));
  376. capacitySpan.className = 'capacity';
  377. capacitySpan.textContent = 'Capacity: ' + formatNumber(furnaceCapacity[i]);
  378. }
  379.  
  380. // show oil cap of oil storage
  381. for (let i = 0; i < maxOilStorageLevel; i++)
  382. {
  383. const key = 'oilStorage' + (i+1);
  384. const capSpan = addSpan2ItemBox(getBoundKey(key));
  385. capSpan.className = 'oil-cap';
  386. capSpan.textContent = 'Oil cap: ' + formatNumber(oilStorageSize[i]);
  387. }
  388.  
  389. // show oil per second
  390. const handheldOilSpan = addSpan2ItemBox('handheldOilPump');
  391. setOilPerSecond(handheldOilSpan, 1*window.miner);
  392. observe('miner', () => setOilPerSecond(handheldOilSpan, 1*window.miner));
  393. const oilPipeSpan = addSpan2ItemBox('boundOilPipe');
  394. setOilPerSecond(oilPipeSpan, 50);
  395.  
  396. // show current tier
  397. hideNumberInItemBox('emptyAnvil', true);
  398. hideNumberInItemBox('farmer', true);
  399. hideNumberInItemBox('planter', true);
  400. hideNumberInItemBox('cooksBook', true);
  401. hideNumberInItemBox('cooksPage', true);
  402. for (let tierItem of tierItemList)
  403. {
  404. for (let i = 0; i < tierLevels.length; i++)
  405. {
  406. const key = getTierKey(tierItem, i);
  407. const toolKey = tierItem == 'rake' ? key : getBoundKey(key);
  408. const tierSpan = addSpan2ItemBox(toolKey);
  409. tierSpan.className = 'tier';
  410. tierSpan.textContent = tierNames[i];
  411. }
  412. }
  413.  
  414. // show boat progress
  415. function setTransitText(span, isInTransit)
  416. {
  417. span.textContent = isInTransit ? 'In transit' : 'Ready';
  418. }
  419. const boatSpan = addSpan2ItemBox('boundRowBoat');
  420. setTransitText(boatSpan, window.rowBoatTimer > 0);
  421. observe('rowBoatTimer', () => setTransitText(boatSpan, window.rowBoatTimer > 0));
  422. const canoeSpan = addSpan2ItemBox('boundCanoe');
  423. setTransitText(canoeSpan, window.canoeTimer > 0);
  424. observe('canoeTimer', () => setTransitText(canoeSpan, window.canoeTimer > 0));
  425. }
  426.  
  427.  
  428.  
  429. /**
  430. * fix wood cutting
  431. */
  432.  
  433. function fixWoodcutting()
  434. {
  435. addStyle(`
  436. img.woodcutting-tree-img
  437. {
  438. border: 1px solid transparent;
  439. }
  440. `);
  441. }
  442.  
  443.  
  444.  
  445. /**
  446. * fix chat
  447. */
  448.  
  449. function isMuted(user)
  450. {
  451. // return window.mutedPeople.some((name) => user.indexOf(name) > -1);
  452. return window.mutedPeople.includes(user);
  453. }
  454. function handleScrolling(chatbox)
  455. {
  456. if (window.isAutoScrolling)
  457. {
  458. setTimeout(() => chatbox.scrollTop = chatbox.scrollHeight);
  459. }
  460. }
  461. const chatHistoryKey = 'chatHistory';
  462. const maxChatHistoryLength = 100;
  463. const TYPE_RELOAD = -1;
  464. const TYPE_NORMAL = 0;
  465. const TYPE_PM_FROM = 1;
  466. const TYPE_PM_TO = 2;
  467. const TYPE_SERVER_MSG = 3;
  468. const reloadedChatData = {
  469. timestamp: 0
  470. , username: ''
  471. , userlevel: 0
  472. , icon: 0
  473. , tag: 0
  474. , type: TYPE_RELOAD
  475. , msg: '[...]'
  476. };
  477. let chatHistory = [];
  478. function processChatData(username, icon, tag, msg, isPM)
  479. {
  480. let userlevel = 0;
  481. let type = tag == 5 ? TYPE_SERVER_MSG : TYPE_NORMAL;
  482. if (isPM == 1)
  483. {
  484. const match = msg.match(/^\s*\[(.+) ([A-Za-z0-9 ]+)\]: (.+?)\s*$/) || ['', '', username, msg];
  485. type = match[1] == 'Sent to' ? TYPE_PM_TO : TYPE_PM_FROM;
  486. username = match[2];
  487. msg = match[3];
  488. }
  489. else if (tag != 5)
  490. {
  491. const match = msg.match(/^\s*\((\d+)\): (.+?)\s*$/);
  492. if (match)
  493. {
  494. userlevel = match[1];
  495. msg = match[2];
  496. }
  497. else
  498. {
  499. userlevel = window.getGlobalLevel();
  500. }
  501. }
  502. const data = {
  503. timestamp: now()
  504. , username: username
  505. , userlevel: userlevel
  506. , icon: icon
  507. , tag: tag
  508. , type: type
  509. , msg: msg
  510. };
  511. return data;
  512. }
  513. function add2ChatHistory(data)
  514. {
  515. chatHistory.push(data);
  516. chatHistory = chatHistory.slice(-maxChatHistoryLength);
  517. store.persist(chatHistoryKey, chatHistory);
  518. }
  519. function getChatTab(username)
  520. {
  521. const chatTabs = document.getElementById('chat-tabs');
  522. let tab = chatTabs.querySelector('div.chat-tab[data-username="' + username + '"]');
  523. if (!tab)
  524. {
  525. tab = document.createElement('div');
  526. tab.className = 'chat-tab';
  527. tab.dataset.username = username;
  528. tab.dataset.new = 0;
  529. const filler = chatTabs.querySelector('.filler');
  530. if (filler)
  531. {
  532. chatTabs.insertBefore(tab, filler);
  533. }
  534. else
  535. {
  536. chatTabs.appendChild(tab);
  537. }
  538. }
  539. return tab;
  540. }
  541. const chatBoxId = 'div-chat';
  542. const generalChatId = 'div-chat-area';
  543. const pmChatPrefix = 'div-chat-pm-';
  544. const msgInputId = 'chat-input-text';
  545. function getChatDiv(username)
  546. {
  547. const id = username == '' ? generalChatId : pmChatPrefix + username;
  548. let div = document.getElementById(id);
  549. if (!div)
  550. {
  551. div = document.createElement('div');
  552. div.setAttribute('disabled', 'disabled');
  553. div.id = id;
  554. div.className = 'div-chat-area';
  555.  
  556. const height = document.getElementById(generalChatId).style.height;
  557. div.style.height = height;
  558.  
  559. const generalChat = document.getElementById(generalChatId);
  560. generalChat.parentNode.insertBefore(div, generalChat);
  561. }
  562. return div;
  563. }
  564. function changeChatTab(oldTab, newTab)
  565. {
  566. const oldChatDiv = getChatDiv(oldTab.dataset.username);
  567. oldChatDiv.classList.remove('selected');
  568. const newChatDiv = getChatDiv(newTab.dataset.username);
  569. newChatDiv.classList.add('selected');
  570.  
  571. const toUsername = newTab.dataset.username;
  572. const newTextPlaceholder = toUsername == '' ? window.username + ':' : 'PM to ' + toUsername + ':';
  573. document.getElementById(msgInputId).placeholder = newTextPlaceholder;
  574.  
  575. if (window.isAutoScrolling)
  576. {
  577. setTimeout(() => newChatDiv.scrollTop = newChatDiv.scrollHeight);
  578. }
  579. }
  580. const chatIcons = [
  581. null
  582. , { key: 'halloween2015', title: 'Halloween 2015' }
  583. , { key: 'christmas2015', title: 'Chirstmas 2015' }
  584. , { key: 'easter2016', title: 'Holiday' }
  585. , { key: 'halloween2016', title: 'Halloween 2016' }
  586. , { key: 'christmas2016', title: 'Chirstmas 2016' }
  587. , { key: 'dh1Max', title: 'Max Level in DH1' }
  588. , { key: 'hardcore', title: 'Hardcore Account' }
  589. , { key: 'quest', title: 'Questmaster' }
  590. ];
  591. const chatTags = [
  592. null
  593. , { key: 'donor', name: '' }
  594. , { key: 'contributor', name: 'Contributor' }
  595. , { key: 'mod', name: 'Moderator' }
  596. , { key: 'dev', name: 'Dev' }
  597. , { key: 'yell', name: 'Server Message' }
  598. ];
  599. function isPM(data)
  600. {
  601. return data.type == TYPE_PM_TO || data.type == TYPE_PM_FROM;
  602. }
  603. const locale = 'en-US';
  604. const localeOptions = {
  605. hour12: false
  606. , year: 'numeric'
  607. , month: 'long'
  608. , day: 'numeric'
  609. , hour: '2-digit'
  610. , minute: '2-digit'
  611. , second: '2-digit'
  612. };
  613. function add2Chat(data)
  614. {
  615. // username is 3-12 characters long
  616. let chatbox = getChatDiv('');
  617.  
  618. const isThisPm = isPM(data);
  619. // don't mute pms (you can just ignore pm-tab if you like)
  620. if (!isThisPm && isMuted(data.username))
  621. {
  622. return;
  623. }
  624.  
  625. const msgUsername = data.type == TYPE_PM_TO ? window.username : data.username;
  626. const historyIndex = chatHistory.indexOf(data);
  627. const historyPart = historyIndex == -1 ? [] : chatHistory.slice(0, historyIndex).reverse();
  628. const msgBeforeUser = historyPart.find(d => isThisPm && isPM(d) || !isThisPm && !isPM(d));
  629. const msgBeforeTime = historyPart.find(d => isThisPm && isPM(d) || !isThisPm && !isPM(d) && d.type != TYPE_RELOAD);
  630. let isSameUser = false;
  631. let isSameTime = false;
  632. if (msgBeforeUser)
  633. {
  634. const beforeUsername = msgBeforeUser.type == TYPE_PM_TO ? window.username : msgBeforeUser.username;
  635. isSameUser = beforeUsername === msgUsername;
  636. }
  637. if (msgBeforeTime)
  638. {
  639. isSameTime = Math.floor(data.timestamp / 1000 / 60) - Math.floor(msgBeforeTime.timestamp / 1000 / 60) === 0;
  640. }
  641.  
  642. const d = new Date(data.timestamp);
  643. const hour = (d.getHours() < 10 ? '0' : '') + d.getHours();
  644. const minute = (d.getMinutes() < 10 ? '0' : '') + d.getMinutes();
  645. const icon = chatIcons[data.icon] || { key: '', title: '' };
  646. const tag = chatTags[data.tag] || { key: '', name: '' };
  647. // thanks aguyd (https://greasyfork.org/forum/profile/aguyd) for the vulnerability warning
  648. const formattedMsg = data.msg.replace(/(https?:\/\/[^\s"<>]+)/g, '<a target="_blank" href="$1">$1</a>');
  649.  
  650. const msgTitle = data.type == TYPE_RELOAD ? 'Chat loaded on ' + d.toLocaleString(locale, localeOptions) : '';
  651. let levelAppendix = data.type == TYPE_NORMAL ? ' (' + data.userlevel + ')' : '';
  652. let chatSegment = `<span class="chat-msg" data-type="${data.type}" data-tag="${tag.key}">`
  653. + `<span
  654. class="timestamp"
  655. title="${d.toLocaleString(locale, localeOptions)}"
  656. data-same-time="${isSameTime}">${hour}:${minute}</span>`
  657. + `<span class="user" data-name="${msgUsername}" data-same-user="${isSameUser}">`
  658. + `<span class="icon ${icon.key}" title="${icon.title}"></span>`
  659. + `<span
  660. class="name chat-tag-${tag.key}"
  661. title="${tag.name}">${msgUsername}${levelAppendix}:</span>`
  662. + `</span>`
  663. + `<span class="msg" title="${msgTitle}">${formattedMsg}</span>`
  664. + `</span>`;
  665.  
  666. const chatTab = getChatTab(isThisPm ? data.username : '');
  667. if (!chatTab.classList.contains('selected'))
  668. {
  669. chatTab.dataset.new = parseInt(chatTab.dataset.new, 10) + 1;
  670. }
  671. if (isThisPm)
  672. {
  673. window.lastPMUser = data.username;
  674. chatbox = getChatDiv(data.username);
  675. }
  676.  
  677. const tmp = document.createElement('templateWrapper');
  678. tmp.innerHTML = chatSegment;
  679. while (tmp.childNodes.length > 0)
  680. {
  681. chatbox.appendChild(tmp.childNodes[0]);
  682. }
  683.  
  684. handleScrolling(chatbox);
  685. }
  686. function applyChatStyle()
  687. {
  688. addStyle(`
  689. span.chat-msg
  690. {
  691. display: flex;
  692. margin-bottom: 1px;
  693. }
  694. span.chat-msg:nth-child(2n)
  695. {
  696. background-color: hsla(0, 0%, 90%, 1);
  697. }
  698. .chat-msg[data-type="${TYPE_RELOAD}"]
  699. {
  700. font-size: 0.8rem;
  701. }
  702. .chat-msg .timestamp
  703. {
  704. display: none;
  705. }
  706. .chat-msg:not([data-type="${TYPE_RELOAD}"]) .timestamp
  707. {
  708. color: hsla(0, 0%, 50%, 1);
  709. display: inline-block;
  710. font-size: .9rem;
  711. margin: 0;
  712. margin-right: 5px;
  713. width: 2.5rem;
  714. }
  715. .chat-msg .timestamp[data-same-time="true"]
  716. {
  717. opacity: .1;
  718. }
  719.  
  720. .chat-msg[data-type="${TYPE_PM_FROM}"] { color: purple; }
  721. .chat-msg[data-type="${TYPE_PM_TO}"] { color: purple; }
  722. .chat-msg[data-type="${TYPE_SERVER_MSG}"] { color: blue; }
  723. .chat-msg[data-tag="contributor"] { color: green; }
  724. .chat-msg[data-tag="mod"] { color: #669999; }
  725. .chat-msg[data-tag="dev"] { color: #666600; }
  726. .chat-msg:not([data-type="${TYPE_RELOAD}"]) .user
  727. {
  728. flex: 0 0 132px;
  729. margin-right: 5px;
  730. white-space: nowrap;
  731. }
  732. #${generalChatId} .chat-msg:not([data-type="${TYPE_RELOAD}"]) .user
  733. {
  734. flex-basis: 182px;
  735. padding-left: 22px;
  736. }
  737. .chat-msg .user[data-same-user="true"]:not([data-name=""])
  738. {
  739. opacity: 0;
  740. }
  741.  
  742. .chat-msg .user .icon
  743. {
  744. margin-left: -22px;
  745. }
  746. .chat-msg .user .icon::before
  747. {
  748. background-size: 20px 20px;
  749. content: '';
  750. display: inline-block;
  751. margin-right: 2px;
  752. width: 20px;
  753. height: 20px;
  754. vertical-align: middle;
  755. }
  756. .chat-msg .user .icon.halloween2015::before { background-image: url('images/chat-icons/1.png'); }
  757. .chat-msg .user .icon.christmas2015::before { background-image: url('images/chat-icons/2.png'); }
  758. .chat-msg .user .icon.easter2016::before { background-image: url('images/chat-icons/3.png'); }
  759. .chat-msg .user .icon.halloween2016::before { background-image: url('images/chat-icons/4.png'); }
  760. .chat-msg .user .icon.christmas2016::before { background-image: url('images/chat-icons/5.png'); }
  761. .chat-msg .user .icon.dh1Max::before { background-image: url('images/chat-icons/6.png'); }
  762. .chat-msg .user .icon.hardcore::before { background-image: url('images/chat-icons/7.png'); }
  763. .chat-msg .user .icon.quest::before { background-image: url('images/chat-icons/8.png'); }
  764.  
  765. .chat-msg .user .name
  766. {
  767. color: rgba(0, 0, 0, 0.7);
  768. cursor: pointer;
  769. }
  770. .chat-msg .user .name.chat-tag-donor::before
  771. {
  772. background-image: url('images/chat-icons/donor.png');
  773. background-size: 20px 20px;
  774. content: '';
  775. display: inline-block;
  776. height: 20px;
  777. width: 20px;
  778. vertical-align: middle;
  779. }
  780. .chat-msg .user .name.chat-tag-yell
  781. {
  782. cursor: pointer;
  783. }
  784. .chat-msg .user .name.chat-tag-yell::before
  785. {
  786. content: 'Server Message';
  787. }
  788. .chat-msg .user .name.chat-tag-contributor,
  789. .chat-msg .user .name.chat-tag-mod,
  790. .chat-msg .user .name.chat-tag-dev,
  791. .chat-msg .user .name.chat-tag-yell
  792. {
  793. color: white;
  794. display: inline-block;
  795. font-size: 10pt;
  796. margin-top: -1px;
  797. padding-bottom: 0;
  798. text-align: center;
  799. /* 2px border, 10 padding */
  800. width: calc(100% - 2*1px - 2*5px);
  801. }
  802.  
  803. .chat-msg[data-type="${TYPE_RELOAD}"] .user > *,
  804. .chat-msg[data-type="${TYPE_PM_FROM}"] .user > .icon,
  805. .chat-msg[data-type="${TYPE_PM_TO}"] .user > .icon
  806. {
  807. display: none;
  808. }
  809.  
  810. .chat-msg .msg
  811. {
  812. word-wrap: break-word;
  813. min-width: 0;
  814. }
  815.  
  816. #div-chat .div-chat-area
  817. {
  818. width: 100%;
  819. height: 130px;
  820. display: none;
  821. }
  822. #div-chat .div-chat-area.selected
  823. {
  824. display: block;
  825. }
  826. #chat-tabs
  827. {
  828. display: flex;
  829. margin: 10px -6px -6px;
  830. flex-wrap: wrap;
  831. }
  832. #chat-tabs .chat-tab
  833. {
  834. background-color: gray;
  835. border-top: 1px solid black;
  836. border-right: 1px solid black;
  837. cursor: pointer;
  838. display: inline-block;
  839. font-weight: normal;
  840. padding: 0.3rem .6rem;
  841. }
  842. #chat-tabs .chat-tab.selected
  843. {
  844. background-color: transparent;
  845. border-top-color: transparent;
  846. }
  847. #chat-tabs .chat-tab.filler
  848. {
  849. background-color: hsla(0, 0%, 90%, 1);
  850. border-right: 0;
  851. box-shadow: inset 5px 5px 5px -5px rgba(0, 0, 0, 0.5);
  852. color: transparent;
  853. cursor: default;
  854. flex-grow: 1;
  855. }
  856. #chat-tabs .chat-tab::before
  857. {
  858. content: attr(data-username);
  859. }
  860. #chat-tabs .chat-tab:not(.filler)[data-username=""]::before
  861. {
  862. content: 'Server';
  863. }
  864. #chat-tabs .chat-tab::after
  865. {
  866. color: white;
  867. content: '(' attr(data-new) ')';
  868. font-size: .9rem;
  869. font-weight: bold;
  870. margin-left: .4rem;
  871. }
  872. #chat-tabs .chat-tab[data-new="0"]::after
  873. {
  874. color: inherit;
  875. font-weight: normal;
  876. }
  877. `);
  878. }
  879. function addIntelligentScrolling()
  880. {
  881. // add checkbox instead of button for toggling auto scrolling
  882. const btn = document.querySelector('input[value="Toggle Autoscroll"]');
  883. const checkboxId = 'chat-toggle-autoscroll';
  884. // create checkbox
  885. const toggleCheckbox = document.createElement('input');
  886. toggleCheckbox.type = 'checkbox';
  887. toggleCheckbox.id = checkboxId;
  888. toggleCheckbox.checked = true;
  889. // create label
  890. const toggleLabel = document.createElement('label');
  891. toggleLabel.htmlFor = checkboxId;
  892. toggleLabel.textContent = 'Autoscroll';
  893. btn.parentNode.insertBefore(toggleCheckbox, btn);
  894. btn.parentNode.insertBefore(toggleLabel, btn);
  895. btn.style.display = 'none';
  896.  
  897. // add checkbox for intelligent scrolling
  898. const isCheckboxId = 'chat-toggle-intelligent-scroll';
  899. const intScrollCheckbox = document.createElement('input');
  900. intScrollCheckbox.type = 'checkbox';
  901. intScrollCheckbox.id = isCheckboxId;
  902. intScrollCheckbox.checked = true;
  903. // add label
  904. const intScrollLabel = document.createElement('label');
  905. intScrollLabel.htmlFor = isCheckboxId;
  906. intScrollLabel.textContent = 'Intelligent Scrolling';
  907. btn.parentNode.appendChild(intScrollCheckbox);
  908. btn.parentNode.appendChild(intScrollLabel);
  909.  
  910. const chatArea = document.getElementById(generalChatId);
  911. function setAutoScrolling(value, full)
  912. {
  913. if (window.isAutoScrolling != value)
  914. {
  915. toggleCheckbox.checked = value;
  916. window.isAutoScrolling = value;
  917. window.scrollText(
  918. 'none'
  919. , value ? 'lime' : 'red'
  920. , (value ? 'En' : 'Dis') + 'abled' + (full ? ' Autoscroll' : '')
  921. );
  922. return true;
  923. }
  924. return false;
  925. }
  926. toggleCheckbox.addEventListener('change', function ()
  927. {
  928. setAutoScrolling(this.checked);
  929. if (this.checked && intScrollCheckbox.checked)
  930. {
  931. chatArea.scrollTop = chatArea.scrollHeight - chatArea.clientHeight;
  932. }
  933. });
  934.  
  935. // does not consider pm tabs; may be changed in a future version?
  936. chatArea.addEventListener('scroll', () =>
  937. {
  938. if (intScrollCheckbox.checked)
  939. {
  940. const scrolled2Bottom = (chatArea.scrollTop + chatArea.clientHeight) >= chatArea.scrollHeight;
  941. setAutoScrolling(scrolled2Bottom, true);
  942. }
  943. });
  944. }
  945. function clickTab(newTab)
  946. {
  947. const oldTab = document.querySelector('#chat-tabs .chat-tab.selected');
  948. if (newTab == oldTab)
  949. {
  950. return;
  951. }
  952. oldTab.classList.remove('selected');
  953. newTab.classList.add('selected');
  954. newTab.dataset.new = 0;
  955.  
  956. changeChatTab(oldTab, newTab);
  957. }
  958. function addChatTabs()
  959. {
  960. const chatBoxArea = document.getElementById(chatBoxId);
  961. const chatTabs = document.createElement('div');
  962. chatTabs.id = 'chat-tabs';
  963. chatTabs.addEventListener('click', (event) =>
  964. {
  965. const newTab = event.target;
  966. if (!newTab.classList.contains('chat-tab') || newTab.classList.contains('filler'))
  967. {
  968. return;
  969. }
  970.  
  971. clickTab(newTab);
  972. });
  973. chatBoxArea.appendChild(chatTabs);
  974.  
  975. const generalTab = getChatTab('');
  976. generalTab.classList.add('selected');
  977. const generalChatDiv = getChatDiv('');
  978. generalChatDiv.classList.add('selected');
  979. // works only if username length of 1 isn't allowed
  980. const fillerTab = getChatTab('f');
  981. fillerTab.classList.add('filler');
  982.  
  983. const _sendChat = window.sendChat;
  984. window.sendChat = (inputEl) =>
  985. {
  986. let msg = inputEl.value;
  987. const selectedTab = document.querySelector('.chat-tab.selected');
  988. if (selectedTab.dataset.username != '' && msg[0] != '/')
  989. {
  990. inputEl.value = '/pm ' + selectedTab.dataset.username + ' ' + msg;
  991. }
  992. _sendChat(inputEl);
  993. };
  994. }
  995. function newAddToChatBox(username, icon, tag, msg, isPM)
  996. {
  997. const data = processChatData(username, icon, tag, msg, isPM);
  998. add2ChatHistory(data);
  999. add2Chat(data);
  1000. }
  1001. function newChat()
  1002. {
  1003. addChatTabs();
  1004. applyChatStyle();
  1005.  
  1006. window.addToChatBox = newAddToChatBox;
  1007.  
  1008. const chatbox = document.getElementById(chatBoxId);
  1009. chatbox.addEventListener('click', (event) =>
  1010. {
  1011. let target = event.target;
  1012. while (target && target.id != chatBoxId && !target.classList.contains('user'))
  1013. {
  1014. target = target.parentElement;
  1015. }
  1016. if (target.id == chatBoxId)
  1017. {
  1018. return;
  1019. }
  1020.  
  1021. const username = target.dataset.name;
  1022. if (username == window.username || username == '')
  1023. {
  1024. return;
  1025. }
  1026.  
  1027. const userTab = getChatTab(username);
  1028. clickTab(userTab);
  1029. document.getElementById(msgInputId).focus();
  1030. });
  1031. }
  1032. function fixChat()
  1033. {
  1034. newChat();
  1035. addIntelligentScrolling();
  1036.  
  1037. const _enlargeChat = window.enlargeChat;
  1038. const chatBoxArea = document.getElementById(chatBoxId);
  1039. function setChatBoxHeight(height)
  1040. {
  1041. document.getElementById(generalChatId).style.height = height;
  1042. const chatDivs = chatBoxArea.querySelectorAll('div[id^="' + pmChatPrefix + '"]');
  1043. for (let i = 0; i < chatDivs.length; i++)
  1044. {
  1045. chatDivs[i].style.height = height;
  1046. }
  1047. }
  1048. window.enlargeChat = (enlargeB) =>
  1049. {
  1050. _enlargeChat(enlargeB);
  1051.  
  1052. const height = document.getElementById(generalChatId).style.height;
  1053. store.persist('chat.height', height);
  1054. setChatBoxHeight(height);
  1055. };
  1056. setChatBoxHeight(store.get('chat.height'));
  1057.  
  1058. // TEMP >>> (due to a naming issue, migrate the data)
  1059. const oldChatHistoryKey = 'chatHistory2';
  1060. const oldChatHistory = store.get(oldChatHistoryKey);
  1061. if (oldChatHistory != null)
  1062. {
  1063. store.persist(chatHistoryKey, oldChatHistory);
  1064. store.remove(oldChatHistoryKey);
  1065. }
  1066. // TEMP <<<
  1067.  
  1068. // load history
  1069. chatHistory = store.get(chatHistoryKey) || chatHistory;
  1070. // find index of last message which is not a pm
  1071. const lastNotPM = chatHistory.slice(0).reverse().find((d) =>
  1072. {
  1073. return !isPM(d);
  1074. });
  1075. // insert a placeholder for a reloaded chat
  1076. if (lastNotPM && lastNotPM.type != TYPE_RELOAD)
  1077. {
  1078. reloadedChatData.timestamp = (new Date()).getTime();
  1079. chatHistory.push(reloadedChatData);
  1080. }
  1081. chatHistory.forEach(d => add2Chat(d));
  1082. // reset the new counter for all tabs
  1083. const tabs = document.querySelectorAll('.chat-tab');
  1084. for (let i = 0; i < tabs.length; i++)
  1085. {
  1086. tabs[i].dataset.new = 0;
  1087. }
  1088. }
  1089.  
  1090.  
  1091.  
  1092. /**
  1093. * hopefully only temporary fixes
  1094. */
  1095.  
  1096. function temporaryFixes()
  1097. {
  1098. // fix grow time of some seeds
  1099. const seeds = {
  1100. 'limeLeafSeeds': '1 hour and 30 minutes'
  1101. };
  1102. for (let seedName in seeds)
  1103. {
  1104. const tooltip = document.getElementById('tooltip-' + seedName);
  1105. tooltip.lastElementChild.lastChild.textContent = seeds[seedName];
  1106. }
  1107.  
  1108. // fix exhaustion timer and updating brewing and cooking recipes
  1109. const _clientGameLoop = window.clientGameLoop;
  1110. window.clientGameLoop = () =>
  1111. {
  1112. _clientGameLoop();
  1113. if (document.getElementById('tab-sub-container-fight').style.display == 'none')
  1114. {
  1115. document.getElementById('combat-countdown').style.display = '';
  1116. }
  1117. if (document.getElementById('tab-container-combat').style.display != 'none')
  1118. {
  1119. window.combatNotFightingTick();
  1120. }
  1121. if (currentOpenTab == 'brewing')
  1122. {
  1123. window.processBrewingTab();
  1124. }
  1125. if (currentOpenTab == 'cooksBook')
  1126. {
  1127. window.processCooksBookTab();
  1128. }
  1129. };
  1130.  
  1131. // fix elements of scrollText (e.g. when joining the game and receiving xp at that moment)
  1132. const textEls = document.querySelectorAll('div.scroller');
  1133. for (let i = 0; i < textEls.length; i++)
  1134. {
  1135. const scroller = textEls[i];
  1136. if (scroller.style.position != 'absolute')
  1137. {
  1138. scroller.style.display = 'none';
  1139. }
  1140. }
  1141.  
  1142. // fix style of tooltips
  1143. addStyle(`
  1144. body > div.tooltip > h2:first-child
  1145. {
  1146. margin-top: 0;
  1147. font-size: 20pt;
  1148. font-weight: normal;
  1149. }
  1150. `);
  1151. }
  1152.  
  1153.  
  1154.  
  1155. /**
  1156. * improve timer
  1157. */
  1158.  
  1159. function improveTimer()
  1160. {
  1161. window.formatTime = (seconds) =>
  1162. {
  1163. return formatTimer(seconds);
  1164. };
  1165. window.formatTimeShort2 = (seconds) =>
  1166. {
  1167. return formatTimer(seconds);
  1168. };
  1169.  
  1170. addStyle(`
  1171. #notif-smelting > span:not(.timer)
  1172. {
  1173. display: none;
  1174. }
  1175. `);
  1176. const smeltingNotifBox = document.getElementById('notif-smelting');
  1177. const smeltingTimerEl = document.createElement('span');
  1178. smeltingTimerEl.className = 'timer';
  1179. smeltingNotifBox.appendChild(smeltingTimerEl);
  1180. function updateSmeltingTimer()
  1181. {
  1182. const totalTime = parseInt(window.smeltingPercD, 10);
  1183. const elapsedTime = parseInt(window.smeltingPercN, 10);
  1184. smeltingTimerEl.textContent = formatTimer(Math.max(totalTime - elapsedTime, 0));
  1185. }
  1186. observe('smeltingPercD', () => updateSmeltingTimer());
  1187. observe('smeltingPercN', () => updateSmeltingTimer());
  1188. updateSmeltingTimer();
  1189.  
  1190. // add tree grow timer
  1191. const treeInfo = {
  1192. 1: {
  1193. name: 'Normal tree'
  1194. // 3h = 10800s
  1195. , growTime: 3 * 60 * 60
  1196. }
  1197. , 2: {
  1198. name: 'Oak tree'
  1199. // 6h = 21600s
  1200. , growTime: 6 * 60 * 60
  1201. }
  1202. , 3: {
  1203. name: 'Willow tree'
  1204. // 8h = 28800s
  1205. , growTime: 8 * 60 * 60
  1206. }
  1207. };
  1208. function updateTreeInfo(place, nameEl, timerEl, init)
  1209. {
  1210. const idKey = 'treeId' + place;
  1211. const growTimerKey = 'treeGrowTimer' + place;
  1212. const lockedKey = 'treeUnlocked' + place;
  1213.  
  1214. const info = treeInfo[window[idKey]];
  1215. if (!info)
  1216. {
  1217. const isLocked = place > 4 && window[lockedKey] == 0;
  1218. nameEl.textContent = isLocked ? 'Locked' : 'Empty';
  1219. timerEl.textContent = '';
  1220. }
  1221. else
  1222. {
  1223. nameEl.textContent = info.name;
  1224. const remainingTime = info.growTime - parseInt(window[growTimerKey], 10);
  1225. timerEl.textContent = remainingTime > 0 ? '(' + formatTimer(remainingTime) + ')' : 'Fully grown';
  1226. }
  1227.  
  1228. if (init)
  1229. {
  1230. observe(
  1231. [idKey, growTimerKey, lockedKey]
  1232. , () => updateTreeInfo(place, nameEl, timerEl, false)
  1233. );
  1234. }
  1235. }
  1236. for (let i = 0; i < 6; i++)
  1237. {
  1238. const treePlace = i+1;
  1239. const treeContainer = document.getElementById('wc-div-tree-' + treePlace);
  1240. treeContainer.style.position = 'relative';
  1241. const infoEl = document.createElement('div');
  1242. infoEl.setAttribute('style', 'position: absolute; top: 0; left: 0; right: 0; pointer-events: none; margin-top: 5px; color: white;');
  1243. const treeName = document.createElement('div');
  1244. treeName.style.fontSize = '1.2rem';
  1245. infoEl.appendChild(treeName);
  1246. const treeTimer = document.createElement('div');
  1247. infoEl.appendChild(treeTimer);
  1248. treeContainer.appendChild(infoEl);
  1249.  
  1250. updateTreeInfo(treePlace, treeName, treeTimer, true);
  1251. }
  1252. }
  1253.  
  1254.  
  1255.  
  1256. /**
  1257. * improve smelting dialog
  1258. */
  1259.  
  1260. const smeltingRequirements = {
  1261. 'glass': {
  1262. sand: 1
  1263. , oil: 10
  1264. }
  1265. , 'bronzeBar': {
  1266. copper: 1
  1267. , tin: 1
  1268. , oil: 10
  1269. }
  1270. , 'ironBar': {
  1271. iron: 1
  1272. , oil: 100
  1273. }
  1274. , 'silverBar': {
  1275. silver: 1
  1276. , oil: 300
  1277. }
  1278. , 'goldBar': {
  1279. gold: 1
  1280. , oil: 1e3
  1281. }
  1282. };
  1283. function improveSmelting()
  1284. {
  1285. const amountInput = document.getElementById('input-smelt-bars-amount');
  1286. amountInput.type = 'number';
  1287. amountInput.min = 0;
  1288. amountInput.step = 5;
  1289. function onValueChange(event)
  1290. {
  1291. smeltingValue = null;
  1292. window.selectBar('', '', amountInput, document.getElementById('smelting-furnace-capacity').value);
  1293. }
  1294. amountInput.addEventListener('mouseup', onValueChange);
  1295. amountInput.addEventListener('keyup', onValueChange);
  1296. amountInput.setAttribute('onkeyup', '');
  1297.  
  1298. const _selectBar = window.selectBar;
  1299. let smeltingValue = null;
  1300. window.selectBar = (bar, inputElement, inputBarsAmountEl, capacity) =>
  1301. {
  1302. const requirements = smeltingRequirements[bar];
  1303. let maxAmount = capacity;
  1304. for (let key in requirements)
  1305. {
  1306. maxAmount = Math.min(Math.floor(window[key] / requirements[key]), maxAmount);
  1307. }
  1308. const value = parseInt(amountInput.value, 10);
  1309. if (value > maxAmount)
  1310. {
  1311. smeltingValue = value;
  1312. amountInput.value = maxAmount;
  1313. }
  1314. else if (smeltingValue != null)
  1315. {
  1316. amountInput.value = Math.min(smeltingValue, maxAmount);
  1317. if (smeltingValue <= maxAmount)
  1318. {
  1319. smeltingValue = null;
  1320. }
  1321. }
  1322. return _selectBar(bar, inputElement, inputBarsAmountEl, capacity);
  1323. };
  1324.  
  1325. const _openFurnaceDialogue = window.openFurnaceDialogue;
  1326. window.openFurnaceDialogue = (furnace) =>
  1327. {
  1328. if (smeltingBarType == 0)
  1329. {
  1330. amountInput.max = getFurnaceCapacity(furnace);
  1331. }
  1332. return _openFurnaceDialogue(furnace);
  1333. };
  1334. }
  1335.  
  1336.  
  1337.  
  1338. /**
  1339. * add chance to time calculator
  1340. */
  1341.  
  1342. /**
  1343. * calculates the number of seconds until the event with the given chance happened at least once with the given
  1344. * probability p (in percent)
  1345. */
  1346. function calcSecondsTillP(chancePerSecond, p)
  1347. {
  1348. return Math.round(Math.log(1 - p/100) / Math.log(1 - chancePerSecond));
  1349. }
  1350. function addChanceTooltip(headline, chancePerSecond, elId, targetEl)
  1351. {
  1352. // ensure element existence
  1353. const tooltipElId = 'tooltip-chance-' + elId;
  1354. let tooltipEl = document.getElementById(tooltipElId);
  1355. if (!tooltipEl)
  1356. {
  1357. tooltipEl = document.createElement('div');
  1358. tooltipEl.id = tooltipElId;
  1359. tooltipEl.style.display = 'none';
  1360. document.getElementById('tooltip-list').appendChild(tooltipEl);
  1361. }
  1362.  
  1363. // set elements content
  1364. const percValues = [1, 10, 20, 50, 80, 90, 99];
  1365. let percRows = '';
  1366. for (let p of percValues)
  1367. {
  1368. percRows += `
  1369. <tr>
  1370. <td>${p}%</td>
  1371. <td>${formatTime2NearestUnit(calcSecondsTillP(chancePerSecond, p), true)}</td>
  1372. </tr>`;
  1373. }
  1374. tooltipEl.innerHTML = `<h2>${headline}</h2>
  1375. <table class="chance">
  1376. <tr>
  1377. <th>Probability</th>
  1378. <th>Time</th>
  1379. </tr>
  1380. ${percRows}
  1381. </table>
  1382. `;
  1383.  
  1384. // ensure binded events to show the tooltip
  1385. if (targetEl.dataset.tooltipId == null)
  1386. {
  1387. targetEl.setAttribute('data-tooltip-id', tooltipElId);
  1388. window.$(targetEl).bind({
  1389. mousemove: window.changeTooltipPosition
  1390. , mouseenter: window.showTooltip
  1391. , mouseleave: window.hideTooltip
  1392. });
  1393. }
  1394. }
  1395. function chance2TimeCalculator()
  1396. {
  1397. addStyle(`
  1398. table.chance
  1399. {
  1400. border-spacing: 0;
  1401. }
  1402. table.chance th
  1403. {
  1404. border-bottom: 1px solid gray;
  1405. }
  1406. table.chance td:first-child
  1407. {
  1408. border-right: 1px solid gray;
  1409. text-align: center;
  1410. }
  1411. table.chance th,
  1412. table.chance td
  1413. {
  1414. padding: 4px 8px;
  1415. }
  1416. table.chance tr:nth-child(2n) td
  1417. {
  1418. background-color: white;
  1419. }
  1420. `);
  1421.  
  1422. const _clicksShovel = window.clicksShovel;
  1423. window.clicksShovel = () =>
  1424. {
  1425. _clicksShovel();
  1426.  
  1427. const shovelChance = document.getElementById('dialogue-shovel-chance');
  1428. const titleEl = shovelChance.parentElement;
  1429. const chance = 1/window.getChanceOfDiggingSand();
  1430. addChanceTooltip('One sand every:', chance, 'shovel', titleEl);
  1431. };
  1432.  
  1433. // depends on fishingXp
  1434. const _clicksFishingRod = window.clicksFishingRod;
  1435. window.clicksFishingRod = () =>
  1436. {
  1437. _clicksFishingRod();
  1438.  
  1439. const fishList = ['shrimp', 'sardine', 'tuna', 'swordfish', 'shark'];
  1440. for (let fish of fishList)
  1441. {
  1442. const rawFish = 'raw' + fish[0].toUpperCase() + fish.substr(1);
  1443. const row = document.getElementById('dialogue-fishing-rod-tr-' + rawFish);
  1444. const chance = row.cells[4].textContent
  1445. .replace(/[^\d\/]/g, '')
  1446. .split('/')
  1447. .reduce((p, c) => p / parseInt(c, 10), 1)
  1448. ;
  1449. addChanceTooltip(`One raw ${fish} every:`, chance, rawFish, row);
  1450. }
  1451. };
  1452. }
  1453.  
  1454.  
  1455.  
  1456. /**
  1457. * add tooltips for recipes
  1458. */
  1459.  
  1460. function updateRecipeTooltips(recipeKey, recipes)
  1461. {
  1462. const table = document.getElementById('table-' + recipeKey + '-recipe');
  1463. const rows = table.rows;
  1464. for (let i = 1; i < rows.length; i++)
  1465. {
  1466. const row = rows[i];
  1467. const key = row.id.replace(recipeKey + '-', '');
  1468. const recipe = recipes[key];
  1469. const requirementCell = row.cells[3];
  1470. requirementCell.title = recipe.recipe
  1471. .map((name, i) =>
  1472. {
  1473. return formatNumber(recipe.recipeCost[i]) + ' '
  1474. + name.replace(/[A-Z]/g, (match) => ' ' + match.toLowerCase())
  1475. ;
  1476. })
  1477. .join(' + ')
  1478. ;
  1479. window.$(requirementCell).tooltip();
  1480. }
  1481. }
  1482. function addRecipeTooltips()
  1483. {
  1484. const _processCraftingTab = window.processCraftingTab;
  1485. window.processCraftingTab = () =>
  1486. {
  1487. const reinit = !!window.refreshLoadCraftingTable;
  1488. _processCraftingTab();
  1489.  
  1490. if (reinit)
  1491. {
  1492. updateRecipeTooltips('crafting', window.craftingRecipes);
  1493. }
  1494. };
  1495.  
  1496. const _processBrewingTab = window.processBrewingTab;
  1497. window.processBrewingTab = () =>
  1498. {
  1499. const reinit = !!window.refreshLoadBrewingTable;
  1500. _processBrewingTab();
  1501.  
  1502. if (reinit)
  1503. {
  1504. updateRecipeTooltips('brewing', window.brewingRecipes);
  1505. }
  1506. }
  1507.  
  1508. const _processMagicTab = window.processMagicTab;
  1509. window.processMagicTab = () =>
  1510. {
  1511. const reinit = !!window.refreshLoadCraftingTable;
  1512. _processMagicTab();
  1513.  
  1514. if (reinit)
  1515. {
  1516. updateRecipeTooltips('magic', window.magicRecipes);
  1517. }
  1518. }
  1519. }
  1520.  
  1521.  
  1522.  
  1523. /**
  1524. * fix formatting of numbers
  1525. */
  1526.  
  1527. function prepareRecipeForTable(recipe)
  1528. {
  1529. // create a copy of the recipe to prevent requirement check from failing
  1530. const newRecipe = JSON.parse(JSON.stringify(recipe));
  1531. newRecipe.recipeCost = recipe.recipeCost.map(cost => formatNumber(cost));
  1532. newRecipe.xp = formatNumber(recipe.xp);
  1533. return newRecipe;
  1534. }
  1535. function fixNumberFormat()
  1536. {
  1537. const _addRecipeToBrewingTable = window.addRecipeToBrewingTable;
  1538. window.addRecipeToBrewingTable = (brewingRecipe) =>
  1539. {
  1540. _addRecipeToBrewingTable(prepareRecipeForTable(brewingRecipe));
  1541. };
  1542.  
  1543. const _addRecipeToMagicTable = window.addRecipeToMagicTable;
  1544. window.addRecipeToMagicTable = (magicRecipe) =>
  1545. {
  1546. _addRecipeToMagicTable(prepareRecipeForTable(magicRecipe));
  1547. };
  1548. }
  1549.  
  1550.  
  1551.  
  1552. /**
  1553. * style tweaks
  1554. */
  1555.  
  1556. function addTweakStyle(setting, style)
  1557. {
  1558. const prefix = 'body.' + setting;
  1559. addStyle(
  1560. style
  1561. .replace(/(^\s*|,\s*|\}\s*)([^\{\},]+)(,|\s*\{)/g, '$1' + prefix + ' $2$3')
  1562. );
  1563. document.body.classList.add(setting);
  1564. }
  1565. function tweakStyle()
  1566. {
  1567. // tweak oil production/consumption
  1568. addTweakStyle('tweak-oil', `
  1569. span#oil-flow-values
  1570. {
  1571. position: relative;
  1572. margin-left: .5em;
  1573. }
  1574. #oil-flow-values > span
  1575. {
  1576. font-size: 0px;
  1577. position: absolute;
  1578. top: -0.75rem;
  1579. visibility: hidden;
  1580. }
  1581. #oil-flow-values > span > span
  1582. {
  1583. font-size: 1rem;
  1584. visibility: visible;
  1585. }
  1586. #oil-flow-values > span:last-child
  1587. {
  1588. top: 0.75rem;
  1589. }
  1590. #oil-flow-values span[data-item-display="oilIn"]::before
  1591. {
  1592. content: '+';
  1593. }
  1594. #oil-flow-values span[data-item-display="oilOut"]::before
  1595. {
  1596. content: '-';
  1597. }
  1598. `);
  1599.  
  1600. addTweakStyle('no-select', `
  1601. table.tab-bar,
  1602. span.item-box,
  1603. div.farming-patch,
  1604. div.farming-patch-locked,
  1605. div.tab-sub-container-combat,
  1606. table.top-links a
  1607. {
  1608. -webkit-user-select: none;
  1609. -moz-user-select: none;
  1610. -ms-user-select: none;
  1611. user-select: none;
  1612. }
  1613. `);
  1614. }
  1615.  
  1616.  
  1617.  
  1618. /**
  1619. * init
  1620. */
  1621.  
  1622. function init()
  1623. {
  1624. temporaryFixes();
  1625.  
  1626. hideCraftedRecipes();
  1627. improveItemBoxes();
  1628. fixWoodcutting();
  1629. fixChat();
  1630. improveTimer();
  1631. improveSmelting();
  1632. chance2TimeCalculator();
  1633. addRecipeTooltips();
  1634.  
  1635. fixNumberFormat();
  1636. tweakStyle();
  1637. }
  1638. document.addEventListener('DOMContentLoaded', () =>
  1639. {
  1640. const _doCommand = window.doCommand;
  1641. window.doCommand = (data) =>
  1642. {
  1643. const values = data.split('=')[1];
  1644. if (data.startsWith('REFRESH_ITEMS='))
  1645. {
  1646. const itemDataValues = values.split(';');
  1647. const itemArray = [];
  1648. for (var i = 0; i < itemDataValues.length; i++)
  1649. {
  1650. const [key, newValue] = itemDataValues[i].split('~');
  1651. if (updateValue(key, newValue))
  1652. {
  1653. itemArray.push(key);
  1654. }
  1655. }
  1656.  
  1657. window.refreshItemValues(itemArray, false);
  1658.  
  1659. if (window.firstLoadGame)
  1660. {
  1661. window.loadInitial();
  1662. window.firstLoadGame = false;
  1663. init();
  1664. }
  1665. else
  1666. {
  1667. window.clientGameLoop();
  1668. }
  1669. return;
  1670. }
  1671. else if (data.startsWith('CHAT='))
  1672. {
  1673. var parts = data.substr(5).split('~');
  1674. return newAddToChatBox(parts[0], parts[1], parts[2], parts[3], 0);
  1675. }
  1676. return _doCommand(data);
  1677. };
  1678. });
  1679.  
  1680.  
  1681.  
  1682. /**
  1683. * fix web socket errors
  1684. */
  1685.  
  1686. function webSocketLoaded(event)
  1687. {
  1688. if (window.webSocket == null)
  1689. {
  1690. console.error('no webSocket instance found!');
  1691. return;
  1692. }
  1693.  
  1694. const messageQueue = [];
  1695. const _onMessage = webSocket.onmessage;
  1696. webSocket.onmessage = (event) => messageQueue.push(event);
  1697. document.addEventListener('DOMContentLoaded', () =>
  1698. {
  1699. messageQueue.forEach(event => onMessage(event));
  1700. webSocket.onmessage = _onMessage;
  1701. });
  1702.  
  1703. const commandQueue = [];
  1704. const _sendBytes = window.sendBytes;
  1705. window.sendBytes = (command) => commandQueue.push(command);
  1706. const _onOpen = webSocket.onopen;
  1707. webSocket.onopen = (event) =>
  1708. {
  1709. window.sendBytes = _sendBytes;
  1710. commandQueue.forEach(command => window.sendBytes(command));
  1711. return _onOpen(event);
  1712. };
  1713. }
  1714. function isWebSocketScript(script)
  1715. {
  1716. return script.src.includes('socket.js');
  1717. }
  1718. function fixWebSocketScript()
  1719. {
  1720. if (!document.head)
  1721. {
  1722. return;
  1723. }
  1724.  
  1725. const scripts = document.head.querySelectorAll('script');
  1726. let found = false;
  1727. for (let i = 0; i < scripts.length; i++)
  1728. {
  1729. if (isWebSocketScript(scripts[i]))
  1730. {
  1731. // does this work?
  1732. scripts[i].onload = webSocketLoaded;
  1733. return;
  1734. }
  1735. }
  1736.  
  1737. // create an observer instance
  1738. const mutationObserver = new MutationObserver((mutationList) =>
  1739. {
  1740. mutationList.forEach((mutation) =>
  1741. {
  1742. if (mutation.addedNodes.length === 0)
  1743. {
  1744. return;
  1745. }
  1746.  
  1747. for (let i = 0; i < mutation.addedNodes.length; i++)
  1748. {
  1749. const node = mutation.addedNodes[i];
  1750. if (node.tagName == 'SCRIPT' && isWebSocketScript(node))
  1751. {
  1752. mutationObserver.disconnect();
  1753. node.onload = webSocketLoaded;
  1754. return;
  1755. }
  1756. }
  1757. });
  1758. });
  1759. mutationObserver.observe(document.head, {
  1760. childList: true
  1761. });
  1762. }
  1763. fixWebSocketScript();
  1764.  
  1765. // fix scrollText (e.g. when joining the game and receiving xp at that moment)
  1766. window.mouseX = window.innerWidth / 2;
  1767. window.mouseY = window.innerHeight / 2;
  1768. })();