DH1 Fixed

Improve Diamond Hunt 1 and fix some inconsistencies

// ==UserScript==
// @name         DH1 Fixed
// @namespace    FileFace
// @description  Improve Diamond Hunt 1 and fix some inconsistencies
// @version      1.36.0
// @author       Zorbing
// @license      ISC; http://opensource.org/licenses/ISC
// @grant        none
// @run-at       document-start
// @include      http://www.diamondhunt.co/DH1/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';

const settings = {
	reorderFarming: {
		title: 'Set seed orders coherent'
		, defaultValue: true
		, requiresReload: true
	}
	, applyNewItemStyle: {
		title: 'Apply a new item style'
		, defaultValue: true
		, requiresReload: true
	}
	, applyNewKeyItemStyle: {
		title: 'Apply a new key item and machinery style'
		, defaultValue: true
		, requiresReload: true
	}
	, improveDialogBtns: {
		title: 'Improve button captions in dialogs'
		, defaultValue: true
	}
	, improveMachineryDialog: {
		title: 'Improve the machinery dialog'
		, defaultValue: true
		, requiresReload: true
	}
	, hideSomeCraftRecipes: {
		title: 'Hide some crafting recipes'
		, defaultValue: true
	}
	, hideMaxRecipes: {
		title: 'Hide recipes of maxed machines'
		, defaultValue: true
	}
	, expandEquipment: {
		title: 'Expand crafting recipes of equipment'
		, defaultValue: true
		, requiresReload: true
	}
	, hideEquipment: {
		title: 'Hide inferiour equipment (only up to gold)'
		, defaultValue: true
	}
	, hideUnnecessaryPrice: {
		title: 'Hide "0 coins"-prices'
		, defaultValue: true
	}
	, useFastLevelCalculation: {
		title: 'Use fast level calculation'
		, defaultValue: true
	}
	, showNotifications: {
		title: 'Show notifications for events'
		, defaultValue: true
	}
	, useNewChat: {
		title: 'Use the new chat with pm tabs'
		, defaultValue: true
		, requiresReload: true
	}
	, addSubTabs: {
		title: 'Add sub tabs'
		, defaultValue: true
	}
};
let fullyLoaded = false;
function notify(title, options)
{
	if (!getSetting('showNotifications'))
	{
		// notifications disabled: return stub notification
		return Promise.resolve({
			close: () => {}
		});
	}
	if (!("Notification" in window) ||
		Notification.permission === 'denied')
	{
		return Promise.reject('Notification permission denied');
	}

	if (Notification.permission === 'granted')
	{
		return Promise.resolve(new Notification(title, options));
	}
	return Notification.requestPermission().then(() => notify(title, options));
};



/**
 * global constants
 */

const maxLevel = 100;
const maxLevelVirtual = 1000;
const furnaceLevels = ['', 'stone', 'bronze', 'iron', 'silver', 'gold', 'ancient', 'promethium', 'runite', 'dragon'];
const ovenLevels = ['bronze', 'iron', 'silver', 'gold', 'ancient', 'promethium', 'runite', 'dragon'];
const barTypes = ['bronze', 'iron', 'silver', 'gold', 'promethium', 'runite'];
const oilConsumption = {
	'drill': 1
	, 'crusher': 15
	, 'giantDrill': 30
	, 'roadHeader': 50
	, 'bucketWheelExcavator': 150
	, 'giantBWE': 500
	, 'sandCollector': 5
};
const machineNames = {
	'drill': 'Mining Drill'
	, 'crusher': 'Crusher'
	, 'giantDrill': 'Giant Drill'
	, 'roadHeader': 'Road Header'
	, 'bucketWheelExcavator': 'Excavator'
	, 'giantBWE': 'Mega Excavator'
	, 'sandCollector': 'Sand Collector'
};



/**
 * observer stuff
 */

let observedKeys = new Map();
/**
 * Observes the given key for change
 * 
 * @param {string} key	The name of the variable
 * @param {Function} fn	The function which is called on change
 */
function observe(key, fn)
{
	if (key instanceof Array)
	{
		for (let k of key)
		{
			observe(k, fn);
		}
	}
	else
	{
		if (!observedKeys.has(key))
		{
			observedKeys.set(key, new Set());
		}
		observedKeys.get(key).add(fn);
	}
	return fn;
}
function unobserve(key, fn)
{
	if (key instanceof Array)
	{
		let ret = [];
		for (let k of key)
		{
			ret.push(unobserve(k, fn));
		}
		return ret;
	}
	if (!observedKeys.has(key))
	{
		return false;
	}
	return observedKeys.get(key).delete(fn);
}
function initObservable()
{
	const oldLoadGlobals = window.loadGlobals;
	window.loadGlobals = (key, newValue) =>
	{
		if (key === undefined)
		{
			return;
		}

		const oldValue = window[key];
		const ret = oldLoadGlobals(key, newValue);
		if (oldValue !== newValue)
		{
			(observedKeys.get(key) || []).forEach(fn => fn(key, oldValue, newValue));
		}
		return ret;
	};
}



/**
 * global misc functions
 */

const itemInfo = {
	oil: {
		name: 'Oil'
		, plural: false
		, img: 'oil.png'
	}
	, sand: {
		name: 'Sand'
		, plural: false
		, img: 'minerals/sand.png'
	}
	, stone: {
		name: 'Stone'
		, plural: false
		, img: 'stone.png'
	}
	, copper: {
		name: 'Copper'
		, plural: false
		, img: 'minerals/copper.png'
	}
	, tin: {
		name: 'Tin'
		, plural: false
		, img: 'minerals/tin.png'
	}
	, iron: {
		name: 'Iron'
		, plural: false
		, img: 'minerals/iron.png'
	}
	, silver: {
		name: 'Silver'
		, plural: false
		, img: 'minerals/silver.png'
	}
	, gold: {
		name: 'Gold'
		, plural: false
		, img: 'minerals/gold.png'
	}
	, marble: {
		name: 'Marble'
		, plural: false
		, img: 'minerals/marble.png'
	}
	, drill: {
		name: 'Mining Drill'
		, img: 'shop/mining-drill.png'
	}
	, superstardustpotion: {
		name: 'Super Stardust Potion'
		, img: 'brewing/superstardustpotion.png'
	}
	, sandcollector: {
		name: 'Sand Collector'
		, img: 'crafting/sandcollector.png'
	}
	, titanium: {
		name: 'Titanium'
		, plural: false
		, img: 'minerals/titanium.png'
	}
	, promethium: {
		name: 'Promethium'
		, plural: false
		, img: 'minerals/promethium.png'
	}
	, sapphire: {
		name: 'Sapphire'
		, img: 'minerals/sapphire.png'
	}
	, emerald: {
		name: 'Emerald'
		, img: 'minerals/emerald.png'
	}
	, ruby: {
		name: 'Ruby'
		, img: 'minerals/ruby.png'
	}
	, diamond: {
		name: 'Diamond'
		, img: 'minerals/diamond.png'
	}
	, bronzebar: {
		name: 'Bronze Bar'
		, img: 'minerals/bronzebar.png'
	}
	, ironbar: {
		name: 'Iron Bar'
		, img: 'minerals/ironbar.png'
	}
	, silverbar: {
		name: 'Silver Bar'
		, img: 'minerals/silverbar.png'
	}
	, goldbar: {
		name: 'Gold Bar'
		, img: 'minerals/goldbar.png'
	}
	, bronzenails: {
		name: 'Bronze Nail'
		, img: 'crafting/bronzenails.png'
	}
	, ironnails: {
		name: 'Iron Nail'
		, img: 'crafting/ironnails.png'
	}
	, silvernails: {
		name: 'Silver Nail'
		, img: 'crafting/silvernails.png'
	}
	, goldnails: {
		name: 'Gold Nail'
		, img: 'crafting/goldnails.png'
	}
	, quartz: {
		name: 'Quartz'
		, plural: false
		, img: 'minerals/quartz.png'
	}
	, flint: {
		name: 'Flint'
		, plural: false
		, img: 'minerals/flint.png'
	}
	, dottedgreenleafseeds: {
		name: 'Dotted Green Leaf Seed'
		, img: 'farming/spotted-green-leaf-seed.png'
	}
	, greenleafseeds: {
		name: 'Green Leaf Seed'
		, img: 'farming/greenleafseed.png'
	}
	, limeleafseeds: {
		name: 'Lime Leaf Seed'
		, img: 'farming/limeleafseed.png'
	}
	, goldleafseeds: {
		name: 'Gold Leaf Seed'
		, img: 'farming/goldleafseed.png'
	}
	, blewitmushroomtreeseeds: {
		name: 'Blewit Mushroom Tree Seed'
		, img: 'farming/blewitmushroomtreeseeds.png'
	}
	, crystalleafseeds: {
		name: 'Crystal Leaf Seed'
		, img: 'farming/crystalleafseed.png'
	}
	, redmushroom: {
		name: 'Red Mushroom'
		, img: 'brewing/redmushroom.png'
	}
	, snapegrass: {
		name: 'Snape Grass'
		, plural: false
		, img: 'brewing/snapegrass.png'
	}
	, redmushroomseeds: {
		name: 'Red Mushroom Seed'
		, img: 'farming/redmushroomseed.png'
	}
	, blewitmushroomseeds: {
		name: 'Snape Grass Seed'
		, img: 'farming/snapegrassseed.png'
	}
	, snapegrassseeds: {
		name: 'Snape Grass Seed'
		, img: 'farming/snapegrassseed.png'
	}
	, stardustseeds: {
		name: 'Stardust Seed'
		, img: 'farming/stardustseed.png'
	}
	, ancientfurnace: {
		name: 'Ancient Furnace'
		, img: 'crafting/ancientfurnace.gif'
	}
	, goldfurnace: {
		name: 'Gold Furnace'
		, img: 'crafting/goldfurnace.gif'
	}
	, promethiumfurnace: {
		name: 'Promethium Furnace'
		, img: 'crafting/promethiumfurnace.gif'
	}
	, silverfurnace: {
		name: 'Silver Furnace'
		, img: 'crafting/silverfurnace.gif'
	}
	, vial: {
		name: 'Vial of Water'
		, plural: false
		, img: 'brewing/vialofwater.png'
	}
	, stardustpotion: {
		name: 'Stardust Potion'
		, img: 'brewing/stardustpotion.png'
	}
	, stardust: {
		name: 'Stardust'
		, plural: false
		, img: 'minerals/stardust.png'
	}
	, coins: {
		name: 'Coin'
		, img: 'pic_coin_bigstack.png'
	}
	, donorcoins: {
		name: 'Donor Coin'
		, img: 'donor_coin.png'
	}
	, unbounddonorcoins: {
		name: 'Donor Coin'
		, img: 'donor_coin.png'
	}
	, pumpjack: {
		name: 'Pumpjack'
		, img: 'shop/pumpjack.png'
	}
	, oilpipe: {
		name: 'Oil Pipe'
		, img: 'shop/oil-pipe.png'
	}
	, treasurechestkey: {
		name: 'Treasure Key'
		, img: 'misc-items/treasureKey.png'
	}
	, sapphirekey: {
		name: 'Sapphire Key'
		, img: 'misc-items/sapphireKey.png'
	}
	, emeraldkey: {
		name: 'Emerald Key'
		, img: 'misc-items/emeraldKey.png'
	}
	, rubykey: {
		name: 'Ruby Key'
		, img: 'misc-items/rubyKey.png'
	}
	, dragonkey: {
		name: 'Dragon Key'
		, img: 'misc-items/dragonKey.png'
	}
	, roadheader: {
		name: 'Road Header'
		, img: 'shop/vip/roadheader.png'
	}
	, giantdrill: {
		name: 'Giant Drill'
		, img: 'shop/vip/giantdrill.png'
	}
	, crusher: {
		name: 'Crusher'
		, img: 'shop/crusher.gif'
	}
	, dottedgreenleaf: {
		name: 'Dotted Green Leaf'
		, img: 'brewing/dottedgreenleaf.png'
	}
	, oilbarrel: {
		name: 'Oil Barrel'
		, img: 'crafting/oilbarrel.png'
	}
	, greenleaf: {
		name: 'Green Leaf'
		, img: 'brewing/greenleaf.png'
	}
	, limeleaf: {
		name: 'Lime Leaf'
		, img: 'brewing/limeleaf.png'
	}
	, goldleaf: {
		name: 'Gold Leaf'
		, img: 'brewing/goldleaf.png'
	}
	, crystalleaf: {
		name: 'Crystal Leaf'
		, img: 'brewing/crystalleaf.png'
	}
	, blewitmushroom: {
		name: 'Blewit Mushroom'
		, img: 'brewing/blewitmushroom.png'
	}
	, trowel: {
		name: 'Trowel'
		, img: 'farming/trowel.png'
	}
	, upgradeoilpipe: {
		name: 'Oil Pipe Upgrade Orb'
		, img: 'crafting/upgradeoilpipe.png'
	}
	, upgradeenchantedhammer: {
		name: 'Enchanted Hammer Upgrade Orb'
		, img: 'crafting/upgradeenchantedhammer.png'
	}
	, upgradefurnaceorb: {
		name: 'Furnace Upgrade Orb'
		, img: 'crafting/upgradefurnaceorb.png'
	}
	, upgradeenchantedrake: {
		name: 'Enchanted Rake Upgrade Orb'
		, img: 'farming/upgradeenchantedrake.png'
	}
	, redmushroomtreeseeds: {
		name: 'Red Mushroom Tree Seed'
		, img: 'farming/redmushroomtreeseeds.png'
	}
	, stardusttreeseeds: {
		name: 'Stardust Tree Seed'
		, img: 'farming/stardusttreeseed.png'
	}
	, rocket: {
		name: 'Rocket'
		, img: 'crafting/rocket.png'
	}
	, promethiumbar: {
		name: 'Promethium Bar'
		, img: 'minerals/promethiumbar.png'
	}
	, upgradepumpjackorb: {
		name: 'Blue Pumpjack Upgrade Orb'
		, img: 'crafting/upgradepumpjackorb.png'
	}
	, upgradewrenchorb: {
		name: 'Wrench Upgrade Orb'
		, img: 'crafting/upgradewrenchorb.png'
	}
	, oilfactory: {
		name: 'Oil Factory'
		, img: 'shop/oilfactory.png'
	}
	, orboftransformation: {
		name: 'Orb of Transformation'
		, plural: 'Orbs of Transformation'
		, img: 'minerals/orb.png'
	}
	, emptyblueorb: {
		name: 'Empty Blue Orb'
		, img: 'crafting/anyorb.png'
	}
	, emptygreenorb: {
		name: 'Empty Green Orb'
		, img: 'crafting/anyorb2.png'
	}
	, diamondminers: {
		name: 'Diamond Pickaxe'
		, img: 'pickaxes/diamond_pickaxe.png'
	}
	, stripedleafseeds: {
		name: 'Striped Leaf Seed'
		, img: 'farming/stripedleafseed.png'
	}
	, stripedleaf: {
		name: 'Striped Leaf'
		, img: 'brewing/stripedleaf.png'
	}
	, stripedcrystalleafseeds: {
		name: 'Striped Crystal Leaf Seed'
		, img: 'farming/stripedcrystalleafseed.png'
	}
	, stripedcrystalleaf: {
		name: 'Striped Crystal Leaf'
		, img: 'brewing/stripedcrystalleaf.png'
	}
	, bucketwheelexcavator: {
		name: 'Bucket-wheel Excavator'
		, img: 'shop/excavators.png'
	}
	, brewingkit: {
		name: 'Brewing Kit'
		, img: 'brewing/brewingkit.png'
	}
	, supercompostpotion: {
		name: 'Super Compost Potion'
		, img: 'brewing/supercompostpotion.png'
	}
	, megastardustpotion: {
		name: 'Mega Stardust Potion'
		, img: 'brewing/megastardustpotion.png'
	}
	, robot: {
		name: 'Robot'
		, img: 'crafting/robot.png'
	}
	, greenpumpjackorb: {
		name: 'Green Pumpjack Upgrade Orb'
		, img: 'crafting/greenpumpjackorb.png'
	}
	, greenwizardorb: {
		name: 'Green Wizard Upgrade Orb'
		, img: 'crafting/greenwizardorb.png'
	}
	, redbrewingkitorb: {
		name: 'Red Brewing Kit Upgrade Orb'
		, img: 'crafting/redbrewingkitorb.png'
	}
	, runite: {
		name: 'Runite'
		, img: 'minerals/runite.png'
	}
	, runitebar: {
		name: 'Runite Bar'
		, img: 'minerals/runitebar.png'
	}
	, potato: {
		name: 'Potato'
		, plural: 'Potatoes'
		, img: 'exploring/potato.png'
	}
	, wheat: {
		name: 'Wheat'
		, plural: false
		, img: 'exploring/wheat.png'
	}
	, strawberry: {
		name: 'Strawberry'
		, img: 'exploring/strawberry.png'
	}
	, strawberrypie: {
		name: 'Strawberry Pie'
		, img: 'exploring/strawberrypie.png'
	}
	, greenmushroom: {
		name: 'Green Mushroom'
		, img: 'exploring/greenmushroom.png'
	}
	, mashedpotatoes: {
		name: 'Mashed Potatoes'
		, plural: false
		, img: 'exploring/mashedpotatoes.png'
	}
	, silveroven: {
		name: 'Silver Oven'
		, img: 'exploring/silveroven.png'
	}
	, goldoven: {
		name: 'Gold Oven'
		, img: 'exploring/goldoven.png'
	}
	, promethiumoven: {
		name: 'Promethium Oven'
		, img: 'exploring/promethiumoven.png'
	}
	, runiteoven: {
		name: 'Runite Oven'
		, img: 'exploring/runiteoven.png'
	}
	, flour: {
		name: 'Flour'
		, plural: false
		, img: 'exploring/flour.png'
	}
	, rocketfuelorb: {
		name: 'Rocket Fuel Orb'
		, img: 'crafting/rocketfuelorb.png'
	}
	, superchestpotion: {
		name: 'Super Chest Potion'
		, img: 'brewing/superchestpotion.png'
	}
	, chestpotion: {
		name: 'Chest Potion'
		, img: 'brewing/chestpotion.png'
	}
	, bread: {
		name: 'Bread'
		, img: 'exploring/bread.png'
	}
	, shrimp: {
		name: 'Shrimp'
		, img: 'exploring/shrimp.png'
	}
	, sardine: {
		name: 'Sardine'
		, img: 'exploring/sardine.png'
	}
	, tuna: {
		name: 'Tuna'
		, img: 'exploring/tuna.png'
	}
	, swordfish: {
		name: 'Swordfish'
		, plural: false
		, img: 'exploring/swordfish.png'
	}
	, shark: {
		name: 'Shark'
		, img: 'exploring/shark.png'
	}
	, lava: {
		name: 'Lava'
		, plural: false
		, img: 'exploring/lava.png'
	}
	, explorerspotion: {
		name: 'Exploring Potion'
		, img: 'brewing/explorerspotion.png'
	}
	, whale: {
		name: 'Whale'
		, img: 'exploring/whale.png'
	}
	, strangeleaf: {
		name: 'Strange Leaf'
		, img: 'exploring/strangeleaf.png'
	}
	, fishingpotion: {
		name: 'Fishing Potion'
		, img: 'brewing/fishingpotion.png'
	}
	, superorboftransformation: {
		name: 'Super Orb of Transformation'
		, plural: 'Super Orbs of Transformation'
		, img: 'minerals/upgradedorb.png'
	}
	, moonstone: {
		name: 'Moonstone'
		, img: 'minerals/moonstone.png'
	}
	, purewaterpotion: {
		name: 'Pure Water'
		, plural: false
		, img: 'exploring/purewater.png'
	}
	, amuletofthesea: {
		name: 'Amulet of the Sea'
		, plural: 'Amulets of the Sea'
		, img: 'exploring/equipement/amuletofthesea.png'
	}
	, appletreeseeds: {
		name: 'Apple Tree Seed'
		, img: 'farming/appletreeseed.png'
	}
	, apple: {
		name: 'Apple'
		, img: 'exploring/apple.png'
	}
	, ironsword: {
		name: 'Iron Sword'
		, img: 'exploring/equipement/ironsword.png'
	}
	, goldbody: {
		name: 'Gold Body'
		, img: 'exploring/equipement/goldbody.png'
	}
	, ironbody: {
		name: 'Iron Body'
		, img: 'exploring/equipement/ironbody.png'
	}
	, runitehelmet: {
		name: 'Runite Helmet'
		, img: 'exploring/equipement/runitehelmet.png'
	}
	, promethiumbody: {
		name: 'Promethium Body'
		, img: 'exploring/equipement/promethiumbody.png'
	}
	, rawshrimp: {
		name: 'Raw Shrimp'
		, img: 'exploring/rawshrimp.png'
	}
	, rawshark: {
		name: 'Raw Shark'
		, img: 'exploring/rawshark.png'
	}
	, rawbread: {
		name: 'Raw Bread'
		, img: 'exploring/rawbread.png'
	}
	, frozenhorn: {
		name: 'Horn'
		, img: 'exploring/horn.png'
	}
	, pumpkinsigil: {
		name: 'Halloween 2015 Sigil'
		, img: 'sigils/halloween2015.png'
	}
	, treesigil: {
		name: 'Christmas 2016 Sigil'
		, img: 'sigils/christmas2016.png'
	}
	, santahatsigil: {
		name: 'Christmas 2015 Sigil'
		, img: 'sigils/christmas2015.png'
	}
	, exploringorb: {
		name: 'Exploring Upgrade Orb'
		, img: 'crafting/exploringorb.png'
	}
	, whaletooth: {
		name: 'Whale Tooth'
		, plural: 'Whale Teeth'
		, img: 'exploring/whaletooth.png'
	}
	, cactuswater: {
		name: 'Cactus Water'
		, plural: false
		, img: 'exploring/cactuswater.png'
	}
	, swampwater: {
		name: 'Swamp Water'
		, plural: false
		, img: 'exploring/swampwater.png'
	}
	, tnt: {
		name: 'TNT'
		, plural: false
		, img: 'crafting/tnt.png'
	}
	, emptyessence: {
		name: 'Empty Essence'
		, img: 'magic/emptyessence.png'
	}
	, chargedmineralessence: {
		name: 'Charged Mineral Essence'
		, img: 'magic/chargedmineralessence.png'
	}
	, chargedmetallicessence: {
		name: 'Charged Metallic Essence'
		, img: 'magic/chargedmetallicessence.png'
	}
	, chargedoilessence: {
		name: 'Charged Oil Essence'
		, img: 'magic/chargedoilessence.png'
	}
	, chargedenergyessence: {
		name: 'Charged Energy Essence'
		, img: 'magic/chargedenergyessence.png'
	}
	, chargednatureessence: {
		name: 'Charged Nature Essence'
		, img: 'magic/chargednatureessence.png'
	}
	, chargedorbessence: {
		name: 'Charged Orb Essence'
		, img: 'magic/chargedorbessence.png'
	}
	, chargedgemessence: {
		name: 'Charged Gem Essence'
		, img: 'magic/chargedgemessence.png'
	}
	, magicpage1: {
		name: '1st Magic Page'
		, img: 'magic/magicpage1.png'
	}
	, magicpage2: {
		name: '2nd Magic Page'
		, img: 'magic/magicpage2.png'
	}
	, magicpage3: {
		name: '3rd Magic Page'
		, img: 'magic/magicpage3.png'
	}
	, magicpage4: {
		name: '4th Magic Page'
		, img: 'magic/magicpage4.png'
	}
	, magicpage5: {
		name: '5th Magic Page'
		, img: 'magic/magicpage5.png'
	}
	, magicpage6: {
		name: '6th Magic Page'
		, img: 'magic/magicpage6.png'
	}
	, dottedgreenroots: {
		name: 'Dotted Green Root'
		, img: 'farming/dottedgreenroots.png'
	}
	, greenroots: {
		name: 'Green Root'
		, img: 'farming/greenroots.png'
	}
	, limeroots: {
		name: 'Lime Root'
		, img: 'farming/limeroots.png'
	}
	, goldroots: {
		name: 'Gold Root'
		, img: 'farming/goldroots.png'
	}
	, stripedgoldroots: {
		name: 'Striped Gold Root'
		, img: 'farming/stripedgoldroots.png'
	}
	, crystalroots: {
		name: 'Crystal Root'
		, img: 'farming/crystalroots.png'
	}
	, stripedcrystalroots: {
		name: 'Striped Crystal Root'
		, img: 'farming/stripedcrystalroots.png'
	}
	, purewaterring: {
		name: 'Pure Water Ring'
		, img: 'exploring/equipement/purewaterring.png'
	}
	, coinring: {
		name: 'Coin Ring'
		, img: 'exploring/equipement/coinring.png'
	}
	, lavaring: {
		name: 'Lava Ring'
		, img: 'exploring/equipement/lavaring.png'
	}
	, promethiumhelmet: {
		name: 'Promethium Helmet'
		, img: 'exploring/equipement/promethiumhelmet.png'
	}
	, promethiumlegs: {
		name: 'Promethium Legs'
		, img: 'exploring/equipement/promethiumlegs.png'
	}
	, promethiumsword: {
		name: 'Promethium Sword'
		, img: 'exploring/equipement/promethiumsword.png'
	}
	, a: {
		name: 'Runite Helmet'
		, img: 'exploring/equipement/runitehelmet.png'
	}
	, runitebody: {
		name: 'Runite Body'
		, img: 'exploring/equipement/runitebody.png'
	}
	, runitelegs: {
		name: 'Runite Legs'
		, img: 'exploring/equipement/runitelegs.png'
	}
	, runitesword: {
		name: 'Runite Sword'
		, img: 'exploring/equipement/runitesword.png'
	}
	, ancientshield: {
		name: 'Ancient Shield'
		, img: 'exploring/equipement/ancientshield.png'
	}
	, redpumpjack: {
		name: 'Red Pumpjack Upgrade'
		, img: 'crafting/redpumpjack.png'
	}
	, essenceseeds: {
		name: 'Essence Seed'
		, img: 'farming/essenceseed.png'
	}
	, giantbwe: {
		name: 'Giant Bucket-wheel Excavator'
		, img: 'crafting/giantbwe.png'
	}
	, redfactoryorb: {
		name: 'Red Factory Upgrade Orb'
		, img: 'crafting/redfactoryorb.png'
	}
	, essencetreeseeds: {
		name: 'Essence Tree Seed'
		, img: 'farming/essencetreeseed.png'
	}
	, vendorrerollscroll: {
		name: 'Vendor Reroll Scroll'
		, img: 'exploring/vendorrerollscroll.png'
	}
	, goldstaff: {
		name: 'Gold Staff'
		, img: 'magic/goldstaff.png'
	}
	, promethiumstaff: {
		name: 'Promethium Staff'
		, img: 'magic/promethiumstaff.png'
	}
	, runitestaff: {
		name: 'Runite Staff'
		, img: 'magic/runitestaff.png'
	}
	, goldwand: {
		name: 'Gold Wand'
		, img: 'magic/goldwand.png'
	}
	, promethiumwand: {
		name: 'Promethium Wand'
		, img: 'magic/promethiumwand.png'
	}
	, runitewand: {
		name: 'Runite Wand'
		, img: 'magic/runitewand.png'
	}
	, ancientbar: {
		name: 'Ancient Bar'
		, img: 'minerals/ancientbar.png'
	}
	, ancientcrystal: {
		name: 'Ancient Crystal'
		, img: 'exploring/ancientcrystal.png'
	}
	, ancientoven: {
		name: 'Ancient Oven'
		, img: 'exploring/ancientoven.png'
	}
	, dragonstone: {
		name: 'Dragonstone'
		, img: 'minerals/dragonstone.png'
	}
	, dragonsword: {
		name: 'Dragon Sword'
		, img: 'exploring/equipement/dragonsword.png'
	}
	, dragonhelmet: {
		name: 'Dragon Helmet'
		, img: 'exploring/equipement/dragonhelmet.png'
	}
	, superrobot: {
		name: 'Super Robot'
		, img: 'crafting/superrobot.png'
	}
	, promethiumwrench: {
		name: 'Promethium Wrench'
		, pluarl: 'Promethium Wrenches'
		, img: 'crafting/promethiumwrench.png'
	}
	, dragonchest: {
		name: 'Dragon Chest'
		, img: 'misc-items/dragonChest.png'
	}
	, dragonaxe: {
		name: 'Dragon Axe'
		, img: 'dragonsquest/dragonaxe.png'
	}
	, dragonfishingrod: {
		name: 'Dragon Fishing Rod'
		, img: 'dragonsquest/dragonfishingrod.png'
	}
	, dragonpickaxe: {
		name: 'Dragon Pickaxe'
		, img: 'dragonsquest/dragonpickaxe.png'
	}
	, dragonpumpjacks: {
		name: 'Dragon Pumpjack'
		, img: 'dragonsquest/dragonpumpjacks.png'
	}
	, dragonstaff: {
		name: 'Dragon Staff'
		, img: 'dragonsquest/dragonstaff.png'
	}
	, dragonwand: {
		name: 'Dragon Wand'
		, img: 'dragonsquest/dragonwand.png'
	}
	, eel: {
		name: 'Eel'
		, img: 'exploring/eel.png'
	}
	, eastereggsigil: {
		name: 'Easter 2016 Sigil'
		, img: 'sigils/easter2016.png'
	}
	, rawsardine: {
		name: 'Raw Sardine'
		, img: 'exploring/rawsardine.png'
	}
	, rawtuna: {
		name: 'Raw Tuna'
		, img: 'exploring/rawtuna.png'
	}
	, rawswordfish: {
		name: 'Raw Swordfish'
		, plural: false
		, img: 'exploring/rawswordfish.png'
	}
	, raweel: {
		name: 'Raw Eel'
		, img: 'exploring/raweel.png'
	}
	, rawwhale: {
		name: 'Raw Whale'
		, img: 'exploring/rawwhale.png'
	}
	, rawrainbowfish: {
		name: 'Raw Rainbow Fish'
		, img: 'exploring/rawrainbowfish.png'
	}
	, tunacooker: {
		name: 'Tuna Cooker'
		, img: 'exploring/tunacooker.png'
	}
	, swordfishcooker: {
		name: 'Swordfish Cooker'
		, img: 'exploring/swordfishcooker.png'
	}
	, sharkcooker: {
		name: 'Shark Cooker'
		, img: 'exploring/sharkcooker.png'
	}
	, whalecooker: {
		name: 'Whale Cooker'
		, img: 'exploring/whalecooker.png'
	}
	, rainbowfishcooker: {
		name: 'Rainbow Fish Cooker'
		, img: 'exploring/rainbowfishcooker.png'
	}
	, fishingnet: {
		name: 'Fishing Net'
		, img: 'exploring/fishingnet.png'
	}
	, mapofthesea: {
		name: 'Map of the Sea'
		, plural: 'Maps of the Sea'
		, img: 'exploring/mapofthesea.png'
	}
	, dragonamulet: {
		name: 'Dragon Amulet'
		, img: 'exploring/equipement/dragonamulet.png'
	}
	, dragonbody: {
		name: 'Dragon Body'
		, img: 'exploring/equipement/dragonbody.png'
	}
	, treasurechestkey2: {
		name: 'Ghost Key'
		, img: 'misc-items/ghostKey.png'
	}
	, ghostsigil: {
		name: 'Halloween 2016 Sigil'
		, img: 'sigils/halloween2016.png'
	}
	, ghostpipe1: {
		name: '1st Ghost Pipe'
		, img: 'crafting/ghostpipe1.png'
	}
	, ghostpipe2: {
		name: '2nd Ghost Pipe'
		, img: 'crafting/ghostpipe2.png'
	}
	, ghostpipe3: {
		name: '3rd Ghost Pipe'
		, img: 'crafting/ghostpipe3.png'
	}
	, ghostpipe4: {
		name: '4th Ghost Pipe'
		, img: 'crafting/ghostpipe4.png'
	}
	, ghostpipe5: {
		name: '5th Ghost Pipe'
		, img: 'crafting/ghostpipe5.png'
	}
	, ghostpipe6: {
		name: '6th Ghost Pipe'
		, img: 'crafting/ghostpipe6.png'
	}
	, ghostpipesheet: {
		name: 'Ghost Pipe Sheet'
		, img: 'crafting/ghostpipesheet.png'
	}
	, dragonoven: {
		name: 'Dragon Oven'
		, img: 'exploring/dragonoven.png'
	}
	, dragonfurnace: {
		name: 'Dragon Furnace'
		, img: 'crafting/dragonfurnace.gif'
	}
	, grouptasktokens: {
		name: 'Group Task Token'
		, img: 'icons/grouptasktokens.png'
	}
	, arrowhead: {
		name: 'Arrowhead'
		, img: 'exploring/arrowhead.png'
	}
	, beetlefossil: {
		name: 'Beetle Fossil'
		, img: 'exploring/beetleFossil.png'
	}
	, goldbranch: {
		name: 'Gold Branch'
		, img: 'exploring/goldBranch.png'
	}
	, seashell: {
		name: 'Seashell'
		, img: 'exploring/seashell.png'
	}
	, seaweed: {
		name: 'Seaweed'
		, img: 'exploring/seaweed.png'
	}
	, sharkfin: {
		name: 'Shark\'s Fin'
		, img: 'exploring/sharkfin.png'
	}
	, redsand: {
		name: 'Red Sand'
		, img: 'exploring/redsand.png'
	}
	, swamptar: {
		name: 'Swamp Tar'
		, img: 'exploring/swamptar.png'
	}
	, seedpotion: {
		name: 'Seed Potion'
		, img: 'brewing/seedPotion.png'
	}
	, reddirt: {
		name: 'Red Dirt'
		, img: 'exploring/redDirt.png'
	}
	, carvedtreebark: {
		name: 'Carved Tree Bark'
		, img: 'exploring/carvedTreeBark.png'
	}
};
function createTemplateWrapper(str)
{
	const tmp = document.createElement('templateWrapper');
	tmp.innerHTML = str;
	return tmp;
}
function formatNumber(num)
{
	// return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
	return parseFloat(num).toLocaleString('en');
}
function formatNumbersInText(text)
{
	return text.replace(/\d(?:[\d',\.]*\d)?/g, (numStr) =>
	{
		return formatNumber(parseInt(numStr.replace(/\D/g, ''), 10));
	});
}
function imgSrc2Info(str)
{
	const match = str.match(/(?:^|")images\/(.+?([^\/]+)\.(?:png|gif|jpe?g))(?:"|$)/);
	if (match)
	{
		const keyFromImg = match[2].toLowerCase();
		if (itemInfo.hasOwnProperty(keyFromImg))
		{
			return itemInfo[keyFromImg];
		}
		const imgLowerCase = match[1].toLowerCase();
		for (let key in itemInfo)
		{
			if (itemInfo[key].img == imgLowerCase)
			{
				return itemInfo[key];
			}
		}
		console.warn('unknown image key:', str, match);
		return {
			name: match[2].replace(/-|_/g, ' ')
				.replace(/([a-z])([A-Z])/g, (wholeMatch, m1, m2) =>
				{
					return m1 + ' ' + m2.toLowerCase();
				})
			, img: match[1].toLowerCase()
		};
	}
	return {};
}
function imgSrc2Name(str)
{
	return imgSrc2Info(str).name || null;
}
function imgSrc2NamePlural(str, num)
{
	const info = imgSrc2Info(str);
	if (!info.hasOwnProperty('name'))
	{
		return null;
	}
	if (num == 1 || info.plural === false)
	{
		return info.name;
	}
	if (!info.hasOwnProperty('plural'))
	{
		return info.name.replace(/([^aeiou])y$/, '$1ie') + 's';
	}
	if (typeof info.plural === 'function')
	{
		return info.plural(num);
	}
	return info.plural;
}



/**
 * settings
 */

function getSettingName(key)
{
	return 'setting.' + key;
}
const observedSettings = new Map();
function observeSetting(key, fn)
{
	if (!observedSettings.has(key))
	{
		observedSettings.set(key, new Set());
	}
	observedSettings.get(key).add(fn);
}
function unobserveSetting(key, fn)
{
	if (!observedKeys.has(key))
	{
		return false;
	}
	return observedKeys.get(key).delete(fn);
}
function getSetting(key)
{
	if (!settings.hasOwnProperty(key))
	{
		return;
	}
	const name = getSettingName(key);
	return localStorage.hasOwnProperty(name) ? JSON.parse(localStorage.getItem(name)) : settings[key].defaultValue;
}
function setSetting(key, newValue)
{
	if (!settings.hasOwnProperty(key))
	{
		return;
	}
	const oldValue = getSetting(key);
	localStorage.setItem(getSettingName(key), JSON.stringify(newValue));
	if (oldValue !== newValue)
	{
		(observedSettings.get(key) || []).forEach(fn => fn(key, oldValue, newValue));
	}
}
function initSettings()
{
	if (!localStorage.hasOwnProperty('setSoundToggleDefault'))
	{
		const defaultSound = 'off';
		localStorage.setItem('soundToggle', defaultSound);
		localStorage.setItem('setSoundToggleDefault', true);
		document.getElementById('sound-toggle').innerHTML = defaultSound;
	}

	const settingStyle = document.createElement('style');
	settingStyle.innerHTML = `
#settings-tab td.setting
{
	color: pink;
}
#settings-tab td.setting label
{
	cursor: pointer;
}
#settings-tab td.setting label:hover
{
	color: red;
}
#settings-tab td.setting.reload::after
{
	color: orange;
	content: '*';
	float: right;
	margin: 0 3px;
}
	`;
	document.head.appendChild(settingStyle);

	const table = document.getElementById('settings-tab').querySelector('table');
	if (!table)
	{
		return;
	}
	const headerRow = table.insertRow(-1);
	headerRow.innerHTML = `<th style="background-color:black;color:orange">
		Userscript "DH1 Fixed"<br>
		<span style="font-size: 0.9rem;">(* changes require reloading the tab)</span>
	</th>`;

	for (let key in settings)
	{
		const row = table.insertRow(-1);
		row.innerHTML = `<td class="setting ${settings[key].requiresReload ? 'reload' : ''}">
			<input type="checkbox" id="userscript-${key}" ${getSetting(key) ? 'checked="checked"' : ''}>
			<label for="userscript-${key}">
				${settings[key].title}
			</label>
		</td>`;
		const checkbox = document.getElementById('userscript-' + key);
		checkbox.addEventListener('change', () =>
		{
			setSetting(key, checkbox.checked);
		});
	}

	const settingLink = document.querySelector('.top-menu td[onclick^="openTab"]');
	settingLink.addEventListener('click', function ()
	{
		const activeTab = document.querySelector('#tab-tr td[style^="background: linear-gradient(rgb"]');
		if (activeTab)
		{
			activeTab.style.background = 'linear-gradient(black, grey)';
		}
	});
}



/**
 * fix key items
 */

const oilKeyItems = {
	'handheldOilPump': {
		oilPerSecond: () => 1 * parseInt(window.miners, 10)
		, observeList: [
			'miners'
		]
	}
	, 'bindedOilPipe': {
		oilPerSecond: () => [100, 200, 300][window.bindedUpgradeOilPipe]
		, observeList: [
			'bindedUpgradeOilPipe'
		]
	}
	, 'bindedPumpJack': {
		oilPerSecond: () =>
		{
			const pumpjackProduction = 30
				+ (window.bindedRedPumpJack > 0 ? 15 : 0)
				+ (window.bindedDragonPumpjacks > 0 ? 15 : 0);
			return pumpjackProduction * parseInt(window.bindedPumpJack, 10);
		}
		, observeList: [
			'bindedRedPumpJack'
			, 'bindedDragonPumpjacks'
		]
	}
	, 'bindedOilFactory': {
		oilPerSecond: () => 2 * parseInt(window.oilFactoryWorkers, 10)
		, observeList: [
			'oilFactoryWorkers'
		]
	}
	, 'bindedOilRefinery': {
		oilPerSecond: () => Math.min(5e-6 * parseInt(window.oil, 10), 500)
		, observeList: [
			'oil'
		]
	}
	, 'bindedGhostPipeSheet': {
		oilPerSecond: () =>
		{
			let ghostPipes = 0;
			for (let i = 1; i <= 6; i++)
			{
				if (window['bindedGhostPipe' + i] > 0)
				{
					ghostPipes++;
				}
			}
			return ghostPipes == 6 ? 200 * 6 : 100 * ghostPipes;
		}
		, observeList: [
			'bindedGhostPipe1'
			, 'bindedGhostPipe2'
			, 'bindedGhostPipe3'
			, 'bindedGhostPipe4'
			, 'bindedGhostPipe5'
			, 'bindedGhostPipe6'
		]
	}
};
function fixKeyItems()
{
	// remove unnecessary br element
	const oilPump = document.getElementById('key-item-handheldOilPump-box');
	let br = oilPump && oilPump.nextElementSibling;
	if (!br)
	{
		br = document.createElement('br');
	}

	// add br element after img in oil pipe element
	const oilPipe = document.getElementById('key-item-bindedOilPipe-box');
	let img = oilPipe && oilPipe.children[0];
	img = img && img.children[0];
	img.parentNode.insertBefore(br, img.nextSibling);

	// add display for oil per second
	const oilStyle = document.createElement('style');
	oilStyle.innerHTML = `
.oil-production-wrapper
{
	color: black;
	display: block;
	font-size: 14px;
}
.oil-production-wrapper::before
{
	content: '+';
}
.oil-production-wrapper::after
{
	content: '/s';
}
#key-item-bindedOilRefinery-box > .text-wrapper,
#key-item-bindedGhostPipeSheet-box > .text-wrapper
{
	display: none;
}
	`;
	document.head.appendChild(oilStyle);

	function updateOilProduction(key, el)
	{
		if (window[key] > 0)
		{
			const oilProd = Math.floor(oilKeyItems[key].oilPerSecond() * 10) / 10;
			el.textContent = formatNumber(oilProd);
		}
	}
	const oilImages = document.querySelectorAll('.item-box-title img[src="images/oil.png"]');
	for (let i = 0; i < oilImages.length; i++)
	{
		const img = oilImages[i];
		const boxTitle = img.parentElement;
		const key = boxTitle.parentElement.id.replace(/^key-item-(.+)-box$/, '$1');
		if (oilKeyItems.hasOwnProperty(key))
		{
			const info = oilKeyItems[key];
			const oilWrapper = document.createElement('span');
			oilWrapper.className = 'oil-production-wrapper';
			const oilProduction = document.createElement('span');
			oilProduction.className = 'oil-production';
			oilWrapper.appendChild(oilProduction);
			boxTitle.insertBefore(oilWrapper, img);
			oilWrapper.appendChild(img);
			updateOilProduction(key, oilProduction);

			info.observeList.unshift(key);
			observe(info.observeList, () => updateOilProduction(key, oilProduction));
		}
	}

	const oldOpenBindedOilFactoryDialogue = window.openBindedOilFactoryDialogue;
	window.openBindedOilFactoryDialogue = () =>
	{
		const maxWorkers = window.bindedRedFactoryOrb > 0 ? 300 : 100;
		window.miscMultipleInput(
			'How many factory workers do you wish to hire?<br>You can hire up to ' + maxWorkers + '.<br><br>'
				+ 'Costs: 1<img src="images/pic_coin2.png" width="20px" height="20px" style="vertical-align: middle"> per worker'
			, 'oilFactoryWorkers'
			, '0'
			, 'MISC_MULTIPLE='
		);
	}
}



/**
 * fix farming
 */

const seedOrder = ['bloodLeafSeeds', 'redMushroomSeeds', 'dottedGreenLeafSeeds', 'potatoSeeds', 'strawberrySeeds', 'greenLeafSeeds', 'redMushroomTreeSeeds', 'wheatSeeds', 'blewitMushroomSeeds', 'limeLeafSeeds', 'blewitMushroomTreeSeeds', 'snapeGrassSeeds', 'starDustSeeds', 'appleTreeSeeds', 'iceBerrySeeds', 'goldLeafSeeds', 'starDustTreeSeeds', 'stripedLeafSeeds', 'essenceSeeds', 'crystalLeafSeeds', 'megaDottedGreenLeafSeeds', 'megaRedMushroomSeeds', 'essenceTreeSeeds', 'megaGreenLeafSeeds', 'stripedCrystalLeafSeeds', 'megaBlewitMushroomSeeds', 'megaLimeLeafSeeds'];
const seeds = {
	bloodLeafSeeds: {
		title: 'Blood Leaf Seed'
		, level: 1
		, diesUntil: 0
		, time: 5
		, xp: 1e6
	}
	, redMushroomSeeds: {
		title: 'Red Mushroom Seed'
		, level: 1
		, diesUntil: 0
		, time: 15
		, xp: 100
	}
	, dottedGreenLeafSeeds: {
		title: 'Green Dotted Leaf Seed'
		, level: 1
		, diesUntil: 15
		, time: 30
		, xp: 250
	}
	, potatoSeeds: {
		title: 'Potato'
		, level: 5
		, diesUntil: 0
		, time: 15
		, xp: 35
	}
	, strawberrySeeds: {
		title: 'Strawberry Seed'
		, level: 10
		, diesUntil: 0
		, time: 30
		, xp: 85
	}
	, greenLeafSeeds: {
		title: 'Green Leaf Seed'
		, level: 10
		, diesUntil: 25
		, time: 60
		, xp: 500
	}
	, redMushroomTreeSeeds: {
		title: 'Red Mushroom Tree Seed'
		, level: 10
		, diesUntil: 30
		, time: 8*60
		, xp: 2e3
	}
	, wheatSeeds: {
		title: 'Wheat Seed'
		, level: 15
		, diesUntil: 0
		, time: 15
		, xp: 95
	}
	, blewitMushroomSeeds: {
		title: 'Blewit Mushroom Seed'
		, level: 15
		, diesUntil: 0
		, time: 20
		, xp: 200
	}
	, limeLeafSeeds: {
		title: 'Lime Leaf Seed'
		, level: 20
		, diesUntil: 40
		, time: 1.5*60
		, xp: 1500
	}
	, blewitMushroomTreeSeeds: {
		title: 'Blewit Mushroom Tree Seed'
		, level: 20
		, diesUntil: 40
		, time: 10*60
		, xp: 4e3
	}
	, snapeGrassSeeds: {
		title: 'Snape Grass Seed'
		, level: 25
		, diesUntil: 0
		, time: 30
		, xp: 300
	}
	, starDustSeeds: {
		title: 'Stardust Seed'
		, level: 30
		, diesUntil: 0
		, time: 30
		, xp: 750
	}
	, appleTreeSeeds: {
		title: 'Apple Tree Seed'
		, level: 30
		, diesUntil: 45
		, time: 8*60
		, xp: 5e3
	}
	, iceBerrySeeds: {
		title: 'Ice Berry Seed'
		, level: 35
		, diesUntil: 0
		, time: 60
		, xp: 450
	}
	, goldLeafSeeds: {
		title: 'Gold Leaf Seed'
		, level: 40
		, diesUntil: 55
		, time: 4*60
		, xp: 10e3
	}
	, starDustTreeSeeds: {
		title: 'Stardust Tree Seed'
		, level: 40
		, diesUntil: 55
		, time: 5*60
		, xp: 15e3
	}
	, stripedLeafSeeds: {
		title: 'Striped Gold Leaf Seed'
		, level: 55
		, diesUntil: 70
		, time: 7*60
		, xp: 25e3
	}
	, essenceSeeds: {
		title: 'Essence Seed'
		, level: 60
		, diesUntil: 0
		, time: 3*60
		, xp: 30e3
	}
	, crystalLeafSeeds: {
		title: 'Crystal Leaf Seed'
		, level: 70
		, diesUntil: 85
		, time: 10*60
		, xp: 40e3
	}
	, megaDottedGreenLeafSeeds: {
		title: 'Mega Dotted Green Leaf Seed'
		, level: 70
		, diesUntil: 0
		, time: 16*60
		, xp: 12500
	}
	, megaRedMushroomSeeds: {
		title: 'Mega Red Mushroom Seed'
		, level: 70
		, diesUntil: 0
		, time: 16*60
		, xp: 20500
	}
	, essenceTreeSeeds: {
		title: 'Essence Tree Seed'
		, level: 80
		, diesUntil: 90
		, time: 12*60
		, xp: 50500
	}
	, megaGreenLeafSeeds: {
		title: 'Mega Green Leaf Seed'
		, level: 80
		, diesUntil: 0
		, time: 20*60
		, xp: 21e3
	}
	, stripedCrystalLeafSeeds: {
		title: 'Striped Crystal Leaf Seed'
		, level: 85
		, diesUntil: 95
		, time: 15*60
		, xp: 90e3
	}
	, megaBlewitMushroomSeeds: {
		title: 'Mega Blewit Mushroom Seed'
		, level: 85
		, diesUntil: 0
		, time: 20*60
		, xp: 21500
	}
	, megaLimeLeafSeeds: {
		title: 'Mega Lime Leaf Seed'
		, level: 85
		, diesUntil: 0
		, time: 23*60
		, xp: 32e3
	}
};
function fixFarming()
{
	const inputs = document.querySelectorAll('#dialog-planter input[type="image"]');
	for (let i = inputs.length-1; i >= 0; i--)
	{
		const input = inputs[i];
		const key = input.id.replace('planter-input-img-', '');
		const seed = seeds[key];
		input.title = seed.title;
	}

	if (!getSetting('reorderFarming'))
	{
		return;
	}

	let planterEl = inputs[0];
	const planterParent = planterEl.parentNode;
	let boxEl = document.querySelector('#farming-tab .inventory-item-box-farming').parentNode;
	const boxParent = boxEl.parentNode;
	const btnParent = document.getElementById('seed-menu-popup');
	let btnEl = btnParent.firstElementChild;
	for (let i = seedOrder.length-1; i >= 0; i--)
	{
		const key = seedOrder[i];
		const input = document.getElementById('planter-input-img-' + key);
		if (input)
		{
			planterParent.insertBefore(input, planterEl);
			planterParent.insertBefore(document.createTextNode(' '), planterEl);
			planterEl = input;
		}
		const box = document.getElementById('item-' + key + '-box');
		if (box)
		{
			boxParent.insertBefore(box.parentNode, boxEl);
			boxParent.insertBefore(document.createTextNode(' '), boxEl);
			boxEl = box.parentNode;
		}
		const btn = document.getElementById('btn-' + key);
		if (btn)
		{
			if (key.startsWith('mega'))
			{
				const title = {
					'megaDottedGreenLeafSeeds': 'Mega Dotted Green Leaf Seed (Stops dying at level 85)'
					, 'megaGreenLeafSeeds': 'Mega Green Leaf Seed (Stops dying at level 95)'
					, 'megaLimeLeafSeeds': 'Mega Lime Leaf Seed (Stops dying at level 95)'
				}[key];
				if (title)
				{
					btn.title = title;
				}
			}
			else
			{
				btn.title = btn.title.replace('dieing', 'dying');
			}
			btnParent.insertBefore(btn, btnEl);
			btnParent.insertBefore(document.createTextNode(' '), btnEl);
			btnEl = btn;
		}
	}

	const oldSelectSeedForPlanter = window.selectSeedForPlanter;
	window.selectSeedForPlanter = (seedChosen) =>
	{
		oldSelectSeedForPlanter(seedChosen);
		localStorage.setItem('farming.plantingSeed', seedChosen);
	}
	const seed = localStorage.getItem('farming.plantingSeed');
	if (window.bindedPlanter >= 1 && seed != null)
	{
		window.selectSeedForPlanter(seed);
	}
}



/**
 * fix server message
 */

function fixServerMsg()
{
	const serverMsgEl = document.querySelector('#server-inner-msg');
	if (!serverMsgEl)
	{
		return;
	}

	const serverMsg = serverMsgEl.textContent;
	const close = document.querySelector('#server-top-msg > *:last-child');
	if (localStorage.getItem('closedServerMsg') == serverMsg)
	{
		close.click();
		return;
	}

	close.addEventListener('click', function ()
	{
		localStorage.setItem('closedServerMsg', serverMsg);
	});
}



/**
 * highlight requirements
 */

const highlightBgColor = 'hsla(0, 100%, 90%, 1)';
const imgSrc2Key = {
	'bronzebar': 'bronzeBar'
	, 'ironbar': 'ironBar'
	, 'silverbar': 'silverBar'
	, 'goldbar': 'goldBar'
	, 'stonefurnace': 'stoneFurnace'
	, 'bronzefurnace': 'bronzeFurnace'
	, 'ironfurnace': 'ironFurnace'
	, 'silverfurnace': 'silverFurnace'
	, 'goldfurnace': 'goldFurnace'
	, 'pic_coin': 'coins'
	, 'stardust': 'starDust'
	, 'treasureKey': 'treasureChestKey'
	, 'dottedgreenleaf': 'dottedGreenLeaf'
	, 'redmushroom': 'redMushroom'
	, 'greenleaf': 'greenLeaf'
	, 'limeleaf': 'limeLeaf'
	, 'blewitmushroom': 'blewitMushroom'
	, 'goldleaf': 'goldLeaf'
	, 'pureWater': 'pureWaterPotion'
	, 'snapegrass': 'snapeGrass'
	, 'crystalleaf': 'crystalLeaf'
	, 'starDustConverter': 'starGemPotion'
	, 'superStargemPotion': 'superStarGemPotion'
	, 'superoilpotion': 'superOilPotion'
	, 'wooden_slave': 'miners'
	, 'fishingRodFarmer': 'fishingRod'
	, 'goldenStriper': 'goldenStriperPotion'
	, 'orb': 'orbOfTransformation'
	, 'anyorb': 'emptyBlueOrb'
	, 'anyorb2': 'emptyGreenOrb'
	, 'upgradedOrb': 'superOrbOfTransformation'
};
const imgSrc2LevelKey = {
	'watering-can': 'merchanting'
	, 'cookingskill': 'cooking'
	, 'archaeology': 'exploring'
	, 'wizardHatIcon': 'magic'
};
function amount2Int(str)
{
	return parseInt(str.replace(/M/i, '000000').replace(/B/i, '000000000').replace(/\D/g, ''), 10);
}
function checkRequirements(row, xpKey, init = true)
{
	const isRed = row.style.backgroundColor == 'rgb(255, 128, 128)';
	let everythingFulfilled = true;
	let keys2Observe = [];

	const levelEl = row.cells[2];
	const neededLevel = parseInt(levelEl.textContent, 10);
	const levelHighEnough = neededLevel <= window.getLevel(window[xpKey]);
	levelEl.style.color = levelHighEnough ? '' : 'red';
	everythingFulfilled = everythingFulfilled && levelHighEnough;
	keys2Observe.push(xpKey);

	const reqEl = row.cells[3];
	const children = reqEl.children;
	// check for each requirement if it is fulfilled
	for (let i = 0; i < children.length; i++)
	{
		const el = children[i];
		if (el.tagName != 'IMG')
		{
			continue;
		}
		const imgKey = el.src.replace(/^.+images\/.*?([^\/]+)\..+$/, '$1');
		const key = imgSrc2Key[imgKey] || imgKey;
		// wrap the amount with a span element
		let valueSpan = el.nextSibling;
		if (valueSpan.nodeType == Node.TEXT_NODE)
		{
			const valueTextNode = valueSpan;
			valueSpan = document.createElement('span');
			valueTextNode.parentNode.insertBefore(valueSpan, valueTextNode);
			valueSpan.appendChild(valueTextNode);
			valueTextNode.textContent = ' ' + formatNumbersInText(valueTextNode.textContent.trim());
		}

		const amount = amount2Int(valueSpan.textContent);
		const has = parseInt(window[key] || '0', 10);
		const isSkill = imgSrc2LevelKey.hasOwnProperty(key);
		let fulfilled = has >= amount;
		if (isSkill)
		{
			const xpKey = imgSrc2LevelKey[key] + 'Xp';
			fulfilled = window.getLevel(window[xpKey]) >= amount;
			keys2Observe.push(xpKey);
		}
		else if (key == 'gem')
		{
			fulfilled = window.sapphire >= amount || window.emerald >= amount || window.ruby >= amount || window.diamond >= amount;
			keys2Observe.push('sapphire', 'emerald', 'ruby', 'diamond');
		}
		else if (/furnace/i.test(key))
		{
			const furnaceLevel = furnaceLevels.indexOf(key.replace(/furnace/i, ''));
			fulfilled = fulfilled || parseInt(window.bindedFurnaceLevel, 10) >= furnaceLevel;
			keys2Observe.push(key, 'bindedFurnaceLevel');
		}
		else if (key == 'anybar')
		{
			const amountArray = valueSpan.parentNode.getAttribute('tooltip').replace(/\D*$/, '').split('/')
				.map(str => amount2Int(str));
			fulfilled = false;
			for (let i = 0; i < barTypes.length; i++)
			{
				const bar = barTypes[i];
				fulfilled = fulfilled || window[bar + 'Bar'] >= amountArray[i];
				keys2Observe.push(bar);
			}
		}
		else if (/(?:wand|staff)$/i.test(key))
		{
			const bindedKey = 'binded' + key[0].toUpperCase() + key.substr(1);
			fulfilled = fulfilled || window[bindedKey] > 0;
			keys2Observe.push(key, bindedKey);
		}
		else
		{
			if (!window.hasOwnProperty(imgKey) && !imgSrc2Key.hasOwnProperty(imgKey))
			{
				console.debug('missing key handling:', key, el);
			}
			keys2Observe.push(key);
		}
		valueSpan.style.color = fulfilled ? '' : 'red';
		everythingFulfilled = everythingFulfilled && (isSkill || fulfilled);
	}
	levelEl.style.backgroundColor = everythingFulfilled ? '' : highlightBgColor;
	reqEl.style.backgroundColor = everythingFulfilled ? '' : highlightBgColor;
	row.style.backgroundColor = everythingFulfilled ? 'rgb(194, 255, 133)' : 'rgb(255, 128, 128)';

	if (init)
	{
		observe(keys2Observe, () => checkRequirements(row, xpKey, false));
	}
}

function highlightRequirements()
{
	const craftingTables = {
		'crafting': {
			tabId: 'crafting'
			, xp: 'crafting'
		}
		, 'brewing': {
			tabId: 'brewing'
			, xp: 'brewing'
		}
		, 'achCraft': {
			tabId: 'archaeology-crafting'
			, xp: 'crafting'
		}
		, 'cooking': {
			tabId: 'cooking'
			, xp: 'cooking'
		}
		, 'magicCraft': {
			tabId: 'magiccrafting'
			, xp: 'crafting'
		}
		, 'spellbook': {
			tabId: 'spellbook'
			, xp: 'magic'
		}
	};
	for (let key in craftingTables)
	{
		const info = craftingTables[key];
		const xpName = info.xp + 'Xp';
		const table = document.querySelector('#' + info.tabId + '-tab table.table-stats');
		const rows = table.rows;
		for (let i = 0; i < rows.length; i++)
		{
			const row = rows[i];
			if (row.getElementsByTagName('th').length > 0 || row.id == 'craft-ghostKey')
			{
				continue;
			}

			checkRequirements(row, xpName, true);
		}
	}

	// hightlight mining level for mining table
	function imageSrc2BindedMachineVar(src)
	{
		return {
			'wooden_slave': 'miners'
			, 'rocket': 'bindedRocket'
			, 'mining-drill': 'bindedDrill'
			, 'crusher': 'bindedCrusher'
			, 'giantDrill': 'bindedGiantDrill'
			, 'roadHeader': 'bindedRoadHeader'
			, 'excavators': 'bindedBucketWheelExcavator'
			, 'diamond_pickaxe': 'bindedDiamondMiners'
			, 'giantBWE': 'bindedGiantBWE'
		}[src.replace(/^(?:.*\/)?([^\/]+)\.[^\.\/]+$/, '$1')];
	}
	const redColor = 'hsla(0, 100%, 75%, 1)';
	const lightRedColor = 'hsla(0, 100%, 90%, 1)';
	function highlightMiningLevel()
	{
		const miningLevel = window.getLevel(window.miningXp);
		const table = document.querySelector('#mining-tab table.table-stats');
		const rows = table.rows;
		for (let i = 2; i < rows.length; i++)
		{
			const row = rows[i];
			const level = parseInt(row.cells[2].textContent, 10);
			const highEnough = level <= miningLevel;
			const machineImg = row.cells[3].querySelector('img');
			const machineVar = imageSrc2BindedMachineVar(machineImg.src);
			const hasMachine = window[machineVar] > 0;
			const fulfilled = highEnough && hasMachine;
			row.cells[2].style.color = highEnough ? '' : 'red';
			machineImg.style.border = hasMachine ? '' : '2px solid red';
			row.cells[2].style.backgroundColor = fulfilled ? '' : lightRedColor;
			row.cells[3].style.backgroundColor = fulfilled ? '' : lightRedColor;
			row.style.backgroundColor = fulfilled ? '' : redColor;
		}
	}
	highlightMiningLevel();
	observe('miningXp', () => highlightMiningLevel());

	const oldLoadGhostPirates = window.loadGhostPirates;
	const ghostKeyRow = document.getElementById('craft-ghostKey');
	window.loadGhostPirates = () =>
	{
		oldLoadGhostPirates();
		if (ghostEssenceTimer > 0)
		{
			// this method is called once per second, so there is no need for observing any values
			checkRequirements(ghostKeyRow, 'craftingXp', false);
		}
	};

	function highlightFarmingLevel()
	{
		const farmingLevel = window.getLevel(window.merchantingXp);
		const seedBtns = document.querySelectorAll('#seed-menu-popup > div.dialogue-seed-btn');
		for (let i = 0; i < seedBtns.length; i++)
		{
			const seedBtn = seedBtns[i];
			const table = seedBtn.firstElementChild;
			const levelCell = table.rows[0].cells[1];
			const level = parseInt(levelCell.textContent.replace(/\D/g, ''), 10);
			const tooLow = level > farmingLevel;
			seedBtn.style.backgroundColor = tooLow ? 'hsla(0, 50%, 75%, 1)' : '';
			levelCell.style.color = tooLow ? 'red' : '';
			levelCell.style.textShadow = tooLow ? '0 0 5px white' : '';
		}
	}
	highlightFarmingLevel();
	observe('merchantingXp', () => highlightFarmingLevel());

	// achievement upgrades
	function highlightAchievementUpgrades()
	{
		const points = parseInt(window.achPoints, 10);
		const spans = document.querySelectorAll('span[id^="cost-ach-"][id$="AchUpgrade"]');
		for (let i = 0; i < spans.length; i++)
		{
			const span = spans[i];
			const notEnough = parseInt(span.textContent, 10) > points;
			span.style.setProperty('color', notEnough ? 'red' : '', 'important');
			span.style.fontWeight  = notEnough ? 'bold' : '';
		}
	}
	highlightAchievementUpgrades();
	observe('achPoints', () => highlightAchievementUpgrades());
}



/**
 * fix market
 */

function filterMarket(category, text)
{
	const tableAlone = document.getElementById('market-buy-table');
	const itemRows = tableAlone.rows;
	const dataBox = document.getElementById('market-data-box');

	for (let i = 1; i < itemRows.length; i++)
	{
		const row = itemRows[i];
		const itemType = row.getAttribute('item-type');
		const showCategory = category == 'all' || category == itemType;
		const itemTitle = row.title.toLowerCase();
		const showText = itemTitle.includes(text.toLowerCase());
		row.style.display = (showCategory && showText) ? '' : 'none';
	}

	dataBox.style.display = 'none';
	if (category == 'data')
	{
		document.getElementById('globals-taxes').textContent = formatNumber(window.globalTaxes);
		dataBox.style.display = 'inline-block';
	}
}
const itemCategories = {
	minerals: {
		title: 'Minerals'
		, items: [
			'stone'
			, 'copper'
			, 'tin'
			, 'iron'
			, 'silver'
			, 'gold'
			, 'quartz'
			, 'flint'
			, 'marble'
			, 'titanium'
			, 'moonStone'
			, 'promethium'
			, 'runite'
			, 'sapphire'
			, 'emerald'
			, 'ruby'
			, 'diamond'
			, 'dragonPickaxe'
		]
	}
	, bindables: {
		title: 'Crafting/Machinery + bars'
		, items: [
			'oil'
			, 'oilBarrel'
			, 'oilPipe'
			, 'pumpJack'
			, 'redPumpJack'
			, 'dragonPumpjacks'
			, 'oilFactory'
			, 'ghostPipeSheet'
			, 'ghostPipe1'
			, 'ghostPipe2'
			, 'ghostPipe3'
			, 'ghostPipe4'
			, 'ghostPipe5'
			, 'ghostPipe6'
			, 'trowel'
			, 'brewingKit'
			, 'silverFurnace'
			, 'goldFurnace'
			, 'ancientFurnace'
			, 'promethiumFurnace'
			, 'dragonFurnace'
			, 'promethiumWrench'
			, 'drill'
			, 'crusher'
			, 'giantDrill'
			, 'roadHeader'
			, 'bucketWheelExcavator'
			, 'sandCollector'
			, 'rocket'
			, 'robot'
			, 'bronzeBar'
			, 'ironBar'
			, 'silverBar'
			, 'goldBar'
			, 'promethiumBar'
			, 'runiteBar'
		]
	}
	, seeds: {
		title: 'Seeds'
		, items: [
			'redMushroomSeeds'
			, 'dottedGreenLeafSeeds'
			, 'greenLeafSeeds'
			, 'redMushroomTreeSeeds'
			, 'blewitMushroomSeeds'
			, 'limeLeafSeeds'
			, 'blewitMushroomTreeSeeds'
			, 'snapeGrassSeeds'
			, 'starDustSeeds'
			, 'appleTreeSeeds'
			, 'goldLeafSeeds'
			, 'starDustTreeSeeds'
			, 'stripedLeafSeeds'
			, 'essenceSeeds'
			, 'crystalLeafSeeds'
			, 'essenceTreeSeeds'
			, 'stripedCrystalLeafSeeds'
		]
	}
	, brewing: {
		title: 'Leafs + Brewing'
		, items: [
			'dottedGreenLeaf'
			, 'greenLeaf'
			, 'limeLeaf'
			, 'goldLeaf'
			, 'stripedLeaf'
			, 'crystalLeaf'
			, 'stripedCrystalLeaf'
			, 'redMushroom'
			, 'blewitMushroom'
			, 'greenMushroom'
			, 'snapeGrass'
			, 'strangeLeaf'
			, 'whaleTooth'
			, 'vial'
			, 'pureWaterPotion'
			, 'cactusWater'
			, 'swampWater'
			, 'starDustPotion'
			, 'superStarDustPotion'
			, 'megaStarDustPotion'
			, 'superCompostPotion'
			, 'explorersPotion'
			, 'chestPotion'
			, 'superChestPotion'
		]
	}
	, exploring: {
		title: 'Exploring'
		, items: [
			'silverOven'
			, 'goldOven'
			, 'promethiumOven'
			, 'runiteOven'
			, 'ancientOven'
			, 'dragonOven'
			, 'potato'
			, 'strawberry'
			, 'wheat'
			, 'apple'
			, 'strawberryPie'
			, 'rawShrimp'
			, 'shrimp'
			, 'rawSardine'
			, 'sardine'
			, 'rawTuna'
			, 'tuna'
			, 'tunaCooker'
			, 'rawSwordfish'
			, 'swordfish'
			, 'swordfishCooker'
			, 'rawShark'
			, 'shark'
			, 'sharkCooker'
			, 'rawWhale'
			, 'whale'
			, 'whaleCooker'
			, 'rawEel'
			, 'eel'
			, 'rawRainbowFish'
			, 'rainbowFishCooker'
			, 'dragonFishingRod'
			, 'fishingNet'
		]
	}
	, equipement: {
		title: 'Equipement'
		, items: [
			'promethiumHelmet'
			, 'runiteHelmet'
			, 'dragonHelmet'
			, 'promethiumBody'
			, 'runiteBody'
			, 'dragonBody'
			, 'promethiumLegs'
			, 'runiteLegs'
			, 'promethiumSword'
			, 'runiteSword'
			, 'dragonSword'
			, 'ancientShield'
			, 'amuletOfTheSea'
			, 'dragonAmulet'
			, 'coinRing'
			, 'lavaRing'
			, 'pureWaterRing'
		]
	}
	, magic: {
		title: 'Magic'
		, items: [
			'goldStaff'
			, 'promethiumStaff'
			, 'runiteStaff'
			, 'dragonStaff'
			, 'goldWand'
			, 'promethiumWand'
			, 'runiteWand'
			, 'dragonWand'
			, 'emptyEssence'
			, 'chargedMineralEssence'
			, 'chargedMetallicEssence'
			, 'chargedOilEssence'
			, 'chargedEnergyEssence'
			, 'chargedNatureEssence'
			, 'chargedOrbEssence'
			, 'chargedGemEssence'
			, 'dottedGreenRoots'
			, 'greenRoots'
			, 'limeRoots'
			, 'goldRoots'
			, 'stripedGoldRoots'
			, 'crystalRoots'
			, 'stripedCrystalRoots'
		]
	}
	, orbs: {
		title: 'Orbs'
		, items: [
			'orbOfTransformation'
			, 'superOrbOfTransformation'
			, 'upgradeEnchantedRake'
			, 'upgradeWrenchOrb'
			, 'upgradeOilPipe'
			, 'exploringOrb'
			, 'upgradeFurnaceOrb'
			, 'upgradePumpJackOrb'
			, 'upgradeEnchantedHammer'
			, 'greenPumpjackOrb'
			, 'greenWizardOrb'
			, 'rocketFuelOrb'
			, 'redBrewingKitOrb'
			, 'redFactoryOrb'
		]
	}
	, misc: {
		title: 'Misc'
		, items: [
			'starDust'
			, 'unboundDonorCoins'
			, 'pumpkinSigil'
			, 'santaHatSigil'
			, 'easterEggSigil'
			, 'ghostSigil'
			, 'treeSigil'
			, 'lava'
			, 'sapphireKey'
			, 'emeraldKey'
			, 'rubyKey'
			, 'treasureChestKey'
			, 'dragonKey'
			, 'treasureChestKey2'
			, 'tnt'
			, 'vendorRerollScroll'
			, 'ancientCrystal'
			, 'dragonAxe'
		]
	}
};
function fixMarket()
{
	// fix loading icons
	const loadingImgs = document.querySelectorAll('[src="images/loading_statique.png"]');
	for (var i = 0; i < loadingImgs.length; i++)
	{
		loadingImgs[i].src = 'images/loading.gif';
	}

	const oldLoadTradableTable = window.loadTradableTable;
	window.loadTradableTable = () =>
	{
		const tradableTable = document.getElementById('selling-tradable-table');
		while (tradableTable.childNodes.length > 0)
		{
			tradableTable.removeChild(tradableTable.firstChild);
		}

		window.platinumTradables = [];
		const itemList = window.tradableItems;
		const itemPrefix = 'tradable-item-';
		for (let i = 0; i < itemList.length; i++)
		{
			const item = itemList[i];
			const tradableData = item.split('~');
			const itemVarName = tradableData[0];
			const lowerLimit = tradableData[1];
			const upperLimit = tradableData[2];
			const isPlatinum = tradableData[3];

			if (document.getElementById(itemPrefix + itemVarName) != null)
			{
				continue;
			}

			const isPlat = parseInt(isPlatinum, 10) == 1;
			if (isPlat)
			{
				window.platinumTradables.push(itemVarName);
			}

			const inputEl = document.createElement('input');
			inputEl.type = 'image';
			inputEl.title = itemVarName;
			inputEl.src = window.getImagePath(itemVarName);
			inputEl.id = itemPrefix + itemVarName;
			inputEl.addEventListener('click', () =>
			{
				window.setItemNameToTradeInSlot(itemVarName, lowerLimit + '-' + upperLimit, (isPlat ? '1' : '0'));
			});
			inputEl.addEventListener('contextmenu', () =>
			{
				window.setItemNameToTradeInSlotLimits(itemVarName, lowerLimit + '-' + upperLimit, (isPlat ? '1' : '0'));
			});
			tradableTable.appendChild(inputEl);
		}

		// add categories for market items
		const tmp = document.createElement('div');
		tradableTable.insertBefore(tmp, tradableTable.firstChild);
		for (let key in itemCategories)
		{
			const category = itemCategories[key];
			const h3 = document.createElement('h3');
			h3.textContent = category.title;
			tradableTable.insertBefore(h3, tmp);

			for (let item of category.items)
			{
				const el = document.getElementById('tradable-item-' + item);
				if (el)
				{
					tradableTable.insertBefore(el, tmp);
				}
			}
		}
		tradableTable.removeChild(tmp);
	};
	if (window.hasOwnProperty('tradableItems'))
	{
		window.loadTradableTable();
	}

	// add style for category tabs
	const style = document.createElement('style');
	style.innerHTML = `
#selling-tradable-table h3
{
	margin-bottom: .25rem;
	margin-top: .75rem;
}
#selling-tradable-table input[type="image"]
{
	margin-right: .5rem;
	height: 50px;
	width: 50px;
}
#selling-tradable-table #tradable-item-trowel
{
	height: 16.7px;
	width: 50px;
}
#selling-tradable-table #tradable-item-orbOfTransformation,
#selling-tradable-table #tradable-item-superOrbOfTransformation
{
	margin-left: -12.5px;
	margin-right: calc(.5rem - 12.5px);
	width: 65px;
}
#selling-tradable-table #tradable-item-upgradeEnchantedHammer
{
	margin-left: -4px;
	margin-right: calc(.5rem - 4px);
	width: 58px;
}
#td-filter-market.selected
{
	background: -webkit-linear-gradient(#800000, #390000);
	background: -o-linear-gradient(#800000, #390000);
	background: -moz-linear-gradient(#800000, #390000);
	background: linear-gradient(#800000, #390000);
}
	`;
	document.head.appendChild(style);

	let lastFilterText = '';
	let lastFilterCategory = 'all';
	window.filterBuyables = (text) =>
	{
		lastFilterText = text;
		filterMarket(lastFilterCategory, lastFilterText);
	};
	window.setFilterTable = (itemFilter) =>
	{
		const row = document.querySelector('.market-filter-tbl-button');
		const oldBtn = row.querySelector('td.selected');
		if (oldBtn)
		{
			oldBtn.classList.remove('selected');
		}
		const filterBtn = row.querySelector(`td[onclick^="setFilterTable('${itemFilter}')"]`);
		if (filterBtn)
		{
			filterBtn.classList.add('selected');
		}
		lastFilterCategory = window.itemFilterGlobal = itemFilter;
	};
	window.filterTable = () =>
	{
		filterMarket(lastFilterCategory, lastFilterText);
	};
	const oldApplyToBuyingTable = window.applyToBuyingTable;
	window.applyToBuyingTable = (...args) =>
	{
		const ret = oldApplyToBuyingTable(...args);
		filterMarket(lastFilterCategory, lastFilterText);
		return ret;
	};
	window.setFilterTable(lastFilterCategory);

	// add "clear search"-button
	const searchInput = document.querySelector('input[onkeyup^="filterBuyables"]');
	searchInput.id = 'market-search';
	const tmpWrapper = createTemplateWrapper(`<input type="button" value="Clear search" style="float: left; margin-left: 10px;" onclick="$('#market-search').val('').keyup()">`);
	const parent = searchInput.parentNode;
	const el = searchInput.nextSibling;
	const childNodes = tmpWrapper.childNodes;
	for (let i = 0; i < childNodes.length; i++)
	{
		parent.insertBefore(childNodes[i], el);
	}

	// fix icon paths
	const oldGetImagePath = window.getImagePath;
	window.getImagePath = (itemVar) =>
	{
		if (itemVar == 'dragonFurnace')
		{
			return 'images/crafting/dragonFurnace.gif';
		}
		return oldGetImagePath(itemVar);
	};

	// auto focus the search input
	const oldSelectItemToTradeDialog = window.selectItemToTradeDialog;
	window.selectItemToTradeDialog = (sellOrBuy, slot) =>
	{
		oldSelectItemToTradeDialog(sellOrBuy, slot);
		window.$('#id_search').focus();
	};
}



/**
 * improve level calculation
 */

let levelXp = new Array(maxLevelVirtual+1);
function calcLevelXp(level)
{
	return level > 0 ? Math.round(Math.pow((level-1), 3 + ((level-1) / 200))) : 0;
}
function getLevelXp(level)
{
	return levelXp[level-1] || calcLevelXp(level);
}
const getDynamicLevel = (function ()
{
	const size = Math.pow(2, Math.ceil(Math.log2(maxLevel)));
	let xpTree = new Array(size);
	let levelTree = new Array(size);
	const sizeVirtual = Math.pow(2, Math.ceil(Math.log2(maxLevelVirtual)));
	let xpTreeVirtual = new Array(sizeVirtual);
	let levelTreeVirtual = new Array(sizeVirtual);
	createNode(xpTree, levelTree, 1, maxLevel, 0);
	createNode(xpTreeVirtual, levelTreeVirtual, 1, maxLevelVirtual, 0);

	function createNode(xpArray, levelArray, start, end, i)
	{
		const current = start + Math.pow(2, Math.floor(Math.log2(end - start + 1))) - 1;
		xpArray[i] = getLevelXp(current);
		levelArray[i] = current;

		if (current - start > 0)
		{
			createNode(xpArray, levelArray, start, current-1, 2*i + 1);
		}
		if (end - current > 0)
		{
			createNode(xpArray, levelArray, current+1, end, 2*i + 2);
		}
	}

	function getDynamicLevel(playerXP, useVirtual = false)
	{
		const isVirtual = window.virtualLevelsOn !== 0 && useVirtual === true;
		const xpArray = isVirtual ? xpTreeVirtual : xpTree;
		const levelArray = isVirtual ? levelTreeVirtual : levelTree;
		let i = 0;
		let level = 0;
		while (xpArray[i] != null)
		{
			if (playerXP == xpArray[i])
			{
				return levelArray[i];
			}
			else if (playerXP < xpArray[i])
			{
				i = 2*i+1;
			}
			else if (playerXP > xpArray[i])
			{
				level = levelArray[i];
				i = 2*i+2;
			}
		}
		return level;
	}

	return getDynamicLevel;
})();
function getLevel(playerXP)
{
	return getDynamicLevel(playerXP, false);
}
function getVirtualLevel(playerXP)
{
	return getDynamicLevel(playerXP, true);
}

function getGlobalLevel()
{
	return getDynamicGlobalLevel(false);
}
function getDynamicGlobalLevel(useVirtual = false)
{
	return Math.floor(getDynamicLevel(parseInt(window.miningXp, 10), useVirtual))
		+ Math.floor(getDynamicLevel(parseInt(window.craftingXp, 10), useVirtual))
		+ Math.floor(getDynamicLevel(parseInt(window.brewingXp, 10), useVirtual))
		+ Math.floor(getDynamicLevel(parseInt(window.merchantingXp, 10), useVirtual))
		+ Math.floor(getDynamicLevel(parseInt(window.exploringXp, 10), useVirtual))
		+ Math.floor(getDynamicLevel(parseInt(window.cookingXp, 10), useVirtual))
		+ Math.floor(getDynamicLevel(parseInt(window.magicXp, 10), useVirtual))
	;
}
function improveLevelCalculation()
{
	for (var i = 1; i < maxLevelVirtual; i++)
	{
		levelXp[i-1] = calcLevelXp(i);
	}

	const oldFns = {
		getLevel: window.getLevel
		, getVirtualLevel: window.getVirtualLevel
		, getGlobalLevel: window.getGlobalLevel
	};
	const newFns = {
		getLevel: getLevel
		, getVirtualLevel: getVirtualLevel
		, getGlobalLevel: getGlobalLevel
	};
	function switch2FastLevelCalculation()
	{
		const fns = getSetting('useFastLevelCalculation') ? newFns : oldFns;
		window.getLevel = fns.getLevel;
		window.getVirtualLevel = fns.getVirtualLevel;
		window.getGlobalLevel = fns.getGlobalLevel;
	}
	switch2FastLevelCalculation();
	observeSetting('useFastLevelCalculation', () => switch2FastLevelCalculation());
}



/**
 * fix inventory
 */

function fixInventory()
{
	const tab = document.getElementById('gatherings-tab');
	const coinImgs = tab.querySelectorAll('span[id^="item-"][id$="-box"] img[src="images/pic_coin.png"]');
	for (let i = 0; i < coinImgs.length; i++)
	{
		const coinImg = coinImgs[i];
		const price = coinImg.nextSibling;
		if (price.nodeType == Node.TEXT_NODE && !/\d/.test(price.textContent))
		{
			const parent = coinImg.parentNode;
			parent.removeChild(coinImg);
			parent.removeChild(price);
		}
	}
}



/**
 * fix machinery
 */

function getMachineCount(machine)
{
	return window['binded' + machine[0].toUpperCase() + machine.substr(1)];
}
function getOilValueFromMachine(machine)
{
	return (oilConsumption[machine] || 0) * getMachineCount(machine);
}
function updateRepairCost(machine)
{
	const input = document.getElementById('machineryChosenPopup');
	const repairCost = document.getElementById('repair-price-dialog');
	if (!input || !repairCost)
	{
		return;
	}

	machine = machine || input.value;
	const percent = window[machine + 'Repair'];
	const cost = window.getRepairCost(machine, percent);
	repairCost.textContent = formatNumber(cost);
}
function openOilDialogue(varname)
{
	const gearOnPath = 'images/spinning-gear.gif';
	const gearOffPath = 'images/spinning-gear-off.gif';
	const oilArea = document.getElementById('oilUsage-area');
	const oilValue = document.getElementById('oilUsage-value');
	const repairArea = document.getElementById('machinery-repair-area');

	let machine = varname.replace(/key-item-binded([^-]+)-box/, '$1');
	machine = machine[0].toLowerCase() + machine.substr(1);

	// machine name + count
	const name = machineNames[machine];
	const count = getMachineCount(machine);
	const max = 10; // don't know if there is a machine with a different limit...
	let title = document.getElementById('machinery-name');
	if (!title)
	{
		title = document.createElement('h3');
		title.style.marginTop = 0;
		title.id = 'machinery-name';
		const parent = document.getElementById('machinery-dialog');
		parent.insertBefore(title, parent.firstChild);
	}
	title.innerHTML = `${name} <span style="float: right;font-size: 1.2rem;">${count}<span style="font-weight: normal;">/${max}</span><span></span></span>`;

	// PROGRESS BAR
	var hasRepair = window.bindedPromethiumWrench > 0;
	if (machine == 'sandCollector')
	{
		// hide repair part (ensure, it is hidden)
		repairArea.setAttribute('style', 'padding: 0; width: 0px; height: 0px; overflow: hidden; border: 0;');
	}
	else
	{
		// show repair part if available
		repairArea.setAttribute('style', 'display: ' + (hasRepair ? 'block' : 'none') + ';');

		const progressBar = document.getElementById('progress-bar-repair-opened');
		const percent = window[machine + 'Repair'];
		const bgColor = percent < 20 ? 'yellow' : (percent >= 50 ? 'lime' : 'yellow');
		progressBar.style.backgroundColor = bgColor;
		progressBar.style.width = percent + '%';

		let repairButton = document.getElementById('repair-current-machine');
		if (!repairButton)
		{
			repairButton = document.createElement('button');
			repairButton.id = 'repair-current-machine';
			repairButton.style.lineHeight = '24px';
			repairButton.style.margin = '10px 5% 0';
			repairButton.style.width = '90%';
			repairButton.style.position = 'relative';
			repairButton.innerHTML = `<img id="bindedPromethiumWrenchOrb-img" src="images/crafting/promethiumWrench.png" alt="workers" width="23px" height="23px" style="position: absolute; top: 3px; left: 13px;">Repair for <span id="repair-price-dialog"></span><img src="images/pic_coin.png" width="25px" height="25px" style="vertical-align: middle;">`;
			repairButton.onclick = () =>
			{
				const machine = document.getElementById('machineryChosenPopup').value;
				window.send('REPAIR_MACHINERY=' + machine);
			};
			const parent = document.getElementById('machinery-repair-area');
			parent.appendChild(repairButton);
		}
		updateRepairCost(machine);
	}
	// END PROGRESS BAR

	oilValue.innerHTML = window.getOilValueFromMachine(machine);
	document.getElementById('machineryChosenPopup').value = machine;
	const isOn = window[machine + 'AreOn'] == 1;
	document.getElementById('myonoffswitch').checked = isOn;
	document.getElementById('myonoffswitch-gear').src = isOn ? gearOnPath : gearOffPath;
	oilArea.style.display = isOn ? '' : 'none';

	window.$('#machinery-dialog').dialog(
	{
		width: 400
	});
}
const smeltingBarRequirements = {
	glass: {
		oil: 12
		, ores: ['sand']
	}
	, bronze: {
		oil: 1
		, ores: ['copper', 'tin']
	}
	, iron: {
		oil: 50
		, ores: ['iron']
	}
	, silver: {
		oil: 150
		, ores: ['silver']
	}
	, gold: {
		oil: 500
		, ores: ['gold']
	}
	, promethium: {
		oil: 10e3
		, ores: ['promethium']
	}
};
function fixMachinery()
{
	const oldSetSmeltingBarAgain = window.setSmeltingBarAgain;
	let smeltingValue = null;
	window.setSmeltingBarAgain = (barType, amountElement) =>
	{
		// update max amount of ore
		const requirements = smeltingBarRequirements[barType] || { oil: 1, ores: [] };
		const value = parseInt(amountElement.value, 10);
		const furnaceMax = window.getFurnaceCapacityAgain(window.bindedFurnaceLevel);
		const maxOil = parseInt(window.oil, 10) / requirements.oil;
		const maxResource = requirements.ores
			.map((name) => parseInt(window[name], 10))
			.reduce((p, c) => Math.min(p, c), Number.MAX_SAFE_INTEGER)
		;
		const max = Math.min(furnaceMax, maxOil, maxResource);
		amountElement.max = max;
		if (max < value)
		{
			smeltingValue = value;
			amountElement.value = max;
		}
		else if (smeltingValue != null)
		{
			amountElement.value = smeltingValue;
		}

		oldSetSmeltingBarAgain(barType, amountElement);
		localStorage.setItem('smelting.bar', barType);
		localStorage.setItem('smelting.amount', window.amountToSmeltGlobal);
	};
	if (localStorage.getItem('smelting.bar') != null)
	{
		window.barTypeSelectedToSmeltGlobal = localStorage.getItem('smelting.bar');
	}
	if (localStorage.getItem('smelting.amount') != null)
	{
		window.amountToSmeltGlobal = localStorage.getItem('smelting.amount');
	}
	const oldChangeSmeltingValue = window.changeSmeltingValue;
	window.changeSmeltingValue = () =>
	{
		smeltingValue = null;
		window.setSmeltingBarAgain(
			window.barTypeSelectedToSmeltGlobal
			, document.getElementById('smeltingAmountRequested')
		);
	};
	const oldOpenFurnaceDialogue = window.openFurnaceDialogue;
	window.openFurnaceDialogue = () =>
	{
		const ret = oldOpenFurnaceDialogue();
		if (furnacePerc == 0)
		{
			const amountInput = document.getElementById('smeltingAmountRequested');
			if (amountInput.type != 'number')
			{
				amountInput.type = 'number';
				amountInput.style.width = '69px';
				amountInput.min = '0';
				amountInput.addEventListener('mouseup', (event) => window.changeSmeltingValue());
			}
			amountInput.max = window.getFurnaceCapacityAgain(window.bindedFurnaceLevel);
			window.changeSmeltingValue();
		}
		return ret;
	};

	const oldRapairMachinery = window.rapairMachinery;
	window.rapairMachinery = () =>
	{
		oldRapairMachinery();
		document.getElementById('perc-all-cost').innerHTML = formatNumber(window.getRepairCost('all', 0));
	};

	const furnaceCapacaties = [0, 10, 30, 75, 150, 300, 500, 750, 1000, 1250];
	function upgradeFurnaceOrb()
	{
		if (window.bindedUpgradeFurnaceOrb != 1)
		{
			return;
		}

		for (let i = 1; i < furnaceLevels.length; i++)
		{
			let furnaceType = furnaceLevels[i];
			furnaceType = furnaceType[0].toUpperCase() + furnaceType.substr(1);
			const capacity = 1.5 * furnaceCapacaties[i];
			const box = document.getElementById('key-item-binded' + furnaceType + 'Furnace-box');
			let textNode = box.lastChild;
			if (textNode.nodeType !== Node.TEXT_NODE)
			{
				textNode = textNode.lastChild;
			}
			textNode.textContent = ' ' + formatNumber(capacity);
		}
	}
	upgradeFurnaceOrb();
	observe('bindedUpgradeFurnaceOrb', () => upgradeFurnaceOrb());

	if (!getSetting('improveMachineryDialog'))
	{
		return;
	}

	window.getOilValueFromMachine = getOilValueFromMachine;
	window.openOilDialogue = openOilDialogue;

	observe(['drillRepair', 'crusherRepair', 'giantDrillRepair', 'roadHeaderRepair', 'bucketWheelExcavatorRepair', 'giantBWERepair'], () => updateRepairCost());
}



/**
 * fix brewing
 */

const potionRequirements = {
	'seedPotion': {
		level: 5
		, dottedGreenLeaf: 5
		, redMushroom: 100
		, greenLeaf: 1
	}
	, 'miningPotion': {
		level: 20
		, limeLeaf: 5
		, dottedGreenLeaf: 20
		, blewitMushroom: 50
	}
};
let oldCanBrewItem;
function canBrewItem(command)
{
	var requirements = potionRequirements[command];
	if (!requirements)
	{
		return oldCanBrewItem(command);
	}

	for (var key in requirements)
	{
		if (key == 'level')
		{
			if (getLevel(brewingXp) < requirements.level)
			{
				return false;
			}
		}
		else if (window[key] < requirements[key])
		{
			return false;
		}
	}
	return true;
}
function fixBrewing()
{
	oldCanBrewItem = window.canBrewItem;
	window.canBrewItem = canBrewItem;

	// fix alignment of brewing items
	const style = document.createElement('style');
	style.innerHTML = `
#brewing-tab center > .item-box-spot
{
	text-align: left;
}
	`;
	document.head.appendChild(style);

	const marginFix = '5px 20px';
	const potionItems = document.querySelectorAll('#brewing-tab [id$="Potion-box"] img.item-box-img');
	for (let i = 0; i < potionItems.length; i++)
	{
		potionItems[i].style.margin = marginFix;
	}
	const smallImgItems = ['vial','enchantedVial','compost'];
	for (let item of smallImgItems)
	{
		document.querySelector('#item-' + item + '-box img.item-box-img').style.margin = marginFix;
	}
}



/**
 * fix tabs
 */

const tabs2Fix = {
	repair: {
		name: 'Machinery'
		, url: 'https://www.reddit.com/r/DiamondHunt/wiki/online/tabs/machinery'
	}
	, store: {
		name: 'Market'
		, url: 'https://www.reddit.com/r/DiamondHunt/wiki/online/tabs/market'
	}
	, 'npc-store': {
		name: 'Game Shop'
		, url: 'https://www.reddit.com/r/DiamondHunt/wiki/online/tabs/market/game'
	}
	, 'donor-store': {
		name: 'Donor Shop'
		, url: 'https://www.reddit.com/r/DiamondHunt/wiki/online/tabs/market/donor'
	}
	, 'player-store': {
		name: 'Player Market'
		, url: 'https://www.reddit.com/r/DiamondHunt/wiki/online/tabs/market/player'
	}
	, stats: {
		name: 'Leaderboards'
		, url: 'https://www.reddit.com/r/DiamondHunt/wiki/online/tabs/stats'
	}
	, coop: {
		name: 'Group Tasks'
		, url: 'https://www.reddit.com/r/DiamondHunt/wiki/online/tabs/coop'
	}
	, collectables: {
		name: 'Collectables'
	}
	, miningEngineer: {
		name: 'Mining Engineer'
	}
	, 'ach-explore': {
		name: 'Exploring — Equipment'
		, url: 'https://www.reddit.com/r/DiamondHunt/wiki/online/tabs/exploration#wiki_equipment'
	}
};
function tabTitleLink2Span(title)
{
	const span = title.parentNode;
	span.appendChild(title.firstChild);
	span.removeChild(title);
	span.setAttribute('tooltip', '');
	span.style.color = 'gold';
	span.style.fontSize = '24pt';
}
function fixTabs()
{
	function removeElement(el)
	{
		el.parentNode.removeChild(el);
	}
	/**
	 * some special treatment
	 */

	const achievementTitle = document.querySelector('#ach-tab a');
	tabTitleLink2Span(achievementTitle);

	const npcH1 = document.querySelector('#npc-store-tab h1');
	removeElement(npcH1);

	const vendorBr = document.querySelector('#vendor-tab > br:first-child');
	removeElement(vendorBr);
	const vendorTitle = document.querySelector('#vendor-tab a');
	tabTitleLink2Span(vendorTitle);

	const wizardBr = document.querySelector('#wizard-tab > br:first-child');
	removeElement(wizardBr);

	const achTitle = document.querySelector('#archaeology-tab a');
	achTitle.title = '';
	const achCraftTitle = document.querySelector('#archaeology-crafting-tab a');
	achCraftTitle.textContent = 'Exploring — Crafting'.toUpperCase();
	tabTitleLink2Span(achCraftTitle);
	const cookingTitle = document.querySelector('#cooking-tab a');
	cookingTitle.textContent = 'Exploring — Cooking'.toUpperCase();
	cookingTitle.title = '';
	cookingTitle.parentNode.setAttribute('tooltip', 'Open Wiki');

	const magicSpellbookTitle = document.querySelector('#spellbook-tab a');
	magicSpellbookTitle.textContent = 'Magic — spellbook'.toUpperCase();
	const magicCraftTitle = document.querySelector('#magiccrafting-tab a');
	magicCraftTitle.textContent = 'Magic — Crafting'.toUpperCase();
	tabTitleLink2Span(magicCraftTitle);

	removeElement(document.querySelector('#repair-tab > br:last-child'));
	removeElement(document.querySelector('#repair-tab > br:last-child'));
	removeElement(document.querySelector('#miningEngineer-tab br'));
	removeElement(document.querySelector('#brewing-tab br'));
	removeElement(document.querySelector('#archaeology-tab br'));
	removeElement(document.querySelector('#ach-explore-tab br'));
	removeElement(document.querySelector('#ach-explore-tab br'));
	const archCraftBr = document.querySelector('#archaeology-crafting-tab br');
	archCraftBr.parentNode.insertBefore(document.createElement('br'), archCraftBr);
	removeElement(document.querySelector('#cooking-tab br'));
	removeElement(document.querySelector('#cooking-tab br'));
	removeElement(document.querySelector('#cooking-tab br'));
	removeElement(document.querySelector('#magic-tab br'));
	removeElement(document.querySelector('#magiccrafting-tab br'));
	removeElement(document.querySelector('#magiccrafting-tab br'));
	for (let i = 0; i < 10; i++)
	{
		removeElement(document.querySelector('#magiccrafting-tab > span + br'));
	}
	removeElement(document.querySelector('#store-tab br'));
	removeElement(document.querySelector('#player-store-tab br'));
	removeElement(document.querySelector('#stats-tab br'));
	removeElement(document.querySelector('#stats-tab br'));
	removeElement(document.querySelector('#grouptasks-createorjoin br'));
	removeElement(document.querySelector('#grouptasks-notstarted br'));
	removeElement(document.querySelector('#grouptasks-notstarted br'));
	removeElement(document.querySelector('#grouptasks-started br'));


	for (let key in tabs2Fix)
	{
		const tab = tabs2Fix[key];
		const tabEl = document.getElementById(key + '-tab');
		let html = '<center>';
		if (tab.url)
		{
			html += `<span class="activate-tooltip">
				<a class="title-link" href="${tab.url}" target="_blank" title="Open Wiki">${tab.name.toUpperCase()}</a>
			</span>`;
		}
		else
		{
			html += `<span class="activate-tooltip" style="color: gold; font-size: 24pt;">
				${tab.name.toUpperCase()}
			</span>`;
		}
		html += '</center><br>';
		const tmpEl = createTemplateWrapper(html);
		let el = tabEl.firstElementChild;
		for (let i = tmpEl.children.length-1; i >= 0; i--)
		{
			const child = tmpEl.children[i];
			tabEl.insertBefore(child, el);
			el = child;
		}
	}
}



/**
 * hide crafting recipes
 */

const recipes = {
	shovel: ['shovel']
	, promethiumWrench: ['promethiumWrench', 'bindedPromethiumWrench']
	, glassBlowingPipe: ['glassBlowingPipe', 'bindedGlassBlowingPipe']
	, oilPipe: ['oilPipe', 'bindedOilPipe']
	, planter: ['planter', 'bindedPlanter']
	, trowel: ['trowel', 'bindedTrowel']
	, shootingStarCrystal: ['shootingStarCrystal', 'bindedShootingStarCrystal']
	, brewingKit: ['brewingKit', 'brewingKitBinded']
	, rocket: ['rocket', 'bindedRocket']
	, redPumpJack: ['redPumpJack', 'bindedRedPumpJack']
	, explorersBrush: ['explorersBrush', 'bindedExplorersBrush']
	, oilFactory: ['oilFactory', 'bindedOilFactory']
	, diamondMiners: ['diamondMiners', 'bindedDiamondMiners']
	, robot: ['robot', 'bindedRobot']
	, oilRefinery: ['oilRefinery', 'bindedOilRefinery']
	, superRobot: ['superRobot', 'bindedSuperRobot']
	, superTNT: ['superTNT', 'dragonFlagBlewUpWall']
	// , fishingRod: ['bronzeRod', 'ironRod', 'goldRod', 'promethiumRod', 'fishingRod', 'dragonFishingRod', 'bindedDragonFishingRod']
	, fishingRod: ['fishingRod', 'dragonFishingRod', 'bindedDragonFishingRod']
	, fishingBoat: ['fishingBoat', 'bindedFishingBoat']
	, largeFishingBoat: ['largeFishingBoat', 'bindedLargeFishingBoat']
};
function hideCraftingRecipes()
{
	(function hideFurnaceRecipes(init = false)
	{
		let maxFurnaceLevel = parseInt(window.bindedFurnaceLevel, 10);
		let keys2Observe = ['bindedFurnaceLevel'];
		for (let i = furnaceLevels.length-1; i >= 0; i--)
		{
			const varName = furnaceLevels[i] + 'Furnace';
			if (window[varName] > 0)
			{
				maxFurnaceLevel = Math.max(maxFurnaceLevel, i);
			}

			const row = document.getElementById('craft-' + furnaceLevels[i] + 'Furnace');
			if (row)
			{
				const hide = getSetting('hideSomeCraftRecipes') && i <= maxFurnaceLevel;
				row.style.display = hide ? 'none' : '';
				keys2Observe.push(varName);
			}
		}

		if (init)
		{
			observe(keys2Observe, () => hideFurnaceRecipes(false));
			observeSetting('hideSomeCraftRecipes', () => hideFurnaceRecipes(false));
		}
	})(true);

	function hideRecipe(key, nameList, init = false)
	{
		const hide = getSetting('hideSomeCraftRecipes') && nameList.some(name => window[name] != 0);
		document.getElementById('craft-' + key).style.display = hide ? 'none' : '';

		if (init)
		{
			observe(nameList, () => hideRecipe(key, nameList, false));
			observeSetting('hideSomeCraftRecipes', () => hideRecipe(key, nameList, false));
		}
	}
	for (let key in recipes)
	{
		hideRecipe(key, recipes[key], true);
	}

	// exploring - crafting
	(function hideOvenRecipes(init = false)
	{
		let maxOvenLevel = -1;
		let keys2Observe = [];
		for (let i = ovenLevels.length-1; i >= 0; i--)
		{
			const type = ovenLevels[i];
			const ovenName = type + 'Oven';
			const bindedOvenName = 'binded' + ovenName[0].toUpperCase() + ovenName.substr(1);
			if (window[ovenName] > 0 || window[bindedOvenName] > 0)
			{
				maxOvenLevel = Math.max(maxOvenLevel, i);
			}
			const row = document.getElementById('craft-' + type + 'Oven');
			if (row)
			{
				const hide = getSetting('hideSomeCraftRecipes') && maxOvenLevel >= i;
				row.style.display = hide ? 'none' : '';
				keys2Observe.push(ovenName, bindedOvenName);
			}
		}

		if (init)
		{
			observe(keys2Observe, () => hideOvenRecipes(false));
			observeSetting('hideSomeCraftRecipes', () => hideOvenRecipes(false));
		}
	})(true);

	// exploring - equipment
	function hideEquipmentRecipe(key, type, init = false)
	{
		let highestLevel = parseInt(window[key + 'SlotId'], 10) - 1;
		for (let i = barTypes.length-1; i >= 0; i--)
		{
			const bar = barTypes[i];
			if (window[bar + type] > 0)
			{
				highestLevel = Math.max(highestLevel, i);
			}
			const row = document.getElementById('craft-' + bar + type);
			if (row)
			{
				const hide = getSetting('hideSomeCraftRecipes') && highestLevel >= i;
				row.style.display = hide ? 'none' : '';
			}
		}

		if (init)
		{
			observe(key + 'SlotId', () => hideEquipmentRecipe(key, type, false));
			for (let i = barTypes.length-1; i >= 0; i--)
			{
				const bar = barTypes[i];
				observe(bar + type, () => hideEquipmentRecipe(key, type, false));
			}
			observeSetting('hideSomeCraftRecipes', () => hideEquipmentRecipe(key, type, false));
		}
	}
	const equipmentTypes = {
		'weapon': 'Sword'
		, 'helmet': 'Helmet'
		, 'body': 'Body'
		, 'leg': 'Legs'
	};
	for (let key in equipmentTypes)
	{
		hideEquipmentRecipe(key, equipmentTypes[key], true);
	}

	// magic - crafting
	const magicRodTypes = ['gold', 'promethium', 'runite', 'dragon'];
	(function hideWandRecipe(init = false)
	{
		let maxWandLevel = -1;
		let keys2Observe = [];
		for (let i = magicRodTypes.length-1; i >= 0; i--)
		{
			const type = magicRodTypes[i];
			const wandName = type + 'Wand';
			const bindedWandName = 'binded' + wandName[0].toUpperCase() + wandName.substr(1);
			if (window[wandName] > 0 || window[bindedWandName] > 0)
			{
				maxWandLevel = Math.max(maxWandLevel, i);
			}
			const wandRow = document.getElementById('craft-' + type + 'Wand');
			if (wandRow)
			{
				const hide = getSetting('hideSomeCraftRecipes') && maxWandLevel >= i;
				wandRow.style.display = hide ? 'none' : '';
				keys2Observe.push(wandName, bindedWandName);
			}
		}

		if (init)
		{
			observe(keys2Observe, () => hideWandRecipe(false));
			observeSetting('hideSomeCraftRecipes', () => hideWandRecipe(false));
		}
	})(true);
	(function hideStaffRecipe(init = false)
	{
		let maxStaffLevel = -1;
		let keys2Observe = [];
		for (let i = magicRodTypes.length-1; i >= 0; i--)
		{
			const type = magicRodTypes[i];
			const staffName = type + 'Staff';
			const bindedStaffName = 'binded' + staffName[0].toUpperCase() + staffName.substr(1);
			if (window[staffName] > 0 || window[bindedStaffName] > 0)
			{
				maxStaffLevel = Math.max(maxStaffLevel, i);
			}
			const staffRow = document.getElementById('craft-' + type + 'Staff');
			if (staffRow)
			{
				const hide = getSetting('hideSomeCraftRecipes') && maxStaffLevel >= i;
				staffRow.style.display = hide ? 'none' : '';
				keys2Observe.push(staffName, bindedStaffName);
			}
		}

		if (init)
		{
			observe(keys2Observe, () => hideStaffRecipe(false));
			observeSetting('hideSomeCraftRecipes', () => hideStaffRecipe(false));
		}
	})(true);
}



/**
 * hide equipment
 */

const equipmentId2Type = {
	general: ['', 'Bronze', 'Iron', 'Silver', 'Gold', 'Promethium', 'Runite', 'Dragon']
	, amulet: ['', 'Amulet of the Sea', 'Moonstone Amulet'/*??? TBD*/, 'Enchanted Amulet of the Sea', 'Dragon Amulet']
	, shield: ['', 'Ancient Shield']
	, ring: ['', 'Coin Ring', 'Pure Water Ring', 'Lava Ring']
	, secondRing: ['', 'Looting Gloves']
};
const equipmentTypes = {
	'weapon': 'Sword'
	, 'helmet': 'Helmet'
	, 'body': 'Body'
	, 'leg': 'Legs'
};
const equipmentTypeList = ['helmet', 'amulet', 'weapon', 'body', 'shield', 'ring', 'leg', 'secondRing'];
const equipmentLevels = ['bronze', 'iron', 'silver', 'gold', 'promethium', 'runite', 'dragon'];
const equipmentType2Name = {
	'weapon': 'sword'
	, 'leg': 'legs'
};
function setEquippedList(listCell, init)
{
	let keys2Observe = [];
	let list = [];
	for (let type of equipmentTypeList)
	{
		const id = parseInt(window[type + 'SlotId'], 10);
		keys2Observe.push(type + 'SlotId');
		type = equipmentType2Name[type] || type;
		if (!equipmentId2Type.hasOwnProperty(type))
		{
			list.push(equipmentId2Type.general[id] + ' ' + type[0].toUpperCase() + type.substr(1));
		}
		else
		{
			list.push(equipmentId2Type[type][id]);
		}
	}
	listCell.textContent = list.filter(str => str != '').join(', ');

	if (init)
	{
		for (let key of keys2Observe)
		{
			observe(key, () => setEquippedList(listCell, false));
		}
	}
}
function examineEquipmentRecipes(key, type, init = false)
{
	const currentLevel = parseInt(window[key + 'SlotId'], 10);
	// hide not more than gold equipment
	for (let i = 0; i < equipmentLevels.length; i++)
	{
		const el = document.getElementById('item-' + equipmentLevels[i] + type + '-box');
		if (el)
		{
			const hide = getSetting('hideEquipment') && i < 4 && i < currentLevel;
			el.parentNode.style.display = hide ? 'none' : '';
		}
	}

	if (init)
	{
		observe(key + 'SlotId', () => examineEquipmentRecipes(key, type, false));
		observeSetting('hideEquipment', () => examineEquipmentRecipes(key, type, false));
	}
}
function hideEquipment()
{
	const table = document.querySelector('#ach-explore-tab table.equipement-area-table');
	const row = table.insertRow(-1);
	row.style.borderTop = '1px dashed';
	const nameCell = row.insertCell(-1);
	nameCell.style.verticalAlign = 'top';
	nameCell.textContent = 'Equipped:';
	const listCell = row.insertCell(-1);
	listCell.colSpan = 2;
	listCell.textContent = '';
	setEquippedList(listCell, true);

	for (let key in equipmentTypes)
	{
		examineEquipmentRecipes(key, equipmentTypes[key], true);
	}
}



/**
 * improve dialog buttons
 */

function improveDialogBtns()
{
	function isOnlyMessageBox(yesButtonVal)
	{
		return getSetting('improveDialogBtns') && (yesButtonVal == null || yesButtonVal == '');
	}

	const oldOpenDialogue = window.openDialogue;
	window.openDialogue = (title, message, yesButtonVal) =>
	{
		const [okBtn, cancelBtn] = document.querySelectorAll('#dialog #buttonCommandYes ~ input[type="button"]');
		// restore default state
		const empty = isOnlyMessageBox(yesButtonVal);
		okBtn.style.display = empty ? 'none' : '';
		okBtn.value = 'OK';
		cancelBtn.value = empty ? 'Close' : 'Cancel';
		if (getSetting('improveDialogBtns'))
		{
			if (/stardust/i.test(title))
			{
				okBtn.value = 'Smash it';
			}
			else if (/bind/i.test(title))
			{
				okBtn.value = 'Bind';
			}
			else if (/drink/i.test(title))
			{
				okBtn.value = 'Drink';
			}
			else if (/^EXPLORE/.test(yesButtonVal))
			{
				okBtn.value = 'Start expedition';
			}
			else if (/^OPEN_LOOT=/.test(yesButtonVal))
			{
				okBtn.value = 'Open';
			}
		}

		return oldOpenDialogue(title, message, yesButtonVal);
	};

	const oldOpenDialogueWidth = window.openDialogueWidth;
	window.openDialogueWidth = (title, message, yesButtonVal, widthWanted) =>
	{
		const [okBtn, cancelBtn] = document.querySelectorAll('#dialog #buttonCommandYes ~ input[type="button"]');
		// restore default state
		const empty = isOnlyMessageBox(yesButtonVal);
		okBtn.style.display = empty ? 'none' : '';
		okBtn.value = 'OK';
		cancelBtn.value = empty ? 'Close' : 'Cancel';
		return oldOpenDialogueWidth(title, message, yesButtonVal, widthWanted);
	};

	const oldClicksKeyItem = window.clicksKeyItem;
	window.clicksKeyItem = (varname) =>
 	{
		oldClicksKeyItem(varname);
		if (getSetting('improveDialogBtns') && varname == 'key-item-bindedRocket-box' && window.rocketTimer == 0)
		{
			const [okBtn, cancelBtn] = document.querySelectorAll('#dialog #buttonCommandYes ~ input[type="button"]');
			okBtn.value = 'Start rocket';
			const textEl = document.querySelector('#dialog #dialog-text');
			textEl.removeChild(textEl.lastChild);
			textEl.removeChild(textEl.lastChild);
			textEl.removeChild(textEl.lastChild);
		}
	};

	const oldOpenFurnaceDialogue = window.openFurnaceDialogue;
	window.openFurnaceDialogue = () =>
	{
		oldOpenFurnaceDialogue();
		if (getSetting('improveDialogBtns') && window.furnacePerc > 0)
		{
			const [okBtn, cancelBtn] = document.querySelectorAll('#dialog #buttonCommandYes ~ input[type="button"]');
			okBtn.value = 'Cancel smelting';
			cancelBtn.value = 'Close';
		}
	};

	const oldOpenAreaDialogue = window.openAreaDialogue;
	window.openAreaDialogue = () =>
	{
		oldOpenAreaDialogue();
		if (getSetting('improveDialogBtns') && window.exploringTimer > 0)
		{
			const [okBtn, cancelBtn] = document.querySelectorAll('#dialog #buttonCommandYes ~ input[type="button"]');
			okBtn.value = 'Cancel trip';
			cancelBtn.value = 'Close';
		}
	};
}



/**
 * expand equipment
 */

function expandEquipment()
{
	if (!getSetting('expandEquipment'))
	{
		return;
	}

	const equipmentRows = document.querySelectorAll('tr[onclick^="openCraftSwordDialogue"]');
	const rowParent = equipmentRows[0].parentNode;
	let newRows = [];
	for (let i = 0; i < equipmentRows.length; i++)
	{
		const row = equipmentRows[i];
		const type = row.getAttribute('onclick').replace(/openCraftSwordDialogue\('([^']+)'\);/, '$1');
		const levels = row.cells[2].textContent.split('/');
		const barCosts = row.cells[3].getAttribute('tooltip').replace(/\D*$/, '').split('/');
		for (let i = 0; i < barTypes.length; i++)
		{
			const bar = barTypes[i];
			const newRow = row.cloneNode(true);
			newRow.id = 'craft-' + bar + type;
			newRow.setAttribute('onclick', '');
			newRow.cells[0].textContent = bar[0].toUpperCase() + bar.substr(1) + ' ' + type;
			newRow.cells[1].firstElementChild.src = 'images/exploring/equipement/' + bar + type + '.png';
			newRow.cells[2].textContent = levels[i];
			newRow.cells[3].firstElementChild.src = 'images/minerals/' + bar + 'Bar.png';
			newRow.cells[3].lastChild.textContent = ' ' + barCosts[i];
			((item) =>
			{
				newRow.addEventListener('click', () => window.craftItem(item));
			})(bar + type);
			newRows.push({
				level: parseInt(levels[i], 10)
				, row: newRow
			});
		}
		rowParent.removeChild(row);
	}
	newRows = newRows.sort((a, b) => a.level - b.level);

	// insert new rows into table
	const rows = rowParent.rows;
	let idx = 0;
	for (let i = 0; i < rows.length && idx < newRows.length; i++)
	{
		const row = rows[i];
		if (row.getElementsByTagName('th').length > 0)
		{
			continue;
		}

		const thisLevel = parseInt(row.cells[2].textContent, 10);
		while (newRows[idx] && newRows[idx].level < thisLevel)
		{
			rowParent.insertBefore(newRows[idx].row, row);
			idx++;
		}
	}
	for (; idx < newRows.length; idx++)
	{
		rowParent.appendChild(newRows[idx].row);
	}
}



/**
 * apply new item style
 */

function applyNewItemStyle()
{
	if (!getSetting('applyNewItemStyle'))
	{
		return;
	}

	// change how the items are styled
	const style = document.createElement('style');
	style.innerHTML = `
span[class^="inventory-item-box"],
#vendor-tab span.shop-box,
#ach-tab span.shop-box-ach,
div[id$="-store-tab"] span.shop-box,
span.shop-box-ach
{
	position: relative;
}
span[class^="inventory-item-box"] img.item-box-img,
#vendor-tab img[id^="vendor-item-img"],
#ach-tab span.shop-box-ach img:first-of-type,
div[id$="-store-tab"] span.shop-box img:first-of-type,
#grp-shop-tab span.shop-box-ach img:first-of-type,
div[id^="miningEngineer-"][id$="-tab"] span.shop-box-ach > img:first-of-type
{
	position: absolute;
	margin: 0 !important;
}
img.item-box-img[height="30px"]  { top: 52.5px; }
img.item-box-img[height="55px"]  { top: 40px; }
img.item-box-img[height="60px"]  { top: 37.5px; }
img.item-box-img[height="70px"]  { top: 32.5px; }
img.item-box-img[height="75px"]  { top: 30px; }
img.item-box-img[height="80px"]  { top: 27.5px; }
img.item-box-img[height="85px"]  { top: 25px; }
img.item-box-img[height="90px"]  { top: 20px; }
img.item-box-img[height="100px"] { top: 9px; }
span[id^="item-binded"] > img.item-box-img[height="100px"]  { top: 17.5px; }
img.item-box-img[height="110px"] { top: 12.5px; }
img.item-box-img[width="55px"]  { left: 42.5px; }
img.item-box-img[width="60px"]  { left: 40px; }
img.item-box-img[width="70px"]  { left: 35px; }
img.item-box-img[width="75px"]  { left: 32.5px; }
img.item-box-img[width="80px"]  { left: 30px; }
img.item-box-img[width="90px"]  { left: 25px; }
img.item-box-img[width="100px"] { left: 20px; }
img.item-box-img[width="110px"] { left: 15px; }
img.item-box-img[width="120px"] { left: 10px; }
span[class^="inventory-item-box"] img.item-box-img[height="60px"][width="60px"] { transform: scale(1.2); }
span[class^="inventory-item-box"] img.item-box-img[height="70px"][width="80px"] { transform: scale(1.2); }
/* this is a special case (converting items into stardust) */
#wizard-tab img.item-box-img[height="50px"] { top: 22px; }
#wizard-tab img.item-box-img[width="50px"] { left: 45px; }

#vendor-tab img[id^="vendor-item-img"]
{
	transform: scale(1.1);
}
/* height: 155px */
img[id^="vendor-item-img"][height="85x"]  { top: 35px; }
/* width: 150px (110px + 40px) */
img[id^="vendor-item-img"][width="80px"]  { left: 35px; }

span[class^="inventory-item-box"] span[id$="mount"],
#ancientCrystalChargesSpan,
#vendor-tab span.box-title,
#ach-tab span[id^="cost-ach-"][id$="AchUpgrade"],
div[id$="-store-tab"] span[id$="-cost"],
#grp-shop-tab span[id$="-cost"],
div[id^="miningEngineer-"][id$="-tab"] span.shop-box-ach span[id^="perk"],
#magicBookPages,
#item-treasureMap-box > span:last-child,
#item-treasureMap2-box > span:last-child,
#item-ghostSpawned1-box > span:last-child
{
	background-color: black;
	border-top: 1px solid rgba(255, 255, 255, 0.5);
	color: white !important;
	font-weight: normal;
	margin: 0 !important;
	padding: 3px;
	text-align: center;
	position: absolute;
	bottom: 0;
	left: 0;
	right: 0;
}
span[class^="inventory-item-box"] span[id$="mount"]:not(#energy-amount),
#vendor-tab span.box-title
{
	padding-right: 9px;
}
span[class^="inventory-item-box"] span[id$="mount"]:not(#energy-amount)::before,
#vendor-tab span.box-title::before
{
	content: '${String.fromCharCode(215)}';
	margin-right: 3px;
}
#fishfarmer-img
{
	top: 18px;
}
#fishingRodAmount
{
	display: none;
}
#hasMapOfTheSea-fishermen
{
	position: absolute;
	bottom: 3px;
	left: calc(50% - 10px);
}
span[class^="inventory-item-box"] span[id$="-price"],
#vendor-tab span[id^="vendor-item-cost"]
{
	font-weight: normal;
	padding-left: 20px;
	position: relative;
	top: 1px;
}
#vendor-tab span[id^="vendor-item-cost"]
{
	font-size: inherit !important;
}
body.hide-zero-price span[class^="inventory-item-box"] span[id$="-price"]:empty,
body.hide-zero-price #sandstone-price,
body.hide-zero-price #moonStone-price,
body.hide-zero-price #glass-price,
body.hide-zero-price #promethiumBar-price,
body.hide-zero-price #runiteBar-price,
body.hide-zero-price #ancientBar-price,
body.hide-zero-price #lava-price,
body.hide-zero-price #brewingKitBinded-price,
body.hide-zero-price #stripedLeaf-price,
body.hide-zero-price #stripedCrystalLeaf-price,
body.hide-zero-price #greenMushroom-price,
body.hide-zero-price #whaleTooth-price,
body.hide-zero-price #snapeGrass-price,
body.hide-zero-price #strangeLeaf-price,
body.hide-zero-price #pureWaterPotion-price,
body.hide-zero-price #cactusWater-price,
body.hide-zero-price #swampWater-price,
body.hide-zero-price #ghostEssence-price,
body.hide-zero-price #ghostRemains-price,
body.hide-zero-price span[id$="Potion-price"]
{
	visibility: hidden;
}
span[class^="inventory-item-box"] span[id$="-price"]::before,
#vendor-tab span[id^="vendor-item-cost"]::before,
div[id$="-store-tab"] span[id$="-cost"]::before,
#grp-shop-tab span[id$="-cost"]::before
{
	content: '';
	display: inline-block;
	width: 20px;
	height: 20px;
	position: absolute;
	left: 0;
	background-image: url('images/pic_coin.png');
	background-size: 20px 20px;
}
#shop-ghostPirates-cost::before
{
	background-image: url('images/pic_coin2.png');
}
#grp-shop-tab span[id$="-cost"]::before
{
	background-image: url('images/icons/groupTaskTokens.png');
}
#grp-shop-tab #grp-chests-badge-cost::before
{
	background-image: url('images/icons/groupTaskBadge4.png');
}
span[class^="inventory-item-box"]:not(.inventory-item-box-smaller) img[src="images/pic_coin.png"],
#vendor-tab img[src="images/pic_coin.png"],
#npc-store-tab img[src^="images/pic_coin"],
#npc-store-tab img[src="images/icons/stats.png"],
#npc-store-tab img[src="images/crafting/anyOrb.png"],
#npc-store-tab img[src="images/spinning-gear-off.gif"],
#donor-store-tab img ~ img[src="images/donor_coin.png"],
#donor-store-tab span[id$="-cost"] img[src="images/donor_coin.png"],
#grp-shop-tab img[src^="images/icons/groupTask"][id^="group-"],
#grp-shop-tab #grp-shop-badge-price-img
{
	display: none;
}
#ach-tab span[id^="cost-ach-"][id$="AchUpgrade"]::before
{
	content: '';
	display: inline-block;
	width: 20px;
	height: 20px;
	position: absolute;
	top: 2px;
	left: 4px;
	background-image: url('images/shop/ach.png');
	background-size: 20px 20px;
}
#ach-tab span.box-title,
div[id^="miningEngineer-"][id$="-tab"] span.box-title
{
	font-size: 14pt;
	font-weight: normal;
	position: relative;
	top: 4px;
}
#ach-tab span.shop-box-ach img:first-of-type[height="60px"] { top: 47.5px; }
#ach-tab span.shop-box-ach img:first-of-type[height="80px"] { top: 37.5px; }
#ach-tab span.shop-box-ach img:first-of-type[width="55px"] { left: 47.5px; }
#ach-tab span.shop-box-ach img:first-of-type[width="80px"] { left: 35px; }
#ach-tab span.shop-box-ach img[src="images/shop/ach.png"]
{
	display: none;
}
#ach-tab span.shop-box-ach img[src="images/division/check.png"],
#grp-shop-tab img[src="images/division/check.png"]
{
	position: absolute;
	bottom: 5px;
	left: calc(50% - 10px);
}

#npc-store-tab span.box-title,
#donor-store-tab span.box-title,
#grp-shop-tab span.box-title
{
	font-size: 1.1rem;
	font-weight: bold;
	margin: 0;
	padding: 4px;
	position: absolute;
	left: 0;
	right: 0;
}
#donor-store-tab span.box-title
{
	font-size: 1.02rem;
}
#shop-coop-level-cost,
#shop-miningEngineer-machines-cost,
#grp-shop-tab #grp-chests-badge-cost
{
	bottom: 26px;
}
#shop-coop-level-cost::before
{
	background-image: url('images/icons/stats.png');
	left: 1px;
}
#shop-wizard-cost::before
{
	background-image: url('images/crafting/anyOrb.png');
}
#shop-miningEngineer-machines-cost::before
{
	background-color: white;
	background-image: url('images/spinning-gear-off.gif');
}
#npc-store-tab img:first-of-type[height="60px"] { top: 47.5px; }
#npc-store-tab img:first-of-type[height="65px"] { top: 45px; }
#npc-store-tab img:first-of-type[height="70px"] { top: 42.5px; }
#npc-store-tab img:first-of-type[height="80px"] { top: 37.5px; }
#npc-store-tab img:first-of-type[height="85x"] { top: 35px; }
#npc-store-tab img:first-of-type[height="100x"] { top: 27.5px; }
#npc-store-tab img:first-of-type[width="60px"] { left: 45px; }
#npc-store-tab img:first-of-type[width="65px"] { left: 42.5px; }
#npc-store-tab img:first-of-type[width="80px"] { left: 35px; }
#npc-store-tab img:first-of-type[width="100px"] { left: 25px; }

#donor-store-tab img:first-of-type[height="80px"] { top: 37.5px; }
#donor-store-tab img:first-of-type[width="80px"] { left: 35px; }
#donor-store-tab span[id$="-cost"]::before
{
	background-image: url('images/donor_coin.png');
}

#grp-shop-tab img[id^="grp-shop-"][height="80px"] { top: 37.5px; }
#grp-shop-tab img[id^="grp-shop-"][width="80px"] { left: 35px; }
#grp-shop-tab img[id^="grp-shop-"][width="90px"] { left: 30px; }
#grp-shop-tab img[src="images/division/check.png"] + span
{
	display: none;
}

img[src^="images/perks/"][height="80px"] { top: 45px; }
img[src^="images/perks/"][width="80px"] { left: 36px; }
span.shop-box-ach span[id^="perk"] > img[src="images/spinning-gear-off.gif"]
{
	background-color: white;
	position: absolute;
	left: 3px;
}
span.shop-box-ach span[id^="perk"] > img[src^="images/division/"]
{
	position: absolute;
	right: 3px;
}
	`;
	document.head.appendChild(style);
	// remove line breaks
	const brs = document.querySelectorAll(
		'[class^="inventory-item-box"] br'
		+ ', span.shop-box br'
		+ ', #ach-tab span.shop-box-ach br'
		+ ', #grp-shop-tab span.shop-box-ach br'
		+ ', div[id^="miningEngineer-"][id$="-tab"] span.shop-box-ach br'
	);
	let i = 0;
	while (brs[i] != null)
	{
		if (!brs[i].parentNode)
		{
			i++;
			continue;
		}
		brs[i].parentNode.removeChild(brs[i]);
	}
	// give the emerald image the correct class name
	const emeraldAmount = document.getElementById('emeraldAmount');
	const previous = emeraldAmount && emeraldAmount.previousElementSibling;
	previous && previous.classList.add('item-box-img');

	// wrap some requirements in npc-shop
	const shopBoxes = document.querySelectorAll('div[id$="-store-tab"] span.shop-box');
	for (let i = 0; i < shopBoxes.length; i++)
	{
		const box = shopBoxes[i];
		if (box.id && box.id.startsWith('shop-enchanted'))
		{
			const boxTitle = box.querySelector('.box-title');
			boxTitle.firstChild.textContent += ' ';
		}
		const children = box.childNodes;
		let foundImg = false;
		let wrapper;
		const idList = {
			'shop-coopUnlocked-box': ['shop-coop-cost', 'shop-coop-level-cost']
			, 'shop-hasVendor-box': ['shop-vendor-cost']
			, 'shop-wizard-box': ['shop-wizard-cost']
			, 'shop-achShop-box': ['shop-achShop-cost']
			, 'shop-miningEngineer-box': ['shop-miningEngineer-cost', 'shop-miningEngineer-machines-cost']
			, 'donor-shop-hasExtraOfflineTimer-box': ['shop-extraOfflineTimer-cost']
			, '': ['shop-offlineTimer-cost']
		}[box.id] || [];
		for (let j = 0; j < children.length; j++)
		{
			const child = children[j];
			if (!foundImg && child.tagName == 'IMG')
			{
				foundImg = true;
			}
			else if (foundImg && child.nodeType == Node.TEXT_NODE)
			{
				wrapper = document.createElement('span');
				wrapper.id = idList.shift() || '';
				box.insertBefore(wrapper, child);
				wrapper.appendChild(child);
			}
			else if (foundImg && wrapper != null)
			{
				wrapper.appendChild(child);
				j--;
			}
		}
	}

	// wrap some requirements in group shop
	const grpBoxes = document.querySelectorAll('#grp-shop-tab span.shop-box-ach');
	const idList = ['grp-badge-cost', 'grp-more-points-cost', 'grp-eels-cost', 'grp-promethium-cost', 'grp-chests-cost', 'grp-chests-badge-cost', 'grp-gloves-cost'];
	for (let i = 0; i < grpBoxes.length; i++)
	{
		const box = grpBoxes[i];
		const children = box.childNodes;
		let foundImg = false;
		let wrapper;
		for (let j = 0; j < children.length; j++)
		{
			const child = children[j];
			if (!foundImg && child.tagName == 'IMG')
			{
				foundImg = true;
			}
			else if (foundImg && wrapper == null)
			{
				wrapper = document.createElement('span');
				wrapper.id = idList.shift();
				box.insertBefore(wrapper, child);
				wrapper.appendChild(child);
			}
			else if (foundImg && wrapper != null)
			{
				if (child.nodeName == 'IMG')
				{
					box.insertBefore(child, wrapper);
					wrapper = null;
				}
				else
				{
					wrapper.appendChild(child);
					j--;
				}
			}
		}
	}

	// add wrapper elements for the perk-levels in mining engineer tab
	const perks = document.querySelectorAll('div[id^="miningEngineer-"][id$="-tab"] span.shop-box-ach');
	for (let i = 0; i < perks.length; i++)
	{
		const perk = perks[i];
		const childNodes = perk.childNodes;
		let foundImg = false;
		let wrapperInserted = false;
		let wrapper = document.createElement('span');
		wrapper.id = perk.getAttribute('onclick')
			.replace(/send\('([^']+)'\)/, '$1')
			.replace(/[=~]/g, '-')
			.toLowerCase()
		;
		for (let j = 0; j < childNodes.length; j++)
		{
			const child = childNodes[j];
			if (!foundImg && child.tagName == 'IMG')
			{
				foundImg = true;
			}
			else if (foundImg)
			{
				if (!wrapperInserted)
				{
					perk.insertBefore(wrapper, child);
					j++;
					wrapperInserted = true;
				}
				wrapper.appendChild(child);
				j--;
			}
		}
	}
	// fix tooltip
	const perkEl = document.querySelector(`span.activate-tooltip[tooltip="Giants drills mine 40% more quartz."]`);
	perkEl.setAttribute('tooltip', 'Giants drills mine 40% more iron.');

	function setVisibilityOfUnnecessaryPrices()
	{
		const hide = getSetting('hideUnnecessaryPrice');
		document.body.classList[hide ? 'add' : 'remove']('hide-zero-price');
	}
	setVisibilityOfUnnecessaryPrices();
	observeSetting('hideUnnecessaryPrice', () => setVisibilityOfUnnecessaryPrices());
}



/**
 * apply new key item style
 */

function applyNewKeyItemStyle()
{
	if (!getSetting('applyNewKeyItemStyle'))
	{
		return;
	}

	// change how key items and machinery is styled
	const style = document.createElement('style');
	style.innerHTML = `
span[class$="-inventory-item-box"]
{
	position: relative;
}
span.item-box-title
{
	color: blue;
	font-weight: bold;
	padding: 4px 8px;
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	z-index: 1;
}
.tooltip
{
	z-index: 10;
}
span.item-box-title > img[src="images/oil.png"]
{
	display: block;
	margin: 0 auto;
}
span.item-box-title > img[src^="images/spinning-gear"]
{
	margin-right: 3px;
	margin-left: -10px;
}
span[class$="-inventory-item-box"] > img
{
	position: absolute;
}
/* heights */
span[class$="-inventory-item-box"] > img[height="80px"]  { top: 60px; }
span[class$="-inventory-item-box"] > img[height="90px"]  { top: 55px; }
span[class$="-inventory-item-box"] > img[height="100px"]  { top: 50px; }
span[class$="-inventory-item-box"] > img[height="110px"]  { top: 45px; }
span[class$="-inventory-item-box"] > img[height="120px"]  { top: 40px; }
span[class$="-inventory-item-box"] > img[height="130px"]  { top: 35px; }
span[class$="-inventory-item-box"] > img[height="140px"]  { top: 30px; }
/* widths */
span[class$="-inventory-item-box"] > img[width="70px"]  { left: 35px; }
span[class$="-inventory-item-box"] > img[width="80px"]  { left: 30px; }
span[class$="-inventory-item-box"] > img[width="90px"]  { left: 25px; }
span[class$="-inventory-item-box"] > img[width="100px"]  { left: 20px; }
span[class$="-inventory-item-box"] > img[width="110px"]  { left: 15px; }
span[class$="-inventory-item-box"] > img[width="120px"]  { left: 10px; }
#key-item-bindedGlassBlowingPipe-box img
{
	top: 75px;
}
span.ghostPipe-wrapper
{
	display: flex;
	flex-wrap: wrap;
	position: absolute;
	top: 66px;
	left: 19px;
	width: 102px;
}
span.ghostPipe-wrapper > img
{
	box-shadow: 0 0 2px black;
	margin: 2px;
}
span.text-wrapper
{
	background-color: black;
	border-top: 1px solid rgba(255, 255, 255, 0.5);
	color: white;
	font-weight: normal;
	line-height: 22px;
	position: absolute;
	bottom: 0;
	left: 0;
	right: 0;
}
span.text-wrapper span[id$="Amount"],
#level-global-4
{
	font-weight: bold;
}
#key-item-handheldOilPump-box span.text-wrapper
{
	display: none;
}
span.text-wrapper img
{
	margin-right: 3px;
}
span.text-wrapper .small-perc-bar
{
	background-color: black;
	border: 0;
	margin: 0;
	padding: 0;
	position: absolute;
	top: -11px;
	left: 0;
	right: 0;
	width: auto;
}
span.text-wrapper .small-perc-bar-inner
{
	background-color: rgba(0, 210, 0, 1) !important;
}
span.text-wrapper .small-perc-bar-inner[style*="background-color: yellow;"]
{
	background-color: rgba(255, 255, 0, 1) !important;
}
span.text-wrapper .small-perc-bar-inner[style*="background-color: red;"]
{
	background-color: rgba(255, 0, 0, 1) !important;
}
	`;
	document.head.appendChild(style);

	const brs = document.querySelectorAll('span[class$="-inventory-item-box"] br');
	let i = 0;
	while (brs[i] != null)
	{
		const br = brs[i];
		const parent = br.parentNode;
		if (!parent)
		{
			i++;
			continue;
		}
		if (parent.classList.contains('item-box-title'))
		{
			parent.insertBefore(document.createTextNode(' '), br);
		}
		parent.removeChild(br);
	}

	const spans = document.querySelectorAll('span[class$="-inventory-item-box"]');
	let ghostWrapper;
	for (let i = 0; i < spans.length; i++)
	{
		const span = spans[i];
		const childs = span.childNodes;
		let wrapper;
		let foundImg = false;
		for (let j = 0; j < childs.length; j++)
		{
			const child = childs[j];
			if (!foundImg && child.tagName == 'IMG')
			{
				if (/ghostPipeHolder/.test(child.id))
				{
					if (!ghostWrapper)
					{
						ghostWrapper = document.createElement('span');
						ghostWrapper.className = 'ghostPipe-wrapper';
						child.parentNode.insertBefore(ghostWrapper, child);
						j++;
					}
					ghostWrapper.appendChild(child);
					j--;

					if (child.id == 'ghostPipeHolder6')
					{
						foundImg = true;
					}
				}
				else
				{
					foundImg = true;
				}
			}
			else if (foundImg)
			{
				if (!wrapper)
				{
					wrapper = document.createElement('span');
					wrapper.className = 'text-wrapper';
					span.insertBefore(wrapper, child);
					j++;
				}
				wrapper.appendChild(child);
				j--;
			}
		}
		if (wrapper && wrapper.textContent == '')
		{
			wrapper.parentNode.removeChild(wrapper);
		}
	}
}



/**
 * hide recipes of maxed machinery
 */

function hideMachineRecipe(key, max, init)
{
	const bindedKey = 'binded' + key[0].toUpperCase() + key.substr(1);
	const row = document.getElementById('craft-' + key);
	if (row)
	{
		const amount = parseInt(window[key], 10) + parseInt(window[bindedKey], 10);
		const hide = getSetting('hideMaxRecipes') && amount >= max();
		row.style.display = hide ? 'none' : '';
		if (init)
		{
			observe(key, () => hideMachineRecipe(key, max, false));
			observe(bindedKey, () => hideMachineRecipe(key, max, false));
			observeSetting('hideMaxRecipes', () => hideMachineRecipe(key, max, false));
		}
	}
}
function defaultMaxFn()
{
	return 10;
}
function calcPumpjackMax()
{
	let maxPumpjacks = 10;
	if (window.bindedUpgradePumpJackOrb == 1)
	{
		maxPumpjacks += 5;
	}
	if (window.bindedGreenPumpjackOrb == 1)
	{
		maxPumpjacks += 10;
	}
	return maxPumpjacks;
}
function hideMaxRecipes()
{
	const machinery = ['drill', 'crusher', 'giantDrill', 'sandCollector', 'roadHeader', 'bucketWheelExcavator', 'giantBWE'];
	for (let key of machinery)
	{
		hideMachineRecipe(key, defaultMaxFn, true);
	}

	// handle pump jacks with its upgrades
	hideMachineRecipe('pumpJack', calcPumpjackMax, true);
	observe('bindedUpgradePumpJackOrb', () => hideMachineRecipe('pumpJack', calcPumpjackMax, false));
	observe('bindedGreenPumpjackOrb', () => hideMachineRecipe('pumpJack', calcPumpjackMax, false));
}



/**
 * fix magic
 */

const essenceMultiplier = {
	mineral: {
		stone: 20e6
		, copper: 10e6
		, tin: 10e6
		, iron: 5e6
		, silver: 2e6
		, gold: 1e6
		, quartz: 100e3
		, flint: 50e3
		, marble: 10e3
		, titanium: 5e3
		, promethium: 100
		, runite: 2
	}
	, oil: {
		oil: 5e7
		, rocketFuel: 1
	}
	, nature: {
		dottedGreenRoots: 18
		, greenRoots: 13
		, limeRoots: 6
		, goldRoots: 3
		, stripedGoldRoots: 1
		, crystalRoots: v => parseInt(v / 2) + 1
		, stripedCrystalRoots: v => parseInt(v / 4) + 1
	}
	, metallic: {
		bronzeBar: 1000
		, ironBar: 700
		, silverBar: 500
		, goldBar: 300
		, promethiumBar: 25
		, runiteBar: 1
	}
	, energy: {
		shrimp: 50
		, sardine: 20
		, tuna: 4
		, swordfish: 1
		, shark: v => parseInt(v / 3) + 1
		, whale: v => parseInt(v / 6) + 1
	}
	, orb: {
		blue: 3
		, green: 1
		, red: v => parseInt(v / 3) + 1
	}
	, gem: {
		sapphire: 8
		, emerald: 3
		, ruby: 1
		, diamond: v => parseInt(v / 5) + 1
	}
};
const essenceObserver = new Map();
function essenceCellStyle(cell, fulfilled)
{
	cell.style.backgroundColor = fulfilled ? '' : 'red';
	cell.style.color = fulfilled ? '' : 'white';
}
function essenceSetFulfilled(key, value, el)
{
	const fulfilled = window[key] >= value;
	essenceCellStyle(el.previousElementSibling, fulfilled);
	essenceCellStyle(el, fulfilled);
	essenceCellStyle(el.nextElementSibling, fulfilled);
}
// thanks /u/Vomera for suggesting this
function essenceRequirements(amount, type)
{
	if (essenceObserver.has(type))
	{
		essenceObserver.get(type).forEach((fn, key) =>
		{
			unobserve(key, fn);
		});
	}

	const observerMap = new Map();
	const makeString = 'make' + type[0].toUpperCase() + type.substr(1) + 'Essence';
	for (let key in essenceMultiplier[type])
	{
		const elId = makeString + key[0].toUpperCase() + key.substr(1) + '-needed';
		const el = document.getElementById(elId);
		const mult = essenceMultiplier[type][key];
		const value = amount == 0 ? 0 : (typeof mult === 'function' ? mult(amount) : amount * mult);
		el.textContent = formatNumber(value);

		const windowKey = type == 'orb' ? 'empty' + key[0].toUpperCase() + key.substr(1) + 'Orb' : key;
		essenceSetFulfilled(windowKey, value, el);
		const observeFn = observe(windowKey, () => essenceSetFulfilled(windowKey, value, el));
		observerMap.set(windowKey, observeFn);
	}
	essenceObserver.set(type, observerMap);
}
function fixMagic()
{
	// move roots to magic panel
	const parent = document.getElementById('magic-tab');
	const roots = document.querySelectorAll('[id$="Roots-box"]');
	for (let i = 0; i < roots.length; i++)
	{
		const el = roots[i].parentNode;
		el.setAttribute('tooltip', el.getAttribute('tooltip').replace(/ \(Used in the magic skill\)$/, ''));
		parent.appendChild(el);
	}
	const style = document.createElement('style');
	style.innerHTML = `
#magic-tab .inventory-item-box-farming
{
	float: left;
}
	`;
	document.head.appendChild(style);

	// add wrapper for collected number of magic book pages
	const magicBookBox = document.getElementById('item-magicBook-box');
	const magicBookChildren = magicBookBox.childNodes;
	const magicPagesWrapper = document.createElement('span');
	magicPagesWrapper.id = 'magicBookPages';
	let foundImg = false;
	for (let i = 0; i < magicBookChildren.length; i++)
	{
		const child = magicBookChildren[i];
		if (!foundImg && child.tagName == 'IMG')
		{
			foundImg = true;
		}
		else if (foundImg)
		{
			magicPagesWrapper.appendChild(child);
			i--;
		}
	}
	magicBookBox.appendChild(magicPagesWrapper);

	// improve tooltip of spell book
	const magicBook = magicBookBox.parentNode;
	function updateTooltip()
	{
		const pages = [];
		for (let i = 1; i <= 6; i++)
		{
			if (window['bindedMagicPage' + i] == '1')
			{
				pages.push(i);
			}
		}
		const pagesString = pages.length === 0 ? '-' : pages.join(', ');
		magicBook.setAttribute('tooltip', `Spell Book (binded pages: ${pagesString})`);
	}
	updateTooltip();
	observe([
		'bindedMagicPage1'
		, 'bindedMagicPage2'
		, 'bindedMagicPage3'
		, 'bindedMagicPage4'
		, 'bindedMagicPage5'
		, 'bindedMagicPage6'
	], () => updateTooltip());

	const oldEmptyEssenceDialogue2 = window.emptyEssenceDialogue2;
	window.emptyEssenceDialogue2 = (type) =>
	{
		oldEmptyEssenceDialogue2(type);
		if (type == 'nature')
		{
			const input = document.querySelector(
				'#emptyEssence2-dialog-nature table.table-stats tr:last-child > td:last-child input'
			);
			if (input && input.style.display != 'none')
			{
				input.style.display = 'none';
				const inputCell = input.parentNode;
				inputCell.style.border = 0;
				const neededCell = inputCell.previousElementSibling;
				neededCell.style.border = 0;
				const imgCell = neededCell.previousElementSibling;
				imgCell.style.border = 0;
			}
		}
	};
	window.refreshOresValuesWhenMakingEssences = (amount) =>
	{
		essenceRequirements(amount, 'mineral');
	};
	window.refreshOilValuesWhenMakingEssences = (amount) =>
	{
		essenceRequirements(amount, 'oil');
	};
	window.refreshSeedsValuesWhenMakingEssences = (amount) =>
	{
		essenceRequirements(amount, 'nature');
	};
	window.refreshBarValuesWhenMakingEssences = (amount) =>
	{
		essenceRequirements(amount, 'metallic');
	};
	window.refreshFoodValuesWhenMakingEssences = (amount) =>
	{
		essenceRequirements(amount, 'energy');
	};
	window.refreshOrbValuesWhenMakingEssences = (amount) =>
	{
		essenceRequirements(amount, 'orb');
	};
	window.refreshGemValuesWhenMakingEssences = (amount) =>
	{
		essenceRequirements(amount, 'gem');
	};
}



/**
 * fix number format
 */

function fixNumberFormat()
{
	// fix achievements
	const achievementFixes = {
		'achABiggerWall': 'Sell excactly ' + formatNumber(1e7) + ' stone to the shop.'
		, 'ach1000Potions': 'Drink a total of ' + formatNumber(1e3) + ' potions. '
		, 'achMaxInt': 'Have a total of ' + formatNumber(Math.pow(2, 31)-1) + ' ores in your inventory. '
		, 'achSmelter': 'Smelt a total of ' + formatNumber(5e5) + ' bars. '
	};
	for (let id in achievementFixes)
	{
		const row = document.getElementById(id);
		row.cells[1].firstChild.textContent = achievementFixes[id];
	}
	const oldLoadAchievements = window.loadAchievements;
	window.loadAchievements = () =>
	{
		oldLoadAchievements();
		document.getElementById('total-potions-drank').innerHTML = formatNumber(window.totalPotionsDrank) + ' potions drank';
		document.getElementById('total-bars-smelted').innerHTML = formatNumber(window.totalBarsSmelted) + ' bars smelted';
		document.getElementById('statVendor2').innerHTML = formatNumber(window.statVendor);
		document.getElementById('total-spellsCasted-ach').innerHTML = formatNumber(window.spellsCasted);
	};

	function checkAchievementRow(row)
	{
		if (window[row.id] != 1)
		{
			return false;
		}
		const pinkSpan = row.cells[1].children[0];
		if (pinkSpan)
		{
			pinkSpan.style.color = 'blue';
		}
		return true;
	}
	const table = document.querySelector('#ach-tab table.table-stats');
	const rows = table.rows;
	for (let i = 0; i < rows.length; i++)
	{
		const row = rows[i];
		if (row.id && !checkAchievementRow(row))
		{
			observe(row.id, () => checkAchievementRow(row));
		}
	}

	// fix explorers energy
	const oldExplorerTick = window.explorerTick;
	window.explorerTick = () =>
	{
		oldExplorerTick();
		const energyElement = document.getElementById('energy-amount');
		energyElement.innerHTML = 'Energy: ' + formatNumber(window.energy);
	};
	let startingText = new Map();
	function updateEnergy()
	{
		const exploringEls = document.querySelectorAll(
				'.inventory-item-box-exploring'
			+ ', .inventory-item-box-exploring-artifact'
		);
		for (let i = 0; i < exploringEls.length; i++)
		{
			const el = exploringEls[i];
			const firstChild = el.firstChild;
			if (firstChild.nodeType != Node.TEXT_NODE)
			{
				continue;
			}

			const id = el.id.replace(/^item-(.+)-box$/, '$1');
			if (!startingText.has(id))
			{
				startingText.set(id, firstChild.textContent);
			}
			let text = startingText.get(id);
			if (['shrimp', 'sardine', 'tuna', 'swordfish', 'eel', 'shark', 'whale', 'rainbowFish'].includes(id) &&
				/\+\d(?:[\d',\.]*\d[kK]?)?\s*E(?:nergy)?/.test(text))
			{
				// TODO: is amuletSlotId == 2 for enchantedAmuletOfTheSea?
				const mult = window.amuletSlotId == 1 ? 1.1 : window.amuletSlotId == 2 ? 1.15 : 1;
				text = text.replace(/\d(?:[\d',\.]*\d)?/, (numStr) =>
				{
					return Math.floor(parseInt(numStr.replace(/\D/g, ''), 10) * mult);
				});
			}
			firstChild.textContent = formatNumbersInText(text);
		}
	}
	updateEnergy();
	observe('amuletSlotId', () => updateEnergy());
	
	// fix energy format in cooking table
	const cookingTable = document.querySelector('#cooking-tab table.table-stats');
	const cookingRows = cookingTable.rows;
	for (let i = 0; i < cookingRows.length; i++)
	{
		const row = cookingRows[i];
		const descriptionCell = row.cells[row.cells.length - 2];
		descriptionCell.textContent = formatNumbersInText(descriptionCell.textContent);
	}

	// fix leveling up in crafting
	window.setConvertBarToXpOnKeyDown = (amount) =>
	{
		var starDustCounter = window.bindedUpgradeEnchantedHammer >= 1 ? 10 : 13;
		document.getElementById('enchantedHammer-XP-hint-box').style.display = 'block';
		document.getElementById('enchantedHammer-XP-earned').innerHTML = '+'
			+ formatNumber(getXPEarnedWhenConvertingBar(barToConvertToXpType) * amount.value);

		document.getElementById('enchantedHammer-XP-total-stardust-cost').innerHTML = '-'
			+ formatNumber(getXPEarnedWhenConvertingBar(barToConvertToXpType) * amount.value * starDustCounter);
	};

	// fix blue coins
	const oldLoadCoins = window.loadCoins;
	window.loadCoins = () =>
	{
		oldLoadCoins();
		document.getElementById('platinumCoinsAmount-statusbar').innerHTML = formatNumber(window.platinumCoins);
	};

	// fix xp in "select a seed" dialog
	const seedButtons = document.querySelectorAll('#seed-menu-popup > div[id^="btn-"]');
	for (let i = 0; i < seedButtons.length; i++)
	{
		const cells = seedButtons[i].children[0].rows[0].cells;
		const xpNode = cells[cells.length-1].lastChild;
		xpNode.textContent = formatNumbersInText(xpNode.textContent);
	}

	// fix oil
	const oldLoadMiscVariables = window.loadMiscVariables;
	window.loadMiscVariables = () =>
	{
		oldLoadMiscVariables();
		document.getElementById('span-oilPerSecond').innerHTML = formatNumber(window.oilPerSeconds);
		document.getElementById('span-oilLosePerSecond').innerHTML = '-' + formatNumber(oilLosePerSeconds);
	};

	// fix artifact star dust calculation
	const artifactInput = document.getElementById('amount-to-convert-artifact');
	artifactInput.onkeyup = function ()
	{
		const xpRate = document.getElementById('artifact-xp-rate').value;
		const sdRate = window.bindedExploringOrb == 1 ? 22 : 26;
		document.getElementById('artifact-xp-earned').innerHTML = formatNumber(xpRate * this.value);
		document.getElementById('artifact-stardust-needed').innerHTML = formatNumber(xpRate * this.value * 22);
	};
	const oldOpenArtifactDialogue = window.openArtifactDialogue;
	window.openArtifactDialogue = (artifact, xp) =>
	{
		oldOpenArtifactDialogue(artifact, xp);
		artifactInput.onkeyup(null);
	};

	// fix xp-numbers in crafting tables
	function fixNumbersInCraftingTables(tabName)
	{
		const table = document.querySelector('#' + tabName + '-tab table.table-stats');
		const rows = table.rows;
		for (let i = 0; i < rows.length; i++)
		{
			const row = rows[i];
			if (row.cells.length <= 1 || row.querySelector(':scope > th') != null)
			{
				continue;
			}

			const lastCell = row.cells[row.cells.length-1];
			lastCell.textContent = formatNumbersInText(lastCell.textContent);
		}
	}
	fixNumbersInCraftingTables('brewing');
	fixNumbersInCraftingTables('cooking');
	fixNumbersInCraftingTables('spellbook');

 	// fix cost numbers in game shop
	function fixNumersInNpcShop()
	{
		const costEls = document.querySelectorAll('#npc-store-tab span[id^="shop-"][id$="-cost"]');
		for (let i = 0; i < costEls.length; i++)
		{
			const costEl = costEls[i].firstChild;
			costEl.textContent = formatNumbersInText(costEl.textContent);
		}
	}
	const oldLoadNpcShop = window.loadNpcShop;
	window.loadNpcShop = () =>
	{
		oldLoadNpcShop();
		fixNumersInNpcShop();
	};

	// fix stardust numbers of wizard tab
	const wizardBoxes = document.querySelectorAll('#wizard-tab .inventory-item-box');
	for (let i = 0; i < wizardBoxes.length; i++)
	{
		const secondChild = wizardBoxes[i].childNodes[1];
		if (secondChild.nodeType != Node.TEXT_NODE)
		{
			continue;
		}
		
		secondChild.textContent = formatNumbersInText(secondChild.textContent);
	}

	const globalLevelEl3 = document.getElementById('level-global-3');
	if (globalLevelEl3)
	{
		const rankNode = globalLevelEl3.parentNode.firstChild;
		rankNode.textContent = formatNumbersInText(rankNode.textContent);
	}

	// fix group task shop
	const oldLoadGroupTaskShop = window.loadGroupTaskShop;
	function fixElementsTextContent(el)
	{
		const num = parseInt(el.textContent, 10);
		if (!isNaN(num))
		{
			el.textContent = formatNumber(num);
		}
	}
	window.loadGroupTaskShop = () =>
	{
		oldLoadGroupTaskShop();

		fixElementsTextContent(document.getElementById('group-task-tokens-value'));
		fixElementsTextContent(document.getElementById('grp-shop-badge-price'));
	};
}



/**
 * initialize notifications
 * 
 * thanks /u/Vomera for the idea
 */

function observeTimer(k, onComplete, zero = 0)
{
	observe(k, (key, oldValue, newValue) =>
	{
		if (oldValue > zero && newValue == zero)
		{
			onComplete(key);
		}
	});
}
function notifyClickable(title, options)
{
	return notify(title, options).then((n) =>
	{
		n.onclick = (...args) =>
		{
			window.focus();
			n.close();
		};
		return n;
	});
}
// use notification2Tab on tab change (window.openTab)
const notification2Tab = new Map();
const notificationMap = new Map();
function notifyTabClickable(title, options, tabName)
{
	if (notificationMap.has(title))
	{
		notificationMap.get(title).close();
	}

	return notifyClickable(title, options).then((n) =>
	{
		if (!notification2Tab.has(tabName))
		{
			notification2Tab.set(tabName, new Set());
		}
		const closeObj = {
			close: () =>
			{
				n.close();
				if (notificationMap.get(title) == closeObj)
				{
					notificationMap.delete(title);
				}
				notification2Tab.get(tabName).delete(closeObj);
			}
		};
		notificationMap.set(title, closeObj);
		notification2Tab.get(tabName).add(closeObj);

		const oldOnclick = n.onclick;
		n.onclick = () =>
		{
			oldOnclick();
			window.openTab(tabName);
		};
		return n;
	});
}
function hideNotificationsFromTab(tabName)
{
	if (notification2Tab.has(tabName))
	{
		notification2Tab.get(tabName).forEach((n) => n.close());
	}
}
function notifyCoop(msg)
{
	window.send('OPEN_TAB=COOP');
	return notifyTabClickable('Group Task', {
		body: msg.replace(/!$/, '.')
		, icon: 'images/icons/coop.png'
	}, 'coop');
}
function notifyVendor()
{
	/*
"I have changed my items, come check them out."<br><br><img src="images/shop/vendor.png" width="120px" height="140px">
	*/
	return notifyTabClickable('Vendor', {
		body: 'The vendor changed his items.'
		, icon: 'images/shop/vendor.png'
	}, 'vendor');
}
function notifyBoat(msg)
{
	/*
<b>Your boat brings back:</b><br><br><span class="exploring-norm-loot"><img class="small-img" src="images/exploring/rawSardine.png"> 2</span> <span class="exploring-norm-loot"><img class="small-img" src="images/exploring/rawTuna.png"> 1</span> 
	*/
	const tmp = document.createElement('templateWrapper');
	tmp.innerHTML = msg;
	const loot = [];
	const lootEls = tmp.querySelectorAll('.exploring-norm-loot');
	for (let i = 0; i < lootEls.length; i++)
	{
		const el = lootEls[i];
		const num = parseInt(el.textContent, 10);
		const itemName = imgSrc2NamePlural(el.innerHTML, num);
		if (itemName)
		{
			loot.push(formatNumber(num) + ' ' + itemName);
		}
		else
		{
			loot.push(el.innerHTML);
		}
	}
	return notifyTabClickable('Fishing boat returns', {
		body: 'Your boat brings back: ' + loot.join(', ')
		, icon: 'images/exploring/fishingBoat.png'
	}, 'archaeology');
}
function notifyRobot(msg)
{
	/*
<b>Your robot brings back:</b><br /><br />9,000,000 stone<br />2,250,000 copper<br />2,250,000 tin<br />675,000 iron<br />450,000 silver<br />180,000 gold<br />22,500 flint<br />4,500 marble<br />1,800 titanium<br />45 promethium<br />
	*/
	return notifyTabClickable('Robot returns', {
		body: msg
			.replace('<br /><br />', ' ')
			.replace(/<br\s*\/?>(?=.)/g, ', ')
			.replace(/<[^>]+>/g, '')
		, icon: 'images/crafting/robot.png'
	}, 'repair');
}
function notifyAchievement()
{
	/*
You have completed an achievement
	*/
	return notifyTabClickable('Achievement got', {
		body: 'You have completed an achievement.'
		, icon: 'images/shop/ach.png'
	}, 'ach');
}
function notifyMsg(msg)
{
	if (msg === 'You have completed your group task!' ||
		/ has completed his group task\.$/.test(msg))
	{
		return notifyCoop(msg);
	}
	else if (/I have changed my items, come check them out/.test(msg))
	{
		return notifyVendor();
	}
	else if (/Your boat brings back:/.test(msg))
	{
		notifyBoat(msg);
	}
	else if (/Your robot brings back:/.test(msg))
	{
		notifyRobot(msg);
	}
	else if (msg === 'You have completed an achievement')
	{
		notifyAchievement();
	}
	else if (document.hidden || !document.hasFocus())
	{
		notifyClickable('Message from server', {
			body: msg
			// , icon: 'images/minerals/diamond.png'
		});
	}
	return Promise.reject();
}
function initNotifications()
{
	function requestNotificationPermission()
	{
		if (!getSetting('showNotifications') ||
			Notification.permission !== 'default')
		{
			return;
		}

		Notification.requestPermission().then(function (result)
		{
			if (result == 'denied')
			{
				console.error('Permission to show notifications has been denied by the user.');
			}
		});
	}
	requestNotificationPermission();
	observeSetting('showNotifications', () => requestNotificationPermission());

	// don't send TAB_OFF when notifications have to be shown
	window.checkIfTabIsOpen = () =>
	{
		if (!document.hidden || getSetting('showNotifications')) //open
		{
			if (tabOn == 0)
			{
				send('TAB_ON');
				tabOn = 1;
			}
		}
		else //minimized
		{
			if (tabOn == 1)
			{
				send('TAB_OFF');
				tabOn = 0;
			}
		}

		window.setTimeout(window.checkIfTabIsOpen, 500);
	};

	let lastFarmingNotification;
	observeTimer(['farmingPatchTimer1', 'farmingPatchTimer2', 'farmingPatchTimer3', 'farmingPatchTimer4', 'farmingPatchTimer5', 'farmingPatchTimer6'], (key) =>
	{
		const now = (new Date).getTime();
		const timeDiff = now - (lastFarmingNotification || 0);
		if (timeDiff < 10e3)
		{
			return;
		}

		lastFarmingNotification = now;
		notifyTabClickable('Harvest', {
			body: 'One or more of your crops is ready for harvest.'
			, icon: 'images/icons/watering-can.png'
		}, 'farming');
	}, 1);
	observeTimer('exploringTimer', (key) =>
	{
		notifyTabClickable('Explorer ready', {
			body: 'Your explorer is back.'
			, icon: 'images/icons/archaeology.png'
		}, 'archaeology');
	});
	observeTimer('furnaceCurrentTimer', (key) =>
	{
		notifyTabClickable('Furnace ready', {
			body: 'Your smelting has finished.'
			, icon: 'images/crafting/' + furnaceLevels[window.bindedFurnaceLevel] + 'Furnace.gif'
		}, 'repair');
	});
	observeTimer('rocketTimer', (key) =>
	{
		notifyTabClickable('Rocket ready', {
			body: 'You landed on the moon.'
			, icon: 'images/crafting/rocket.png'
		}, 'repair');
	});
	observeTimer('robotTimer', (key) =>
	{
		notifyTabClickable('Robot ready', {
			body: 'Your robot is back.'
			, icon: 'images/crafting/robot.png'
		}, 'repair');
	});
	observeTimer('fishingBoatTimer', (key) =>
	{
		notifyTabClickable('Fishing boat ready', {
			body: 'Your fishing boat is back.'
			, icon: 'images/exploring/fishingBoat.png'
		}, 'archaeology');
	});
	observeTimer('largeFishingBoatTimer', (key) =>
	{
		notifyTabClickable('Large fishing boat ready', {
			body: 'Your large fishing boat is back.'
			, icon: 'images/exploring/largeFishingBoat.png'
		}, 'archaeology');
	});
	/*
	// potions
	'starDustPotionTimer'
	'coinPotionTimer'
	'seedPotionTimer'
	'smeltingPotionTimer'
	'oilPotionTimer'
	'miningPotionTimer'
	'superStarDustPotionTimer'
	'fastFurnacePotionTimer'
	'superCompostPotionTimer'
	'megaStarDustPotionTimer'
	'superOilPotionTimer'
	'whaleFishingPotionTimer'
	'fishingPotionTimer'
	'essencePotionTimer'
	'megaOilPotionTimer'
	'superEssencePotionTimer'
	'sparklingCompostPotionTimer'
	'engineeringPotionTimer'

	// magic effects
	'superDrillsTimer'
	'superGemFinderTimer'
	'smallSipsTimer'
	'superPirateTimer'
	'superCrushersTimer'
	'superGiantDrillsTimer'
	'fastVendorTimer'
	'superRoadHeadersTimer'
	'animatedAxeTimer'
	'superExcavatorsTimer'

	// ?
	'compostTimer'
	'eatingTimer'
	'exploringTimeReductionPerc'
	'ghostEssenceTimer'
	*/
}



/**
 * fix level bar
 */

function fixLevelBar()
{
	// size changing: 1267x65 -> 1256x105
	document.getElementById('level-status-up').style.lineHeight = '102px';

	const style = document.createElement('style');
	style.innerHTML = `
.top-status-bar td.no-borders[width="27%"]
{
	position: relative;
}
#span-oil ~ span
{
	position: absolute;
	margin-left: 10px;
	top: 29px;
}
#span-oil + span
{
	top: 5px;
}
tr[id^="level-status-row"] > td > img:first-child[width="40px"]
{
	margin: 0 5px;
}
#level-status-row2 > td > img:first-child
{
	height: 50px;
}
.unlock-skill-btn
{
	margin-left: 5px;
}
span[id^="progress-percentage-"][id$="-small"]
{
	height: calc(90% + 2px);
	margin: 0;
}

.notification-timer-box
{
	line-height: 52px;
}
#fishingBoat-timer > img:first-child
{
	height: 40px;
	width: 53px;
}
#largeFishingBoat-timer > img:first-child
{
	padding: 5px 5px 8px 0px !important;
	height: 40px;
	width: 56px;
}
	`;
	document.head.appendChild(style);

	const oilPerSecond = document.getElementById('span-oilPerSecond');
	oilPerSecond.previousSibling.textContent = '+';
	oilPerSecond.nextSibling.textContent = '';
	const oilLosePerSecond = document.getElementById('span-oilLosePerSecond');
	oilLosePerSecond.previousSibling.textContent = '';
	oilLosePerSecond.nextSibling.textContent = '';
}



/**
 * fix message box
 */

function fixMsgBox()
{
	const oldDialogFn = window.$.fn.dialog;
	window.$.fn.dialog = function (...args)
	{
		if (args[0] != 'close')
		{
			$('.ui-widget-header').show();
		}
		return oldDialogFn.apply(this, args);
	};

	const oldMessageBox = window.messageBox;
	let timeout;
	window.messageBox = (msg) =>
	{
		const $el = $('#dialog-timer');
		if ($el.hasClass('ui-dialog-content'))
		{
			$el.dialog('destroy');
		}

		document.getElementById('dialog-text-timer').innerHTML = msg;

		$el.dialog(
		{
			create: function (event, ui)
			{
				$('.ui-widget-header').hide();
			}
			, width: 550
			, height: 100
			, show:
			{
				effect: 'fade'
				, duration: 50
			}
			, hide:
			{
				effect: 'fade'
				, delay: 1000
				, duration: 1000
			}
		}).dialog('close');
	};
}



/**
 * add a notification box (like the harvest one) for coop events
 */

function addCoopNotificationBox()
{
	const notifBox = document.createElement('span');
	notifBox.id = 'coop-notif';
	notifBox.classList.add('notification-timer-box');
	notifBox.style.width = 'auto';
	notifBox.style.cursor = 'pointer';
	notifBox.style.display = 'none';
	notifBox.style.padding = '0 10px';
	notifBox.onclick = () =>
	{
		window.openTab('coop');
		window.send('OPEN_TAB=COOP');
	};
	notifBox.innerHTML = `<span class="activate-tooltip" title="Group task is finished">
		<img width="46px" height="40px" style="vertical-align: middle; padding: 5px 0px 5px 0px;" src="images/icons/coop.png">
		<span class="progress"></span>
	</span>`;
	document.getElementById('farming-notif').parentNode.appendChild(notifBox);

	const oldLoadCoop = window.loadCoop;
	window.loadCoop = (data) =>
	{
		/**
		 * There are some userscripts (DH QoL *cough*) which uses setting the innerHTML of the notification box parent
		 * to add elements.
		 * So the reference to the created element (above) isn't valid anymore and has to be refreshed after this
		 * addition to the innerHTML.
		 * This functions "appendChild" and "insertBefore" aren't there for no reason... :(
		 * This error took me more than 4 hours to find and is just stupid (because someone is too lazy to create clean
		 * and considerate software).
		 * 
		 * Edit:
		 * I don't know if anybody care to read my code, but I want to apologize for the comment above.
		 * I love DHQoL (I didn't even spell it correctly - shame on me) and was more annoyed by my own incompetence
		 * than by any behaviour of DHQoL.
		 * Sorry for that unnecessary salty comment.
		 */
		const notifBox = document.getElementById('coop-notif');
		const coopProgress = notifBox.querySelector('span.progress');
		const dataArray = data == 'none' ? [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] : data.split('~');
		const player = [
			dataArray[0]
			, dataArray[1]
			, dataArray[2]
			, dataArray[3]
		];
		const task_id = [
			dataArray[4]
			, dataArray[5]
			, dataArray[6]
			, dataArray[7]
		];
		const task_value = [
			dataArray[8]
			, dataArray[9]
			, dataArray[10]
			, dataArray[11]
		];
		const task_neededValue = [
			dataArray[12]
			, dataArray[13]
			, dataArray[14]
			, dataArray[15]
		];

		function isPlayer(i)
		{
			return player[i] !== 'none' && player[i] !== 'claimed';
		}
		const started = task_id.every((id, i) => !isPlayer(i) || id != 0);
		const totalNum = player.filter((name, i) => isPlayer(i)).length;
		const finishedNum = task_value.filter((value, i) => isPlayer(i) && value == task_neededValue[i]).length;
		const showBox = started && finishedNum > 0;
		notifBox.style.display = showBox ? '' : 'none';
		coopProgress.textContent = finishedNum == totalNum ? '' : finishedNum + '/' + totalNum;

		const i = player.indexOf(window.username);
		const thisFinished = started && task_value[i] == task_neededValue[i];
		coopProgress.style.color = thisFinished ? 'lime' : '';

		for (let j = 1; j <= 4; j++)
		{
			const row = document.getElementById('started-row-p' + j);
			if (row)
			{
				row.style.backgroundColor = j == (i+1) ? 'lightblue' : '';
			}
		}

		return oldLoadCoop(data);
	};
	window.send('OPEN_TAB=COOP');
}



/**
 * fix chat
 */

const chatHistoryKey = 'chatHistory';
const maxChatHistoryLength = 100;
const reloadedChatData = {
	timestamp: 0
	, username: ''
	, userlevel: 0
	, sigil: 0
	, tag: 0
	, type: -1
	, msg: '[...]'
};
let chatHistory = [];
function add2ChatHistory(data)
{
	const splitArray = data.split('~');
	data = {
		timestamp: (new Date()).getTime()
		, username: splitArray[0]
		, userlevel: parseInt(splitArray[1], 10)
		, sigil: parseInt(splitArray[3], 10)
		, tag: parseInt(splitArray[2], 10)
		, type: parseInt(splitArray[5], 10)
		, msg: splitArray[4]
	};
	if (data.type == 2)
	{
		data.userlevel = window.getGlobalLevel();
	}

	chatHistory.push(data);
	chatHistory = chatHistory.slice(-maxChatHistoryLength);
	localStorage.setItem(chatHistoryKey, JSON.stringify(chatHistory));
	return data;
}
function getChatTab(username)
{
	const chatTabs = document.getElementById('chat-tabs');
	let tab = chatTabs.querySelector('div.chat-tab[data-username="' + username + '"]');
	if (!tab)
	{
		tab = document.createElement('div');
		tab.className = 'chat-tab';
		tab.dataset.username = username;
		tab.dataset.new = 0;
		const filler = chatTabs.querySelector('.filler');
		if (filler)
		{
			chatTabs.insertBefore(tab, filler);
		}
		else
		{
			chatTabs.appendChild(tab);
		}
	}
	return tab;
}
function getChatDiv(username)
{
	const id = 'chat-' + (username == '' ? 'area-div' : 'pm-' + username);
	let div = document.getElementById(id);
	if (!div)
	{
		div = document.createElement('div');
		div.setAttribute('disabled', 'disabled');
		div.id = 'chat-pm-' + username;
		div.className = 'chat-area-div';

		const height = document.getElementById('chat-area-div').style.height;
		div.style.height = height;

		const generalChat = document.getElementById('chat-area-div');
		generalChat.parentNode.insertBefore(div, generalChat);
	}
	return div;
}
function changeChatTab(oldTab, newTab)
{
	const oldChatDiv = getChatDiv(oldTab.dataset.username);
	oldChatDiv.classList.remove('selected');
	const newChatDiv = getChatDiv(newTab.dataset.username);
	newChatDiv.classList.add('selected');

	const toUsername = newTab.dataset.username;
	const newTextPlaceholder = toUsername == '' ? window.username + ':' : 'PM to ' + toUsername + ':';
	document.getElementById('textbox-chat').placeholder = newTextPlaceholder;

	if (window.isAutoScrolling)
	{
		setTimeout(() => newChatDiv.scrollTop = newChatDiv.scrollHeight);
	}
}
const chatSigils = [
	null
	, { key: 'maxLevel',		title: 'Maxed Skills' }
	, { key: 'maxMining',		title: 'Master in Mining' }
	, { key: 'maxCrafting',		title: 'Master in Crafting' }
	, { key: 'maxBrewing',		title: 'Master in Brewing' }
	, { key: 'maxFarming',		title: 'Master in Farming' }
	, { key: 'hardcore',		title: 'Hardcore Account' }
	, { key: 'halloween2015',	title: 'Halloween 2015' }
	, { key: 'maxExploring',	title: 'Master in Exploring' }
	, { key: 'christmas2015',	title: 'Chirstmas 2015' }
	, { key: 'maxMagic',		title: 'Master in Magic' }
	, { key: 'easter2016',		title: 'Holiday' }
	, { key: 'coop',			title: 'COOP' }
	, { key: 'maxCooking',		title: 'Master in Cooking' }
	, { key: 'halloween2016',	title: 'Halloween 2016' }
	, { key: 'christmas2016',	title: 'Chirstmas 2016' }
];
const chatTags = [
	null
	, { key: 'donor', name: '' }
	, { key: 'contributor', name: 'Contributor' }
	, null
	, { key: 'mod', name: 'Moderator' }
	, { key: 'dev', name: 'Dev' }
];
const linkParseRegex = /(^|\s)(https?:\/\/\S+|\S*www\.|\S+\.(?:com|ca|co|net|us))(\s|$)/;
function isPM(data)
{
	return data.type == 1 || data.type == 2;
}
const locale = 'en-US';
const localeOptions = {
	hour12: false
	, year: 'numeric'
	, month: 'long'
	, day: 'numeric'
	, hour: '2-digit'
	, minute: '2-digit'
	, second: '2-digit'
};
function newRefreshChat(data)
{
	// username is 3-12 characters long
	let chatbox = document.getElementById('chat-area-div');

	if (mutedPeople.some((name) => name == data.username))
	{
		return;
	}

	const isThisPm = isPM(data);
	const msgUsername = data.type == 2 ? window.username : data.username;

	const historyIndex = chatHistory.indexOf(data);
	const historyPart = historyIndex == -1 ? [] : chatHistory.slice(0, historyIndex).reverse();
	const msgBeforeUser = historyPart.find(d => isThisPm && isPM(d) || !isThisPm && !isPM(d));
	const msgBeforeTime = historyPart.find(d => isThisPm && isPM(d) || !isThisPm && !isPM(d) && d.type != -1);
	let isSameUser = false;
	let isSameTime = false;
	if (msgBeforeUser)
	{
		const beforeUsername = msgBeforeUser.type == 2 ? window.username : msgBeforeUser.username;
		isSameUser = beforeUsername === msgUsername;
	}
	if (msgBeforeTime)
	{
		isSameTime = Math.floor(data.timestamp / 1000 / 60) - Math.floor(msgBeforeTime.timestamp / 1000 / 60) === 0;
	}

	const d = new Date(data.timestamp);
	const hour = (d.getHours() < 10 ? '0' : '') +  d.getHours();
	const minute = (d.getMinutes() < 10 ? '0' : '') +  d.getMinutes();
	const sigil = chatSigils[data.sigil] || { key: '', title: '' };
	const tag = chatTags[data.tag] || { key: '', name: '' };
	const formattedMsg = data.msg.replace(new RegExp(linkParseRegex, 'g'), (wholeMatch, before, link, after) =>
	{
		if (/%22|%27|%3E|%3C|&#62|&#60;|;|~|\\"|<|>|javascript:|window|document|cookie/.test(link))
		{
			return wholeMatch;
		}
		link = (link.startsWith('http') ? '' : 'http://') + link;
		return before + `<a href="${link}" target="_blank">${link}</a>` + after;
	});

	const msgTitle = data.type == -1 ? 'Chat loaded on ' + d.toLocaleString(locale, localeOptions) : '';
	let chatSegment = `<span class="chat-msg" data-type="${data.type}" data-tag="${tag.key}">`
		+ `<span
			class="timestamp"
			data-hour="${hour}"
			data-minute="${minute}"
			title="${d.toLocaleString(locale, localeOptions)}"
			data-same-time="${isSameTime}"></span>`
		+ `<span class="user" data-name="${msgUsername}" data-same-user="${isSameUser}">`
			+ `<span class="sigil ${sigil.key}" title="${sigil.title}"></span>`
			+ `<span class="tag chat-tag-${tag.key}">${tag.name}</span>`
			+ `<span
				class="name"
				data-level="${data.userlevel}"
				oncontextmenu="searchPlayerHicores('${msgUsername}');return false;"
				onclick="preparePM('${msgUsername}')">${msgUsername}</span>`
		+ `</span>`
		+ `<span class="msg" title="${msgTitle}">${formattedMsg}</span>`
	+ `</span>`;

	const chatTab = getChatTab(isThisPm ? data.username : '');
	if (!chatTab.classList.contains('selected'))
	{
		chatTab.dataset.new = parseInt(chatTab.dataset.new, 10) + 1;
	}
	if (isThisPm)
	{
		window.lastPMFrom = data.username;
		chatbox = getChatDiv(data.username);
	}

	const tmp = document.createElement('templateWrapper');
	tmp.innerHTML = chatSegment;
	while (tmp.childNodes.length > 0)
	{
		chatbox.appendChild(tmp.childNodes[0]);
	}

	if (window.isAutoScrolling)
	{
		setTimeout(() => chatbox.scrollTop = chatbox.scrollHeight);
	}
}
function applyChatStyle()
{
	const style = document.createElement('style');
	style.innerHTML = `
span.chat-msg
{
	display: flex;
	margin-bottom: 1px;
}
.chat-msg[data-type="-1"]
{
	font-size: 0.8rem;
}
.chat-msg .timestamp::before
{
	color: hsla(0, 0%, 50%, 1);
	font-size: .9rem;
}
.chat-msg .timestamp[data-same-time="true"]::before
{
}
#chat-toggle-timestamps:checked ~ div[id^="chat-"] .chat-msg:not([data-type="-1"]) .timestamp::before
{
	content: attr(data-hour) ':' attr(data-minute);
	display: inline-block;
	margin: 0 5px;
	width: 2.5rem;
}

.chat-msg[data-type="1"] { color: purple; }
.chat-msg[data-type="2"] { color: purple; }
.chat-msg[data-type="3"] { color: blue; }
.chat-msg[data-tag="contributor"] { color: green; }
.chat-msg[data-tag="mod"] { color: #669999; }
.chat-msg[data-tag="dev"] { color: #666600; }
.chat-msg .user
{
	margin-right: 5px;
	white-space: nowrap;
}
.chat-msg .user[data-same-user="true"]:not([data-name="none"])
{
	opacity: .3;
}
.chat-msg .user .name:not([data-level=""])::after
{
	content: ' (' attr(data-level) '):';
}

.chat-msg .user .sigil:not([class$=" "])::before
{
	background-size: 20px 20px;
	content: '';
	display: inline-block;
	margin-right: 1px;
	width: 20px;
	height: 20px;
	vertical-align: middle;
}
.chat-msg .user .sigil.maxLevel::before { background-image: url('images/icons/stats.png'); }
.chat-msg .user .sigil.maxCrafting::before { background-image: url('images/icons/anvil.png'); }
.chat-msg .user .sigil.maxMining::before { background-image: url('images/icons/pickaxe.png'); }
.chat-msg .user .sigil.maxBrewing::before { background-image: url('images/brewing/vialofwater_chat.png'); }
.chat-msg .user .sigil.maxFarming::before { background-image: url('images/icons/watering-can.png'); }
.chat-msg .user .sigil.maxExploring::before { background-image: url('images/icons/archaeology.png'); }
.chat-msg .user .sigil.maxCooking::before { background-image: url('images/icons/cookingskill.png'); }
.chat-msg .user .sigil.maxMagic::before { background-image: url('images/magic/wizardHatIcon.png'); }
.chat-msg .user .sigil.hardcore::before { background-image: url('images/icons/hardcoreIcon.png'); }
.chat-msg .user .sigil.coop::before { background-image: url('images/icons/groupTaskBadge5.png'); }
.chat-msg .user .sigil.halloween2015::before { background-image: url('images/icons/halloween2015.png'); }
.chat-msg .user .sigil.christmas2015::before { background-image: url('images/sigils/christmas2015.png'); }
.chat-msg .user .sigil.easter2016::before { background-image: url('images/sigils/easter2016.png'); }
.chat-msg .user .sigil.halloween2016::before { background-image: url('images/sigils/halloween2016.png'); }
.chat-msg .user .sigil.christmas2016::before { background-image: url('images/sigils/christmas2016.png'); }

.chat-msg .user .tag
{
	margin-right: 3px;
}
.chat-msg .user .tag.chat-tag-
{
	display: none;
}
.chat-msg .user .tag.chat-tag-donor::before
{
	background-image: url('images/icons/donor-icon.gif');
	background-size: 20px 20px;
	content: '';
	display: inline-block;
	height: 20px;
	width: 20px;
	vertical-align: middle;
}

.chat-msg .user .name
{
	color: rgba(0, 0, 0, 0.7);
}
.chat-msg[data-type="-1"] .user > *,
.chat-msg[data-type="1"] .user > .sigil,
.chat-msg[data-type="1"] .user > .tag,
.chat-msg[data-type="2"] .user > .sigil,
.chat-msg[data-type="2"] .user > .tag,
.chat-msg[data-type="3"] .user > *
{
	display: none;
}
.chat-msg[data-type="3"] .user::before
{
	background: -webkit-linear-gradient(#004747, #00FFFF);
	background: -o-linear-gradient(#004747, #00FFFF);
	background: -moz-linear-gradient(#004747, #00FFFF);
	background: linear-gradient(#004747, #00FFFF);
	border: 1px solid black;
	color: white;
	content: 'Server Message';
	font-family: Comic Sans MS, "Times New Roman", Georgia, Serif;
	font-size: 9pt;
	padding: 0px 5px 2px 5px;
}

.chat-msg .msg
{
	word-wrap: break-word;
	min-width: 0;
}

#chat-box-area .chat-area-div
{
	width: 100%;
	height: 130px;
	display: none;
}
#chat-box-area .chat-area-div.selected
{
	display: block;
}
#chat-tabs
{
	background-color: hsla(0, 0%, 90%, 1);
	display: flex;
	margin: 10px -10px -10px;
	flex-wrap: wrap;
}
#chat-tabs .chat-tab
{
	background-color: gray;
	border-top: 1px solid black;
	border-right: 1px solid black;
	cursor: pointer;
	display: inline-block;
	font-weight: normal;
	padding: 0.3rem .6rem;
}
#chat-tabs .chat-tab.selected
{
	background-color: silver;
	border-top-color: silver;
}
#chat-tabs .chat-tab.filler
{
	background-color: transparent;
	border-right: 0;
	box-shadow: inset 5px 5px 5px -5px rgba(0, 0, 0, 0.5);
	color: transparent;
	cursor: default;
	flex-grow: 1;
}
#chat-tabs .chat-tab::before
{
	content: attr(data-username);
}
#chat-tabs .chat-tab:not(.filler)[data-username=""]::before
{
	content: 'Server';
}
#chat-tabs .chat-tab::after
{
	content: '(' attr(data-new) ')';
	font-size: .9rem;
	font-weight: bold;
	margin-left: .4rem;
}
#chat-tabs .chat-tab[data-new="0"]::after
{
	font-weight: normal;
}
	`;
	document.head.appendChild(style);
}
function fixChat()
{
	if (!getSetting('useNewChat'))
	{
		return;
	}

	const chatBoxArea = document.getElementById('chat-box-area');

	const toggles = chatBoxArea.querySelectorAll('input[value^="Toggle"]');
	function getChatValue(key)
	{
		if (key == 'autoscroll' || key == 'timestamps')
		{
			return JSON.parse(localStorage.getItem('chat.' + key) || 'true');
		}
		return false;
	}
	function setChatValue(key, value)
	{
		if (key == 'autoscroll' || key == 'timestamps')
		{
			localStorage.setItem('chat.' + key, JSON.stringify(value));
			if (key == 'autoscroll')
			{
				window.isAutoScrolling = value;
			}
			else if (key == 'timestamps')
			{
				window.showTimestamps = value;
			}
			return true;
		}
		return false;
	}
	for (let i = 0; i < toggles.length; i++)
	{
		const toggle = toggles[i];
		const parent = toggle.parentNode;
		const toggleWhat = toggle.value.replace('Toggle ', '');
		const toggleKey = toggleWhat.toLowerCase();
		const id = 'chat-toggle-' + toggleKey;
		const checkbox = document.createElement('input');
		checkbox.type = 'checkbox';
		checkbox.id = id;
		checkbox.value = toggleKey;
		const checkedValue = getChatValue(toggleKey);
		setChatValue(toggleKey, checkedValue);
		checkbox.checked = checkedValue;
		parent.insertBefore(checkbox, toggle);
		const label = document.createElement('label');
		label.htmlFor = id;
		label.textContent = toggleWhat;
		parent.insertBefore(label, toggle);
		toggle.style.display = 'none';

		checkbox.addEventListener('change', () =>
		{
			if (!setChatValue(checkbox.value, checkbox.checked))
			{
				toggle.click();
			}
		});
	}

	// add chat tabs
	const chatTabs = document.createElement('div');
	chatTabs.id = 'chat-tabs';
	chatTabs.addEventListener('click', (event) =>
	{
		const newTab = event.target;
		if (!newTab.classList.contains('chat-tab') || newTab.classList.contains('filler'))
		{
			return;
		}

		const oldTab = chatTabs.querySelector('.chat-tab.selected');
		if (newTab == oldTab)
		{
			return;
		}
		oldTab.classList.remove('selected');
		newTab.classList.add('selected');
		newTab.dataset.new = 0;

		changeChatTab(oldTab, newTab);
	});
	chatBoxArea.appendChild(chatTabs);

	const generalTab = getChatTab('');
	generalTab.classList.add('selected');
	const generalChatDiv = getChatDiv('');
	generalChatDiv.classList.add('selected');
	// works only if username length of 1 isn't allowed
	const fillerTab = getChatTab('f');
	fillerTab.classList.add('filler');

	const oldSendChat = window.sendChat;
	window.sendChat = (msg) =>
	{
		const selectedTab = document.querySelector('.chat-tab.selected');
		if (selectedTab.dataset.username != '')
		{
			msg = '/pm ' + selectedTab.dataset.username + ' ' + msg;
		}
		oldSendChat(msg);
	};

	const oldChatBoxZoom = window.chatBoxZoom;
	function setChatBoxHeight(height)
	{
		document.getElementById('chat-area-div').style.height = height;
		const chatDivs = chatBoxArea.querySelectorAll('div[id^="chat-pm-"]');
		for (let i = 0; i < chatDivs.length; i++)
		{
			chatDivs[i].style.height = height;
		}
	}
	window.chatBoxZoom = (zoom) =>
	{
		oldChatBoxZoom(zoom);

		const height = document.getElementById('chat-area-div').style.height;
		localStorage.setItem('chat.height', height);
		setChatBoxHeight(height);
	};
	setChatBoxHeight(localStorage.getItem('chat.height'));

	chatHistory = JSON.parse(localStorage.getItem(chatHistoryKey) || JSON.stringify(chatHistory));
	const lastNotPM = chatHistory.slice(0).reverse().find((d) =>
	{
		return d.type != 1 && d.type != 2;
	});
	if (lastNotPM && lastNotPM.type != -1)
	{
		reloadedChatData.timestamp = (new Date()).getTime();
		chatHistory.push(reloadedChatData);
	}
	chatHistory.forEach(d => newRefreshChat(d));
	// reset the new counter for all tabs
	const tabs = document.querySelectorAll('.chat-tab');
	for (let i = 0; i < tabs.length; i++)
	{
		tabs[i].dataset.new = 0;
	}
	applyChatStyle();

	const oldRefreshChat = window.refreshChat;
	window.refreshChat = (data) =>
	{
		data = add2ChatHistory(data);
		return newRefreshChat(data);
	};
}



/**
 * fix crafting
 */

function fixCrafting()
{
	// show selection for the bar type
	const oldSetConvertBarToXpAgain = window.setConvertBarToXpAgain;
	window.setConvertBarToXpAgain = (barType, amount) =>
	{
		oldSetConvertBarToXpAgain(barType, amount);

		const selector = (bar = '') => `#enchanted-hammer-boxes input[type="image"][src$="${bar}bar.png"]`;
		const barImages = document.querySelectorAll(selector());
		for (let i = 0; i < barImages.length; i++)
		{
			barImages[i].style.backgroundColor = '';
		}
		const img = document.querySelector(selector(barType));
		img.style.backgroundColor = 'red';
	};
}



/**
 * activity log
 * 
 * thanks /u/Vomera
 */

const activityLogKey = 'activityLog';
const maxActivityLogLength = 200;
let activityLog = [];
const explorerReturns = 'Your explorer brings back:';
const explorerReturnsWithoutArtifacts = 'You find 0 artifacts (+0 xp)';
// unused atm
function processExplorerMessage(msg)
{
	const tmp = createTemplateWrapper(
		msg
			.replace(explorerReturns, '')
			.replace(explorerReturnsWithoutArtifacts, '')
			.replace(/<br\s*\/?>/g, '')
			.trim()
	);

	// handle used artifact potion
	const artifactPotionEl = tmp.querySelector('center > img[src$="artifactPotion.png"]');
	let artifactPotion = '';
	if (artifactPotionEl)
	{
		artifactPotion = tmp.firstElementChild.textContent.trim().replace(/used/i, 'One artifact potion used');
		tmp.removeChild(tmp.firstElementChild);
	}

	// handle loot bags and received artifacts
	const lootEls = tmp.querySelectorAll('span[class^="exploring-norm-loot"]');
	const loot = [];
	const artifacts = [];
	for (let i = 0; i < lootEls.length; i++)
	{
		const el = lootEls[i];
		const quantityStr = el.textContent.trim();
		const num = parseInt(quantityStr, 10);
		const isArtifact = quantityStr.includes('xp');
		const itemName = isArtifact ? imgSrc2Name(el.innerHTML) : imgSrc2NamePlural(el.innerHTML, num);
		if (itemName)
		{
			if (isArtifact)
			{
				artifacts.push(itemName + ' (' + quantityStr.replace(num.toString(), formatNumber(num)) + ')');
			}
			else
			{
				loot.push(formatNumber(num) + ' ' + itemName);
			}
		}
		else
		{
			loot.push(el.innerHTML);
		}
		el.parentNode.removeChild(el);
	}

	const noArtifacts = msg.includes(explorerReturnsWithoutArtifacts);
	let newMsg = '';
	if (tmp.textContent.trim() != '')
	{
		const regex = /<([^>\s]+)[^>]*>\s*<\/\1>/;
		let html = tmp.innerHTML;
		while (regex.test(html))
		{
			html = html.replace(regex, '');
		}
		newMsg += html + '. ';
	}
	if (artifactPotion != '')
	{
		newMsg += artifactPotion + '. ';
	}
	newMsg += explorerReturns + ' ' + loot.concat(artifacts).join(', ');
	if (noArtifacts)
	{
		newMsg += '. ' + explorerReturnsWithoutArtifacts;
	}
	return newMsg;
}
// unused atm
function processGroupMessage(msg)
{
	const tmp = createTemplateWrapper(msg);
	const tokenEl = tmp.querySelector('.basic-smallbox');
	const tokenNum = parseInt(tokenEl.textContent, 10);
	let newMsg = tokenEl.textContent.trim() + ' ' + imgSrc2NamePlural(tokenEl.innerHTML, tokenNum);
	const infoSpan = tmp.querySelector(':scope > span');
	if (infoSpan)
	{
		newMsg += ' - ' + infoSpan.textContent;
	}
	return newMsg;
}
/*
<span style='color:green;'>1 products cooked</span><br /><br /><img src='images/icons/cookingskill.png' width='20px' height='20px' style='vertical-align:middle'> +630 xp
<span style='color:green;'>1 products cooked</span><br /><br /><img src='images/icons/cookingskill.png' width='20px' height='20px' style='vertical-align:middle'> +720 xp
<span style='color:green;'>5 products cooked</span><br /><br /><img src='images/icons/cookingskill.png' width='20px' height='20px' style='vertical-align:middle'> +180 xp
<span style='color:green;'>3 products cooked</span><br /><br /><img src='images/icons/cookingskill.png' width='20px' height='20px' style='vertical-align:middle'> +1680 xp
<span style='color:green;'>3 products cooked</span><br /><br /><img src='images/icons/cookingskill.png' width='20px' height='20px' style='vertical-align:middle'> +84 xp
<span style='color:green;'>1 products cooked</span><br /><br /><img src='images/icons/cookingskill.png' width='20px' height='20px' style='vertical-align:middle'> +560 xp
<span style='color:green;'>0 products cooked</span><br /><br /><span style='color:red;'>1 products burnt</span><br /><br /><img src='images/icons/cookingskill.png' width='20px' height='20px' style='vertical-align:middle'> +0 xp
*/
// unused atm
function processCookingMessage(msg)
{
	const tmp = createTemplateWrapper(msg);
	const products = [];
	const spans = tmp.querySelectorAll(':scope > span');
	for (let i = 0; i < spans.length; i++)
	{
		products.push(spans[i].textContent.trim());
	}
	products.push(tmp.lastChild.textContent);
	return products.join(', ');
}
/*
<center><img width='150px' height='150px' src='images/misc-items/openChest.png'></center><br /><br /><span class='inventory-item-box-smaller'><img class='small-img' src='images/brewing/blewitmushroom.png'> Blewit Mushrooms (400)</span><br />

<center><img width='150px' height='150px' src='images/misc-items/openChest.png'></center><br /><br /><span class='inventory-item-box-smaller'><img class='small-img' src='images/minerals/goldbar.png'> Gold Bars (500)</span><br /><span class='inventory-item-box-smaller'><img class='small-img' src='images/crafting/upgradeFurnaceOrb.png'> Furnace Orb</span>
*/
// unused atm
function processChestMessage(msg)
{
	const tmp = createTemplateWrapper(msg);
	const lootEls = tmp.querySelectorAll('.inventory-item-box-smaller');
	const loot = [];
	for (let i = 0; i < lootEls.length; i++)
	{
		const el = lootEls[i];
		const match = el.textContent.match(/(.+) \((\d+)\)/);
		let num = 1;
		if (match)
		{
			num = parseInt(match[2]);
		}
		const itemName = imgSrc2Name(el.innerHTML, num);
		if (itemName)
		{
			loot.push(num + ' ' + itemName);
		}
		else
		{
			loot.push(el.textContent.trim());
		}
	}
	return 'Opened Chest and got: ' + loot.join(', ');
}
/*
"I have changed my items, come check them out."<br /><br /><img src='images/shop/vendor.png' width='120px' height='140px' />
*/
const vendorChangedText = 'I have changed my items, come check them out.';
// unused atm
function processVendorMessage(msg)
{
	return 'The vendor has changed his items.';
}
/*
== collection of some messages: ==
You mix 1 potions.
You mix 1 (+0) potions.
You mix 1 (+1) potions.
You have completed your group task!
x has completed his group task.
Machinery upgraded to level: 6
Your fix all your machinery
You also use your glass blowing pipe.
You do not have enough stardust
+15000 stardust.
Your upgraded rake shines as you harvest.
You dont have this seed.
New food item added.
1 artifact potions activated.
You have completed an achievement
You pour lava over your runite ore.
Your account has been running for 5 Minutes
You craft a key
You craft a gold oven
You craft a Brewing Kit
<img src='images/icons/cookingskill.png' width='50px' height='50px'/><br /><br /> You need a skilling level of 40 to bind this.
<span style='color:green;'>Item succesfully added to the market.</span>
The wizard takes your orb and extracts its energy.<br /><br /><img height='200px' width='250px' src='images/shop/wizard2.png'><br /><br /><b>'I still need more power!'</b>
The wizard takes your orb and extracts its energy.<br /><br /><img height='200px' width='250px' src='images/shop/wizard2.png'><br /><br /><b>'You may now use me to convert seeds into mega seeds!'</b>
*/
// unused atm
function processMessage(msg)
{
	if (msg.includes(explorerReturns))
	{
		return processExplorerMessage(msg);
	}
	else if (msg.includes('icons/groupTaskTokens.png'))
	{
		return processGroupMessage(msg);
	}
	else if (msg.includes('products cooked'))
	{
		return processCookingMessage(msg);
	}
	else if (msg.includes('images/misc-items/openChest.png'))
	{
		return processChestMessage(msg);
	}
	else if (msg.includes(vendorChangedText))
	{
		return processVendorMessage(msg);
	}
	return msg;
}
const autoreadMsgList = [
	'You also use your glass blowing pipe.'
	, 'New food item added.'
	, 'Your upgraded rake shines as you harvest.'
	, 'Your fix all your machinery'
	, 'Perk unlocked.'
	, 'You pour lava over your runite ore.'
	, 'You fill transform your oil into 1 container of rocket fuel.'
	, 'Item succesfully added to the market.'
	, 'products cooked'
	, 'Items purchased.'
	, 'You craft '
	, 'Your account has been running for'
	, 'You don\'t have this seed.'
	, 'You gain some mining experience.'
	, 'You do not have enough stardust'
	, 'Your robot starts his journey.'
];
function add2ActivityLog(cmd)
{
	const data = {
		type: cmd.type
		, time: (new Date()).getTime()
		// , msg: processMessage(msg)
		, msg: cmd.msg
	};

	activityLog.push(data);
	activityLog = activityLog.slice(-maxActivityLogLength);
	localStorage.setItem(activityLogKey, JSON.stringify(activityLog));
	return data;
}
const activityLogInputId = 'show-activity-log';
function updateActivity(data, triesLeft = 50)
{
	const inputEl = document.getElementById(activityLogInputId);
	const activityLogLabel = document.getElementById('activity-log-label');
	// delay this function call until the dom is fully loaded
	if (!activityLogLabel || !inputEl)
	{
		if (triesLeft > 0)
		{
			setTimeout(() => updateActivity(data, triesLeft-1), 100);
		}
		return;
	}
	const read = autoreadMsgList.some((str) => data.msg.indexOf(str) > -1);
	if (!inputEl.checked && !read)
	{
		activityLogLabel.dataset.new = parseInt(activityLogLabel.dataset.new, 10) + 1;
	}

	// add an entry for the given data to the activity list (DOM)
	const activityLogList = document.getElementById('activity-log');
	const listItem = document.createElement('li');
	listItem.dataset.time = (new Date(data.time)).toLocaleString();
	listItem.innerHTML = data.msg;
	const before = activityLogList.firstElementChild;
	if (before)
	{
		activityLogList.insertBefore(listItem, before);
	}
	else
	{
		activityLogList.appendChild(listItem);
	}
}
function initActivityLog()
{
	const gameScreen = document.getElementById('game-screen');
	const table = document.querySelector('div.top-menu > table');
	const row = table.rows[0];
	// change text to reduce used size
	const onlineCell = row.cells[row.cells.length-2];
	onlineCell.firstChild.textContent = 'Currently online: ';
	onlineCell.removeChild(onlineCell.lastChild);
	// insert activity log cell
	const cell = row.insertCell(-1);
	cell.classList.add('table-top');

	// creat input and label
	const inputEl = document.createElement('input');
	inputEl.id = activityLogInputId;
	inputEl.type = 'checkbox';
	inputEl.style.display = 'none';
	inputEl.addEventListener('change', function ()
	{
		if (inputEl.checked)
		{
			logEl.dataset.new = 0;
		}
	});
	document.body.insertBefore(inputEl, gameScreen);
	// create clickable text
	const logEl = document.createElement('label');
	logEl.id = 'activity-log-label';
	logEl.htmlFor = activityLogInputId;
	logEl.dataset.new = 0;
	logEl.textContent = 'Activity Log';
	cell.appendChild(logEl);

	// add a container for the entry listing
	const logOverlay = document.createElement('label');
	logOverlay.id = 'activity-log-overlay';
	logOverlay.htmlFor = activityLogInputId;
	document.body.appendChild(logOverlay);
	const log = document.createElement('ul');
	log.id = 'activity-log';
	document.body.appendChild(log);

	const style = document.createElement('style');
	style.innerHTML = `
#${activityLogInputId}
{
	display: none;
}
body
{
	overflow-y: scroll;
}
/*
#game-screen
{
	overflow: auto;
	position: absolute;
	bottom: 0;
	left: 0;
	top: 0;
	right: 0;
}
#${activityLogInputId}:checked + #game-screen
{
	overflow: hidden;
}
*/
#activity-log-label
{
	cursor: pointer;
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}
#activity-log-label::after
{
	content: ' (' attr(data-new) ')';
	color: lime;
}
#activity-log-label[data-new="0"]::after
{
	color: white;
}
#activity-log-overlay
{
	background-color: transparent;
	color: transparent;
	pointer-events: none;
	position: fixed;
	bottom: 0;
	left: 0;
	top: 0;
	right: 0;
	transition: background-color .3s ease-out;
	z-index: 1000;
}
#${activityLogInputId}:checked ~ #activity-log-overlay
{
	background-color: rgba(0, 0, 0, 0.4);
	pointer-events: all;
}
#activity-log
{
	background-color: white;
	color: black;
	list-style: none;
	margin: 0;
	overflow-y: scroll;
	padding: .4rem .8rem;
	position: fixed;
	top: 0;
	right: 0;
	bottom: 0;
	transform: translateX(100%);
	transition: transform .3s ease-out;
	min-width: 15rem;
	width: 40%;
	max-width: 30rem;
	z-index: 1000;
}
#${activityLogInputId}:checked ~ #activity-log
{
	transform: translateX(0%);
}
#activity-log::before
{
	content: 'Activity Log';
	display: block;
	font-size: 1rem;
	font-weight: bold;
	margin-bottom: 0.8rem;
}
#activity-log:empty::after
{
	content: 'Activities will be listed here.';
}
#activity-log li
{
	border: 1px solid gray;
	border-radius: .2rem;
	margin: .2rem 0;
	padding: .4rem .8rem;
}
#activity-log li::before
{
	color: gray;
	content: attr(data-time);
	display: block;
	font-size: 0.8rem;
	margin: -4px 0 4px -4px;
}
	`;
	document.head.appendChild(style);

	activityLog = JSON.parse(localStorage.getItem(activityLogKey) || JSON.stringify(activityLog));
	activityLog.forEach(d => updateActivity(d));
	logEl.dataset.new = 0;
}



/**
 * improve tabs
 */

function highlightTab(tabName)
{
	const tabKey = {
		'gatherings': 'ores'
		, 'shop': 'market'
		, 'stats': 'skills'
	}[tabName] || tabName;
	const oldTab = document.querySelector('td[id^="table-tab-"].selected');
	if (oldTab)
	{
		oldTab.classList.remove('selected');
	}
	const newTab = document.getElementById('table-tab-' + tabKey);
	if (newTab)
	{
		newTab.classList.add('selected');
	}

	const oldSubTabs = document.querySelector('#sub-tabs > .sub-tab-container.show');
	if (oldSubTabs)
	{
		oldSubTabs.classList.remove('show');
	}
	const subTabs = document.getElementById('sub-tabs-' + tabName);
	if (subTabs)
	{
		subTabs.classList.add('show');
		// tidy up old sub tabs
		const oldSubTab = document.querySelector('.sub-tab-container > .selected');
		if (oldSubTab)
		{
			oldSubTab.classList.remove('selected');
		}
		subTabs.firstElementChild.classList.add('selected');
	}
}
function highlightSubTab(tabName)
{
	const oldSubTab = document.querySelector('.sub-tab-container > .selected');
	if (oldSubTab)
	{
		oldSubTab.classList.remove('selected');
	}
	document.getElementById('table-tab-' + tabName).classList.add('selected');
}
const mainTabList = ['gatherings', 'key-items', 'repair', 'mining', 'crafting', 'farming', 'brewing', 'archaeology', 'magic', 'shop', 'stats', 'coop', 'settings'];
const subTab2MainTab = {
	'collectables': 'key-items'
	, 'ach': 'key-items'
	, 'vendor': 'key-items'
	, 'wizard': 'key-items'
	, 'dragon': 'key-items'
	, 'miningEngineer': 'repair'
	, 'ach-explore': 'archaeology'
	, 'archaeology-crafting': 'archaeology'
	, 'cooking': 'archaeology'
	, 'spellbook': 'magic'
	, 'magiccrafting': 'magic'
	, 'npc-store': 'shop'
	, 'donor-store': 'shop'
	, 'player-store': 'shop'
};
function newOpenTab(tabName)
{
	if (tabName == 'player-store')
	{
		window.marketRefreshOn = 1;
		window.send('OPEN_MARKET');
	}
	else if (window.marketRefreshOn == 1)
	{
		window.send('CLOSE_MARKET');
		window.marketRefreshOn = 0;
	}

	if (tabName == 'coop' && window.getGlobalLevel() < 100)
	{
		window.openDialogue('Global Level', 'You need a global level of at least 100 to start the CO-OP adventure!', '');
		return;
	}
	else if (tabName == 'cooking' && window.cookingUnlocked == 0)
	{
		window.openDialogue('Skill Missing', 'You must unlock the cooking skill to cook food.', '');
		return;
	}
	if (tabName == 'spellbook' && window.bindedMagicPage1 == 0 && window.bindedMagicPage2 == 0 && window.bindedMagicPage3 == 0 && window.bindedMagicPage4 == 0 && window.bindedMagicPage5 == 0)
	{
		window.openDialogue('Spellbook', 'You do not have any pages in your spellbook yet.', '');
		return;
	}

	window.hideAllTabs();

	const panelName = {
		'shop': 'store'
	}[tabName] || tabName;
	const panelEl = document.getElementById(panelName + '-tab');
	if (panelEl)
	{
		panelEl.style.display = 'block';
	}
	if (mainTabList.includes(tabName))
	{
		highlightTab(tabName);
	}
	if (subTab2MainTab.hasOwnProperty(tabName))
	{
		// highlight the main tab (if the openTab call if from notification)
		const mainTabName = subTab2MainTab[tabName];
		highlightTab(mainTabName);
		highlightSubTab(tabName);
		hideNotificationsFromTab(mainTabName);
	}
	hideNotificationsFromTab(tabName);

	if (tabName == 'itemStats')
	{
		window.loadAllStats();
	}
	else if (tabName == 'archaeology' && mapOfTheSea > 0)
	{
		document.getElementById('hasMapOfTheSea-fishermen').style.display = '';
	}
	else if (tabName == 'mining' && enchantedPickaxeStarDust > 0)
	{
		document.getElementById('charge-pickaxe-box').style.display = '';
	}
	else if (tabName == 'crafting' && enchantedHammerStarDust > 0)
	{
		document.getElementById('uncharge-hammer-box').style.display = '';
	}
	else if (tabName == 'collectables')
	{
		window.loadCollectables();
	}
	else if (tabName == 'vendor')
	{
		if (vendorChangedFlag > 0)
		{
			window.send('RESET_VENDOR_CHANGED_FLAG');
		}

		if (window.getLevel(craftingXp) >= 100 ||
			window.getLevel(miningXp) >= 100 ||
			window.getLevel(merchantingXp) >= 100 ||
			window.getLevel(exploringXp) >= 100 ||
			window.getLevel(brewingXp) >= 100 ||
			window.getLevel(magicXp) >= 100)
		{
			if (window.dragonOrb == 0)
			{
				document.getElementById('vendor-item-box-quest').style.display = 'block';
				window.openDialogue('Vendor', '<center><img src="images/shop/vendor.png" width="100px" height="200px" /><br /><br /> "I have something very special for you."</center>', '');
			}
		}
	}
	else if (tabName == 'repair')
	{
		window.refreshRepairTab();
	}
	else if (tabName == 'dragon')
	{
		if (window.dragonOrb == 0)
		{
			document.getElementById('dragon-tab').style.display = 'block';
			return;
		}
		window.loadDragonTab();
	}
}
function applyNewTabStyle()
{
	const style = document.createElement('style');
	style.innerHTML = `
#tab-tr > td,
.sub-tab-container > span
{
	background: -webkit-linear-gradient(black, grey);
	background: -o-linear-gradient(black, grey);
	background: -moz-linear-gradient(black, grey);
	background: linear-gradient(black, grey);
	cursor: pointer;
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}
#tab-tr > td:hover,
.sub-tab-container > span:hover
{
	background: -webkit-linear-gradient(red, grey);
	background: -o-linear-gradient(red, grey);
	background: -moz-linear-gradient(red, grey);
	background: linear-gradient(red, grey);
}
#tab-tr > td.selected,
.sub-tab-container > span.selected,
#table-tab-ach.selected
{
	background: -webkit-linear-gradient(#800000, #390000);
	background: -o-linear-gradient(#800000, #390000);
	background: -moz-linear-gradient(#800000, #390000);
	background: linear-gradient(#800000, #390000);
}
#sub-tabs
{
	font-size: 10pt;
	height: 43px;
}
body.hide-sub-tabs #sub-tabs
{
	display: none;
}
.sub-tab-container
{
	display: none;
}
.sub-tab-container.show
{
	display: inline-block;
}
.sub-tab-container > span,
#table-tab-ach
{
	border: 0 solid #aaa;
	border-right-width: 1px;
	color: white;
	cursor: pointer;
	display: inline-block;
	line-height: 27px;
	padding: 8px 10px;
}
.sub-tab-container > span:first-child
{
	border-left-width: 1px;
}
.sub-tab-container > span:not(:first-child)::before
{
	background-repeat: no-repeat;
	background-size: 25px 25px;
	content: '';
	display: inline-block;
	margin-right: .4rem;
	height: 25px;
	width: 25px;
	vertical-align: middle;
}
#sub-tabs.hide-tab-text .sub-tab-container > span:not(:first-child)::before
{
	margin-right: 0;
}
.sub-tab-container > span::after
{
	content: attr(data-tab-text);
}
#sub-tabs.hide-tab-text .sub-tab-container > span:not(:first-child)::after
{
	display: none;
}
#sub-tabs-shop > span:not(:first-child)::before
{
	display: none;
}
#table-tab-collectables::before
{
	background-image: url('images/icons/collectables.png');
	background-size: 32px 25px;
	width: 32px;
}
#table-tab-ach::before
{
	background-image: url('images/shop/ach.png');
}
#table-tab-vendor::before
{
	background-image: url('images/shop/vendor.png');
}
#table-tab-wizard::before
{
	background-image: url('images/shop/superWizard.png');
	background-size: 13px 25px;
	width: 13px;
}
#table-tab-dragon::before
{
	background-image: url('images/crafting/dragonOrb.png');
}
#table-tab-miningEngineer::before
{
	background-image: url('images/shop/miningEngineer.png');
	background-size: 19px 25px;
	width: 19px;
}
#table-tab-ach-explore::before
{
	background-image: url('images/exploring/equipement/silverBody.png');
}
#table-tab-archaeology-crafting::before,
#table-tab-magiccrafting::before
{
	background-image: url('images/icons/anvil.png');
	background-size: 28px 25px;
	width: 28px;
}
#table-tab-cooking::before
{
	background-image: url('images/icons/cookingskill_baw.png');
	background-size: 17px 25px;
	width: 17px;
}
#table-tab-spellbook::before
{
	background-image: url('images/magic/magicBook.png');
	background-size: 32px 25px;
	width: 32px;
}
#sub-tabs.hide-tab-text .sub-tab-container > span#table-tab-npc-store::after,
#sub-tabs.hide-tab-text .sub-tab-container > span#table-tab-donor-store::after,
#sub-tabs.hide-tab-text .sub-tab-container > span#table-tab-player-store::after
{
	display: initial;
}
	`;
	document.head.appendChild(style);

	const subTabContainer = document.createElement('div');
	subTabContainer.id = 'sub-tabs';

	// add all sub-tabs
	function addSubContainer(parentKey, content)
	{
		const subContainer = document.createElement('div');
		subContainer.className = 'sub-tab-container';
		subContainer.id = 'sub-tabs-' + parentKey;
		subContainer.innerHTML = '<span>Overview</span>' + content;
		subTabContainer.appendChild(subContainer);
	}
	addSubContainer(
		'key-items'
		, `<span id="table-tab-collectables" data-tab-text="Collectables"></span>`
		+ `<span id="table-tab-ach" data-tab-text="Achievements"></span>`
		+ `<span id="table-tab-vendor" data-tab-text="Vendor"></span>`
		+ `<span id="table-tab-wizard" data-tab-text="Wizard"></span>`
		+ `<span id="table-tab-dragon" data-tab-text="Dragon's Lair"></span>`
	);
	addSubContainer(
		'repair'
		, `<span id="table-tab-miningEngineer" data-tab-text="Mining Engineer"></span>`
	);
	addSubContainer(
		'archaeology'
		, `<span id="table-tab-ach-explore" data-tab-text="Equipment"></span>`
		+ `<span id="table-tab-archaeology-crafting" data-tab-text="Craft"></span>`
		+ `<span id="table-tab-cooking" data-tab-text="Cooking"></span>`
	);
	addSubContainer(
		'magic'
		, `<span id="table-tab-spellbook" data-tab-text="Cast Spell"></span>`
		+ `<span id="table-tab-magiccrafting" data-tab-text="Craft"></span>`
	);
	addSubContainer(
		'shop'
		, `<span id="table-tab-npc-store" data-tab-text="Game Shop"></span>`
		+ `<span id="table-tab-donor-store" data-tab-text="Donor Shop"></span>`
		+ `<span id="table-tab-player-store" data-tab-text="Player Market"></span>`
	);

	subTabContainer.addEventListener('click', (event) =>
	{
		const target = event.target;
		const parent = target.parentNode;
		if (parent.classList.contains('sub-tab-container'))
		{
			const newTabName = (target.id ? target : parent).id
				.replace('table-tab-', '')
				.replace('sub-tabs-', '')
			;
			if (newTabName != '')
			{
				window.openTab(newTabName);
			}
		}
	});
	document.getElementById('tab-container').appendChild(subTabContainer);
	function handleTabTextVisibility()
	{
		const isOn = window.tabTextOff > 0;
		subTabContainer.classList[isOn ? 'add' : 'remove']('hide-tab-text');
	}
	handleTabTextVisibility();
	observe('tabTextOff', () => handleTabTextVisibility());

	// fix on click handler
	const craftExploringBtn = document.querySelector('[onclick^="switchToCraftingExploringTab"]');
	craftExploringBtn.setAttribute('onclick', "openTab('archaeology-crafting')");
}
function checkSubTab(tabKey, windowKeys, init)
{
	const fulfilled = windowKeys.some(key => window[key] > 0);
	document.getElementById('table-tab-' + tabKey).style.display = fulfilled ? '' : 'none';

	if (init)
	{
		for (let key of windowKeys)
		{
			observe(key, () => checkSubTab(tabKey, windowKeys, false));
		}
	}
}
function improveTabs()
{
	applyNewTabStyle();
	window.openTab = newOpenTab;
	newOpenTab('gatherings');

	// observe some values to show sub tabs as soon as they are accessible
	checkSubTab('ach', ['achShop'], true);
	checkSubTab('vendor', ['hasVendor'], true);
	checkSubTab('wizard', ['wizard'], true);
	checkSubTab('dragon', ['dragonOrb'], true);
	checkSubTab('miningEngineer', ['miningEngineer'], true);
	checkSubTab('cooking', ['cookingUnlocked'], true);
	checkSubTab('spellbook', [
		'bindedMagicPage1'
		, 'bindedMagicPage2'
		, 'bindedMagicPage3'
		, 'bindedMagicPage4'
		, 'bindedMagicPage5'
		, 'bindedMagicPage6'
	], true);

	function setVisibilityOfSubTabs()
	{
		const hide = !getSetting('addSubTabs');
		document.body.classList[hide ? 'add' : 'remove']('hide-sub-tabs');
	}
	setVisibilityOfSubTabs();
	observeSetting('addSubTabs', () => setVisibilityOfSubTabs());
}



/**
 * improve exploring dialog
 */

function improveExploringDialog()
{
	const style = document.createElement('style');
	style.innerHTML = `
div#dialog-explore-areas tr > td:nth-child(2) img
{
	height: 38px;
	width: 63px;
}
	`;
	document.head.appendChild(style);

	const table = document.querySelector('#dialog-explore-areas table.table-stats');
	const rows = table.rows;
	for (let i = 0; i < rows.length; i++)
	{
		const row = rows[i];
		if (i == 0)
		{
			const refCell = row.cells[2];

			const levelTh = document.createElement('th');
			levelTh.innerHTML = 'Level';
			row.insertBefore(levelTh, refCell);

			const energyTh = document.createElement('th');
			energyTh.innerHTML = 'Energy/XP Cost';
			row.insertBefore(energyTh, refCell);

			refCell.textContent = 'Lair';
		}
		else
		{
			const reqCell = row.cells[2];
			const levelCell = row.insertCell(2);
			const img = reqCell.firstElementChild;
			const levelChild = img.nextSibling;
			const level = parseInt(levelChild.textContent.replace(/\D*(?=\d)/, ''), 10);
			const xpMatch = levelChild.textContent.match(/\((.+) XP\)/);
			levelCell.textContent = formatNumber(level);
			reqCell.removeChild(img);
			reqCell.removeChild(levelChild);
			if (levelChild.textContent.trim() == '')
			{
				reqCell.removeChild(levelChild.nextElementSibling);
			}

			const energyCell = row.insertCell(3);
			const energyText = reqCell.lastChild.textContent.replace(/\D*(?=\d)/, '');
			const energy = parseInt(energyText, 10) * (/\d\s*M(\W|$)/.test(energyText) ? 1e6 : 1);
			const energyFactor = 1 - parseInt(window.exploringEnergyReductionPerc, 10) / 100;
			energyCell.textContent = formatNumber(Math.floor(energyFactor * energy));
			if (xpMatch)
			{
				const xp = parseInt(xpMatch[1], 10) * (/\d\s*M(\W|$)/.test(xpMatch[1]) ? 1e6 : 1);
				energyCell.innerHTML += '<br>' + formatNumber(xp) + ' XP';
			}
			reqCell.removeChild(reqCell.lastChild);

			if (reqCell.textContent.indexOf('Access to lair') != -1)
			{
				const br = reqCell.lastElementChild;
				const lairText = br.previousSibling;
				reqCell.removeChild(br);
				reqCell.removeChild(lairText);
			}

			const timeCell = row.cells[row.cells.length-1];
			const timeMatch = timeCell.textContent.match(/(\d+)(?::(\d+))?\s*(min|h)\./);
			if (timeMatch)
			{
				const num1 = parseInt(timeMatch[1], 10);
				const num2 = parseInt(timeMatch[2] || '0', 10);
				const factor = {
					'min': 60
					, 'h': 3600
				}[timeMatch[3]] || 1;
				const timeFactor = 1 - parseInt(window.exploringTimeReductionPerc, 10) / 100;
				const totalSeconds = Math.floor((num1 * factor + num2 * (factor / 60)) * timeFactor);
				const hours = Math.floor(totalSeconds / 3600);
				const minutes = Math.floor(totalSeconds / 60) % 60;
				const seconds = totalSeconds % 60;
				timeCell.textContent = (hours < 10 ? '0' : '') + hours
					+ ':' + (minutes < 10 ? '0' : '') + minutes
					+ ':' + (seconds < 10 ? '0' : '') + seconds
				;
			}
		}
	}
}



/**
 * fix DH1 links
 */

function fixDH1Links()
{
	const links = document.querySelectorAll('a[href]');
	for (let i = 0; i < links.length; i++)
	{
		const link = links[i];
		if (/^https?:\/\/(?:www\.)?diamondhunt\.co\/(?!DH1\/)/.test(link.href))
		{
			link.href = link.href.replace(/(diamondhunt\.co\/)/, '$1DH1/');
		}
	}
}



/**
 * init
 */

function init()
{
	console.info('[%s] "DH1 Fixed" up and running!', (new Date).toLocaleTimeString());

	initObservable();
	initSettings();
	initActivityLog();
	initNotifications();

	fixKeyItems();
	fixFarming();
	fixServerMsg();
	applyNewItemStyle();
	applyNewKeyItemStyle();

	expandEquipment();
	highlightRequirements();
	fixMarket();
	improveLevelCalculation();
	fixInventory();
	fixMachinery();
	fixBrewing();
	fixTabs();
	hideCraftingRecipes();
	hideEquipment();
	improveDialogBtns();
	hideMaxRecipes();
	fixMagic();
	fixNumberFormat();
	fixLevelBar();
	fixMsgBox();
	fixChat();
	addCoopNotificationBox();
	fixCrafting();
	improveTabs();
	improveExploringDialog();

	fixDH1Links();
}
class Command
{
	static isMsg(type)
	{
		return type === 'QUESTION' || type === 'MESSAGE' || type === 'MSG_BOX';
	}

	constructor(cmd)
	{
		this.type = cmd.replace(/=.+$/, '');
		const restCmd = cmd.substr(this.type.length + 1);
		this.params = restCmd.split('~');
		this.isMsg = Command.isMsg(this.type);
		this._msg = '';
		if (this.isMsg)
		{
			this.msg = formatNumbersInText(this.params[0].trim());
		}
	}

	get msg()
	{
		return this._msg;
	}
	set msg(newMsg)
	{
		this.params[0] = this._msg = newMsg;
	}

	prepare()
	{
		if (!this.isMsg)
		{
			return;
		}
		if (this.msg === 'You dont have this seed.')
		{
			this.type = 'MSG_BOX';
			this.msg = `You don't have this seed.`;
		}
		else if (/Your account has been running for/.test(this.msg))
		{
			/*
Your account has been running for 234 Minutes
=> convert minutes into better readable format (hours and minutes)
			*/
			this.msg = this.msg.replace(/(\d+) Minutes/, (str, min) =>
			{
				min = parseInt(min, 10);
				const hours = Math.floor(min / 60);
				min = min % 60;
				return (hours > 0 ? hours + ' hour' + (hours == 1 ? '' : 's') + ' and ' : '')
					+ min + ' minute' + (min == 1 ? '' : 's');
			});
		}
		else
		{
			this.msg = this.msg.replace(
				`<img class='small-img' src='images/brewing/pic_coin.png'>`
				, `<img class='small-img' src='images/pic_coin_bigstack.png'>`
			);
		}
	}
	toString()
	{
		return this.type + '=' + this.params.join('~');
	}
}
document.addEventListener('DOMContentLoaded', () =>
{
	const oldLoadCommand = window.loadCommand;
	window.loadCommand = (cmdString) =>
	{
		const cmd = new Command(cmdString);
		if (!fullyLoaded && cmd.type == 'ITEMS_DATA')
		{
			const ret = oldLoadCommand(cmdString);
			fullyLoaded = true;
			init();
			return ret;
		}

		cmd.prepare();
		// add message to activity log
		if (cmd.isMsg)
		{
			const data = add2ActivityLog(cmd);
			updateActivity(data);
		}
		if (cmd.msg === 'You have completed an achievement')
		{
			notifyMsg(cmd.msg)
				.catch(() => oldLoadCommand(cmd.toString()))
			;
			return;
		}
		else if (cmd.type === 'MESSAGE')
		{
			notifyMsg(cmd.msg)
				.catch(() => oldLoadCommand(cmd.toString()))
			;
			return;
		}
		return oldLoadCommand(cmd.toString());
	};
});



/**
 * fix annoying errors in console caused by web socket events when DOM still loading
 */

function addMessageListenerFix()
{
	const newScript = document.createElement('script');
	newScript.textContent = `
if (window.webSocket != null)
{
	const messageQueue = [];
	const oldOnMessage = webSocket.onmessage;
 	webSocket.onmessage = (event) => messageQueue.push(event);
	document.addEventListener('DOMContentLoaded', () =>
	{
		messageQueue.forEach(event => onMessage(event));
		webSocket.onmessage = oldOnMessage;
	});
}
	`;
	document.head.appendChild(newScript);
}
function isWebSocketScript(script)
{
	return script.textContent.includes('webSocket.onmessage');
}
function fixWebSocketScript()
{
	if (!document.head)
	{
		return;
	}

	const scripts = document.head.querySelectorAll('script');
	let found = false;
	for (let i = 0; i < scripts.length; i++)
	{
		if (isWebSocketScript(scripts[i]))
		{
			addMessageListenerFix();
			return;
		}
	}

	// create an observer instance
	const mutationObserver = new MutationObserver((mutationList) =>
	{
		mutationList.forEach((mutation) =>
		{
			if (mutation.addedNodes.length === 0)
			{
				return;
			}

			for (let i = 0; i < mutation.addedNodes.length; i++)
			{
				const node = mutation.addedNodes[i];
				if (node.tagName == 'SCRIPT' && isWebSocketScript(node))
				{
					mutationObserver.disconnect();
					setTimeout(() => addMessageListenerFix());
					return;
				}
			}
		});
	});
	mutationObserver.observe(document.head, {
		childList: true
	});
}
fixWebSocketScript();

})();