DH2 Fixed

Improve Diamond Hunt 2

目前為 2017-03-18 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         DH2 Fixed
// @namespace    FileFace
// @description  Improve Diamond Hunt 2
// @version      0.64.6
// @author       Zorbing
// @grant        none
// @run-at       document-start
// @include      http://www.diamondhunt.co/game.php
// ==/UserScript==

(function ()
{
'use strict';
/**
 * observer
 */
var observedKeys = new Map();
/**
 * Observes the given key for change
 *
 * @param {string|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 (var _i = 0, key_1 = key; _i < key_1.length; _i++) {
			var k = key_1[_i];
			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) {
		var ret = [];
		for (var _i = 0, key_2 = key; _i < key_2.length; _i++) {
			var k = key_2[_i];
			ret.push(unobserve(k, fn));
		}
		return ret;
	}
	if (!observedKeys.has(key)) {
		return false;
	}
	return observedKeys.get(key).delete(fn);
}
function updateValue(key, newValue) {
	// convert the value to number if possible
	if (!isNaN(newValue)) {
		newValue = Number(newValue);
	}
	// thanks aguyd: don't use identity operator (===) here
	if (window[key] == newValue) {
		return false;
	}
	var oldValue = window[key];
	window[key] = newValue;
	if (observedKeys.has(key)) {
		observedKeys.get(key).forEach(function (fn) { return fn(key, oldValue, newValue); });
	}
	return true;
}
/**
 * global constants
 */
var tierLevels = ['empty', 'sapphire', 'emerald', 'ruby', 'diamond'];
var tierNames = ['Standard', 'Sapphire', 'Emerald', 'Ruby', 'Diamond'];
var tierItemList = ['pickaxe', 'shovel', 'hammer', 'axe', 'rake', 'fishingRod'];
var furnaceLevels = ['stone', 'bronze', 'iron', 'silver', 'gold'];
var furnaceCapacity = [10, 30, 75, 150, 300];
var ovenLevels = ['bronze', 'iron', 'silver', 'gold'];
var maxOilStorageLevel = 5;
var oilStorageSize = [10e3, 50e3, 100e3, 300e3, 600e3];
/**
 * general functions
 */
var 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 (typeof num === 'number' ? num : Number(num)).toLocaleString('en');
}
function formatNumbersInText(text) {
	return text.replace(/\d(?:[\d',\.]*\d)?/g, function (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) {
	if (typeof timer === 'string') {
		timer = parseInt(timer, 10);
	}
	var hours = Math.floor(timer / 3600);
	var minutes = Math.floor((timer % 3600) / 60);
	var seconds = timer % 60;
	return padLeft(hours, '0') + ':' + padLeft(minutes, '0') + ':' + padLeft(seconds, '0');
}
var 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) {
	if (long === void 0) { long = false; }
	var step = timeSteps[0];
	for (var i = timeSteps.length - 1; i > 0; i--) {
		if (time >= timeSteps[i].threshold) {
			step = timeSteps[i];
			break;
		}
	}
	var factor = Math.pow(10, step.padp);
	var num = Math.round(time / step.threshold * factor) / factor;
	var unit = long ? step.name + (num === 1 ? '' : 's') : step.short;
	return num + ' ' + unit;
}
function ensureTooltip(id, target) {
	var tooltipId = 'tooltip-' + id;
	var 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;
		window.$(target).bind({
			mousemove: window.changeTooltipPosition,
			mouseenter: window.showTooltip,
			mouseleave: window.hideTooltip
		});
	}
	return tooltipEl;
}
/**
 * persistence store
 */
var store;
(function (store) {
	var storePrefix = 'dh2-';
	function get(key) {
		var value = localStorage.getItem(storePrefix + key);
		try {
			return JSON.parse(value);
		}
		catch (e) { }
		return value;
	}
	store.get = get;
	function has(key) {
		return localStorage.hasOwnProperty(storePrefix + key);
	}
	store.has = has;
	function persist(key, value) {
		localStorage.setItem(storePrefix + key, JSON.stringify(value));
	}
	store.persist = persist;
	function remove(key) {
		localStorage.removeItem(storePrefix + key);
	}
	store.remove = remove;
})(store || (store = {}));
var settings = {
	hideCraftingRecipes: {
		name: 'Hide crafting recipes of finished items',
		title: "Hides crafting recipes of:\n\t\t\t<ul style=\"margin: .5rem 0 0;\">\n\t\t\t\t<li>furnace, oil storage and oven recipes if they aren't better than the current level</li>\n\t\t\t\t<li>machines if the user has the maximum amount of this type (counts bound and unbound items)</li>\n\t\t\t\t<li>non-stackable items which the user already owns (counts bound and unbound items)</li>\n\t\t\t</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
	}
};
/**
 * settings
 */
function getSettingName(key) {
	return 'setting.' + key;
}
var 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 (!observedSettings.has(key)) {
		return false;
	}
	return observedSettings.get(key).delete(fn);
}
function getSetting(key) {
	if (!settings.hasOwnProperty(key)) {
		return;
	}
	var name = getSettingName(key);
	return store.has(name) ? store.get(name) : settings[key].defaultValue;
}
function setSetting(key, newValue) {
	if (!settings.hasOwnProperty(key)) {
		return;
	}
	var oldValue = getSetting(key);
	store.persist(getSettingName(key), newValue);
	if (oldValue !== newValue && observedSettings.has(key)) {
		observedSettings.get(key).forEach(function (fn) { return fn(key, oldValue, newValue); });
	}
}
function initSettings() {
	var settingsTableId = 'd2h-settings';
	var settingIdPrefix = 'dh2-setting-';
	addStyle("\ntable.table-style1 tr:not([onclick])\n{\n\tcursor: initial;\n}\n#tab-container-profile h2.section-title\n{\n\tcolor: orange;\n\tline-height: 1.2rem;\n\tmargin-top: 2rem;\n}\n#tab-container-profile h2.section-title > span.note\n{\n\tfont-size: 0.9rem;\n}\n#" + settingsTableId + " tr.reload td:first-child::after\n{\n\tcontent: '*';\n\tfont-weight: bold;\n\tmargin-left: 3px;\n}\n\t");
	function insertAfter(newChild, oldChild) {
		var 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';
	}
	var profileTable = document.getElementById('profile-toggleTable');
	var settingsHeader = document.createElement('h2');
	settingsHeader.className = 'section-title';
	settingsHeader.innerHTML = "Userscript \"DH2 Fixed\"<br>\n\t\t<span class=\"note\">(* changes require reloading the tab)</span>";
	insertAfter(settingsHeader, profileTable);
	var settingsTable = document.createElement('table');
	settingsTable.id = settingsTableId;
	settingsTable.className = 'table-style1';
	settingsTable.width = '40%';
	settingsTable.innerHTML = "\n\t<tr style=\"background-color:grey;\">\n\t\t<th>Setting</th>\n\t\t<th>Enabled</th>\n\t</tr>\n\t";
	var _loop_1 = function (key) {
		var setting = settings[key];
		var settingId = settingIdPrefix + key;
		var row = settingsTable.insertRow(-1);
		row.classList.add('setting');
		if (setting.requiresReload) {
			row.classList.add('reload');
		}
		row.setAttribute('onclick', '');
		row.innerHTML = "\n\t\t<td>" + setting.name + "</td>\n\t\t<td><img src=\"" + getCheckImageSrc(getSetting(key)) + "\" id=\"" + settingId + "\" class=\"image-icon-20\"></td>\n\t\t";
		var 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 changes to this setting.</span>";
		}
		row.addEventListener('click', function () {
			var newValue = !getSetting(key);
			setSetting(key, newValue);
			document.getElementById(settingId).src = getCheckImageSrc(newValue);
		});
	};
	for (var key in settings) {
		_loop_1(key);
	}
	insertAfter(settingsTable, settingsHeader);
}
/**
 * hide crafting recipes of lower tiers or of maxed machines
 */
function setRecipeVisibility(key, visible) {
	var recipeRow = document.getElementById('crafting-' + key);
	if (recipeRow) {
		recipeRow.style.display = (!getSetting('hideCraftingRecipes') || visible) ? '' : 'none';
	}
}
function hideLeveledRecipes(max, getKey, init) {
	if (init === void 0) { init = false; }
	var keys2Observe = [];
	var maxLevel = 0;
	for (var i = max - 1; i >= 0; i--) {
		var level = i + 1;
		var key = getKey(i);
		var 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, function () { return hideLeveledRecipes(max, getKey, false); });
	}
}
function hideToolRecipe(key, init) {
	if (init === void 0) { init = false; }
	var emptyKey = getTierKey(key, 0);
	var keys2Observe = [emptyKey];
	var hasTool = window[emptyKey] > 0;
	for (var i = 0; i < tierLevels.length; i++) {
		var boundKey = getBoundKey(getTierKey(key, i));
		hasTool = hasTool || window[boundKey] > 0;
		keys2Observe.push(boundKey);
	}
	setRecipeVisibility(emptyKey, !hasTool);
	if (init) {
		observe(keys2Observe, function () { return hideToolRecipe(key, false); });
	}
}
function hideRecipe(key, max, init) {
	if (init === void 0) { init = false; }
	var maxValue = typeof max === 'function' ? max() : max;
	var boundKey = getBoundKey(key);
	var unbound = window[key];
	var bound = window[boundKey];
	setRecipeVisibility(key, (bound + unbound) < maxValue);
	if (init) {
		observe([key, boundKey], function () { return hideRecipe(key, max, false); });
	}
}
function hideCraftedRecipes() {
	function processRecipes(init) {
		if (init === void 0) { init = false; }
		// furnace
		hideLeveledRecipes(furnaceLevels.length, function (i) { return furnaceLevels[i] + 'Furnace'; }, init);
		// oil storage
		hideLeveledRecipes(7, function (i) { return 'oilStorage' + (i + 1); }, init);
		// oven recipes
		hideLeveledRecipes(ovenLevels.length, function (i) { return 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);
		// stuff
		// thanks aguyd
		hideRecipe('bonemealBin', 1, init);
		if (init) {
			observeSetting('hideCraftingRecipes', function () { return processRecipes(false); });
		}
	}
	processRecipes(true);
	var _processCraftingTab = window.processCraftingTab;
	window.processCraftingTab = function () {
		var reinit = !!window.refreshLoadCraftingTable;
		_processCraftingTab();
		if (reinit) {
			processRecipes(false);
		}
	};
}
/**
 * improve item boxes
 */
function hideNumberInItemBox(key, setVisibility) {
	if (setVisibility === void 0) { setVisibility = false; }
	var itemBox = document.getElementById('item-box-' + key);
	var numberElement = itemBox.lastElementChild;
	if (setVisibility) {
		numberElement.style.visibility = 'hidden';
	}
	else {
		numberElement.style.display = 'none';
	}
}
function addSpan2ItemBox(key) {
	hideNumberInItemBox(key);
	var itemBox = document.getElementById('item-box-' + key);
	var 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 (var i = 0; i < furnaceLevels.length; i++) {
		var key = furnaceLevels[i] + 'Furnace';
		var capacitySpan = addSpan2ItemBox(getBoundKey(key));
		capacitySpan.className = 'capacity';
		capacitySpan.textContent = 'Capacity: ' + formatNumber(furnaceCapacity[i]);
	}
	// show oil cap of oil storage
	for (var i = 0; i < maxOilStorageLevel; i++) {
		var key = 'oilStorage' + (i + 1);
		var capSpan = addSpan2ItemBox(getBoundKey(key));
		capSpan.className = 'oil-cap';
		capSpan.textContent = 'Oil cap: ' + formatNumber(oilStorageSize[i]);
	}
	// show oil per second
	var handheldOilSpan = addSpan2ItemBox('handheldOilPump');
	setOilPerSecond(handheldOilSpan, 1 * window.miner);
	observe('miner', function () { return setOilPerSecond(handheldOilSpan, 1 * window.miner); });
	var 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 (var _i = 0, tierItemList_1 = tierItemList; _i < tierItemList_1.length; _i++) {
		var tierItem = tierItemList_1[_i];
		for (var i = 0; i < tierLevels.length; i++) {
			var key = getTierKey(tierItem, i);
			var toolKey = tierItem == 'rake' ? key : getBoundKey(key);
			var tierSpan = addSpan2ItemBox(toolKey);
			tierSpan.className = 'tier';
			tierSpan.textContent = tierNames[i];
		}
	}
	// show boat progress
	var boatKeys = ['rowBoat', 'canoe'];
	var boatTimerKeys = boatKeys.map(function (k) { return k + 'Timer'; });
	function checkBoat(span, timerKey, init) {
		if (init === void 0) { init = false; }
		var isInTransit = window[timerKey] > 0;
		var otherInTransit = boatTimerKeys.some(function (k) { return k != timerKey && window[k] > 0; });
		span.textContent = isInTransit ? 'In transit' : 'Ready';
		span.style.visibility = otherInTransit ? 'hidden' : '';
		if (init) {
			observe(boatTimerKeys, function () { return checkBoat(span, timerKey, false); });
		}
	}
	for (var i = 0; i < boatKeys.length; i++) {
		var span = addSpan2ItemBox(getBoundKey(boatKeys[i]));
		checkBoat(span, boatTimerKeys[i], true);
	}
}
/**
 * fix wood cutting
 */
function fixWoodcutting() {
	addStyle("\nimg.woodcutting-tree-img\n{\n\tborder: 1px solid transparent;\n}\n\t");
}
/**
 * add new chat
 */
function isMuted(user) {
	// return window.mutedPeople.some((name) => user.indexOf(name) > -1);
	return window.mutedPeople.indexOf(user) !== -1;
}
function handleScrolling(chatbox) {
	if (window.isAutoScrolling) {
		setTimeout(function () { return chatbox.scrollTop = chatbox.scrollHeight; });
	}
}
var chatHistoryKey = 'chatHistory';
var maxChatHistoryLength = 100;
var TYPE_RELOAD = -1;
var TYPE_NORMAL = 0;
var TYPE_PM_FROM = 1;
var TYPE_PM_TO = 2;
var 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.
 */
var chunkHidingMinChunks = 10;
var msgChunkSize = 100;
var reloadedChatData = {
	timestamp: 0,
	username: '',
	userlevel: 0,
	icon: 0,
	tag: 0,
	type: TYPE_RELOAD,
	msg: '[...]'
};
// load chat history
var chatHistory = store.get(chatHistoryKey) || [];
// find index of last message which is not a pm
var lastNotPM = chatHistory.slice(0).reverse().find(function (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
var chatInitialized = false;
function processChatData(username, icon, tag, msg, isPM) {
	var userlevel = 0;
	var type = tag == '5' ? TYPE_SERVER_MSG : TYPE_NORMAL;
	if (isPM == 1) {
		var 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') {
		var match = msg.match(/^\s*\((\d+)\): (.+?)\s*$/);
		if (match) {
			userlevel = parseInt(match[1], 10);
			msg = match[2];
		}
		else {
			userlevel = window.getGlobalLevel();
		}
	}
	// turn some critical characters into HTML entities
	msg = msg.replace(/[;=<>]/g, function (char) { return '&#' + char.charCodeAt(0) + ';'; });
	return {
		timestamp: now(),
		username: username,
		userlevel: userlevel,
		icon: parseInt(icon, 10),
		tag: parseInt(tag, 10),
		type: type,
		msg: msg
	};
}
function add2ChatHistory(data) {
	chatHistory.push(data);
	chatHistory = chatHistory.slice(-maxChatHistoryLength);
	store.persist(chatHistoryKey, chatHistory);
}
var chatBoxId = 'div-chat';
var generalChatTabId = 'tab-chat-general';
var generalChatDivId = 'div-chat-area';
var pmChatTabPrefix = 'tab-chat-pm-';
var pmChatDivPrefix = 'div-chat-pm-';
var chatInputId = 'chat-input-text';
function getChatTab(username) {
	var id = username == '' ? generalChatTabId : pmChatTabPrefix + username.replace(/ /g, '_');
	var 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
		var closeSpan = document.createElement('span');
		closeSpan.className = 'close';
		tab.appendChild(closeSpan);
		var chatTabs = document.getElementById('chat-tabs');
		var filler = chatTabs.querySelector('.filler');
		if (filler) {
			chatTabs.insertBefore(tab, filler);
		}
		else {
			chatTabs.appendChild(tab);
		}
	}
	return tab;
}
function getChatDiv(username) {
	var id = username == '' ? generalChatDivId : pmChatDivPrefix + username.replace(/ /g, '_');
	var div = document.getElementById(id);
	if (!div) {
		div = document.createElement('div');
		div.setAttribute('disabled', 'disabled');
		div.id = id;
		div.className = 'div-chat-area';
		var height = document.getElementById(generalChatDivId).style.height;
		div.style.height = height;
		var 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';
	var oldChatDiv = getChatDiv(oldTab.dataset.username);
	oldChatDiv.classList.remove('selected');
	var newChatDiv = getChatDiv(newTab.dataset.username);
	newChatDiv.classList.add('selected');
	var toUsername = newTab.dataset.username;
	var newTextPlaceholder = toUsername == '' ? window.username + ':' : 'PM to ' + toUsername + ':';
	document.getElementById(chatInputId).placeholder = newTextPlaceholder;
	if (window.isAutoScrolling) {
		setTimeout(function () { return newChatDiv.scrollTop = newChatDiv.scrollHeight; });
	}
}
function closeChatTab(username) {
	// TODO: maybe delete pms stored for that user?
	var oldTab = document.querySelector('#chat-tabs .chat-tab.selected');
	var tab2Close = getChatTab(username);
	if (oldTab.dataset.username == username) {
		var generalTab = getChatTab('');
		changeChatTab(tab2Close, generalTab);
	}
	tab2Close.parentElement.removeChild(tab2Close);
}
var 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' }
];
var 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;
}
var locale = 'en-US';
var localeOptions = {
	hour12: false,
	year: 'numeric',
	month: 'long',
	day: 'numeric',
	hour: '2-digit',
	minute: '2-digit',
	second: '2-digit'
};
var msgChunkMap = new Map();
var chatboxFragments = new Map();
function createMessageSegment(data) {
	var isThisPm = isPM(data);
	var msgUsername = data.type === TYPE_PM_TO ? window.username : data.username;
	var historyIndex = chatHistory.indexOf(data);
	var isSameUser = null;
	var isSameTime = null;
	for (var i = historyIndex - 1; i >= 0 && (isSameUser === null || isSameTime === null); i--) {
		var dataBefore = chatHistory[i];
		if (isThisPm && isPM(dataBefore) ||
			!isThisPm && !isPM(dataBefore)) {
			if (isSameUser === null) {
				var 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;
			}
		}
	}
	var d = new Date(data.timestamp);
	var hour = (d.getHours() < 10 ? '0' : '') + d.getHours();
	var minute = (d.getMinutes() < 10 ? '0' : '') + d.getMinutes();
	var icon = chatIcons[data.icon] || { key: '', title: '' };
	var tag = chatTags[data.tag] || { key: '', name: '' };
	var formattedMsg = data.msg
		.replace(/<a href='(.+?)' target='_blank'>\1<\/a>/g, '$1')
		.replace(/(https?:\/\/[^\s"<>]+)/g, '<a target="_blank" href="$1">$1</a>');
	var msgTitle = data.type == TYPE_RELOAD ? 'Chat loaded on ' + d.toLocaleString(locale, localeOptions) : '';
	var user = data.type === TYPE_SERVER_MSG ? 'Server Message' : msgUsername;
	var levelAppendix = data.type == TYPE_NORMAL ? ' (' + data.userlevel + ')' : '';
	var userTitle = data.tag != 5 ? tag.name : '';
	return "<span class=\"chat-msg\" data-type=\"" + data.type + "\" data-tag=\"" + tag.key + "\">"
		+ ("<span\n\t\t\tclass=\"timestamp\"\n\t\t\tdata-timestamp=\"" + data.timestamp + "\"\n\t\t\tdata-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;
	}
	var isThisPm = isPM(data);
	// don't mute pms (you can just ignore pm-tab if you like)
	if (!isThisPm && isMuted(data.username)) {
		return;
	}
	var userKey = isThisPm ? data.username : '';
	var 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
	var chatbox = getChatDiv(userKey);
	var 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);
		}
	}
	var tmp = document.createElement('templateWrapper');
	tmp.innerHTML = createMessageSegment(data);
	msgChunk.appendChild(tmp.children[0]);
	handleScrolling(chatbox);
}
function applyChatStyle() {
	addStyle("\nspan.chat-msg\n{\n\tdisplay: flex;\n\tmargin-bottom: 1px;\n}\nspan.chat-msg:nth-child(2n)\n{\n\tbackground-color: hsla(0, 0%, 90%, 1);\n}\n.chat-msg[data-type=\"" + TYPE_RELOAD + "\"]\n{\n\tfont-size: 0.8rem;\n}\n.chat-msg .timestamp\n{\n\tdisplay: none;\n}\n.chat-msg:not([data-type=\"" + TYPE_RELOAD + "\"]) .timestamp\n{\n\tcolor: hsla(0, 0%, 50%, 1);\n\tdisplay: inline-block;\n\tfont-size: .9rem;\n\tmargin: 0;\n\tmargin-right: 5px;\n\tposition: relative;\n\twidth: 2.5rem;\n}\n.chat-msg .timestamp[data-same-time=\"true\"]\n{\n\tcolor: hsla(0, 0%, 50%, .1);\n}\n.chat-msg:not([data-type=\"" + TYPE_RELOAD + "\"]) .timestamp:hover::after\n{\n\tbackground-color: hsla(0, 0%, 12%, 1);\n\tborder-radius: .2rem;\n\tcontent: attr(data-fulltime);\n\tcolor: hsla(0, 0%, 100%, 1);\n\tline-height: 1.35rem;\n\tpadding: .4rem .8rem;\n\tposition: absolute;\n\tleft: 2.5rem;\n\ttop: -0.4rem;\n\ttext-align: center;\n\twhite-space: nowrap;\n}\n\n.chat-msg[data-type=\"" + TYPE_PM_FROM + "\"] { color: purple; }\n.chat-msg[data-type=\"" + TYPE_PM_TO + "\"] { color: purple; }\n.chat-msg[data-type=\"" + TYPE_SERVER_MSG + "\"] { color: blue; }\n.chat-msg[data-tag=\"contributor\"] { color: green; }\n.chat-msg[data-tag=\"mod\"] { color: #669999; }\n.chat-msg[data-tag=\"dev\"] { color: #666600; }\n.chat-msg:not([data-type=\"" + TYPE_RELOAD + "\"]) .user\n{\n\tflex: 0 0 132px;\n\tmargin-right: 5px;\n\twhite-space: nowrap;\n}\n#" + generalChatDivId + " .chat-msg:not([data-type=\"" + TYPE_RELOAD + "\"]) .user\n{\n\tflex-basis: 182px;\n\tpadding-left: 22px;\n}\n.chat-msg .user[data-same-user=\"true\"]:not([data-name=\"\"])\n{\n\topacity: 0;\n}\n\n.chat-msg .user .icon\n{\n\tmargin-left: -22px;\n}\n.chat-msg .user .icon::before\n{\n\tbackground-size: 20px 20px;\n\tcontent: '';\n\tdisplay: inline-block;\n\tmargin-right: 2px;\n\twidth: 20px;\n\theight: 20px;\n\tvertical-align: middle;\n}\n.chat-msg .user .icon.halloween2015::before\t{ background-image: url('images/chat-icons/1.png'); }\n.chat-msg .user .icon.christmas2015::before\t{ background-image: url('images/chat-icons/2.png'); }\n.chat-msg .user .icon.easter2016::before\t{ background-image: url('images/chat-icons/3.png'); }\n.chat-msg .user .icon.halloween2016::before\t{ background-image: url('images/chat-icons/4.png'); }\n.chat-msg .user .icon.christmas2016::before\t{ background-image: url('images/chat-icons/5.png'); }\n.chat-msg .user .icon.dh1Max::before\t\t{ background-image: url('images/chat-icons/6.png'); }\n.chat-msg .user .icon.hardcore::before\t\t{ background-image: url('images/chat-icons/7.png'); }\n.chat-msg .user .icon.quest::before\t\t\t{ background-image: url('images/chat-icons/8.png'); }\n\n.chat-msg .user .name\n{\n\tcolor: rgba(0, 0, 0, 0.7);\n\tcursor: pointer;\n}\n.chat-msg .user .name.chat-tag-donor::before\n{\n\tbackground-image: url('images/chat-icons/donor.png');\n\tbackground-size: 20px 20px;\n\tcontent: '';\n\tdisplay: inline-block;\n\theight: 20px;\n\twidth: 20px;\n\tvertical-align: middle;\n}\n.chat-msg .user .name.chat-tag-yell\n{\n\tcursor: default;\n}\n.chat-msg .user .name.chat-tag-contributor,\n.chat-msg .user .name.chat-tag-mod,\n.chat-msg .user .name.chat-tag-dev,\n.chat-msg .user .name.chat-tag-yell\n{\n\tcolor: white;\n\tdisplay: inline-block;\n\tfont-size: 10pt;\n\tmargin-top: -1px;\n\tpadding-bottom: 0;\n\ttext-align: center;\n\t/* 2px border, 10 padding */\n\twidth: calc(100% - 2*1px - 2*5px);\n}\n\n.chat-msg[data-type=\"" + TYPE_RELOAD + "\"] .user > *,\n.chat-msg[data-type=\"" + TYPE_PM_FROM + "\"] .user > .icon,\n.chat-msg[data-type=\"" + TYPE_PM_TO + "\"] .user > .icon\n{\n\tdisplay: none;\n}\n\n.chat-msg .msg\n{\n\tmin-width: 0;\n\toverflow: hidden;\n\tword-wrap: break-word;\n}\n\n#div-chat .div-chat-area\n{\n\twidth: 100%;\n\theight: 130px;\n\tdisplay: none;\n}\n#div-chat .div-chat-area.selected\n{\n\tdisplay: block;\n}\n#chat-tabs\n{\n\tdisplay: flex;\n\tmargin: 10px -6px -6px;\n\tflex-wrap: wrap;\n}\n#chat-tabs .chat-tab\n{\n\tbackground-color: gray;\n\tborder-top: 1px solid black;\n\tborder-right: 1px solid black;\n\tcursor: pointer;\n\tdisplay: inline-block;\n\tfont-weight: normal;\n\tpadding: 0.3rem .6rem;\n\tposition: relative;\n}\n#chat-tabs .chat-tab.selected\n{\n\tbackground-color: transparent;\n\tborder-top-color: transparent;\n}\n#chat-tabs .chat-tab.filler\n{\n\tbackground-color: hsla(0, 0%, 90%, 1);\n\tborder-right: 0;\n\tbox-shadow: inset 5px 5px 5px -5px rgba(0, 0, 0, 0.5);\n\tcolor: transparent;\n\tcursor: default;\n\tflex-grow: 1;\n}\n#chat-tabs .chat-tab::after\n{\n\tcolor: white;\n\tcontent: '(' attr(data-new) ')';\n\tfont-size: .9rem;\n\tfont-weight: bold;\n\tmargin-left: .4rem;\n}\n#chat-tabs .chat-tab[data-new=\"0\"]::after\n{\n\tcolor: inherit;\n\tfont-weight: normal;\n}\n#chat-tabs .chat-tab:not(.general).selected::after,\n#chat-tabs .chat-tab:not(.general):hover::after\n{\n\tvisibility: hidden;\n}\n#chat-tabs .chat-tab:not(.general).selected .close::after,\n#chat-tabs .chat-tab:not(.general):hover .close::after\n{\n\tcontent: '\u00D7';\n\tfont-size: 1.5rem;\n\tposition: absolute;\n\ttop: 0;\n\tright: .6rem;\n\tbottom: 0;\n}\n\t");
}
function addIntelligentScrolling() {
	// add checkbox instead of button for toggling auto scrolling
	var btn = document.querySelector('input[value="Toggle Autoscroll"]');
	var checkboxId = 'chat-toggle-autoscroll';
	// create checkbox
	var toggleCheckbox = document.createElement('input');
	toggleCheckbox.type = 'checkbox';
	toggleCheckbox.id = checkboxId;
	toggleCheckbox.checked = true;
	// create label
	var 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
	var isCheckboxId = 'chat-toggle-intelligent-scroll';
	var intScrollCheckbox = document.createElement('input');
	intScrollCheckbox.type = 'checkbox';
	intScrollCheckbox.id = isCheckboxId;
	intScrollCheckbox.checked = true;
	// add label
	var intScrollLabel = document.createElement('label');
	intScrollLabel.htmlFor = isCheckboxId;
	intScrollLabel.textContent = 'Intelligent Scrolling';
	btn.parentNode.appendChild(intScrollCheckbox);
	btn.parentNode.appendChild(intScrollLabel);
	var chatArea = document.getElementById(generalChatDivId);
	var showScrollTextTimeout = null;
	function setAutoScrolling(value, full) {
		if (full === void 0) { full = false; }
		if (window.isAutoScrolling != value) {
			toggleCheckbox.checked = value;
			window.isAutoScrolling = value;
			var icon_1 = 'none';
			var color_1 = value ? 'lime' : 'red';
			var text_1 = (value ? 'En' : 'Dis') + 'abled' + (full ? ' Autoscroll' : '');
			if (full) {
				window.clearTimeout(showScrollTextTimeout);
				showScrollTextTimeout = window.setTimeout(function () { return window.scrollText(icon_1, color_1, text_1); }, 300);
			}
			else {
				window.scrollText(icon_1, color_1, text_1);
			}
			return true;
		}
		return false;
	}
	toggleCheckbox.addEventListener('change', function () {
		setAutoScrolling(this.checked);
		if (this.checked && intScrollCheckbox.checked) {
			chatArea.scrollTop = chatArea.scrollHeight - chatArea.clientHeight;
		}
	});
	var placeholderTemplate = document.createElement('div');
	placeholderTemplate.className = 'placeholder';
	var childStore = new WeakMap();
	function scrollHugeChat() {
		// # of children
		var chunkNum = chatArea.children.length;
		// start chunk hiding at a specific amount of chunks
		if (chunkNum < chunkHidingMinChunks) {
			return;
		}
		var visibleTop = chatArea.scrollTop;
		var visibleBottom = visibleTop + chatArea.clientHeight;
		var referenceTop = visibleTop - window.innerHeight;
		var referenceBottom = visibleBottom + window.innerHeight;
		var top = 0;
		// never hide the last element since its size may change at any time when a new message gets appended
		for (var i = 0; i < chunkNum - 1; i++) {
			var child = chatArea.children[i];
			var height = child.clientHeight;
			var bottom = top + height;
			var isVisible = top >= referenceTop && top <= referenceBottom
				|| bottom >= referenceTop && bottom <= referenceBottom
				|| top < referenceTop && bottom > referenceBottom;
			var isPlaceholder = child.classList.contains('placeholder');
			if (!isVisible && !isPlaceholder) {
				var newPlaceholder = placeholderTemplate.cloneNode(false);
				newPlaceholder.style.height = height + 'px';
				chatArea.replaceChild(newPlaceholder, child);
				childStore.set(newPlaceholder, child);
			}
			else if (isVisible && isPlaceholder) {
				var oldChild = childStore.get(child);
				chatArea.replaceChild(oldChild, child);
				childStore.delete(child);
			}
			top = bottom;
		}
	}
	var delayedScrollStart = null;
	var delayedScrollTimeout = null;
	// does not consider pm tabs; may be changed in a future version?
	chatArea.addEventListener('scroll', function () {
		if (intScrollCheckbox.checked) {
			var scrolled2Bottom = (chatArea.scrollTop + chatArea.clientHeight) >= chatArea.scrollHeight;
			setAutoScrolling(scrolled2Bottom, true);
		}
		var n = now();
		if (delayedScrollStart == null) {
			delayedScrollStart = n;
		}
		if (delayedScrollStart + 300 > n) {
			window.clearTimeout(delayedScrollTimeout);
			delayedScrollTimeout = window.setTimeout(function () {
				delayedScrollStart = null;
				delayedScrollTimeout = null;
				scrollHugeChat();
			}, 50);
		}
	});
}
function clickChatTab(newTab) {
	var oldTab = document.querySelector('#chat-tabs .chat-tab.selected');
	if (newTab == oldTab) {
		return;
	}
	changeChatTab(oldTab, newTab);
}
function clickCloseChatTab(tab) {
	var username = tab.dataset.username;
	var chatDiv = getChatDiv(username);
	if (chatDiv.children.length === 0 ||
		confirm("Do you want to close the pm tab of \"" + username + "\"?")) {
		closeChatTab(username);
	}
}
function addChatTabs() {
	var chatBoxArea = document.getElementById(chatBoxId);
	var chatTabs = document.createElement('div');
	chatTabs.id = 'chat-tabs';
	chatTabs.addEventListener('click', function (event) {
		var 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);
	var generalTab = getChatTab('');
	generalTab.classList.add('general');
	generalTab.classList.add('selected');
	generalTab.textContent = 'Server';
	var generalChatDiv = getChatDiv('');
	generalChatDiv.classList.add('selected');
	// works only if username length of 1 isn't allowed
	var fillerTab = getChatTab('f');
	fillerTab.classList.add('filler');
	fillerTab.textContent = '';
	var _sendChat = window.sendChat;
	window.sendChat = function (inputEl) {
		var msg = inputEl.value;
		var 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) {
	var 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;
	var chatbox = document.getElementById(chatBoxId);
	chatbox.addEventListener('click', function (event) {
		var target = event.target;
		while (target && target.id != chatBoxId && !target.classList.contains('user')) {
			target = target.parentElement;
		}
		if (!target || target.id == chatBoxId) {
			return;
		}
		var username = target.dataset.name || '';
		if (username == window.username || username == '') {
			return;
		}
		var userTab = getChatTab(username);
		clickChatTab(userTab);
		document.getElementById(chatInputId).focus();
	});
	chatbox.addEventListener('mouseover', function (event) {
		var target = event.target;
		if (!target.classList.contains('timestamp') || !target.dataset.timestamp) {
			return;
		}
		var timestamp = parseInt(target.dataset.timestamp, 10);
		target.dataset.fulltime = (new Date(timestamp)).toLocaleDateString(locale, localeOptions);
		target.dataset.timestamp = '';
	});
}
var commands = ['pm', 'mute', 'ipmute'];
function addCommandSuggester() {
	var input = document.getElementById(chatInputId);
	input.addEventListener('keyup', function (event) {
		if (event.key != 'Backspace' && event.key != 'Delete' &&
			input.selectionStart == input.selectionEnd &&
			input.selectionStart == input.value.length &&
			input.value.startsWith('/')) {
			var value_1 = input.value.substr(1);
			var suggestions = commands.filter(function (c) { return c.startsWith(value_1); });
			if (suggestions.length == 1) {
				input.value = '/' + suggestions[0];
				input.selectionStart = 1 + value_1.length;
				input.selectionEnd = input.value.length;
			}
		}
	});
}
var tutorialCmd = 'tutorial';
function addOwnCommands() {
	commands.push(tutorialCmd);
	function processOwnCommands(value) {
		if (!value.startsWith('/')) {
			return value;
		}
		var msgPrefix = '/';
		var msg = value.substr(1);
		if (msg.startsWith('pm')) {
			var split = msg.split(' ');
			msgPrefix = '/' + split.slice(0, 2).join(' ') + ' ';
			msg = split.slice(2).join(' ');
		}
		if (msg.startsWith(tutorialCmd)) {
			// thanks aguyd (https://greasyfork.org/forum/profile/aguyd) for the idea
			var name_1 = msg.substr(tutorialCmd.length).trim();
			msgPrefix = '';
			msg = 'https://www.reddit.com/r/DiamondHunt/comments/5vrufh/diamond_hunt_2_starter_faq/';
			if (name_1.length != 0) {
				// maybe add '@' before the name?
				msg = name_1 + ', ' + msg;
			}
		}
		return msgPrefix + msg;
	}
	var _sendChat = window.sendChat;
	window.sendChat = function (inputEl) {
		inputEl.value = processOwnCommands(inputEl.value);
		_sendChat(inputEl);
	};
}
function initChat() {
	if (!getSetting('useNewChat')) {
		return;
	}
	newChat();
	addIntelligentScrolling();
	addCommandSuggester();
	addOwnCommands();
	var _enlargeChat = window.enlargeChat;
	var chatBoxArea = document.getElementById(chatBoxId);
	function setChatBoxHeight(height) {
		document.getElementById(generalChatDivId).style.height = height;
		var chatDivs = chatBoxArea.querySelectorAll('div[id^="' + pmChatDivPrefix + '"]');
		for (var i = 0; i < chatDivs.length; i++) {
			chatDivs[i].style.height = height;
		}
	}
	window.enlargeChat = function (enlargeB) {
		_enlargeChat(enlargeB);
		var generalChatDiv = document.getElementById(generalChatDivId);
		var height = generalChatDiv.style.height;
		store.persist('chat.height', height);
		setChatBoxHeight(height);
		handleScrolling(generalChatDiv);
	};
	setChatBoxHeight(store.get('chat.height'));
	// TEMP >>> (due to a naming issue, migrate the data)
	var oldChatHistoryKey = 'chatHistory2';
	var oldChatHistory = store.get(oldChatHistoryKey);
	if (oldChatHistory != null) {
		store.persist(chatHistoryKey, oldChatHistory);
		store.remove(oldChatHistoryKey);
	}
	// TEMP <<<
	// add history to chat
	chatHistory.forEach(function (d) { return add2Chat(d); });
	chatboxFragments.forEach(function (fragment, key) {
		var chatbox = getChatDiv(key);
		chatbox.appendChild(fragment);
	});
	chatboxFragments = null;
	// reset the new counter for all tabs
	var tabs = document.querySelectorAll('.chat-tab');
	for (var i = 0; i < tabs.length; i++) {
		tabs[i].dataset.new = '0';
	}
}
/**
 * hopefully only temporary fixes
 */
function temporaryFixes() {
	// fix grow time of some seeds
	var seeds = {
		'limeLeafSeeds': {
			replace: '1 hour',
			replaceWith: '1 hour and 30 minutes'
		}
	};
	for (var seedName in seeds) {
		var tooltip = document.getElementById('tooltip-' + seedName);
		var timeNode = tooltip.lastElementChild.lastChild;
		var seed = seeds[seedName];
		timeNode.textContent = timeNode.textContent.replace(seed.replace, seed.replaceWith);
	}
	// fix exhaustion timer and updating brewing and cooking recipes
	var _clientGameLoop = window.clientGameLoop;
	window.clientGameLoop = function () {
		_clientGameLoop();
		setHeroClickable();
		if (window.isInCombat() && window.combatCommenceTimer != 0) {
			document.getElementById('combat-countdown').style.display = '';
		}
		if (document.getElementById('tab-container-combat').style.display != 'none') {
			window.combatNotFightingTick();
		}
		if (window.currentOpenTab == 'brewing') {
			window.processBrewingTab();
		}
		if (window.currentOpenTab == 'cooksBook') {
			window.processCooksBookTab();
		}
	};
	// fix elements of scrollText (e.g. when joining the game and receiving xp at that moment)
	var textEls = document.querySelectorAll('div.scroller');
	for (var i = 0; i < textEls.length; i++) {
		var scroller = textEls[i];
		if (scroller.style.position != 'absolute') {
			scroller.style.display = 'none';
		}
	}
	// fix style of tooltips
	addStyle("\nbody > div.tooltip > h2:first-child\n{\n\tmargin-top: 0;\n\tfont-size: 20pt;\n\tfont-weight: normal;\n}\n\t");
	// fix buiulding magic table dynamically
	window.refreshLoadMagicTable = true;
	var _processMagicTab = window.processMagicTab;
	window.processMagicTab = function () {
		var _refreshLoadCraftingTable = window.refreshLoadCraftingTable;
		window.refreshLoadCraftingTable = window.refreshLoadMagicTable;
		_processMagicTab();
		window.refreshLoadCraftingTable = _refreshLoadCraftingTable;
	};
	// update hero being clickable in combat
	function setHeroClickable() {
		var heroArea = document.getElementById('hero-area');
		var equipment = heroArea.lastElementChild;
		equipment.style.pointerEvents = window.isInCombat() ? 'none' : '';
	}
}
/**
 * improve timer
 */
function improveTimer() {
	window.formatTime = function (seconds) {
		return formatTimer(seconds);
	};
	window.formatTimeShort2 = function (seconds) {
		return formatTimer(seconds);
	};
	addStyle("\n#notif-smelting > span:not(.timer)\n{\n\tdisplay: none;\n}\n\t");
	var smeltingNotifBox = document.getElementById('notif-smelting');
	var smeltingTimerEl = document.createElement('span');
	smeltingTimerEl.className = 'timer';
	smeltingNotifBox.appendChild(smeltingTimerEl);
	function updateSmeltingTimer() {
		var totalTime = window.smeltingPercD;
		var elapsedTime = window.smeltingPercN;
		smeltingTimerEl.textContent = formatTimer(Math.max(totalTime - elapsedTime, 0));
	}
	observe('smeltingPercD', function () { return updateSmeltingTimer(); });
	observe('smeltingPercN', function () { return updateSmeltingTimer(); });
	updateSmeltingTimer();
	// add tree grow timer
	addStyle("\n/* hide timer elements of DH2QoL, because I can :P */\n.woodcutting-tree > *:not(img):not(.timer)\n{\n\tdisplay: none;\n}\n.woodcutting-tree > div.timer\n{\n\tcolor: white;\n\tmargin-top: 5px;\n\tpointer-events: none;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n}\n\t");
	var 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) {
		if (init === void 0) { init = false; }
		var infoEl = document.getElementById(infoElId);
		var nameEl = infoEl.firstElementChild;
		var timerEl = infoEl.lastElementChild;
		var idKey = 'treeId' + place;
		var growTimerKey = 'treeGrowTimer' + place;
		var lockedKey = 'treeUnlocked' + place;
		var info = treeInfo[window[idKey]];
		if (!info) {
			var isLocked = place > 4 && window[lockedKey] == 0;
			nameEl.textContent = isLocked ? 'Locked' : 'Empty';
			timerEl.textContent = '';
		}
		else {
			nameEl.textContent = info.name;
			var remainingTime = info.growTime - window[growTimerKey];
			timerEl.textContent = remainingTime > 0 ? '(' + formatTimer(remainingTime) + ')' : 'Fully grown';
		}
		if (init) {
			observe([idKey, growTimerKey, lockedKey], function () { return updateTreeInfo(place, infoElId, false); });
		}
	}
	for (var i = 0; i < 6; i++) {
		var treePlace = i + 1;
		var infoElId = 'wc-tree-timer-' + treePlace;
		var treeContainer = document.getElementById('wc-div-tree-' + treePlace);
		treeContainer.style.position = 'relative';
		var infoEl = document.createElement('div');
		infoEl.className = 'timer';
		infoEl.id = infoElId;
		var treeName = document.createElement('div');
		treeName.style.fontSize = '1.2rem';
		infoEl.appendChild(treeName);
		var treeTimer = document.createElement('div');
		infoEl.appendChild(treeTimer);
		treeContainer.appendChild(infoEl);
		updateTreeInfo(treePlace, infoElId, true);
	}
	// fix tooltip of whale/rainbowfish
	var tooltipTemplate = document.getElementById('tooltip-rawShark');
	function createRawFishTooltip(id, name) {
		var 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
 */
var 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() {
	var amountInput = document.getElementById('input-smelt-bars-amount');
	amountInput.type = 'number';
	amountInput.min = '0';
	amountInput.step = '5';
	function onValueChange() {
		smeltingValue = null;
		window.selectBar('', null, amountInput, document.getElementById('smelting-furnace-capacity').value);
	}
	amountInput.addEventListener('mouseup', onValueChange);
	amountInput.addEventListener('keyup', onValueChange);
	amountInput.setAttribute('onkeyup', '');
	var _selectBar = window.selectBar;
	var smeltingValue = null;
	window.selectBar = function (bar, inputElement, inputBarsAmountEl, capacity) {
		var requirements = smeltingRequirements[bar];
		var maxAmount = parseInt(capacity, 10);
		for (var key in requirements) {
			maxAmount = Math.min(Math.floor(window[key] / requirements[key]), maxAmount);
		}
		var value = parseInt(amountInput.value, 10);
		if (value > maxAmount) {
			smeltingValue = value;
			amountInput.value = maxAmount.toString();
		}
		else if (smeltingValue != null) {
			amountInput.value = Math.min(smeltingValue, maxAmount).toString();
			if (smeltingValue <= maxAmount) {
				smeltingValue = null;
			}
		}
		return _selectBar(bar, inputElement, inputBarsAmountEl, capacity);
	};
	var _openFurnaceDialogue = window.openFurnaceDialogue;
	window.openFurnaceDialogue = function (furnace) {
		if (window.smeltingBarType == 0) {
			amountInput.max = window.getFurnaceCapacity(furnace).toString();
		}
		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
	var tooltipEl = ensureTooltip('chance-' + elId, targetEl);
	// set elements content
	var percValues = [1, 10, 20, 50, 80, 90, 99];
	var percRows = '';
	for (var _i = 0, percValues_1 = percValues; _i < percValues_1.length; _i++) {
		var p = percValues_1[_i];
		percRows += "\n\t\t\t<tr>\n\t\t\t\t<td>" + p + "%</td>\n\t\t\t\t<td>" + formatTime2NearestUnit(calcSecondsTillP(chancePerSecond, p), true) + "</td>\n\t\t\t</tr>";
	}
	tooltipEl.innerHTML = "<h2>" + headline + "</h2>\n\t\t<table class=\"chance\">\n\t\t\t<tr>\n\t\t\t\t<th>Probability</th>\n\t\t\t\t<th>Time</th>\n\t\t\t</tr>\n\t\t\t" + percRows + "\n\t\t</table>\n\t";
}
function chance2TimeCalculator() {
	addStyle("\ntable.chance\n{\n\tborder-spacing: 0;\n}\ntable.chance th\n{\n\tborder-bottom: 1px solid gray;\n}\ntable.chance td:first-child\n{\n\tborder-right: 1px solid gray;\n\ttext-align: center;\n}\ntable.chance th,\ntable.chance td\n{\n\tpadding: 4px 8px;\n}\ntable.chance tr:nth-child(2n) td\n{\n\tbackground-color: white;\n}\n\t");
	var _clicksShovel = window.clicksShovel;
	window.clicksShovel = function () {
		_clicksShovel();
		var shovelChance = document.getElementById('dialogue-shovel-chance');
		var titleEl = shovelChance.parentElement;
		var chance = 1 / window.getChanceOfDiggingSand();
		addChanceTooltip('One sand at least every:', chance, 'shovel', titleEl);
	};
	// depends on fishingXp
	var _clicksFishingRod = window.clicksFishingRod;
	window.clicksFishingRod = function () {
		_clicksFishingRod();
		var fishList = ['shrimp', 'sardine', 'tuna', 'swordfish', 'shark'];
		for (var _i = 0, fishList_1 = fishList; _i < fishList_1.length; _i++) {
			var fish = fishList_1[_i];
			var rawFish = 'raw' + fish[0].toUpperCase() + fish.substr(1);
			var row = document.getElementById('dialogue-fishing-rod-tr-' + rawFish);
			var chance = row.cells[4].textContent
				.replace(/[^\d\/]/g, '')
				.split('/')
				.reduce(function (p, c) { return p / parseInt(c, 10); }, 1);
			addChanceTooltip("One raw " + fish + " at least every:", chance, rawFish, row);
		}
	};
}
/**
 * add tooltips for recipes
 */
function updateRecipeTooltips(recipeKey, recipes) {
	var table = document.getElementById('table-' + recipeKey + '-recipe');
	var rows = table.rows;
	var _loop_2 = function (i) {
		var row = rows.item(i);
		var key = row.id.replace(recipeKey + '-', '');
		var recipe = recipes[key];
		var requirementCell = row.cells.item(3);
		requirementCell.title = recipe.recipe
			.map(function (name, i) {
			return formatNumber(recipe.recipeCost[i]) + ' '
				+ name.replace(/[A-Z]/g, function (match) { return ' ' + match.toLowerCase(); });
		})
			.join(' + ');
		window.$(requirementCell).tooltip();
	};
	for (var i = 1; i < rows.length; i++) {
		_loop_2(i);
	}
}
function updateTooltipsOnReinitRecipes(key) {
	var capitalKey = key[0].toUpperCase() + key.substr(1);
	var processKey = 'process' + capitalKey + 'Tab';
	var _processTab = window[processKey];
	window[processKey] = function () {
		var 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
	var newRecipe = JSON.parse(JSON.stringify(recipe));
	newRecipe.recipeCost = recipe.recipeCost.map(function (cost) { return formatNumber(cost); });
	newRecipe.xp = formatNumber(recipe.xp);
	return newRecipe;
}
function fixNumberFormat() {
	var _addRecipeToBrewingTable = window.addRecipeToBrewingTable;
	window.addRecipeToBrewingTable = function (brewingRecipe) {
		_addRecipeToBrewingTable(prepareRecipeForTable(brewingRecipe));
	};
	var _addRecipeToMagicTable = window.addRecipeToMagicTable;
	window.addRecipeToMagicTable = function (magicRecipe) {
		_addRecipeToMagicTable(prepareRecipeForTable(magicRecipe));
	};
}
/**
 * style tweaks
 */
function addTweakStyle(setting, style) {
	var 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', "\nspan#oil-flow-values\n{\n\tmargin-left: .5em;\n\tpadding-left: 2rem;\n\tposition: relative;\n}\n#oil-flow-values > span:nth-child(-n+2)\n{\n\tfont-size: 0px;\n\tposition: absolute;\n\tleft: 0;\n\ttop: -0.75rem;\n\tvisibility: hidden;\n}\n#oil-flow-values > span:nth-child(-n+2) > span\n{\n\tfont-size: 1rem;\n\tvisibility: visible;\n}\n#oil-flow-values > span:nth-child(2)\n{\n\ttop: 0.75rem;\n}\n#oil-flow-values span[data-item-display=\"oilIn\"]::before\n{\n\tcontent: '+';\n}\n#oil-flow-values span[data-item-display=\"oilOut\"]::before\n{\n\tcontent: '-';\n}\n\t");
	// make room for oil cell on small devices
	var oilFlowValues = document.getElementById('oil-flow-values');
	oilFlowValues.parentElement.style.width = '30%';
	addTweakStyle('no-select', "\ntable.tab-bar,\nspan.item-box,\ndiv.farming-patch,\ndiv.farming-patch-locked,\ndiv#tab-sub-container-combat > span,\ntable.top-links a,\n#hero-area > div:last-child\n{\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n\t");
}
/**
 * init
 */
function init() {
	initSettings();
	temporaryFixes();
	hideCraftedRecipes();
	improveItemBoxes();
	fixWoodcutting();
	initChat();
	improveTimer();
	improveSmelting();
	chance2TimeCalculator();
	addRecipeTooltips();
	fixNumberFormat();
	tweakStyle();
}
document.addEventListener('DOMContentLoaded', function () {
	var _doCommand = window.doCommand;
	window.doCommand = function (data) {
		var values = data.split('=')[1];
		if (data.startsWith('REFRESH_ITEMS=')) {
			var itemDataValues = values.split(';');
			var itemArray = [];
			for (var i = 0; i < itemDataValues.length; i++) {
				var _a = itemDataValues[i].split('~'), keyIdx = _a[0], newValue = _a[1];
				var key = jsItemArray[keyIdx];
				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);
		}
		else if (data.startsWith("PM=")) {
			return newAddToChatBox(window.username, '0', '0', data.substr(3), 1);
		}
		return _doCommand(data);
	};
});
/**
 * fix web socket errors
 */
function webSocketLoaded(event) {
	if (window.webSocket == null) {
		console.error('no webSocket instance found!');
		return;
	}
	var ws = window.webSocket;
	var messageQueue = [];
	var _onMessage = window.webSocket.onmessage;
	ws.onmessage = function (event) { return messageQueue.push(event); };
	document.addEventListener('DOMContentLoaded', function () {
		messageQueue.forEach(function (event) { return window.onMessage(event); });
		ws.onmessage = _onMessage;
	});
	var commandQueue = [];
	var _sendBytes = window.sendBytes;
	window.sendBytes = function (command) { return commandQueue.push(command); };
	var _onOpen = ws.onopen;
	ws.onopen = function (event) {
		window.sendBytes = _sendBytes;
		commandQueue.forEach(function (command) { return window.sendBytes(command); });
		return _onOpen.call(ws, event);
	};
}
function isScriptElement(el) {
	return el.nodeName === 'SCRIPT';
}
function isWebSocketScript(script) {
	return script.src.includes('socket.js');
}
function fixWebSocketScript() {
	if (!document.head) {
		return;
	}
	var scripts = document.head ? document.head.querySelectorAll('script') : [];
	for (var i = 0; i < scripts.length; i++) {
		if (isWebSocketScript(scripts[i])) {
			// does this work?
			scripts[i].onload = webSocketLoaded;
			return;
		}
	}
	// create an observer instance
	var mutationObserver_1 = new MutationObserver(function (mutationList) {
		mutationList.forEach(function (mutation) {
			if (mutation.addedNodes.length === 0) {
				return;
			}
			for (var i = 0; i < mutation.addedNodes.length; i++) {
				var node = mutation.addedNodes[i];
				if (isScriptElement(node) && isWebSocketScript(node)) {
					mutationObserver_1.disconnect();
					node.onload = webSocketLoaded;
					return;
				}
			}
		});
	});
	mutationObserver_1.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;
})();