DH2 Fixed

Improve Diamond Hunt 2

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

// ==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;
})();