DH2 Fixed

Improve Diamond Hunt 2

目前為 2017-04-13 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 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.140.1
// @author       Zorbing
// @license      ISC; http://opensource.org/licenses/ISC
// @grant        none
// @run-at       document-start
// @include      http://www.diamondhunt.co/game.php
// ==/UserScript==

/**
 * ISC License (ISC)
 * 
 * Copyright (c) 2017, Martin Boekhoff
 * 
 * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
 * granted, provided that the above copyright notice and this permission notice appear in all copies.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 * 
 * Source: http://opensource.org/licenses/ISC
 */

(function ()
{
'use strict';
var version = '0.140.1';
/**
 * observer
 */
var observer;
(function (observer)
{
	var observedKeys = new Map();

	function add(key, fn)
	{
		if (key instanceof Array)
		{
			for (var _i = 0, key_1 = key; _i < key_1.length; _i++)
			{
				var k = key_1[_i];
				add(k, fn);
			}
		}
		else
		{
			if (!observedKeys.has(key))
			{
				observedKeys.set(key, new Set());
			}
			observedKeys.get(key).add(fn);
		}
		return fn;
	}
	observer.add = add;

	function notify(key, oldValue)
	{
		var newValue = getGameValue(key);
		window[key] = newValue;
		if (observedKeys.has(key))
		{
			observedKeys.get(key).forEach(function (fn)
			{
				return fn(key, oldValue, newValue);
			});
		}
	}
	observer.notify = notify;

	function remove(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(remove(k, fn));
			}
			return ret;
		}
		if (!observedKeys.has(key))
		{
			return false;
		}
		return observedKeys.get(key).delete(fn);
	}
	observer.remove = remove;
})(observer || (observer = {}));
/**
 * global constants
 */
var TIER_LEVELS = ['empty', 'sapphire', 'emerald', 'ruby', 'diamond'];
var TIER_NAMES = ['Standard', 'Sapphire', 'Emerald', 'Ruby', 'Diamond'];
var TIER_ITEMS = ['pickaxe', 'shovel', 'hammer', 'axe', 'rake', 'fishingRod', 'chisel'];
var FURNACE_LEVELS = ['stone', 'bronze', 'iron', 'silver', 'gold'];
var OVEN_LEVELS = ['bronze', 'iron', 'silver', 'gold'];
/** /
function calcSize(level: number)
{
    if (level == 1)
    {
        return 10e3;
    }
    const r = 10e3 * Math.exp(0.805 * level);
    const f = Math.pow(10, Math.floor(Math.log10(r)));
    return Math.round(r / f) * f;
}
/**/
/**
 * next values may be:
 *	- level  6:  1,000,000
 *	- level  7:  3,000,000
 *	- level  8:  6,000,000
 *	- level  9: 10,000,000
 *	- level 10: 30,000,000
 */
var OIL_STORAGE_SIZES = [10e3, 50e3, 100e3, 300e3, 600e3, 1e6, 3e6];
var HIDE_RECIPES = {
	'drills':
	{
		max: 10
	}
	, 'crushers':
	{
		max: 10
	}
	, 'giantDrills':
	{
		max: 10
	}
	, 'excavators':
	{
		max: 10
	}
	, 'oilPipe':
	{
		max: 1
	}
	, 'pumpjacks':
	{
		max: 10
	}
	, 'rowBoat':
	{
		max: 1
	}
	, 'canoe':
	{
		max: 1
	}
	// thanks aguyd
	, 'bonemealBin':
	{
		extraKeys: ['boundFilledBonemealBin']
		, max: 1
	}
	, 'oilFactory':
	{
		max: 1
	}
};
var SMELTING_REQUIREMENTS = {
	'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
	}
};
var SEED_NAME = {
	'1': 'Dark Mushrooms'
	, '2': 'Red Mushrooms'
	, '3': 'Dotted Green Leafs'
	, '4': 'Green Leafs'
	, '5': 'Lime Leafs'
	, '6': 'Gold Leafs'
	, '7': 'Striped Gold Leafs'
	, '8': 'Crystal Leafs'
	, '9': 'Striped Crystal Leafs'
	, '10': 'Blewit Mushrooms'
	, '11': 'Snapegrass'
	, '12': 'Tree'
	, '13': 'Oak Tree'
	, '14': 'Wheat'
	, '15': 'Willow Tree'
	, '16': 'Grass'
	, '17': 'Maple Tree'
	, '18': 'Stardust Tree'
};
var SKILL_LIST = ['mining', 'crafting', 'woodcutting', 'farming', 'brewing', 'combat', 'fishing', 'cooking', 'magic'];
var AREA_LIST = ['fields', 'forests', 'caves', 'volcano'];
var SMELTING_TIME = {
	'glass': 1
	, 'bronzeBar': 1
	, 'ironBar': 5
	, 'silverBar': 10
	, 'goldBar': 30
};
var FISH_XP = {
	'rawShrimp': 50
	, 'rawSardine': 500
	, 'rawTuna': 3e3
	, 'rawSwordfish': 5e3
	, 'rawShark': 12e3
};
var BOAT_LIST = ['rowBoat', 'canoe'];
var format;
(function (format)
{
	var TIME_STEPS = [
	{
		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 zeroPadLeft(num)
	{
		return (num < 10 ? '0' : '') + num;
	}

	function number(num)
	{
		return (typeof num === 'number' ? num : Number(num)).toLocaleString('en');
	}
	format.number = number;

	function numbersInText(text)
	{
		return text.replace(/\d(?:[\d',\.]*\d)?/g, function (numStr)
		{
			return number(Number(numStr.replace(/\D/g, '')));
		});
	}
	format.numbersInText = numbersInText;
	// use time format established in DHQoL (https://greasyfork.org/scripts/16041-dhqol)
	function timer(timer, shorten)
	{
		if (shorten === void 0)
		{
			shorten = true;
		}
		if (typeof timer === 'string')
		{
			timer = parseInt(timer, 10);
		}
		timer = Math.max(timer, 0);
		var hours = Math.floor(timer / 3600);
		var minutes = Math.floor((timer % 3600) / 60);
		var seconds = timer % 60;
		return (shorten && hours === 0 ? '' : zeroPadLeft(hours) + ':')
			+ zeroPadLeft(minutes) + ':'
			+ zeroPadLeft(seconds);
	}
	format.timer = timer;

	function time2NearestUnit(time, long)
	{
		if (long === void 0)
		{
			long = false;
		}
		var step = TIME_STEPS[0];
		for (var i = TIME_STEPS.length - 1; i > 0; i--)
		{
			if (time >= TIME_STEPS[i].threshold)
			{
				step = TIME_STEPS[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;
	}
	format.time2NearestUnit = time2NearestUnit;

	function min2Str(minutes)
	{
		if (typeof minutes === 'string')
		{
			minutes = parseInt(minutes, 10);
		}
		var m = minutes % 60;
		var h = Math.floor(minutes / 60);
		return (h > 0 ? h + ' hour' + (h == 1 ? '' : 's') + ' and ' : '')
			+ m + ' minute' + (m == 1 ? '' : 's');
	}
	format.min2Str = min2Str;
})(format || (format = {}));
/**
 * general functions
 */
function addStyle(styleCode, elId)
{
	var id = elId != null ? 'style-' + elId : null;
	var styleElement = id != null ? document.getElementById(id) : null;
	if (styleElement == null)
	{
		styleElement = document.createElement('style');
		if (id != null)
		{
			styleElement.id = id;
		}
		styleElement.type = 'text/css';
		document.head.appendChild(styleElement);
	}
	styleElement.innerHTML += styleCode;
}

function capitalize(str)
{
	return str[0].toUpperCase() + str.substr(1);
}

function key2Name(key, lowerCase)
{
	if (lowerCase === void 0)
	{
		lowerCase = false;
	}
	var name = key.replace(/[A-Z]/g, function (c)
	{
		return ' ' + (lowerCase ? c.toLowerCase() : c);
	});
	return lowerCase ? name : capitalize(name);
}

function split2Words(str, char)
{
	if (char === void 0)
	{
		char = ' ';
	}
	return str.replace(/[A-Z]/g, char + '$&');
}

function getBoundKey(key)
{
	return 'bound' + capitalize(key);
}

function getTierKey(key, tierLevel)
{
	return TIER_LEVELS[tierLevel] + capitalize(key);
}

function now()
{
	return (new Date()).getTime();
}

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';
		var tooltipList = document.getElementById('tooltip-list');
		tooltipList.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;
}
var timeStr2Sec = (function ()
{
	var unitFactors = {
		'd': 24 * 60 * 60
		, 'h': 60 * 60
		, 'm': 60
		, 's': 1
	};
	return function timeStr2Sec(str)
	{
		return str
			.replace(/(\d+)([hms])/g, function (wholeMatch, num, unit)
			{
				return parseInt(num) * (unitFactors[unit] || 1) + '+';
			})
			.split('+')
			.map(function (s)
			{
				return parseInt(s, 10);
			})
			.filter(function (n)
			{
				return !isNaN(n);
			})
			.reduce(function (p, c)
			{
				return p + c;
			}, 0);
	};
})();

function getGameValue(key)
{
	return window[key];
}

function getFurnaceLevel()
{
	for (var i = FURNACE_LEVELS.length - 1; i >= 0; i--)
	{
		if (getGameValue(getBoundKey(FURNACE_LEVELS[i] + 'Furnace')) > 0)
		{
			return i;
		}
	}
	return -1;
}

function getFurnaceLevelName()
{
	return FURNACE_LEVELS[getFurnaceLevel()] || '';
}

function getPrice(item)
{
	var price = window.getPrice(item);
	if (typeof price === 'number')
	{
		return price;
	}
	var match = price.match(/(\d+)([kM])/);
	if (!match)
	{
		return parseInt(price, 10);
	}
	var FACTORS = {
		'k': 1e3
		, 'M': 1e6
	};
	return parseInt(match[1], 10) * (FACTORS[match[2]] || 1);
}
/**
 * persistence store
 */
var store;
(function (store)
{
	var storePrefix = 'dh2-';

	function get(key)
	{
		var value = localStorage.getItem(storePrefix + key);
		if (value != null)
		{
			try
			{
				return JSON.parse(value);
			}
			catch (e)
			{}
		}
		return value;
	}
	store.get = get;

	function has(key)
	{
		return localStorage.hasOwnProperty(storePrefix + key);
	}
	store.has = has;

	function remove(key)
	{
		localStorage.removeItem(storePrefix + key);
	}
	store.remove = remove;

	function set(key, value)
	{
		localStorage.setItem(storePrefix + key, JSON.stringify(value));
	}
	store.set = set;
})(store || (store = {}));
var settings;
(function (settings)
{
	settings.name = 'settings';
	var KEY;
	(function (KEY)
	{
		KEY[KEY["hideCraftingRecipes"] = 0] = "hideCraftingRecipes";
		KEY[KEY["hideUselessItems"] = 1] = "hideUselessItems";
		KEY[KEY["useNewChat"] = 2] = "useNewChat";
		KEY[KEY["colorizeChat"] = 3] = "colorizeChat";
		KEY[KEY["intelligentScrolling"] = 4] = "intelligentScrolling";
		KEY[KEY["showTimestamps"] = 5] = "showTimestamps";
		KEY[KEY["showIcons"] = 6] = "showIcons";
		KEY[KEY["showTags"] = 7] = "showTags";
		KEY[KEY["showNotifications"] = 8] = "showNotifications";
	})(KEY = settings.KEY || (settings.KEY = {}));;
	var CFG = (_a = {}
		, _a[KEY.hideCraftingRecipes] = {
			name: 'Hide crafting recipes of finished items'
			, description: "Hides crafting recipes of:\n\t\t\t\t<ul style=\"margin: .5rem 0 0;\">\n\t\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\t<li>machines if the user has the maximum amount of this type (counts bound and unbound items)</li>\n\t\t\t\t\t<li>non-stackable items which the user already owns (counts bound and unbound items)</li>\n\t\t\t\t</ul>"
			, defaultValue: true
			, special: true
		}
		, _a[KEY.hideUselessItems] = {
			name: 'Hide useless items'
			, description: "Hides <em>unbound</em> items which may has been crafted accidentially and are of no use for the player:\n\t\t\t\t<ul style=\"margin: .5rem 0 0;\">\n\t\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\t<li>machines if the user has already bound the maximum amount of this type</li>\n\t\t\t\t\t<li>non-stackable items which the user has already bound</li>\n\t\t\t\t</ul>"
			, defaultValue: false
			, special: true
		}
		, _a[KEY.useNewChat] = {
			name: 'Use the new chat'
			, description: "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
			, special: true
		}
		, _a[KEY.colorizeChat] = {
			name: 'Colorize chat messages'
			, description: "Colorize chat messages according to a unique color for each user"
			, defaultValue: false
			, special: true
		}
		, _a[KEY.intelligentScrolling] = {
			name: 'Intelligent scrolling'
			, description: "Autoscroll gets disabled when you scroll up and gets enabled again when you scroll all the way down to the bottom of the chat."
			, defaultValue: true
			, special: true
		}
		, _a[KEY.showTimestamps] = {
			name: 'Show timestamps'
			, description: "Enables showing timestamps in chat"
			, defaultValue: true
			, special: true
		}
		, _a[KEY.showIcons] = {
			name: 'Show user-icons'
			, description: "Enables showing icons (formerly sigils) for each user in chat"
			, defaultValue: true
			, special: true
		}
		, _a[KEY.showTags] = {
			name: 'Show user-tags'
			, description: "Enables showing tags (Dev, Mod, Contributor) and colors for messages in chat"
			, defaultValue: true
			, special: true
		}
		, _a[KEY.showNotifications] = {
			name: 'Show browser notifications'
			, description: "Shows browser notifications for the following events:\n\t\t\t<ul style=\"margin: .5rem 0 0;\">\n\t\t\t\t<li>Smelting finished</li>\n\t\t\t\t<li>Tree fully grown</li>\n\t\t\t\t<li>Plant ready for harvest</li>\n\t\t\t\t<li>Potion effect has ended for: (super) stardust, bar and seed potions</li>\n\t\t\t\t<li>Boat returned (either the row boat or the canoe)</li>\n\t\t\t\t<li>Hero fully recoverd and able to fight again</li>\n\t\t\t\t<li>Items sold on the market</li>\n\t\t\t\t<li>Private chat messages (pms)</li>\n\t\t\t\t<li>Server messages (like <em>Server is restarting...</em>)</li>\n\t\t\t</ul>"
			, defaultValue: true
		}
		, _a);
	var SETTINGS_TABLE_ID = 'd2h-settings';
	var SETTING_ID_PREFIX = 'dh2-setting-';
	/**
	 * settings
	 */
	function toName(key)
	{
		return typeof key === 'string' ? key : KEY[key];
	}

	function getStoreKey(key)
	{
		return 'setting.' + toName(key);
	}
	var observedSettings = new Map();

	function observe(key, fn)
	{
		var n = toName(key);
		if (!observedSettings.has(n))
		{
			observedSettings.set(n, new Set());
		}
		observedSettings.get(n).add(fn);
	}
	settings.observe = observe;

	function unobserve(key, fn)
	{
		var n = toName(key);
		if (!observedSettings.has(n))
		{
			return false;
		}
		return observedSettings.get(n).delete(fn);
	}
	settings.unobserve = unobserve;
	var settingsProxies = new Map();

	function get(key)
	{
		if (!CFG.hasOwnProperty(key))
		{
			return false;
		}
		if (settingsProxies.has(key))
		{
			var proxy = settingsProxies.get(key);
			return proxy.get(key);
		}
		var name = getStoreKey(key);
		return store.has(name) ? store.get(name) : CFG[key].defaultValue;
	}
	settings.get = get;

	function set(key, newValue)
	{
		if (!CFG.hasOwnProperty(key))
		{
			return;
		}
		var oldValue = get(key);
		var n = toName(key);
		if (settingsProxies.has(key))
		{
			var proxy = settingsProxies.get(key);
			proxy.set(key, oldValue, newValue);
		}
		else
		{
			store.set(getStoreKey(key), newValue);
		}
		if (oldValue !== newValue && observedSettings.has(n))
		{
			observedSettings.get(n).forEach(function (fn)
			{
				return fn(key, oldValue, newValue);
			});
		}
	}
	settings.set = set;

	function initSettingsStyle()
	{
		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#" + SETTINGS_TABLE_ID + " tr.reload td:first-child::after\n{\n\tcontent: '*';\n\tfont-weight: bold;\n\tmargin-left: 3px;\n}\n\n.settings-container\n{\n\tlist-style: none;\n\tmargin: 5px 2.5%;\n\tpadding: 0;\n}\n.settings-container > li.setting\n{\n\tbackground-color: silver;\n\tborder: 1px solid black;\n\tborder-top-width: 0;\n}\n.settings-container > li.setting:first-child\n{\n\tborder-top-width: 1px;\n}\n.settings-container > li.setting,\n.settings-container > li.setting *\n{\n\tcursor: pointer;\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n.settings-container > li.setting:hover\n{\n\tbackground-color: gray;\n}\n.settings-container > li.setting > input[type=\"checkbox\"]\n{\n\tdisplay: none;\n}\n.settings-container > li.setting > label\n{\n\tdisplay: block;\n\tpadding: .25rem .5rem;\n}\n.settings-container > li.setting > input + label::before\n{\n\tbackground-image: url(images/icons/x.png);\n\tbackground-size: 20px;\n\tcontent: '';\n\tdisplay: inline-block;\n\theight: 20px;\n\tmargin: 0 .25rem;\n\twidth: 20px;\n\tvertical-align: middle;\n}\n.settings-container > li.setting > input:checked + label::before\n{\n\tbackground-image: url(images/icons/check.png);\n}\n\t\t");
	}

	function initSettingTable()
	{
		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');
		if (!profileTable)
		{
			return;
		}
		var settingsHeader = document.createElement('h2');
		settingsHeader.className = 'section-title';
		settingsHeader.innerHTML = "Userscript \"DH2 Fixed\"<br>\n\t\t\t<span class=\"note\" style=\"display: none;\">(* changes require reloading the tab)</span>";
		var requiresReloadNote = settingsHeader.querySelector('.note');
		insertAfter(settingsHeader, profileTable);
		var settingsTable = document.createElement('table');
		settingsTable.id = SETTINGS_TABLE_ID;
		settingsTable.className = 'table-style1';
		settingsTable.width = '40%';
		settingsTable.innerHTML = "\n\t\t<tr style=\"background-color:grey;\">\n\t\t\t<th>Setting</th>\n\t\t\t<th>Enabled</th>\n\t\t</tr>\n\t\t";

		function addRowClickListener(row, key, settingId)
		{
			row.addEventListener('click', function ()
			{
				var newValue = !get(key);
				set(key, newValue);
				document.getElementById(settingId).src = getCheckImageSrc(newValue);
			});
		}
		for (var k in CFG)
		{
			// convert it into a KEY
			var key = parseInt(k, 10);
			var setting = CFG[key];
			if (setting == null)
			{
				console.error('missing setting entry:', key, toName(key));
				continue;
			}
			if (setting.special)
			{
				continue;
			}
			var settingId = SETTING_ID_PREFIX + toName(key);
			var row = settingsTable.insertRow(-1);
			row.classList.add('setting');
			if (setting.requiresReload)
			{
				row.classList.add('reload');
				requiresReloadNote.style.display = '';
			}
			row.setAttribute('onclick', '');
			row.innerHTML = "\n\t\t\t<td>" + setting.name + "</td>\n\t\t\t<td><img src=\"" + getCheckImageSrc(get(key)) + "\" id=\"" + settingId + "\" class=\"image-icon-20\"></td>\n\t\t\t";
			var tooltipEl = ensureTooltip(settingId, row);
			tooltipEl.innerHTML = setting.description;
			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>";
			}
			addRowClickListener(row, key, settingId);
		}
		insertAfter(settingsTable, settingsHeader);
	}

	function initProxies()
	{
		var row = document.querySelector('tr[data-tooltip-id="tooltip-profile-removeCraftingFilter"]');
		if (row)
		{
			settingsProxies.set(KEY.hideCraftingRecipes
			, {
				get: function (key)
				{
					return getGameValue('profileRemoveCraftingFilter') != 1;
				}
				, set: function (key, oldValue, newValue)
				{
					if (oldValue != newValue)
					{
						row.click();
					}
				}
			});
			observer.add('profileRemoveCraftingFilter', function ()
			{
				set(KEY.hideCraftingRecipes, getGameValue('profileRemoveCraftingFilter') != 1);
			});
		}
	}

	function createSettingsContainer(settingList)
	{
		var settingsContainer = document.createElement('ul');
		settingsContainer.className = 'settings-container';
		for (var _i = 0, settingList_1 = settingList; _i < settingList_1.length; _i++)
		{
			var key = settingList_1[_i];
			var settingId = 'setting-' + split2Words(KEY[key], '-');
			var setting = CFG[key];
			var listEl = document.createElement('li');
			listEl.classList.add('setting');
			if (setting.requiresReload)
			{
				listEl.classList.add('reload');
			}
			var checkbox = document.createElement('input');
			checkbox.type = 'checkbox';
			checkbox.id = settingId;
			checkbox.checked = get(key);
			var label = document.createElement('label');
			label.htmlFor = settingId;
			label.textContent = setting.name;
			(function (key, checkbox)
			{
				checkbox.addEventListener('change', function ()
				{
					set(key, checkbox.checked);
				});
			})(key, checkbox);
			listEl.appendChild(checkbox);
			listEl.appendChild(label);
			settingsContainer.appendChild(listEl);
			var tooltipEl = ensureTooltip(settingId, listEl);
			tooltipEl.innerHTML = setting.description;
			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>";
			}
		}
		return settingsContainer;
	}

	function initCraftingSettings()
	{
		var craftingItems = document.getElementById('tab-sub-container-crafting');
		if (!craftingItems)
		{
			return;
		}
		var br = craftingItems.nextElementSibling;
		var after = br.nextElementSibling;
		var parent = after.parentElement;
		var settingList = [KEY.hideCraftingRecipes, KEY.hideUselessItems];
		var settingsContainer = createSettingsContainer(settingList);
		parent.insertBefore(settingsContainer, after);
	}

	function initChatSettings()
	{
		var controlDiv = document.querySelector('#div-chat > div:first-child');
		if (!controlDiv)
		{
			return;
		}
		var btn = document.createElement('button');
		btn.textContent = 'Chat Settings';
		controlDiv.appendChild(btn);
		var dialog = document.createElement('div');
		dialog.id = 'dialog-chat-settings';
		dialog.style.display = 'none';
		dialog.innerHTML = "<h2 style=\"margin-top: 0\">Chat Settings</h2>";
		var settingList = [KEY.useNewChat, KEY.colorizeChat, KEY.intelligentScrolling, KEY.showTimestamps, KEY.showIcons, KEY.showTags];
		var settingsContainer = createSettingsContainer(settingList);
		dialog.appendChild(settingsContainer);
		document.body.appendChild(dialog);
		btn.addEventListener('click', function ()
		{
			window.$(dialog).dialog();
		});
	}

	function init()
	{
		initProxies();
		initSettingsStyle();
		initSettingTable();
		initCraftingSettings();
		initChatSettings();
	}
	settings.init = init;
	var _a;
})(settings || (settings = {}));
/**
 * Code from https://github.com/davidmerfield/randomColor
 */
var colorGenerator;
(function (colorGenerator)
{
	// seed to get repeatable colors
	var seed = null;
	var COLOR_NOT_FOUND = {
		hueRange: []
		, lowerBounds: []
		, saturationRange: []
		, brightnessRange: []
	};
	var COLOR_BOUNDS = {
		'monochrome':
		{
			hueRange: []
			, lowerBounds: [
				[0, 0]
				, [100, 0]
			]
		}
		, 'red':
		{
			hueRange: [-26, 18]
			, lowerBounds: [
				[20, 100]
				, [30, 92]
				, [40, 89]
				, [50, 85]
				, [60, 78]
				, [70, 70]
				, [80, 60]
				, [90, 55]
				, [100, 50]
			]
		}
		, 'orange':
		{
			hueRange: [19, 46]
			, lowerBounds: [
				[20, 100]
				, [30, 93]
				, [40, 88]
				, [50, 86]
				, [60, 85]
				, [70, 70]
				, [100, 70]
			]
		}
		, 'yellow':
		{
			hueRange: [47, 62]
			, lowerBounds: [
				[25, 100]
				, [40, 94]
				, [50, 89]
				, [60, 86]
				, [70, 84]
				, [80, 82]
				, [90, 80]
				, [100, 75]
			]
		}
		, 'green':
		{
			hueRange: [63, 178]
			, lowerBounds: [
				[30, 100]
				, [40, 90]
				, [50, 85]
				, [60, 81]
				, [70, 74]
				, [80, 64]
				, [90, 50]
				, [100, 40]
			]
		}
		, 'blue':
		{
			hueRange: [179, 257]
			, lowerBounds: [
				[20, 100]
				, [30, 86]
				, [40, 80]
				, [50, 74]
				, [60, 60]
				, [70, 52]
				, [80, 44]
				, [90, 39]
				, [100, 35]
			]
		}
		, 'purple':
		{
			hueRange: [258, 282]
			, lowerBounds: [
				[20, 100]
				, [30, 87]
				, [40, 79]
				, [50, 70]
				, [60, 65]
				, [70, 59]
				, [80, 52]
				, [90, 45]
				, [100, 42]
			]
		}
		, 'pink':
		{
			hueRange: [283, 334]
			, lowerBounds: [
				[20, 100]
				, [30, 90]
				, [40, 86]
				, [60, 84]
				, [80, 80]
				, [90, 75]
				, [100, 73]
			]
		}
	};
	// shared color dictionary
	var colorDictionary = {};

	function defineColor(name, hueRange, lowerBounds)
	{
		var _a = lowerBounds[0]
			, sMin = _a[0]
			, bMax = _a[1];
		var _b = lowerBounds[lowerBounds.length - 1]
			, sMax = _b[0]
			, bMin = _b[1];
		colorDictionary[name] = {
			hueRange: hueRange
			, lowerBounds: lowerBounds
			, saturationRange: [sMin, sMax]
			, brightnessRange: [bMin, bMax]
		};
	}

	function loadColorBounds()
	{
		for (var name_1 in COLOR_BOUNDS)
		{
			defineColor(name_1, COLOR_BOUNDS[name_1].hueRange, COLOR_BOUNDS[name_1].lowerBounds);
		}
	}

	function randomWithin(min, max)
	{
		if (min === void 0)
		{
			min = 0;
		}
		if (max === void 0)
		{
			max = 0;
		}
		if (seed === null)
		{
			return Math.floor(min + Math.random() * (max + 1 - min));
		}
		else
		{
			// seeded random algorithm from http://indiegamr.com/generate-repeatable-random-numbers-in-js/
			seed = (seed * 9301 + 49297) % 233280;
			var rnd = seed / 233280.0;
			return Math.floor(min + rnd * (max - min));
		}
	}

	function getColorInfo(hue)
	{
		// maps red colors to make picking hue easier
		if (hue >= 334 && hue <= 360)
		{
			hue -= 360;
		}
		for (var colorName in colorDictionary)
		{
			var color = colorDictionary[colorName];
			if (color.hueRange.length > 0
				&& hue >= color.hueRange[0]
				&& hue <= color.hueRange[1])
			{
				return colorDictionary[colorName];
			}
		}
		return COLOR_NOT_FOUND;
	}

	function getHueRange(colorInput)
	{
		var number = typeof colorInput === 'undefined' ? Number.NaN : colorInput;
		if (typeof number === 'string')
		{
			number = parseInt(number, 10);
		}
		if (colorInput && isNaN(number) && colorDictionary.hasOwnProperty(colorInput))
		{
			var color = colorDictionary[colorInput];
			if (color.hueRange.length > 0)
			{
				return color.hueRange;
			}
		}
		else if (!isNaN(number) && number < 360 && number > 0)
		{
			return [number, number];
		}
		return [0, 360];
	}

	function pickHue(options)
	{
		var hueRange = getHueRange(options.hue);
		var hue = randomWithin(hueRange[0], hueRange[1]);
		// instead of storing red as two seperate ranges, we group them, using negative numbers
		if (hue < 0)
		{
			return 360 + hue;
		}
		return hue;
	}

	function getSaturationRange(hue)
	{
		return getColorInfo(hue).saturationRange;
	}

	function pickSaturation(hue, options)
	{
		if (options.luminosity === 'random')
		{
			return randomWithin(0, 100);
		}
		if (options.hue === 'monochrome')
		{
			return 0;
		}
		var _a = getSaturationRange(hue)
			, sMin = _a[0]
			, sMax = _a[1];
		switch (options.luminosity)
		{
		case 'bright':
			sMin = 55;
			break;
		case 'dark':
			sMin = sMax - 10;
			break;
		case 'light':
			sMax = 55;
			break;
		}
		return randomWithin(sMin, sMax);
	}

	function getMinimumBrightness(H, S)
	{
		var lowerBounds = getColorInfo(H).lowerBounds;
		for (var i = 0; i < lowerBounds.length - 1; i++)
		{
			var _a = lowerBounds[i]
				, s1 = _a[0]
				, v1 = _a[1];
			var _b = lowerBounds[i + 1]
				, s2 = _b[0]
				, v2 = _b[1];
			if (S >= s1 && S <= s2)
			{
				var m = (v2 - v1) / (s2 - s1);
				var b = v1 - m * s1;
				return m * S + b;
			}
		}
		return 0;
	}

	function pickBrightness(H, S, options)
	{
		var bMin = getMinimumBrightness(H, S);
		var bMax = 100;
		switch (options.luminosity)
		{
		case 'dark':
			bMax = bMin + 20;
			break;
		case 'light':
			bMin = (bMax + bMin) / 2;
			break;
		case 'random':
			bMin = 0;
			bMax = 100;
			break;
		}
		return randomWithin(bMin, bMax);
	}

	function HSVtoHSL(hsv)
	{
		var h = hsv[0];
		var s = hsv[1] / 100;
		var v = hsv[2] / 100;
		var k = (2 - s) * v;
		return [
			h
			, Math.round(s * v / (k < 1 ? k : 2 - k) * 10000) / 100
			, k / 2 * 100
		];
	}

	function HSVtoRGB(hsv)
	{
		// this doesn't work for the values of 0 and 360 here's the hacky fix
		var h = Math.min(Math.max(hsv[0], 1), 359);
		// Rebase the h,s,v values
		h = h / 360;
		var s = hsv[1] / 100;
		var v = hsv[2] / 100;
		var h_i = Math.floor(h * 6);
		var f = h * 6 - h_i;
		var p = v * (1 - s);
		var q = v * (1 - f * s);
		var t = v * (1 - (1 - f) * s);
		var r = 256;
		var g = 256;
		var b = 256;
		switch (h_i)
		{
		case 0:
			r = v;
			g = t;
			b = p;
			break;
		case 1:
			r = q;
			g = v;
			b = p;
			break;
		case 2:
			r = p;
			g = v;
			b = t;
			break;
		case 3:
			r = p;
			g = q;
			b = v;
			break;
		case 4:
			r = t;
			g = p;
			b = v;
			break;
		case 5:
			r = v;
			g = p;
			b = q;
			break;
		}
		return [Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)];
	}

	function HSVtoHex(hsv)
	{
		function componentToHex(c)
		{
			var hex = c.toString(16);
			return hex.length == 1 ? '0' + hex : hex;
		}
		var rgb = HSVtoRGB(hsv);
		return '#' + componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);
	}

	function setFormat(hsv, options)
	{
		switch (options.format)
		{
		case 'hsvArray':
			return hsv;
		case 'hslArray':
			return HSVtoHSL(hsv);
		case 'hsl':
			var hsl = HSVtoHSL(hsv);
			return 'hsl(' + hsl[0] + ', ' + hsl[1] + '%, ' + hsl[2] + '%)';
		case 'hsla':
			var hslColor = HSVtoHSL(hsv);
			var alpha = options.alpha || Math.random();
			return 'hsla(' + hslColor[0] + ', ' + hslColor[1] + '%, ' + hslColor[2] + '%, ' + alpha + ')';
		case 'rgbArray':
			return HSVtoRGB(hsv);
		case 'rgb':
			var rgb = HSVtoRGB(hsv);
			return 'rgb(' + rgb.join(', ') + ')';
		case 'rgba':
			var rgbColor = HSVtoRGB(hsv);
			var alpha = options.alpha || Math.random();
			return 'rgba(' + rgbColor.join(', ') + ', ' + alpha + ')';
		case 'hex':
		default:
			return HSVtoHex(hsv);
		}
	}

	function generateColor(options)
	{
		// pick a hue (H)
		var H = pickHue(options);
		// use H to determine saturation (S)
		var S = pickSaturation(H, options);
		// use S and H to determine brightness (B)
		var B = pickBrightness(H, S, options);
		// return the HSB color in the desired format
		return setFormat([H, S, B], options);
	}

	function getRandom(options)
	{
		options = options ||
		{};
		seed = options.seed == null ? null : options.seed;
		// check if we need to generate multiple colors
		if (options.count !== null && options.count !== undefined)
		{
			var totalColors = options.count;
			var colors = [];
			options.count = null;
			while (totalColors > colors.length)
			{
				// Since we're generating multiple colors, the seed has to be incrememented.
				// Otherwise we'd just generate the same color each time...
				if (seed !== null)
				{
					seed += 1;
				}
				colors.push(generateColor(options));
			}
			options.count = totalColors;
			return colors;
		}
		return generateColor(options);
	}
	colorGenerator.getRandom = getRandom;
	var ColorInterval = (function ()
	{
		function ColorInterval(start, end)
		{
			this.start = start;
			this.end = end;
			this.left = null;
			this.right = null;
			this.value = null;
		}
		ColorInterval.prototype.getNextValue = function ()
		{
			if (this.value == null)
			{
				this.value = (this.start + this.end) / 2;
				return this.value;
			}
			if (this.left == null)
			{
				this.left = new ColorInterval(this.start, this.value);
				return this.left.getNextValue();
			}
			if (this.right == null)
			{
				this.right = new ColorInterval(this.value, this.end);
				return this.right.getNextValue();
			}
			if (this.left.getHeight() <= this.right.getHeight())
			{
				return this.left.getNextValue();
			}
			else
			{
				return this.right.getNextValue();
			}
		};
		ColorInterval.prototype.getHeight = function ()
		{
			return 1
				+ (this.left == null ? 0 : this.left.getHeight())
				+ (this.right == null ? 0 : this.right.getHeight());
		};
		return ColorInterval;
	}());
	var rootInterval = new ColorInterval(0, 360);

	function getEquallyDistributed()
	{
		return 'hsl(' + rootInterval.getNextValue() + ', 100%, 80%)';
	}
	colorGenerator.getEquallyDistributed = getEquallyDistributed;
	// populate the color dictionary
	loadColorBounds();
})(colorGenerator || (colorGenerator = {}));
/**
 * notifications
 */
var notifications;
(function (notifications)
{
	notifications.name = 'notifications';

	function event(title, options)
	{
		if ((!options || options.whenActive !== true) && !document.hidden && document.hasFocus())
		{
			return;
		}
		if (!settings.get(settings.KEY.showNotifications))
		{
			// notifications disabled: return stub notification
			return Promise.resolve(
			{
				close: function () {}
			});
		}
		if (!("Notification" in window))
		{
			return Promise.reject('Your browser does not support notifications.');
		}
		return Notification.requestPermission()
			.then(function (permission)
			{
				if (permission === 'granted')
				{
					var n_1 = new Notification(title, options);
					n_1.onclick = function (event)
					{
						if (options && options.autoFocus !== false)
						{
							window.focus();
						}
						if (options && options.autoClose !== false)
						{
							n_1.close();
						}
						if (options && options.onclick)
						{
							options.onclick(n_1, event);
						}
					};
					return Promise.resolve(n_1);
				}
				else
				{
					return Promise.reject('Notification permission denied');
				}
			});
	}
	notifications.event = event;

	function requestPermission()
	{
		if (settings.get(settings.KEY.showNotifications))
		{
			Notification.requestPermission();
		}
	}

	function init()
	{
		requestPermission();
		settings.observe(settings.KEY.showNotifications, function ()
		{
			return requestPermission();
		});
	}
	notifications.init = init;
})(notifications || (notifications = {}));
/**
 * game events
 */
var gameEvents;
(function (gameEvents)
{
	gameEvents.name = 'gameEvents';
	// min time difference between two notifications with the same title (10 seconds)
	var MIN_TIME_DIFFERENCE = 10;
	gameEvents.enabled = {
		smelting: true
		, chopping: true
		, harvest: true
		, boat: true
		, battle: true
		, brewing: true
		, market: true
	};
	var lastTimestamp = new Map();

	function notifyTabClickable(title, body, icon, tabName)
	{
		var now = (new Date).getTime();
		var timeDiff = now - (lastTimestamp.get(title) || 0);
		if (timeDiff < MIN_TIME_DIFFERENCE * 1e3)
		{
			return;
		}
		if (document.hidden || !document.hasFocus())
		{
			lastTimestamp.set(title, now);
			notifications.event(title
			, {
				body: body
				, icon: 'images/' + icon
				, onclick: function ()
				{
					window.openTab(tabName);
				}
			});
		}
	}

	function observeTimer(k, fn)
	{
		observer.add(k, function (key, oldValue, newValue)
		{
			if (oldValue > 0 && newValue == 0)
			{
				fn(key, oldValue, newValue);
			}
		});
	}

	function smelting()
	{
		observeTimer('smeltingPercD', function (key, oldValue, newValue)
		{
			if (!gameEvents.enabled.smelting)
			{
				return;
			}
			notifyTabClickable('Hot topic', 'Your smelting has finished.', getFurnaceLevelName() + 'Furnace.png', 'crafting');
		});
	}

	function chopping()
	{
		observer.add([
			'treeStage1'
			, 'treeStage2'
			, 'treeStage3'
			, 'treeStage4'
			, 'treeStage5'
			, 'treeStage6'
		], function (key, oldValue, newValue)
		{
			if (!gameEvents.enabled.chopping)
			{
				return;
			}
			if (newValue == 4)
			{
				notifyTabClickable('Wood you be mine?', 'One or more of your trees are fully grown and can be chopped.', 'icons/woodcutting.png', 'woodcutting');
			}
		});
	}

	function harvest()
	{
		observer.add([
			'farmingPatchStage1'
			, 'farmingPatchStage2'
			, 'farmingPatchStage3'
			, 'farmingPatchStage4'
			, 'farmingPatchStage5'
			, 'farmingPatchStage6'
		], function (key, oldValue, newValue)
		{
			if (!gameEvents.enabled.harvest)
			{
				return;
			}
			if (newValue == 4)
			{
				notifyTabClickable('Green thumb', 'One or more of your crops is ready for harvest.', 'icons/watering-can.png', 'farming');
			}
			else if (newValue > 4)
			{
				notifyTabClickable('I didn\'t plant this', 'One or more of your crops died.', 'icons/watering-can.png', 'farming');
			}
		});
	}

	function boat()
	{
		observeTimer([
			'rowBoatTimer'
			, 'canoeTimer'
		], function (k, oldValue, newValue)
		{
			if (!gameEvents.enabled.boat)
			{
				return;
			}
			var key = k.replace(/Timer$/, '');
			notifyTabClickable('Fishy business', 'Your ' + split2Words(key).toLowerCase() + ' returned from its trip.', key + '.png', 'combat');
		});
	}

	function battle()
	{
		observeTimer('combatGlobalCooldown', function (key, oldValue, newValue)
		{
			if (!gameEvents.enabled.battle)
			{
				return;
			}
			notifyTabClickable('Ready to work', 'Your hero is eager to fight.', 'icons/combat.png', 'combat');
		});
	}

	function brewing()
	{
		observeTimer([
			'barPotionTimer'
			, 'seedPotionTimer'
			, 'stardustPotionTimer'
			, 'superStardustPotionTimer'
		], function (key, oldValue, newValue)
		{
			if (!gameEvents.enabled.brewing)
			{
				return;
			}
			var potionKey = key.replace(/Timer$/, '');
			if (getGameValue(potionKey) > 0)
			{
				notifyTabClickable('Cheers!', 'You can drink another ' + split2Words(potionKey) + '.', key.replace(/Timer$/, '') + '.png', 'brewing');
			}
		});
	}

	function market()
	{
		var _refreshMarketSlot = window.refreshMarketSlot;
		window.refreshMarketSlot = function (offerId, itemName, amount, price, collectText, slotId, timeLeft)
		{
			if (gameEvents.enabled.market && collectText > 0)
			{
				notifyTabClickable('Ka-ching', 'You\'ve sold ' + (amount > 0 ? 'some' : 'all') + ' ' + split2Words(itemName).toLowerCase() + ' to the market.', 'icons/shop.png', 'playermarket');
			}
			_refreshMarketSlot(offerId, itemName, amount, price, collectText, slotId, timeLeft);
		};
	}

	function gameValues()
	{
		observer.add('treasureMap', function (key, oldValue, newValue)
		{
			if (oldValue > newValue)
			{
				notifyTabClickable('Arrrr!', 'Your pirate found a treasure map.', 'treasureMap.png', 'items');
			}
		});
	}

	function init()
	{
		smelting();
		chopping();
		harvest();
		boat();
		battle();
		brewing();
		market();
		gameValues();
	}
	gameEvents.init = init;
})(gameEvents || (gameEvents = {}));
/**
 * hide crafting recipes of lower tiers or of maxed machines
 */
var hideCraftedRecipes;
(function (hideCraftedRecipes)
{
	hideCraftedRecipes.name = 'hideCraftedRecipes';

	function setRecipeVisibility(key, visible)
	{
		var recipeRow = document.getElementById('crafting-' + key);
		if (recipeRow)
		{
			recipeRow.style.display = (!settings.get(settings.KEY.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 (getGameValue(key) > 0 || getGameValue(boundKey) > 0)
			{
				maxLevel = Math.max(maxLevel, level);
			}
			setRecipeVisibility(key, level > maxLevel);
		}
		if (init)
		{
			observer.add(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 = getGameValue(emptyKey) > 0;
		for (var i = 0; i < TIER_LEVELS.length; i++)
		{
			var boundKey = getBoundKey(getTierKey(key, i));
			hasTool = hasTool || getGameValue(boundKey) > 0;
			keys2Observe.push(boundKey);
		}
		setRecipeVisibility(emptyKey, !hasTool);
		if (init)
		{
			observer.add(keys2Observe, function ()
			{
				return hideToolRecipe(key, false);
			});
		}
	}

	function hideRecipe(key, hideInfo, init)
	{
		if (init === void 0)
		{
			init = false;
		}
		var maxValue = typeof hideInfo.max === 'function' ? hideInfo.max() : hideInfo.max;
		var boundKey = getBoundKey(key);
		var unbound = getGameValue(key);
		var bound = getGameValue(boundKey);
		var extra = (hideInfo.extraKeys || []).map(function (k)
		{
			return getGameValue(k);
		}).reduce(function (p, c)
		{
			return p + c;
		}, 0);
		setRecipeVisibility(key, (bound + unbound + extra) < maxValue);
		if (init)
		{
			observer.add([key, boundKey], function ()
			{
				return hideRecipe(key, hideInfo, false);
			});
		}
	}

	function init()
	{
		function processRecipes(init)
		{
			if (init === void 0)
			{
				init = false;
			}
			// furnace
			hideLeveledRecipes(FURNACE_LEVELS.length, function (i)
			{
				return FURNACE_LEVELS[i] + 'Furnace';
			}, init);
			// oil storage
			hideLeveledRecipes(OIL_STORAGE_SIZES.length, function (i)
			{
				return 'oilStorage' + (i + 1);
			}, init);
			// oven recipes
			hideLeveledRecipes(OVEN_LEVELS.length, function (i)
			{
				return OVEN_LEVELS[i] + 'Oven';
			}, init);
			// tools
			for (var _i = 0, TIER_ITEMS_1 = TIER_ITEMS; _i < TIER_ITEMS_1.length; _i++)
			{
				var tool = TIER_ITEMS_1[_i];
				hideToolRecipe(tool, init);
			}
			// other stuff
			for (var key in HIDE_RECIPES)
			{
				hideRecipe(key, HIDE_RECIPES[key], init);
			}
			if (init)
			{
				settings.observe(settings.KEY.hideCraftingRecipes, function ()
				{
					return processRecipes(false);
				});
			}
		}
		processRecipes(true);
		var _processCraftingTab = window.processCraftingTab;
		window.processCraftingTab = function ()
		{
			var reinit = !!window.refreshLoadCraftingTable;
			_processCraftingTab();
			if (reinit)
			{
				processRecipes(false);
			}
		};
	}
	hideCraftedRecipes.init = init;
})(hideCraftedRecipes || (hideCraftedRecipes = {}));
/**
 * hide useless items
 */
var hideUselessItems;
(function (hideUselessItems)
{
	hideUselessItems.name = 'hideUselessItems';

	function setItemVisibility(key, visible)
	{
		var itemBox = document.getElementById('item-box-' + key);
		if (itemBox)
		{
			itemBox.style.display = getGameValue(key) > 0 && (!settings.get(settings.KEY.hideUselessItems) || visible) ? '' : 'none';
		}
	}

	function hideLeveledItems(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 (getGameValue(boundKey) > 0)
			{
				maxLevel = Math.max(maxLevel, level);
			}
			setItemVisibility(key, level > maxLevel);
		}
		if (init)
		{
			observer.add(keys2Observe, function ()
			{
				return hideLeveledItems(max, getKey, false);
			});
		}
	}

	function hideItem(key, hideInfo, init)
	{
		if (init === void 0)
		{
			init = false;
		}
		var maxValue = typeof hideInfo.max === 'function' ? hideInfo.max() : hideInfo.max;
		var boundKey = getBoundKey(key);
		var bound = getGameValue(boundKey);
		var extra = (hideInfo.extraKeys || []).map(function (k)
		{
			return getGameValue(k);
		}).reduce(function (p, c)
		{
			return p + c;
		}, 0);
		setItemVisibility(key, (bound + extra) < maxValue);
		if (init)
		{
			observer.add([key, boundKey], function ()
			{
				return hideItem(key, hideInfo, false);
			});
		}
	}

	function init()
	{
		function processItems(init)
		{
			if (init === void 0)
			{
				init = false;
			}
			// furnace
			hideLeveledItems(FURNACE_LEVELS.length, function (i)
			{
				return FURNACE_LEVELS[i] + 'Furnace';
			}, init);
			// oil storage
			hideLeveledItems(OIL_STORAGE_SIZES.length, function (i)
			{
				return 'oilStorage' + (i + 1);
			}, init);
			// oven recipes
			hideLeveledItems(OVEN_LEVELS.length, function (i)
			{
				return OVEN_LEVELS[i] + 'Oven';
			}, init);
			// other stuff
			for (var key in HIDE_RECIPES)
			{
				hideItem(key, HIDE_RECIPES[key], init);
			}
			if (init)
			{
				settings.observe(settings.KEY.hideUselessItems, function ()
				{
					return processItems(false);
				});
			}
		}
		processItems(true);
	}
	hideUselessItems.init = init;
})(hideUselessItems || (hideUselessItems = {}));
/**
 * improve item boxes
 */
var improveItemBoxes;
(function (improveItemBoxes)
{
	improveItemBoxes.name = 'improveItemBoxes';

	function hideNumberInItemBox(key, setVisibility)
	{
		if (setVisibility === void 0)
		{
			setVisibility = false;
		}
		var itemBox = document.getElementById('item-box-' + key);
		if (!itemBox)
		{
			return;
		}
		var numberElement = itemBox.lastElementChild;
		if (!numberElement)
		{
			return;
		}
		if (setVisibility)
		{
			numberElement.style.visibility = 'hidden';
		}
		else
		{
			numberElement.style.display = 'none';
		}
	}

	function addSpan2ItemBox(key)
	{
		hideNumberInItemBox(key);
		var itemBox = document.getElementById('item-box-' + key);
		if (!itemBox)
		{
			return;
		}
		var span = document.createElement('span');
		itemBox.appendChild(span);
		return span;
	}

	function setOilPerSecond(span, oil)
	{
		span.innerHTML = "+ " + format.number(oil) + " L/s <img src=\"images/oil.png\" class=\"image-icon-20\" style=\"margin-top: -2px;\">";
	}
	// show capacity of furnace
	function addFurnaceCaption()
	{
		for (var i = 0; i < FURNACE_LEVELS.length; i++)
		{
			var key = FURNACE_LEVELS[i] + 'Furnace';
			var boundKey = getBoundKey(key);
			var capacitySpan = addSpan2ItemBox(boundKey);
			if (capacitySpan)
			{
				capacitySpan.className = 'capacity';
				capacitySpan.textContent = 'Capacity: ' + format.number(window.getFurnaceCapacity(boundKey));
			}
		}
	}
	// show oil cap of oil storage
	function addOilStorageCaption()
	{
		for (var i = 0; i < OIL_STORAGE_SIZES.length; i++)
		{
			var key = 'oilStorage' + (i + 1);
			var capSpan = addSpan2ItemBox(getBoundKey(key));
			if (capSpan)
			{
				capSpan.className = 'oil-cap';
				capSpan.textContent = 'Oil cap: ' + format.number(OIL_STORAGE_SIZES[i]);
			}
		}
	}
	// show oil per second
	function addOilCaption()
	{
		var handheldOilSpan = addSpan2ItemBox('handheldOilPump');
		if (handheldOilSpan)
		{
			setOilPerSecond(handheldOilSpan, 1 * window.miner);
			observer.add('miner', function ()
			{
				return setOilPerSecond(handheldOilSpan, 1 * window.miner);
			});
		}
		var oilPipeSpan = addSpan2ItemBox('boundOilPipe');
		if (oilPipeSpan)
		{
			setOilPerSecond(oilPipeSpan, 50);
		}
		// add number of workers as caption to oil factory
		var workerSpan = addSpan2ItemBox('boundOilFactory');
		if (workerSpan)
		{
			var setCaption_1 = function ()
			{
				return workerSpan.textContent = 'Workers: ' + window.oilFactoryWorkers;
			};
			setCaption_1();
			observer.add('oilFactoryWorkers', function ()
			{
				return setCaption_1;
			});
		}
	}

	function hideNumberCaption()
	{
		hideNumberInItemBox('emptyAnvil', true);
		hideNumberInItemBox('tap', true);
		hideNumberInItemBox('farmer', true);
		hideNumberInItemBox('planter', true);
		hideNumberInItemBox('cooksBook', true);
		hideNumberInItemBox('cooksPage', true);
		hideNumberInItemBox('combatDropTable', true);
	}
	// show current tier
	function addTierCaption()
	{
		for (var _i = 0, TIER_ITEMS_2 = TIER_ITEMS; _i < TIER_ITEMS_2.length; _i++)
		{
			var tierItem = TIER_ITEMS_2[_i];
			for (var i = 0; i < TIER_LEVELS.length; i++)
			{
				var key = getTierKey(tierItem, i);
				var toolKey = tierItem == 'rake' ? key : getBoundKey(key);
				var tierSpan = addSpan2ItemBox(toolKey);
				if (tierSpan)
				{
					tierSpan.className = 'tier';
					tierSpan.textContent = TIER_NAMES[i];
				}
			}
		}
	}
	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 = getGameValue(timerKey) > 0;
		var otherInTransit = boatTimerKeys.some(function (k)
		{
			return k != timerKey && getGameValue(k) > 0;
		});
		span.textContent = isInTransit ? 'In transit' : 'Ready';
		span.style.visibility = otherInTransit ? 'hidden' : '';
		if (init)
		{
			observer.add(boatTimerKeys, function ()
			{
				return checkBoat(span, timerKey, false);
			});
		}
	}
	// show boat progress
	function addBoatCaption()
	{
		for (var i = 0; i < boatKeys.length; i++)
		{
			var span = addSpan2ItemBox(getBoundKey(boatKeys[i]));
			if (span)
			{
				checkBoat(span, boatTimerKeys[i], true);
			}
		}
	}
	// show bonemeal
	function addBonemealCaption()
	{
		var noBonemealSpan = addSpan2ItemBox('boundBonemealBin');
		if (!noBonemealSpan)
		{
			return;
		}
		noBonemealSpan.textContent = 'Bonemeal: 0';
		var bonemealSpan = addSpan2ItemBox('boundFilledBonemealBin');
		if (!bonemealSpan)
		{
			return;
		}
		bonemealSpan.dataset.itemDisplay = 'bonemeal';
		bonemealSpan.textContent = format.number(window.bonemeal);
		var captionSpan = document.createElement('span');
		captionSpan.textContent = 'Bonemeal: ';
		bonemealSpan.parentElement.insertBefore(captionSpan, bonemealSpan);
	}

	function warningBeforeSellingGems()
	{
		var _sellNPCItemDialogue = window.sellNPCItemDialogue;
		window.sellNPCItemDialogue = function (item, amount)
		{
			if (item == 'sapphire' || item == 'emerald' || item == 'ruby' || item == 'diamond' || item == 'bloodDiamond')
			{
				var itemName = key2Name(amount == 1 ? item : item.replace(/y$/, 'ie') + 's', true);
				if (amount == 0
					|| !window.confirm('Gems are precious and rare. Please consider carefully:\nDo you really want to sell ' + amount + ' ' + itemName + '?'))
				{
					return;
				}
			}
			else if (item == 'logs' || item == 'oakLogs' || item == 'willowLogs' || item == 'mapleLogs' || item == 'stardustLogs' || item == 'ancientLogs')
			{
				var itemName = key2Name(amount == 1 ? item.replace(/s$/, '') : item, true);
				if (amount == 0
					|| !window.confirm('Logs are time consuming to collect. Please consider carefully:\nDo you really want to sell ' + amount + ' ' + itemName + '?'))
				{
					return;
				}
			}
			_sellNPCItemDialogue(item, amount);
		};
	}

	function init()
	{
		addFurnaceCaption();
		addOilStorageCaption();
		addOilCaption();
		hideNumberCaption();
		addTierCaption();
		addBoatCaption();
		addBonemealCaption();
		warningBeforeSellingGems();
	}
	improveItemBoxes.init = init;
})(improveItemBoxes || (improveItemBoxes = {}));
/**
 * add new chat
 */
var chat;
(function (chat)
{
	chat.name = 'chat';
	var KEYWORD_LIST_KEY = 'keywordList';
	var keywordList = store.get(KEYWORD_LIST_KEY) || [];
	var CHAT_HISTORY_KEY = 'chatHistory';
	var MAX_CHAT_HISTORY_LENGTH = 100;
	var Type;
	(function (Type)
	{
		Type[Type["reload"] = -1] = "reload";
		Type[Type["normal"] = 0] = "normal";
		Type[Type["pmReceived"] = 1] = "pmReceived";
		Type[Type["pmSent"] = 2] = "pmSent";
		Type[Type["serverMsg"] = 3] = "serverMsg";
	})(Type || (Type = {}));;
	var Tag;
	(function (Tag)
	{
		Tag[Tag["none"] = 0] = "none";
		Tag[Tag["donor"] = 1] = "donor";
		Tag[Tag["contributor"] = 2] = "contributor";
		Tag[Tag["mod"] = 3] = "mod";
		Tag[Tag["dev"] = 4] = "dev";
		Tag[Tag["server"] = 5] = "server";
	})(Tag || (Tag = {}));;
	/**
	 * 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 CHUNK_HIDING_MIN_CHUNKS = 10;
	var MSG_CHUNK_SIZE = 100;
	var RELOADED_CHAT_DATA = {
		timestamp: 0
		, username: ''
		, userlevel: 0
		, icon: 0
		, tag: 0
		, type: Type.reload
		, msg: '[...]'
	};
	var CHAT_BOX_ID = 'div-chat';
	var DEFAULT_CHAT_DIV_ID = 'div-chat-area';
	var GENERAL_CHAT_DIV_ID = 'div-chat-general';
	var PM_CHAT_TAB_PREFIX = 'tab-chat-pm-';
	var PM_CHAT_DIV_PREFIX = 'div-chat-pm-';
	var CHAT_TABS_ID = 'chat-tabs';
	var CHAT_INPUT_ID = 'chat-input-text';
	var CHAT_CLASS = 'div-chat-area';
	var COLORIZE_CLASS = 'colorize';
	var SpecialTab;
	(function (SpecialTab)
	{
		SpecialTab[SpecialTab["default"] = 0] = "default";
		SpecialTab[SpecialTab["general"] = 1] = "general";
		SpecialTab[SpecialTab["filler"] = 2] = "filler";
	})(SpecialTab || (SpecialTab = {}));;
	var CHAT_SPECIAL_TAB_ID = (_a = {}
		, _a[SpecialTab.default] = 'tab-chat-default'
		, _a[SpecialTab.general] = 'tab-chat-general'
		, _a[SpecialTab.filler] = 'tab-chat-filler'
		, _a);
	var CONTEXTMENU_ID = 'player-contextmenu';
	var CHAT_ICONS = [
		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 CHAT_TAGS = [
		null
		, {
			key: 'donor'
			, name: ''
		}
		, {
			key: 'contributor'
			, name: 'Contributor'
		}
		, {
			key: 'mod'
			, name: 'Moderator'
		}
		, {
			key: 'dev'
			, name: 'Dev'
		}
		, {
			key: 'yell'
			, name: 'Server Message'
		}
	];
	var LOCALE = 'en-US';
	var LOCALE_OPTIONS = {
		hour12: false
		, year: 'numeric'
		, month: 'long'
		, day: 'numeric'
		, hour: '2-digit'
		, minute: '2-digit'
		, second: '2-digit'
	};
	var TUTORIAL_CMD = 'tutorial';
	var KEYWORD_ADD_CMD = 'keyword add';
	var KEYWORD_REMOVE_CMD = 'keyword remove';
	// load chat history
	var chatHistory = store.get(CHAT_HISTORY_KEY) || [];
	// find index of last message which is not a pm
	var isLastMsgNotReload = false;
	for (var i = chatHistory.length - 1; i >= 0; i--)
	{
		if (!isDataPM(chatHistory[i]))
		{
			isLastMsgNotReload = chatHistory[i].type != Type.reload;
			break;
		}
	}
	// insert a placeholder for a reloaded chat
	if (isLastMsgNotReload)
	{
		RELOADED_CHAT_DATA.timestamp = (new Date()).getTime();
		chatHistory.push(RELOADED_CHAT_DATA);
	}
	// store chat colors for each user
	var user2Color = new Map();
	var usedColors = new Set();
	// reserve color for special messages (e.g. server messages): white
	usedColors.add('#ffffff');

	function isMuted(user)
	{
		// ATTENTION: this will filter all people who are named similar to one of the muted people!
		return window.mutedPeople.some(function (name)
		{
			return user.indexOf(name) > -1;
		});
		// return window.mutedPeople.indexOf(user) !== -1;
	}

	function isSpam(data)
	{
		// allow all messages from contributors, mods, devs and all server messages
		if (data.tag != Tag.none)
		{
			return false;
		}
		/**
		 * get last message of current user
		 */
		var isThisPm = isDataPM(data);
		var msgUsername = data.type === Type.pmSent ? window.username : data.username;
		var historyIndex = chatHistory.indexOf(data);
		if (historyIndex == -1)
		{
			historyIndex = chatHistory.length;
		}
		var lastData = null;
		for (var i = historyIndex - 1; i >= 0 && (lastData === null); i--)
		{
			var dataBefore = chatHistory[i];
			if (isThisPm === isDataPM(dataBefore))
			{
				var beforeUsername = dataBefore.type == Type.pmSent ? window.username : dataBefore.username;
				if (beforeUsername === msgUsername)
				{
					lastData = dataBefore;
				}
			}
		}
		/**
		 * compare message and don't allow the same message twice
		 */
		if (lastData
			&& lastData.msg === data.msg
			&& (data.timestamp - lastData.timestamp) < 10e3)
		{
			return true;
		}
		return false;
	}

	function handleScrolling(chatbox)
	{
		if (window.isAutoScrolling)
		{
			setTimeout(function ()
			{
				return chatbox.scrollTop = chatbox.scrollHeight;
			});
		}
	}
	// for chat messages which arrive before DOMContentLoaded and can not be displayed since the DOM isn't ready
	var chatInitialized = false;

	function processChatData(username, iconString, tagString, msg, isPM)
	{
		var tag = parseInt(tagString, 10);
		var userlevel = 0;
		var type = Type.normal;
		if (isPM == 1)
		{
			var match = msg.match(/^\s*\[(.+) ([A-Za-z0-9 ]+)\]: (.+?)\s*$/) || ['', '', username, msg];
			type = match[1] == 'Sent to' ? Type.pmSent : Type.pmReceived;
			username = match[2];
			msg = match[3];
		}
		else if (tag == Tag.server)
		{
			type = Type.serverMsg;
		}
		else
		{
			var match = msg.match(/^\s*\((\d+)\): (.+?)\s*$/);
			if (match)
			{
				userlevel = parseInt(match[1], 10);
				msg = match[2];
			}
			else
			{
				userlevel = window.getGlobalLevel();
			}
		}
		// unlinkify when using DH2QoL to store the plain message
		if (window.addToChatBox.toString().includes('linkify(arguments[3])'))
		{
			msg = msg.replace(/<a href='([^']+)' target='_blank'>\1<\/a>/ig, '$1');
		}
		if (type == Type.pmSent)
		{
			// 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(iconString, 10)
			, tag: tag
			, type: type
			, msg: msg
		};
	}

	function add2ChatHistory(data)
	{
		chatHistory.push(data);
		chatHistory = chatHistory.slice(-MAX_CHAT_HISTORY_LENGTH);
		store.set(CHAT_HISTORY_KEY, chatHistory);
	}

	function username2Id(username)
	{
		return username.replace(/[ \+]/g, '_');
	}

	function getChatTab(username, specialTab)
	{
		var id = (specialTab != null)
			? CHAT_SPECIAL_TAB_ID[specialTab]
			: PM_CHAT_TAB_PREFIX + username2Id(username);
		var tab = document.getElementById(id);
		if (!tab)
		{
			tab = document.createElement('div');
			tab.className = 'chat-tab';
			if (specialTab != null)
			{
				tab.classList.add(SpecialTab[specialTab]);
			}
			tab.id = id;
			tab.dataset.username = username;
			tab.dataset.new = '0';
			if (username.length > 2)
			{
				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_ID);
			var filler = chatTabs.querySelector('.filler');
			if (filler)
			{
				chatTabs.insertBefore(tab, filler);
			}
			else
			{
				chatTabs.appendChild(tab);
			}
		}
		return tab;
	}

	function getChatDiv(username)
	{
		var id = username == '' ? GENERAL_CHAT_DIV_ID : PM_CHAT_DIV_PREFIX + username2Id(username);
		var div = document.getElementById(id);
		if (!div)
		{
			div = document.createElement('div');
			div.setAttribute('disabled', 'disabled');
			div.id = id;
			div.className = CHAT_CLASS;
			var defaultChat = document.getElementById(DEFAULT_CHAT_DIV_ID);
			var height = defaultChat.style.height;
			div.style.height = height;
			var chatDiv = defaultChat.parentElement;
			chatDiv.insertBefore(div, defaultChat);
		}
		return div;
	}

	function changeChatTab(oldTab, newTab)
	{
		if (oldTab)
		{
			oldTab.classList.remove('selected');
			var oldChatDiv = void 0;
			if (oldTab.classList.contains('default'))
			{
				oldChatDiv = document.getElementById(DEFAULT_CHAT_DIV_ID);
			}
			else
			{
				oldChatDiv = getChatDiv(oldTab.dataset.username || '');
			}
			oldChatDiv.classList.remove('selected');
		}
		newTab.classList.add('selected');
		newTab.dataset.new = '0';
		var newChatDiv;
		if (newTab.classList.contains('default'))
		{
			newChatDiv = document.getElementById(DEFAULT_CHAT_DIV_ID);
		}
		else
		{
			newChatDiv = getChatDiv(newTab.dataset.username || '');
		}
		newChatDiv.classList.add('selected');
		var toUsername = newTab.dataset.username;
		var newTextPlaceholder = toUsername == '' ? window.username + ':' : 'PM to ' + toUsername + ':';
		document.getElementById(CHAT_INPUT_ID).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_ID + ' .chat-tab.selected');
		var tab2Close = getChatTab(username, null);
		if (oldTab.dataset.username == username)
		{
			var generalTab = getChatTab('', SpecialTab.general);
			changeChatTab(tab2Close, generalTab);
		}
		var tabContainer = tab2Close.parentElement;
		tabContainer.removeChild(tab2Close);
	}

	function isDataPM(data)
	{
		return data.type === Type.pmSent || data.type === Type.pmReceived;
	}
	var msgChunkMap = new Map();
	var chatboxFragments = new Map();

	function colorizeMsg(username)
	{
		if (username == '')
		{
			return null;
		}
		if (!user2Color.has(username))
		{
			var color = void 0;
			do {
				// color = colorGenerator.getRandom({ luminosity: 'light' });
				color = colorGenerator.getEquallyDistributed();
			} while (usedColors.has(color));
			user2Color.set(username, color);
			usedColors.add(color);
			addStyle("\n#" + CHAT_BOX_ID + "." + COLORIZE_CLASS + " .chat-msg[data-username=\"" + username + "\"]\n{\n\tbackground-color: " + color + ";\n}\n\t\t\t", 'name-color');
		}
		return user2Color.get(username);
	}

	function createMessageSegment(data)
	{
		var isThisPm = isDataPM(data);
		var msgUsername = data.type === Type.pmSent ? 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 === isDataPM(dataBefore))
			{
				if (isSameUser === null)
				{
					var beforeUsername = dataBefore.type == Type.pmSent ? 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 = CHAT_ICONS[data.icon] ||
		{
			key: ''
			, title: ''
		};
		var tag = CHAT_TAGS[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>');
		colorizeMsg(msgUsername);
		var msgTitle = data.type == Type.reload ? 'Chat loaded on ' + d.toLocaleString(LOCALE, LOCALE_OPTIONS) : '';
		var user = data.type === Type.serverMsg ? 'Server Message' : msgUsername;
		var levelAppendix = data.type == Type.normal ? ' (' + data.userlevel + ')' : '';
		var userTitle = data.tag != Tag.server ? tag.name : '';
		return "<span class=\"chat-msg\" data-type=\"" + data.type + "\" data-tag=\"" + tag.key + "\" data-username=\"" + msgUsername + "\">"
			+ ("<span\n\t\t\t\tclass=\"timestamp\"\n\t\t\t\tdata-timestamp=\"" + data.timestamp + "\"\n\t\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 = isDataPM(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, isThisPm ? null : SpecialTab.general);
		if (!chatTab.classList.contains('selected'))
		{
			chatTab.dataset.new = (parseInt(chatTab.dataset.new || '0', 10) + 1).toString();
		}
		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 >= MSG_CHUNK_SIZE)
		{
			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\tpadding: 1px 0;\n}\n#" + CHAT_BOX_ID + ":not(." + COLORIZE_CLASS + ") span.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_BOX_ID + ".showTimestamps .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\tpointer-events: none;\n\tposition: absolute;\n\tleft: 2.5rem;\n\ttop: -0.4rem;\n\ttext-align: center;\n\twhite-space: nowrap;\n}\n\n#" + CHAT_BOX_ID + ".showTags .chat-msg[data-type=\"" + Type.pmReceived + "\"] { color: purple; }\n#" + CHAT_BOX_ID + ".showTags .chat-msg[data-type=\"" + Type.pmSent + "\"] { color: purple; }\n#" + CHAT_BOX_ID + ".showTags .chat-msg[data-type=\"" + Type.serverMsg + "\"] { color: blue; }\n#" + CHAT_BOX_ID + ".showTags .chat-msg[data-tag=\"contributor\"] { color: green; }\n#" + CHAT_BOX_ID + ".showTags .chat-msg[data-tag=\"mod\"] { color: #669999; }\n#" + CHAT_BOX_ID + ".showTags .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#" + GENERAL_CHAT_DIV_ID + " .chat-msg:not([data-type=\"" + Type.reload + "\"]) .user\n{\n\tflex-basis: 182px;\n}\n#" + CHAT_BOX_ID + ".showIcons #" + GENERAL_CHAT_DIV_ID + " .chat-msg:not([data-type=\"" + Type.reload + "\"]) .user\n{\n\tpadding-left: 22px;\n}\n.chat-msg .user[data-same-user=\"true\"]:not([data-name=\"\"])\n{\n\tcursor: default;\n\topacity: 0;\n}\n\n.chat-msg .user .icon\n{\n\tdisplay: none;\n}\n#" + CHAT_BOX_ID + ".showIcons .chat-msg .user .icon\n{\n\tdisplay: inline-block;\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:not([data-same-user=\"true\"]) .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_BOX_ID + ".showTags .chat-msg .user .name.chat-tag-contributor,\n#" + CHAT_BOX_ID + ".showTags .chat-msg .user .name.chat-tag-mod,\n#" + CHAT_BOX_ID + ".showTags .chat-msg .user .name.chat-tag-dev,\n#" + CHAT_BOX_ID + ".showTags .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#" + CHAT_BOX_ID + ":not(.showTags) .chat-msg .user .name.chat-tag-contributor,\n#" + CHAT_BOX_ID + ":not(.showTags) .chat-msg .user .name.chat-tag-mod,\n#" + CHAT_BOX_ID + ":not(.showTags) .chat-msg .user .name.chat-tag-dev,\n#" + CHAT_BOX_ID + ":not(.showTags) .chat-msg .user .name.chat-tag-yell\n{\n\tbackground: initial;\n\tborder: inherit;\n\tfont-family: inherit;\n\tfont-size: inherit;\n\tpadding: initial;\n}\n\n.chat-msg[data-type=\"" + Type.reload + "\"] .user > *,\n.chat-msg[data-type=\"" + Type.pmReceived + "\"] .user > .icon,\n.chat-msg[data-type=\"" + Type.pmSent + "\"] .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#" + CHAT_BOX_ID + " ." + CHAT_CLASS + "\n{\n\twidth: 100%;\n\theight: 130px;\n\tdisplay: none;\n}\n#" + CHAT_BOX_ID + " ." + CHAT_CLASS + ".selected\n{\n\tdisplay: block;\n}\n#" + CHAT_TABS_ID + "\n{\n\tdisplay: flex;\n\tmargin: 10px -6px -6px;\n\tflex-wrap: wrap;\n}\n#" + CHAT_TABS_ID + " .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_ID + " .chat-tab.selected\n{\n\tbackground-color: transparent;\n\tborder-top-color: transparent;\n}\n#" + CHAT_TABS_ID + " .chat-tab.default\n{\n\tdisplay: none;\n}\n#" + CHAT_TABS_ID + " .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_ID + " .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_ID + " .chat-tab[data-new=\"0\"]::after\n{\n\tcolor: inherit;\n\tfont-weight: normal;\n}\n#" + CHAT_TABS_ID + " .chat-tab:not(.general).selected::after,\n#" + CHAT_TABS_ID + " .chat-tab:not(.general):hover::after\n{\n\tvisibility: hidden;\n}\n#" + CHAT_TABS_ID + " .chat-tab:not(.general).selected .close::after,\n#" + CHAT_TABS_ID + " .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\n#" + CONTEXTMENU_ID + "\n{\n\tbox-shadow: rgba(0, 0, 0, 0.8) 4px 4px 4px -2px;\n\tposition: fixed;\n}\n#" + CONTEXTMENU_ID + " .ui-widget-header\n{\n\tcursor: default;\n\tpadding: .25rem;\n}\n\t\t");
	}

	function addIntelligentScrolling()
	{
		// add checkbox instead of button for toggling auto scrolling
		var btn = document.querySelector('input[value="Toggle Autoscroll"]');
		var btnParent = btn.parentElement;
		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';
		btnParent.insertBefore(toggleCheckbox, btn);
		btnParent.insertBefore(toggleLabel, btn);
		btn.style.display = 'none';
		var chatArea = document.getElementById(GENERAL_CHAT_DIV_ID);
		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)
				{
					if (showScrollTextTimeout)
					{
						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 && settings.get(settings.KEY.intelligentScrolling))
			{
				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 < CHUNK_HIDING_MIN_CHUNKS)
			{
				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 (settings.get(settings.KEY.intelligentScrolling))
			{
				var scrolled2Bottom = (chatArea.scrollTop + chatArea.clientHeight) >= chatArea.scrollHeight;
				setAutoScrolling(scrolled2Bottom, true);
			}
			var n = now();
			if (delayedScrollStart == null)
			{
				delayedScrollStart = n;
			}
			if (delayedScrollStart + 300 > n)
			{
				if (delayedScrollTimeout)
				{
					window.clearTimeout(delayedScrollTimeout);
				}
				delayedScrollTimeout = window.setTimeout(function ()
				{
					delayedScrollStart = null;
					delayedScrollTimeout = null;
					scrollHugeChat();
				}, 50);
			}
		});
	}

	function getSelectedTab()
	{
		return document.querySelector('#' + CHAT_TABS_ID + ' .chat-tab.selected');
	}

	function clickChatTab(newTab)
	{
		var oldTab = getSelectedTab();
		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 checkSetting(init)
	{
		if (init === void 0)
		{
			init = false;
		}
		var enabled = settings.get(settings.KEY.useNewChat);
		// dis-/enable chat tabs
		var chatTabs = document.getElementById(CHAT_TABS_ID);
		chatTabs.style.display = enabled ? '' : 'none';
		// dis-/enable checkbox for intelligent scrolling
		var intelScrollId = 'chat-toggle-intelligent-scroll';
		var input = document.getElementById(intelScrollId);
		if (input)
		{
			input.style.display = enabled ? '' : 'none';
		}
		var label = document.querySelector('label[for="' + intelScrollId + '"]');
		if (label)
		{
			label.style.display = enabled ? '' : 'none';
		}
		// virtually click on a tab
		var defaultTab = getChatTab('', SpecialTab.default);
		var generalTab = getChatTab('', SpecialTab.general);
		clickChatTab(enabled ? generalTab : defaultTab);
		if (init)
		{
			settings.observe(settings.KEY.useNewChat, function ()
			{
				return checkSetting(false);
			});
		}
	}

	function addChatTabs()
	{
		var chatBoxArea = document.getElementById(CHAT_BOX_ID);
		var chatTabs = document.createElement('div');
		chatTabs.id = CHAT_TABS_ID;
		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);
		// default tab (for disabled new chat)
		getChatTab('', SpecialTab.default);
		// general server chat
		var generalTab = getChatTab('', SpecialTab.general);
		generalTab.textContent = 'Server';
		getChatDiv('');
		getChatTab('', SpecialTab.filler);
		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 || '').replace(/ /g, '+') + ' ' + msg;
			}
			_sendChat(inputEl);
		};
	}

	function switch2PmTab(username)
	{
		var newTab = getChatTab(username, null);
		clickChatTab(newTab);
	}

	function notifyPm(data)
	{
		notifications.event('Message from "' + data.username + '"'
		, {
			body: data.msg
			, onclick: function ()
			{
				return switch2PmTab(data.username);
			}
			, whenActive: getSelectedTab().dataset.username != data.username
		});
	}

	function checkMentionAndKeywords(data)
	{
		var lowerMsg = data.msg.toLowerCase();
		if (lowerMsg.indexOf(window.username) > -1)
		{
			notifications.event('You\'ve been mentioned'
			, {
				body: data.msg
			});
		}
		var match = [];
		for (var _i = 0, keywordList_1 = keywordList; _i < keywordList_1.length; _i++)
		{
			var keyword = keywordList_1[_i];
			if (lowerMsg.indexOf(keyword) > -1)
			{
				match.push(keyword);
			}
		}
		if (match.length > 0)
		{
			notifications.event('Keyword: "' + match.join('", "') + '"'
			, {
				body: data.msg
			});
		}
	}
	var addToChatBox_ = null;

	function newAddToChatBox(username, icon, tag, msg, isPM)
	{
		var data = processChatData(username, icon, tag, msg, isPM);
		if (isDataPM(data))
		{
			if (data.type == Type.pmSent)
			{
				switch2PmTab(data.username);
			}
			notifyPm(data);
		}
		else if (isSpam(data))
		{
			console.info('detected spam:', data);
			return;
		}
		else
		{
			// check mentioning and keywords only for non-pms
			checkMentionAndKeywords(data);
		}
		add2ChatHistory(data);
		add2Chat(data);
		var fn = addToChatBox_ == null ? window.addToChatBox : addToChatBox_;
		fn(username, icon, tag, msg, isPM);
	}
	chat.newAddToChatBox = newAddToChatBox;

	function openPmTab(username)
	{
		if (username == window.username || username == '')
		{
			return;
		}
		var userTab = getChatTab(username, null);
		clickChatTab(userTab);
		var input = document.getElementById(CHAT_INPUT_ID);
		input.focus();
	}

	function newChat()
	{
		addChatTabs();
		applyChatStyle();
		addToChatBox_ = window.addToChatBox;
		window.addToChatBox = newAddToChatBox;
		chatInitialized = true;
		var chatbox = document.getElementById(CHAT_BOX_ID);
		chatbox.addEventListener('click', function (event)
		{
			var target = event.target;
			var userEl = target && target.parentElement;
			if (!target || !userEl || !target.classList.contains('name') || !userEl.classList.contains('user'))
			{
				return;
			}
			if (userEl.dataset.sameUser != 'true')
			{
				openPmTab(userEl.dataset.name || '');
			}
		});
		chatbox.addEventListener('mouseover', function (event)
		{
			var target = event.target;
			if (!target.classList.contains('timestamp') || !target.dataset.timestamp)
			{
				return;
			}
			var timestamp = parseInt(target.dataset.timestamp || '0', 10);
			target.dataset.fulltime = (new Date(timestamp)).toLocaleDateString(LOCALE, LOCALE_OPTIONS);
			target.dataset.timestamp = '';
		});
		// add context menu
		var contextmenu = document.createElement('ul');
		contextmenu.id = CONTEXTMENU_ID;
		contextmenu.style.display = 'none';
		contextmenu.innerHTML = "<li class=\"name ui-widget-header\"><div></div></li>\n\t\t<li class=\"open-pm\"><div>Open pm tab</div></li>\n\t\t<li class=\"mute\"><div>Mute</div></li>\n\t\t<li class=\"unmute\"><div>Unmute</div></li>";
		document.body.appendChild(contextmenu);
		window.$(contextmenu).menu(
		{
			items: '> :not(.ui-widget-header)'
		});
		var nameListEl = contextmenu.querySelector('.name');
		var nameDivEl = nameListEl.firstElementChild;
		var muteEl = contextmenu.querySelector('.mute');
		var unmuteEl = contextmenu.querySelector('.unmute');
		chatbox.addEventListener('contextmenu', function (event)
		{
			var target = event.target;
			var userEl = target && target.parentElement;
			if (!userEl || !userEl.classList.contains('user'))
			{
				return;
			}
			var username = userEl.dataset.name;
			// ignore clicks on server messages or other special messages
			if (!username || userEl.dataset.sameUser == 'true')
			{
				return;
			}
			contextmenu.style.left = event.clientX + 'px';
			contextmenu.style.top = event.clientY + 'px';
			contextmenu.style.display = '';
			contextmenu.dataset.username = username;
			nameDivEl.textContent = username;
			var isMuted = window.mutedPeople.indexOf(username) !== -1;
			muteEl.style.display = isMuted ? 'none' : '';
			unmuteEl.style.display = isMuted ? '' : 'none';
			event.preventDefault();
		});
		// add click listener for context menu and stop propagation
		contextmenu.addEventListener('click', function (event)
		{
			var target = event.target;
			event.stopPropagation();
			while (target && target.id != CONTEXTMENU_ID && target.tagName != 'LI')
			{
				target = target.parentElement;
			}
			if (!target || target.id == CONTEXTMENU_ID)
			{
				return;
			}
			var username = contextmenu.dataset.username || '';
			if (target.classList.contains('open-pm'))
			{
				openPmTab(username);
			}
			else if (target.classList.contains('mute'))
			{
				if (username == '')
				{
					return;
				}
				window.mutedPeople.push(username);
				window.scrollText('none', 'lime', '<em>' + username + '</em> muted');
			}
			else if (target.classList.contains('unmute'))
			{
				if (username == '')
				{
					return;
				}
				var index = window.mutedPeople.indexOf(username);
				if (index !== -1)
				{
					window.mutedPeople.splice(index, 1);
				}
				window.scrollText('none', 'red', '<em>' + username + '</em> unmuted');
			}
			else
			{
				return;
			}
			contextmenu.style.display = 'none';
		});
		// add click listener to hide context menu
		document.addEventListener('click', function (event)
		{
			if (contextmenu.style.display != 'none')
			{
				contextmenu.style.display = 'none';
			}
		});
		// handle settings
		var showSettings = [settings.KEY.showTimestamps, settings.KEY.showIcons, settings.KEY.showTags];

		function setShowSetting(key)
		{
			var enabled = settings.get(key);
			chatbox.classList[enabled ? 'add' : 'remove'](settings.KEY[key]);
		}
		for (var _i = 0, showSettings_1 = showSettings; _i < showSettings_1.length; _i++)
		{
			var key = showSettings_1[_i];
			setShowSetting(key);
			settings.observe(key, function (k)
			{
				return setShowSetting(k);
			});
		}
	}
	var commands = ['pm', 'mute', 'ipmute'];

	function addCommandSuggester()
	{
		var input = document.getElementById(CHAT_INPUT_ID);
		input.addEventListener('keyup', function (event)
		{
			if (event.key == 'Backspace' || event.key == 'Delete' || event.key == 'Enter' || event.key == 'Tab'
				|| input.selectionStart != input.selectionEnd
				|| input.selectionStart != input.value.length
				|| !input.value.startsWith('/'))
			{
				return;
			}
			var value = input.value.substr(1);
			for (var _i = 0, commands_1 = commands; _i < commands_1.length; _i++)
			{
				var cmd = commands_1[_i];
				if (cmd.startsWith(value))
				{
					input.value = '/' + cmd;
					input.selectionStart = 1 + value.length;
					input.selectionEnd = input.value.length;
					break;
				}
			}
		});
	}

	function addOwnCommands()
	{
		commands.push(TUTORIAL_CMD);
		commands.push(KEYWORD_ADD_CMD);
		commands.push(KEYWORD_REMOVE_CMD);

		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(TUTORIAL_CMD))
			{
				// thanks aguyd (https://greasyfork.org/forum/profile/aguyd) for the idea
				var name_2 = msg.substr(TUTORIAL_CMD.length).trim();
				msgPrefix = '';
				msg = 'https://www.reddit.com/r/DiamondHunt/comments/5vrufh/diamond_hunt_2_starter_faq/';
				if (name_2.length != 0)
				{
					// maybe add '@' before the name?
					msg = name_2 + ', ' + msg;
				}
			}
			else if (msg.startsWith(KEYWORD_ADD_CMD))
			{
				var keyword = msg.substr(KEYWORD_ADD_CMD.length).trim().toLowerCase();
				if (keywordList.indexOf(keyword) == -1)
				{
					keywordList.push(keyword);
					store.set(KEYWORD_LIST_KEY, keywordList);
				}
				window.scrollText('none', 'lime', 'Keyword added: <em>' + keyword + '</em>');
			}
			else if (msg.startsWith(KEYWORD_REMOVE_CMD))
			{
				var keyword = msg.substr(KEYWORD_REMOVE_CMD.length).trim().toLowerCase();
				var index = keywordList.indexOf(keyword);
				if (index != -1)
				{
					keywordList.splice(index, 1);
					store.set(KEYWORD_LIST_KEY, keywordList);
				}
				window.scrollText('none', 'lime', 'Keyword removed: <em>' + keyword + '</em>');
			}
			return msgPrefix + msg;
		}
		var _sendChat = window.sendChat;
		window.sendChat = function (inputEl)
		{
			inputEl.value = processOwnCommands(inputEl.value);
			_sendChat(inputEl);
		};
	}

	function checkColorize(init)
	{
		if (init === void 0)
		{
			init = false;
		}
		var chatDiv = document.getElementById(CHAT_BOX_ID);
		chatDiv.classList[settings.get(settings.KEY.colorizeChat) ? 'add' : 'remove'](COLORIZE_CLASS);
		if (init)
		{
			settings.observe(settings.KEY.colorizeChat, function ()
			{
				return checkColorize(false);
			});
		}
	}

	function init()
	{
		newChat();
		addIntelligentScrolling();
		addCommandSuggester();
		addOwnCommands();
		checkColorize(true);
		checkSetting(true);
		var _enlargeChat = window.enlargeChat;
		var chatBoxArea = document.getElementById(CHAT_BOX_ID);

		function setChatBoxHeight(height)
		{
			var defaultChat = document.getElementById(DEFAULT_CHAT_DIV_ID);
			defaultChat.style.height = height;
			var generalChat = document.getElementById(GENERAL_CHAT_DIV_ID);
			generalChat.style.height = height;
			var chatDivs = chatBoxArea.querySelectorAll('div[id^="' + PM_CHAT_DIV_PREFIX + '"]');
			for (var i = 0; i < chatDivs.length; i++)
			{
				chatDivs[i].style.height = height;
			}
		}
		window.enlargeChat = function (enlargeB)
		{
			_enlargeChat(enlargeB);
			var defaultChatDiv = document.getElementById(DEFAULT_CHAT_DIV_ID);
			var height = defaultChatDiv.style.height;
			store.set('chat.height', height);
			setChatBoxHeight(height);
			handleScrolling(defaultChatDiv);
		};
		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.set(CHAT_HISTORY_KEY, oldChatHistory);
			store.remove(oldChatHistoryKey);
		}
		// TEMP <<<
		// add history to chat
		chatHistory.forEach(function (d)
		{
			return add2Chat(d);
		});
		if (chatboxFragments)
		{
			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';
		}
	}
	chat.init = init;
	var _a;
})(chat || (chat = {}));
/**
 * hopefully only temporary fixes
 */
var temporaryFixes;
(function (temporaryFixes)
{
	temporaryFixes.name = 'temporaryFixes';
	// update spells being clickable in combat
	function setSpellsClickable()
	{
		var spellbox = document.getElementById('fight-spellboox');
		if (spellbox)
		{
			spellbox.style.pointerEvents = window.isInCombat() ? '' : 'none';
		}
	}
	// warn before unloading/reloading the tab if combat is in progress
	function combatWarnOnUnload()
	{
		if (!window.isInCombat())
		{
			window.onbeforeunload = null;
		}
		else
		{
			if (window.onbeforeunload == null)
			{
				window.onbeforeunload = function ()
				{
					return 'You are in a fight!';
				};
			}
		}
	}

	function fixCombatCountdown()
	{
		if (window.isInCombat() && window.combatCommenceTimer != 0)
		{
			document.getElementById('combat-countdown').style.display = '';
		}
	}
	// fix exhaustion timer and updating brewing and cooking recipes
	function fixExhaustionTimer()
	{
		if (document.getElementById('tab-container-combat').style.display != 'none')
		{
			window.combatNotFightingTick();
		}
	}

	function fixClientGameLoop()
	{
		var _clientGameLoop = window.clientGameLoop;
		window.clientGameLoop = function ()
		{
			_clientGameLoop();
			setSpellsClickable();
			combatWarnOnUnload();
			fixCombatCountdown();
			fixExhaustionTimer();
		};
	}
	// fix elements of scrollText (e.g. when joining the game and receiving xp at that moment)
	function fixScroller()
	{
		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
	function fixTooltipStyle()
	{
		addStyle("\nbody > div.tooltip > h2:first-child\n{\n\tmargin-top: 0;\n\tfont-size: 20pt;\n\tfont-weight: normal;\n}\n\t\t");
	}
	// fix buiulding magic table dynamically
	function fixRefreshingMagicRecipes()
	{
		window.refreshLoadMagicTable = true;
		var _processMagicTab = window.processMagicTab;
		window.processMagicTab = function ()
		{
			var _refreshLoadCraftingTable = window.refreshLoadCraftingTable;
			window.refreshLoadCraftingTable = window.refreshLoadMagicTable;
			_processMagicTab();
			window.refreshLoadCraftingTable = _refreshLoadCraftingTable;
		};
	}
	// move the strange leaf to brewing tab (thanks lasse_brus for this idea)
	function moveStrangeLeafs()
	{
		var strangeLeafBox = document.getElementById('item-box-strangeLeaf');
		var brewingContainer = document.getElementById('tab-sub-container-brewing');
		brewingContainer.appendChild(strangeLeafBox);
		// remove event listeners before binding the tooltip to it
		var $strangeLeafBox = window.$(strangeLeafBox);
		$strangeLeafBox.off('mouseover').off('mouseleave');
		strangeLeafBox.title = '';
		// bind tooltip to item box
		ensureTooltip('ingredient-secondary', strangeLeafBox);
		// change color
		var color1 = '#800080';
		var color2 = '#990099';
		strangeLeafBox.style.background = 'linear-gradient(' + color1 + ', ' + color2 + ')';
		$strangeLeafBox
			.mouseover(function ()
			{
				strangeLeafBox.style.background = 'none';
				strangeLeafBox.style.backgroundColor = color2;
			})
			.mouseleave(function ()
			{
				strangeLeafBox.style.background = 'linear-gradient(' + color1 + ', ' + color2 + ')';
			});
	}
	// fix height of map item
	function fixTreasureMap()
	{
		var mapBox = document.getElementById('item-box-treasureMap');
		var numSpan = mapBox.lastElementChild;
		numSpan.style.display = '';
		numSpan.style.visibility = 'hidden';
	}
	// fix wobbling tree places on hover (in wood cutting)
	function fixWoodcutting()
	{
		addStyle("\nimg.woodcutting-tree-img\n{\n\tborder: 1px solid transparent;\n}\n\t\t");
	}
	// fix rake dialog
	function fixRakeDialog()
	{
		var _clicksRake = window.clicksRake;
		window.clicksRake = function ()
		{
			_clicksRake();
			var upgradeBtn = document.querySelector('#dialogue-id-upgrade-rake input[value="Upgrade"]');
			if (upgradeBtn)
			{
				var hide = window.diamondRake == 1;
				upgradeBtn.style.display = hide ? 'none' : '';
			}
		};
	}
	// fix tooltip of promethium, whale and rainbowfish
	function fixTooltips()
	{
		var fishTooltipTemplate = document.querySelector('#tooltip-list > div[id^="tooltip-raw"]');
		var oreTooltipTemplate = document.getElementById('tooltip-marble');
		if (!fishTooltipTemplate || !oreTooltipTemplate)
		{
			return;
		}

		function ensureTooltip(item, tooltipTemplate, onCreate)
		{
			var tooltipId = 'tooltip-' + item;
			if (document.getElementById(tooltipId) != null)
			{
				return;
			}
			var newTooltip = tooltipTemplate.cloneNode(true);
			newTooltip.id = tooltipId;
			onCreate(newTooltip);
			tooltipTemplate.parentElement.appendChild(newTooltip);
		}

		function ensureRawFishTooltip(fish)
		{
			ensureTooltip('raw' + fish, fishTooltipTemplate, function (newTooltip)
			{
				var foodName = 'Raw' + split2Words(fish);
				newTooltip.firstChild.textContent = foodName;
				var foodKey = fish[0].toLowerCase() + fish.substr(1);
				var energy = window.getEnergyGained(foodKey);
				newTooltip.lastChild.firstChild.textContent = '+' + format.number(energy) + ' ';
			});
		}
		var boxes = document.querySelectorAll('#tab-sub-container-combat-large-btns > span.item-box[id^="item-box-raw"]');
		for (var i = 0; i < boxes.length; i++)
		{
			var food = boxes[i].id.replace('item-box-raw', '');
			ensureRawFishTooltip(food);
		}
		// promethium
		ensureTooltip('promethium', oreTooltipTemplate, function (newTooltip)
		{
			var spans = newTooltip.getElementsByTagName('span');
			var priceNode = newTooltip.lastChild;
			spans[0].textContent = 'Promethium';
			spans[1].textContent = 'Can be used for... nothing at the moment.';
			priceNode.textContent = ' ' + format.number(getPrice('promethium'));
		});
	}
	// fix wobbling quest rows on hover (in quest book)
	function fixQuestBook()
	{
		addStyle("\n#table-quest-list tr\n{\n\tborder: 1px solid transparent;\n}\n\t\t");
	}
	// this fix was realized first here: https://www.reddit.com/r/DiamondHunt/60z9h9/
	function fixedLoadSkillTabs()
	{
		for (var _i = 0, SKILL_LIST_1 = SKILL_LIST; _i < SKILL_LIST_1.length; _i++)
		{
			var skil = SKILL_LIST_1[_i];
			var unlocked = getGameValue(skil + 'Unlocked') == 1;
			if (!unlocked)
			{
				continue;
			}
			var xp = getGameValue(skil + 'Xp');
			var currentLevelXp = window.getXpNeeded(window.getLevel(xp));
			var nextLevelXp = window.getXpNeeded(window.getLevel(xp) + 1);
			var perc = (xp - currentLevelXp) / (nextLevelXp - currentLevelXp) * 100;
			var xpBarEl = document.getElementById('inner-skill-xp-bar-' + skil);
			xpBarEl.style.minWidth = xpBarEl.style.maxWidth = perc + '%';
		}
	}

	function fixSkillBars()
	{
		var _loadSkillTabs = window.loadSkillTabs;
		window.loadSkillTabs = function ()
		{
			_loadSkillTabs();
			fixedLoadSkillTabs();
		};
	}

	function fixCraftingXpTooltip()
	{
		var el = document.getElementById('tooltip-skills-crafting-xp-left-value');
		if (!el)
		{
			return;
		}
		var br = el.nextElementSibling;
		if (!br)
		{
			return;
		}
		var annoyingTick = br.nextSibling;
		if (!annoyingTick || annoyingTick.nodeType != Node.TEXT_NODE || !annoyingTick.textContent)
		{
			return;
		}
		annoyingTick.textContent = annoyingTick.textContent.replace('`', '');
	}

	function fixScrollImages()
	{
		function fixIcon(icon)
		{
			return icon + (icon != 'none' && !/\..{3,4}$/.test(icon) ? '.png' : '');
		}
		var _scrollTextHitSplat = window.scrollTextHitSplat;
		window.scrollTextHitSplat = function (icon, color, text, elId, cbType)
		{
			_scrollTextHitSplat(fixIcon(icon), color, text, elId, cbType);
		};
		var _scrollText = window.scrollText;
		window.scrollText = function (icon, color, text)
		{
			_scrollText(fixIcon(icon), color, text);
		};
	}

	function fixQuest8BraveryRecipe()
	{
		observer.add([
			'quest8'
			, 'braveryPotion'
		], function ()
		{
			var show = window.quest8 > 0 && window.braveryPotion == 0;
			var recipe = document.getElementById('brewing-braveryPotion');
			if (recipe)
			{
				recipe.style.display = show ? '' : 'none';
			}
		});
	}
	// fix cooking dialog of uncookable fish (e.g. whale/rainbow fish)
	function fixCookingDialogForUncookableFish()
	{
		var _cookFoodDialogue = window.cookFoodDialogue;
		window.cookFoodDialogue = function (rawFood)
		{
			_cookFoodDialogue(rawFood);
			var dialog = document.getElementById('dialogue-id-cook-food');
			if (!dialog)
			{
				return;
			}
			var isCookable = window.getHeatNeeded(rawFood) != 0;

			function hideWhenNotCookable(el, mustBeCookable)
			{
				el.style.display = isCookable == mustBeCookable ? '' : 'none';
			}
			var boxes = dialog.getElementsByClassName('basic-smallbox');
			var amountBox = boxes[1];
			var notCookableBox = amountBox.nextElementSibling;
			if (notCookableBox.tagName != 'DIV')
			{
				var newEl = amountBox.cloneNode(false);
				newEl.innerHTML = "<b>Not cookable at the moment</b>";
				notCookableBox.parentElement.insertBefore(newEl, notCookableBox);
				notCookableBox = newEl;
			}
			hideWhenNotCookable(amountBox, true);
			hideWhenNotCookable(notCookableBox, false);
			var heatNeededBox = document.getElementById('heat-needed-dyn');
			if (heatNeededBox)
			{
				hideWhenNotCookable(heatNeededBox, true);
				hideWhenNotCookable(heatNeededBox.previousElementSibling, true);
			}
			var btn = dialog.querySelector('input[value="Cook"]');
			if (btn)
			{
				btn.disabled = !isCookable;
			}
			// TODO: move to some other module like "requirementCheck"
			var levelReq = document.getElementById('dialogue-cook-levelReq');
			var levelReqLabel = levelReq && levelReq.previousElementSibling;
			if (!levelReq || !levelReqLabel)
			{
				return;
			}
			var fulfilled = window.getCookingLevelReq(rawFood) > window.getLevel(window.cookingXp);
			levelReq.style.color = fulfilled ? 'rgb(204, 0, 0)' : '';
			levelReq.style.fontWeight = fulfilled ? 'bold' : '';
			levelReqLabel.style.color = fulfilled ? 'rgb(204, 0, 0)' : '';
		};
	}

	function fixHitText()
	{
		window.scrollTextHitSplat = function (icon, color, text, elId, cbType)
		{
			var imgTag = icon != 'none' ? "<img src=\"images/" + icon + "\" class=\"image-icon-50\" />" : '';
			var elementChosen = document.getElementById(elId);
			if (!elementChosen)
			{
				return;
			}
			var rect = elementChosen.getBoundingClientRect();
			var xCoord = (rect.left + rect.right) / 2;
			var yCoord = (rect.bottom + rect.top) / 2;
			var extraStyle = '';
			if (cbType == 'melee')
			{
				extraStyle = 'border: 1px solid red; background-color: #4d0000;';
			}
			else if (cbType == 'heal')
			{
				extraStyle = 'border: 1px solid green; background-color: lime;';
			}
			var $elementToAppend = window.$("<div class=\"scroller\" style=\"" + extraStyle + " color: " + color + "; position: fixed;\">" + imgTag + text + "</div>").appendTo('body');
			if (xCoord == 0 && yCoord == 0)
			{
				var tab = document.getElementById('tab-container-bar-combat');
				var tabRect = tab.getBoundingClientRect();
				var boxRect = $elementToAppend.get(0).getBoundingClientRect();
				xCoord = elId == 'img-hero' ? (tabRect.left - boxRect.width) : tabRect.right;
				yCoord = tabRect.top;
			}
			$elementToAppend
				.css(
				{
					left: xCoord
					, top: yCoord
				})
				.animate(
				{
					top: '-=50px'
				}, function ()
				{
					return $elementToAppend.fadeOut(1000, function ()
					{
						return $elementToAppend.remove();
					});
				});
		};
	}

	function fixBoatTooltips()
	{
		var boatBox = document.getElementById('item-box-boundRowBoat');
		var boatTooltip = boatBox && document.getElementById(boatBox.dataset.tooltipId || '');
		var canoeBox = document.getElementById('item-box-boundCanoe');
		if (!boatBox || !canoeBox || !boatTooltip)
		{
			return;
		}
		var canoeTooltip = boatTooltip.cloneNode(true);
		canoeTooltip.id = 'tooltip-boundCanoe';
		var header = canoeTooltip.firstElementChild;
		header.textContent = 'Canoe';
		boatTooltip.parentElement.appendChild(canoeTooltip);
		canoeBox.dataset.tooltipId = 'tooltip-boundCanoe';
		var boatDuration = document.createElement('div');
		boatDuration.innerHTML = '<strong>Trip duration:</strong> 3 hours';
		boatTooltip.appendChild(boatDuration);
		var canoeDuration = document.createElement('div');
		canoeDuration.innerHTML = '<strong>Trip duration:</strong> 6 hours';
		canoeTooltip.appendChild(canoeDuration);
	}

	function fixAlignments()
	{
		addStyle("\n#tab-container-crafting settings-container\n{\n\tmargin: 5px 30px;\n}\n#table-crafting-recipe,\n#table-brewing-recipe,\n#table-magic-recipe\n{\n\twidth: calc(100% - 2*20px - 2*10px);\n}\n#tab-sub-container-magic-items\n{\n\tmargin: 5px 0px;\n}\n#table-magic-recipe\n{\n\twidth: calc(100% - 2*10px);\n}\n\n#tab-container-farming\n{\n\tpadding: 0 20px;\n}\n#tab-sub-container-farming\n{\n\tmargin: 5px 0;\n\tmargin-bottom: -10px;\n}\ndiv.farming-patch,\ndiv.farming-patch-locked\n{\n\tmargin: 10px;\n}\n\n#combat-table-area\n{\n\tborder-spacing: 0;\n}\ndiv#hero-area.hero,\ndiv#monster-area.monster\n{\n\tmargin-left: 20px;\n\tmargin-right: 20px;\n\tmargin-top: 10px;\n}\ntable.table-hero-stats,\ndiv.hp-bar,\n#hero-area div.fight-spellbook\n{\n\tmargin-left: 0;\n}\ndiv.hp-bar\n{\n\tmin-width: calc(100% - 2px);\n}\n#hero-area span.fight-spell\n{\n\tmargin-bottom: 0;\n\tmargin-top: 0;\n}\n#hero-area span.fight-spell:first-child\n{\n\tmargin-left: 0;\n}\n#hero-area span.fight-spell:last-child\n{\n\tmargin-right: 0;\n}\n#hero-area > div:last-child,\n.imageMonster\n{\n\theight: 556px !important;\n\tmargin-top: -50px;\n}\n#monster-area div.hp-bar\n{\n\tmargin-top: 66px;\n\tmargin-bottom: 74px;\n}\n#monster-area > br:first-child,\n#monster-area table.table-hero-stats + br\n{\n\tdisplay: none;\n}\n.imageMonster\n{\n\talign-items: flex-end;\n\tdisplay: flex;\n\tposition: relative;\n}\n#combat-table-area[style$=\"auto;\"]\n{\n\tborder-color: transparent;\n}\n#img-monster\n{\n\tposition: absolute;\n}\n#img-monster[src$=\"/1.png\"]\n{\n\theight: 250px;\n}\n#img-monster[src$=\"/2.png\"]\n{\n\ttransform: translateY(30px);\n}\n#img-monster[src$=\"/3.png\"]\n{\n\theight: 180px;\n\ttransform: translateY(-350px);\n}\n#img-monster[src$=\"/4.png\"]\n{\n\theight: 180px;\n}\n#img-monster[src$=\"/5.png\"]\n{\n\theight: 700px;\n\ttransform: translateY(130px);\n}\n#img-monster[src$=\"/7.png\"]\n{\n\theight: 450px;\n\ttransform: translateY(30px);\n}\n#img-monster[src$=\"/8.png\"]\n{\n\theight: 280px;\n\ttransform: translateY(-260px);\n}\n#img-monster[src$=\"/9.png\"]\n{\n\theight: 450px;\n\ttransform: translateY(-10px);\n}\n#img-monster[src$=\"/11.png\"],\n#img-monster[src$=\"/15.png\"]\n{\n\ttransform: translateY(-180px);\n}\n#img-monster[src$=\"/14.png\"]\n{\n\theight: 500px;\n\tmargin-left: -50px;\n\tmargin-right: -50px;\n}\n#combat-table-area span.large-button,\n#combat-table-area span.medium-button\n{\n\tmargin: 10px;\n}\n#combat-table-area span.medium-button + br + br\n{\n\tdisplay: none;\n}\n\ndiv#market-slot-1.market-slot\n{\n\tfloat: initial;\n\tmargin-left: 10px !important;\n}\n\t\t");
	}

	function addHeroStatTooltips()
	{
		var table = document.querySelector('#hero-area table.table-hero-stats');
		if (!table)
		{
			return;
		}
		var statRow = table.rows.item(0);
		var attackCell = statRow.cells.item(0);
		attackCell.title = 'Attack Damage';
		window.$(attackCell).tooltip();
		var accuracyCell = statRow.cells.item(1);
		accuracyCell.title = 'Attack Accuracy';
		window.$(accuracyCell).tooltip();
		var speedCell = statRow.cells.item(2);
		speedCell.title = 'Attack Speed';
		window.$(speedCell).tooltip();
		var defenseCell = statRow.cells.item(3);
		defenseCell.title = 'Defense';
		window.$(defenseCell).tooltip();
	}

	function init()
	{
		fixClientGameLoop();
		fixScroller();
		fixTooltipStyle();
		fixRefreshingMagicRecipes();
		moveStrangeLeafs();
		fixTreasureMap();
		fixWoodcutting();
		fixRakeDialog();
		fixTooltips();
		fixQuestBook();
		fixSkillBars();
		fixCraftingXpTooltip();
		// apply fix for scroll images later to fix images in this code too
		fixHitText();
		fixScrollImages();
		fixQuest8BraveryRecipe();
		fixCookingDialogForUncookableFish();
		fixBoatTooltips();
		fixAlignments();
		addHeroStatTooltips();
	}
	temporaryFixes.init = init;
})(temporaryFixes || (temporaryFixes = {}));
/**
 * improve timer
 */
var improveTimer;
(function (improveTimer_1)
{
	improveTimer_1.name = 'improveTimer';

	function bindNewFormatter()
	{
		window.formatTime = function (seconds)
		{
			return format.timer(seconds);
		};
		window.formatTimeShort2 = function (seconds)
		{
			return format.timer(seconds);
		};
	}

	function improveSmeltingTimer()
	{
		addStyle("\n#notif-smelting > span:not(.timer)\n{\n\tdisplay: none;\n}\n\t\t");
		var smeltingNotifBox = document.getElementById('notif-smelting');
		var smeltingTimerEl = document.createElement('span');
		smeltingTimerEl.className = 'timer';
		smeltingNotifBox.appendChild(smeltingTimerEl);
		var smeltingInterval;

		function updatePercValues()
		{
			if (smeltingInterval)
			{
				window.clearInterval(smeltingInterval);
			}
			var delta = 0;
			smeltingInterval = window.setInterval(function ()
			{
				return updateSmeltingTimer(++delta);
			}, 1000);
			updateSmeltingTimer();
		}

		function updateSmeltingTimer(delta)
		{
			if (delta === void 0)
			{
				delta = 0;
			}
			var totalTime = window.smeltingPercD;
			// thanks at /u/marcus898 for your bug report
			var elapsedTime = Math.round(window.smeltingPerc * totalTime / 100) + delta;
			smeltingTimerEl.textContent = format.timer(Math.max(totalTime - elapsedTime, 0));
		}
		observer.add('smeltingPercD', function ()
		{
			return updatePercValues();
		});
		observer.add('smeltingPerc', function ()
		{
			return updatePercValues();
		});
		updatePercValues();
	}

	function improveTimer(cssRulePrefix, textColor, timerColor, infoIdPrefx, containerPrefix, updateFn)
	{
		addStyle("\n/* hide built in timer elements */\n" + cssRulePrefix + " > *:not(img):not(.info)\n{\n\tdisplay: none;\n}\n" + cssRulePrefix + " > div.info\n{\n\tcolor: " + textColor + ";\n\tmargin-top: 5px;\n\tpointer-events: none;\n\ttext-align: center;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n}\n" + cssRulePrefix + " > div.info > div.name\n{\n\tfont-size: 1.2rem;\n}\n" + cssRulePrefix + " > div.info > div.timer\n{\n\tcolor: " + timerColor + ";\n}\n\t\t");
		for (var i = 0; i < 6; i++)
		{
			var num = i + 1;
			var infoId = infoIdPrefx + num;
			var container = document.getElementById(containerPrefix + num);
			container.style.position = 'relative';
			var infoEl = document.createElement('div');
			infoEl.className = 'info';
			infoEl.id = infoId;
			infoEl.innerHTML = "<div class=\"name\"></div><div class=\"timer\"></div>";
			container.appendChild(infoEl);
			updateFn(num, infoId, true);
		}
	}

	function updateTreeInfo(placeId, infoElId, init)
	{
		if (init === void 0)
		{
			init = false;
		}
		var infoEl = document.getElementById(infoElId);
		var nameEl = infoEl.firstElementChild;
		var timerEl = infoEl.lastElementChild;
		var idKey = 'treeId' + placeId;
		var growTimerKey = 'treeGrowTimer' + placeId;
		var lockedKey = 'treeUnlocked' + placeId;
		var treeId = getGameValue(idKey);
		if (treeId == 0)
		{
			var isLocked = placeId > 4 && getGameValue(lockedKey) == 0;
			nameEl.textContent = isLocked ? 'Locked' : 'Empty';
			timerEl.textContent = '';
		}
		else
		{
			nameEl.textContent = key2Name(window.getTreeName(treeId)) || 'Unknown Tree';
			var remainingTime = window.TREE_GROW_TIME[treeId - 1] - getGameValue(growTimerKey);
			timerEl.textContent = remainingTime > 0 ? '(' + format.timer(remainingTime, true) + ')' : 'Fully grown';
		}
		if (init)
		{
			observer.add([idKey, growTimerKey, lockedKey], function ()
			{
				return updateTreeInfo(placeId, infoElId, false);
			});
		}
	}
	// add tree grow timer
	function improveTreeGrowTimer()
	{
		improveTimer('.woodcutting-tree', 'white', 'yellow', 'wc-tree-info-', 'wc-div-tree-', updateTreeInfo);
	}

	function updatePatchInfo(patchId, infoElId, init)
	{
		if (init === void 0)
		{
			init = false;
		}
		var infoEl = document.getElementById(infoElId);
		var nameEl = infoEl.querySelector('.name');
		var timerEl = infoEl.querySelector('.timer');
		var idKey = 'farmingPatchSeed' + patchId;
		var growTimeKey = 'farmingPatchGrowTime' + patchId;
		var timerKey = 'farmingPatchTimer' + patchId;
		var stageKey = 'farmingPatchStage' + patchId;
		var stage = getGameValue(stageKey);
		var seedName = SEED_NAME[getGameValue(idKey)] || 'Unkown Plant';
		if (stage == 0)
		{
			var isLocked = patchId > 4 && window.donorFarmingPatch == 0;
			nameEl.textContent = isLocked ? 'Locked' : 'Click to grow';
			timerEl.textContent = '';
		}
		else if (stage >= 4)
		{
			nameEl.textContent = stage > 4 ? 'Dead Plant' : seedName;
			timerEl.textContent = stage > 4 ? 'Click to remove' : 'Click to harvest';
		}
		else
		{
			nameEl.textContent = seedName;
			var remainingTime = getGameValue(growTimeKey) - getGameValue(timerKey);
			timerEl.textContent = '(' + format.timer(remainingTime, true) + ')';
		}
		if (init)
		{
			observer.add([idKey, timerKey, stageKey, 'donorFarmingPatch'], function ()
			{
				return updatePatchInfo(patchId, infoElId, false);
			});
		}
	}
	// add seed name and change color of timer
	function improveSeedGrowTimer()
	{
		improveTimer('div[id^="farming-patch-area-"]', 'black', 'blue', 'farming-patch-info-', 'farming-patch-area-', updatePatchInfo);
	}

	function init()
	{
		bindNewFormatter();
		improveSmeltingTimer();
		improveTreeGrowTimer();
		improveSeedGrowTimer();
	}
	improveTimer_1.init = init;
})(improveTimer || (improveTimer = {}));
/**
 * improve smelting dialog
 */
var improveSmelting;
(function (improveSmelting)
{
	improveSmelting.name = 'improveSmelting';
	var TIME_NEEDED_ID = 'smelting-time-needed';
	var LAST_SMELTING_AMOUNT_KEY = 'lastSmeltingAmount';
	var LAST_SMELTING_BAR_KEY = 'lastSmeltingBar';
	var smeltingValue = null;
	var amountInput;

	function prepareAmountInput()
	{
		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', '');
	}

	function setBarCap(bar, capacity)
	{
		if (bar == '')
		{
			bar = window.selectedBar;
		}
		var requirements = SMELTING_REQUIREMENTS[bar];
		var maxAmount = parseInt(capacity, 10);
		for (var key in requirements)
		{
			var req = requirements[key];
			maxAmount = Math.min(Math.floor(getGameValue(key) / req), 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;
			}
		}
	}

	function prepareTimeNeeded()
	{
		var neededMatsEl = document.getElementById('dialogue-furnace-mats-needed');
		var parent = neededMatsEl && neededMatsEl.parentElement;
		if (!neededMatsEl || !parent)
		{
			return;
		}
		var br = document.createElement('br');
		var timeBox = document.createElement('div');
		timeBox.className = 'basic-smallbox';
		timeBox.innerHTML = "<img src=\"images/icons/hourglass.png\" class=\"image-icon-30\">\n\t\tDuration: <span id=\"" + TIME_NEEDED_ID + "\"></span>";
		var next = neededMatsEl.nextElementSibling;
		parent.insertBefore(br, next);
		parent.insertBefore(timeBox, next);
	}

	function updateTimeNeeded(value)
	{
		var timeEl = document.getElementById(TIME_NEEDED_ID);
		if (!timeEl)
		{
			return;
		}
		var num = parseInt(value, 10);
		var timePerBar = SMELTING_TIME[window.selectedBar];
		timeEl.textContent = format.timer(timePerBar * num);
	}

	function init()
	{
		prepareAmountInput();
		prepareTimeNeeded();
		var _selectBar = window.selectBar;
		var updateSmeltingRequirements = function (bar, inputElement, inputBarsAmountEl, capacity)
		{
			_selectBar(bar, inputElement, inputBarsAmountEl, capacity);
			updateTimeNeeded(inputBarsAmountEl.value);
		};
		window.selectBar = function (bar, inputElement, inputBarsAmountEl, capacity)
		{
			setBarCap(bar, capacity);
			// save selected bar
			if (bar != '')
			{
				store.set(LAST_SMELTING_BAR_KEY, bar);
			}
			// save amount
			store.set(LAST_SMELTING_AMOUNT_KEY, inputBarsAmountEl.value);
			updateSmeltingRequirements(bar, inputElement, inputBarsAmountEl, capacity);
		};
		var lastBar = store.get(LAST_SMELTING_BAR_KEY);
		var lastAmount = store.get(LAST_SMELTING_AMOUNT_KEY);
		var _openFurnaceDialogue = window.openFurnaceDialogue;
		window.openFurnaceDialogue = function (furnace)
		{
			var capacity = window.getFurnaceCapacity(furnace);
			if (window.smeltingBarType == 0)
			{
				amountInput.max = capacity.toString();
			}
			// restore amount
			var inputBarsAmountEl = document.getElementById('input-smelt-bars-amount');
			if (inputBarsAmountEl && inputBarsAmountEl.value == '-1' && lastAmount != null)
			{
				inputBarsAmountEl.value = lastAmount;
			}
			_openFurnaceDialogue(furnace);
			// restore selected bar
			if ((!window.selectedBar || window.selectedBar == 'none') && lastBar != null)
			{
				window.selectedBar = lastBar;
			}
			// update whether requirements are fulfilled
			var barInputId = 'input-furnace-' + split2Words(window.selectedBar, '-').toLowerCase();
			var inputElement = document.getElementById(barInputId);
			if (inputElement && inputBarsAmountEl)
			{
				updateSmeltingRequirements(window.selectedBar, inputElement, inputBarsAmountEl, capacity.toString());
			}
		};
	}
	improveSmelting.init = init;
})(improveSmelting || (improveSmelting = {}));
/**
 * add chance to time calculator
 */
var fishingInfo;
(function (fishingInfo)
{
	fishingInfo.name = 'fishingInfo';
	/**
	 * 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\t<tr>\n\t\t\t\t\t<td>" + p + "%</td>\n\t\t\t\t\t<td>" + format.time2NearestUnit(calcSecondsTillP(chancePerSecond, p), true) + "</td>\n\t\t\t\t</tr>";
		}
		tooltipEl.innerHTML = "<h2>" + headline + "</h2>\n\t\t\t<table class=\"chance\">\n\t\t\t\t<tr>\n\t\t\t\t\t<th>Probability</th>\n\t\t\t\t\t<th>Time</th>\n\t\t\t\t</tr>\n\t\t\t\t" + percRows + "\n\t\t\t</table>\n\t\t";
	}

	function addChanceStyle()
	{
		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\t");
	}

	function addXp()
	{
		var table = document.querySelector('#dialogue-id-fishingRod table');
		if (!table)
		{
			return;
		}
		var rows = table.rows;
		for (var i = 0; i < rows.length; i++)
		{
			var row = rows.item(i);
			if (row.classList.contains('xp-added'))
			{
				continue;
			}
			if (i == 0)
			{
				var xpCell = document.createElement('th');
				xpCell.textContent = 'XP';
				row.appendChild(xpCell);
			}
			else
			{
				var cell = row.insertCell(-1);
				var rawFish = row.id.replace('dialogue-fishing-rod-tr-', '');
				var xp = FISH_XP[rawFish];
				cell.textContent = xp == null ? '?' : format.number(xp);
			}
			row.classList.add('xp-added');
		}
	}

	function chance2TimeCalculator()
	{
		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' + capitalize(fish);
			var row = document.getElementById('dialogue-fishing-rod-tr-' + rawFish);
			if (!row)
			{
				continue;
			}
			var chanceCell = row.cells.item(4);
			var chance = (chanceCell.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);
		}
	}

	function init()
	{
		addChanceStyle();
		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();
			addXp();
			chance2TimeCalculator();
		};
	}
	fishingInfo.init = init;
})(fishingInfo || (fishingInfo = {}));
/**
 * add tooltips for recipes
 */
var recipeTooltips;
(function (recipeTooltips)
{
	recipeTooltips.name = 'recipeTooltips';

	function updateRecipeTooltips(recipeKey, recipes)
	{
		var table = document.getElementById('table-' + recipeKey + '-recipe');
		var rows = table.rows;

		function recipe2Title(recipe)
		{
			return recipe.recipe
				.map(function (name, i)
				{
					return format.number(recipe.recipeCost[i]) + ' '
						+ split2Words(name).toLowerCase();
				})
				.join(' + ');
		};
		for (var i = 1; i < rows.length; i++)
		{
			var row = rows.item(i);
			var key = row.id.replace(recipeKey + '-', '');
			var recipe = recipes[key];
			var requirementCell = row.cells.item(3);
			requirementCell.title = recipe2Title(recipe);
			window.$(requirementCell).tooltip();
		}
	}

	function updateTooltipsOnReinitRecipes(key)
	{
		var capitalKey = capitalize(key);
		var processKey = 'process' + capitalKey + 'Tab';
		var _processTab = window[processKey];
		window[processKey] = function ()
		{
			var reinit = !!getGameValue('refreshLoad' + capitalKey + 'Table');
			_processTab();
			if (reinit)
			{
				updateRecipeTooltips(key, getGameValue(key + 'Recipes'));
			}
		};
	}

	function init()
	{
		updateTooltipsOnReinitRecipes('crafting');
		updateTooltipsOnReinitRecipes('brewing');
		updateTooltipsOnReinitRecipes('magic');
		updateTooltipsOnReinitRecipes('cooksBook');
	}
	recipeTooltips.init = init;
})(recipeTooltips || (recipeTooltips = {}));
/**
 * fix formatting of numbers
 */
var fixNumbers;
(function (fixNumbers)
{
	fixNumbers.name = 'fixNumbers';

	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 format.number(cost);
		});
		newRecipe.xp = format.number(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));
		};
		var tooltipList = document.querySelectorAll('#tooltip-list div[id^="tooltip-"][id$="Seeds"]');
		for (var i = 0; i < tooltipList.length; i++)
		{
			var tooltip = tooltipList[i];
			tooltip.innerHTML = format.numbersInText(tooltip.innerHTML);
		}
		var fightEnergyCells = document.querySelectorAll('#dialogue-fight tr > td:nth-child(4)');
		for (var i = 0; i < fightEnergyCells.length; i++)
		{
			var cell = fightEnergyCells[i];
			cell.innerHTML = format.numbersInText(cell.innerHTML);
		}
	}

	function init()
	{
		fixNumberFormat();
	}
	fixNumbers.init = init;
})(fixNumbers || (fixNumbers = {}));
/**
 * add slider for machines
 */
var betterMachines;
(function (betterMachines)
{
	betterMachines.name = 'betterMachines';
	var $slider;

	function createSlider()
	{
		var br = document.querySelector('#dialogue-machinery-current-total ~ br');
		var parent = br && br.parentElement;
		if (!br || !parent)
		{
			return;
		}
		var slider = document.createElement('div');
		slider.style.margin = '10px 5px';
		parent.insertBefore(slider, br);
		$slider = window.$(slider)
			.slider(
			{
				range: false
				, min: 0
				, max: 10
				, value: 0
				, slide: function (event, ui)
				{
					return updateValue(ui.value);
				}
			});
		// hide br and up/down arrows
		br.style.display = 'none';
		var arrows = document.querySelectorAll('input[onclick^="turnOn("]');
		for (var i = 0; i < arrows.length; i++)
		{
			arrows[i].style.display = 'none';
		}
		var els = document.querySelectorAll('[onclick*="openMachineryDialogue("]');
		var boundMachineKeyList = [];
		for (var i = 0; i < els.length; i++)
		{
			var match = els[i].id.match(/openMachineryDialogue\('(.+?)'\)/);
			if (match)
			{
				boundMachineKeyList.push(getBoundKey(match[1]));
			}
		}
		observer.add(boundMachineKeyList, function ()
		{
			return updateMax();
		});
	}

	function updateMax()
	{
		var machineEl = document.getElementById('dialogue-machinery-chosen');
		if (machineEl && machineEl.value != '')
		{
			var boundMachineKey = getBoundKey(machineEl.value);
			$slider.slider('option', 'max', getGameValue(boundMachineKey));
		}
	}

	function updateValue(value)
	{
		var typeEl = document.getElementById('dialogue-machinery-chosen');
		var numEl = document.getElementById('dialogue-machinery-current-on');
		if (numEl && typeEl)
		{
			var valueBefore = parseInt(numEl.textContent || '0', 10);
			var machine = typeEl.value;
			var increment = valueBefore < value;
			var diff = Math.abs(valueBefore - value);
			for (var i = 0; i < diff; i++)
			{
				window.turnOn(machine, increment);
			}
		}
	}

	function init()
	{
		createSlider();
		var _openMachineryDialogue = window.openMachineryDialogue;
		window.openMachineryDialogue = function (machineType)
		{
			_openMachineryDialogue(machineType);
			updateMax();
			$slider.slider('value', getGameValue(machineType + 'On'));
		};
	}
	betterMachines.init = init;
})(betterMachines || (betterMachines = {}));
/**
 * improve behaviour of amount inputs
 */
var amountInputs;
(function (amountInputs)
{
	amountInputs.name = 'amountInputs';

	function getMax(recipe)
	{
		var max = Number.MAX_SAFE_INTEGER;
		for (var i = 0; i < recipe.recipe.length; i++)
		{
			max = Math.min(max, Math.floor(getGameValue(recipe.recipe[i]) / recipe.recipeCost[i]));
		}
		return max;
	}

	function setAmount(id, recipeCollection, key)
	{
		var numInput = document.getElementById(id);
		if (!numInput)
		{
			return;
		}
		var max = getMax(recipeCollection[key]);
		numInput.value = max.toString();
		numInput.select();
	}

	function init()
	{
		var _multiCraft = window.multiCraft;
		window.multiCraft = function (item)
		{
			_multiCraft(item);
			setAmount('dialogue-multicraft-input', window.craftingRecipes, item);
		};
		var _brew = window.brew;
		window.brew = function (potion)
		{
			_brew(potion);
			setAmount('dialogue-brewing-input', window.brewingRecipes, potion);
		};
		var _cooksBookInputDialogue = window.cooksBookInputDialogue;
		window.cooksBookInputDialogue = function (food)
		{
			_cooksBookInputDialogue(food);
			setAmount('dialogue-cooksBook-input', window.cooksBookRecipes, food);
		};
	}
	amountInputs.init = init;
})(amountInputs || (amountInputs = {}));
/**
 * improves the top bar
 */
var newTopbar;
(function (newTopbar)
{
	newTopbar.name = 'newTopbar';

	function init()
	{
		addStyle("\ntable.top-links,\ntable.top-links *\n{\n\tpadding: 0;\n}\ntable.top-links td > *\n{\n\tdisplay: inline-block;\n\tpadding: 2px 6px;\n}\n\t\t");
		var table = document.querySelector('table.top-links');
		if (!table)
		{
			return;
		}
		var row = table.rows.item(0);
		var cells = row.cells;
		var tabIdx = [2, 4];
		var infoIdx = [5, 6];
		var newRow = table.insertRow(-1);
		var linkCell = newRow.insertCell(-1);
		var tabCell = newRow.insertCell(-1);
		tabCell.style.textAlign = 'center';
		var infoCell = newRow.insertCell(-1);
		infoCell.style.textAlign = 'right';
		for (var i = 0; i < cells.length; i++)
		{
			var container = linkCell;
			if (tabIdx.indexOf(i) != -1)
			{
				container = tabCell;
			}
			else if (infoIdx.indexOf(i) != -1)
			{
				container = infoCell;
			}
			var cell = cells.item(i);
			var el = cell.firstElementChild;
			if (cell.childNodes.length > 1)
			{
				el = document.createElement('span');
				el.style.color = 'yellow';
				while (cell.childNodes.length > 0)
				{
					el.appendChild(cell.childNodes[0]);
				}
			}
			if (container.children.length > 0)
			{
				container.appendChild(document.createTextNode('|'));
			}
			if (el)
			{
				container.appendChild(el);
			}
		}
		var parent = row.parentElement;
		if (parent)
		{
			parent.removeChild(row);
		}
		var _openTab = window.openTab;
		window.openTab = function (newTab)
		{
			var oldTab = window.currentOpenTab;
			_openTab(newTab);
			var children = tabCell.children;
			for (var i = 0; i < children.length; i++)
			{
				var el = children[i];
				var match = (el.getAttribute('onclick') || '').match(/openTab\('([^']+)'\)/);
				if (!match)
				{
					continue;
				}
				var tab = match[1];
				if (oldTab == tab)
				{
					el.style.color = '';
				}
				if (newTab == tab)
				{
					el.style.color = 'white';
				}
			}
		};
	}
	newTopbar.init = init;
})(newTopbar || (newTopbar = {}));
/**
 * style tweaks
 */
var styleTweaks;
(function (styleTweaks)
{
	styleTweaks.name = 'styleTweaks';

	function addTweakStyle(setting, style)
	{
		if (setting != '')
		{
			var prefix = setting == '' ? '' : 'body.' + setting;
			style = style.replace(/(^\s*|,\s*|\}\s*)([^\{\},]+)(,|\s*\{)/g, '$1' + prefix + ' $2$3');
			document.body.classList.add(setting);
		}
		addStyle(style, setting != '' ? setting : null);
	}
	// tweak oil production/consumption
	function tweakOil()
	{
		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\t");
		// make room for oil cell on small devices
		var oilFlowValues = document.getElementById('oil-flow-values');
		var oilFlowCell = oilFlowValues.parentElement;
		oilFlowCell.style.width = '30%';
	}

	function tweakSelection()
	{
		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\t");
	}
	// tweak stardust monitor of DH2QoL to keep it in place
	function tweakStardust()
	{
		addTweakStyle('dh2qol', "\n#dh2qol-stardustMonitor\n{\n\tdisplay: inline-block;\n\tmargin-left: .25rem;\n\ttext-align: left;\n\twidth: 2.5rem;\n}\n\t\t");
	}

	function tweakSkillLevelText()
	{
		addTweakStyle('', "\ndiv.skill-xp-label\n{\n\ttext-shadow: white 0px 0px 0.5rem;\n}\n\t\t");
	}

	function tweakFightDialog()
	{
		addTweakStyle('smaller-fight-dialog', "\n#dialogue-fight img[width=\"300px\"]\n{\n\twidth: 120px;\n\theight: 50px;\n}\n#dialogue-fight img[src=\"images/icons/combat.png\"] ~ br\n{\n\tdisplay: none;\n}\n\t\t");
	}
	function init()
	{
		tweakOil();
		tweakSelection();
		tweakStardust();
		tweakSkillLevelText();
		tweakFightDialog();
	}
	styleTweaks.init = init;
})(styleTweaks || (styleTweaks = {}));
/**
 * init
 */
var scriptInitialized = false;

function init()
{
	console.info('[%s] "DH2 Fixed %s" up and running.', (new Date).toLocaleTimeString(), version);
	scriptInitialized = true;
	var initModules = [
		settings
		, notifications
		, gameEvents
		, temporaryFixes
		, hideCraftedRecipes
		, hideUselessItems
		, improveItemBoxes
		, chat
		, improveTimer
		, improveSmelting
		, fishingInfo
		, recipeTooltips
		, fixNumbers
		, betterMachines
		, amountInputs
		, newTopbar
		, styleTweaks
	];
	window.initModules = initModules;
	for (var _i = 0, initModules_1 = initModules; _i < initModules_1.length; _i++)
	{
		var module = initModules_1[_i];
		try
		{
			module.init();
		}
		catch (error)
		{
			console.error('Error during initialization in module "' + module.name + '":', error);
		}
	}
}
document.addEventListener('DOMContentLoaded', function ()
{
	var oldValues = new Map();
	var _doCommand = window.doCommand;
	window.doCommand = function (data)
	{
		if (data.startsWith('REFRESH_ITEMS='))
		{
			oldValues = new Map();
			for (var _i = 0, _a = window.jsItemArray; _i < _a.length; _i++)
			{
				var key = _a[_i];
				oldValues.set(key, getGameValue(key));
			}
			_doCommand(data);
			if (!scriptInitialized)
			{
				init();
			}
			return;
		}
		else if (!scriptInitialized)
		{
			if (data.startsWith('CHAT='))
			{
				var parts = data.substr(5).split('~');
				return chat.newAddToChatBox(parts[0], parts[1], parts[2], parts[3], 0);
			}
			else if (data.startsWith('PM='))
			{
				return chat.newAddToChatBox(window.username, '0', '0', data.substr(3), 1);
			}
		}
		var formattedData = data;
		if (data.startsWith('STHS=') || data.startsWith('STE=') || data.startsWith('SM=') || data.startsWith('ST='))
		{
			if (data.indexOf('Your account has been running for:') != -1)
			{
				data = data.replace(/Your account has been running for: (\d+) minutes./, function (wholeMatch, minutes)
				{
					return 'Your account has been running for ' + format.min2Str(minutes) + '.';
				});
			}
			formattedData = format.numbersInText(data);
		}
		var ret = _doCommand(formattedData);
		// notifications for this kind of message: "SM=An update has been scheduled for today."
		if (data.startsWith('SM='))
		{
			if (settings.get(settings.KEY.showNotifications))
			{
				var msg = data.substr(3)
					.replace(/<br\s*\/?>/g, '\n')
					.replace(/<img src='images\/(.+?)\.png'.+?\/?> (\d+)/g, function (wholeMatch, key, amount)
					{
						return format.number(amount) + ' ' + split2Words(key) + ', ';
					})
					.replace(/(\s)\1+/g, '$1')
					.replace(/, $/, '')
					.replace(/<.+?>/g, '');
				notifications.event('Message from server'
				, {
					body: msg
				});
			}
		}
		return ret;
	};
	var _refreshItemValues = window.refreshItemValues;
	window.refreshItemValues = function (itemKeyList, firstLoad)
	{
		_refreshItemValues(itemKeyList, firstLoad);
		for (var _i = 0, itemKeyList_1 = itemKeyList; _i < itemKeyList_1.length; _i++)
		{
			var key = itemKeyList_1[_i];
			observer.notify(key, oldValues.get(key));
		}
	};
});
/**
 * fix web socket errors
 */
var main;
(function (main)
{
	function webSocketLoaded(event)
	{
		var ws = window.webSocket;
		if (ws == null)
		{
			console.error('no webSocket instance found!');
			return;
		}
		var messageQueue = [];
		var _onMessage = ws.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 _cBytes = window.cBytes;
		window.cBytes = function (command)
		{
			return commandQueue.push(command);
		};
		var _onOpen = ws.onopen;
		ws.onopen = function (event)
		{
			window.cBytes = _cBytes;
			commandQueue.forEach(function (command)
			{
				return window.cBytes(command);
			});
			return _onOpen.call(ws, event);
		};
	}

	function isScriptElement(el)
	{
		return el.nodeName === 'SCRIPT';
	}

	function isWebSocketScript(script)
	{
		return script.src.includes('socket.js');
	}
	var found = false;
	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;
			found = true;
		}
	}
	if (!found)
	{
		// 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
		});
	}
	// fix scrollText (e.g. when joining the game and receiving xp at that moment)
	window.mouseX = window.innerWidth / 2;
	window.mouseY = window.innerHeight / 2;
})(main || (main = {}));

})();