您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improve Diamond Hunt 2
当前为
// ==UserScript== // @name DH2 Fixed // @namespace FileFace // @description Improve Diamond Hunt 2 // @version 0.61.2 // @author Zorbing // @grant none // @run-at document-start // @include http://www.diamondhunt.co/game.php // ==/UserScript== (function () { 'use strict'; const settings = { hideCraftingRecipes: { name: 'Hide crafting recipes of finished items' , title: `Hides crafting recipes of: <ul style="margin: .5rem 0 0;"> <li>furnace, oil storage and oven recipes if they aren't better than the current level</li> <li>machines if the user has the maximum amount of this type (counts bound and unbound items)</li> <li>non-stackable items which the user already owns (counts bound and unbound items)</li> </ul>` , defaultValue: true } , useNewChat: { name: 'Use the new chat' , title: `Enables using the completely new chat with pm tabs, clickable links, clickable usernames to send a pm, intelligent scrolling and suggesting commands while typing` , defaultValue: true , requiresReload: true } }; /** * observer */ let observedKeys = new Map(); /** * Observes the given key for change * * @param {string} key The name of the variable * @param {Function} fn The function which is called on change */ function observe(key, fn) { if (key instanceof Array) { for (let k of key) { observe(k, fn); } } else { if (!observedKeys.has(key)) { observedKeys.set(key, new Set()); } observedKeys.get(key).add(fn); } return fn; } function unobserve(key, fn) { if (key instanceof Array) { let ret = []; for (let k of key) { ret.push(unobserve(k, fn)); } return ret; } if (!observedKeys.has(key)) { return false; } return observedKeys.get(key).delete(fn); } function updateValue(key, newValue) { if (window[key] === newValue) { return false; } const oldValue = window[key]; window[key] = newValue; (observedKeys.get(key) || []).forEach(fn => fn(key, oldValue, newValue)); return true; } /** * global constants */ const tierLevels = ['empty', 'sapphire', 'emerald', 'ruby', 'diamond']; const tierNames = ['Standard', 'Sapphire', 'Emerald', 'Ruby', 'Diamond']; const tierItemList = ['pickaxe', 'shovel', 'hammer', 'axe', 'rake', 'fishingRod']; const furnaceLevels = ['stone', 'bronze', 'iron', 'silver', 'gold']; const furnaceCapacity = [10, 30, 75, 150, 300]; const ovenLevels = ['bronze', 'iron', 'silver', 'gold']; const maxOilStorageLevel = 4; // 7 const oilStorageSize = [10e3, 50e3, 100e3, 300e3]; /** * general functions */ let styleElement = null; function addStyle(styleCode) { if (styleElement === null) { styleElement = document.createElement('style'); document.head.appendChild(styleElement); } styleElement.innerHTML += styleCode; } function getBoundKey(key) { return 'bound' + key[0].toUpperCase() + key.substr(1); } function getTierKey(key, tierLevel) { return tierLevels[tierLevel] + key[0].toUpperCase() + key.substr(1); } function formatNumber(num) { return parseFloat(num).toLocaleString('en'); } function formatNumbersInText(text) { return text.replace(/\d(?:[\d',\.]*\d)?/g, (numStr) => { return formatNumber(parseInt(numStr.replace(/\D/g, ''), 10)); }); } function now() { return (new Date()).getTime(); } function padLeft(num, padChar) { return (num < 10 ? padChar : '') + num; } // use time format established in DHQoL (https://greasyfork.org/scripts/16041-dhqol) function formatTimer(timer) { timer = parseInt(timer, 10); const hours = Math.floor(timer / 3600); const minutes = Math.floor((timer % 3600) / 60); const seconds = timer % 60; return padLeft(hours, '0') + ':' + padLeft(minutes, '0') + ':' + padLeft(seconds, '0'); } const timeSteps = [ { threshold: 1 , name: 'second' , short: 'sec' , padp: 0 } , { threshold: 60 , name: 'minute' , short: 'min' , padp: 0 } , { threshold: 3600 , name: 'hour' , short: 'h' , padp: 1 } , { threshold: 86400 , name: 'day' , short: 'd' , padp: 2 } ]; function formatTime2NearestUnit(time, long = false) { let step = timeSteps[0]; for (let i = timeSteps.length-1; i > 0; i--) { if (time >= timeSteps[i].threshold) { step = timeSteps[i]; break; } } const factor = Math.pow(10, step.padp); const num = Math.round(time / step.threshold * factor) / factor; const unit = long ? step.name + (num === 1 ? '' : 's') : step.short; return num + ' ' + unit; } function ensureTooltip(id, target) { const tooltipId = 'tooltip-' + id; let tooltipEl = document.getElementById(tooltipId); if (!tooltipEl) { tooltipEl = document.createElement('div'); tooltipEl.id = tooltipId; tooltipEl.style.display = 'none'; document.getElementById('tooltip-list').appendChild(tooltipEl); } // ensure binded events to show the tooltip if (target.dataset.tooltipId == null) { target.dataset.tooltipId = tooltipId; // target.setAttribute('data-tooltip-id', tooltipId); window.$(target).bind({ mousemove: window.changeTooltipPosition , mouseenter: window.showTooltip , mouseleave: window.hideTooltip }); } return tooltipEl; } /** * persistent store */ const storePrefix = 'dh2-'; const store = { get: (key) => { const value = localStorage.getItem(storePrefix + key); try { return JSON.parse(value); } catch (e) {} return value; } , has: (key) => { return localStorage.hasOwnProperty(storePrefix + key); } , persist: (key, value) => { localStorage.setItem(storePrefix + key, JSON.stringify(value)); } , remove: (key) => { localStorage.removeItem(storePrefix + key); } }; /** * settings */ function getSettingName(key) { return 'setting.' + key; } const observedSettings = new Map(); function observeSetting(key, fn) { if (!observedSettings.has(key)) { observedSettings.set(key, new Set()); } observedSettings.get(key).add(fn); } function unobserveSetting(key, fn) { if (!observedKeys.has(key)) { return false; } return observedKeys.get(key).delete(fn); } function getSetting(key) { if (!settings.hasOwnProperty(key)) { return; } const name = getSettingName(key); return store.has(name) ? store.get(name) : settings[key].defaultValue; } function setSetting(key, newValue) { if (!settings.hasOwnProperty(key)) { return; } const oldValue = getSetting(key); store.persist(getSettingName(key), newValue); if (oldValue !== newValue && observedSettings.has(key)) { observedSettings.get(key).forEach(fn => fn(key, oldValue, newValue)); } } function initSettings() { const settingsTableId = 'd2h-settings'; const settingIdPrefix = 'dh2-setting-'; addStyle(` table.table-style1 tr:not([onclick]) { cursor: initial; } #tab-container-profile h2.section-title { color: orange; line-height: 1.2rem; margin-top: 2rem; } #tab-container-profile h2.section-title > span.note { font-size: 0.9rem; } #${settingsTableId} tr.reload td:first-child::after { content: '*'; font-weight: bold; margin-left: 3px; } `); function insertAfter(newChild, oldChild) { const parent = oldChild.parentElement; if (oldChild.nextElementSibling == null) { parent.appendChild(newChild); } else { parent.insertBefore(newChild, oldChild.nextElementSibling); } } function getCheckImageSrc(value) { return 'images/icons/' + (value ? 'check' : 'x') + '.png'; } const profileTable = document.getElementById('profile-toggleTable'); const settingsHeader = document.createElement('h2'); settingsHeader.className = 'section-title'; settingsHeader.innerHTML = `Userscript "DH2 Fixed"<br> <span class="note">(* changes require reloading the tab)</span>`; insertAfter(settingsHeader, profileTable); const settingsTable = document.createElement('table'); settingsTable.id = settingsTableId; settingsTable.className = 'table-style1'; settingsTable.width = '40%'; settingsTable.innerHTML = ` <tr style="background-color:grey;"> <th>Setting</th> <th>Enabled</th> </tr> `; for (let key in settings) { const setting = settings[key]; const settingId = settingIdPrefix + key; const row = settingsTable.insertRow(-1); row.classList.add('setting'); if (setting.requiresReload) { row.classList.add('reload'); } row.setAttribute('onclick', ''); row.innerHTML = ` <td>${setting.name}</td> <td><img src="${getCheckImageSrc(getSetting(key))}" id="${settingId}" class="image-icon-20"></td> `; const tooltipEl = ensureTooltip(settingId, row); tooltipEl.innerHTML = setting.title; if (setting.requiresReload) { tooltipEl.innerHTML += `<span style="color: hsla(20, 100%, 50%, 1); font-size: .9rem; display: block; margin-top: 0.5rem;">You have to reload the browser tab to apply changed settings.</span>`; } row.addEventListener('click', () => { const newValue = !getSetting(key); setSetting(key, newValue); document.getElementById(settingId).src = getCheckImageSrc(newValue); }); } insertAfter(settingsTable, settingsHeader); } /** * hide crafting recipes of lower tiers or of maxed machines */ function setRecipeVisibility(key, visible) { const recipeRow = document.getElementById('crafting-' + key); if (recipeRow) { recipeRow.style.display = (!getSetting('hideCraftingRecipes') || visible) ? '' : 'none'; } } function hideLeveledRecipes(max, getKey, init) { const keys2Observe = []; let maxLevel = 0; for (let i = max-1; i >= 0; i--) { const level = i+1; const key = getKey(i); const boundKey = getBoundKey(key); keys2Observe.push(key); keys2Observe.push(boundKey); if (window[key] > 0 || window[boundKey] > 0) { maxLevel = Math.max(maxLevel, level); } setRecipeVisibility(key, level > maxLevel); } if (init) { observe(keys2Observe, () => hideLeveledRecipes(max, getKey, false)); } } function hideToolRecipe(key, init) { const emptyKey = getTierKey(key, 0); const keys2Observe = [emptyKey]; let hasTool = window[emptyKey] > 0; for (let i = 0; i < tierLevels.length; i++) { const boundKey = getBoundKey(getTierKey(key, i)); hasTool = hasTool || window[boundKey] > 0; keys2Observe.push(boundKey); } setRecipeVisibility(emptyKey, !hasTool); if (init) { observe(keys2Observe, () => hideToolRecipe(key, false)); } } function hideRecipe(key, max, init) { const maxValue = typeof max === 'function' ? max() : max; const boundKey = getBoundKey(key); const unbound = parseInt(window[key], 10); const bound = parseInt(window[boundKey], 10); setRecipeVisibility(key, (bound + unbound) < maxValue); if (init) { observe([key, boundKey], () => hideRecipe(key, max, false)); } } function hideCraftedRecipes() { function processRecipes(init) { // furnace hideLeveledRecipes( furnaceLevels.length , i => furnaceLevels[i] + 'Furnace' , init ); // oil storage hideLeveledRecipes( 7 , i => 'oilStorage' + (i+1) , init ); // oven recipes hideLeveledRecipes( ovenLevels.length , i => ovenLevels[i] + 'Oven' , init ); // tools hideToolRecipe('axe', init); hideToolRecipe('hammer', init); hideToolRecipe('shovel', init); hideToolRecipe('pickaxe', init); hideToolRecipe('fishingRod', init); // drills hideRecipe('drills', 10, init); // crushers hideRecipe('crushers', 10, init); // oil pipe hideRecipe('oilPipe', 1, init); // boats hideRecipe('rowBoat', 1, init); hideRecipe('canoe', 1, init); if (init) { observeSetting('hideCraftingRecipes', () => processRecipes(false)); } } processRecipes(true); const _processCraftingTab = window.processCraftingTab; window.processCraftingTab = () => { const reinit = !!window.refreshLoadCraftingTable; _processCraftingTab(); if (reinit) { processRecipes(false); } }; } /** * improve item boxes */ function hideNumberInItemBox(key, setVisibility) { const itemBox = document.getElementById('item-box-' + key); const numberElement = itemBox.lastElementChild; if (setVisibility) { numberElement.style.visibility = 'hidden'; } else { numberElement.style.display = 'none'; } } function addSpan2ItemBox(key) { hideNumberInItemBox(key); const itemBox = document.getElementById('item-box-' + key); const span = document.createElement('span'); itemBox.appendChild(span); return span; } function setOilPerSecond(span, oil) { span.innerHTML = `+ ${formatNumber(oil)} L/s <img src="images/oil.png" class="image-icon-20" style="margin-top: -2px;">`; } function improveItemBoxes() { // show capacity of furnace for (let i = 0; i < furnaceLevels.length; i++) { const key = furnaceLevels[i] + 'Furnace'; const capacitySpan = addSpan2ItemBox(getBoundKey(key)); capacitySpan.className = 'capacity'; capacitySpan.textContent = 'Capacity: ' + formatNumber(furnaceCapacity[i]); } // show oil cap of oil storage for (let i = 0; i < maxOilStorageLevel; i++) { const key = 'oilStorage' + (i+1); const capSpan = addSpan2ItemBox(getBoundKey(key)); capSpan.className = 'oil-cap'; capSpan.textContent = 'Oil cap: ' + formatNumber(oilStorageSize[i]); } // show oil per second const handheldOilSpan = addSpan2ItemBox('handheldOilPump'); setOilPerSecond(handheldOilSpan, 1*window.miner); observe('miner', () => setOilPerSecond(handheldOilSpan, 1*window.miner)); const oilPipeSpan = addSpan2ItemBox('boundOilPipe'); setOilPerSecond(oilPipeSpan, 50); // show current tier hideNumberInItemBox('emptyAnvil', true); hideNumberInItemBox('farmer', true); hideNumberInItemBox('planter', true); hideNumberInItemBox('cooksBook', true); hideNumberInItemBox('cooksPage', true); for (let tierItem of tierItemList) { for (let i = 0; i < tierLevels.length; i++) { const key = getTierKey(tierItem, i); const toolKey = tierItem == 'rake' ? key : getBoundKey(key); const tierSpan = addSpan2ItemBox(toolKey); tierSpan.className = 'tier'; tierSpan.textContent = tierNames[i]; } } // show boat progress const boatKeys = ['rowBoat', 'canoe']; const boatTimerKeys = boatKeys.map(k => k + 'Timer'); function checkBoat(span, timerKey, init) { const isInTransit = window[timerKey] > 0; const otherInTransit = boatTimerKeys.some(k => k != timerKey && window[k] > 0); span.textContent = isInTransit ? 'In transit' : 'Ready'; span.style.visibility = otherInTransit ? 'hidden' : ''; if (init) { observe(boatTimerKeys, () => checkBoat(span, timerKey, false)); } } for (let i = 0; i < boatKeys.length; i++) { const span = addSpan2ItemBox(getBoundKey(boatKeys[i])); checkBoat(span, boatTimerKeys[i], true); } } /** * fix wood cutting */ function fixWoodcutting() { addStyle(` img.woodcutting-tree-img { border: 1px solid transparent; } `); } /** * fix chat */ function isMuted(user) { // return window.mutedPeople.some((name) => user.indexOf(name) > -1); return window.mutedPeople.includes(user); } function handleScrolling(chatbox) { if (window.isAutoScrolling) { setTimeout(() => chatbox.scrollTop = chatbox.scrollHeight); } } const chatHistoryKey = 'chatHistory'; const maxChatHistoryLength = 100; const TYPE_RELOAD = -1; const TYPE_NORMAL = 0; const TYPE_PM_FROM = 1; const TYPE_PM_TO = 2; const TYPE_SERVER_MSG = 3; /** * The chunk hiding starts with at least 10 chunks. * So there are at least * (chunkHidingMinChunks-1) * msgChunkSize + 1 = 9 * 100 + 1 = 901 * messages before the chunk hiding mechanism starts. */ const chunkHidingMinChunks = 10; const msgChunkSize = 100; const reloadedChatData = { timestamp: 0 , username: '' , userlevel: 0 , icon: 0 , tag: 0 , type: TYPE_RELOAD , msg: '[...]' }; // load chat history let chatHistory = store.get(chatHistoryKey) || []; // find index of last message which is not a pm const lastNotPM = chatHistory.slice(0).reverse().find((d) => { return !isPM(d); }); // insert a placeholder for a reloaded chat if (lastNotPM && lastNotPM.type != TYPE_RELOAD) { reloadedChatData.timestamp = (new Date()).getTime(); chatHistory.push(reloadedChatData); } // for chat messages which arrive before DOMContentLoaded and can not be displayed since the DOM isn't ready let chatInitialized = false; function processChatData(username, icon, tag, msg, isPM) { let userlevel = 0; let type = tag == 5 ? TYPE_SERVER_MSG : TYPE_NORMAL; if (isPM == 1) { const match = msg.match(/^\s*\[(.+) ([A-Za-z0-9 ]+)\]: (.+?)\s*$/) || ['', '', username, msg]; type = match[1] == 'Sent to' ? TYPE_PM_TO : TYPE_PM_FROM; username = match[2]; msg = match[3]; } else if (tag != 5) { const match = msg.match(/^\s*\((\d+)\): (.+?)\s*$/); if (match) { userlevel = match[1]; msg = match[2]; } else { userlevel = window.getGlobalLevel(); } } const data = { timestamp: now() , username: username , userlevel: userlevel , icon: icon , tag: tag , type: type , msg: msg }; return data; } function add2ChatHistory(data) { chatHistory.push(data); chatHistory = chatHistory.slice(-maxChatHistoryLength); store.persist(chatHistoryKey, chatHistory); } const chatBoxId = 'div-chat'; const generalChatTabId = 'tab-chat-general'; const generalChatDivId = 'div-chat-area'; const pmChatTabPrefix = 'tab-chat-pm-'; const pmChatDivPrefix = 'div-chat-pm-'; const chatInputId = 'chat-input-text'; function getChatTab(username) { const id = username == '' ? generalChatTabId : pmChatTabPrefix + username.replace(/ /g, '_'); let tab = document.getElementById(id); if (!tab) { tab = document.createElement('div'); tab.className = 'chat-tab'; tab.id = id; tab.dataset.username = username; tab.dataset.new = 0; tab.textContent = username; // thanks /u/Spino-Prime for pointing out this was missing const closeSpan = document.createElement('span'); closeSpan.className = 'close'; tab.appendChild(closeSpan); const chatTabs = document.getElementById('chat-tabs'); const filler = chatTabs.querySelector('.filler'); if (filler) { chatTabs.insertBefore(tab, filler); } else { chatTabs.appendChild(tab); } } return tab; } function getChatDiv(username) { const id = username == '' ? generalChatDivId : pmChatDivPrefix + username.replace(/ /g, '_'); let div = document.getElementById(id); if (!div) { div = document.createElement('div'); div.setAttribute('disabled', 'disabled'); div.id = id; div.className = 'div-chat-area'; const height = document.getElementById(generalChatDivId).style.height; div.style.height = height; const generalChat = document.getElementById(generalChatDivId); generalChat.parentNode.insertBefore(div, generalChat); } return div; } function changeChatTab(oldTab, newTab) { oldTab.classList.remove('selected'); newTab.classList.add('selected'); newTab.dataset.new = 0; const oldChatDiv = getChatDiv(oldTab.dataset.username); oldChatDiv.classList.remove('selected'); const newChatDiv = getChatDiv(newTab.dataset.username); newChatDiv.classList.add('selected'); const toUsername = newTab.dataset.username; const newTextPlaceholder = toUsername == '' ? window.username + ':' : 'PM to ' + toUsername + ':'; document.getElementById(chatInputId).placeholder = newTextPlaceholder; if (window.isAutoScrolling) { setTimeout(() => newChatDiv.scrollTop = newChatDiv.scrollHeight); } } function closeChatTab(username) { // TODO: maybe delete pms stored for that user? const oldTab = document.querySelector('#chat-tabs .chat-tab.selected'); const tab2Close = getChatTab(username); if (oldTab.dataset.username == username) { const generalTab = getChatTab(''); changeChatTab(tab2Close, generalTab); } tab2Close.parentElement.removeChild(tab2Close); } const chatIcons = [ null , { key: 'halloween2015', title: 'Halloween 2015' } , { key: 'christmas2015', title: 'Chirstmas 2015' } , { key: 'easter2016', title: 'Holiday' } , { key: 'halloween2016', title: 'Halloween 2016' } , { key: 'christmas2016', title: 'Chirstmas 2016' } , { key: 'dh1Max', title: 'Max Level in DH1' } , { key: 'hardcore', title: 'Hardcore Account' } , { key: 'quest', title: 'Questmaster' } ]; const chatTags = [ null , { key: 'donor', name: '' } , { key: 'contributor', name: 'Contributor' } , { key: 'mod', name: 'Moderator' } , { key: 'dev', name: 'Dev' } , { key: 'yell', name: 'Server Message' } ]; function isPM(data) { return data.type == TYPE_PM_TO || data.type == TYPE_PM_FROM; } const locale = 'en-US'; const localeOptions = { hour12: false , year: 'numeric' , month: 'long' , day: 'numeric' , hour: '2-digit' , minute: '2-digit' , second: '2-digit' }; const msgChunkMap = new Map(); let chatboxFragments = new Map(); function createMessageSegment(data) { const isThisPm = isPM(data); const msgUsername = data.type == TYPE_PM_TO ? window.username : data.username; const historyIndex = chatHistory.indexOf(data); let isSameUser = null; let isSameTime = null; for (let i = historyIndex-1; i >= 0 && (isSameUser === null || isSameTime === null); i--) { const dataBefore = chatHistory[i]; if (isThisPm && isPM(dataBefore) || !isThisPm && !isPM(dataBefore)) { if (isSameUser === null) { const beforeUsername = dataBefore.type == TYPE_PM_TO ? window.username : dataBefore.username; isSameUser = beforeUsername === msgUsername; } if (dataBefore.type != TYPE_RELOAD) { isSameTime = Math.floor(data.timestamp / 1000/60) - Math.floor(dataBefore.timestamp / 1000/60) === 0; } } } const d = new Date(data.timestamp); const hour = (d.getHours() < 10 ? '0' : '') + d.getHours(); const minute = (d.getMinutes() < 10 ? '0' : '') + d.getMinutes(); const icon = chatIcons[data.icon] || { key: '', title: '' }; const tag = chatTags[data.tag] || { key: '', name: '' }; // thanks aguyd (https://greasyfork.org/forum/profile/aguyd) for the vulnerability warning const formattedMsg = data.msg.replace(/(https?:\/\/[^\s"<>]+)/g, '<a target="_blank" href="$1">$1</a>'); const msgTitle = data.type == TYPE_RELOAD ? 'Chat loaded on ' + d.toLocaleString(locale, localeOptions) : ''; const user = data.type === TYPE_SERVER_MSG ? 'Server Message' : msgUsername; const levelAppendix = data.type == TYPE_NORMAL ? ' (' + data.userlevel + ')' : ''; const userTitle = data.tag != 5 ? tag.name : ''; return `<span class="chat-msg" data-type="${data.type}" data-tag="${tag.key}">` + `<span class="timestamp" data-timestamp="${data.timestamp}" data-same-time="${isSameTime}">${hour}:${minute}</span>` + `<span class="user" data-name="${msgUsername}" data-same-user="${isSameUser}">` + `<span class="icon ${icon.key}" title="${icon.title}"></span>` + `<span class="name chat-tag-${tag.key}" title="${userTitle}">${user}${levelAppendix}:</span>` + `</span>` + `<span class="msg" title="${msgTitle}">${formattedMsg}</span>` + `</span>`; } function add2Chat(data) { if (!chatInitialized) { return; } const isThisPm = isPM(data); // don't mute pms (you can just ignore pm-tab if you like) if (!isThisPm && isMuted(data.username)) { return; } const userKey = isThisPm ? data.username : ''; const chatTab = getChatTab(userKey); if (!chatTab.classList.contains('selected')) { chatTab.dataset.new = parseInt(chatTab.dataset.new, 10) + 1; } if (isThisPm) { window.lastPMUser = data.username; } // username is 3-12 characters long const chatbox = getChatDiv(userKey); let msgChunk = msgChunkMap.get(userKey); if (!msgChunk || msgChunk.children.length >= msgChunkSize) { msgChunk = document.createElement('div'); msgChunk.className = 'msg-chunk'; msgChunkMap.set(userKey, msgChunk); if (chatboxFragments != null) { if (!chatboxFragments.has(userKey)) { chatboxFragments.set(userKey, document.createDocumentFragment()); } chatboxFragments.get(userKey).appendChild(msgChunk); } else { chatbox.appendChild(msgChunk); } } const tmp = document.createElement('templateWrapper'); tmp.innerHTML = createMessageSegment(data); msgChunk.appendChild(tmp.children[0]); handleScrolling(chatbox); } function applyChatStyle() { addStyle(` span.chat-msg { display: flex; margin-bottom: 1px; } span.chat-msg:nth-child(2n) { background-color: hsla(0, 0%, 90%, 1); } .chat-msg[data-type="${TYPE_RELOAD}"] { font-size: 0.8rem; } .chat-msg .timestamp { display: none; } .chat-msg:not([data-type="${TYPE_RELOAD}"]) .timestamp { color: hsla(0, 0%, 50%, 1); display: inline-block; font-size: .9rem; margin: 0; margin-right: 5px; position: relative; width: 2.5rem; } .chat-msg .timestamp[data-same-time="true"] { color: hsla(0, 0%, 50%, .1); } .chat-msg:not([data-type="${TYPE_RELOAD}"]) .timestamp:hover::after { background-color: hsla(0, 0%, 12%, 1); border-radius: .2rem; content: attr(data-fulltime); color: hsla(0, 0%, 100%, 1); line-height: 1.35rem; padding: .4rem .8rem; position: absolute; left: 2.5rem; top: -0.4rem; text-align: center; white-space: nowrap; } .chat-msg[data-type="${TYPE_PM_FROM}"] { color: purple; } .chat-msg[data-type="${TYPE_PM_TO}"] { color: purple; } .chat-msg[data-type="${TYPE_SERVER_MSG}"] { color: blue; } .chat-msg[data-tag="contributor"] { color: green; } .chat-msg[data-tag="mod"] { color: #669999; } .chat-msg[data-tag="dev"] { color: #666600; } .chat-msg:not([data-type="${TYPE_RELOAD}"]) .user { flex: 0 0 132px; margin-right: 5px; white-space: nowrap; } #${generalChatDivId} .chat-msg:not([data-type="${TYPE_RELOAD}"]) .user { flex-basis: 182px; padding-left: 22px; } .chat-msg .user[data-same-user="true"]:not([data-name=""]) { opacity: 0; } .chat-msg .user .icon { margin-left: -22px; } .chat-msg .user .icon::before { background-size: 20px 20px; content: ''; display: inline-block; margin-right: 2px; width: 20px; height: 20px; vertical-align: middle; } .chat-msg .user .icon.halloween2015::before { background-image: url('images/chat-icons/1.png'); } .chat-msg .user .icon.christmas2015::before { background-image: url('images/chat-icons/2.png'); } .chat-msg .user .icon.easter2016::before { background-image: url('images/chat-icons/3.png'); } .chat-msg .user .icon.halloween2016::before { background-image: url('images/chat-icons/4.png'); } .chat-msg .user .icon.christmas2016::before { background-image: url('images/chat-icons/5.png'); } .chat-msg .user .icon.dh1Max::before { background-image: url('images/chat-icons/6.png'); } .chat-msg .user .icon.hardcore::before { background-image: url('images/chat-icons/7.png'); } .chat-msg .user .icon.quest::before { background-image: url('images/chat-icons/8.png'); } .chat-msg .user .name { color: rgba(0, 0, 0, 0.7); cursor: pointer; } .chat-msg .user .name.chat-tag-donor::before { background-image: url('images/chat-icons/donor.png'); background-size: 20px 20px; content: ''; display: inline-block; height: 20px; width: 20px; vertical-align: middle; } .chat-msg .user .name.chat-tag-yell { cursor: default; } .chat-msg .user .name.chat-tag-contributor, .chat-msg .user .name.chat-tag-mod, .chat-msg .user .name.chat-tag-dev, .chat-msg .user .name.chat-tag-yell { color: white; display: inline-block; font-size: 10pt; margin-top: -1px; padding-bottom: 0; text-align: center; /* 2px border, 10 padding */ width: calc(100% - 2*1px - 2*5px); } .chat-msg[data-type="${TYPE_RELOAD}"] .user > *, .chat-msg[data-type="${TYPE_PM_FROM}"] .user > .icon, .chat-msg[data-type="${TYPE_PM_TO}"] .user > .icon { display: none; } .chat-msg .msg { min-width: 0; overflow: hidden; word-wrap: break-word; } #div-chat .div-chat-area { width: 100%; height: 130px; display: none; } #div-chat .div-chat-area.selected { display: block; } #chat-tabs { display: flex; margin: 10px -6px -6px; flex-wrap: wrap; } #chat-tabs .chat-tab { background-color: gray; border-top: 1px solid black; border-right: 1px solid black; cursor: pointer; display: inline-block; font-weight: normal; padding: 0.3rem .6rem; position: relative; } #chat-tabs .chat-tab.selected { background-color: transparent; border-top-color: transparent; } #chat-tabs .chat-tab.filler { background-color: hsla(0, 0%, 90%, 1); border-right: 0; box-shadow: inset 5px 5px 5px -5px rgba(0, 0, 0, 0.5); color: transparent; cursor: default; flex-grow: 1; } #chat-tabs .chat-tab::after { color: white; content: '(' attr(data-new) ')'; font-size: .9rem; font-weight: bold; margin-left: .4rem; } #chat-tabs .chat-tab[data-new="0"]::after { color: inherit; font-weight: normal; } #chat-tabs .chat-tab:not(.general).selected::after, #chat-tabs .chat-tab:not(.general):hover::after { visibility: hidden; } #chat-tabs .chat-tab:not(.general).selected .close::after, #chat-tabs .chat-tab:not(.general):hover .close::after { content: '\xd7'; font-size: 1.5rem; position: absolute; top: 0; right: .6rem; bottom: 0; } `); } function addIntelligentScrolling() { // add checkbox instead of button for toggling auto scrolling const btn = document.querySelector('input[value="Toggle Autoscroll"]'); const checkboxId = 'chat-toggle-autoscroll'; // create checkbox const toggleCheckbox = document.createElement('input'); toggleCheckbox.type = 'checkbox'; toggleCheckbox.id = checkboxId; toggleCheckbox.checked = true; // create label const toggleLabel = document.createElement('label'); toggleLabel.htmlFor = checkboxId; toggleLabel.textContent = 'Autoscroll'; btn.parentNode.insertBefore(toggleCheckbox, btn); btn.parentNode.insertBefore(toggleLabel, btn); btn.style.display = 'none'; // add checkbox for intelligent scrolling const isCheckboxId = 'chat-toggle-intelligent-scroll'; const intScrollCheckbox = document.createElement('input'); intScrollCheckbox.type = 'checkbox'; intScrollCheckbox.id = isCheckboxId; intScrollCheckbox.checked = true; // add label const intScrollLabel = document.createElement('label'); intScrollLabel.htmlFor = isCheckboxId; intScrollLabel.textContent = 'Intelligent Scrolling'; btn.parentNode.appendChild(intScrollCheckbox); btn.parentNode.appendChild(intScrollLabel); const chatArea = document.getElementById(generalChatDivId); let showScrollTextTimeout = null; function setAutoScrolling(value, full) { if (window.isAutoScrolling != value) { toggleCheckbox.checked = value; window.isAutoScrolling = value; const color = value ? 'lime' : 'red'; const text = (value ? 'En' : 'Dis') + 'abled' + (full ? ' Autoscroll' : ''); const scrollArgs = ['none', color, text]; if (full) { window.clearTimeout(showScrollTextTimeout); showScrollTextTimeout = window.setTimeout(() => window.scrollText(...scrollArgs), 300); } else { window.scrollText(...scrollArgs); } return true; } return false; } toggleCheckbox.addEventListener('change', function () { setAutoScrolling(this.checked); if (this.checked && intScrollCheckbox.checked) { chatArea.scrollTop = chatArea.scrollHeight - chatArea.clientHeight; } }); const placeholderTemplate = document.createElement('div'); placeholderTemplate.className = 'placeholder'; const childStore = new WeakMap(); function scrollHugeChat() { // # of children const chunkNum = chatArea.children.length; // start chunk hiding at a specific amount of chunks if (chunkNum < chunkHidingMinChunks) { return; } const visibleTop = chatArea.scrollTop; const visibleBottom = visibleTop + chatArea.clientHeight; const referenceTop = visibleTop - window.innerHeight; const referenceBottom = visibleBottom + window.innerHeight; let top = 0; // never hide the last element since its size may change at any time when a new message gets appended for (let i = 0; i < chunkNum-1; i++) { const child = chatArea.children[i]; const height = child.clientHeight; const bottom = top + height; const isVisible = top >= referenceTop && top <= referenceBottom || bottom >= referenceTop && bottom <= referenceBottom || top < referenceTop && bottom > referenceBottom ; const isPlaceholder = child.classList.contains('placeholder'); if (!isVisible && !isPlaceholder) { const newPlaceholder = placeholderTemplate.cloneNode(false); newPlaceholder.style.height = height + 'px'; chatArea.replaceChild(newPlaceholder, child); childStore.set(newPlaceholder, child); } else if (isVisible && isPlaceholder) { const oldChild = childStore.get(child); chatArea.replaceChild(oldChild, child); childStore.delete(child); } top = bottom; } } let timeouts = {}; const timeoutDelay = 50; const maxDelay = 300; function startCancelableTimeout(key, handler) { let obj = timeouts[key] || {}; const n = now(); if (obj.start == null) { obj.start = n; } if (obj.start + maxDelay > n) { window.clearTimeout(obj.ref); obj.ref = window.setTimeout(() => { obj.start = null; obj.ref = null; handler(); }, timeoutDelay); } timeouts[key] = obj; } // does not consider pm tabs; may be changed in a future version? chatArea.addEventListener('scroll', () => { if (intScrollCheckbox.checked) { const scrolled2Bottom = (chatArea.scrollTop + chatArea.clientHeight) >= chatArea.scrollHeight; setAutoScrolling(scrolled2Bottom, true); } startCancelableTimeout('scrollHugeChat', () => scrollHugeChat()); }); } function clickChatTab(newTab) { const oldTab = document.querySelector('#chat-tabs .chat-tab.selected'); if (newTab == oldTab) { return; } changeChatTab(oldTab, newTab); } function clickCloseChatTab(tab) { const username = tab.dataset.username; const chatDiv = getChatDiv(username); if (chatDiv.children.length === 0 || confirm(`Do you want to close the pm tab of "${username}"?`)) { closeChatTab(username); } } function addChatTabs() { const chatBoxArea = document.getElementById(chatBoxId); const chatTabs = document.createElement('div'); chatTabs.id = 'chat-tabs'; chatTabs.addEventListener('click', (event) => { const newTab = event.target; if (newTab.classList.contains('close')) { return clickCloseChatTab(newTab.parentElement); } if (!newTab.classList.contains('chat-tab') || newTab.classList.contains('filler')) { return; } clickChatTab(newTab); }); chatBoxArea.appendChild(chatTabs); const generalTab = getChatTab(''); generalTab.classList.add('general'); generalTab.classList.add('selected'); generalTab.textContent = 'Server'; const generalChatDiv = getChatDiv(''); generalChatDiv.classList.add('selected'); // works only if username length of 1 isn't allowed const fillerTab = getChatTab('f'); fillerTab.classList.add('filler'); fillerTab.textContent = ''; const _sendChat = window.sendChat; window.sendChat = (inputEl) => { let msg = inputEl.value; const selectedTab = document.querySelector('.chat-tab.selected'); if (selectedTab.dataset.username != '' && msg[0] != '/') { inputEl.value = '/pm ' + selectedTab.dataset.username + ' ' + msg; } _sendChat(inputEl); }; } function newAddToChatBox(username, icon, tag, msg, isPM) { const data = processChatData(username, icon, tag, msg, isPM); add2ChatHistory(data); if (getSetting('useNewChat')) { add2Chat(data); } else { window.addToChatBox(username, icon, tag, msg, isPM); } } function newChat() { addChatTabs(); applyChatStyle(); window.addToChatBox = newAddToChatBox; chatInitialized = true; const chatbox = document.getElementById(chatBoxId); chatbox.addEventListener('click', (event) => { let target = event.target; while (target && target.id != chatBoxId && !target.classList.contains('user')) { target = target.parentElement; } if (!target || target.id == chatBoxId) { return; } const username = target.dataset.name; if (username == window.username || username == '') { return; } const userTab = getChatTab(username); clickChatTab(userTab); document.getElementById(chatInputId).focus(); }); chatbox.addEventListener('mouseover', (event) => { const target = event.target; if (!target.classList.contains('timestamp') || !target.dataset.timestamp) { return; } const timestamp = parseInt(target.dataset.timestamp, 10); target.dataset.fulltime = (new Date(timestamp)).toLocaleDateString(locale, localeOptions); target.dataset.timestamp = ''; }); } const commands = ['pm', 'mute', 'ipmute']; function addCommandSuggester() { const input = document.getElementById(chatInputId); input.addEventListener('keyup', (event) => { if (event.key != 'Backspace' && event.key != 'Delete' && input.selectionStart == input.selectionEnd && input.selectionStart == input.value.length && input.value.startsWith('/')) { const value = input.value.substr(1); const suggestions = commands.filter(c => c.startsWith(value)); if (suggestions.length == 1) { input.value = '/' + suggestions[0]; input.selectionStart = 1 + value.length; input.selectionEnd = input.value.length; } } }); } const tutorialCmd = 'tutorial'; function addOwnCommands() { commands.push(tutorialCmd); const _doChatCommand = window.doChatCommand; window.doChatCommand = (value) => { // thanks aguyd (https://greasyfork.org/forum/profile/aguyd) for the idea if (value.startsWith('/')) { const rest = value.substr(1); if (rest.startsWith(tutorialCmd)) { const name = rest.substr(tutorialCmd.length).trim(); let msg = 'https://www.reddit.com/r/DiamondHunt/comments/5vrufh/diamond_hunt_2_starter_faq/'; if (name.length != 0) { // maybe add '@' before the name? msg = name + ', ' + msg; } window.sendBytes('CHAT=' + msg); return true; } } return _doChatCommand(value); }; } function initChat() { if (!getSetting('useNewChat')) { return; } newChat(); addIntelligentScrolling(); addCommandSuggester(); addOwnCommands(); const _enlargeChat = window.enlargeChat; const chatBoxArea = document.getElementById(chatBoxId); function setChatBoxHeight(height) { document.getElementById(generalChatDivId).style.height = height; const chatDivs = chatBoxArea.querySelectorAll('div[id^="' + pmChatDivPrefix + '"]'); for (let i = 0; i < chatDivs.length; i++) { chatDivs[i].style.height = height; } } window.enlargeChat = (enlargeB) => { _enlargeChat(enlargeB); const height = document.getElementById(generalChatDivId).style.height; store.persist('chat.height', height); setChatBoxHeight(height); }; setChatBoxHeight(store.get('chat.height')); // TEMP >>> (due to a naming issue, migrate the data) const oldChatHistoryKey = 'chatHistory2'; const oldChatHistory = store.get(oldChatHistoryKey); if (oldChatHistory != null) { store.persist(chatHistoryKey, oldChatHistory); store.remove(oldChatHistoryKey); } // TEMP <<< // add history to chat chatHistory.forEach(d => add2Chat(d)); chatboxFragments.forEach((fragment, key) => { const chatbox = getChatDiv(key); chatbox.appendChild(fragment); }); chatboxFragments = null; // reset the new counter for all tabs const tabs = document.querySelectorAll('.chat-tab'); for (let i = 0; i < tabs.length; i++) { tabs[i].dataset.new = 0; } } /** * hopefully only temporary fixes */ function temporaryFixes() { // fix grow time of some seeds const seeds = { 'limeLeafSeeds': { replace: '1 hour' , replaceWith: '1 hour and 30 minutes' } }; for (let seedName in seeds) { const tooltip = document.getElementById('tooltip-' + seedName); const timeNode = tooltip.lastElementChild.lastChild; const seed = seeds[seedName]; timeNode.textContent = timeNode.textContent.replace(seed.replace, seed.replaceWith); } // fix exhaustion timer and updating brewing and cooking recipes const _clientGameLoop = window.clientGameLoop; window.clientGameLoop = () => { _clientGameLoop(); setHeroClickable(); if (window.isInCombat() && combatCommenceTimer != 0) { document.getElementById('combat-countdown').style.display = ''; } if (document.getElementById('tab-container-combat').style.display != 'none') { window.combatNotFightingTick(); } if (currentOpenTab == 'brewing') { window.processBrewingTab(); } if (currentOpenTab == 'cooksBook') { window.processCooksBookTab(); } }; // fix elements of scrollText (e.g. when joining the game and receiving xp at that moment) const textEls = document.querySelectorAll('div.scroller'); for (let i = 0; i < textEls.length; i++) { const scroller = textEls[i]; if (scroller.style.position != 'absolute') { scroller.style.display = 'none'; } } // fix style of tooltips addStyle(` body > div.tooltip > h2:first-child { margin-top: 0; font-size: 20pt; font-weight: normal; } `); // fix buiulding magic table dynamically window.refreshLoadMagicTable = true; const _processMagicTab = window.processMagicTab; window.processMagicTab = () => { const _refreshLoadCraftingTable = window.refreshLoadCraftingTable; window.refreshLoadCraftingTable = window.refreshLoadMagicTable; _processMagicTab(); window.refreshLoadCraftingTable = _refreshLoadCraftingTable; }; // update hero being clickable in combat function setHeroClickable() { const heroArea = document.getElementById('hero-area'); const equipment = heroArea.lastElementChild; equipment.style.pointerEvents = window.isInCombat() ? 'none' : ''; } // fix crafting level of giant drills const _processCraftingTab = window.processCraftingTab; window.processCraftingTab = () => { const reinit = !!window.refreshLoadCraftingTable; _processCraftingTab(); if (reinit) { craftingRecipes.giantDrills.levelReq = 35; document.getElementById('recipe-level-req-giantDrills').textContent = '35'; } }; } /** * improve timer */ function improveTimer() { window.formatTime = (seconds) => { return formatTimer(seconds); }; window.formatTimeShort2 = (seconds) => { return formatTimer(seconds); }; addStyle(` #notif-smelting > span:not(.timer) { display: none; } `); const smeltingNotifBox = document.getElementById('notif-smelting'); const smeltingTimerEl = document.createElement('span'); smeltingTimerEl.className = 'timer'; smeltingNotifBox.appendChild(smeltingTimerEl); function updateSmeltingTimer() { const totalTime = parseInt(window.smeltingPercD, 10); const elapsedTime = parseInt(window.smeltingPercN, 10); smeltingTimerEl.textContent = formatTimer(Math.max(totalTime - elapsedTime, 0)); } observe('smeltingPercD', () => updateSmeltingTimer()); observe('smeltingPercN', () => updateSmeltingTimer()); updateSmeltingTimer(); // add tree grow timer addStyle(` /* hide timer elements of DH2QoL, because I can :P */ .woodcutting-tree > span, .woodcutting-tree > br { display: none; } .woodcutting-tree > div.timer { color: white; margin-top: 5px; pointer-events: none; position: absolute; top: 0; left: 0; right: 0; } `); const treeInfo = { 1: { name: 'Normal tree' // 3h = 10800s , growTime: 3 * 60 * 60 } , 2: { name: 'Oak tree' // 6h = 21600s , growTime: 6 * 60 * 60 } , 3: { name: 'Willow tree' // 8h = 28800s , growTime: 8 * 60 * 60 } , 4: { name: 'Maple tree' // 12h = 43200s , growTime: 12 * 60 * 60 } }; function updateTreeInfo(place, infoElId, init) { const infoEl = document.getElementById(infoElId); const nameEl = infoEl.firstElementChild; const timerEl = infoEl.lastElementChild; const idKey = 'treeId' + place; const growTimerKey = 'treeGrowTimer' + place; const lockedKey = 'treeUnlocked' + place; const info = treeInfo[window[idKey]]; if (!info) { const isLocked = place > 4 && window[lockedKey] == 0; nameEl.textContent = isLocked ? 'Locked' : 'Empty'; timerEl.textContent = ''; } else { nameEl.textContent = info.name; const remainingTime = info.growTime - parseInt(window[growTimerKey], 10); timerEl.textContent = remainingTime > 0 ? '(' + formatTimer(remainingTime) + ')' : 'Fully grown'; } if (init) { observe( [idKey, growTimerKey, lockedKey] , () => updateTreeInfo(place, infoElId, false) ); } } for (let i = 0; i < 6; i++) { const treePlace = i+1; const infoElId = 'wc-tree-timer-' + treePlace; const treeContainer = document.getElementById('wc-div-tree-' + treePlace); treeContainer.style.position = 'relative'; const infoEl = document.createElement('div'); infoEl.className = 'timer'; infoEl.id = infoElId; const treeName = document.createElement('div'); treeName.style.fontSize = '1.2rem'; infoEl.appendChild(treeName); const treeTimer = document.createElement('div'); infoEl.appendChild(treeTimer); treeContainer.appendChild(infoEl); updateTreeInfo(treePlace, infoElId, true); } // fix tooltip of whale/rainbowfish const tooltipTemplate = document.getElementById('tooltip-rawShark'); function createRawFishTooltip(id, name) { const newTooltip = tooltipTemplate.cloneNode(true); newTooltip.id = 'tooltip-' + id; newTooltip.firstChild.textContent = name; newTooltip.lastChild.firstChild.textContent = '+? '; tooltipTemplate.parentElement.appendChild(newTooltip); } createRawFishTooltip('rawWhale', 'Raw Whale'); createRawFishTooltip('rawRainbowFish', 'Raw Rainbowfish'); } /** * improve smelting dialog */ const smeltingRequirements = { 'glass': { sand: 1 , oil: 10 } , 'bronzeBar': { copper: 1 , tin: 1 , oil: 10 } , 'ironBar': { iron: 1 , oil: 100 } , 'silverBar': { silver: 1 , oil: 300 } , 'goldBar': { gold: 1 , oil: 1e3 } }; function improveSmelting() { const amountInput = document.getElementById('input-smelt-bars-amount'); amountInput.type = 'number'; amountInput.min = 0; amountInput.step = 5; function onValueChange(event) { smeltingValue = null; window.selectBar('', '', amountInput, document.getElementById('smelting-furnace-capacity').value); } amountInput.addEventListener('mouseup', onValueChange); amountInput.addEventListener('keyup', onValueChange); amountInput.setAttribute('onkeyup', ''); const _selectBar = window.selectBar; let smeltingValue = null; window.selectBar = (bar, inputElement, inputBarsAmountEl, capacity) => { const requirements = smeltingRequirements[bar]; let maxAmount = capacity; for (let key in requirements) { maxAmount = Math.min(Math.floor(window[key] / requirements[key]), maxAmount); } const value = parseInt(amountInput.value, 10); if (value > maxAmount) { smeltingValue = value; amountInput.value = maxAmount; } else if (smeltingValue != null) { amountInput.value = Math.min(smeltingValue, maxAmount); if (smeltingValue <= maxAmount) { smeltingValue = null; } } return _selectBar(bar, inputElement, inputBarsAmountEl, capacity); }; const _openFurnaceDialogue = window.openFurnaceDialogue; window.openFurnaceDialogue = (furnace) => { if (smeltingBarType == 0) { amountInput.max = getFurnaceCapacity(furnace); } return _openFurnaceDialogue(furnace); }; } /** * add chance to time calculator */ /** * calculates the number of seconds until the event with the given chance happened at least once with the given * probability p (in percent) */ function calcSecondsTillP(chancePerSecond, p) { return Math.round(Math.log(1 - p/100) / Math.log(1 - chancePerSecond)); } function addChanceTooltip(headline, chancePerSecond, elId, targetEl) { // ensure tooltip exists and is correctly binded const tooltipEl = ensureTooltip('chance-' + elId, targetEl); // set elements content const percValues = [1, 10, 20, 50, 80, 90, 99]; let percRows = ''; for (let p of percValues) { percRows += ` <tr> <td>${p}%</td> <td>${formatTime2NearestUnit(calcSecondsTillP(chancePerSecond, p), true)}</td> </tr>`; } tooltipEl.innerHTML = `<h2>${headline}</h2> <table class="chance"> <tr> <th>Probability</th> <th>Time</th> </tr> ${percRows} </table> `; } function chance2TimeCalculator() { addStyle(` table.chance { border-spacing: 0; } table.chance th { border-bottom: 1px solid gray; } table.chance td:first-child { border-right: 1px solid gray; text-align: center; } table.chance th, table.chance td { padding: 4px 8px; } table.chance tr:nth-child(2n) td { background-color: white; } `); const _clicksShovel = window.clicksShovel; window.clicksShovel = () => { _clicksShovel(); const shovelChance = document.getElementById('dialogue-shovel-chance'); const titleEl = shovelChance.parentElement; const chance = 1/window.getChanceOfDiggingSand(); addChanceTooltip('One sand every:', chance, 'shovel', titleEl); }; // depends on fishingXp const _clicksFishingRod = window.clicksFishingRod; window.clicksFishingRod = () => { _clicksFishingRod(); const fishList = ['shrimp', 'sardine', 'tuna', 'swordfish', 'shark']; for (let fish of fishList) { const rawFish = 'raw' + fish[0].toUpperCase() + fish.substr(1); const row = document.getElementById('dialogue-fishing-rod-tr-' + rawFish); const chance = row.cells[4].textContent .replace(/[^\d\/]/g, '') .split('/') .reduce((p, c) => p / parseInt(c, 10), 1) ; addChanceTooltip(`One raw ${fish} every:`, chance, rawFish, row); } }; } /** * add tooltips for recipes */ function updateRecipeTooltips(recipeKey, recipes) { const table = document.getElementById('table-' + recipeKey + '-recipe'); const rows = table.rows; for (let i = 1; i < rows.length; i++) { const row = rows[i]; const key = row.id.replace(recipeKey + '-', ''); const recipe = recipes[key]; const requirementCell = row.cells[3]; requirementCell.title = recipe.recipe .map((name, i) => { return formatNumber(recipe.recipeCost[i]) + ' ' + name.replace(/[A-Z]/g, (match) => ' ' + match.toLowerCase()) ; }) .join(' + ') ; window.$(requirementCell).tooltip(); } } function updateTooltipsOnReinitRecipes(key) { const capitalKey = key[0].toUpperCase() + key.substr(1); const processKey = 'process' + capitalKey + 'Tab'; const _processTab = window[processKey]; window[processKey] = () => { const reinit = !!window['refreshLoad' + capitalKey + 'Table']; _processTab(); if (reinit) { updateRecipeTooltips(key, window[key + 'Recipes']); } }; } function addRecipeTooltips() { updateTooltipsOnReinitRecipes('crafting'); updateTooltipsOnReinitRecipes('brewing'); updateTooltipsOnReinitRecipes('magic'); updateTooltipsOnReinitRecipes('cooksBook'); } /** * fix formatting of numbers */ function prepareRecipeForTable(recipe) { // create a copy of the recipe to prevent requirement check from failing const newRecipe = JSON.parse(JSON.stringify(recipe)); newRecipe.recipeCost = recipe.recipeCost.map(cost => formatNumber(cost)); newRecipe.xp = formatNumber(recipe.xp); return newRecipe; } function fixNumberFormat() { const _addRecipeToBrewingTable = window.addRecipeToBrewingTable; window.addRecipeToBrewingTable = (brewingRecipe) => { _addRecipeToBrewingTable(prepareRecipeForTable(brewingRecipe)); }; const _addRecipeToMagicTable = window.addRecipeToMagicTable; window.addRecipeToMagicTable = (magicRecipe) => { _addRecipeToMagicTable(prepareRecipeForTable(magicRecipe)); }; } /** * style tweaks */ function addTweakStyle(setting, style) { const prefix = 'body.' + setting; addStyle( style .replace(/(^\s*|,\s*|\}\s*)([^\{\},]+)(,|\s*\{)/g, '$1' + prefix + ' $2$3') ); document.body.classList.add(setting); } function tweakStyle() { // tweak oil production/consumption addTweakStyle('tweak-oil', ` span#oil-flow-values { margin-left: .5em; padding-left: 2rem; position: relative; } #oil-flow-values > span:nth-child(-n+2) { font-size: 0px; position: absolute; left: 0; top: -0.75rem; visibility: hidden; } #oil-flow-values > span:nth-child(-n+2) > span { font-size: 1rem; visibility: visible; } #oil-flow-values > span:nth-child(2) { top: 0.75rem; } #oil-flow-values span[data-item-display="oilIn"]::before { content: '+'; } #oil-flow-values span[data-item-display="oilOut"]::before { content: '-'; } `); // make room for oil cell on small devices const oilFlowValues = document.getElementById('oil-flow-values'); oilFlowValues.parentElement.style.width = '30%'; addTweakStyle('no-select', ` table.tab-bar, span.item-box, div.farming-patch, div.farming-patch-locked, div#tab-sub-container-combat > span, table.top-links a, #hero-area > div:last-child { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } `); } /** * init */ function init() { initSettings(); temporaryFixes(); hideCraftedRecipes(); improveItemBoxes(); fixWoodcutting(); initChat(); improveTimer(); improveSmelting(); chance2TimeCalculator(); addRecipeTooltips(); fixNumberFormat(); tweakStyle(); } document.addEventListener('DOMContentLoaded', () => { const _doCommand = window.doCommand; window.doCommand = (data) => { const values = data.split('=')[1]; if (data.startsWith('REFRESH_ITEMS=')) { const itemDataValues = values.split(';'); const itemArray = []; for (var i = 0; i < itemDataValues.length; i++) { const [key, newValue] = itemDataValues[i].split('~'); if (updateValue(key, newValue)) { itemArray.push(key); } } window.refreshItemValues(itemArray, false); if (window.firstLoadGame) { window.loadInitial(); window.firstLoadGame = false; init(); } else { window.clientGameLoop(); } return; } else if (data.startsWith('CHAT=')) { var parts = data.substr(5).split('~'); return newAddToChatBox(parts[0], parts[1], parts[2], parts[3], 0); } return _doCommand(data); }; }); /** * fix web socket errors */ function webSocketLoaded(event) { if (window.webSocket == null) { console.error('no webSocket instance found!'); return; } const messageQueue = []; const _onMessage = webSocket.onmessage; webSocket.onmessage = (event) => messageQueue.push(event); document.addEventListener('DOMContentLoaded', () => { messageQueue.forEach(event => onMessage(event)); webSocket.onmessage = _onMessage; }); const commandQueue = []; const _sendBytes = window.sendBytes; window.sendBytes = (command) => commandQueue.push(command); const _onOpen = webSocket.onopen; webSocket.onopen = (event) => { window.sendBytes = _sendBytes; commandQueue.forEach(command => window.sendBytes(command)); return _onOpen(event); }; } function isWebSocketScript(script) { return script.src.includes('socket.js'); } function fixWebSocketScript() { if (!document.head) { return; } const scripts = document.head.querySelectorAll('script'); let found = false; for (let i = 0; i < scripts.length; i++) { if (isWebSocketScript(scripts[i])) { // does this work? scripts[i].onload = webSocketLoaded; return; } } // create an observer instance const mutationObserver = new MutationObserver((mutationList) => { mutationList.forEach((mutation) => { if (mutation.addedNodes.length === 0) { return; } for (let i = 0; i < mutation.addedNodes.length; i++) { const node = mutation.addedNodes[i]; if (node.tagName == 'SCRIPT' && isWebSocketScript(node)) { mutationObserver.disconnect(); node.onload = webSocketLoaded; return; } } }); }); mutationObserver.observe(document.head, { childList: true }); } fixWebSocketScript(); // fix scrollText (e.g. when joining the game and receiving xp at that moment) window.mouseX = window.innerWidth / 2; window.mouseY = window.innerHeight / 2; })();