DH2 Fixed

Improve Diamond Hunt 2

目前为 2017-02-26 提交的版本。查看 最新版本

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

(function ()
{
'use strict';



/**
 * observer
 */

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 updateValue(key, newValue)
{
	const oldValue = window[key];
	window[key] = newValue;
	if (oldValue !== newValue)
	{
		(observedKeys.get(key) || []).forEach(fn => fn(key, oldValue, newValue));
	}
}



/**
 * global constants
 */

const tierLevels = ['empty', 'sapphire', 'emerald', 'ruby', 'diamond'];
const furnaceLevels = ['stone', 'bronze', 'iron', 'silver', 'gold'];
const furnaceCapacity = [10, 30, 75, 150, 300];
const ovenLevels = ['bronze', 'iron', 'silver', 'gold'];
const maxOilStorageLevel = 3; // 7
const oilStorageSize = [10e3, 50e3, 100e3];



/**
 * general functions
 */

let styleElement = null;
function addStyle(styleCode)
{
	if (styleElement === null)
	{
		styleElement = document.createElement('style');
		document.head.appendChild(styleElement);
	}
	styleElement.innerHTML += styleCode;
}
function getBoundKey(key)
{
	return 'bound' + key[0].toUpperCase() + key.substr(1);
}
function getTierKey(key, tierLevel)
{
	return tierLevels[tierLevel] + key[0].toUpperCase() + key.substr(1);
}
function formatNumber(num)
{
	return parseFloat(num).toLocaleString('en');
}
function formatNumbersInText(text)
{
	return text.replace(/\d(?:[\d',\.]*\d)?/g, (numStr) =>
	{
		return formatNumber(parseInt(numStr.replace(/\D/g, ''), 10));
	});
}
function now()
{
	return (new Date()).getTime();
}
function padLeft(num, padChar)
{
	return (num < 10 ? padChar : '') + num;
}
function formatTimer(timer)
{
	timer = parseInt(timer, 10);
	const hours = Math.floor(timer / 3600);
	const minutes = Math.floor((timer % 3600) / 60);
	const seconds = timer % 60;
	return padLeft(hours, '0') + ':' + padLeft(minutes, '0') + ':' + padLeft(seconds, '0');
}



/**
 * hide crafting recipes of lower tiers or of maxed machines
 */

function hideTierRecipes(max, getKey, init)
{
	const keys2Observe = [];
	let maxLevel = 0;
	for (let i = max-1; i >= 0; i--)
	{
		const level = i+1;
		const key = getKey(i);
		const boundKey = getBoundKey(key);
		keys2Observe.push(key);
		keys2Observe.push(boundKey);
		if (window[key] > 0 || window[boundKey] > 0)
		{
			maxLevel = Math.max(maxLevel, level);
		}

		const recipeRow = document.getElementById('crafting-' + key);
		if (recipeRow)
		{
			const hide = level <= maxLevel;
			recipeRow.style.display = hide ? 'none' : '';
		}
	}

	if (init)
	{
		observe(keys2Observe, () => hideTierRecipes(max, getKey, false));
	}
}
function hideRecipe(key, max, init)
{
	const maxValue = typeof max === 'function' ? max() : max;
	const boundKey = getBoundKey(key);
	const unbound = parseInt(window[key], 10);
	const bound = parseInt(window[boundKey], 10);

	const recipeRow = document.getElementById('crafting-' + key);
	if (recipeRow)
	{
		const hide = (bound + unbound) >= maxValue;
		recipeRow.style.display = hide ? 'none' : '';
	}

	if (init)
	{
		observe([key, boundKey], () => hideRecipe(key, max, false));
	}
}
function hideCraftedRecipes()
{
	function processRecipes(init)
	{
		// furnace
		hideTierRecipes(
			furnaceLevels.length
			, i => furnaceLevels[i] + 'Furnace'
			, init
		);
		// oil storage
		hideTierRecipes(
			7
			, i => 'oilStorage' + (i+1)
			, init
		);
		// oven recipes
		hideTierRecipes(
			ovenLevels.length
			, i => ovenLevels[i] + 'Oven'
			, init
		);
		// drills
		hideRecipe('drills', 10, init);
		// crushers
		hideRecipe('crushers', 10, init);
		// oil pipe
		hideRecipe('oilPipe', 1, init);
		// row boat
		hideRecipe('rowBoat', 1, init);
	}
	processRecipes(true);

	const oldProcessCraftingTab = window.processCraftingTab;
	window.processCraftingTab = () =>
	{
		const reinit = !!window.refreshLoadCraftingTable;
		oldProcessCraftingTab();

		if (reinit)
		{
			processRecipes(true);
		}
	};
}



/**
 * improve item boxes
 */

function hideNumberInItemBox(key, setVisibility)
{
	const itemBox = document.getElementById('item-box-' + key);
	const numberElement = itemBox.lastElementChild;
	if (setVisibility)
	{
		numberElement.style.visibility = 'hidden';
	}
	else
	{
		numberElement.style.display = 'none';
	}
}
function addSpan2ItemBox(key)
{
	hideNumberInItemBox(key);

	const itemBox = document.getElementById('item-box-' + key);
	const span = document.createElement('span');
	itemBox.appendChild(span);
	return span;
}
function setOilPerSecond(span, oil)
{
	span.innerHTML = `+ ${formatNumber(oil)} L/s <img src="images/oil.png" class="image-icon-20" style="margin-top: -2px;">`;
}
function improveItemBoxes()
{
	// show capacity of furnace
	for (let i = 0; i < furnaceLevels.length; i++)
	{
		const key = furnaceLevels[i] + 'Furnace';
		const capacitySpan = addSpan2ItemBox(getBoundKey(key));
		capacitySpan.className = 'capacity';
		capacitySpan.textContent = 'Capacity: ' + formatNumber(furnaceCapacity[i]);
	}

	// show oil cap of oil storage
	for (let i = 0; i < maxOilStorageLevel; i++)
	{
		const key = 'oilStorage' + (i+1);
		const capSpan = addSpan2ItemBox(getBoundKey(key));
		capSpan.className = 'oil-cap';
		capSpan.textContent = 'Oil cap: ' + formatNumber(oilStorageSize[i]);
	}

	// show oil per second
	const handheldOilSpan = addSpan2ItemBox('handheldOilPump');
	setOilPerSecond(handheldOilSpan, 1*window.miner);
	observe('miner', () => setOilPerSecond(handheldOilSpan, 1*window.miner));
	const oilPipeSpan = addSpan2ItemBox('boundOilPipe');
	setOilPerSecond(oilPipeSpan, 50);

	// show current tier
	hideNumberInItemBox('emptyAnvil', true);
	hideNumberInItemBox('farmer', true);
	const tierItemList = ['pickaxe', 'shovel', 'hammer', 'axe', 'rake', 'fishingRod'];
	for (let tierItem of tierItemList)
	{
		for (let i = 0; i < tierLevels.length; i++)
		{
			const key = getTierKey(tierItem, i);
			const tierSpan = addSpan2ItemBox(tierItem == 'rake' ? key : getBoundKey(key));
			tierSpan.className = 'tier';
			tierSpan.textContent = 'Tier: ' + i;
		}
	}

	// show boat progress
	function setTransitText(span, isInTransit)
	{
		span.textContent = isInTransit ? 'In transit' : 'Ready';
	}
	const boatSpan = addSpan2ItemBox('boundRowBoat');
	setTransitText(boatSpan, window.rowBoatTimer > 0);
	observe('rowBoatTimer', () => setTransitText(boatSpan, window.rowBoatTimer > 0));
	const canoeSpan = addSpan2ItemBox('boundCanoe');
	setTransitText(canoeSpan, window.canoeTimer > 0);
	observe('canoeTimer', () => setTransitText(canoeSpan, window.canoeTimer > 0));
}



/**
 * fix wood cutting
 */

function fixWoodcutting()
{
	addStyle(`
img.woodcutting-tree-img
{
	border: 1px solid transparent;
}
	`);
}



/**
 * fix chat
 */

const lastMsg = new Map();
function isMuted(user)
{
	// return window.mutedPeople.some((name) => user.indexOf(name) > -1);
	return window.mutedPeople.includes(user);
}
function isSpam(user, msg)
{
	return lastMsg.has(user) &&
		lastMsg.get(user).msg == msg &&
		// last message in the last 30 seconds?
		(now() - lastMsg.get(user).time) < 30e3;
}
function handleSpam(user, msg)
{
	const msgObj = lastMsg.get(user);
	msgObj.time = now();
	msgObj.repeat++;
	// a user is allowed to repeat a message twice (write it 3 times in total)
	if (msgObj.repeat > 1)
	{
		window.mutedPeople.push(user);
	}
}
function fixChat()
{
	const oldAddToChatBox = window.addToChatBox;
	window.addToChatBox = (userChatting, iconSet, tagSet, msg, isPM) =>
	{
		if (isMuted(userChatting))
		{
			return;
		}
		if (isSpam(userChatting, msg))
		{
			return handleSpam(userChatting, msg);
		}
		lastMsg.set(userChatting, {
			time: now()
			, msg: msg
			, repeat: 0
		});

		// add clickable links
		msg = msg.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>');

		oldAddToChatBox(userChatting, iconSet, tagSet, msg, isPM);
	};

	// add checkbox instead of button for toggling auto scrolling
	const btn = document.querySelector('input[value="Toggle Autoscroll"]');
	const checkboxId = 'chat-toggle-autoscroll';
	// create checkbox
	const toggleCheckbox = document.createElement('input');
	toggleCheckbox.type = 'checkbox';
	toggleCheckbox.id = checkboxId;
	toggleCheckbox.checked = true;
	// create label
	const toggleLabel = document.createElement('label');
	toggleLabel.htmlFor = checkboxId;
	toggleLabel.textContent = 'Autoscroll';
	btn.parentNode.insertBefore(toggleCheckbox, btn);
	btn.parentNode.insertBefore(toggleLabel, btn);
	btn.style.display = 'none';

	toggleCheckbox.addEventListener('change', function ()
	{
		window.isAutoScrolling = this.checked;
		window.scrollText('none', this.checked ? 'lime' : 'red', (this.checked ? 'En' : 'Dis') + 'abled');
	});
}



/**
 * hopefully only temporary fixes
 */

function temporaryFixes()
{
	// fix recipe of oil storage 3
	const oldProcessCraftingTab = window.processCraftingTab;
	window.processCraftingTab = () =>
	{
		const reinit = !!window.refreshLoadCraftingTable;
		oldProcessCraftingTab();

		if (reinit)
		{
			// 200 instead of 100 gold bars
			window.craftingRecipes['oilStorage3'].recipeCost[2] = 200;
			document.getElementById('recipe-cost-oilStorage3-2').textContent = 200;
			window.showMateriesNeededAndLevelLabels('oilStorage3');
		}
	};
	
	// fix magic tab
	const els = document.querySelectorAll('img[src="images/essence"], img[src="images/stardust"]');
	for (let i = 0; i < els.length; i++)
	{
		els[i].src = els[i].src + '.png';
	}

	const oldAddRecipeToMagicTable = window.addRecipeToMagicTable;
	window.addRecipeToMagicTable = (magicRecipe) =>
	{
		console.log('addRecipeToMagicTable');

		const itemName = magicRecipe.itemName;

		let htmlCode = `<tr id="magic-${itemName}">`
			+ `<td>${getItemName(itemName)}</td>`
			+ `<td><img src="images/hero/spells/${itemName}.png" style="margin-top:3px;border:1px solid black;" class="image-icon-40" /></td>`
			+ `<td id="magic-recipe-level-req-${itemName}">${magicRecipe.levelReq}</td>`
		;

		//mats
		htmlCode += `<td>`;
		const recipeParts = magicRecipe.recipe;
		for (let i = 0; i < recipeParts.length; i++)
		{
			// important part here: add ".png" to image src after recipe part
			htmlCode += `<img src="images/${recipeParts[i]}.png" class="image-icon-30" /> `
				+ `<span id="magic-recipe-cost-${itemName}-${i}">${magicRecipe.recipeCost[i]}</span>`
				+ `<br />`
			;
		}

		//special cases
		switch (itemName)
		{
		}
		//spanItemAmount.setAttribute("data-item-display", itemName);
		htmlCode += `</td>`;

		htmlCode += `<td>${magicRecipe.description}</td>`
			+ `<td>" + magicRecipe.xp + "</td>`
			+ `<td id='magic-charges-" + itemName + "'>0</td>`
		;

		window.$('#table-magic-recipe tr:last').after(htmlCode);
		window.$('#table-magic-recipe tr:last').click(() =>
		{
			window.sendBytes("SPELL_CHARGE=" + itemName);
		});
	}
}



/**
 * improve timer
 */

function improveTimer()
{
	window.formatTime = (seconds) =>
	{
		return formatTimer(seconds);
	};
	window.formatTimeShort2 = (seconds) =>
	{
		return formatTimer(seconds);
	};

	const barInfo = {
		1: {
			name: 'bronze'
			, timePerBar: 1
		}
		, 2: {
			name: 'iron'
			, timePerBar: 5
		}
		, 3: {
			name: 'silver'
			, timePerBar: 10
		}
		, 4: {
			name: 'gold'
			, timePerBar: 30
		}
		, 5: {
			name: 'glass'
			, timePerBar: 1
		}
	};
	const smeltingPercEl = document.querySelector('span[data-item-display="smeltingPerc"]');
	const smeltingTimerEl = document.createElement('span');
	smeltingPercEl.style.display = 'none';
	smeltingPercEl.parentNode.lastElementChild.style.display = 'none';
	smeltingPercEl.parentNode.appendChild(smeltingTimerEl);
	let interval = null;
	let barName = '';
	let remainingTime = 0;
	function setRemainingTime()
	{
		smeltingTimerEl.textContent = formatTimer(Math.max(remainingTime, 0));
	}
	function updateSmeltingPerc()
	{
		const info = barInfo[window.smeltingBarType];
		if (info)
		{
			const totalTime = info.timePerBar * window.smeltingTotalAmount;
			const time = Math.round(totalTime * (1 - window.smeltingPerc / 100));
			if (interval == null || barName != info.name)
			{
				barName = info.name;
				remainingTime = time;
				interval = window.setInterval(() =>
				{
					remainingTime--;
					setRemainingTime();
				}, 1000);
				setRemainingTime();
			}
			// tolerate up to 2 seconds deviation (to make it smoother for the user)
			else if (Math.abs(remainingTime - time) > 2)
			{
				remainingTime = time;
				setRemainingTime();
			}
		}
		else if (interval)
		{
			window.clearInterval(interval);
			barName = '';
			interval = null;
		}
	}
	observe('smeltingPerc', () => updateSmeltingPerc());
	updateSmeltingPerc();
}



/**
 * init
 */

function init()
{
	temporaryFixes();

	hideCraftedRecipes();
	improveItemBoxes();
	fixWoodcutting();
	fixChat();
	improveTimer();
}
document.addEventListener('DOMContentLoaded', () =>
{
	const oldDoCommand = window.doCommand;
	window.doCommand = (data) =>
	{
		if (data.startsWith('REFRESH_ITEMS='))
		{
			const itemDataValues = data.split('=')[1].split(';');
			const itemArray = [];
			for (var i = 0; i < itemDataValues.length; i++)
			{
				const [key, newValue] = itemDataValues[i].split('~');
				itemArray.push(key);
				updateValue(key, newValue);
			}

			window.refreshItemValues(itemArray, false);

			if (window.firstLoadGame)
			{
				window.loadInitial();
				window.firstLoadGame = false;
				init();
			}
			else
			{
				window.clientGameLoop();
			}
			return;
		}
		return oldDoCommand(data);
	};
});



/**
 * fix web socket errors
 */

function webSocketLoaded(event)
{
	if (window.webSocket == null)
	{
		console.error('no webSocket instance found!');
		return;
	}

	const messageQueue = [];
	const oldOnMessage = webSocket.onmessage;
	webSocket.onmessage = (event) => messageQueue.push(event);
	document.addEventListener('DOMContentLoaded', () =>
	{
		messageQueue.forEach(event => onMessage(event));
		webSocket.onmessage = oldOnMessage;
	});

	const commandQueue = [];
	const oldSendBytes = window.sendBytes;
	window.sendBytes = (command) => commandQueue.push(command);
	const oldOnOpen = webSocket.onopen;
	webSocket.onopen = (event) =>
	{
		window.sendBytes = oldSendBytes;
		commandQueue.forEach(command => window.sendBytes(command));
		return oldOnOpen(event);
	};
}
function isWebSocketScript(script)
{
	return script.src.includes('socket.js');
}
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]))
		{
			// does this work?
			scripts[i].onload = webSocketLoaded;
			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();
					node.onload = webSocketLoaded;
					return;
				}
			}
		});
	});
	mutationObserver.observe(document.head, {
		childList: true
	});
}
fixWebSocketScript();
})();