Greasy Fork is available in English.

DH2 Fixed

Improve Diamond Hunt 2

// ==UserScript==
// @name         DH2 Fixed
// @namespace    FileFace
// @description  Improve Diamond Hunt 2
// @version      0.246.2
// @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.246.2';
var buildTime = new Date('2017-08-12T16:37:11.942Z');
var win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
"use strict";

/**
 * observer
 */
var observer;
(function (observer)
{
	observer.GAME_TICK_KEY = 'dh2.gameTick';
	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);
		if (observedKeys.has(key))
		{
			observedKeys.get(key).forEach(function (fn)
			{
				return fn(key, oldValue, newValue);
			});
		}
	}
	observer.notify = notify;

	function notifyTick()
	{
		notify(observer.GAME_TICK_KEY, Math.floor(now() / 1000));
	}
	observer.notifyTick = notifyTick;

	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;

	function addTick(fn)
	{
		return add(observer.GAME_TICK_KEY, fn);
	}
	observer.addTick = addTick;

	function removeTick(fn)
	{
		return remove(observer.GAME_TICK_KEY, fn);
	}
	observer.removeTick = removeTick;
})(observer || (observer = {}));
/**
 * global constants
 */
var PLUS_MINUS_SIGN = String.fromCharCode(177);
var TIER_LEVELS = ['empty', 'sapphire', 'emerald', 'ruby', 'diamond'];
var TIER_NAMES = ['Standard', 'Sapphire', 'Emerald', 'Ruby', 'Diamond'];
var TIER_ITEMS = ['pickaxe', 'shovel', 'hammer', 'axe', 'rake', 'trowel', 'fishingRod', 'chisel'];
var ORB_ITEMS = ['pickaxe', 'shovel', 'hammer', 'axe', 'rake', 'trowel', 'fishingRod', 'chisel', 'oilPipe'];
var TIER_ITEMS_NOT_BINDABLE = ['rake', 'trowel'];
var FURNACE_LEVELS = ['stone', 'bronze', 'iron', 'silver', 'gold', 'promethium'];
var OVEN_LEVELS = ['bronze', 'iron', 'silver', 'gold', 'promethium'];
var WAND_LEVELS = ['wooden', 'oak', 'willow', 'maple', 'stardust'];
var OIL_STORAGE_SIZES = [10e3, 50e3, 100e3, 300e3, 600e3, 2e6];
var RECIPE_MAX = {
	'brewing':
	{
		'braveryPotion':
		{
			max: 1
		}
		, 'stardustCrystalPotion':
		{
			max: 1
		}
	}
	, 'cooksBook':
	{}
	, 'crafting':
	{
		'drills':
		{
			max: 10
		}
		, 'crushers':
		{
			max: 10
		}
		, 'giantDrills':
		{
			max: 10
		}
		, 'excavators':
		{
			max: 10
		}
		, 'oilPipe':
		{
			max: 1
		}
		, 'pumpjacks':
		{
			max: 10
		}
		, 'rowBoat':
		{
			max: 1
		}
		, 'canoe':
		{
			max: 1
		}
		, 'sailBoat':
		{
			max: 1
		}
		, 'steamBoat':
		{
			max: 1
		}
		// thanks aguyd
		, 'bonemealBin':
		{
			extraKeys: ['boundFilledBonemealBin']
			, max: 1
		}
		, 'oilFactory':
		{
			max: 1
		}
		, 'brewingKit':
		{
			max: 1
		}
		, 'rocket':
		{
			max: 1
		}
	}
	, 'magic':
	{}
};
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
	}
	, 'promethiumBar':
	{
		promethium: 1
		, charcoal: 1
	}
};
var PLANT_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'
	, '19': 'Carrots'
	, '20': 'Tomatoes'
	, '21': 'Potatoes'
	, '22': 'Essence Tree'
};
var SKILL_LIST = ['mining', 'crafting', 'woodcutting', 'farming', 'brewing', 'combat', 'fishing', 'cooking', 'magic'];
var AREA_LIST = ['fields', 'forests', 'caves', 'volcano', 'northFields', 'hauntedMansion'];
var AREA_NAMES = ['Fields', 'Forests', 'Caves', 'Volcano', 'Northern Fields', 'Haunted Mansion'];

function getAreaName(areaId)
{
	if (areaId === 33)
	{
		return 'Quest';
	}
	else
	{
		return AREA_NAMES[areaId];
	}
}
var MONSTER_NAMES = ['Chicken', 'Rat', 'Bee', 'Snake', 'Field Tree', 'Thief', 'Bear', 'Bat', 'Skeleton', 'Golem', 'Fire Bird', 'Healer', 'Lizard', 'Northern Tree', 'Ice Bird', 'Phantom', 'Ghost', 'Grim Reaper', 'Troll', 'Five Eyed', 'Stone Golem'];

function getMonsterName(monsterId)
{
	if (monsterId === 101)
	{
		return 'Ghostly Old Mage';
	}
	else
	{
		return MONSTER_NAMES[monsterId];
	}
}
var FISH_XP = {
	'rawShrimp': 50
	, 'rawSardine': 500
	, 'rawSalmon': 700
	, 'rawTuna': 3e3
	, 'rawLobster': 5e3
	, 'rawSwordfish': 5e3
	, 'rawEel': 6e3
	, 'rawShark': 12e3
	, 'rawWhale': 20e3
	, 'rawRainbowFish': 30e3
};
var BOAT_LIST = ['rowBoat', 'canoe', 'sailBoat', 'steamBoat'];
var TRIP_DURATION = {
	'rowBoat': 3
	, 'canoe': 5
	, 'sailBoat': 7
	, 'steamBoat': 10
};
var MAX_ROCKET_KM = 384400;

var format;
(function (format)
{
	var UNITS = [
	{
		threshold: 10e3
		, factor: 1e3
		, token: 'k'
	}
	, {
		threshold: 1e6
		, factor: 1e6
		, token: 'M'
	}
	, {
		threshold: 1e9
		, factor: 1e9
		, token: 'B'
	}
	, {
		threshold: 1e12
		, factor: 1e12
		, token: 'T'
	}
	, {
		threshold: 1e15
		, factor: 1e15
		, token: 'Q'
	}];
	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 ensureNumber(num)
	{
		return (typeof num === 'number' ? num : Number(num));
	}

	function number(num, shorten)
	{
		if (shorten === void 0)
		{
			shorten = false;
		}
		num = ensureNumber(num);
		if (shorten)
		{
			for (var i = UNITS.length - 1; i >= 0; i--)
			{
				var unit = UNITS[i];
				if (num >= unit.threshold)
				{
					return number(Math.round(num / unit.factor)) + unit.token;
				}
			}
		}
		return num.toLocaleString('en');
	}
	format.number = number;

	function numbersInText(text)
	{
		return text.replace(/\d(?:[\d',\.]*\d)?/g, function (numStr)
		{
			return 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 days = Math.floor(timer / 86400); // 24 * 60 * 60
		var hours = Math.floor((timer % 86400) / 3600); // 60 * 60
		var minutes = Math.floor((timer % 3600) / 60);
		var seconds = timer % 60;
		return (shorten && days === 0 ? '' : days + 'd ')
			+ (shorten && days === 0 && 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 sec2Str(seconds)
	{
		seconds = Number(seconds);
		if (seconds < 0)
		{
			return seconds.toString();
		}
		var s = seconds % 60;
		var m = Math.floor(seconds / 60) % 60;
		var h = Math.floor(seconds / 3600);
		var strs = [];
		if (h > 0)
		{
			strs.push(h + ' hour' + (h == 1 ? '' : 's'));
		}
		if (m > 0)
		{
			strs.push(m + ' minute' + (m == 1 ? '' : 's'));
		}
		if (s > 0)
		{
			strs.push(s + ' second' + (s == 1 ? '' : 's'));
		}
		if (strs.length > 1)
		{
			var glue = ' and ';
			for (var i = strs.length - 2; i >= 0; i--)
			{
				strs[i] = strs[i] + glue + strs[i + 1];
				glue = ', ';
			}
			return strs[0];
		}
		else
		{
			return strs[0] || '';
		}
	}
	format.sec2Str = sec2Str;

	function min2Str(minutes)
	{
		return sec2Str(Number(minutes) * 60);
	}
	format.min2Str = min2Str;
})(format || (format = {}));

/**
 * general functions
 */
function getStyle(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);
	}
	return styleElement;
}

function addStyle(styleCode, elId)
{
	var styleElement = getStyle(elId);
	styleElement.innerHTML += styleCode;
}

function zeroPadLeft(num)
{
	return (num < 10 ? '0' : '') + num;
}

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 pluralize(name)
{
	return name.replace(/([^aeiou])y$/, '$1ie').replace(/s?$/, '') + 's';
}

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 getWikiaKey(key)
{
	return key2Name(key.replace(/^bound-?|^special-case-/i, '').replace(/\d+[km]?$/i, ''))
		.replace(/^\s/, '').replace(/[ -]/g, '_')
		.replace(/^(?:Empty|Sapphire|Emerald|Ruby|Diamond|Raw|Uncooked|Filled)_/, '')
		.replace(/^(?:Bronze|Iron|Silver|Gold|Promethium|Runite)_(?!Bar)/, '')
		.replace(/^Npc_/, 'Monster_')
		.replace(/_(?:Unlocked|Quest)$/, '');
}

function getWikiaLink(key)
{
	return 'http://diamondhuntonline.wikia.com/wiki/' + getWikiaKey(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;
		win.$(target).bind(
		{
			mousemove: win.changeTooltipPosition
			, mouseenter: win.showTooltip
			, mouseleave: function (event)
			{
				var target = event.target;
				var parent = target.parentElement;
				// ensure tooltips inside an tooltip element is possible
				if (!!target.dataset.tooltipId && parent && !!parent.dataset.tooltipId)
				{
					win.showTooltip.call(parent, event);
				}
				else
				{
					win.hideTooltip(event);
				}
			}
		});
	}
	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 win[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 = win.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);
}

function doGet(url)
{
	return new Promise(function (resolve, reject)
	{
		var request = new XMLHttpRequest();
		request.onreadystatechange = function (event)
		{
			if (request.readyState != XMLHttpRequest.DONE)
			{
				return;
			}
			if (request.status != 200)
			{
				return reject(event);
			}
			resolve(request.responseText);
		};
		request.open('GET', url);
		request.send();
	});
}

function removeWhitespaceChildNodes(el)
{
	for (var i = 0; i < el.childNodes.length; i++)
	{
		var child = el.childNodes.item(i);
		if (child.nodeType === Node.TEXT_NODE && /^\s*$/.test(child.textContent || ''))
		{
			el.removeChild(child);
			i--;
		}
	}
}

function debounce(func, wait, immediate)
{
	var timeout;
	return function ()
	{
		var _this = this;
		var args = [];
		for (var _i = 0; _i < arguments.length; _i++)
		{
			args[_i] = arguments[_i];
		}
		var callNow = immediate && !timeout;
		timeout && clearTimeout(timeout);
		timeout = setTimeout(function ()
		{
			timeout = null;
			if (!immediate)
			{
				func.apply(_this, args);
			}
		}, wait);
		if (callNow)
		{
			func.apply(this, args);
		}
	};
}

function passThis(fn)
{
	return function ()
	{
		var args = [];
		for (var _i = 0; _i < arguments.length; _i++)
		{
			args[_i] = arguments[_i];
		}
		return fn.apply(void 0, [this].concat(args));
	};
}
/**
 * persistence store
 */
var store;
(function (store)
{
	var oldPrefix = 'dh2-';
	var storePrefix = 'dh2.';

	function update(key, keepOldValue)
	{
		if (keepOldValue === void 0)
		{
			keepOldValue = true;
		}
		if (localStorage.hasOwnProperty(oldPrefix + key))
		{
			if (keepOldValue)
			{
				localStorage.setItem(storePrefix + key, localStorage.getItem(oldPrefix + key));
			}
			localStorage.removeItem(oldPrefix + key);
		}
	}
	var changeListener = new Map();

	function changeDetected(key, oldValue, newValue)
	{
		if (changeListener.has(key))
		{
			setTimeout(function ()
			{
				changeListener.get(key).forEach(function (fn)
				{
					return fn(key, oldValue, newValue);
				});
			});
		}
	}

	function watchFn(fnName)
	{
		var _fn = localStorage[fnName];
		localStorage[fnName] = function (key)
		{
			var args = [];
			for (var _i = 1; _i < arguments.length; _i++)
			{
				args[_i - 1] = arguments[_i];
			}
			var oldValue = localStorage.getItem(key);
			_fn.apply(localStorage, [key].concat(args));
			var newValue = localStorage.getItem(key);
			if (oldValue !== newValue)
			{
				changeDetected(key, oldValue, newValue);
			}
		};
	}
	watchFn('setItem');
	watchFn('removeItem');
	var _clear = localStorage.clear;
	localStorage.clear = function ()
	{
		var oldValues = new Map();
		for (var i = 0; i < localStorage.length; i++)
		{
			var key = localStorage.key(i);
			oldValues.set(key, localStorage.getItem(key));
		}
		_clear();
		for (var key in oldValues)
		{
			var newValue = localStorage.getItem(key);
			if (oldValues.get(key) !== newValue)
			{
				changeDetected(key, oldValues.get(key), newValue);
			}
		}
	};

	function addChangeListener(key, fn)
	{
		if (!changeListener.has(key))
		{
			changeListener.set(key, new Set());
		}
		changeListener.get(key).add(fn);
	}
	store.addChangeListener = addChangeListener;

	function removeChangeListener(key, fn)
	{
		if (changeListener.has(key))
		{
			changeListener.get(key).delete(fn);
		}
	}
	store.removeChangeListener = removeChangeListener;

	function get(key)
	{
		update(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)
	{
		update(key);
		return localStorage.hasOwnProperty(storePrefix + key);
	}
	store.has = has;

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

	function set(key, value)
	{
		update(key, false);
		localStorage.setItem(storePrefix + key, JSON.stringify(value));
	}
	store.set = set;
})(store || (store = {}));

var settings;
(function (settings)
{
	settings.name = 'settings';
	var DIALOG_WIDTH = 450;
	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["enableSpamDetection"] = 8] = "enableSpamDetection";
		KEY[KEY["showNotifications"] = 9] = "showNotifications";
		KEY[KEY["showEssencePopup"] = 10] = "showEssencePopup";
		KEY[KEY["wikiaLinks"] = 11] = "wikiaLinks";
		KEY[KEY["newXpAnimation"] = 12] = "newXpAnimation";
		KEY[KEY["amountSymbol"] = 13] = "amountSymbol";
		KEY[KEY["showTabTimer"] = 14] = "showTabTimer";
		KEY[KEY["showLootTab"] = 15] = "showLootTab";
		KEY[KEY["useEfficiencyStyle"] = 16] = "useEfficiencyStyle";
		KEY[KEY["makeNumberInputs"] = 17] = "makeNumberInputs";
		KEY[KEY["addKeepInput"] = 18] = "addKeepInput";
		KEY[KEY["addMaxBtn"] = 19] = "addMaxBtn";
		KEY[KEY["highlightUnplantableSeed"] = 20] = "highlightUnplantableSeed";
		KEY[KEY["showSdChange"] = 21] = "showSdChange";
		KEY[KEY["usePotionWarning"] = 22] = "usePotionWarning";
		KEY[KEY["showCaptions"] = 23] = "showCaptions";
		KEY[KEY["syncPriceHistory"] = 24] = "syncPriceHistory";
		KEY[KEY["useNewToolbar"] = 25] = "useNewToolbar";
		KEY[KEY["changeMachineDialog"] = 26] = "changeMachineDialog";
	})(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
		}
		, _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
		}
		, _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
		}
		, _a[KEY.colorizeChat] = {
			name: 'Colorize chat messages'
			, description: "Colorize chat messages according to a unique color for each user"
			, defaultValue: false
			, sub:
			{
				'colorizer':
				{
					defaultValue: 0
					, label: ['Equally Distributed', 'Random (light colors)', 'Random (dark colors)']
					, options: ['equallyDistributed', 'random1', 'random2']
				}
			}
		}
		, _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
		}
		, _a[KEY.showTimestamps] = {
			name: 'Show timestamps'
			, description: "Enables showing timestamps in chat"
			, defaultValue: true
		}
		, _a[KEY.showIcons] = {
			name: 'Show user-icons'
			, description: "Enables showing icons (formerly sigils) for each user in chat"
			, defaultValue: true
		}
		, _a[KEY.showTags] = {
			name: 'Show user-tags'
			, description: "Enables showing tags (Dev, Mod, Contributor) and colors for messages in chat"
			, defaultValue: true
		}
		, _a[KEY.enableSpamDetection] = {
			name: 'Enable spam detection'
			, description: "Enables simple spam detection"
			, defaultValue: true
		}
		, _a[KEY.showNotifications] = {
			name: 'Show browser notifications'
			, description: "Shows browser notifications for enabled events (click the little gear for more options)"
			, defaultValue: true
			, sub:
			{
				'showType':
				{
					defaultValue: 0
					, label: ['only when window inactive', 'always']
					, options: ['whenInactive', 'always']
				}
				, 'smelting':
				{
					defaultValue: true
					, label: 'Smelting finishes'
				}
				, 'chopping':
				{
					defaultValue: true
					, label: 'A tree is fully grown'
				}
				, 'harvest':
				{
					defaultValue: true
					, label: 'A plant can be harvested'
				}
				, 'potionEffect':
				{
					defaultValue: true
					, label: 'A potion\'s effect ends'
				}
				, 'boatReturned':
				{
					defaultValue: true
					, label: 'A boat returns'
				}
				, 'heroReady':
				{
					defaultValue: true
					, label: 'The hero is fully recovered and ready to fight'
				}
				, 'itemsSold':
				{
					defaultValue: true
					, label: 'Items are sold on the market'
				}
				, 'pirate':
				{
					defaultValue: true
					, label: 'A pirate has found a treasure map'
				}
				, 'essence':
				{
					defaultValue: true
					, label: 'An essence was found'
				}
				, 'rocket':
				{
					defaultValue: true
					, label: 'The rocket has landed on the moon or earth'
				}
				, 'wind':
				{
					defaultValue: true
					, label: 'The wind for the sail boat has changed'
				}
				, 'perk':
				{
					defaultValue: true
					, label: 'A new perk is unlocked (achievement set completed)'
				}
				, 'pm':
				{
					defaultValue: true
					, label: 'A private messages (pm) arrives'
				}
				, 'mention':
				{
					defaultValue: true
					, label: 'The username is mentioned in chat'
				}
				, 'keyword':
				{
					defaultValue: true
					, label: 'A keyword is mentioned in chat'
				}
				, 'serverMsg':
				{
					defaultValue: true
					, label: 'Server messages (like <em>Server is restarting...</em>)'
				}
			}
		}
		, _a[KEY.showEssencePopup] = {
			name: 'Show essence popup'
			, description: "Shown a popup (like the ones when a diamond is found or the server is restarting) for finding an essence"
			, defaultValue: false
		}
		, _a[KEY.wikiaLinks] = {
			name: 'Show wikia links'
			, description: "Show wikia links for every item on hover (the little icon in the upper left corner)"
			, defaultValue: true
		}
		, _a[KEY.newXpAnimation] = {
			name: 'New XP-gain animation'
			, description: "Show gained xp on top skill bar instead on the position of the mouse"
			, defaultValue: true
		}
		, _a[KEY.amountSymbol] = {
			name: 'Show \u00D7 on items'
			, description: "Show a tiny \u00D7-symbol before amount numbers of items"
			, defaultValue: true
		}
		, _a[KEY.showTabTimer] = {
			name: 'Show tab timer and info'
			, description: "Show timer on tabs for trees, plants and hero"
			, defaultValue: true
		}
		, _a[KEY.showLootTab] = {
			name: 'Show sub tab for loot table'
			, description: "Show a sub tab for combat drop table in combat"
			, defaultValue: true
		}
		, _a[KEY.useEfficiencyStyle] = {
			name: 'Use space efficient style'
			, description: "Use a space efficient style with less blank space"
			, defaultValue: false
		}
		, _a[KEY.makeNumberInputs] = {
			name: 'Turn text inputs into number inputs'
			, description: "Number inputs allow you to change the amount via arrow buttons"
			, defaultValue: true
		}
		, _a[KEY.addKeepInput] = {
			name: 'Add keep input for selling to npc shop'
			, description: "A keep input allows you to set the amount of items you want to keep when selling"
			, defaultValue: true
		}
		, _a[KEY.addMaxBtn] = {
			name: 'Add max button for some crafting inputs'
			, description: "Add max button for crafting (e.g. vials), brewing potions and cooking food"
			, defaultValue: true
		}
		, _a[KEY.highlightUnplantableSeed] = {
			name: 'Show whether a seed can be planted'
			, description: "Fades the item box of a seed when it's not plantable"
			, defaultValue: true
		}
		, _a[KEY.showSdChange] = {
			name: 'Show stardust change'
			, description: "Shows the amount of stardust earned or spent in the last tick"
			, defaultValue: true
		}
		, _a[KEY.usePotionWarning] = {
			name: 'Use drink warning for active potions'
			, description: "Disable drink button for 3 seconds if the potion is already active"
			, defaultValue: true
		}
		, _a[KEY.showCaptions] = {
			name: 'Show item captions'
			, description: "Show item captions for some items instead of the number of owned items"
			, defaultValue: true
		}
		, _a[KEY.syncPriceHistory] = {
			name: 'Sync price history'
			, description: "Synchronize the local price history"
			, defaultValue: false
			, sub:
			{
				'url':
				{
					defaultValue: ''
					, label: 'paste url here'
				}
			}
		}
		, _a[KEY.useNewToolbar] = {
			name: 'Use new toolbar'
			, description: "Use new reordered toolbar"
			, defaultValue: true
			, requiresReload: true
		}
		, _a[KEY.changeMachineDialog] = {
			name: 'Use slider for machine dialog'
			, description: "Change buttons in machine dialog into slider"
			, defaultValue: true
			, requiresReload: true
		}
		, _a);
	var SETTINGS_TABLE_ID = 'dh2-settings';
	var SETTING_ID_PREFIX = 'dh2-setting-';
	var settings2Init = Object.keys(CFG);
	/**
	 * settings
	 */
	function toName(key, subKey)
	{
		var name = typeof key === 'string' ? key : KEY[key];
		if (subKey !== undefined)
		{
			return name + '.' + subKey;
		}
		return name;
	}

	function getStoreKey(key, subKey)
	{
		return 'setting.' + toName(key, subKey);
	}
	var observedSettings = new Map();
	var observedSubSettings = 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 observeSub(key, subKey, fn)
	{
		var n = toName(key, subKey);
		if (!observedSubSettings.has(n))
		{
			observedSubSettings.set(n, new Set());
		}
		observedSubSettings.get(n).add(fn);
	}
	settings.observeSub = observeSub;

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

	function unobserveSub(key, subKey, fn)
	{
		var n = toName(key, subKey);
		if (!observedSubSettings.has(n))
		{
			return false;
		}
		return observedSubSettings.get(n).delete(fn);
	}
	settings.unobserveSub = unobserveSub;
	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 getSub(key, subKey)
	{
		if (!CFG.hasOwnProperty(key))
		{
			return null;
		}
		var name = getStoreKey(key, subKey);
		var def = CFG[key].sub[subKey].defaultValue;
		if (store.has(name))
		{
			var stored = store.get(name);
			if (def instanceof Array)
			{
				for (var i = 0; i < def.length; i++)
				{
					if (stored.indexOf(def[i]) === -1)
					{
						stored.push(def[i]);
					}
				}
				for (var i = 0; i < stored.length; i++)
				{
					if (def.indexOf(stored[i]) === -1)
					{
						stored.splice(i, 1);
						i--;
					}
				}
			}
			return stored;
		}
		else
		{
			return def;
		}
	}
	settings.getSub = getSub;

	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 setSub(key, subKey, newValue)
	{
		if (!CFG.hasOwnProperty(key))
		{
			return;
		}
		var oldValue = getSub(key, subKey);
		var n = toName(key, subKey);
		store.set(getStoreKey(key, subKey), newValue);
		if (oldValue !== newValue && observedSubSettings.has(n))
		{
			observedSubSettings.get(n).forEach(function (fn)
			{
				return fn(key, subKey, oldValue, newValue);
			});
		}
	}
	settings.setSub = setSub;

	function getSubCfg(key)
	{
		if (!CFG.hasOwnProperty(key))
		{
			return;
		}
		return CFG[key].sub;
	}
	settings.getSubCfg = getSubCfg;

	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 > a.version\n{\n\tcolor: orange;\n\tfont-size: 1.2rem;\n\ttext-decoration: none;\n}\n#tab-container-profile h2.section-title > a.version:hover\n{\n\tcolor: white;\n\ttext-decoration: underline;\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#" + SETTINGS_TABLE_ID + " tr.sub td\n{\n\tposition: relative;\n}\n#" + SETTINGS_TABLE_ID + " tr.sub td button:last-child\n{\n\tmargin: -1px;\n\tposition: absolute;\n\tright: 0;\n}\n\n.ui-dialog-content > h2:first-child\n{\n\tmargin-top: 0;\n}\n\n.settings-container\n{\n\tlist-style: none;\n\tmargin: 5px 30px;\n\tpadding: 0;\n}\n.ui-dialog-content .settings-container\n{\n\tmargin: 5px 0;\n}\n.settings-container > li.setting\n{\n\tbackground-color: silver;\n\tborder: 1px solid black;\n\tborder-left: 0;\n\tborder-right: 0;\n\tborder-top-width: 0;\n\tdisplay: flex;\n}\n.settings-container > li.setting:first-child\n{\n\tborder-top-width: 1px;\n}\n.ui-dialog-content .settings-container > li.setting,\n.ui-dialog-content .settings-container > li.setting:hover\n{\n\tbackground-color: transparent;\n\tborder: 0;\n\tmargin: .25rem 0;\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\tflex-grow: 1;\n\tpadding: .25rem .5rem;\n}\n.settings-container > li.setting > label.ui-checkboxradio-label\n{\n\ttext-align: left;\n}\n.settings-container > li.setting > label.ui-checkboxradio-label .ui-checkboxradio-icon-space\n{\n\tmargin-right: .25rem;\n}\n.settings-container > li.setting > input + label:not(.ui-checkboxradio-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:not(.ui-checkboxradio-label)::before\n{\n\tbackground-image: url(images/icons/check.png);\n}\n.ui-dialog-content .settings-container > li.setting > label + button\n{\n\tmargin-left: -.2rem;\n\tz-index: 1;\n}\n.settings-container.sortable > li.setting > span.ui-icon.handle\n{\n\tfloat: left;\n\tmargin: 6px 10px;\n\tz-index: 10;\n}\n.settings-container > li.setting span.ui-selectmenu-button\n{\n\twidth: calc(100% - 2em - 2*3px + 2*.1em);\n}\n.settings-container > li.setting > button.ui-button\n{\n\twidth: 100%;\n}\n.ui-textfield\n{\n\tbackground: none;\n\tcolor: inherit;\n\tcursor: text;\n\tfont: inherit;\n\toutline: none;\n\ttext-align: inherit;\n}\n.ui-textfield.ui-state-active,\n.ui-widget-content .ui-textfield.ui-state-active,\n.ui-widget-header .ui-textfield.ui-state-active,\n.ui-button.ui-textfield:active,\n.ui-button.ui-textfield.ui-state-active:hover\n{\n\tbackground: transparent;\n\tborder: 1px solid #c5c5c5;\n\tcolor: #333333;\n\tfont-weight: normal;\n}\n.settings-container.list > li\n{\n\tborder: 1px solid #c5c5c5;\n\tborder-radius: 3px;\n\tdisplay: flex;\n\tmargin: 5px 0;\n}\n.settings-container.list > li > span.content\n{\n\tflex: 1 0 auto;\n\tline-height: 2rem;\n\tmargin: 0 5px 0 1rem;\n}\n.settings-container.list > li > button.ui-button\n{\n\tmargin: -1px;\n}\n.instruction\n{\n\tcursor: default;\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n.instruction code,\n.instruction a\n{\n\tcursor: initial;\n\t-webkit-user-select: text;\n\t-moz-user-select: text;\n\t-ms-user-select: text;\n\tuser-select: text;\n}\n.instruction code\n{\n\tbackground-color: lightgray;\n\tdisplay: inline-block;\n\tpadding: .25rem;\n}\n\t\t");
	}

	function getSettingId(key, subKey)
	{
		var name = toName(key) + (subKey !== undefined ? '-' + subKey : '');
		return SETTING_ID_PREFIX + split2Words(name, '-').toLowerCase();
	}

	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\" <a class=\"version\" href=\"https://greasyfork.org/scripts/27642-dh2-fixed\" target=\"_blank\">v" + version + "</a><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);
			});
		}

		function addSubClickListener(btn, dialog)
		{
			btn.addEventListener('click', function (event)
			{
				initJQueryDialog(dialog);
				event.stopPropagation();
				event.preventDefault();
			});
		}
		for (var _i = 0, settings2Init_1 = settings2Init; _i < settings2Init_1.length; _i++)
		{
			var k = settings2Init_1[_i];
			// 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;
			}
			var settingId = getSettingId(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";
			if (setting.sub)
			{
				row.classList.add('sub');
				var subBtn = document.createElement('button');
				subBtn.innerHTML = "<img src=\"images/icons/gearOff.gif\" class=\"image-icon-15\">";
				row.cells.item(0).appendChild(subBtn);
				var dialog = createSubSettingDialog(key);
				addSubClickListener(subBtn, dialog);
			}
			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)
		{
			var valueCache_1 = getGameValue('profileRemoveCraftingFilter') != 1;
			settingsProxies.set(KEY.hideCraftingRecipes
			, {
				get: function (key)
				{
					return getGameValue('profileRemoveCraftingFilter') != 1;
				}
				, set: function (key, oldValue, newValue)
				{
					if (valueCache_1 != newValue)
					{
						row.click();
						valueCache_1 = newValue;
					}
				}
			});
			observer.add('profileRemoveCraftingFilter', function ()
			{
				set(KEY.hideCraftingRecipes, getGameValue('profileRemoveCraftingFilter') != 1);
			});
		}
	}
	var subDialog;
	(function (subDialog)
	{
		function defaultHandler(key, dialog)
		{
			var setting = CFG[key];
			var subSettings = setting.sub;
			var settingContainer = createSubSettingsContainer(key, subSettings);
			dialog.appendChild(settingContainer);
		}

		function colorizeChat(dialog)
		{
			defaultHandler(KEY.colorizeChat, dialog);
		}
		subDialog.colorizeChat = colorizeChat;

		function showNotifications(dialog)
		{
			dialog.appendChild(document.createTextNode('Show notifications\u2026'));
			defaultHandler(KEY.showNotifications, dialog);
			dialog.appendChild(document.createTextNode('Events for which notifications are shown:'));
			var ulNotifType = dialog.lastElementChild;
			var ulEvents = ulNotifType.cloneNode(false);
			while (ulNotifType.children.length > 1)
			{
				ulEvents.appendChild(ulNotifType.children.item(1));
			}
			dialog.appendChild(ulEvents);
		}
		subDialog.showNotifications = showNotifications;

		function syncPriceHistory(dialog)
		{
			var setting = CFG[KEY.syncPriceHistory];
			var subSettings = setting.sub;
			var instructionEl = document.createElement('div');
			instructionEl.className = 'instruction';
			instructionEl.innerHTML = "Go to <a href=\"http://myjson.com/\" target=\"_blank\">http://myjson.com/</a>, insert <code>{}</code> and press \"<em>Save</em>\". Then copy the URL of the created store (e.g. <code>http://myjson.com/ltk51</code>) and insert it into the following input:";
			dialog.appendChild(instructionEl);
			var settingContainer = createSubSettingsContainer(KEY.syncPriceHistory, subSettings);
			dialog.appendChild(settingContainer);
		}
		subDialog.syncPriceHistory = syncPriceHistory;
	})(subDialog || (subDialog = {}));

	function createSubSettingDialog(key)
	{
		var settingId = getSettingId(key);
		var setting = CFG[key];
		var dialog = document.createElement('div');
		dialog.id = 'dialog-' + settingId;
		dialog.style.display = 'none';
		dialog.innerHTML = "<h2>" + setting.name + "</h2>";
		var name = toName(key);
		if (subDialog.hasOwnProperty(name))
		{
			subDialog[name](dialog);
		}
		else
		{
			console.warn('missing setting handler for "%s"', name);
			var todoEl = document.createElement('span');
			todoEl.textContent = 'TODO';
			dialog.appendChild(todoEl);
		}
		document.body.appendChild(dialog);
		return dialog;
	}

	function createSubSettingsContainer(parentKey, subSettings)
	{
		var settingsContainer = document.createElement('ul');
		settingsContainer.className = 'settings-container';

		function addCheckbox(listEl, subKey, id, setting)
		{
			var checkbox = document.createElement('input');
			checkbox.type = 'checkbox';
			checkbox.id = id;
			checkbox.name = id;
			checkbox.checked = getSub(parentKey, subKey);
			var label = document.createElement('label');
			label.htmlFor = id;
			label.innerHTML = setting.label;
			checkbox.addEventListener('change', function ()
			{
				return setSub(parentKey, subKey, checkbox.checked);
			});
			listEl.appendChild(checkbox);
			listEl.appendChild(label);
		}

		function addSelectmenu(listEl, subKey, id, setting)
		{
			var select = document.createElement('select');
			select.id = id;
			select.name = id;
			var options = setting.options;
			var selectedIndex = getSub(parentKey, subKey);
			for (var i = 0; i < options.length; i++)
			{
				var option = document.createElement('option');
				option.value = options[i];
				if (setting.label)
				{
					option.innerHTML = setting.label[i];
				}
				else
				{
					option.innerHTML = key2Name(options[i]);
				}
				option.selected = i == selectedIndex;
				select.appendChild(option);
			}
			select.addEventListener('change', function ()
			{
				return setSub(parentKey, subKey, select.selectedIndex);
			});
			listEl.appendChild(select);
		}

		function addInput(listEl, subKey, id, setting)
		{
			var input = document.createElement('input');
			input.type = 'text';
			input.placeholder = setting.label || '';
			input.value = getSub(parentKey, subKey);
			var onChange = function ()
			{
				return setSub(parentKey, subKey, input.value);
			};
			input.addEventListener('click', onChange);
			input.addEventListener('change', onChange);
			input.addEventListener('keyup', onChange);
			listEl.appendChild(input);
		}
		var keyList = Object.keys(subSettings);
		var orderIndex = keyList.findIndex(function (k)
		{
			return subSettings[k].defaultValue instanceof Array;
		});
		var isSortable = orderIndex != -1;
		if (isSortable)
		{
			keyList = getSub(parentKey, keyList[orderIndex]);
		}
		for (var _i = 0, keyList_1 = keyList; _i < keyList_1.length; _i++)
		{
			var subKey = keyList_1[_i];
			var settingId = getSettingId(parentKey, subKey);
			var setting = subSettings[subKey];
			var listEl = document.createElement('li');
			listEl.classList.add('setting');
			if (isSortable)
			{
				listEl.dataset.subKey = subKey;
				var sortableIcon = document.createElement('span');
				sortableIcon.className = 'ui-icon ui-icon-arrowthick-2-n-s handle';
				listEl.appendChild(sortableIcon);
			}
			if (setting.options)
			{
				addSelectmenu(listEl, subKey, settingId, setting);
			}
			else if (typeof setting.defaultValue === 'boolean')
			{
				addCheckbox(listEl, subKey, settingId, setting);
			}
			else if (typeof setting.defaultValue === 'string')
			{
				addInput(listEl, subKey, settingId, setting);
			}
			settingsContainer.appendChild(listEl);
		}
		return settingsContainer;
	}

	function initJQueryDialog(dialog)
	{
		var $dialog = win.$(dialog);
		$dialog.dialog(
		{
			width: DIALOG_WIDTH + 'px'
		});
		$dialog.find('input[type="checkbox"]').checkboxradio()
			.next().children(':first-child').removeClass('ui-state-hover');
		$dialog.find('button:not(.sub)').button();
		$dialog.find('input:text').button()
			.addClass('ui-textfield')
			.off('mouseenter').off('mousedown').off('keydown');
		$dialog.find('select').selectmenu(
		{
			change: function (event, ui)
			{
				var changeEvent = document.createEvent('HTMLEvents');
				changeEvent.initEvent('change', false, true);
				event.target.dispatchEvent(changeEvent);
			}
		});
		$dialog.find('.sortable').sortable(
		{
			handle: '.handle'
			, update: function (event, ui)
			{
				var newOrder = [];
				var children = event.target.children;
				for (var i = 0; i < children.length; i++)
				{
					var child = children[i];
					newOrder.push(child.dataset.subKey);
				}
				var updateEvent = new CustomEvent('sortupdate'
				, {
					detail: newOrder
				});
				event.target.dispatchEvent(updateEvent);
			}
		});
		return $dialog;
	}

	function createSettingsContainer(settingList)
	{
		var settingsContainer = document.createElement('ul');
		settingsContainer.className = 'settings-container';

		function addOpenDialogClickListener(el, dialog)
		{
			el.addEventListener('click', function (event)
			{
				initJQueryDialog(dialog);
				event.stopPropagation();
				event.preventDefault();
			});
		}

		function addChangeListener(key, checkbox)
		{
			checkbox.addEventListener('change', function ()
			{
				set(key, checkbox.checked);
			});
		}
		for (var _i = 0, settingList_1 = settingList; _i < settingList_1.length; _i++)
		{
			var key = settingList_1[_i];
			var settingId = getSettingId(key);
			var setting = CFG[key];
			var index = settings2Init.indexOf(key.toString());
			if (index != -1)
			{
				settings2Init.splice(index, 1);
			}
			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;
			addChangeListener(key, checkbox);
			listEl.appendChild(checkbox);
			listEl.appendChild(label);
			if (setting.sub)
			{
				var moreBtn = document.createElement('button');
				moreBtn.className = 'sub';
				moreBtn.innerHTML = "<img src=\"images/icons/gearOff.gif\" class=\"image-icon-20\" />";
				listEl.appendChild(moreBtn);
				var dialog = createSubSettingDialog(key);
				addOpenDialogClickListener(moreBtn, dialog);
			}
			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 initMuteDialog(settingsContainer)
	{
		// muted people dialog
		var dialog = document.createElement('div');
		dialog.id = 'dialog-chat-muted-people';
		dialog.style.display = 'none';
		dialog.innerHTML = "<h2>Muted people</h2>";
		var input = document.createElement('input');
		input.type = 'text';
		input.placeholder = 'username';
		dialog.appendChild(input);
		var addBtn = document.createElement('button');
		addBtn.textContent = '+';
		dialog.appendChild(addBtn);
		var listEl = document.createElement('ul');
		listEl.className = 'settings-container list';
		var username2Item = {};
		var username2Btn = {};

		function removeListener(event)
		{
			var target = event.target;
			var username = target.dataset.username || '';
			var index = win.mutedPeople.indexOf(username);
			if (index !== -1)
			{
				win.mutedPeople.splice(index, 1);
			}
		}

		function add2List(username)
		{
			var item = document.createElement('li');
			item.innerHTML = "<span class=\"content\">" + username + "</span>";
			var removeBtn = document.createElement('button');
			removeBtn.dataset.username = username;
			removeBtn.textContent = '-';
			win.$(removeBtn).button();
			removeBtn.addEventListener('click', removeListener);
			username2Btn[username] = removeBtn;
			item.appendChild(removeBtn);
			username2Item[username] = item;
			listEl.appendChild(item);
		}
		var _push = win.mutedPeople.push;
		win.mutedPeople.push = function ()
		{
			var items = [];
			for (var _i = 0; _i < arguments.length; _i++)
			{
				items[_i] = arguments[_i];
			}
			items.forEach(function (username)
			{
				return add2List(username);
			});
			return _push.call.apply(_push, [win.mutedPeople].concat(items));
		};
		var _splice = win.mutedPeople.splice;
		win.mutedPeople.splice = function (start, deleteCount)
		{
			var items = [];
			for (var _i = 2; _i < arguments.length; _i++)
			{
				items[_i - 2] = arguments[_i];
			}
			for (var i = 0; i < deleteCount; i++)
			{
				var username = win.mutedPeople[start + i];
				var item = username2Item[username];
				delete username2Item[username];
				listEl.removeChild(item);
				var btn = username2Btn[username];
				delete username2Btn[username];
				btn.removeEventListener('click', removeListener);
			}
			items.forEach(function (username)
			{
				return add2List(username);
			});
			return _splice.call.apply(_splice, [win.mutedPeople, start, deleteCount].concat(items));
		};
		dialog.appendChild(listEl);
		addBtn.addEventListener('click', function ()
		{
			win.mutedPeople.push(input.value);
			input.value = '';
		});
		document.body.appendChild(dialog);
		var listItem = document.createElement('li');
		listItem.classList.add('setting');
		var dialogBtn = document.createElement('button');
		dialogBtn.innerHTML = "List of muted people";
		dialogBtn.addEventListener('click', function ()
		{
			initJQueryDialog(dialog);
		});
		listItem.appendChild(dialogBtn);
		settingsContainer.appendChild(listItem);
	}

	function initKeywordDialog(settingsContainer)
	{
		// keyword dialog
		var dialog = document.createElement('div');
		dialog.id = 'dialog-chat-keyword-list';
		dialog.style.display = 'none';
		dialog.innerHTML = "<h2>Keywords</h2>";
		var input = document.createElement('input');
		input.type = 'text';
		input.placeholder = 'keyword';
		dialog.appendChild(input);
		var addBtn = document.createElement('button');
		addBtn.textContent = '+';
		dialog.appendChild(addBtn);
		var listEl = document.createElement('ul');
		listEl.className = 'settings-container list';

		function add2List(keyword)
		{
			var item = document.createElement('li');
			item.innerHTML = "<span class=\"content\">" + keyword + "</span>";
			var removeBtn = document.createElement('button');
			removeBtn.textContent = '-';
			win.$(removeBtn).button();
			var remove = function ()
			{
				if (chat.removeKeyword(keyword))
				{
					listEl.removeChild(item);
					removeBtn.removeEventListener('click', remove);
				}
			};
			removeBtn.addEventListener('click', remove);
			item.appendChild(removeBtn);
			listEl.appendChild(item);
		}
		// add all keywords
		chat.keywordList.forEach(function (keyword)
		{
			return add2List(keyword);
		});
		dialog.appendChild(listEl);
		addBtn.addEventListener('click', function ()
		{
			var keyword = input.value;
			if (chat.addKeyword(keyword))
			{
				add2List(keyword);
				input.value = '';
			}
		});
		document.body.appendChild(dialog);
		var listItem = document.createElement('li');
		listItem.classList.add('setting');
		var dialogBtn = document.createElement('button');
		dialogBtn.innerHTML = "Manage list of keywords";
		dialogBtn.addEventListener('click', function ()
		{
			initJQueryDialog(dialog);
		});
		listItem.appendChild(dialogBtn);
		settingsContainer.appendChild(listItem);
	}

	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>Chat Settings</h2>";
		var settingList = [KEY.useNewChat, KEY.colorizeChat, KEY.intelligentScrolling, KEY.showTimestamps, KEY.showIcons, KEY.showTags, KEY.enableSpamDetection];
		var settingsContainer = createSettingsContainer(settingList);
		initMuteDialog(settingsContainer);
		initKeywordDialog(settingsContainer);
		dialog.appendChild(settingsContainer);
		document.body.appendChild(dialog);
		btn.addEventListener('click', function ()
		{
			initJQueryDialog(dialog);
		});
	}

	function init()
	{
		initProxies();
		initSettingsStyle();
		initCraftingSettings();
		initChatSettings();
		initSettingTable();
	}
	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);
	}
	var HSVColor = (function ()
	{
		function HSVColor(H, S, V)
		{
			this.H = H;
			this.S = S;
			this.V = V;
		}
		HSVColor.fromHSVArray = function (hsv)
		{
			return new HSVColor(hsv[0], hsv[1], hsv[2]);
		};
		HSVColor.prototype.toHex = function ()
		{
			var rgb = this.toRGB();
			return '#' + this.componentToHex(rgb[0]) + this.componentToHex(rgb[1]) + this.componentToHex(rgb[2]);
		};
		HSVColor.prototype.toHSL = function ()
		{
			var h = this.H;
			var s = this.S / 100;
			var v = this.V / 100;
			var k = (2 - s) * v;
			return [
				h
				, Math.round(s * v / (k < 1 ? k : 2 - k) * 10e3) / 100
				, k / 2 * 100
			];
		};
		HSVColor.prototype.toHSLString = function (alpha)
		{
			var hsl = this.toHSL();
			if (alpha !== undefined)
			{
				return "hsla(" + hsl[0] + ", " + hsl[1] + "%, " + hsl[2] + "%, " + alpha + ")";
			}
			else
			{
				return "hsl(" + hsl[0] + ", " + hsl[1] + "%, " + hsl[2] + "%)";
			}
		};
		HSVColor.prototype.toRGB = function ()
		{
			// this doesn't work for the values of 0 and 360 here's the hacky fix
			var h = Math.min(Math.max(this.H, 1), 359);
			// Rebase the h,s,v values
			h = h / 360;
			var s = this.S / 100;
			var v = this.V / 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)];
		};
		HSVColor.prototype.toRGBString = function (alpha)
		{
			var rgb = this.toRGB();
			if (alpha !== undefined)
			{
				return "rgba(" + rgb.join(', ') + ", " + alpha + ")";
			}
			else
			{
				return "rgb(" + rgb.join(', ') + ")";
			}
		};
		HSVColor.prototype.componentToHex = function (c)
		{
			var hex = c.toString(16);
			return hex.length == 1 ? '0' + hex : hex;
		};
		return HSVColor;
	}());
	colorGenerator.HSVColor = HSVColor;

	function setFormat(hsv, options)
	{
		var color = HSVColor.fromHSVArray(hsv);
		switch (options.format)
		{
		case 'object':
			return color;
		case 'hsvArray':
			return hsv;
		case 'hslArray':
			return color.toHSL();
		case 'hsl':
			return color.toHSLString();
		case 'hsla':
			return color.toHSLString(options.alpha || Math.random());
		case 'rgbArray':
			return color.toRGB();
		case 'rgb':
			return color.toRGBString();
		case 'rgba':
			return color.toRGBString(options.alpha || Math.random());
		case 'hex':
		default:
			return color.toHex();
		}
	}

	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 colors = [];
			while (options.count > 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));
			}
			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;
	}());
	colorGenerator.ColorInterval = ColorInterval;
	var defaultRootInterval = new ColorInterval(0, 360);

	function getEquallyDistributed(rootInterval)
	{
		if (rootInterval === void 0)
		{
			rootInterval = defaultRootInterval;
		}
		return 'hsl(' + rootInterval.getNextValue() + ', 100%, 80%)';
	}
	colorGenerator.getEquallyDistributed = getEquallyDistributed;
	var Color = (function ()
	{
		function Color(r, g, b)
		{
			this.r = r;
			this.g = g;
			this.b = b;
		}
		Color.fromHex = function (hex)
		{
			return new Color(parseInt(hex.substr(1, 2), 16), parseInt(hex.substr(3, 2), 16), parseInt(hex.substr(5, 2), 16));
		};
		Color.fromRgb = function (rgb)
		{
			var match = rgb.match(this.rgbRegex);
			return new Color(parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10));
		};
		Color.fromString = function (str)
		{
			if (this.hexRegex.test(str))
			{
				return this.fromHex(str);
			}
			else if (this.rgbRegex.test(str))
			{
				return this.fromRgb(str);
			}
			else
			{
				throw new Error('Unexpected color format: ' + str);
			}
		};
		Color.prototype.toString = function (hex)
		{
			if (hex === void 0)
			{
				hex = true;
			}
			return '#' + this.toHex(this.r) + this.toHex(this.g) + this.toHex(this.b);
		};
		Color.prototype.toHex = function (x)
		{
			var xStr = x.toString(16);
			return (xStr.length == 1 ? '0' : '') + xStr;
		};
		Color.hexRegex = /^#(?:[0-9a-f]{3}){1,2}$/i;
		Color.rgbRegex = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i;
		return Color;
	}());

	function ratioColor(color1, color2, ratio)
	{
		var color = new Color(Math.ceil(color1.r * (1 - ratio) + color2.r * ratio), Math.ceil(color1.g * (1 - ratio) + color2.g * ratio), Math.ceil(color1.b * (1 - ratio) + color2.b * ratio));
		return color.toString();
	}

	function getColorTransition(value, colorStrings)
	{
		var smallerValue = -1;
		var biggerValue = Number.MAX_SAFE_INTEGER;
		var colors = {};
		for (var v in colorStrings)
		{
			var vNum = Number(v);
			if (vNum === value)
			{
				return colorStrings[v];
			}
			else if (vNum < value)
			{
				smallerValue = Math.max(smallerValue, vNum);
			}
			else
			{
				biggerValue = Math.min(biggerValue, vNum);
			}
			colors[v] = Color.fromString(colorStrings[v]);
		}
		if (smallerValue === -1)
		{
			return colorStrings[biggerValue];
		}
		if (biggerValue === Number.MAX_SAFE_INTEGER)
		{
			return colorStrings[smallerValue];
		}
		var ratio = (value - smallerValue) / (biggerValue - smallerValue);
		return ratioColor(colors[smallerValue], colors[biggerValue], ratio);
	}
	colorGenerator.getColorTransition = getColorTransition;
	// populate the color dictionary
	loadColorBounds();
})(colorGenerator || (colorGenerator = {}));

/**
 * provides icons
 */
var icons;
(function (icons)
{
	icons.CHART_LINE = 'M16,11.78L20.24,4.45L21.97,5.45L16.74,14.5L10.23,10.75L5.46,19H22V21H2V3H4V17.54L9.5,8L16,11.78Z';
	icons.WIKIA = '<defs><linearGradient id="a" x1="0%" x2="63.85%" y1="100%" y2="32.54%"><stop stop-color="#94D11F" offset="0%"/><stop stop-color="#09D3BF" offset="100%"/></linearGradient></defs><path fill="url(#a)" fill-rule="evenodd" d="M10.18 16.8c0 .2-.05.46-.26.67l-.8.7-7.38-6.95v-2.7l8.1 7.62c.12.12.33.36.33.66zm11.2-8.1v2.53l-9.15 8.86a.67.67 0 0 1-.5.2.73.73 0 0 1-.5-.2l-.85-.77 11-10.62zm-6.97 4.5l-2.53 2.43-8.04-7.67a2 2 0 0 1 0-2.9l2.53-2.43 8.04 7.67c.84.8.84 2.1 0 2.9zm-1.5-6.68L15.56 4c.4-.4.94-.6 1.52-.6.57 0 1.1.2 1.52.6l2.72 2.6-4.16 3.98-1.52-1.45-2.73-2.6zm10.18-.4l-6-5.8L17 .2l-.14.12-5.22 5.03L6.96.87l-.6-.48-.12-.1-.1.1-6.1 5.7-.04.06v5.76l.05.05 11.4 10.87.12.1.12-.1 11.37-10.87.05-.05V6.17l-.05-.05z"/>';

	function getSvgAsUrl(svg)
	{
		return "url('data:image/svg+xml;base64," + btoa(svg) + "')";
	}
	icons.getSvgAsUrl = getSvgAsUrl;

	function wrapCodeWithSvg(code, viewBox, width, height)
	{
		return "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"" + width + "\" height=\"" + height + "\" viewBox=\"" + viewBox + "\">" + code + "</svg>";
	}
	icons.wrapCodeWithSvg = wrapCodeWithSvg;

	function getMd(pathDots, color, width, height)
	{
		if (color === void 0)
		{
			color = 'black';
		}
		if (width === void 0)
		{
			width = '30';
		}
		if (height === void 0)
		{
			height = '30';
		}
		return getSvgAsUrl(wrapCodeWithSvg("<path fill=\"" + color + "\" d=\"" + pathDots + "\" />", '0 0 24 24', width, height));
	}
	icons.getMd = getMd;
})(icons || (icons = {}));

/**
 * notifications
 */
var notifications;
(function (notifications)
{
	notifications.name = 'notifications';

	function event(title, options)
	{
		if ((!options || options.whenActive !== true)
			&& !document.hidden && document.hasFocus()
			&& settings.getSub(settings.KEY.showNotifications, 'showType') !== 1)
		{
			return;
		}
		if (!settings.get(settings.KEY.showNotifications))
		{
			// notifications disabled: return stub notification
			return Promise.resolve(
			{
				close: function () {}
			});
		}
		if (!("Notification" in win))
		{
			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)
						{
							win.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 = {}));

/**
 * process commands
 */
var commands;
(function (commands)
{
	var XP_GAIN_KEY = 'xpGain';
	var MAX_XP_GAIN_HISTORY_LENGTH = 100;
	var IMAGE2SKILL = {
		// mining = #cc0000
		'icons/pickaxe': 'mining'
			// crafting = #cc0000
		, 'icons/anvil': 'crafting'
			// woodcutting = cyan
		, 'icons/woodcutting': 'woodcutting'
			// farming = green
		, 'icons/watering-can': 'farming'
			// brewing = #800080
		, 'vialOfWater': 'brewing'
		, 'largeVialOfWater': 'brewing'
		, 'hugeVialOfWater': 'brewing'
			// combat = lime
		, 'icons/combat': 'combat'
			// magic = blue
		, 'icons/wizardhat': 'magic'
			// fishing = blue
		, 'tuna': 'fishing'
			// cooking = yellow
		, 'icons/cooking': 'cooking'
	};
	var xpGainHistory = store.has(XP_GAIN_KEY) ? store.get(XP_GAIN_KEY) :
	{};
	addStyle("\n.scroller.xp\n{\n\tfont-size: 18pt;\n\tposition: absolute;\n\ttext-align: center;\n}\n\t");

	function minutes2String(data)
	{
		return data.replace(/Your account has been running for: (\d+) minutes./, function (wholeMatch, minutes)
		{
			return 'Your account has been running for ' + format.min2Str(minutes) + '.';
		});
	}
	var LOOT_MSG_PREFIX = 'SHOW_LOOT_DIAG=';

	function processLoot(data)
	{
		if (!/^SM=Your boat found nothing\.$|^SHOW_LOOT_DIAG=/.test(data))
		{
			return false;
		}
		var loot = {
			type: 'loot'
			, title: ''
			, itemList: []
		};
		if (data.startsWith('SM='))
		{
			loot.title = 'Boat';
			loot.emptyText = 'Your boat found nothing.';
		}
		else if (data.startsWith(LOOT_MSG_PREFIX))
		{
			var split = data.substr(LOOT_MSG_PREFIX.length).split('~');
			loot.title = split[0];
			for (var i = 1; i < split.length; i += 2)
			{
				loot.itemList.push(
				{
					icon: split[i]
					, text: split[i + 1]
				});
			}
		}
		log.add(loot);
		return true;
	}
	var XP_GAIN_REGEX = /^ST=([^~]+)\.png~([^~]+)~\+(\d+)\s*xp(.*)$/;
	var animationQueue = {};

	function queueXpAnimation(skill, cell, color, xpAmount, extraXp)
	{
		if (!settings.get(settings.KEY.newXpAnimation))
		{
			return;
		}
		animationQueue[skill] = animationQueue[skill] || [];
		animationQueue[skill].push(
		{
			cell: cell
			, color: color
			, xpAmount: xpAmount
			, extraXp: extraXp
		});
		if (animationQueue[skill].length === 1)
		{
			nextAnimation(skill);
		}
	}

	function nextAnimation(skill)
	{
		var entry = animationQueue[skill][0];
		if (!entry || !settings.get(settings.KEY.newXpAnimation))
		{
			return;
		}
		var cell = entry.cell
			, color = entry.color
			, xpAmount = entry.xpAmount
			, extraXp = entry.extraXp;
		var rect = cell.getBoundingClientRect();
		var extraXpStr = extraXp > 0 ? " (+" + extraXp + ")" : '';
		var $el = win.$("<div class=\"scroller xp\" style=\"color: " + color + "; left: " + (rect.left + 50) + "px; top: " + (document.body.scrollTop + rect.top) + "px; width: " + (rect.width - 2 * 20 - 50) + "px;\">+" + format.number(xpAmount) + extraXpStr + "</div>")
			.appendTo('body');
		// ensure the existence of $el, so the complete-function can be called instantly if the window is hidden
		$el
			.animate(
			{
				top: '-=15px'
			}
			, {
				duration: 1500
				, easing: 'easeOutQuad'
				, complete: function ()
				{
					animationQueue[skill].shift();
					nextAnimation(skill);
				}
			})
			.fadeOut(
			{
				duration: 2500
				, queue: false
				, complete: function ()
				{
					return $el.remove();
				}
			});
	}

	function processXpGain(data)
	{
		var match = data.match(XP_GAIN_REGEX);
		if (!match)
		{
			return false;
		}
		var icon = match[1];
		var skill = IMAGE2SKILL[icon] || '';
		var color = match[2];
		var xpAmount = Number(match[3]);
		var extra = match[4];
		var cell = document.getElementById('top-bar-level-td-' + skill);
		if (!cell)
		{
			console.debug('match (no cell found):', match);
			return false;
		}
		var entry = {
			time: now()
			, amount: xpAmount
		};
		if (match[4])
		{
			entry.extra = match[4];
		}
		if (skill == 'fishing')
		{
			log.processFishingXpChange(xpAmount);
		}
		var extraXp = 0;
		if (extra && settings.get(settings.KEY.newXpAnimation))
		{
			var extraMatch = extra.match(/^\s*\(<img[^>]+src=(['"])images\/([^']+)\.png\1[^>]+>\s*(.+)\)$/);
			var extraXpMatch = extra.match(/^\s*\(\+(\d+)\s*xp\)\s*$/);
			if (extraMatch)
			{
				var icon_1 = extraMatch[2];
				var text = extraMatch[3];
				if (icon_1 == 'brewingKit')
				{
					text = '+' + text;
				}
				win.scrollText(icon_1, color, text);
			}
			else if (extraXpMatch)
			{
				extraXp = Number(extraXpMatch[1]);
			}
			else
			{
				win.scrollText('none', color, extra);
			}
		}
		// save the xp event
		var list = xpGainHistory[skill] || [];
		list.push(entry);
		xpGainHistory[skill] = list.slice(-MAX_XP_GAIN_HISTORY_LENGTH);
		store.set(XP_GAIN_KEY, xpGainHistory);
		if (settings.get(settings.KEY.newXpAnimation))
		{
			queueXpAnimation(skill, cell, color, xpAmount, extraXp);
		}
		return true;
	}

	function processLevelUp(data)
	{
		if (!data.startsWith('LVL_UP='))
		{
			return false;
		}
		var skill = data.substr('LVL_UP='.length);
		var xp = getGameValue(skill + 'Xp');
		var oldLvl = win.getLevel(xp);
		log.add(
		{
			type: 'lvlup'
			, skill: skill
			, newLevel: oldLvl + 1
		});
		return true;
	}

	function processCombat(data)
	{
		var match = data.match(/^STHS=([^~]+)~([^~]+)~([^~]+)~img-(.+)~(melee|heal)$/);
		if (!match)
		{
			return false;
		}
		// keep track of different battles and add the data to the current battle
		var number = match[3];
		if (!/\D/.test(number))
		{
			number = Number(number);
		}
		log.add(
		{
			type: 'combat'
			, what: match[5]
			, who: match[4]
			, text: number
		});
		return true;
	}

	function processEnergy(data)
	{
		var match = data.match(/^ST=steak\.png~orange~\+([\d',]+)$/);
		if (!match)
		{
			return false;
		}
		log.add(
		{
			type: 'energy'
			, energy: Number(match[1].replace(/\D/g, ''))
		});
		return true;
	}

	function processHeat(data)
	{
		var match = data.match(/^ST=icons\/fire\.png~red~\+([\d',]+)$/);
		if (!match)
		{
			return false;
		}
		log.add(
		{
			type: 'heat'
			, heat: Number(match[1].replace(/\D/g, ''))
		});
		return true;
	}

	function processMarket(data)
	{
		if (data === 'ST=icons/shop.png~orange~Item Purchased')
		{
			log.add(
			{
				type: 'market'
			});
			return true;
		}
		var match = data.match(/^ST=coins\.png~yellow~\+([\d',]+)$/);
		if (!match)
		{
			return false;
		}
		var coins = Number(match[1].replace(/\D/g, ''));
		log.add(
		{
			type: 'market'
			, coins: coins
		});
		return true;
	}

	function processBonemeal(data)
	{
		var match = data.match(/^ST=filledBonemealBin\.png~white~\+([\d',]+)$/);
		if (!match)
		{
			return false;
		}
		var bonemeal = Number(match[1].replace(/\D/g, ''));
		log.add(
		{
			type: 'bonemeal'
			, bonemeal: bonemeal
		});
		return true;
	}

	function processCrafting(data)
	{
		if (data === 'ST=none~#806600~Item Crafted')
		{
			log.add(
			{
				type: 'crafting'
			});
			return true;
		}
		return false;
	}

	function processStardust(data)
	{
		var match = data.match(/^ST=(?:icons\/)?stardust\.png~yellow~\+([\d',]+)$/);
		if (!match)
		{
			return false;
		}
		var stardust = Number(match[1].replace(/\D/g, ''));
		log.add(
		{
			type: 'stardust'
			, stardust: stardust
		});
		return true;
	}
	var RUNNING_ACCOUNT_STR = 'Your account has been running for:';

	function formatData(data)
	{
		if (data.startsWith('STHS=')
			|| data.startsWith('STE=')
			|| data.startsWith('SM=')
			|| data.startsWith('ST=')
			|| data.startsWith('SHOW_LOOT_DIAG='))
		{
			if (data.indexOf(RUNNING_ACCOUNT_STR) != -1)
			{
				data = minutes2String(data);
			}
			data = format.numbersInText(data);
		}
		return data;
	}
	commands.formatData = formatData;

	function process(data)
	{
		// prepare for logging events in an activity log
		if (processLoot(data))
		{
			return;
		}
		else if (processXpGain(data))
		{
			// return undefined to let the original function be called
			return settings.get(settings.KEY.newXpAnimation) ? null : void 0;
		}
		else if (processLevelUp(data)
			|| processCombat(data)
			|| processEnergy(data)
			|| processHeat(data)
			|| processMarket(data)
			|| processBonemeal(data)
			|| processCrafting(data)
			|| processStardust(data))
		{
			return;
		}
		else if (data.startsWith('SM='))
		{
			log.add(
			{
				data: minutes2String(data.replace(/^[^=]+=/, ''))
			});
		}
		else if (data.startsWith('STHS=') || data.startsWith('STE=') || data.startsWith('ST='))
		{}
		// notifications for this kind of message: "SM=An update has been scheduled for today."
		if (data.startsWith('SM='))
		{
			if (settings.getSub(settings.KEY.showNotifications, 'serverMsg'))
			{
				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(/<.+?>/g, '')
					.replace(/(\s)\1+/g, '$1')
					.replace(/, $/, '');
				notifications.event('Message from server'
				, {
					body: minutes2String(msg)
				});
			}
		}
		return;
	}
	commands.process = process;
})(commands || (commands = {}));

/**
 * log activities and stuff
 */
var log;
(function (log)
{
	log.name = 'log';
	var LOG_KEY = 'activityLog';
	var MAX_LOG_SIZE = 100;
	var logList = store.has(LOG_KEY) ? store.get(LOG_KEY) : [];
	var currentCombat = null;
	var currentCombatEl = null;
	var LOG_FILTER = {
		'combat':
		{
			title: 'Combat'
			, img: 'images/icons/combat.png'
		}
		, 'loot':
		{
			title: 'Loot'
			, img: 'http://www.clker.com/cliparts/U/a/v/n/h/w/bag-hi.png'
		}
		, 'fish':
		{
			title: 'Caught fish'
			, img: 'images/tuna.png'
		}
		, 'skill':
		{
			title: 'Skill advance'
			, img: 'images/icons/skills.png'
		}
		, 'other':
		{
			title: 'All other'
			, label: 'Other'
		}
	};
	var logEl;

	function isFightStarted()
	{
		return win.fightMonsterId !== 0;
	}

	function saveLog()
	{
		store.set(LOG_KEY, logList);
	}

	function createLi(entry)
	{
		var entryEl = document.createElement('li');
		entryEl.dataset.time = (new Date(entry.time || 0)).toLocaleString();
		entryEl.dataset.type = entry.type;
		return entryEl;
	}

	function appendLi(entryEl)
	{
		var filterEl = logEl.firstElementChild;
		var next = filterEl && filterEl.nextElementSibling;
		if (next)
		{
			logEl.insertBefore(entryEl, next);
		}
		else
		{
			logEl.appendChild(entryEl);
		}
		logEl.classList.remove('empty');
	}

	function setGenericEntry(entry, init)
	{
		var el = createLi(entry);;
		el.innerHTML = typeof entry.data === 'string' ? format.numbersInText(entry.data) : JSON.stringify(entry.data);
		appendLi(el);
	}

	function setLootEntry(entry, init)
	{
		var el = createLi(entry);
		var header = document.createElement('h1');
		header.className = 'container-title';
		header.textContent = entry.title;
		el.appendChild(header);
		var itemContainer = document.createElement('span');
		if (entry.itemList.length === 0)
		{
			itemContainer.innerHTML = "<span class=\"dialogue-loot\">" + entry.emptyText + "</span>";
		}
		else
		{
			var update = false;
			for (var _i = 0, _a = entry.itemList; _i < _a.length; _i++)
			{
				var item = _a[_i];
				if (item.hasOwnProperty('key'))
				{
					item.icon = item.key;
					delete item.key;
					update = true;
				}
				if (item.hasOwnProperty('amount'))
				{
					item.text = (item.amount || Number.NaN).toString();
					delete item.amount;
					update = true;
				}
				var itemEl = document.createElement('span');
				itemEl.className = 'dialogue-loot';
				itemEl.innerHTML = "<img src=\"" + item.icon + "\" class=\"image-icon-50\"> " + format.numbersInText(item.text);
				itemContainer.appendChild(itemEl);
				itemContainer.appendChild(document.createTextNode(' '));
			}
			if (update)
			{
				saveLog();
			}
		}
		el.appendChild(itemContainer);
		var valueContainer = document.createElement('div');
		valueContainer.className = 'total-value';
		valueContainer.appendChild(document.createTextNode('Total value: '));
		var totalValue = document.createElement('span');
		totalValue.style.cursor = 'pointer';
		totalValue.textContent = 'Click to calculate';
		valueContainer.appendChild(totalValue);
		totalValue.addEventListener('click', function ()
		{
			var items = {};
			for (var _i = 0, _a = entry.itemList; _i < _a.length; _i++)
			{
				var item = _a[_i];
				if (item.text.indexOf('xp') === -1)
				{
					var key = item.icon.replace(/^.+\/([^\/]+)\.png$/, '$1');
					var num = Number(item.text.replace(/\D/g, ''));
					items[key] = (items[key] || 0) + num;
				}
			}
			market.calcMarketValue(items)
				.then(function (sum)
				{
					totalValue.innerHTML = "<img class=\"image-icon-20\" src=\"images/coins.png\"> " + format.number(sum[0]) + " - <img class=\"image-icon-20\" src=\"images/coins.png\"> " + format.number(sum[1]);
				});
		});
		el.appendChild(valueContainer);
		appendLi(el);
	}

	function setFishEntry(entry, init)
	{
		var el = createLi(entry);
		el.innerHTML = "You caught a " + key2Name(entry.fish, true) + ".";
		appendLi(el);
	}

	function setEnergyEntry(entry, init)
	{
		var el = createLi(entry);
		el.innerHTML = "Your hero gained " + format.number(entry.energy) + " energy.";
		appendLi(el);
	}

	function setHeatEntry(entry, init)
	{
		var el = createLi(entry);
		el.innerHTML = "You added " + format.number(entry.heat) + " heat to your oven.";
		appendLi(el);
	}

	function setLevelUpEntry(entry, init)
	{
		var el = createLi(entry);
		el.innerHTML = "You advanced your " + entry.skill + " skill to level " + entry.newLevel + ".";
		appendLi(el);
	}

	function getCombatInfo(data, initHp, scaleX, width)
	{
		var points = [];
		var startHp = -1;
		var hp = initHp;
		for (var tick in data)
		{
			hp = data[tick];
			if (startHp === -1)
			{
				startHp = hp;
			}
			points.push((scaleX * Number(tick)) + ' ' + hp);
		}
		if (points.length === 0)
		{
			points.push('0 ' + initHp);
		}
		points.push(width + ' ' + hp, width + ' 0', '0 0');
		return {
			points: points
			, startHp: startHp === -1 ? initHp : startHp
			, endHp: hp
		};
	}

	function getHTMLFromCombatInfo(info, name)
	{
		return "<div class=\"combat-log-graph\">\n\t\t\t<span>" + name + " (" + info.startHp + " Hp to " + info.endHp + " Hp):</span><br>\n\t\t\t<svg style=\"height: " + info.startHp + "px;\"><polygon points=\"" + info.points.join(',') + "\"></polygon></svg>\n\t\t</div>";
	}

	function setCombatEntry(entry, init)
	{
		var created = init || currentCombatEl == null;
		if (init || currentCombatEl == null)
		{
			currentCombatEl = createLi(entry);
		}
		var HTML = '';
		// support old log format
		if (!entry.hasOwnProperty('ticks'))
		{
			var info = {
				hero:
				{
					heal: 0
					, melee: 0
				}
				, monster:
				{
					heal: 0
					, melee: 0
				}
			};
			for (var i = 0; i < entry.parts.length; i++)
			{
				var part = entry.parts[i];
				info[part.who][part.type] += part.number;
			}
			HTML = "<div>Hero: <span style=\"color: green;\">+" + info.hero.heal + "</span> <span style=\"color: red;\">-" + info.hero.melee + "</span></div>\n\t\t\t<div>Monster: <span style=\"color: green;\">+" + info.monster.heal + "</span> <span style=\"color: red;\">-" + info.monster.melee + "</span></div>";
		}
		else
		{
			var currentTick = Math.max(entry.ticks, 0);
			var width = logEl.scrollWidth - 4 * 12.8 - 2;
			var scaleX = currentTick === 0 ? 0 : width / currentTick;
			var hero = getCombatInfo(entry.hero, win.heroHp, scaleX, width);
			var monster = getCombatInfo(entry.monster, win.fightMonsterHp, scaleX, width);
			// TODO: who won?
			HTML = "The fight took " + format.sec2Str(currentTick) + ".\n\t\t\t" + getHTMLFromCombatInfo(hero, 'Hero') + "\n\t\t\t" + getHTMLFromCombatInfo(monster, 'Monster') + "\n\t\t\t";
		}
		// map monster name and area name from monster id (the ids are starting at 1)
		var isShiny = entry.monsterId > 1e3;
		var mId = entry.monsterId - (isShiny ? 1001 : 1);
		var monsterName = (isShiny ? 'Shiny ' : '') + (getMonsterName(mId) || '(' + (mId % 3 + 1) + ')');
		var areaId = Math.floor(mId / 3);
		var areaName = getAreaName(areaId) || '(' + (areaId + 1) + ')';
		currentCombatEl.innerHTML = "<h2>Combat against " + monsterName + " in " + areaName + "</h2>\n\t\t" + HTML;
		if (created)
		{
			appendLi(currentCombatEl);
		}
		if (!isFightStarted())
		{
			currentCombatEl = null;
		}
	}

	function setMarketEntry(entry, init)
	{
		var el = createLi(entry);
		if (entry.coins)
		{
			el.innerHTML = "You collected " + format.number(entry.coins) + " from market.";
		}
		else
		{
			el.innerHTML = "You purchased an item on market.";
		}
		appendLi(el);
	}

	function setBonemealEntry(entry, init)
	{
		var el = createLi(entry);
		el.innerHTML = "You added " + format.number(entry.bonemeal) + " bonemeal to your bonemeal bin.";
		appendLi(el);
	}

	function setCraftingEntry(entry, init)
	{
		var el = createLi(entry);
		el.innerHTML = "You crafted an item.";
		appendLi(el);
	}

	function setStardustEntry(entry, init)
	{
		var el = createLi(entry);
		el.innerHTML = "You got " + format.number(entry.stardust) + " stardust.";
		appendLi(el);
	}
	var entryType2Fn = {
		'loot': setLootEntry
		, 'fish': setFishEntry
		, 'energy': setEnergyEntry
		, 'heat': setHeatEntry
		, 'lvlup': setLevelUpEntry
		, 'combat': setCombatEntry
		, 'market': setMarketEntry
		, 'bonemeal': setBonemealEntry
		, 'crafting': setCraftingEntry
		, 'stardust': setStardustEntry
	};

	function updateLog(entry, init)
	{
		if (init === void 0)
		{
			init = false;
		}
		if (!logEl)
		{
			return;
		}
		if (entry.type && entryType2Fn.hasOwnProperty(entry.type))
		{
			entryType2Fn[entry.type](entry, init);
		}
		else
		{
			setGenericEntry(entry, init);
		}
	}

	function add2Log(entry)
	{
		logList.push(entry);
		logList = logList.slice(-MAX_LOG_SIZE);
		saveLog();
	}
	// use the last stored combat, compare monster id and health state to check whether this combat might be interrupted last time (hero health != 0 && monster health != 0) and continue logging to that fight
	function findCurrentCombat()
	{
		for (var i = logList.length - 1; i >= 0; i--)
		{
			if (logList[i].type == 'combat')
			{
				var entry = logList[i];
				if (entry.monsterId == win.fightMonsterId
					&& entry.hero[entry.ticks] !== 0
					&& entry.hero[entry.ticks] !== 0)
				{
					return entry;
				}
				break;
			}
		}
		return null;
	}

	function add(entry)
	{
		if (!entry.time)
		{
			entry.time = now();
		}
		if (entry.type == 'combat')
		{
			currentCombat = currentCombat || findCurrentCombat();
			if (!currentCombat)
			{
				return;
			}
			// skip entries without further information
			if (typeof entry.text !== 'number' || entry.text === 0)
			{
				return;
			}
			var hp = entry.who == 'hero' ? win.heroHp : win.fightMonsterHp;
			// the hp values are updated after this event, so I have to calculate the new value by myself
			hp += (entry.what == 'heal' ? 1 : -1) * entry.text;
			currentCombat[entry.who][currentCombat.ticks] = hp;
			saveLog();
			updateLog(currentCombat);
		}
		else
		{
			add2Log(entry);
			updateLog(entry);
		}
	}
	log.add = add;

	function addLogEl()
	{
		addStyle("\n#show-activity-log\n{\n\tdisplay: none;\n}\nbody\n{\n\toverflow-y: scroll;\n}\n#activity-log-label\n{\n\tcolor: pink;\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#activity-log-overlay\n{\n\tbackground-color: transparent;\n\tcolor: transparent;\n\tpointer-events: none;\n\tposition: fixed;\n\tbottom: 0;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\ttransition: background-color .3s ease-out;\n\tz-index: 1000;\n}\n#show-activity-log:checked ~ #activity-log-overlay\n{\n\tbackground-color: rgba(0, 0, 0, 0.4);\n\tpointer-events: all;\n}\n#activity-log\n{\n\tbackground-color: white;\n\tcolor: black;\n\tlist-style: none;\n\tmargin: 0;\n\toverflow-y: scroll;\n\tpadding: .4rem .8rem;\n\tposition: fixed;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\ttransform: translateX(100%);\n\ttransition: transform .3s ease-out;\n\tmin-width: 15rem;\n\twidth: 40%;\n\tmax-width: 30rem;\n\tz-index: 1000;\n}\n#show-activity-log:checked ~ #activity-log\n{\n\ttransform: translateX(0%);\n}\n#activity-log::before\n{\n\tcontent: 'Activity Log';\n\tdisplay: block;\n\tfont-size: 1rem;\n\tfont-weight: bold;\n\tmargin-bottom: 0.8rem;\n}\n#activity-log.empty::after\n{\n\tcontent: 'Activities will be listed here.';\n}\n#activity-log li:not(.filter)\n{\n\tborder: 1px solid gray;\n\tborder-radius: .2rem;\n\tdisplay: none;\n\tmargin: .2rem 0;\n\tpadding: .4rem .8rem;\n}\n#activity-log li:not(.filter)::before\n{\n\tcolor: gray;\n\tcontent: attr(data-time);\n\tdisplay: block;\n\tfont-size: 0.8rem;\n\tmargin: -4px 0 4px -4px;\n}\n.combat-log-graph > svg\n{\n\ttransform: scaleY(-1);\n\twidth: 100%;\n}\n.combat-log-graph > svg polygon\n{\n\tfill: green;\n\tstroke: black;\n\tstroke-width: 1px;\n}\n#activity-log.combat > li[data-type=\"combat\"]\n{\n\tdisplay: block;\n}\n#activity-log.loot > li[data-type=\"loot\"]\n{\n\tdisplay: block;\n}\n#activity-log.fish > li[data-type=\"fish\"]\n{\n\tdisplay: block;\n}\n#activity-log.lvlup > li[data-type=\"lvlup\"]\n{\n\tdisplay: block;\n}\n#activity-log.other > li[data-type=\"energy\"],\n#activity-log.other > li[data-type=\"heat\"],\n#activity-log.other > li[data-type=\"market\"],\n#activity-log.other > li[data-type=\"bonemeal\"],\n#activity-log.other > li[data-type=\"crafting\"],\n#activity-log.other > li[data-type=\"stardust\"],\n#activity-log.other > li[data-type=\"undefined\"]\n{\n\tdisplay: block;\n}\n\t\t");
		// add new tab "Activity Log"
		var checkboxId = 'show-activity-log';
		var activityLogLabel = document.createElement('label');
		activityLogLabel.id = 'activity-log-label';
		activityLogLabel.htmlFor = checkboxId;
		activityLogLabel.textContent = 'Activity Log';
		newTopbar.addTabEntry(activityLogLabel);
		var checkbox = document.createElement('input');
		checkbox.id = checkboxId;
		checkbox.type = 'checkbox';
		checkbox.style.display = 'none';
		document.body.insertBefore(checkbox, document.body.firstChild);
		var label = document.createElement('label');
		label.id = 'activity-log-overlay';
		label.htmlFor = checkboxId;
		document.body.appendChild(label);
		logEl = document.createElement('ul');
		logEl.id = 'activity-log';
		var classList = [];
		var html = '';
		for (var key in LOG_FILTER)
		{
			// TODO: load saved filter
			var checked = true;
			classList.push(key);
			html += "<label for=\"log-filter-" + key + "\" title=\"" + LOG_FILTER[key].title + "\">\n\t\t\t\t" + (LOG_FILTER[key].img ? "<img class=\"image-icon-20\" src=\"" + LOG_FILTER[key].img + "\">" : LOG_FILTER[key].label) + "\n\t\t\t</label>\n\t\t\t<input type=\"checkbox\" id=\"log-filter-" + key + "\" " + (checked ? 'checked' : '') + ">";
		}
		logEl.className = 'empty ' + classList.join(' ');
		logEl.innerHTML = "<li class=\"filter\">\n\t\t\t" + html + "\n\t\t</li>";
		document.body.appendChild(logEl);
		var $checkboxes = win.$('li.filter > input[id^="log-filter-"]');
		$checkboxes.checkboxradio(
		{
			icon: false
		});
		$checkboxes.change(function (event)
		{
			var id = event.target.id;
			var key = id.replace('log-filter-', '');
			var checked = document.getElementById(id).checked;
			logEl.classList[checked ? 'add' : 'remove'](key);
			// TODO: save current state
		});
		// add all stored elements
		logList.forEach(function (e)
		{
			return updateLog(e, true);
		});
	}

	function observeCombat()
	{
		observer.add('fightMonsterId', function (key, oldValue, newValue)
		{
			if (isFightStarted())
			{
				currentCombat = {
					type: 'combat'
					, time: now()
					, monsterId: newValue
					, ticks: -5
					, hero:
					{}
					, monster:
					{}
				};
				add2Log(currentCombat);
				updateLog(currentCombat);
			}
			else
			{
				if (currentCombat)
				{
					currentCombat.ticks--;
					saveLog();
				}
				currentCombat = null;
				currentCombatEl = null;
			}
		});
		observer.addTick(function ()
		{
			if (currentCombat !== null)
			{
				currentCombat.ticks++;
				if (currentCombat.ticks === 0)
				{
					currentCombat.hero[0] = win.heroHp;
					currentCombat.monster[0] = win.fightMonsterHp;
				}
				updateLog(currentCombat);
			}
		});
	}
	var possiblyCaughtFish;
	var lastFishingXpChange = 0;

	function fishObserver(key, oldValue, newValue)
	{
		if (oldValue < newValue && lastFishingXpChange >= now() - 5e3)
		{
			var idx = possiblyCaughtFish.indexOf(key);
			if (idx !== -1)
			{
				add(
				{
					type: 'fish'
					, fish: key
				});
				possiblyCaughtFish = [];
				lastFishingXpChange = 0;
			}
		}
	}

	function processFishingXpChange(xp)
	{
		lastFishingXpChange = now();
		possiblyCaughtFish = [];
		for (var fish in FISH_XP)
		{
			if (FISH_XP[fish] == xp)
			{
				possiblyCaughtFish.push(fish);
			}
		}
	}
	log.processFishingXpChange = processFishingXpChange;

	function observeFishing()
	{
		for (var fish in FISH_XP)
		{
			observer.add(fish, fishObserver);
		}
	}

	function init()
	{
		addLogEl();
		observeCombat();
		observeFishing();
	}
	log.init = init;
})(log || (log = {}));

/**
 * 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
		, map: true
		, essence: true
		, rocket: true
		, wind: true
		, perk: true
	};
	var lastTimestamp = new Map();

	function notifyTabClickable(title, body, icon, tabKey, whenActive)
	{
		if (whenActive === void 0)
		{
			whenActive = false;
		}
		var now = (new Date).getTime();
		var timeDiff = now - (lastTimestamp.get(title) || 0);
		if (timeDiff < MIN_TIME_DIFFERENCE * 1e3)
		{
			return;
		}
		var promise = notifications.event(title
		, {
			body: body
			, icon: 'images/' + icon
			, whenActive: whenActive
			, onclick: function ()
			{
				var tabNames = tabKey.split('.');
				win.openTab(tabNames[0]);
				if (tabNames.length > 1)
				{
					win.openSubTab(tabNames[1]);
				}
			}
		});
		if (promise)
		{
			lastTimestamp.set(title, now);
		}
	}

	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 || !settings.getSub(settings.KEY.showNotifications, '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 || !settings.getSub(settings.KEY.showNotifications, '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 || !settings.getSub(settings.KEY.showNotifications, '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()
	{
		var timerKeys = BOAT_LIST.map(function (boatKey)
		{
			return boatKey + 'Timer';
		});
		observeTimer(timerKeys, function (key, oldValue, newValue)
		{
			if (!gameEvents.enabled.boat || !settings.getSub(settings.KEY.showNotifications, 'boatReturned'))
			{
				return;
			}
			var boatKey = key.replace(/Timer$/, '');
			notifyTabClickable('Fishy business', 'Your ' + split2Words(boatKey).toLowerCase() + ' returned from its trip.', boatKey + '.png', 'combat');
		});
	}

	function battle()
	{
		observeTimer('combatGlobalCooldown', function (key, oldValue, newValue)
		{
			if (!gameEvents.enabled.battle || !settings.getSub(settings.KEY.showNotifications, 'heroReady'))
			{
				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 || !settings.getSub(settings.KEY.showNotifications, 'potionEffect'))
			{
				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 = win.refreshMarketSlot;
		var lastCollectText = 0;
		win.refreshMarketSlot = function (offerId, itemKey, amount, price, collectText, slotId, timeLeft)
		{
			var diff = collectText - lastCollectText;
			lastCollectText = collectText;
			if (gameEvents.enabled.market && settings.getSub(settings.KEY.showNotifications, 'itemsSold') && collectText > 0)
			{
				var soldAmount = diff / price;
				var amountText = ['one (1)', 'two (2)', 'three (3)'][soldAmount - 1] || format.number(soldAmount);
				var itemName = split2Words(itemKey).toLowerCase();
				if (soldAmount > 1)
				{
					itemName = pluralize(itemName);
				}
				var textTemplate = function (itemText)
				{
					return "You've sold " + itemText + " to the market.";
				};
				if (amount > 0)
				{
					notifyTabClickable('Ka-ching', textTemplate(amountText + ' ' + itemName), 'icons/shop.png', 'playermarket');
				}
				else
				{
					notifyTabClickable('Sold out', textTemplate((soldAmount === 1 ? 'your' : 'all') + ' ' + amountText + ' ' + itemName), 'icons/shop.png', 'playermarket');
				}
			}
			_refreshMarketSlot(offerId, itemKey, amount, price, collectText, slotId, timeLeft);
		};
	}

	function gameValues()
	{
		observer.add('treasureMap', function (key, oldValue, newValue)
		{
			if (gameEvents.enabled.map && settings.getSub(settings.KEY.showNotifications, 'pirate') && oldValue < newValue)
			{
				notifyTabClickable('Arrrr!', 'Your pirate found a treasure map.', 'treasureMap.png', 'items');
			}
		});
		observer.add('essence', function (key, oldValue, newValue)
		{
			if (oldValue < newValue)
			{
				var diff = newValue - oldValue;
				var num = ['an', 'two', 'three'][diff - 1] || diff;
				var text = 'You found ' + num + ' essence' + (diff > 1 ? 's' : '') + '.';
				if (gameEvents.enabled.essence && settings.get(settings.KEY.showEssencePopup))
				{
					win.confirmDialogue(400, text, 'Close', '', '');
				}
				if (gameEvents.enabled.essence && settings.getSub(settings.KEY.showNotifications, 'essence'))
				{
					notifyTabClickable('Essence of Life', text, 'essence.png', 'combat.spells');
				}
			}
		});
		observer.add('rocketMoonId', function (key, oldValue, newValue)
		{
			if (gameEvents.enabled.rocket && settings.getSub(settings.KEY.showNotifications, 'rocket'))
			{
				if (newValue > 0)
				{
					notifyTabClickable('One small step for a man...', 'Your rocket landed on the moon.', 'rocket.png', 'mining');
				}
				else if (oldValue < 0 && newValue === 0)
				{
					notifyTabClickable('Back home', 'Your rocket returned to earth.', 'rocket.png', 'mining');
				}
			}
		});
		var WIND_DESCRIPTION = [
			'The sea is sleeping like a baby'
			, 'There is a slight breeze'
			, 'A normal day on the sea'
			, 'There is a storm coming'
			, 'The sea is raging'
		];
		var WIND_CATEGORY = ['none', 'low', 'medium', 'high', 'very high'];
		var _setSailBoatWind = win.setSailBoatWind;
		var oldValue = -1;
		win.setSailBoatWind = function (windLevel)
		{
			_setSailBoatWind(windLevel);
			var newValue = win.sailBoatWindGlobal;
			if (oldValue !== -1
				&& oldValue !== newValue
				&& win.boundSailBoat > 0
				&& gameEvents.enabled.wind
				&& settings.getSub(settings.KEY.showNotifications, 'wind'))
			{
				var windText = (WIND_DESCRIPTION[win.sailBoatWindGlobal] || 'The wind is turning')
					+ ' (' + (WIND_CATEGORY[win.sailBoatWindGlobal] || 'level ' + win.sailBoatWindGlobal) + ' wind).';
				notifyTabClickable('Wind of change', windText, 'sailBoat.png', 'combat');
			}
			oldValue = newValue;
		};
		// trigger getting the wind level once at page load
		// so the script can distinguish between getting the wind initially and an actual wind change
		win.processTab('combat');
		// achievements (e.g. achBrewingEasyCompleted)
		var achRegex = /^ach([A-Z][a-z]+)([A-Z][a-z]+)Completed$/;

		function checkAchievement(key, oldValue, newValue)
		{
			if (gameEvents.enabled.perk && settings.getSub(settings.KEY.showNotifications, 'perk') && oldValue < newValue)
			{
				var match = key.match(/^ach([A-Z][a-z]+)([A-Z][a-z]+)Completed$/);
				var skillName = match[1].toLowerCase();
				var difficulty = match[2].toLowerCase();
				notifyTabClickable('New perk unlocked', 'You completed the ' + difficulty + ' ' + skillName + ' achievement set.', 'achievementBook.png', 'achievements');
			}
		}
		for (var _i = 0, _a = win.jsItemArray; _i < _a.length; _i++)
		{
			var key = _a[_i];
			if (achRegex.test(key))
			{
				observer.add(key, checkAchievement);
			}
		}
		var stardustEl = document.querySelector('span[data-item-display="stardust"]');
		var parent = stardustEl && stardustEl.parentElement;
		if (stardustEl && parent)
		{
			addStyle("\n#dh2qol-stardustMonitor\n{\n\tdisplay: none !important;\n}\n#stardust-change\n{\n\tcolor: grey;\n\tdisplay: inline-block;\n\tmargin-left: .25rem;\n\ttext-align: left;\n\twidth: 2.5rem;\n}\n#stardust-change.hide\n{\n\tvisibility: hidden;\n}\n\t\t\t");
			var changeEl_1 = document.createElement('span');
			changeEl_1.className = 'hide';
			changeEl_1.id = 'stardust-change';
			parent.appendChild(changeEl_1);
			var HIDE_AFTER_TICKS_1 = 5;
			var ticksSinceSdChange_1 = HIDE_AFTER_TICKS_1;
			var sdDiff_1 = 0;
			observer.add('stardust', function (key, oldValue, newValue)
			{
				sdDiff_1 = Math.max(newValue - oldValue, 0);
				if (sdDiff_1 > 0)
				{
					ticksSinceSdChange_1 = 0;
				}
			});
			observer.addTick(function ()
			{
				var show = settings.get(settings.KEY.showSdChange) && ticksSinceSdChange_1 < HIDE_AFTER_TICKS_1;
				changeEl_1.classList[show ? 'remove' : 'add']('hide');
				ticksSinceSdChange_1++;
				var diff = ticksSinceSdChange_1 > 1 ? 0 : sdDiff_1;
				var sign = diff > 0 ? '+' : PLUS_MINUS_SIGN;
				changeEl_1.textContent = '(' + sign + format.number(diff) + ')';
			});
		}
	}

	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 crafting;
(function (crafting)
{
	crafting.name = 'crafting';
	/**
	 * hide crafted recipes
	 */
	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, init)
	{
		if (init === void 0)
		{
			init = false;
		}
		var info = RECIPE_MAX.crafting[key];
		var maxValue = typeof info.max === 'function' ? info.max() : info.max;
		var boundKey = getBoundKey(key);
		var unbound = getGameValue(key);
		var bound = getGameValue(boundKey);
		var extra = (info.extraKeys || []).map(function (k)
		{
			return getGameValue(k);
		}).reduce(function (p, c)
		{
			return p + c;
		}, 0);
		setRecipeVisibility(key, maxValue - (bound + unbound + extra) > 0);
		if (init)
		{
			observer.add([key, boundKey], function ()
			{
				return hideRecipe(key, false);
			});
		}
	}
	/**
	 * hide useless items
	 */
	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 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 RECIPE_MAX.crafting)
			{
				hideRecipe(key, init);
			}
			if (init)
			{
				settings.observe(settings.KEY.hideCraftingRecipes, function ()
				{
					return processRecipes(false);
				});
			}
		}
		processRecipes(true);
		var _processCraftingTab = win.processCraftingTab;
		win.processCraftingTab = function ()
		{
			var reinit = !!win.refreshLoadCraftingTable;
			_processCraftingTab();
			if (reinit)
			{
				processRecipes(false);
			}
		};

		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 RECIPE_MAX.crafting)
			{
				hideItem(key, RECIPE_MAX.crafting[key], init);
			}
			if (init)
			{
				settings.observe(settings.KEY.hideUselessItems, function ()
				{
					return processItems(false);
				});
			}
		}
		processItems(true);
	}
	crafting.init = init;
})(crafting || (crafting = {}));

/**
 * improve item boxes
 */
var itemBoxes;
(function (itemBoxes)
{
	itemBoxes.name = 'itemBoxes';

	function hideNumberInItemBox(key, setVisibility)
	{
		if (setVisibility === void 0)
		{
			setVisibility = false;
		}
		var itemBox = document.getElementById('item-box-' + key);
		if (!itemBox)
		{
			return;
		}
		var numberElement = itemBox.querySelector('span[data-item-display]');
		if (!numberElement)
		{
			return;
		}
		numberElement.classList.add('number-caption');
		if (setVisibility)
		{
			numberElement.classList.remove('hide');
			numberElement.classList.add('hidden');
		}
		else
		{
			numberElement.classList.remove('hidden');
			numberElement.classList.add('hide');
		}
	}

	function addSpan2ItemBox(key, replace, setVisibility)
	{
		if (replace === void 0)
		{
			replace = true;
		}
		if (setVisibility === void 0)
		{
			setVisibility = false;
		}
		if (replace)
		{
			hideNumberInItemBox(key, setVisibility);
		}
		var itemBox = document.getElementById('item-box-' + key);
		if (!itemBox)
		{
			return;
		}
		var span = document.createElement('span');
		span.className = 'caption';
		itemBox.appendChild(span);
		return span;
	}

	function addCaptionStyle()
	{
		var CLASS_NAME = 'show-captions';
		addStyle("\nbody:not(." + CLASS_NAME + ") span.caption\n{\n\tdisplay: none;\n}\nbody." + CLASS_NAME + " span.number-caption.hidden\n{\n\tvisibility: hidden;\n}\nbody." + CLASS_NAME + " span.number-caption.hide\n{\n\tdisplay: none;\n}\n\t\t");

		function updateBodyClass()
		{
			var show = settings.get(settings.KEY.showCaptions);
			document.body.classList[show ? 'add' : 'remove'](CLASS_NAME);
		}
		updateBodyClass();
		settings.observe(settings.KEY.showCaptions, function ()
		{
			return updateBodyClass();
		});
	}

	function setOilPerSecond(span, oil)
	{
		span.innerHTML = "+ " + format.number(oil) + " L/s <img src=\"images/oil.png\" class=\"image-icon-20\">";
	}
	// 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.classList.add('capacity');
				capacitySpan.textContent = 'Capacity: ' + format.number(win.getFurnaceCapacity(boundKey), true);
			}
		}
		// charcoal foundry
		var foundryCapacitySpan = addSpan2ItemBox('charcoalFoundry');
		if (foundryCapacitySpan)
		{
			foundryCapacitySpan.classList.add('capacity');
			foundryCapacitySpan.textContent = 'Capacity: 100';
		}
	}
	// 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.classList.add('oil-cap');
				capSpan.textContent = 'Oil cap: ' + format.number(OIL_STORAGE_SIZES[i], true);
			}
		}
	}
	var oilPipeOrbKey = 'boundBlueOilPipeOrb';

	function setOilPipeCaption(span)
	{
		setOilPerSecond(span, 50 + win.achMiningEasyCompleted * 50 + getGameValue(oilPipeOrbKey) * 100);
	}
	// show oil per second
	function addOilCaption()
	{
		addStyle("\n#item-box-handheldOilPump,\n#item-box-boundOilPipe,\n#item-box-boundPumpjacks,\n#item-box-boundOilFactory\n{\n\tposition: relative;\n}\nspan.caption.oil\n{\n\tfont-size: .9rem;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n}\nspan.caption.oil img[src=\"images/oil.png\"]\n{\n\t-webkit-filter: drop-shadow(0px 0px 5px rgb(255,255,255));\n\tfilter: url(#drop-shadow);\n\t-ms-filter: \"progid:DXImageTransform.Microsoft.Dropshadow(OffX=0, OffY=0, Color='#FFF')\";\n\tfilter: \"progid:DXImageTransform.Microsoft.Dropshadow(OffX=0, OffY=0, Color='#FFF')\";\n}\n\t\t");
		var tpl = document.createElement('templateWrapper');
		tpl.innerHTML = "\n<svg height=\"0\" xmlns=\"http://www.w3.org/2000/svg\">\n    <filter id=\"drop-shadow\">\n        <feGaussianBlur in=\"SourceAlpha\" stdDeviation=\"1\"></feGaussianBlur>\n        <feOffset dx=\"0\" dy=\"0\" result=\"offsetblur\"></feOffset>\n        <feFlood flood-color=\"rgba(255,255,255,1)\"></feFlood>\n        <feComposite in2=\"offsetblur\" operator=\"in\"></feComposite>\n        <feMerge>\n\n            <feMergeNode></feMergeNode><feMergeNode in=\"SourceGraphic\"></feMergeNode>\n        </feMerge>\n    </filter>\n</svg>\n\t\t";
		var shadowDrop = tpl.firstElementChild;
		document.body.appendChild(shadowDrop);
		var handheldOilSpan = addSpan2ItemBox('handheldOilPump', true, true);
		if (handheldOilSpan)
		{
			handheldOilSpan.classList.add('oil');
			setOilPerSecond(handheldOilSpan, 1 * win.miner);
			observer.add('miner', function ()
			{
				return setOilPerSecond(handheldOilSpan, 1 * win.miner);
			});
		}
		var oilPipeSpan = addSpan2ItemBox('boundOilPipe', true, true);
		if (oilPipeSpan)
		{
			oilPipeSpan.classList.add('oil');
			setOilPipeCaption(oilPipeSpan);
			observer.add(oilPipeOrbKey, function ()
			{
				return setOilPipeCaption(oilPipeSpan);
			});
		}
		// add pump jack oil display
		var pumpjackSpan = addSpan2ItemBox('boundPumpjacks', false);
		if (pumpjackSpan)
		{
			pumpjackSpan.classList.add('oil');
			var setCaption_1 = function ()
			{
				return setOilPerSecond(pumpjackSpan, win.boundPumpjacks * 10);
			};
			setCaption_1();
			observer.add('boundPumpjacks', function ()
			{
				return setCaption_1();
			});
		}
		// add number of workers as caption to oil factory
		var workerSpan = addSpan2ItemBox('boundOilFactory');
		if (workerSpan)
		{
			var setCaption_2 = function ()
			{
				return workerSpan.textContent = 'Workers: ' + format.number(win.oilFactoryCheapWorkers, true);
			};
			setCaption_2();
			observer.add('oilFactoryCheapWorkers', function ()
			{
				return setCaption_2();
			});
		}
		var factoryOilSpan = addSpan2ItemBox('boundOilFactory');
		if (factoryOilSpan)
		{
			factoryOilSpan.classList.add('oil');
			var setCaption_3 = function ()
			{
				return setOilPerSecond(factoryOilSpan, win.oilFactoryCheapWorkers);
			};
			setCaption_3();
			observer.add('oilFactoryCheapWorkers', function ()
			{
				return setCaption_3();
			});
		}
	}

	function addWandCaption()
	{
		for (var i = 0; i < WAND_LEVELS.length; i++)
		{
			var level = WAND_LEVELS[i];
			var key = level + 'Wand';
			var wandSpan = addSpan2ItemBox(key);
			if (wandSpan)
			{
				wandSpan.textContent = capitalize(level) + ' Wand';
			}
		}
	}

	function addVariousCaptions()
	{
		var key2Name = {
			'achievementBook': 'Achievements'
			, 'emptyAnvil': 'Anvil'
			, 'tap': 'Tree Tap'
			, 'farmer': 'Farmer'
			, 'gardener': 'Gardener'
			, 'planter': 'Planter'
			, 'boundBrewingKit': 'Brewing Kit'
			, 'cooksBook': 'Cooks Book'
			, 'cooksPage': 'Cooks Page'
			, 'combatDropTable': 'Loot Table'
			, 'magicBook': 'Spell Book'
		};
		for (var key in key2Name)
		{
			var span = addSpan2ItemBox(key);
			if (span)
			{
				span.textContent = key2Name[key];
			}
		}
	}
	// show current tier
	function addTierCaption()
	{
		addStyle("\nspan.item-box > span.orb::before\n{\n\tbackground-color: aqua;\n\tborder: 1px solid silver;\n\tborder-radius: 100%;\n\tcontent: '';\n\tdisplay: inline-block;\n\tmargin-left: -5px;\n\tmargin-right: 5px;\n\twidth: 10px;\n\theight: 10px;\n}\n\t\t");

		function addOrbObserver(key, spanList)
		{
			var boundOrbKey = getBoundKey('Blue' + capitalize(key) + 'Orb');

			function checkOrb()
			{
				var classAction = getGameValue(boundOrbKey) > 0 ? 'add' : 'remove';
				for (var _i = 0, spanList_1 = spanList; _i < spanList_1.length; _i++)
				{
					var span = spanList_1[_i];
					span.classList[classAction]('orb');
				}
			}
			checkOrb();
			observer.add(boundOrbKey, function ()
			{
				return checkOrb();
			});
		}
		var remainingOrbItems = ORB_ITEMS;
		for (var _i = 0, TIER_ITEMS_2 = TIER_ITEMS; _i < TIER_ITEMS_2.length; _i++)
		{
			var tierItem = TIER_ITEMS_2[_i];
			var isBindable = TIER_ITEMS_NOT_BINDABLE.indexOf(tierItem) === -1;
			var spanList = [];
			for (var i = 0; i < TIER_LEVELS.length; i++)
			{
				var key = getTierKey(tierItem, i);
				var toolKey = isBindable ? getBoundKey(key) : key;
				var tierSpan = addSpan2ItemBox(toolKey);
				if (tierSpan)
				{
					tierSpan.classList.add('tier');
					tierSpan.textContent = TIER_NAMES[i];
					spanList.push(tierSpan);
				}
			}
			var orbIndex = remainingOrbItems.indexOf(tierItem);
			if (orbIndex !== -1)
			{
				addOrbObserver(tierItem, spanList);
				remainingOrbItems.splice(orbIndex, 1);
			}
		}
		for (var _a = 0, remainingOrbItems_1 = remainingOrbItems; _a < remainingOrbItems_1.length; _a++)
		{
			var itemKey = remainingOrbItems_1[_a];
			var captionSpan = document.querySelector('#item-box-' + getBoundKey(itemKey) + ' > span:last-of-type');
			if (!captionSpan)
			{
				continue;
			}
			addOrbObserver(itemKey, [captionSpan]);
		}
	}
	var boatTimerKeys = BOAT_LIST.map(function (boatKey)
	{
		return boatKey + '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' : '';
		var parent = span.parentElement;
		parent.style.opacity = otherInTransit ? '.5' : '';
		if (init)
		{
			observer.add(boatTimerKeys, function ()
			{
				return checkBoat(span, timerKey, false);
			});
		}
	}
	// show boat progress
	function addBoatCaption()
	{
		addStyle("\n#item-box-boundSailBoat.item-box > span[data-item-display] + span + span\n{\n\tdisplay: none;\n}\n\t\t");
		for (var i = 0; i < BOAT_LIST.length; i++)
		{
			var span = addSpan2ItemBox(getBoundKey(BOAT_LIST[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(win.bonemeal);
		var captionSpan = document.createElement('span');
		captionSpan.className = 'caption';
		captionSpan.textContent = 'Bonemeal: ';
		bonemealSpan.parentElement.insertBefore(captionSpan, bonemealSpan);
	}

	function warningBeforeSellingGems()
	{
		var _sellNPCItemDialogue = win.sellNPCItemDialogue;
		win.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
					|| !win.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
					|| !win.confirm('Logs are time consuming to collect. Please consider carefully:\nDo you really want to sell ' + amount + ' ' + itemName + '?'))
				{
					return;
				}
			}
			_sellNPCItemDialogue(item, amount);
		};
	}

	function addWikiaLinks()
	{
		var WIKIA_CLASS = 'wikia-links';
		addStyle("\n." + WIKIA_CLASS + " .item-box\n{\n\tposition: relative;\n}\n.item-box > .wikia-link\n{\n\tbackground-color: black;\n\tbackground-image: " + icons.getSvgAsUrl(icons.wrapCodeWithSvg(icons.WIKIA, '-2 -2 26 27', 30, 30)) + ";\n\tbackground-repeat: no-repeat;\n\tdisplay: none;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\twidth: 30px;\n\theight: 30px;\n}\n." + WIKIA_CLASS + " .item-box:hover > .wikia-link\n{\n\tdisplay: block;\n}\n\t\t");

		function setWikiaLinksVisibility(init)
		{
			if (init === void 0)
			{
				init = false;
			}
			var show = settings.get(settings.KEY.wikiaLinks);
			document.body.classList[show ? 'add' : 'remove'](WIKIA_CLASS);
			if (init)
			{
				settings.observe(settings.KEY.wikiaLinks, function ()
				{
					return setWikiaLinksVisibility();
				});
			}
		}
		setWikiaLinksVisibility(true);
		var boxes = document.getElementsByClassName('item-box');

		function disableClickPropagation(el)
		{
			el.addEventListener('click', function (event)
			{
				event.stopPropagation();
			});
		}
		for (var i = 0; i < boxes.length; i++)
		{
			var box = boxes.item(i);
			var key = box.id.replace(/^item-box-/, '');
			var linkArea = document.createElement('a');
			linkArea.className = 'wikia-link';
			linkArea.href = getWikiaLink(key);
			linkArea.target = '_blank';
			disableClickPropagation(linkArea);
			box.appendChild(linkArea);
			var tooltipEl = ensureTooltip('wikiLink', linkArea);
			if (tooltipEl.innerHTML === '')
			{
				tooltipEl.innerHTML = "Click to open the wikia page about this item.";
			}
		}
	}

	function init()
	{
		addCaptionStyle();
		addFurnaceCaption();
		addOilStorageCaption();
		addOilCaption();
		addWandCaption();
		addVariousCaptions();
		addTierCaption();
		addBoatCaption();
		addBonemealCaption();
		warningBeforeSellingGems();
		addWikiaLinks();
	}
	itemBoxes.init = init;
})(itemBoxes || (itemBoxes = {}));

/**
 * add new chat
 */
var chat;
(function (chat)
{
	chat.name = 'chat';
	// min time difference between repeated messages to not be considered as spam
	var MIN_DIFF_REPEATED_MSG = 5e3;
	var KEYWORD_LIST_KEY = 'keywordList';
	chat.keywordList = store.has(KEYWORD_LIST_KEY) ? store.get(KEYWORD_LIST_KEY) : [];
	var CHAT_HISTORY_KEY = 'chatHistory';
	var MAX_CHAT_HISTORY_LENGTH = 100;
	var PM_HISTORY_KEY = 'pmHistory';
	var MAX_PM_HISTORY_LENGTH = 50;
	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 = [
	{
		key: ''
		, title: ''
	}
	, {
		key: 'halloween2015'
		, title: 'Halloween Gamer (2015)'
	}
	, {
		key: 'christmas2015'
		, title: 'Chirstmas Gamer (2015)'
	}
	, {
		key: 'easter2016'
		, title: 'Easter Gamer (2016)'
	}
	, {
		key: 'halloween2016'
		, title: 'Halloween Gamer (2016)'
	}
	, {
		key: 'christmas2016'
		, title: 'Chirstmas Gamer (2016)'
	}
	, {
		key: 'dh1Max'
		, title: 'DH1 Pro'
	}
	, {
		key: 'hardcore'
		, title: 'Hardcore Player'
	}
	, {
		key: 'quest'
		, title: 'Questmaster'
	}
	, {
		key: 'maxMining'
		, title: 'Mastery in mining'
	}
	, {
		key: 'maxCrafting'
		, title: 'Mastery in crafting'
	}
	, {
		key: 'maxWC'
		, title: 'Mastery in woodcutting'
	}
	, {
		key: 'maxFarming'
		, title: 'Mastery in farming'
	}
	, {
		key: 'maxBrewing'
		, title: 'Mastery in brewing'
	}
	, {
		key: 'maxCombat'
		, title: 'Mastery in combat'
	}
	, {
		key: 'maxMagic'
		, title: 'Mastery in magic'
	}
	, {
		key: 'maxFishing'
		, title: 'Mastery in fishing'
	}
	, {
		key: 'maxCooking'
		, title: 'Mastery in cooking'
	}
	, {
		key: 'maxLevel'
		, title: 'Mastery of all skills'
	}
	, {
		key: 'birdcage'
		, title: 'Stole a birdcage'
	}
	, {
		key: 'achievement'
		, title: 'Achievement Hunter'
	}];
	var getUnknownChatIcon = function (icon)
	{
		return {
			key: 'unknown'
			, title: ''
			, img: '<img src="images/chat-icons/' + icon + '.png" class="image-icon-20" />'
		};
	};
	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'
	};
	// game commands
	var COMMANDS = [
		'pm'
		, 'mute'
		, 'clear'
		, 'ipmute'
	];
	var CLEAR_CMD = 'clear';
	var TUTORIAL_CMD = 'tutorial';
	// load chat history
	var chatHistory = store.get(CHAT_HISTORY_KEY) || [];
	var pmHistory = store.get(PM_HISTORY_KEY) || [];
	// store chat colors for each user
	var user2Color;
	var usedColors;
	// reserve color for special messages (e.g. server messages): white
	var reservedColors = ['#ffffff'];
	// message chunks
	var msgChunkMap = new Map();
	// for adding elements at startup
	var chatboxFragments = new Map();
	var chatInitialized = false;
	// 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);
	}

	function isMuted(user)
	{
		return user !== win.username
			&& win.mutedPeople.some(function (name)
			{
				return user.indexOf(name) > -1;
			});
	}

	function isSpam(data)
	{
		// allow all own messages, messages from contributors, mods, devs and all server messages
		if (data.username === win.username || data.tag != Tag.none)
		{
			return false;
		}
		/**
		 * get last message of current user
		 */
		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 (dataBefore.username === data.username)
			{
				lastData = dataBefore;
			}
		}
		/**
		 * compare message and don't allow the same message twice
		 */
		if (lastData
			&& lastData.msg === data.msg
			&& (data.timestamp - lastData.timestamp) < MIN_DIFF_REPEATED_MSG)
		{
			return true;
		}
		return false;
	}

	function saveKeywordList()
	{
		store.set(KEYWORD_LIST_KEY, chat.keywordList);
	}

	function addKeyword(keyword)
	{
		if (keyword !== '' && chat.keywordList.indexOf(keyword) === -1)
		{
			chat.keywordList.push(keyword);
			saveKeywordList();
			return true;
		}
		return false;
	}
	chat.addKeyword = addKeyword;

	function removeKeyword(keyword)
	{
		var index = chat.keywordList.indexOf(keyword);
		if (index !== -1)
		{
			chat.keywordList.splice(index, 1);
			saveKeywordList();
			return true;
		}
		return false;
	}
	chat.removeKeyword = removeKeyword;

	function handleScrolling(chatbox)
	{
		if (win.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
	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*\[(PM from|Sent to) ([A-Za-z0-9_ ]+)\]: (.+?)\s*$/) || ['', '', username, msg];
			type = match[1] == 'Sent to' ? Type.pmSent : Type.pmReceived;
			username = match[2];
			if (username !== 'sexy_squid')
			{
				username = username.replace(/_/g, ' ');
			}
			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 = win.getGlobalLevel();
			}
		}
		// unlinkify when using DH2QoL to store the plain message
		if (win.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 saveChatHistory()
	{
		store.set(CHAT_HISTORY_KEY, chatHistory);
	}

	function savePmHistory()
	{
		store.set(PM_HISTORY_KEY, pmHistory);
	}

	function add2ChatHistory(data)
	{
		if (data.type === Type.pmReceived
			|| data.type === Type.pmSent)
		{
			pmHistory.push(data);
			pmHistory = pmHistory.slice(-MAX_PM_HISTORY_LENGTH);
			savePmHistory();
		}
		else
		{
			chatHistory.push(data);
			chatHistory = chatHistory.slice(-MAX_CHAT_HISTORY_LENGTH);
			saveChatHistory();
		}
	}

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

	function setNewCounter(tab, num, force)
	{
		if (force === void 0)
		{
			force = false;
		}
		var panel = getChatPanel(tab.dataset.username || '');
		if (force
			|| !tab.classList.contains('selected')
			|| !win.isAutoScrolling && panel.scrollHeight > panel.scrollTop + panel.offsetHeight)
		{
			tab.dataset.new = num.toString();
		}
	}

	function incrementNewCounter(tab)
	{
		setNewCounter(tab, parseInt(tab.dataset.new || '0', 10) + 1);
	}

	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;
			setNewCounter(tab, 0, true);
			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 getChatPanel(username)
	{
		var id = username == '' ? GENERAL_CHAT_DIV_ID : PM_CHAT_DIV_PREFIX + username2Id(username);
		var panel = document.getElementById(id);
		if (!panel)
		{
			panel = document.createElement('div');
			panel.setAttribute('disabled', 'disabled');
			panel.id = id;
			panel.className = CHAT_CLASS;
			var defaultChat = document.getElementById(DEFAULT_CHAT_DIV_ID);
			var height = defaultChat.style.height;
			panel.style.height = height;
			var chatDiv = defaultChat.parentElement;
			chatDiv.insertBefore(panel, defaultChat);
		}
		return panel;
	}

	function changeChatTab(oldTab, newTab)
	{
		if (oldTab)
		{
			oldTab.classList.remove('selected');
			var oldChatPanel = void 0;
			if (oldTab.classList.contains('default'))
			{
				oldChatPanel = document.getElementById(DEFAULT_CHAT_DIV_ID);
			}
			else
			{
				oldChatPanel = getChatPanel(oldTab.dataset.username || '');
			}
			oldChatPanel.classList.remove('selected');
		}
		newTab.classList.add('selected');
		setNewCounter(newTab, 0, true);
		var newChatPanel;
		if (newTab.classList.contains('default'))
		{
			newChatPanel = document.getElementById(DEFAULT_CHAT_DIV_ID);
		}
		else
		{
			newChatPanel = getChatPanel(newTab.dataset.username || '');
		}
		newChatPanel.classList.add('selected');
		var toUsername = newTab.dataset.username;
		var newTextPlaceholder = toUsername == '' ? win.username + ':' : 'PM to ' + toUsername + ':';
		document.getElementById(CHAT_INPUT_ID).placeholder = newTextPlaceholder;
		handleScrolling(newChatPanel);
	}

	function clearChat(username)
	{
		if (username === '')
		{
			// clean server chat
			chatHistory = [];
			saveChatHistory();
		}
		else
		{
			// delete pms stored for that user
			for (var i = 0; i < pmHistory.length; i++)
			{
				var data = pmHistory[i];
				if (data.username == username)
				{
					pmHistory.splice(i, 1);
					i--;
				}
			}
			savePmHistory();
		}
		// clear pm-chat panel
		var panel = getChatPanel(username);
		while (panel.children.length > 0)
		{
			panel.removeChild(panel.children[0]);
		}
		msgChunkMap.delete(username);
		return panel;
	}

	function closeChatTab(username)
	{
		// clear pm-chat panel and remove message-history
		clearChat(username);
		// remove pm-tab (and change tab if necessary)
		var selectedTab = getSelectedTab();
		var tab2Close = getChatTab(username, null);
		if (selectedTab.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;
	}

	function colorizeMsg(username)
	{
		if (username == '')
		{
			return null;
		}
		if (!user2Color.has(username))
		{
			var color = void 0;
			do {
				var colorizer = settings.getSub(settings.KEY.colorizeChat, 'colorizer');
				if (colorizer == 1)
				{
					color = colorGenerator.getRandom(
					{
						luminosity: 'light'
					});
				}
				else if (colorizer == 2)
				{
					color = colorGenerator.getRandom(
					{
						luminosity: 'dark'
					});
				}
				else
				{
					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 ? win.username : data.username;
		var history = isThisPm ? pmHistory : chatHistory;
		var historyIndex = history.indexOf(data);
		var isSameUser = null;
		var isSameTime = null;
		for (var i = historyIndex - 1; i >= 0 && (isSameUser === null || isSameTime === null); i--)
		{
			var dataBefore = history[i];
			if (isThisPm === isDataPM(dataBefore))
			{
				if (isSameUser === null)
				{
					var beforeUsername = dataBefore.type == Type.pmSent ? win.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] || getUnknownChatIcon(data.icon);
		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 : '';
		if (isThisPm)
		{
			win.lastPMUser = data.username;
		}
		// username is 3-12 characters long
		var chatbox = getChatPanel(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);
		// add delay because handleScrolling is will set scrollTop delayed
		setTimeout(function ()
		{
			var chatTab = getChatTab(userKey, isThisPm ? null : SpecialTab.general);
			incrementNewCounter(chatTab);
		});
	}

	function applyChatStyle()
	{
		addStyle("\ndiv.div-chat-area\n{\n\tpadding-left: 0;\n}\nspan.chat-msg\n{\n\tdisplay: flex;\n\tmin-height: 21px;\n\tpadding: 1px 0;\n\tpadding-left: 5px;\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\tline-height: 1.2rem;\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.unknown > img,\n.chat-msg .user .icon:not(.unknown)::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.chat-msg .user .icon.maxMining::before\t\t{ background-image: url('images/chat-icons/9.png'); }\n.chat-msg .user .icon.maxCrafting::before\t{ background-image: url('images/chat-icons/10.png'); }\n.chat-msg .user .icon.maxWC::before\t\t\t{ background-image: url('images/chat-icons/11.png'); }\n.chat-msg .user .icon.maxFarming::before\t{ background-image: url('images/chat-icons/12.png'); }\n.chat-msg .user .icon.maxBrewing::before\t{ background-image: url('images/chat-icons/13.png'); }\n.chat-msg .user .icon.maxCombat::before\t\t{ background-image: url('images/chat-icons/14.png'); }\n.chat-msg .user .icon.maxMagic::before\t\t{ background-image: url('images/chat-icons/15.png'); }\n.chat-msg .user .icon.maxFishing::before\t{ background-image: url('images/chat-icons/16.png'); }\n.chat-msg .user .icon.maxCooking::before\t{ background-image: url('images/chat-icons/17.png'); }\n.chat-msg .user .icon.maxLevel::before\t\t{ background-image: url('images/chat-icons/18.png'); }\n.chat-msg .user .icon.birdcage::before\t\t{ background-image: url('images/chat-icons/19.png'); }\n.chat-msg .user .icon.achievement::before\t{ background-image: url('images/chat-icons/20.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-bottom: -1px;\n\tmargin-top: -1px;\n\tpadding-bottom: 2px;\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: calc(100% - 5px);\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 -5px -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.selected::after\n{\n\tcolor: gray;\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 initColorizer(init)
	{
		if (init === void 0)
		{
			init = false;
		}
		var usernameList = user2Color && Array.from(user2Color.keys()) || [];
		user2Color = new Map();
		usedColors = new Set();
		for (var _i = 0, reservedColors_1 = reservedColors; _i < reservedColors_1.length; _i++)
		{
			var color = reservedColors_1[_i];
			usedColors.add(color);
		}
		var colorStyle = getStyle('name-color');
		colorStyle.innerHTML = '';
		for (var _a = 0, usernameList_1 = usernameList; _a < usernameList_1.length; _a++)
		{
			var username = usernameList_1[_a];
			colorizeMsg(username);
		}
		if (init)
		{
			settings.observeSub(settings.KEY.colorizeChat, 'colorizer', function ()
			{
				return initColorizer();
			});
		}
	}

	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 (win.isAutoScrolling != value)
			{
				toggleCheckbox.checked = value;
				win.isAutoScrolling = value;
				var icon_2 = 'none';
				var color_1 = value ? 'lime' : 'red';
				var text_1 = (value ? 'En' : 'Dis') + 'abled' + (full ? ' Autoscroll' : '');
				if (full)
				{
					if (showScrollTextTimeout)
					{
						win.clearTimeout(showScrollTextTimeout);
					}
					showScrollTextTimeout = win.setTimeout(function ()
					{
						return win.scrollText(icon_2, color_1, text_1);
					}, 300);
				}
				else
				{
					win.scrollText(icon_2, color_1, text_1);
				}
				setNewCounter(getSelectedTab(), 0, true);
				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 - win.innerHeight;
			var referenceBottom = visibleBottom + win.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 - 1;
				setAutoScrolling(scrolled2Bottom, true);
			}
			var n = now();
			if (delayedScrollStart == null)
			{
				delayedScrollStart = n;
			}
			if (delayedScrollStart + 300 > n)
			{
				if (delayedScrollTimeout)
				{
					win.clearTimeout(delayedScrollTimeout);
				}
				delayedScrollTimeout = win.setTimeout(function ()
				{
					delayedScrollStart = null;
					delayedScrollTimeout = null;
					scrollHugeChat();
				}, 50);
			}
		});
	}

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

	function getSelectedTabUsername()
	{
		var selectedTab = getSelectedTab();
		return selectedTab.dataset.username || '';
	}

	function clickChatTab(newTab)
	{
		var oldTab = getSelectedTab();
		if (newTab == oldTab)
		{
			return;
		}
		changeChatTab(oldTab, newTab);
	}

	function clickCloseChatTab(tab)
	{
		var username = tab.dataset.username || '';
		var chatPanel = getChatPanel(username);
		if (chatPanel.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';
		getChatPanel('');
		getChatTab('', SpecialTab.filler);
		var _sendChat = win.sendChat;
		win.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();
		var usernameRegex = new RegExp('\\b' + win.username + '\\b', 'i');
		if (settings.getSub(settings.KEY.showNotifications, 'mention') && usernameRegex.test(lowerMsg))
		// if (lowerMsg.indexOf(win.username) > -1)
		{
			notifications.event('You\'ve been mentioned'
			, {
				body: data.msg
			});
		}
		var match = [];
		for (var _i = 0, keywordList_1 = chat.keywordList; _i < keywordList_1.length; _i++)
		{
			var keyword = keywordList_1[_i];
			var regex = new RegExp('\\b' + keyword + '\\b', 'i');
			if (regex.test(lowerMsg))
			// if (lowerMsg.indexOf(keyword) > -1)
			{
				match.push(keyword);
			}
		}
		if (settings.getSub(settings.KEY.showNotifications, 'keyword') && 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);
		var isThisSpam = false;
		if (isDataPM(data))
		{
			if (data.type == Type.pmSent)
			{
				switch2PmTab(data.username);
			}
			else
			{
				notifyPm(data);
			}
		}
		else
		{
			isThisSpam = settings.get(settings.KEY.enableSpamDetection) && isSpam(data);
			if (!isThisSpam && data.username != win.username)
			{
				// check mentioning and keywords only for non-pms and only for messages from other players
				checkMentionAndKeywords(data);
			}
		}
		if (isThisSpam)
		{
			console.info('detected spam:', data);
		}
		else
		{
			add2ChatHistory(data);
			add2Chat(data);
		}
		var fn = addToChatBox_ == null ? win.addToChatBox : addToChatBox_;
		fn(username, icon, tag, msg, isPM);
	}
	chat.newAddToChatBox = newAddToChatBox;

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

	function newChat()
	{
		addChatTabs();
		applyChatStyle();
		initColorizer(true);
		addToChatBox_ = win.addToChatBox;
		win.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=\"stats\"><div>Open stats</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);
		win.$(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 = win.mutedPeople.indexOf(username) !== -1;
			muteEl.style.display = isMuted ? 'none' : '';
			unmuteEl.style.display = isMuted ? '' : 'none';
			event.stopPropagation();
			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('stats'))
			{
				win.lookup(username);
			}
			else if (target.classList.contains('mute'))
			{
				if (username == '')
				{
					return;
				}
				win.mutedPeople.push(username);
				win.scrollText('none', 'lime', '<em>' + username + '</em> muted');
			}
			else if (target.classList.contains('unmute'))
			{
				if (username == '')
				{
					return;
				}
				var index = win.mutedPeople.indexOf(username);
				if (index !== -1)
				{
					win.mutedPeople.splice(index, 1);
				}
				win.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';
			}
		});
		win.addEventListener('contextmenu', 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);
			});
		}
	}

	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);

		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(CLEAR_CMD))
			{
				// clear current chat (pm chat, or general chat)
				var username = getSelectedTabUsername();
				clearChat(username);
			}
			else 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;
				}
			}
			return msgPrefix + msg;
		}
		var _sendChat = win.sendChat;
		win.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 = win.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;
			}
		}
		win.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'));
		// add history to chat
		// TEMP >>>
		// move pm entries to pm history
		var changed = false;
		for (var i = 0; i < chatHistory.length; i++)
		{
			var data = chatHistory[i];
			if (isDataPM(data))
			{
				chatHistory.splice(i, 1);
				i--;
				pmHistory.push(data);
				changed = true;
			}
		}
		if (changed)
		{
			saveChatHistory();
			savePmHistory();
		}
		// TEMP <<<
		chatHistory.forEach(function (d)
		{
			return add2Chat(d);
		});
		pmHistory.forEach(function (d)
		{
			return add2Chat(d);
		});
		if (chatboxFragments)
		{
			chatboxFragments.forEach(function (fragment, key)
			{
				var chatbox = getChatPanel(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++)
		{
			setNewCounter(tabs[i], 0, true);
		}
	}
	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)
		{
			for (var i = 0; i < spellbox.children.length; i++)
			{
				var child = spellbox.children.item(i);
				if (!win.isInCombat() && child.hasAttribute('onclick'))
				{
					child.dataset.onclick = child.getAttribute('onclick') || '';
					child.removeAttribute('onclick');
				}
				else if (win.isInCombat() && !!child.dataset.onclick)
				{
					child.setAttribute('onclick', child.dataset.onclick || '');
					child.dataset.onclick = '';
				}
			}
		}
	}
	// warn before unloading/reloading the tab if combat is in progress
	function combatWarnOnUnload()
	{
		if (!win.isInCombat())
		{
			win.onbeforeunload = null;
		}
		else
		{
			if (win.onbeforeunload == null)
			{
				win.onbeforeunload = function ()
				{
					return 'You are in a fight!';
				};
			}
		}
	}

	function fixCombatCountdown()
	{
		var el = document.getElementById('combat-countdown');
		if (!el)
		{
			return;
		}
		if (win.isInCombat())
		{
			el.style.display = '';
			var visible = win.combatCommenceTimer != 0;
			el.style.visibility = visible ? '' : 'hidden';
		}
	}
	// fix exhaustion timer and updating brewing and cooking recipes
	function fixExhaustionTimer()
	{
		if (document.getElementById('tab-container-combat').style.display != 'none')
		{
			win.combatNotFightingTick();
		}
	}

	function fixClientGameLoop()
	{
		var _clientGameLoop = win.clientGameLoop;
		win.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()
	{
		// define missing properties for checking the needed materials
		win.enchantStargemPotionMagic = 0;
		win.changeWeatherMagic = 0;
		win.refreshLoadMagicTable = true;
		var _processMagicTab = win.processMagicTab;
		win.processMagicTab = function ()
		{
			var _refreshLoadCraftingTable = win.refreshLoadCraftingTable;
			win.refreshLoadCraftingTable = win.refreshLoadMagicTable;
			_processMagicTab();
			win.refreshLoadCraftingTable = _refreshLoadCraftingTable;
			if (win.magicPage3 == 1)
			{
				win.showMateriesNeededAndLevelLabelsMagic('enchantStargemPotion');
				win.showMateriesNeededAndLevelLabelsMagic('beam');
				win.showMateriesNeededAndLevelLabelsMagic('changeWeather');
			}
		};
	}

	function moveItemBox(itemKey, targetElId, color1, color2)
	{
		var itemBox = document.getElementById('item-box-' + itemKey);
		var targetContainer = document.getElementById(targetElId);
		targetContainer.appendChild(itemBox);
		// remove event listeners before binding the tooltip to it
		var $itemBox = win.$(itemBox);
		$itemBox.off('mouseover').off('mouseleave');
		itemBox.title = '';
		// bind tooltip to item box
		ensureTooltip('ingredient-secondary', itemBox);
		// change color
		itemBox.style.background = 'linear-gradient(' + color1 + ', ' + color2 + ')';
		$itemBox
			.mouseover(function ()
			{
				itemBox.style.background = 'none';
				itemBox.style.backgroundColor = color2;
			})
			.mouseleave(function ()
			{
				itemBox.style.background = 'linear-gradient(' + color1 + ', ' + color2 + ')';
			});
	}
	// move the strange leaf to brewing tab (thanks lasse_brus for this idea)
	function moveStrangeLeafs()
	{
		moveItemBox('strangeLeaf', 'tab-sub-container-brewing', '#800080', '#990099');
		moveItemBox('strangerLeaf', 'tab-sub-container-brewing', '#800080', '#990099');
	}
	// 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 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");
	}

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

	function fixQuest8BraveryRecipe()
	{
		observer.add([
			'quest8'
			, 'braveryPotion'
		], function ()
		{
			var show = win.quest8 > 0 && win.braveryPotion == 0;
			var recipe = document.getElementById('brewing-braveryPotion');
			if (recipe)
			{
				recipe.style.display = show ? '' : 'none';
			}
		});
	}

	function fixHitText()
	{
		win.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 = win.$("<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 tooltipParent = boatTooltip && boatTooltip.parentElement;
		if (!boatBox || !boatTooltip || !tooltipParent)
		{
			return;
		}

		function setTripDuration(durationEl, boatKey)
		{
			var durationStr = TRIP_DURATION.hasOwnProperty(boatKey) ? TRIP_DURATION[boatKey].toString(10) : '?';
			durationEl.innerHTML = "<strong>Trip duration:</strong> " + durationStr + " hours";
		}
		boatTooltip.id = boatBox.dataset.tooltipId = 'tooltip-boundRowBoat';
		boatTooltip.appendChild(document.createElement('br'));
		var boatDuration = document.createElement('span');
		boatDuration.className = 'trip-duration';
		setTripDuration(boatDuration, 'rowBoat');
		boatTooltip.appendChild(boatDuration);
		for (var _i = 0, BOAT_LIST_1 = BOAT_LIST; _i < BOAT_LIST_1.length; _i++)
		{
			var boatKey = BOAT_LIST_1[_i];
			var boundKey = getBoundKey(boatKey);
			var itemBox = document.getElementById('item-box-' + boundKey);
			if (!itemBox)
			{
				continue;
			}
			var tooltip = document.getElementById('tooltip-' + boundKey);
			if (!tooltip)
			{
				tooltip = boatTooltip.cloneNode(true);
				tooltip.id = 'tooltip-' + boundKey;
				var header = tooltip.firstElementChild;
				header.textContent = capitalize(split2Words(boatKey));
				tooltipParent.appendChild(tooltip);
				itemBox.dataset.tooltipId = 'tooltip-' + boundKey;
			}
			var durationEl = tooltip.getElementsByClassName('trip-duration').item(0);
			if (durationEl)
			{
				setTripDuration(durationEl, boatKey);
			}
		}
	}

	function fixAlignments()
	{
		addStyle("\nspan.item-box[id^=\"item-box-\"] > img:not(.image-icon-100),\nspan.item-box[id^=\"item-box-\"] > span > img\n{\n\tmargin-top: -2px;\n}\n\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}\nimg.farming-patch-img\n{\n\twidth: 349px;\n\theight: 400px;\n}\n/* fix position of some plant images */\nimg.farming-patch-img[src$=\"/3_1.png\"]\n{\n\theight: 398px;\n\tmargin-top: -2px;\n\tmargin-bottom: 4px;\n}\nimg.farming-patch-img[src$=\"/3_2.png\"]\n{\n\theight: 399px;\n\tmargin-top: -1px;\n\tmargin-bottom: 2px;\n\tmargin-left: 2px;\n\tmargin-right: -2px;\n}\nimg.farming-patch-img[src$=\"/3_4.png\"]\n{\n\tmargin-top: 1px;\n\tmargin-bottom: -1px;\n\tmargin-left: -2px;\n\tmargin-right: 2px;\n}\n\n#combat-table-area\n{\n\tborder-spacing: 0;\n}\n#combat-table-area > tbody > tr > td\n{\n\tvertical-align: top;\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 div.fight-spellbook\n{\n\tmargin: 0 -3px;\n}\n#hero-area span.fight-spell\n{\n\tmargin-bottom: 0;\n\tmargin-top: 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#img-monster[src$=\"/100.png\"]\n{\n\theight: 300px;\n}\n#img-monster[src$=\"/101.png\"]\n{\n\ttransform: translateY(-10px);\n}\n#tab-sub-container-combat > .large-button > .image-icon-50\n{\n\theight: 70px;\n\tmargin-top: -10px;\n\twidth: 70px;\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.large-button\n{\n\tfont-size: 3rem;\n}\n#combat-table-area span.medium-button + br + br\n{\n\tdisplay: none;\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';
		win.$(attackCell).tooltip();
		var accuracyCell = statRow.cells.item(1);
		accuracyCell.title = 'Attack Accuracy';
		win.$(accuracyCell).tooltip();
		var speedCell = statRow.cells.item(2);
		speedCell.title = 'Attack Speed';
		win.$(speedCell).tooltip();
		var defenseCell = statRow.cells.item(3);
		defenseCell.title = 'Defense';
		win.$(defenseCell).tooltip();
	}

	function unifyTooltips()
	{
		function getLastNonEmptyChild(parent)
		{
			for (var i = parent.childNodes.length - 1; i >= 0; i--)
			{
				var child = parent.childNodes.item(i);
				if (child.nodeType === Node.TEXT_NODE
					&& (child.textContent || '').trim() !== '')
				{
					return null;
				}
				else if (child.nodeType === Node.ELEMENT_NODE)
				{
					return child;
				}
			}
			return null;
		}
		// clean unnecessary br-tags in tooltips
		var tooltips = document.querySelectorAll('#tooltip-list > div[id^="tooltip-"]');
		for (var i = 0; i < tooltips.length; i++)
		{
			var tooltip = tooltips[i];
			var lneChild = void 0;
			while ((lneChild = getLastNonEmptyChild(tooltip)) && lneChild.tagName == 'BR')
			{
				tooltip.removeChild(lneChild);
			}
		}

		function getTooltip(item)
		{
			return document.getElementById('tooltip-' + item);
		}
		var boldify = [
			'oilBarrel'
			, 'boundEmptyPickaxe'
			, 'boundEmptyShovel'
			, 'boundRocket'
			, 'ashes'
			, 'iceBones'
		];
		var lastDotRegex = /\.\s*$/;
		for (var _i = 0, boldify_1 = boldify; _i < boldify_1.length; _i++)
		{
			var item = boldify_1[_i];
			var tooltip = getTooltip(item);
			if (!tooltip)
			{
				continue;
			}
			var textNode = tooltip.lastChild;
			while (textNode && (textNode.nodeType != Node.TEXT_NODE || (textNode.textContent || '').trim() === ''))
			{
				if (textNode.nodeName === 'SPAN')
				{
					textNode = textNode.lastChild;
				}
				else
				{
					textNode = textNode.previousSibling;
				}
			}
			if (!textNode)
			{
				continue;
			}
			var text = textNode.textContent || '';
			var split = text.split(/\.(?=\s*\S+)/);
			var clickText = split[split.length - 1];
			textNode.textContent = text.replace(clickText, '');
			if (split.length > 1)
			{
				tooltip.appendChild(document.createElement('br'));
				tooltip.appendChild(document.createElement('br'));
			}
			var boldText = document.createElement('b');
			boldText.textContent = clickText;
			tooltip.appendChild(boldText);
		}

		function prepareTooltip(item, editText, createOnMissing)
		{
			if (createOnMissing === void 0)
			{
				createOnMissing = false;
			}
			var tooltip = getTooltip(item);
			if (!tooltip)
			{
				return;
			}
			// try to find the b-node:
			var bNode = getLastNonEmptyChild(tooltip);
			if (bNode && bNode.tagName === 'SPAN')
			{
				bNode = getLastNonEmptyChild(bNode);
			}
			if (!bNode || bNode.tagName !== 'B')
			{
				if (!createOnMissing)
				{
					bNode = null;
				}
				else
				{
					tooltip.appendChild(document.createElement('br'));
					tooltip.appendChild(document.createElement('br'));
					bNode = document.createElement('b');
					tooltip.appendChild(bNode);
				}
			}
			if (bNode)
			{
				bNode.textContent = editText(bNode);
			}
		}
		// remove dots
		for (var i = 0; i < tooltips.length; i++)
		{
			var item = tooltips.item(i).id.replace(/^tooltip-/, '');
			prepareTooltip(item, function (bNode)
			{
				var text = bNode.textContent || '';
				if (/Click to /.test(text))
				{
					return text.replace(lastDotRegex, '');
				}
				return text;
			});
		}
		// add click texts
		function setText(item, text)
		{
			prepareTooltip(item, function ()
			{
				return text;
			}, true);
		}
		for (var _a = 0, FURNACE_LEVELS_1 = FURNACE_LEVELS; _a < FURNACE_LEVELS_1.length; _a++)
		{
			var furnaceLevel = FURNACE_LEVELS_1[_a];
			var furnaceItem = getBoundKey(furnaceLevel + 'Furnace');
			setText(furnaceItem, 'Click to operate');
			var ovenItem = getBoundKey(furnaceLevel + 'Oven');
			setText(ovenItem, 'Click to operate');
		}
		// fix tooltip of quests-book
		var questBookTooltip = getTooltip('quests-book');
		if (questBookTooltip)
		{
			var childNodes = questBookTooltip.childNodes;
			for (var i = 0; i < childNodes.length; i++)
			{
				var node = childNodes[i];
				if (node.nodeType === Node.TEXT_NODE
					&& (node.textContent || '').indexOf('Click to see a list of quests.') > -1)
				{
					var next = node.nextSibling;
					if (next)
					{
						questBookTooltip.removeChild(next);
					}
					questBookTooltip.removeChild(node);
				}
			}
		}
		// fix tooltip of axe
		var axeTooltip = getTooltip('boundEmptyAxe');
		if (axeTooltip)
		{
			axeTooltip.insertBefore(document.createElement('br'), axeTooltip.lastElementChild);
		}
		var texts = {
			'quests-book': 'Click to see the list of quests'
			, 'achievementBook': 'Click to see the list of achievements'
			, 'boundEmptyChisel': 'Click to use'
			, 'rake': 'Click to upgrade your rake'
			, 'boundBoat': 'Click to send boat'
		};
		for (var item in texts)
		{
			setText(item, texts[item]);
		}
		for (var _b = 0, BOAT_LIST_2 = BOAT_LIST; _b < BOAT_LIST_2.length; _b++)
		{
			var boatKey = BOAT_LIST_2[_b];
			setText(getBoundKey(boatKey), 'Click to send boat');
		}
	}
	var cached = {
		scrollWidth: 0
		, scrollHeight: 0
	};

	function changeTooltipPosition(event)
	{
		var tooltipX = event.pageX - 8;
		var tooltipY = event.pageY + 8;
		var el = document.querySelector('body > div.tooltip');
		if (!el)
		{
			return;
		}
		if (!this)
		{
			// init
			cached.scrollWidth = document.body.scrollWidth;
			cached.scrollHeight = document.body.scrollHeight;
		}
		var rect = el.getBoundingClientRect();
		var css = {
			left: tooltipX
			, top: tooltipY
			, width: ''
			, height: ''
			, maxWidth: cached.scrollWidth
			, maxHeight: cached.scrollHeight
		};
		var diffX = cached.scrollWidth - 20 - tooltipX - rect.width;
		if (diffX < 0)
		{
			css.left += diffX;
			css.width = rect.width - 42;
		}
		var diffY = cached.scrollHeight - 20 - tooltipY - rect.height;
		if (diffY < 0)
		{
			css.top += diffY;
			css.height = rect.height - 22;
		}
		win.$(el).css(css);
	}

	function fixTooltipPositioning()
	{
		win.changeTooltipPosition = changeTooltipPosition;
		win.loadTooltips();
	}

	function fixCombatNavigation()
	{
		var backBtns = document.querySelectorAll('span.medium-button[onclick*="openTab(\'combat\')"]');
		for (var i = 0; i < backBtns.length; i++)
		{
			var btn = backBtns.item(i);
			var img = btn.firstElementChild;
			var textNode = btn.lastChild;
			if (!img || img.tagName != 'IMG' || !textNode)
			{
				continue;
			}
			img.className = img.className.replace(/(-\d+)-b/, '$1');
			textNode.textContent = ' back';
		}
	}

	function fixPromethiumSmeltingTime()
	{
		var _getTimerPerBar = win.getTimerPerBar;
		win.getTimerPerBar = function (bar)
		{
			if (bar == 'promethiumBar')
			{
				return 80;
			}
			return _getTimerPerBar(bar);
		};
	}

	function fixImage()
	{
		var oxygenEl = document.querySelector('img[src="images/oxygenPotion"]');
		if (oxygenEl)
		{
			oxygenEl.src += '.png';
		}
	}

	function init()
	{
		fixClientGameLoop();
		fixScroller();
		fixTooltipStyle();
		fixRefreshingMagicRecipes();
		moveStrangeLeafs();
		fixTreasureMap();
		fixWoodcutting();
		fixQuestBook();
		// apply fix for scroll images later to fix images in this code too
		fixHitText();
		fixScrollImages();
		fixQuest8BraveryRecipe();
		fixBoatTooltips();
		fixAlignments();
		addHeroStatTooltips();
		unifyTooltips();
		fixTooltipPositioning();
		fixCombatNavigation();
		fixPromethiumSmeltingTime();
		fixImage();
	}
	temporaryFixes.init = init;
})(temporaryFixes || (temporaryFixes = {}));

/**
 * improve timer
 */
var timer;
(function (timer)
{
	timer.name = 'timer';
	var IMPROVED_CLASS = 'improved';
	var NOTIFICATION_AREA_ID = 'notifaction-area';
	var PERCENT_CLASS = 'percent';
	var REMAINING_CLASS = 'remaining';
	var TIMER_CLASS = 'timer';

	function bindNewFormatter()
	{
		function doBind()
		{
			win.formatTime = win.formatTimeShort = win.formatTimeShort2 = function (seconds)
			{
				return format.timer(seconds);
			};
		}
		win.addEventListener('load', function ()
		{
			return setTimeout(function ()
			{
				return doBind();
			}, 100);
		});
		doBind();
		setTimeout(function ()
		{
			return doBind();
		}, 100);
	}

	function applyStyle()
	{
		addStyle("\nspan.notif-box." + IMPROVED_CLASS + "\n{\n\tposition: relative;\n}\nspan.notif-box." + IMPROVED_CLASS + " > span:not(." + TIMER_CLASS + "):not(." + REMAINING_CLASS + "):not(." + PERCENT_CLASS + ")\n{\n\tdisplay: none;\n}\nspan.notif-box." + IMPROVED_CLASS + " > span." + REMAINING_CLASS + ",\nspan.notif-box." + IMPROVED_CLASS + " > span." + PERCENT_CLASS + "\n{\n\tposition: absolute;\n\tleft: 10px;\n\tfont-size: 0.9rem;\n\tbottom: 0px;\n\twidth: 50px;\n\ttext-align: right;\n\ttext-shadow: 1px 1px 4px black;\n}\nspan.notif-box." + IMPROVED_CLASS + " > span." + REMAINING_CLASS + "::before\n{\n\tcontent: '\\0D7';\n\tmargin-right: .25rem;\n\tmargin-left: -.5rem;\n}\nspan.notif-box." + IMPROVED_CLASS + " > span." + PERCENT_CLASS + "::after\n{\n\tcontent: '%';\n}\n\t\t");
	}

	function improveSmeltingTimer()
	{
		var el = document.getElementById('notif-smelting');
		if (!el)
		{
			return;
		}
		var smeltingNotifBox = el;
		smeltingNotifBox.classList.add(IMPROVED_CLASS);
		var smeltingTimerEl = document.createElement('span');
		smeltingTimerEl.className = TIMER_CLASS;
		smeltingNotifBox.appendChild(smeltingTimerEl);
		var remainingBarsEl = document.createElement('span');
		remainingBarsEl.className = REMAINING_CLASS;
		smeltingNotifBox.appendChild(remainingBarsEl);
		var delta = 0;

		function updatePercValues(init)
		{
			if (init === void 0)
			{
				init = false;
			}
			updateSmeltingTimer(delta = 0);
			if (init)
			{
				observer.add('smeltingPercD', function ()
				{
					return updatePercValues();
				});
				observer.add('smeltingPerc', function ()
				{
					return updatePercValues();
				});
			}
		}

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

	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(win.getTreeName(treeId)) || 'Unknown Tree';
			var remainingTime = win.TREE_GROW_TIME[treeId - 1] - getGameValue(growTimerKey);
			timerEl.textContent = remainingTime > 0 ? '(' + format.timer(remainingTime) + ')' : '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 = PLANT_NAME[getGameValue(idKey)] || 'Unkown Plant';
		if (stage == 0)
		{
			var isLocked = patchId > 4 && win.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) + ')';
		}
		if (init)
		{
			observer.add([idKey, timerKey, stageKey, 'donorFarmingPatch'], function ()
			{
				return updatePatchInfo(patchId, infoElId, false);
			});
		}
	}
	// add seed name and change color of timer
	function getSoonestTreeTimer()
	{
		if (win.treeStage1 == 4
			|| win.treeStage2 == 4
			|| win.treeStage3 == 4
			|| win.treeStage4 == 4
			|| win.treeStage5 == 4
			|| win.treeStage6 == 4)
		{
			return -1;
		}
		var minTimer = null;
		for (var i = 1; i <= 6; i++)
		{
			var treeId = getGameValue('treeId' + i);
			var unlocked = getGameValue('treeUnlocked' + i) == 1;
			var timerValue = getGameValue('treeGrowTimer' + i);
			if (unlocked && treeId !== 0 && timerValue > 0)
			{
				var remainingTime = win.TREE_GROW_TIME[treeId - 1] - timerValue;
				minTimer = minTimer === null ? remainingTime : Math.min(minTimer, remainingTime);
			}
		}
		return minTimer || 0;
	}

	function getSoonestFarmingTimer()
	{
		if (win.farmingPatchStage1 == 0 || win.farmingPatchStage1 == 4
			|| win.farmingPatchStage2 == 0 || win.farmingPatchStage2 == 4
			|| win.farmingPatchStage3 == 0 || win.farmingPatchStage3 == 4
			|| win.farmingPatchStage4 == 0 || win.farmingPatchStage4 == 4
			|| win.donorFarmingPatch != 0 && (win.farmingPatchStage5 == 0 || win.farmingPatchStage5 == 4
				|| win.farmingPatchStage6 == 0 || win.farmingPatchStage6 == 4))
		{
			return -1;
		}
		var minTimer = null;
		for (var i = 1; i <= (win.donorFarmingPatch ? 6 : 4); i++)
		{
			var remainingTimer = getGameValue('farmingPatchGrowTime' + i) - getGameValue('farmingPatchTimer' + i);
			minTimer = minTimer === null ? remainingTimer : Math.min(minTimer, remainingTimer);
		}
		return minTimer || 0;
	}

	function improveSeedGrowTimer()
	{
		improveTimer('div[id^="farming-patch-area-"]', 'black', 'blue', 'farming-patch-info-', 'farming-patch-area-', updatePatchInfo);
	}

	function addTabTimer()
	{
		var TAB_TIMER_KEY = 'tabTimer';
		addStyle("\ntable.tab-bar td\n{\n\tposition: relative;\n}\n." + TAB_TIMER_KEY + " table.tab-bar td.ready > img:first-child\n{\n\tbackground-image: linear-gradient(#161618, #48ab32);\n\tmargin: -2px -5px -3px;\n\tpadding: 6px 5px 7px;\n}\ntable.tab-bar td .info\n{\n\tcolor: yellow;\n\tdisplay: none;\n\tfont-size: 0.8rem;\n\tpadding-left: 50px;\n\tposition: absolute;\n\tleft: 0;\n\tright: 0;\n\ttext-align: center;\n}\n." + TAB_TIMER_KEY + " table.tab-bar td .info\n{\n\tdisplay: block;\n}\ntable.tab-bar td .info.timer\n{\n\tbottom: 0;\n\tpadding-bottom: 5px;\n}\ntable.tab-bar td .info.timer:not(:empty)::before\n{\n\tcontent: '(';\n}\ntable.tab-bar td .info.timer:not(:empty)::after\n{\n\tcontent: ')';\n}\nbody.short-tabs table.tab-bar td .info.timer\n{\n\tdisplay: none;\n}\ntable.tab-bar td .info.extra\n{\n\tcolor: white;\n\tpadding-top: 5px;\n\ttop: 0;\n}\nbody.short-tabs table.tab-bar td .info.extra\n{\n\tdisplay: none;\n}\n." + TAB_TIMER_KEY + " #dhqol-notif-woodcutting,\n." + TAB_TIMER_KEY + " #dhqol-notif-farming,\n." + TAB_TIMER_KEY + " #dhqol-notif-combat,\n." + TAB_TIMER_KEY + " #dhqol-notif-vial\n{\n\tdisplay: none !important;\n}\n\t\t");

		function getTabEl(key)
		{
			return document.getElementById('tab-container-bar-' + key);
		}

		function addInfoDiv(key)
		{
			var infoDiv = document.createElement('div');
			infoDiv.className = 'info';
			var tab = getTabEl(key);
			if (tab)
			{
				tab.appendChild(infoDiv);
			}
			return infoDiv;
		}

		function createTabTimer(key, timerFn)
		{
			var tab = getTabEl(key);
			var timerDiv = addInfoDiv(key);
			if (!tab || !timerDiv)
			{
				return;
			}
			timerDiv.classList.add('timer');

			function updateTimer()
			{
				var minTimer = timerFn();
				if (tab)
				{
					tab.classList[minTimer == -1 ? 'add' : 'remove']('ready');
				}
				timerDiv.textContent = minTimer <= 0 ? '' : format.timer(minTimer);
			}
			updateTimer();
			observer.addTick(function ()
			{
				return updateTimer();
			});
		}
		createTabTimer('woodcutting', getSoonestTreeTimer);
		createTabTimer('farming', getSoonestFarmingTimer);
		createTabTimer('combat', function ()
		{
			return win.combatGlobalCooldown;
		});
		var energyDiv = addInfoDiv('combat');
		energyDiv.classList.add('extra');

		function updateEnergy()
		{
			energyDiv.innerHTML = '<img src="images/steak.png" class="image-icon-15"> ' + format.number(win.energy);
		}
		updateEnergy();
		observer.add('energy', function ()
		{
			return updateEnergy();
		});
		// add highlight for stardust potions
		var potionDiv = addInfoDiv('brewing');
		potionDiv.classList.add('extra');
		var potionList = ['stardustPotion', 'superStardustPotion'];
		var potionImageList = [];

		function updatePotion(key, img, init)
		{
			if (init === void 0)
			{
				init = false;
			}
			var timerKey = key + 'Timer';
			var show = getGameValue(key) > 0 && getGameValue(timerKey) === 0;
			img.style.display = show ? '' : 'none';
			if (init)
			{
				observer.add(key, function ()
				{
					return updatePotion(key, img);
				});
				observer.add(timerKey, function ()
				{
					return updatePotion(key, img);
				});
			}
		}
		for (var i = 0; i < potionList.length; i++)
		{
			var key = potionList[i];
			var img = document.createElement('img');
			img.src = 'images/' + key + '.png';
			img.className = 'image-icon-15';
			potionImageList[i] = img;
			potionDiv.appendChild(img);
			updatePotion(key, img, true);
		}

		function updateVisibility()
		{
			document.body.classList[settings.get(settings.KEY.showTabTimer) ? 'add' : 'remove'](TAB_TIMER_KEY);
		}
		updateVisibility();
		settings.observe(settings.KEY.showTabTimer, function ()
		{
			return updateVisibility();
		});
		observer.add('profileShortTabs', function ()
		{
			var short = !!win.profileShortTabs;
			document.body.classList[short ? 'add' : 'remove']('short-tabs');
		});
	}

	function addOilInfo()
	{
		var NULL_TYPE = 'null';
		var PLUS_TYPE = 'plus';
		var MINUS_TYPE = 'minus';
		addStyle("\n#oil-filling-level\n{\n\tbackground-color: black;\n\tborder: 1px solid white;\n\tdisplay: inline-block;\n\tposition: absolute;\n\tbottom: 0;\n\ttop: 0;\n\ttransform: translateX(-10px);\n\twidth: 8px;\n}\n#oil-filling-level > div\n{\n\tbackground-color: white;\n\twidth: 100%;\n}\n\ntable.top-bar span[id^=\"dh2qol-oil\"]\n{\n\tdisplay: none;\n}\n#oil-flow-net\n{\n\tcolor: hsla(195, 100%, 50%, 1);\n\tfont-weight: bold;\n}\n#oil-flow-net[data-type=\"" + NULL_TYPE + "\"]\n{\n\tcolor: hsla(195, 100%, 50%, 1);\n}\n#oil-flow-net[data-type=\"" + PLUS_TYPE + "\"]\n{\n\tcolor: green;\n}\n#oil-flow-net[data-type=\"" + MINUS_TYPE + "\"]\n{\n\tcolor: red;\n}\n#oil-flow-net-timer[data-type=\"" + NULL_TYPE + "\"]\n{\n\tdisplay: none;\n}\n#oil-flow-net-timer[data-type=\"" + PLUS_TYPE + "\"]\n{\n\tcolor: yellow;\n}\n#oil-flow-net-timer[data-type=\"" + MINUS_TYPE + "\"]\n{\n\tcolor: orange;\n}\n\t\t");
		var oilFlow = document.getElementById('oil-flow-values');
		var parent = oilFlow && oilFlow.parentElement;
		if (!oilFlow || !parent)
		{
			return;
		}
		var container = document.createElement('div');
		container.id = 'oil-filling-level';
		var fillingLevel = document.createElement('div');
		container.appendChild(fillingLevel);
		var first = parent.firstElementChild;
		if (first)
		{
			parent.insertBefore(container, first);
		}
		else
		{
			parent.appendChild(container);
		}
		parent.style.position = 'relative';
		var netFlow = document.createElement('span');
		netFlow.id = 'oil-flow-net';
		parent.insertBefore(netFlow, oilFlow);
		var next = oilFlow.nextElementSibling;
		var netTimer = document.createElement('span');
		netTimer.id = 'oil-flow-net-timer';
		if (next)
		{
			parent.insertBefore(netTimer, next);
		}
		else
		{
			parent.appendChild(netTimer);
		}
		var oilNet;
		var oilNetType;

		function updateNetFlow(init)
		{
			if (init === void 0)
			{
				init = false;
			}
			oilNet = win.oilIn - win.oilOut;
			oilNetType = oilNet === 0 ? NULL_TYPE : (oilNet > 0 ? PLUS_TYPE : MINUS_TYPE);
			netFlow.dataset.type = oilNetType;
			var sign = oilNet === 0 ? PLUS_MINUS_SIGN : (oilNet > 0 ? '+' : '');
			netFlow.textContent = sign + oilNet;
			if (init)
			{
				observer.add('oilIn', function ()
				{
					return updateNetFlow();
				});
				observer.add('oilOut', function ()
				{
					return updateNetFlow();
				});
			}
			updateFullTimer(init);
		}
		var hour2Color = (_a = {}
			, // 30min
			_a[.5 * 60 * 60] = 'rgb(255, 0, 0)'
			, _a[5 * 60 * 60] = 'rgb(255, 255, 0)'
			, _a[8 * 60 * 60] = 'rgb(255, 255, 255)'
			, _a);

		function updateFullTimer(init)
		{
			if (init === void 0)
			{
				init = false;
			}
			netTimer.dataset.type = oilNetType;
			var time = 0;
			if (oilNet > 0)
			{
				netTimer.title = 'full in...';
				var diff = win.maxOil - win.oil;
				time = diff / oilNet;
			}
			else if (oilNet < 0)
			{
				netTimer.title = 'empty in...';
				time = win.oil / Math.abs(oilNet);
			}
			netTimer.textContent = '(' + format.timer(Math.ceil(time)) + ')';
			var filledPercent = win.oil / win.maxOil * 100;
			fillingLevel.style.height = (100 - filledPercent) + '%';
			/**
			 * colorize filling level according to the time it needs to be full/empty:
			 *	- red		iff oil storage full/empty in 30min
			 *	- yellow	iff oil storage full/empty in 5h
			 *	- white		iff oil storage full/empty in 8h or more
			 */
			var color = oilNet === 0 ? '#ffffff' : colorGenerator.getColorTransition(time, hour2Color);
			container.style.borderColor = color;
			if (init)
			{
				observer.add('maxOil', function ()
				{
					return updateFullTimer();
				});
				observer.add('oil', function ()
				{
					return updateFullTimer();
				});
				observer.addTick(function ()
				{
					return updateFullTimer();
				});
			}
		}
		updateNetFlow(true);
		var _a;
	}

	function addRocketTimer()
	{
		var notifArea = document.getElementById(NOTIFICATION_AREA_ID);
		if (!notifArea)
		{
			return;
		}
		var notifBox = document.createElement('span');
		notifBox.className = 'notif-box ' + IMPROVED_CLASS;
		notifBox.id = 'notif-rocket';
		notifBox.style.display = 'none';
		notifBox.innerHTML = "<img src=\"images/rocket.png\" class=\"image-icon-50\" id=\"notif-rocket-img\" style=\"margin-right: 10px;\"><span class=\"timer\" data-item-display=\"rocketTimer\"></span><span class=\"" + PERCENT_CLASS + "\" data-item-display=\"rocketPercent\"></span>";
		notifBox.title = 'This value is only an estimation based on an average speed of 15km per second.';
		notifArea.appendChild(notifBox);
		var img = notifBox.getElementsByTagName('img').item(0);
		var timerEl = notifBox.getElementsByClassName(TIMER_CLASS).item(0);
		var percentEl = notifBox.getElementsByClassName(PERCENT_CLASS).item(0);
		var AVG_KM_PER_SEC = 15;
		var smoothedTime = 0;

		function updateRocketKm()
		{
			var hideStatic = win.rocketKm < MAX_ROCKET_KM;
			var hideTimer = win.rocketKm <= 0 || !hideStatic;
			notifBox.style.display = hideTimer ? 'none' : '';
			var percent = win.rocketKm / MAX_ROCKET_KM;
			var diff = MAX_ROCKET_KM - win.rocketKm;
			if (win.rocketMoonId < 0)
			{
				percent = 1 - percent;
				diff = win.rocketKm;
			}
			var avgRemainingTime = Math.round(diff / AVG_KM_PER_SEC);
			// be more accurate in the last few seconds (may be the last 2 up to 16 seconds)
			var threshold = smoothedTime < 10 ? 1 : 8;
			if (Math.abs(smoothedTime - avgRemainingTime) >= threshold)
			{
				smoothedTime = avgRemainingTime + 1;
			}
			percentEl.textContent = Math.floor(percent * 100).toString();
		}

		function tickRocketTimer()
		{
			if (smoothedTime > 0)
			{
				smoothedTime = Math.max(smoothedTime - 1, 0);
				timerEl.textContent = format.timer(smoothedTime);
			}
		}
		updateRocketKm();
		observer.add('rocketKm', function (key, oldValue, newValue)
		{
			return updateRocketKm();
		});
		observer.addTick(function ()
		{
			return tickRocketTimer();
		});

		function updateRocketDirection()
		{
			// alternatively: `transform: rotateZ(180deg) rotateY(180deg)`
			var transform = win.rocketMoonId >= 0 ? '' : 'rotate(90deg)';
			img.style.transform = transform;
			var itemBox = document.getElementById('default-item-img-tag-boundRocket');
			if (itemBox)
			{
				itemBox.style.transform = transform;
			}
		}
		updateRocketDirection();
		observer.add('rocketMoonId', function ()
		{
			return updateRocketDirection();
		});
	}

	function getLogTypeList()
	{
		var list = [];
		var els = document.querySelectorAll('input[id^="input-charcoalFoundry-"]');
		for (var i = 0; i < els.length; i++)
		{
			list.push(els[i].id.replace(/^input-charcoalFoundry-/i, ''));
		}
		return list;
	}

	function improveFoundryTimer()
	{
		var el = document.getElementById('notif-charcoalFoundry');
		if (!el)
		{
			return;
		}
		var notifBox = el;
		notifBox.classList.add(IMPROVED_CLASS);
		var timerEl = document.createElement('span');
		timerEl.className = TIMER_CLASS;
		notifBox.appendChild(timerEl);
		var remainingEl = document.createElement('span');
		remainingEl.className = REMAINING_CLASS;
		notifBox.appendChild(remainingEl);
		var logTypeList = null;
		observer.add('charcoalFoundryN', function (key, oldValue, newValue)
		{
			timerEl.textContent = format.timer(win.charcoalFoundryD - win.charcoalFoundryN);
			// init log type list when needed
			if (!logTypeList)
			{
				logTypeList = getLogTypeList();
			}
			var woodAmount = win.charcoalFoundryTotal - win.charcoalFoundryCurrent;
			var coalPerLog = win.getCharcoalPerLog(logTypeList[win.charcoalFoundryLogId - 1]);
			var remainingCoal = woodAmount * (isNaN(coalPerLog) ? 1 : coalPerLog);
			remainingEl.textContent = remainingCoal.toString();
		});
	}

	function init()
	{
		bindNewFormatter();
		applyStyle();
		improveSmeltingTimer();
		improveTreeGrowTimer();
		improveSeedGrowTimer();
		addTabTimer();
		addOilInfo();
		addRocketTimer();
		improveFoundryTimer();
	}
	timer.init = init;
})(timer || (timer = {}));

/**
 * improve smelting dialog
 */
var smelting;
(function (smelting)
{
	smelting.name = 'smelting';
	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;
			win.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 = win.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 = win.getTimerPerBar(win.selectedBar);
		timeEl.textContent = format.timer(timePerBar * num);
	}

	function init()
	{
		prepareAmountInput();
		prepareTimeNeeded();
		var _selectBar = win.selectBar;
		var updateSmeltingRequirements = function (bar, inputElement, inputBarsAmountEl, capacity)
		{
			_selectBar(bar, inputElement, inputBarsAmountEl, capacity);
			var matsArea = document.getElementById('dialogue-furnace-mats-needed');
			if (matsArea)
			{
				matsArea.innerHTML = format.numbersInText(matsArea.innerHTML);
			}
			updateTimeNeeded(inputBarsAmountEl.value);
		};
		win.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 = win.openFurnaceDialogue;
		win.openFurnaceDialogue = function (furnace)
		{
			var capacity = win.getFurnaceCapacity(furnace);
			if (win.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 ((!win.selectedBar || win.selectedBar == 'none') && lastBar != null)
			{
				win.selectedBar = lastBar;
			}
			// update whether requirements are fulfilled
			var barInputId = 'input-furnace-' + split2Words(win.selectedBar, '-').toLowerCase();
			var inputElement = document.getElementById(barInputId);
			if (inputElement && inputBarsAmountEl)
			{
				updateSmeltingRequirements(win.selectedBar, inputElement, inputBarsAmountEl, capacity.toString());
			}
		};
	}
	smelting.init = init;
})(smelting || (smelting = {}));

/**
 * 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 table = document.querySelector('#dialogue-id-fishingRod table');
		if (!table)
		{
			return;
		}
		var rows = table.rows;
		for (var i = 1; i < rows.length; i++)
		{
			var row = rows.item(i);
			var rawFish = row.id.replace('dialogue-fishing-rod-tr-', '');
			var fish = rawFish.replace('raw', '').toLowerCase();
			if (!rawFish || !fish)
			{
				continue;
			}
			var chanceCell = row.cells.item(row.cells.length - 2);
			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 = win.clicksShovel;
		win.clicksShovel = function ()
		{
			_clicksShovel();
			var shovelChance = document.getElementById('dialogue-shovel-chance');
			var titleEl = shovelChance.parentElement;
			var chance = 1 / win.getChanceOfDiggingSand();
			addChanceTooltip('One sand at least every:', chance, 'shovel', titleEl);
		};
		// depends on fishingXp
		var _clicksFishingRod = win.clicksFishingRod;
		win.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]) + String.fromCharCode(160)
						+ 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);
			win.$(requirementCell).tooltip();
		}
	}

	function updateTooltipsOnReinitRecipes(key)
	{
		var capitalKey = capitalize(key);
		var processKey = 'process' + capitalKey + 'Tab';
		var _processTab = win[processKey];
		win[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.description = format.numbersInText(newRecipe.description);
		newRecipe.xp = format.number(recipe.xp);
		return newRecipe;
	}

	function init()
	{
		var _addRecipeToBrewingTable = win.addRecipeToBrewingTable;
		win.addRecipeToBrewingTable = function (brewingRecipe)
		{
			_addRecipeToBrewingTable(prepareRecipeForTable(brewingRecipe));
		};
		var _addRecipeToMagicTable = win.addRecipeToMagicTable;
		win.addRecipeToMagicTable = function (magicRecipe)
		{
			_addRecipeToMagicTable(prepareRecipeForTable(magicRecipe));
		};
		var _addRecipeToCooksBookTable = win.addRecipeToCooksBookTable;
		win.addRecipeToCooksBookTable = function (cooksBookRecipe)
		{
			_addRecipeToCooksBookTable(prepareRecipeForTable(cooksBookRecipe));
		};
		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);
		}
		var _rocketTick = win.rocketTick;
		win.rocketTick = function ()
		{
			_rocketTick();
			var rocketBox = document.getElementById('itembox-rocket');
			if (rocketBox && /^\d+\s*Km$/i.test(rocketBox.textContent || ''))
			{
				rocketBox.innerHTML = format.numbersInText(rocketBox.innerHTML).replace('Km', 'km');
			}
		};
	}
	fixNumbers.init = init;
})(fixNumbers || (fixNumbers = {}));

/**
 * add slider for machines
 */
var machineDialog;
(function (machineDialog)
{
	machineDialog.name = 'machineDialog';
	var $slider;

	function createSlider()
	{
		var br = document.querySelector('#dialogue-machinery-current-total ~ br');
		var parent = br && br.parentElement;
		if (!br || !parent)
		{
			return;
		}
		addStyle("\n#dialogue-id-boundMachinery .ui-slider\n{\n\tmargin: 10px 5px;\n}\n#dialogue-id-boundMachinery .ui-slider:not([data-owned=\"10\"])::after\n{\n\tbackground: hsla(0, 0%, 0%, 1);\n\tborder: 1px solid #c5c5c5;\n\tborder-radius: 3px;\n\tborder-top-left-radius: 0;\n\tborder-bottom-left-radius: 0;\n\tcontent: '';\n\tmargin-left: -3px;\n\tpadding-left: 3px;\n\theight: 100%;\n\twidth: 0%;\n\tposition: absolute;\n\tleft: 100%;\n\ttop: -1px;\n}\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"9\"] { width: calc((100% - 10px - 2px) / 10*9); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"9\"]::after { width: calc(100% / 9 * 1); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"8\"] { width: calc((100% - 10px - 2px) / 10*8); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"8\"]::after { width: calc(100% / 8 * 2); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"7\"] { width: calc((100% - 10px - 2px) / 10*7); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"7\"]::after { width: calc(100% / 7 * 3); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"6\"] { width: calc((100% - 10px - 2px) / 10*6); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"6\"]::after { width: calc(100% / 6 * 4); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"5\"] { width: calc((100% - 10px - 2px) / 10*5); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"5\"]::after { width: calc(100% / 5 * 5); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"4\"] { width: calc((100% - 10px - 2px) / 10*4); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"4\"]::after { width: calc(100% / 4 * 6); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"3\"] { width: calc((100% - 10px - 2px) / 10*3); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"3\"]::after { width: calc(100% / 3 * 7); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"2\"] { width: calc((100% - 10px - 2px) / 10*2); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"2\"]::after { width: calc(100% / 2 * 8); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"1\"] { width: calc((100% - 10px - 2px) / 10*1); }\n#dialogue-id-boundMachinery .ui-slider[data-owned=\"1\"]::after { width: calc(100% / 1 * 9); }\n\t\t");
		var slider = document.createElement('div');
		parent.insertBefore(slider, br);
		$slider = win.$(slider)
			.slider(
			{
				range: 'max'
				, 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);
			var ownedNum = getGameValue(boundMachineKey);
			$slider.slider('option', 'max', ownedNum);
			$slider.get(0).dataset.owned = ownedNum.toString();
		}
	}

	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++)
			{
				win.turnOn(machine, increment);
			}
		}
	}

	function init()
	{
		if (!settings.get(settings.KEY.changeMachineDialog))
		{
			return;
		}
		createSlider();
		var _openMachineryDialogue = win.openMachineryDialogue;
		win.openMachineryDialogue = function (machineType)
		{
			_openMachineryDialogue(machineType);
			updateMax();
			$slider.slider('value', getGameValue(machineType + 'On'));
		};
	}
	machineDialog.init = init;
})(machineDialog || (machineDialog = {}));

/**
 * improve behaviour of amount inputs
 */
var amountInputs;
(function (amountInputs)
{
	amountInputs.name = 'amountInputs';

	function getVialType(recipe)
	{
		return recipe.levelReq < 35 ? 'vialOfWater' : (recipe.levelReq < 65 ? 'largeVialOfWater' : 'hugeVialOfWater');
	}

	function getSimpleMax(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 getMax(recipe)
	{
		var max = getSimpleMax(recipe);
		if (/Potion$/.test(recipe.itemName))
		{
			var vialType = getVialType(recipe);
			max = Math.min(max, getGameValue(vialType));
		}
		return max;
	}

	function ensureNumberInput(idOrEl)
	{
		var numInput = typeof idOrEl === 'string' ? document.getElementById(idOrEl) : idOrEl;
		if (numInput)
		{
			if (numInput.type != 'number' && settings.get(settings.KEY.makeNumberInputs))
			{
				var width = numInput.clientWidth;
				if (width !== 0)
				{
					numInput.style.width = width + 'px';
				}
				numInput.type = 'number';
				numInput.min = '0';
				var onkeyup_1 = numInput.getAttribute('onkeyup');
				if (onkeyup_1)
				{
					numInput.setAttribute('onmouseup', onkeyup_1);
				}
			}
			else if (numInput.type == 'number' && !settings.get(settings.KEY.makeNumberInputs))
			{
				numInput.style.width = '';
				numInput.type = '';
				numInput.removeAttribute('onmouseup');
			}
		}
		return numInput;
	}

	function getCurrentMax(keyId, recipeCollection)
	{
		var keyEl = document.getElementById(keyId);
		if (!keyEl)
		{
			return 0;
		}
		var key = keyEl.value;
		return getMax(recipeCollection[key]);
	};

	function ensureMaxBtn(keyId, inputId, recipeCollection, key)
	{
		var recipe = recipeCollection[key];
		var numInput = ensureNumberInput(inputId);
		var next = numInput && numInput.nextElementSibling;
		var parent = numInput && numInput.parentElement;
		if (numInput && parent)
		{
			if ((!next || next.nodeName !== 'BUTTON') && settings.get(settings.KEY.addMaxBtn))
			{
				var btn = document.createElement('button');
				btn.textContent = 'Max';
				btn.addEventListener('click', function ()
				{
					numInput.value = getCurrentMax(keyId, recipeCollection).toString();
				});
				parent.appendChild(btn);
			}
			else if (next && next.nodeName === 'BUTTON' && !settings.get(settings.KEY.addMaxBtn))
			{
				parent.removeChild(next);
			}
			numInput.value = Math.min(1, getMax(recipe)).toString();
			numInput.select();
		}
	}

	function watchKeepInput(event)
	{
		var itemInput = document.getElementById('npc-sell-item-chosen');
		var numInput = ensureNumberInput('dialogue-input-cmd');
		if (!itemInput || !numInput)
		{
			return;
		}
		var item = itemInput.value;
		var newValue = Math.max(getGameValue(item) - Number(this.value), 0);
		numInput.value = newValue.toString();
	}

	function updateKeepMaxValue(keepInput, init)
	{
		if (init === void 0)
		{
			init = false;
		}
		var itemInput = document.getElementById('npc-sell-item-chosen');
		if (!itemInput)
		{
			return;
		}
		var item = itemInput.value;
		var max = getGameValue(item);
		keepInput.max = max.toString();
		if (init)
		{
			observer.addTick(function ()
			{
				return updateKeepMaxValue(keepInput);
			});
		}
	}

	function ensureKeepInput(item)
	{
		var numInput = ensureNumberInput('dialogue-input-cmd');
		var parent = numInput && numInput.parentElement;
		var next = numInput && numInput.nextElementSibling;
		var nextNext = next && next.nextElementSibling;
		if (next && nextNext && parent)
		{
			if (nextNext.nodeName === 'BR' && settings.get(settings.KEY.addKeepInput))
			{
				var div = document.createElement('div');
				var text = document.createTextNode('Keep: ');
				div.appendChild(text);
				var keepInput = document.createElement('input');
				keepInput.type = 'number';
				keepInput.value = keepInput.min = '0';
				keepInput.max = getGameValue(item).toString();
				keepInput.addEventListener('keyup', watchKeepInput);
				keepInput.addEventListener('mouseup', watchKeepInput);
				updateKeepMaxValue(keepInput, true);
				div.appendChild(keepInput);
				parent.insertBefore(div, nextNext);
			}
			else if (nextNext.nodeName !== 'BR' && !settings.get(settings.KEY.addKeepInput))
			{
				var br = document.createElement('br');
				parent.insertBefore(br, nextNext);
				parent.removeChild(nextNext);
			}
		}
	}

	function init()
	{
		var _multiCraft = win.multiCraft;
		win.multiCraft = function (item)
		{
			_multiCraft(item);
			ensureMaxBtn('dialogue-multicraft-chosen', 'dialogue-multicraft-input', win.craftingRecipes, item);
		};
		var _brew = win.brew;
		win.brew = function (potion)
		{
			_brew(potion);
			ensureMaxBtn('dialogue-potion-chosen', 'dialogue-brewing-input', win.brewingRecipes, potion);
		};
		var _cooksBookInputDialogue = win.cooksBookInputDialogue;
		win.cooksBookInputDialogue = function (food)
		{
			_cooksBookInputDialogue(food);
			ensureMaxBtn('dialogue-cooksBook-chosen', 'dialogue-cooksBook-input', win.cooksBookRecipes, food);
		};
		var _openSellNPCDialogue = win.openSellNPCDialogue;
		win.openSellNPCDialogue = function (item)
		{
			_openSellNPCDialogue(item);
			ensureKeepInput(item);
		};
		var allowedInputs = [
			'dialogue-ashes'
			, 'dialogue-bindDonorCoins'
			, 'dialogue-bonemeal'
			, 'dialogue-bones'
			, 'dialogue-brewing'
			, 'dialogue-buy-item-2'
			, 'dialogue-buyFromMarket'
			, 'dialogue-charcoalFoundry'
			, 'dialogue-consume'
			, 'dialogue-cooksBook'
			, 'dialogue-createArrows'
			, 'dialogue-createFireArrows'
			, 'dialogue-createIceArrows'
			, 'dialogue-furnace'
			, 'dialogue-iceBones'
			, 'dialogue-id-boundHammer'
			, 'dialogue-id-boundPickaxe'
			, 'dialogue-id-cook-food'
			, 'dialogue-id-oven-addheat'
			, 'dialogue-market-chosenpostitem'
			, 'dialogue-multicraft'
			, 'dialogue-oilBarrels'
			, 'dialogue-oilFactory'
			, 'dialogue-sell-item'
			, 'dialogue-stardustCrystals'
			, 'dialogue-wand'
		];
		var _openDialogue = win.openDialogue;
		win.openDialogue = function (id, width, position)
		{
			_openDialogue(id, width, position);
			if (allowedInputs.indexOf(id) === -1
				|| id === 'dialogue-buyFromMarket' && market.detectTedsUI()
				|| id === 'dialogue-market-chosenpostitem' && market.detectTedsUI())
			{
				return;
			}
			var dialog = document.getElementById(id);
			var input = dialog && dialog.querySelector('input[type="text"],input[type="number"]');
			if (!input)
			{
				return;
			}
			ensureNumberInput(input);
		};
	}
	amountInputs.init = init;
})(amountInputs || (amountInputs = {}));

/**
 * improves the top bar
 */
var newTopbar;
(function (newTopbar)
{
	newTopbar.name = 'newTopbar';
	var linkCell, tabCell, infoCell;
	var addQueues = {
		link: []
		, tab: []
		, info: []
	};

	function createPipeNode()
	{
		return document.createTextNode('|');
	}

	function addLinkEntry(el)
	{
		if (!linkCell)
		{
			addQueues.link.push(el);
		}
		else
		{
			linkCell.appendChild(createPipeNode());
			linkCell.appendChild(el);
		}
	}
	newTopbar.addLinkEntry = addLinkEntry;

	function addTabEntry(el)
	{
		if (!tabCell)
		{
			addQueues.tab.push(el);
		}
		else
		{
			tabCell.appendChild(createPipeNode());
			tabCell.appendChild(el);
		}
	}
	newTopbar.addTabEntry = addTabEntry;

	function addInfoEntry(el)
	{
		if (!infoCell)
		{
			addQueues.info.push(el);
		}
		else
		{
			if (infoCell.firstChild)
			{
				infoCell.insertBefore(createPipeNode(), infoCell.firstChild);
				infoCell.insertBefore(el, infoCell.firstChild);
			}
			else
			{
				infoCell.appendChild(createPipeNode());
				infoCell.appendChild(el);
			}
		}
	}
	newTopbar.addInfoEntry = addInfoEntry;

	function init()
	{
		if (!settings.get(settings.KEY.useNewToolbar))
		{
			return;
		}
		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, 5];
		var infoIdx = [6, 7];
		var newRow = table.insertRow(-1);
		linkCell = newRow.insertCell(-1);
		tabCell = newRow.insertCell(-1);
		tabCell.style.textAlign = 'center';
		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(createPipeNode());
			}
			if (el)
			{
				container.appendChild(el);
			}
		}
		var parent = row.parentElement;
		if (parent)
		{
			parent.removeChild(row);
		}
		for (var _i = 0, _a = addQueues.link; _i < _a.length; _i++)
		{
			var el = _a[_i];
			addLinkEntry(el);
		}
		for (var _b = 0, _c = addQueues.tab; _b < _c.length; _b++)
		{
			var el = _c[_b];
			addTabEntry(el);
		}
		for (var _d = 0, _e = addQueues.info; _d < _e.length; _d++)
		{
			var el = _e[_d];
			addInfoEntry(el);
		}
		var _openTab = win.openTab;
		win.openTab = function (newTab)
		{
			var oldTab = win.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';
	var bodyRegex = /(\bbody)(\s|$)/i;

	function addTweakStyle(setting, style)
	{
		if (setting != '')
		{
			var prefix_1 = setting === '' ? '' : 'body.' + setting + ' ';
			style = style
				.replace(/(^\s*|\}\s*)([^\{\}]+)(?=\s*\{)/g, function (wholeMatch, before, rules)
				{
					return before + rules.split(',').map(function (rule)
					{
						if (bodyRegex.test(rule) && setting !== '')
						{
							return rule.replace(bodyRegex, '$1.' + setting + '$2');
						}
						return rule.replace(/^(\s*\n\s*)?/, '$1' + prefix_1);
					}).join(',');
				});
			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=\"150px\"]\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 addAdditionalSkillBars()
	{
		var _loadSkillTabs = win.loadSkillTabs;
		win.loadSkillTabs = function ()
		{
			_loadSkillTabs();
			for (var _i = 0, SKILL_LIST_1 = SKILL_LIST; _i < SKILL_LIST_1.length; _i++)
			{
				var skill = SKILL_LIST_1[_i];
				var unlocked = getGameValue(skill + 'Unlocked') == 1;
				if (!unlocked)
				{
					continue;
				}
				var xp = getGameValue(skill + 'Xp');
				var currentLevelXp = win.getXpNeeded(win.getLevel(xp));
				var nextLevelXp = win.getXpNeeded(win.getLevel(xp) + 1);
				var perc = (xp - currentLevelXp) / (nextLevelXp - currentLevelXp) * 100;
				var progress = document.getElementById('skill-progress-' + skill);
				if (progress)
				{
					progress.style.width = perc + '%';
				}
			}
		};
		// init additional skill bars
		addStyle("\ntd[id^=\"top-bar-level-td-\"]\n{\n\tposition: relative;\n}\n#top-bar-levels .skill-bar\n{\n\tbackground-color: grey;\n\theight: 5px;\n\tposition: absolute;\n\tbottom: 5px;\n\tleft: 60px;\n\tright: 10px;\n}\n#top-bar-levels .skill-bar > .skill-progress\n{\n\tbackground-color: rgb(51, 204, 51);\n\theight: 100%;\n\twidth: 0%;\n}\n\t\t");
		for (var _i = 0, SKILL_LIST_2 = SKILL_LIST; _i < SKILL_LIST_2.length; _i++)
		{
			var skill = SKILL_LIST_2[_i];
			var cell = document.getElementById('top-bar-level-td-' + skill);
			if (!cell)
			{
				continue;
			}
			var levelBar = document.createElement('div');
			levelBar.className = 'skill-bar';
			var progress = document.createElement('div');
			progress.id = 'skill-progress-' + skill;
			progress.className = 'skill-progress';
			levelBar.appendChild(progress);
			cell.appendChild(levelBar);
			// update skill level progress bars on click
			levelBar.addEventListener('click', function ()
			{
				return win.loadSkillTabs();
			});
		}
		win.loadSkillTabs();
	}
	// highlight cooking level requirement when not matched
	function highlightCookinglevel()
	{
		var _cookFoodDialogue = win.cookFoodDialogue;
		win.cookFoodDialogue = function (rawFood)
		{
			_cookFoodDialogue(rawFood);
			var dialog = document.getElementById('dialogue-id-cook-food');
			if (!dialog)
			{
				return;
			}
			var levelReq = document.getElementById('dialogue-cook-levelReq');
			var levelReqLabel = levelReq && levelReq.previousElementSibling;
			if (!levelReq || !levelReqLabel)
			{
				return;
			}
			var fulfilled = win.getCookingLevelReq(rawFood) > win.getLevel(win.cookingXp);
			levelReq.style.color = fulfilled ? 'rgb(204, 0, 0)' : '';
			levelReq.style.fontWeight = fulfilled ? 'bold' : '';
			levelReqLabel.style.color = fulfilled ? 'rgb(204, 0, 0)' : '';
			var ratioEl = document.getElementById('dialogue-cook-ratio');
			if (!ratioEl)
			{
				var cookReqBox = levelReq.parentElement;
				var br = document.createElement('br');
				cookReqBox.appendChild(br);
				var b = document.createElement('b');
				b.innerHTML = "<img src=\"images/steak.png\" class=\"image-icon-20\" title=\"Energy\"> per <img src=\"images/icons/fire.png\" class=\"image-icon-20\" title=\"Heat\">: ";
				cookReqBox.appendChild(b);
				ratioEl = document.createElement('span');
				ratioEl.id = 'dialogue-cook-ratio';
				cookReqBox.appendChild(ratioEl);
			}
			var heat = win.getHeatNeeded(rawFood);
			var energy = win.getEnergyGained(rawFood);
			ratioEl.textContent = format.number(Math.round(energy / heat * 100) / 100);
		};
	}

	function amountStyle()
	{
		var tweakName = 'amount-symbol';
		addTweakStyle(tweakName, "\n.item-box:not(#item-box-special-case-questsUnlocked):not(#item-box-pirate):not(#item-box-miner):not(#item-box-boundPumpjacks):not([onclick^=\"openMachineryDialogue(\"]):not(#item-box-sandCollectorsQuest):not(#item-box-boundFilledBonemealBin) > span[data-item-display]::before\n{\n\tcontent: '\\0D7';\n\tmargin-right: .25rem;\n\tmargin-left: -.5rem;\n}\n\t\t");

		function setAmountSymbolVisibility(init)
		{
			if (init === void 0)
			{
				init = false;
			}
			var show = settings.get(settings.KEY.amountSymbol);
			document.body.classList[show ? 'add' : 'remove'](tweakName);
			if (init)
			{
				settings.observe(settings.KEY.amountSymbol, function ()
				{
					return setAmountSymbolVisibility();
				});
			}
		}
		setAmountSymbolVisibility(true);
	}

	function efficiency()
	{
		var EFFICIENCY_CLASS = 'efficiency';
		addTweakStyle(EFFICIENCY_CLASS, "\nbody\n{\n\tmargin: 0;\n}\nbody > br\n{\n\tdisplay: none;\n}\ntable.top-links\n{\n\tborder-left-width: 0px;\n\tborder-right-width: 0px;\n}\n#game-div\n{\n\tmargin-top: 29px;\n}\n#game-div > table.top-bar,\n#game-div > table.tab-bar,\n#div-chat\n{\n\tborder-width: 0;\n\tmargin-top: 0;\n}\n#game-div > table.top-bar#top-bar-levels\n{\n\tborder-width: 1px 0;\n}\n#notifaction-area\n{\n\tpadding: 0;\n}\nspan.notif-box\n{\n\tmargin: -1px;\n\tmargin-left: 0;\n\tpadding: 5px;\n}\n#game-div > div.tab-container\n{\n\tborder-width: 1px 0 0;\n\tpadding: 0;\n}\ndiv.tab-container > h1.container-title:first-child\n{\n\tdisplay: none;\n}\ndiv.item-box-area,\n#tab-sub-container-farming,\n#tab-sub-container-magic-items\n{\n\tmargin: 0;\n\tpadding: 1px 1px 0 0;\n}\nspan.item-box\n{\n\tmargin: -1px -1px 0 0;\n}\ndiv.tab-container > center > table.table-default,\n#table-crafting-recipe,\n#table-brewing-recipe,\n#table-magic-recipe\n{\n\twidth: calc(100% - 1px);\n}\nul.settings-container,\n#tab-container-crafting .settings-container\n{\n\tmargin: 0;\n}\ndiv.tab-container > br:last-child,\n#tab-sub-container-crafting + br,\n#tab-sub-container-woodcutting + br,\n#tab-sub-container-farming + br,\n#tab-sub-container-brewing + br,\n#tab-sub-container-spells > br,\n#tab-container-shop > br:last-child\n{\n\tdisplay: none;\n}\n\ndiv.side-by-side > div\n{\n\tmargin: 0 !important;\n\twidth: 50%;\n}\n.side-by-side h1.container-title\n{\n\tmargin: 2px;\n}\n.side-by-side h1.container-title + br,\n.side-by-side h1.container-title + br + br,\n.side-by-side input[type=\"image\"] + br,\n#hiscores-table-ingame + br\n{\n\tdisplay: none;\n}\n\ndiv.farming-patch,\ndiv.farming-patch-locked\n{\n\tborder-width: 0;\n\tmargin: 0;\n}\n\n#combat-table-area\n{\n\tborder-width: 0;\n}\n#combat-table-area > tbody > tr > td\n{\n\tborder-width: 0;\n\tborder-right-width: 1px;\n}\n#combat-table-area > tbody > tr > td:last-child\n{\n\tborder-right-width: 0;\n}\n#combat-table-area span.large-button,\n#combat-table-area span.medium-button\n{\n\tmargin: 2px 2px 4px;\n}\n#combat-loot-tables\n{\n\tmargin-top: -3px;\n}\n#combat-loot-tables > table.hiscores-table\n{\n\tmargin: 2px -1px 0 0;\n\twidth: calc(33.33% - 4px);\n}\n#combat-loot-tables > div[style*=\"both\"]\n{\n\theight: 0px;\n}\n\t\t");
		var farmingTab = document.getElementById('tab-container-farming');
		if (farmingTab)
		{
			removeWhitespaceChildNodes(farmingTab);
		}
		var combatSubTab = document.getElementById('tab-sub-container-combat');
		if (combatSubTab)
		{
			removeWhitespaceChildNodes(combatSubTab);
		}

		function checkSetting(init)
		{
			if (init === void 0)
			{
				init = false;
			}
			var show = settings.get(settings.KEY.useEfficiencyStyle);
			document.body.classList[show ? 'add' : 'remove'](EFFICIENCY_CLASS);
			if (init)
			{
				settings.observe(settings.KEY.useEfficiencyStyle, function ()
				{
					return checkSetting();
				});
			}
		}
		checkSetting(true);
	}

	function hardcore()
	{
		if (win.isHardcore != 1)
		{
			return;
		}
		addStyle("\nspan#shop-giant-button-playermarket\n{\n\tbackground-color: gray;\n\tbackground-image: none;\n\tcursor: not-allowed;\n}\n\t\t");
		var marketBtn = document.getElementById('shop-giant-button-playermarket');
		if (marketBtn)
		{
			marketBtn.removeAttribute('onclick');
			marketBtn.setAttribute('title', 'The player market is disabled for hardcore accounts');
		}
	}

	function smallScreen()
	{
		addStyle("\ntable.top-links\n{\n\tz-index: 10;\n}\n\t\t");
	}

	function init()
	{
		tweakOil();
		tweakSelection();
		tweakStardust();
		tweakSkillLevelText();
		tweakFightDialog();
		addAdditionalSkillBars();
		highlightCookinglevel();
		amountStyle();
		efficiency();
		hardcore();
		smallScreen();
	}
	styleTweaks.init = init;
})(styleTweaks || (styleTweaks = {}));

/**
 * add ingame notification boxes
 */
var notifBoxes;
(function (notifBoxes)
{
	notifBoxes.name = 'notifBoxes';

	function addNotifBox(imageKey, itemKey, showFront)
	{
		if (itemKey === void 0)
		{
			itemKey = null;
		}
		if (showFront === void 0)
		{
			showFront = false;
		}
		var notifBox = document.createElement('span');
		notifBox.className = 'notif-box';
		notifBox.id = 'notif-' + imageKey;
		notifBox.style.display = 'none';
		if (showFront)
		{
			notifBox.style.cssFloat = 'left';
		}
		notifBox.innerHTML = "<img src=\"images/" + imageKey + ".png\" class=\"image-icon-50\" id=\"notif-" + imageKey + "-img\">";
		if (itemKey != null)
		{
			notifBox.innerHTML += "<span data-item-display=\"" + itemKey + "\" style=\"margin-left: 10px;\"></span>";
		}
		var notifArea = document.getElementById('notifaction-area');
		if (notifArea)
		{
			notifArea.appendChild(notifBox);
		}
		return notifBox;
	}

	function addWorker()
	{
		var notifBox = addNotifBox('workers', null, true);

		function setVisibility()
		{
			var show = win.workersTimer === 1;
			notifBox.style.display = show ? '' : 'none';
		}
		setVisibility();
		observer.add('workersTimer', function ()
		{
			return setVisibility();
		});
	}

	function init()
	{
		addStyle("\n#notifaction-area\n{\n\tpadding: 5px 0;\n}\nspan.notif-box\n{\n\tfont-size: 1rem;\n\tmargin: 0;\n\tmargin-right: 5px;\n}\ntable.tab-bar\n{\n\tmargin-top: 0;\n}\nspan.notif-box,\ntable.tab-bar td\n{\n\tborder-color: gray;\n}\nspan.notif-box[id^=\"notification-static-\"]\n{\n\tbackground: linear-gradient(rgb(22, 22, 24), rgb(72, 171, 50));\n}\n\t\t");
		// remove pure text nodes
		var notifArea = document.getElementById('notifaction-area');
		if (notifArea)
		{
			removeWhitespaceChildNodes(notifArea);
		}
		addWorker();
	}
	notifBoxes.init = init;
})(notifBoxes || (notifBoxes = {}));


/**
 * extend market
 */
var market;
(function (market)
{
	market.name = 'market';
	// max limit age: 5min
	var MAX_LIMIT_AGE = 5 * 60 * 1e3;
	var PRICE_HISTORY_KEY = 'priceHistory';
	// restrict the size of the history of each item to 2000 entries (for a number comparison: 1 entry per minute, would result in 1440 entries per day)
	var MAX_ENTRIES_PER_ITEM = 2e3;
	var SYNC_URL_REGEX = /^(?:https?:\/\/)?(?:(?:www\.)?myjson\.com\/|api\.myjson\.com\/bins\/)([^\/]+)$/i;
	var detectedTedsUIOnce = false;

	function detectTedsUI()
	{
		return detectedTedsUIOnce = detectedTedsUIOnce || typeof win.changeSetting === 'function';
	}
	market.detectTedsUI = detectTedsUI;
	var priceHistory = store.has(PRICE_HISTORY_KEY) ? store.get(PRICE_HISTORY_KEY) :
	{};
	var getItemColor = function (H, S, L)
	{
		return [
			"hsl(" + H + ", " + S + "%, " + L + "%)"
			, "hsl(" + H + ", " + S + "%, " + (L < 35 ? L + 35 : L - 35) + "%)"
		];
	};
	var itemColor = {
		'blewitMushroom': getItemColor(255, 100, 78)
		, 'bronzeBar': getItemColor(39, 100, 46)
		, 'crystalLeaf': getItemColor(226, 100, 50)
		, 'diamond': getItemColor(186, 76, 82)
		, 'dottedGreenLeaf': getItemColor(92, 63, 19)
		, 'emerald': getItemColor(110, 100, 48)
		, 'goldLeaf': getItemColor(50, 100, 50)
		, 'goldBar': getItemColor(54, 100, 46)
		, 'greenLeaf': getItemColor(92, 63, 28)
		, 'ironBar': getItemColor(44, 11, 46)
		, 'limeLeaf': getItemColor(110, 72, 40)
		, 'promethiumBar': getItemColor(354, 81, 46)
		, 'redMushroom': getItemColor(0, 83, 48)
		, 'ruby': getItemColor(5, 87, 45)
		, 'sapphire': getItemColor(197, 100, 32)
		, 'shrimp': getItemColor(17, 88, 50)
		, 'silverBar': getItemColor(0, 0, 74)
		, 'snapegrass': getItemColor(120, 99, 42)
		, 'stardust': getItemColor(37, 100, 50)
		, 'strangeLeaf': getItemColor(195, 100, 40)
	};
	// use ambassadors to name the categories
	var categoryAmbassador2CategoryName = {
		'stone': 'Ores' // 0
		, 'emptyChisel': 'Crystals' // 1
		, 'bronzeBar': 'Bars' // 2
		, 'dottedGreenLeafSeeds': 'Seeds' // 3
		, 'logs': 'Logs' // 4
		, 'dottedGreenLeaf': 'Ingredients' // 5
		, 'rawShrimp': 'Fish' // 6
		, 'shrimp': 'Food' // 7
		, 'stinger': 'Equipment' // 8
		, 'promethiumHelmetMould': 'Mould' // 9
		, 'essence': 'Magic' // 10
		, 'blueFishingRodOrb': 'Orbs' // 11
		, 'stardust': 'Other' // 12
	};
	var item2Category = new Map();
	var category2Name = new Map();
	var item2Resolver = new Map();
	var itemLimits = new Map();
	var offerPerItem = new Map();
	var offerList = new Array();
	var lastSyncValue = '{}';

	function getSyncUrl()
	{
		if (!settings.get(settings.KEY.syncPriceHistory))
		{
			return null;
		}
		var url = settings.getSub(settings.KEY.syncPriceHistory, 'url');
		var match = url.match(SYNC_URL_REGEX);
		if (!match)
		{
			console.error('URL "' + url + '" does not match the expected pattern: ' + SYNC_URL_REGEX.source);
			return null;
		}
		return 'https://api.myjson.com/bins/' + match[1];
	}

	function integratePriceData(data, responseText)
	{
		var changed = recIntegrate(data, priceHistory);
		lastSyncValue = responseText;
		return changed;

		function recIntegrate(source, target)
		{
			var changed = false;
			if (typeof source !== typeof target)
			{
				console.error('Different data types. Could not integrate data into local price history.\nsource: ' + JSON.stringify(source) + '\ntarget: ' + JSON.stringify(target));
			}
			else if (typeof source === 'object')
			{
				for (var key in source)
				{
					if (source.hasOwnProperty(key))
					{
						if (!target.hasOwnProperty(key))
						{
							target[key] = source[key];
							changed = true;
						}
						else if (recIntegrate(source[key], target[key]))
						{
							changed = true;
						}
					}
				}
			}
			else
			{
				// do nothing and prefer the local value
			}
			return changed;
		}
	}

	function loadPriceHistory()
	{
		var url = getSyncUrl();
		if (url)
		{
			win.$.get(url, function (data, textStatus, jqXHR)
			{
				if (integratePriceData(data, jqXHR.responseText))
				{
					savePriceHistory(true);
				}
			});
		}
	}

	function savePriceHistory(forceWrite)
	{
		if (forceWrite === void 0)
		{
			forceWrite = false;
		}
		for (var itemKey in priceHistory)
		{
			var history_1 = priceHistory[itemKey];
			var timestampList = Object.keys(history_1).sort();
			var i = 0;
			for (var _i = 0, timestampList_1 = timestampList; _i < timestampList_1.length; _i++)
			{
				var timestamp = timestampList_1[_i];
				i++;
				if (i > MAX_ENTRIES_PER_ITEM)
				{
					delete history_1[timestamp];
				}
			}
		}
		store.set(PRICE_HISTORY_KEY, priceHistory);
		var url = getSyncUrl();
		if (url)
		{
			var doPut_1 = function ()
			{
				$.ajax(
				{
					url: url
					, type: 'PUT'
					, data: JSON.stringify(priceHistory)
					, contentType: 'application/json; charset=utf-8'
					, dataType: 'json'
					, success: function (data, textStatus, jqXHR)
					{
						lastSyncValue = jqXHR.responseText;
					}
				});
			};
			if (forceWrite === true)
			{
				doPut_1();
			}
			else
			{
				win.$.get(url, function (data, textStatus, jqXHR)
				{
					if (lastSyncValue !== jqXHR.responseText)
					{
						integratePriceData(data, jqXHR.responseText);
					}
					doPut_1();
				});
			}
		}
	}

	function processMarketData(data)
	{
		var nowKey = now();
		offerPerItem = new Map();
		offerList = new Array();
		if (data != 'NONE')
		{
			offerList = data.split(';').map(function (offerData)
			{
				var values = offerData.split('~');
				var itemId = Number(values[1]);
				var itemKey = win.jsItemArray[itemId];
				var itemName = key2Name(itemKey);
				var categoryId = item2Category.has(itemKey) ? item2Category.get(itemKey) : -1;
				var offer = {
					offerId: Number(values[0])
					, itemId: itemId
					, itemKey: itemKey
					, itemName: itemName
					, categoryId: categoryId
					, amount: Number(values[2])
					, price: Number(values[3])
					, timeLeft: values[4]
					, playerId: Number(values[5])
				};
				if (!offerPerItem.has(itemKey))
				{
					offerPerItem.set(itemKey, []);
				}
				offerPerItem.get(itemKey).push(offer);
				var history = priceHistory[itemKey];
				if (!history)
				{
					history = {};
					priceHistory[itemKey] = history;
				}
				if (!history.hasOwnProperty(nowKey)
					|| history[nowKey] > offer.price)
				{
					history[nowKey] = offer.price;
				}
				return offer;
			});
		}
		savePriceHistory();
	}

	function processItemLimits(itemKey, lowerLimit, upperLimit)
	{
		var limit = {
			timestamp: now()
			, min: lowerLimit
			, max: upperLimit
		};
		itemLimits.set(itemKey, limit);
		if (item2Resolver.has(itemKey))
		{
			var limitArr_1 = [lowerLimit, upperLimit];
			item2Resolver.get(itemKey).forEach(function (resolve)
			{
				return resolve(limitArr_1);
			});
			item2Resolver.delete(itemKey);
			return false;
		}
		return true;
	}

	function showOfferCancelCooldown()
	{
		if (detectTedsUI())
		{
			return;
		}
		addStyle("\n.market-slot-cancel:not([data-cooldown=\"0\"])\n{\n\tbackground: linear-gradient(hsla(12, 40%, 50%, 1), hsla(12, 40%, 40%, 1));\n\tcursor: not-allowed;\n\tposition: relative;\n}\n.market-slot-cancel:not([data-cooldown=\"0\"]):hover\n{\n\tbackground-color: hsla(0, 40%, 50%, 1);\n}\n.market-slot-cancel:not([data-cooldown=\"0\"])::after\n{\n\tcontent: attr(data-cooldown);\n\tposition: absolute;\n\tright: 10px;\n}\n\t\t");

		function slotCooldown(i, init)
		{
			if (init === void 0)
			{
				init = false;
			}
			var cooldownKey = 'marketCancelCooldownSlot' + i;
			var btn = document.getElementById('market-slot-' + i + '-cancel-btn');
			if (btn)
			{
				btn.dataset.cooldown = detectTedsUI() ? '0' : getGameValue(cooldownKey).toString();
			}
			if (init)
			{
				observer.add(cooldownKey, function ()
				{
					return slotCooldown(i);
				});
			}
		}
		for (var i = 1; i <= 3; i++)
		{
			slotCooldown(i, true);
		}
	}

	function addExtraBtns()
	{
		var browseBtn = document.querySelector('.market-browse-button');
		if (!browseBtn)
		{
			return;
		}
		var HISTORY_CLASS = 'local-history';
		var paddingLeft = 30 + 42;
		var paddingRight = 30;
		addStyle("\ncenter > span.market-browse-button,\n#ted-market-ui > span.market-browse-button\n{\n\tposition: relative;\n}\ncenter > span.market-browse-button\n{\n\tpadding-left: " + paddingLeft + "px;\n\tpadding-right: " + paddingRight + "px;\n}\nspan.market-browse-button > span.market-browse-button\n{\n\tpadding: 10px 20px;\n\tposition: absolute;\n\ttop: -1px;\n\tbottom: -1px;\n}\n/*\n*/\nspan.market-browse-button > span.market-browse-button." + HISTORY_CLASS + "\n{\n\tleft: -1px;\n}\nspan.market-browse-button > span.market-browse-button::before\n{\n\tbackground-color: transparent;\n\tbackground-position: center;\n\tbackground-repeat: no-repeat;\n\tbackground-size: 30px;\n\tcontent: '';\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\tbottom: 0;\n\tright: 0;\n}\n/*\n*/\nspan.market-browse-button." + HISTORY_CLASS + "::before\n{\n\tbackground-image: " + icons.getMd(icons.CHART_LINE) + ";\n}\n/*\n*/\n#ted-market-ui > span.market-browse-button > span.market-browse-button." + HISTORY_CLASS + "\n{\n\tborder-left: 0;\n\tleft: 0;\n}\n\t\t");
		var historyBtn = document.createElement('span');
		historyBtn.className = 'market-browse-button ' + HISTORY_CLASS;
		browseBtn.appendChild(historyBtn);
		var historyItemKey = null;
		var _postItemDialogue = win.postItemDialogue;
		win.postItemDialogue = function (offerTypeEl, itemName, inputEl)
		{
			historyItemKey = itemName;
			_postItemDialogue(offerTypeEl, itemName, inputEl);
		};
		var PRICE_HISTORY_DIALOG_ID = 'dialog-price-history';
		var PRICE_HISTORY_ID = 'price-history';
		var PRICE_HISTORY_ITEM_SELECT_ID = 'price-history-item-select';
		addStyle("\n#" + PRICE_HISTORY_DIALOG_ID + "\n{\n\tdisplay: flex;\n\tflex-direction: column;\n}\n#" + PRICE_HISTORY_ID + "\n{\n\tflex-grow: 1;\n\tposition: relative;\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n#" + PRICE_HISTORY_ID + " > div\n{\n\tposition: absolute !important;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n}\n#" + PRICE_HISTORY_ID + " .anychart-credits\n{\n\tdisplay: none;\n}\n\t\t");
		var dialog = document.createElement('dialog');
		dialog.id = PRICE_HISTORY_DIALOG_ID;
		dialog.style.display = 'none';
		dialog.style.overflowX = 'hidden';
		dialog.innerHTML = "\n\t\t<select id=\"" + PRICE_HISTORY_ITEM_SELECT_ID + "\" multiple=\"multiple\" data-placeholder=\"Add items\" style=\"width: 100%\"></select>\n\t\t<div id=\"" + PRICE_HISTORY_ID + "\"></div>\n\t\t";
		document.body.appendChild(dialog);
		var itemSelect = document.getElementById(PRICE_HISTORY_ITEM_SELECT_ID);
		var $itemSelect = win.$(itemSelect);

		function loadScripts(urlList, callback)
		{
			var url = urlList[0];
			if (!url)
			{
				callback && callback();
				return;
			}
			var script = document.createElement('script');
			script.src = url;
			script.onload = function ()
			{
				return loadScripts(urlList.slice(1), callback);
			};
			document.head.appendChild(script);
		}
		var monthArray = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
		var lastHistoryItemKey;
		var itemKey2SeriesId = {};
		var chart;
		var stage;
		// add style for select2
		var style = document.createElement('link');
		style.rel = 'stylesheet';
		style.href = 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css';
		document.head.appendChild(style);
		loadScripts([
			'https://cdn.anychart.com/js/7.14.3/anychart.min.js'
			, 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js'
		], function ()
		{
			chart = win.anychart.area();
			// pass the container id, chart will be displayed there
			stage = anychart.graphics.create(PRICE_HISTORY_ID);
			chart.container(stage);
			var tooltip = chart.tooltip();
			tooltip.displayMode('union');
			tooltip.format(passThis(function (context)
			{
				var name = context.seriesName || 'Price';
				return name + ': ' + (isNaN(context.value) ? '-' : format.number(context.value));
			}));
			tooltip.titleFormat(passThis(function (context)
			{
				var d = new Date(context.x);
				return monthArray[d.getMonth()] + ' ' + d.getDate() + ' @ ' + zeroPadLeft(d.getHours()) + ':' + zeroPadLeft(d.getMinutes()) + ':' + zeroPadLeft(d.getSeconds());
			}));
			var valueAxis = chart.yAxis();
			valueAxis.title().text('Price').enabled(true);
			valueAxis.labels().format(passThis(function (context)
			{
				return format.number(context.value);
			}));
			var timeAxis = chart.xAxis();
			timeAxis.labels().format(passThis(function (context)
			{
				var d = new Date(context.tickValue);
				return d.getDate() + '. ' + monthArray[d.getMonth()];
			}));
			var timeScale = win.anychart.scales.dateTime();
			var ticks = timeScale.ticks();
			ticks.interval(0, 0, 1);
			chart.xScale(timeScale);
			var timeScroller = chart.xScroller();
			timeScroller.enabled(true);
			chart.animation(true, 300);
			chart.legend(true);
		});
		historyBtn.addEventListener('click', function (event)
		{
			event.preventDefault();
			event.stopPropagation();
			var height = Math.floor(.66 * window.innerHeight);
			var width = Math.min(Math.floor(.66 * window.innerWidth), window.innerWidth - 30);
			win.$(dialog).dialog(
			{
				title: 'Price history from local data'
				, height: height
				, width: width
			});
			dialog.style.height = (height) + 'px';
			var itemKeyList = Object.keys(priceHistory).sort();
			itemSelect.innerHTML = "";
			var category2OptGroup = {};

			function ensureOptGroup(categoryId)
			{
				var optGroup = category2OptGroup[categoryId];
				if (!optGroup)
				{
					optGroup = document.createElement('optgroup');
					optGroup.label = category2Name.get(categoryId) || 'Stuff';
					itemSelect.appendChild(optGroup);
					category2OptGroup[categoryId] = optGroup;
				}
				return optGroup;
			}
			var categoryList = Array.from(category2Name.keys()).map(function (id)
			{
				return Number(id);
			}).sort();
			for (var _i = 0, categoryList_1 = categoryList; _i < categoryList_1.length; _i++)
			{
				var categoryId = categoryList_1[_i];
				ensureOptGroup(categoryId);
			}
			var itemKey2EnabledFn = {};

			function replaceEnabled(itemKey, series)
			{
				var _enabled = series.enabled.bind(series);
				itemKey2EnabledFn[itemKey] = _enabled;
				series.enabled = function (value)
				{
					if (value !== undefined)
					{
						var itemList = $itemSelect.val();
						var index = itemList.indexOf(itemKey);
						if (index !== -1)
						{
							itemList.splice(index, 1);
						}
						else
						{
							itemList.push(itemKey);
						}
						$itemSelect.val(itemList).trigger('change');
					}
					return _enabled(value);
				};
			}
			var min = Number.MAX_SAFE_INTEGER;
			var max = 0;
			var enabledSeriesList = [];
			var _loop_1 = function (itemKey)
			{
				if (!itemColor[itemKey])
				{
					var baseColor = colorGenerator.getRandom(
					{
						format: 'hslArray'
					});
					var borderColor = baseColor.slice(0);
					if (borderColor[2] < 35)
					{
						borderColor[2] += 35;
					}
					else
					{
						borderColor[2] -= 35;
					}
					itemColor[itemKey] = [
						"hsl(" + baseColor[0] + ", " + baseColor[1] + "%, " + baseColor[2] + "%)"
						, "hsl(" + borderColor[0] + ", " + borderColor[1] + "%, " + borderColor[2] + "%)"
					];
				}
				var history_2 = priceHistory[itemKey];
				var keyList = Object.keys(history_2).sort();
				var data = keyList
					.map(function (n)
					{
						return ([
							Number(n)
							, history_2[n]
						]);
					});
				min = Math.min(Number(keyList[0]), min);
				max = Math.max(Number(keyList[keyList.length - 1]), max);
				var id = itemKey2SeriesId[itemKey];
				var series = void 0;
				if (id != null)
				{
					series = chart.getSeries(id);
					series.data(data);
				}
				else
				{
					var hoverifyColor = function (hslColor)
					{
						return (
						{
							color: hslColor
							, opacity: .8
						});
					};
					series = chart.area(data);
					itemKey2SeriesId[itemKey] = series.id();
					series.name(key2Name(itemKey));
					var bgColor = itemColor[itemKey][0];
					var strokeColor = itemColor[itemKey][1];
					series.fill(bgColor);
					var bgColorHover = hoverifyColor(bgColor);
					series.selectFill(bgColorHover);
					series.hoverFill(bgColorHover);
					series.stroke(strokeColor, 2);
					var strokeColorHover = hoverifyColor(strokeColor);
					series.hoverStroke(strokeColorHover, 2);
					series.selectStroke(strokeColorHover, 2);
					var markerOptions = {
						fill: strokeColor
						, size: 5
						, type: 'circle'
					};
					series.hoverMarkers(markerOptions);
					series.selectMarkers(markerOptions);
					replaceEnabled(itemKey, series);
				}
				if (lastHistoryItemKey !== historyItemKey)
				{
					if (itemKey === historyItemKey)
					{
						enabledSeriesList.push(series);
					}
					series.enabled(false);
				}
				var categoryId = item2Category.has(itemKey) ? item2Category.get(itemKey) : -1;
				var optGroup = ensureOptGroup(categoryId);
				var option = document.createElement('option');
				option.value = itemKey;
				option.textContent = key2Name(itemKey);
				optGroup.appendChild(option);
			};
			for (var _a = 0, itemKeyList_1 = itemKeyList; _a < itemKeyList_1.length; _a++)
			{
				var itemKey = itemKeyList_1[_a];
				_loop_1(itemKey);
			}
			stage.listenOnce('renderfinish', function ()
			{
				enabledSeriesList.forEach(function (series)
				{
					return series.enabled(true);
				});
			});
			var timeScale = chart.xScale();
			timeScale.minimum(min);
			timeScale.maximum(max);
			var timeZoom = chart.xZoom();
			var threeDaysLong = 3 * 24 * 60 * 60 * 1e3;
			timeZoom.setToValues(Math.max(max - threeDaysLong, min), max);
			// call the chart draw() method to initiate chart display
			chart.draw(true);
			// init item select
			if ($itemSelect.data('select2'))
			{
				$itemSelect.select2('destroy');
			}
			$itemSelect.select2();

			function getEnabledFn(event)
			{
				var data = event.params.data;
				var itemKey = data.id;
				var enabledFn = itemKey2EnabledFn[itemKey];
				if (enabledFn)
				{
					return enabledFn;
				}
				else
				{
					var id = itemKey2SeriesId[itemKey];
					var series = chart.getSeries(id);
					return series.enabled.bind(series);
				}
			}
			$itemSelect.on('select2:select', function (event)
			{
				getEnabledFn(event)(true);
			});
			$itemSelect.on('select2:unselect', function (event)
			{
				getEnabledFn(event)(false);
				// close select menu when it was closed before an element has been removed
				var openBefore = $itemSelect.data('select2').$container.hasClass('select2-container--open');
				setTimeout(function ()
				{
					if (!openBefore && $itemSelect.data('select2').$container.hasClass('select2-container--open'))
					{
						$itemSelect.select2('close');
					}
				});
			});
			lastHistoryItemKey = historyItemKey;
		});
	}
	var categoryList = [-1];
	var itemListPerCategory = new Map();

	function improveOfferList()
	{
		var itemArea = document.getElementById('dialogue-market-items-area');
		if (itemArea)
		{
			var children = itemArea.children;
			for (var i = 1; i < children.length; i++)
			{
				var categoryId = i - 1;
				categoryList.push(categoryId);
				var box = children.item(i);
				var inputs = box.children;
				for (var j = 0; j < inputs.length; j++)
				{
					var match = inputs.item(j).src.match(/images\/([^\/]+)\.(?:png|jpe?g|gif)/);
					if (!match)
					{
						continue;
					}
					var itemKey = match[1];
					item2Category.set(itemKey, categoryId);
					if (categoryAmbassador2CategoryName[itemKey])
					{
						category2Name.set(categoryId, categoryAmbassador2CategoryName[itemKey]);
					}
					if (!itemListPerCategory.has(categoryId))
					{
						itemListPerCategory.set(categoryId, []);
					}
					itemListPerCategory.get(categoryId).push(itemKey);
				}
			}
		}
	}

	function getItemLimit(itemKey)
	{
		// TODO: combine list of offers with min/max-boundries
		var limit = itemLimits.get(itemKey);
		if (limit && limit.timestamp > now() - MAX_LIMIT_AGE)
		{
			return Promise.resolve([limit.min, limit.max]);
		}
		else if (!win.jsTradalbeItems.hasOwnProperty(itemKey))
		{
			return Promise.resolve([0, 0]);
		}
		return new Promise(function (resolve, reject)
		{
			win.postItemDialogue(
			{
				value: 'sell'
			}, itemKey, null);
			if (!item2Resolver.has(itemKey))
			{
				item2Resolver.set(itemKey, []);
			}
			item2Resolver.get(itemKey).push(resolve);
			setTimeout(function ()
			{
				return reject(new Error('Request timed out'));
			}, 30e3);
		});
	}

	function calcMarketValue(items)
	{
		var itemKeyList = Object.keys(items);
		return Promise.all(itemKeyList.map(function (key)
			{
				return getItemLimit(key);
			}))
			.then(function (limitList)
			{
				var sum = [0, 0];
				for (var i = 0; i < itemKeyList.length; i++)
				{
					var amount = items[itemKeyList[i]];
					var limit = limitList[i];
					sum[0] += amount * limit[0];
					sum[1] += amount * limit[1];
				}
				return sum;
			});
	}
	market.calcMarketValue = calcMarketValue;

	function init()
	{
		showOfferCancelCooldown();
		addExtraBtns();
		improveOfferList();
		var _chosenPostItemDialogue = win.chosenPostItemDialogue;
		win.chosenPostItemDialogue = function (itemName, lowerLimit, upperLimit)
		{
			if (processItemLimits(itemName, Number(lowerLimit), Number(upperLimit)))
			{
				_chosenPostItemDialogue(itemName, lowerLimit, upperLimit);
			}
		};
		var _addToPlayerMarket = win.addToPlayerMarket;
		win.addToPlayerMarket = function (data)
		{
			processMarketData(data);
			_addToPlayerMarket(data);
		};
		loadPriceHistory();
		// delay (debounce) sending the request for 3s
		var startDebouncedRequest = debounce(function ()
		{
			return loadPriceHistory();
		}, 3e3);
		settings.observe(settings.KEY.syncPriceHistory, function ()
		{
			return startDebouncedRequest();
		});
		settings.observeSub(settings.KEY.syncPriceHistory, 'url', function ()
		{
			return startDebouncedRequest();
		});
	}
	market.init = init;
})(market || (market = {}));

var combat;
(function (combat)
{
	combat.name = 'combat';
	var LOOT_TABLE_URL = '/wiki/combat.php';
	var COMBAT_LOOT_TABLES_ID = 'combat-loot-tables';
	var CAT_2_NAME = {
		'always': 'Always'
		, 'common': 'Common'
		, 'uncommon': 'Uncommon'
		, 'rare': 'Rare'
		, 'veryrare': 'Very Rare'
	};
	var lootInfoInitialized = false;
	var lootInfo = {};

	function readLootTable(table)
	{
		var monsterImg = table.getElementsByTagName('img').item(0);
		var src = monsterImg.getAttribute('src') || '';
		var monsterId = src.replace(/.+npc\/(\d+)\.png$/, '$1');
		var info = {
			always: []
			, common: []
			, uncommon: []
			, rare: []
			, veryrare: []
		};
		for (var i = 2; i < table.rows.length; i++)
		{
			var row = table.rows.item(i);
			var match = row.cells.item(0).innerHTML.match(/images\/(.+)\.png/);
			if (!match)
			{
				console.error('no item key found:', row.innerHTML);
				continue;
			}
			var itemKey = match[1];
			var amount = row.cells.item(1).textContent || '';
			var rarityCategory = row.cells.item(2).className;
			if (!info.hasOwnProperty(rarityCategory))
			{
				console.error('unknown rarity category:', rarityCategory);
				continue;
			}
			info[rarityCategory].push(
			{
				key: itemKey
				, amount: amount.split(' - ').map(function (s)
				{
					return Number(s.replace(/\D/g, ''));
				})
			});
		}
		lootInfo[monsterId] = info;
		lootInfoInitialized = true;
	}

	function updateLootTableInfo()
	{
		return doGet(LOOT_TABLE_URL)
			.then(function (response)
			{
				var parser = new DOMParser();
				var doc = parser.parseFromString(response, 'text/html');
				var tables = doc.getElementsByTagName('table');
				for (var i = 0; i < tables.length; i++)
				{
					readLootTable(tables.item(i));
				}
				return lootInfo;
			})
			.then(function (info)
			{
				setLootTableTabContent(info);
			});
	}

	function addLootTableTab()
	{
		var subTabContainer = document.getElementById('tab-sub-container-combat');
		var itemContainer = document.getElementById('tab-sub-container-combat-large-btns');
		var afterEl = itemContainer && itemContainer.previousElementSibling;
		if (!subTabContainer || !afterEl)
		{
			return;
		}
		addStyle("\nspan.medium-button.active\n{\n\tbackground: hsla(109, 55%, 43%, 1);\n\tcursor: not-allowed;\n}\n#combat-table-area:not([style$=\"auto;\"]) > tbody > tr > td:last-child\n{\n\twidth: 100%;\n}\n#" + COMBAT_LOOT_TABLES_ID + " td.always\n{\n\tbackground-color: #ccffff;\n}\n#" + COMBAT_LOOT_TABLES_ID + " td.common\n{\n\tbackground-color: #ccffcc;\n}\n#" + COMBAT_LOOT_TABLES_ID + " td.uncommon\n{\n\tbackground-color: #ffffcc;\n}\n#" + COMBAT_LOOT_TABLES_ID + " td.rare\n{\n\tbackground-color: #ffcc99;\n}\n#" + COMBAT_LOOT_TABLES_ID + " td.veryrare\n{\n\tbackground-color: #ff9999;\n}\n\n#" + COMBAT_LOOT_TABLES_ID + " table.hiscores-table\n{\n\tfloat: left;\n\tmargin: 0 10px;\n\twidth: calc(33.3% - 20px);\n}\n#" + COMBAT_LOOT_TABLES_ID + " table.hiscores-table img.image-icon-50\n{\n\twidth: auto;\n}\n\t\t");
		var REFRESH_LOOT_TABLE_ID = 'refresh-loot-table';
		var subTab = document.createElement('span');
		subTab.className = 'large-button';
		subTab.innerHTML = "<img class=\"image-icon-50\" src=\"images/combatDropTable.png\" style=\"filter: grayscale(100%);\">Loot";
		subTab.addEventListener('click', function ()
		{
			var _confirmDialogue = win.confirmDialogue;
			win.confirmDialogue = function () {};
			win.clicksOpenDropTable();
			win.confirmDialogue = _confirmDialogue;
			win.openSubTab('loot');
		});

		function setLootTabVisibility()
		{
			var show = settings.get(settings.KEY.showLootTab);
			subTab.style.display = show ? '' : 'none';
			var dropTableItemBox = document.getElementById('item-box-combatDropTable');
			if (dropTableItemBox)
			{
				dropTableItemBox.style.display = show ? 'none' : '';
			}
			if (show && !lootInfoInitialized)
			{
				updateLootTableInfo();
			}
		}
		setLootTabVisibility();
		settings.observe(settings.KEY.showLootTab, function ()
		{
			return setLootTabVisibility();
		});
		subTabContainer.insertBefore(subTab, afterEl);
		var combatSubTab = document.getElementById('tab-sub-container-combat');
		var equipSubTab = document.getElementById('tab-sub-container-equip');
		var spellsSubTab = document.getElementById('tab-sub-container-spells');
		var subPanelContainer = combatSubTab.parentElement;
		var lootSubTab = document.createElement('div');
		lootSubTab.id = 'tab-sub-container-loot';
		lootSubTab.style.display = 'none';
		lootSubTab.innerHTML = "<span onclick=\"openTab('combat')\" class=\"medium-button\"><img class=\"image-icon-30\" src=\"images/icons/back.png\"> back</span>\n\t\t<span id=\"" + REFRESH_LOOT_TABLE_ID + "\" class=\"medium-button\">refresh</span>\n\t\t<div id=\"" + COMBAT_LOOT_TABLES_ID + "\">Loading...</div>";
		subPanelContainer.appendChild(lootSubTab);
		var refreshBtn = document.getElementById(REFRESH_LOOT_TABLE_ID);
		if (refreshBtn)
		{
			refreshBtn.addEventListener('click', function ()
			{
				if (refreshBtn.classList.contains('active'))
				{
					return;
				}
				refreshBtn.classList.add('active');
				updateLootTableInfo()
					.then(function ()
					{
						return refreshBtn.classList.remove('active');
					})
					.catch(function ()
					{
						return refreshBtn.classList.remove('active');
					});
			});
		}
		var _openSubTab = win.openSubTab;
		win.openSubTab = function (tab)
		{
			combatSubTab.style.display = 'none';
			equipSubTab.style.display = 'none';
			spellsSubTab.style.display = 'none';
			lootSubTab.style.display = 'none';
			_openSubTab(tab);
			if (tab == 'loot')
			{
				lootSubTab.style.display = 'block';
			}
		};
		var _loadDefaultCombatTab = win.loadDefaultCombatTab;
		win.loadDefaultCombatTab = function ()
		{
			_loadDefaultCombatTab();
			lootSubTab.style.display = 'none';
		};
	}

	function setLootTableTabContent(lootInfo)
	{
		var combatTableWrapper = document.getElementById(COMBAT_LOOT_TABLES_ID);
		if (!combatTableWrapper)
		{
			return;
		}
		combatTableWrapper.innerHTML = "";
		for (var monsterId in lootInfo)
		{
			var info = lootInfo[monsterId];
			var monsterNum = Number(monsterId);
			if (monsterNum > 1 && monsterNum % 3 === 1)
			{
				var lineBreak = document.createElement('div');
				lineBreak.style.clear = 'both';
				lineBreak.innerHTML = "<br>";
				combatTableWrapper.appendChild(lineBreak);
			}
			var table = document.createElement('table');
			table.className = 'hiscores-table';
			var imgRow = table.insertRow(-1);
			imgRow.innerHTML = "<td colspan=\"3\">\n\t\t\t\t<img src=\"../images/hero/npc/" + monsterId + ".png\" class=\"image-icon-50\">\n\t\t\t</td>";
			var headerRow = table.insertRow(-1);
			headerRow.innerHTML = "<th>Item</th><th>Amount</th><th>Rarity</th>";
			for (var rarityCategory in info)
			{
				var itemList = info[rarityCategory];
				for (var i = 0; i < itemList.length; i++)
				{
					var item = itemList[i];
					var row = table.insertRow(-1);
					row.innerHTML = "<td><img src=\"../images/" + item.key + ".png\" class=\"image-icon-40\"></td><td>" + item.amount.map(function (n)
					{
						return format.number(n);
					}).join(' - ') + "</td><td class=\"" + rarityCategory + "\">" + CAT_2_NAME[rarityCategory] + "</td>";
				}
			}
			combatTableWrapper.appendChild(table);
		}
	}

	function init()
	{
		addLootTableTab();
		if (settings.get(settings.KEY.showLootTab))
		{
			updateLootTableInfo();
		}
	}
	combat.init = init;
})(combat || (combat = {}));

/**
 * farming improvements
 */
var farming;
(function (farming)
{
	farming.name = 'farming';
	var SEED_INFO_REGEX = {
		minLevel: />\s*Level:/
		, stopsDyingLevel: />\s*Stops\s+Dying\s+Level:/
		, bonemeal: />\s*Bonemeal:/
		, woodcuttingLevel: />\s*Woodcutting\s+Level:/
	};
	var seedInfoSpans = {};
	var seedInfo = {};
	var checkInfo = {
		bonemeal: function (amount)
		{
			return amount <= win.bonemeal;
		}
		, minLevel: function (level)
		{
			return level <= win.getLevel(win.farmingXp);
		}
		, stopsDyingLevel: function (level)
		{
			return level <= win.getLevel(win.farmingXp);
		}
		, woodcuttingLevel: function (level)
		{
			return level <= win.getLevel(win.woodcuttingXp);
		}
	};
	var RED = 'rgb(204, 0, 0)';

	function addBetterStyle()
	{
		var CLASS_NAME = 'seedHighlight';
		addStyle("\n#dialogue-plant-farming input.input-img-farming-patch-dialogue-seeds\n{\n\tpadding: 2px 4px;\n}\n#dialogue-plant-farming #dialogue-plant-grassSeeds\n{\n\theight: 75px;\n\tpadding: 0;\n\twidth: 75px;\n}\n#dialogue-plant-farming #dialogue-plant-treeSeeds,\n#dialogue-plant-farming #dialogue-plant-oakTreeSeeds,\n#dialogue-plant-farming #dialogue-plant-willowTreeSeeds,\n#dialogue-plant-farming #dialogue-plant-mapleTreeSeeds\n{\n\tpadding: 0;\n}\n\nbody." + CLASS_NAME + " #dialogue-plant-farming input.input-img-farming-patch-dialogue-seeds:hover\n{\n\tbackground-color: transparent;\n\tborder: 1px solid black;\n\tmargin: -1px;\n\ttransform: scale(1.1);\n}\n\t\t");
		// seedHighlight
		function updateHoverStyle()
		{
			document.body.classList[settings.get(settings.KEY.highlightUnplantableSeed) ? 'add' : 'remove'](CLASS_NAME);
		}
		updateHoverStyle();
		settings.observe(settings.KEY.highlightUnplantableSeed, function ()
		{
			return updateHoverStyle();
		});
	}

	function readSeedInfo(seedName, tooltipEl)
	{
		var spans = tooltipEl.querySelectorAll(':scope > span');
		var infoSpans = {
			bonemeal: null
			, minLevel: null
			, stopsDyingLevel: null
			, woodcuttingLevel: null
		};
		var info = {
			bonemeal: 0
			, minLevel: 0
			, stopsDyingLevel: 0
			, woodcuttingLevel: 0
		};
		var i = 2;
		for (var key in SEED_INFO_REGEX)
		{
			if (SEED_INFO_REGEX[key].test(spans[i].innerHTML))
			{
				infoSpans[key] = spans.item(i);
				var textNode = spans.item(i).lastChild;
				info[key] = parseInt(textNode.textContent || '', 10);
				i++;
			}
		}
		seedInfoSpans[seedName] = infoSpans;
		seedInfo[seedName] = info;
	}

	function checkSpan(span, fulfilled)
	{
		span.style.color = fulfilled ? '' : RED;
		span.style.fontWeight = fulfilled ? '' : 'bold';
	}

	function checkSeedInfo(seedName, init)
	{
		if (init === void 0)
		{
			init = false;
		}
		var highlight = settings.get(settings.KEY.highlightUnplantableSeed);
		var info = seedInfo[seedName];
		var spans = seedInfoSpans[seedName];
		var canBePlanted = true;
		for (var key in info)
		{
			var span = spans[key];
			if (span)
			{
				var fulfilled = checkInfo[key](info[key]);
				checkSpan(span, !highlight || fulfilled);
				canBePlanted = !highlight || canBePlanted && (key == 'stopsDyingLevel' || fulfilled);
			}
		}
		var itemBox = document.getElementById('item-box-' + seedName);
		if (itemBox)
		{
			itemBox.style.opacity = (!highlight || canBePlanted) ? '' : '.5';
		}
		var plantInput = document.getElementById('dialogue-plant-' + seedName);
		if (plantInput)
		{
			plantInput.style.backgroundColor = (!highlight || canBePlanted) ? '' : 'hsla(0, 100%, 50%, .5)';
		}
		if (init)
		{
			observer.add('bonemeal', function ()
			{
				return checkSeedInfo(seedName);
			});
			observer.add('farmingXp', function ()
			{
				return checkSeedInfo(seedName);
			});
			observer.add('woodcuttingXp', function ()
			{
				return checkSeedInfo(seedName);
			});
			settings.observe(settings.KEY.highlightUnplantableSeed, function ()
			{
				return checkSeedInfo(seedName);
			});
		}
	}

	function getSeedInfo(seedName)
	{
		return seedInfo[seedName];
	}
	farming.getSeedInfo = getSeedInfo;

	function init()
	{
		addBetterStyle();
		// read all seed information
		var tooltipEls = document.querySelectorAll('div[id^="tooltip-"][id$="Seeds"]');
		for (var i = 0; i < tooltipEls.length; i++)
		{
			var tooltipEl = tooltipEls[i];
			var seedName = tooltipEl.id.replace(/^tooltip-/, '');
			readSeedInfo(seedName, tooltipEl);
			checkSeedInfo(seedName, true);
		}
	}
	farming.init = init;
})(farming || (farming = {}));

/**
 * general features which doesn't really belong anywhere
 */
var general;
(function (general)
{
	general.name = 'general';
	// disable the drink button for 3 seconds
	var DRINK_DELAY = 3;

	function getSentBoat()
	{
		for (var i = 0; i < BOAT_LIST.length; i++)
		{
			if (getGameValue(BOAT_LIST[i] + 'Timer') > 0)
			{
				return BOAT_LIST[i];
			}
		}
		return null;
	}

	function checkBoat(boat)
	{
		var boatDialog = null;
		var sendBtn = null;
		var initiatedDialogs = document.querySelectorAll('div[role="dialog"]');
		for (var i = 0; i < initiatedDialogs.length; i++)
		{
			var dialog = initiatedDialogs[i];
			if (dialog.style.display !== 'none')
			{
				var btn = dialog.querySelector('input[type="button"][value="Send Boat"]');
				if (btn)
				{
					sendBtn = btn;
					boatDialog = dialog;
					break;
				}
			}
		}
		if (!boatDialog || !sendBtn)
		{
			return;
		}
		var smallboxes = boatDialog.querySelectorAll('div.basic-smallbox');
		var baitBox = smallboxes[0];
		var runningBox = smallboxes[1];
		if (smallboxes.length === 1)
		{
			runningBox = document.createElement('div');
			runningBox.className = 'basic-smallbox';
			runningBox.style.display = 'none';
			var parent_1 = baitBox.parentElement;
			var next = baitBox.nextElementSibling;
			if (parent_1)
			{
				if (next)
				{
					parent_1.insertBefore(runningBox, next);
				}
				else
				{
					parent_1.appendChild(runningBox);
				}
			}
		}
		var sentBoat = getSentBoat();
		baitBox.style.display = sentBoat !== null ? 'none' : '';
		runningBox.style.display = sentBoat !== null ? '' : 'none';
		// just in case Smitty changes this game mechanic somehow, don't disable the button:
		// sendBtn.disabled = sentBoat !== null;
		sendBtn.style.color = sentBoat !== null ? 'gray' : '';
		win.$(boatDialog).on('dialogclose', function ()
		{
			if (sendBtn)
			{
				sendBtn.style.color = '';
			}
		});
		if (sentBoat === boat)
		{
			runningBox.innerHTML = "<b>Returning in:</b> <span data-item-display=\"" + boat + "Timer\">" + format.timer(getGameValue(boat + 'Timer')) + "</span>";
		}
		else if (sentBoat !== null)
		{
			runningBox.innerHTML = "Wait for the other boat to return.";
		}
		else
		{
			var enoughBaitAndCoal = win.fishingBait >= win.fishingBaitCost(boat)
				&& (boat !== 'steamBoat' || win.charcoal >= 300);
			baitBox.style.color = enoughBaitAndCoal ? '' : 'red';
		}
	}

	function initBoatDialog()
	{
		var _clicksBoat = win.clicksBoat;
		win.clicksBoat = function (boat)
		{
			_clicksBoat(boat);
			checkBoat(boat);
		};
		var _doCommand = win.doCommand;
		win.doCommand = function (data)
		{
			_doCommand(data);
			if (data.startsWith('RUN_FUNC=SAIL_BOAT_WIND'))
			{
				checkBoat('sailBoat');
			}
		};
	}
	var potionDrinkEnable = null;
	var POTION_ACTIVE_HTML = "<br>It's already active.";

	function updateDialogEls(timerKey, dialog, close)
	{
		if (close === void 0)
		{
			close = false;
		}
		var timer = getGameValue(timerKey);
		var showActive = settings.get(settings.KEY.usePotionWarning) && timer > 0 && !close;
		var confirmText = document.getElementById('dialogue-confirm-text');
		var br = confirmText && confirmText.nextElementSibling;
		if (confirmText && br)
		{
			if (showActive)
			{
				confirmText.innerHTML += POTION_ACTIVE_HTML;
			}
			else
			{
				confirmText.innerHTML = confirmText.innerHTML.replace(POTION_ACTIVE_HTML, '');
			}
			br.style.display = showActive ? 'none' : '';
		}
		var confirmBtn = document.getElementById('dialogue-confirm-yes');
		if (confirmBtn && showActive)
		{
			confirmBtn.disabled = true;
			var i_1 = DRINK_DELAY;
			var updateValue_1 = function ()
			{
				confirmBtn.value = 'Drink' + (i_1 > 0 ? ' (' + i_1 + ')' : '');
				if (i_1 === 0)
				{
					potionDrinkEnable && potionDrinkEnable();
				}
				else
				{
					i_1--;
				}
			};
			var countDownInterval_1;
			var dialogClose_1 = function ()
			{
				return potionDrinkEnable && potionDrinkEnable();
			};
			potionDrinkEnable = function ()
			{
				potionDrinkEnable = null;
				win.$(dialog).off('dialogclose', dialogClose_1);
				countDownInterval_1 && clearInterval(countDownInterval_1);
				confirmBtn.disabled = false;
				confirmBtn.value = 'Drink';
			};
			updateValue_1();
			countDownInterval_1 = setInterval(function ()
			{
				return updateValue_1();
			}, 1e3);
			win.$(dialog).on('dialogclose', dialogClose_1);
		}
		else if (!showActive)
		{
			potionDrinkEnable && potionDrinkEnable();
		}
	}

	function checkPotionActive(potion)
	{
		var dialog = document.getElementById('dialogue-confirm');
		var parent = dialog && dialog.parentElement;
		if (!dialog || !parent || parent.style.display === 'none')
		{
			return;
		}
		var timerKey = potion + 'Timer';
		updateDialogEls(timerKey, dialog);
		var fn = observer.add(timerKey, function (key, oldValue, newValue)
		{
			if (oldValue < newValue && oldValue === 0
				|| oldValue > newValue && newValue === 0)
			{
				updateDialogEls(timerKey, dialog);
			}
		});
		win.$(dialog).on('dialogclose', function ()
		{
			updateDialogEls(timerKey, dialog, true);
			observer.remove(timerKey, fn);
		});
	}

	function initPotionDialog()
	{
		var _confirmDialogue = win.confirmDialogue;
		win.confirmDialogue = function (width, text, btn1Text, btn2Text, cmd)
		{
			potionDrinkEnable && potionDrinkEnable();
			_confirmDialogue(width, text, btn1Text, btn2Text, cmd);
		};
		var _clicksPotion = win.clicksPotion;
		win.clicksPotion = function (potion)
		{
			_clicksPotion(potion);
			checkPotionActive(potion);
		};
	}

	function init()
	{
		initBoatDialog();
		initPotionDialog();
	}
	general.init = init;
})(general || (general = {}));

/**
 * 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
		, log
		, gameEvents
		, temporaryFixes
		, crafting
		, itemBoxes
		, chat
		, timer
		, smelting
		, fishingInfo
		, recipeTooltips
		, fixNumbers
		, machineDialog
		, amountInputs
		, newTopbar
		, styleTweaks
		, notifBoxes
		, market
		, combat
		, farming
		, general
	];
	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 = win.doCommand;
	win.doCommand = function (data)
	{
		if (data.startsWith('REFRESH_ITEMS='))
		{
			oldValues = new Map();
			for (var _i = 0, _a = win.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(win.username, '0', '0', data.substr(3), 1);
			}
		}
		var ret = commands.process(data);
		if (ret === void 0)
		{
			ret = _doCommand(commands.formatData(data));
		}
		return ret;
	};
	var _refreshItemValues = win.refreshItemValues;
	win.refreshItemValues = function (itemKeyList, firstLoad)
	{
		_refreshItemValues(itemKeyList, firstLoad);
		for (var _i = 0, itemKeyList_2 = itemKeyList; _i < itemKeyList_2.length; _i++)
		{
			var key = itemKeyList_2[_i];
			observer.notify(key, oldValues.get(key));
		}
		observer.notifyTick();
	};
});

/**
 * fix web socket errors
 */
var main;
(function (main)
{
	var WS_TIMEOUT_SEC = 30;
	var WS_TIMEOUT_CODE = 3000;
	var WS_OPEN_TIMEOUT_SEC = 2 * 60; // 2 minutes
	// reload the page after 5 consecutive reconnect attempts without successfully opening the websocket once
	var MAX_RECONNECTS = 5;

	function webSocketLoaded(event)
	{
		if (win.webSocket == null)
		{
			console.error('WebSocket instance not initialized!');
			return;
		}
		// cache old event listener
		var _onClose = win.webSocket.onclose;
		var _onError = win.webSocket.onerror;
		var _onMessage = win.webSocket.onmessage;
		var _onOpen = win.webSocket.onopen;
		var commandQueue = [];
		var _cBytes = win.cBytes;
		win.cBytes = function (command)
		{
			if (win.webSocket && win.webSocket.readyState === WebSocket.OPEN)
			{
				_cBytes(command);
			}
			else
			{
				commandQueue.push(command);
			}
		};
		var pageLoaded = false;
		var wsTimeout = null;
		var reconnectAttempts = 0;

		function onTimeout()
		{
			wsTimeout = null;
			// renew the websocket
			if (reconnectAttempts <= MAX_RECONNECTS)
			{
				win.webSocket = new WebSocket(win.SSL_ENABLED);
				win.ignoreBytesTracker = Date.now();
				initWSListener(win.webSocket);
				reconnectAttempts++;
			}
			if (win.webSocket)
			{
				win.webSocket.close(WS_TIMEOUT_CODE, 'Connection timed out after ' + WS_TIMEOUT_SEC + ' seconds');
			}
		}

		function updateWSTimeout()
		{
			if (wsTimeout)
			{
				win.clearTimeout(wsTimeout);
			}
			wsTimeout = win.setTimeout(onTimeout, WS_TIMEOUT_SEC * 1e3);
		}
		var messageQueue = [];

		function onMessage(event)
		{
			if (pageLoaded)
			{
				updateWSTimeout();
				return _onMessage.call(this, event);
			}
			else
			{
				messageQueue.push(event);
			}
		};
		var wsOpenTimeout = null;

		function onOpenTimeout()
		{
			wsOpenTimeout = null;
			location.reload();
		}

		function onOpen(event)
		{
			reconnectAttempts = 0;
			if (wsOpenTimeout)
			{
				win.clearTimeout(wsOpenTimeout);
				wsOpenTimeout = null;
			}
			// do the handshake first
			_onOpen.call(this, event);
			commandQueue.forEach(function (command)
			{
				return win.cBytes(command);
			});
		}

		function onError(event)
		{
			console.error('error in websocket:', event);
			return _onError.call(this, event);
		}

		function onClose(event)
		{
			console.info('websocket closed:', event);
			if (event.code !== WS_TIMEOUT_CODE || reconnectAttempts > MAX_RECONNECTS)
			{
				location.reload();
			}
			return _onClose.call(this, event);
		}

		function initWSListener(ws)
		{
			if (ws.readyState === WebSocket.CONNECTING)
			{
				wsOpenTimeout = win.setTimeout(onOpenTimeout, WS_OPEN_TIMEOUT_SEC * 1e3);
			}
			ws.onclose = onClose;
			ws.onerror = onError;
			ws.onmessage = onMessage;
			ws.onopen = onOpen;
		}
		initWSListener(win.webSocket);
		document.addEventListener('DOMContentLoaded', function ()
		{
			pageLoaded = true;
			messageQueue.forEach(function (event)
			{
				return win.webSocket.onmessage(event);
			});
		});
	}

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

	function isWebSocketScript(script)
	{
		return script.src.includes('socket.js');
	}
	var found = false;
	if (document.head)
	{
		var scripts = 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)
	win.mouseX = win.innerWidth / 2;
	win.mouseY = win.innerHeight / 2;
	var _confirm = win.confirm;
	win.confirm = function (message)
	{
		// don't show the annoying update confirm box (instead of a confirm box, an ingame dialog could be used...)
		if (message && message.indexOf('Ted\'s Market Script') !== -1)
		{
			return false;
		}
		return _confirm(message);
	};
})(main || (main = {}));

})();