DH2QoL

Quality of Life tweaks for Diamond Hunt 2

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name		DH2QoL
// @namespace	https://greasyfork.org/
// @version		0.1.9
// @description	Quality of Life tweaks for Diamond Hunt 2
// @author		John
// @match		http://*.diamondhunt.co/game.php
// @match		https://*.diamondhunt.co/game.php
// @run-at document-idle
// @grant		none
// ==/UserScript==
(function() {
'use strict';

const DEFAULT_CONFIG = {
	bool : {
		enhancedNotifications : {
			text : "Use persistent notifications with enhanced functionality?",
			value : true
		},
		enableRightClickFurnaceRepeat : {
			text : "Enable right clicking bound furnace to repeat last action?",
			value : true
		},
		enableBatchCommands : {
			text : "Enable batch commands (right click to cook all, eat all, brew all, craft all, etc)?",
			value : true
		},
		disableUnequipInCombat : {
			text : "Disable unequip in combat?",
			value : true
		},
		disableLeftClickSellGems : {
			text : "Disable selling gems on left click?",
			value : true
		},
		disableLeftClickSellCraftingSupplies : {
			text : "Disable selling crafting supplies on left click?",
			value : true
		},
		stardustMonitor : {
			text : "Use stardust monitor?",
			value : true
		},
		updateOilTimer : {
			text : "Add oil timer & oil gains/consumption info?",
			value : true
		}/*,
		linkify : {
			text : "Make chat links clickable?",
			value : true
		}*/
	},

	enablePotionMonitor : true,
	enablePotionHelper : true,
	monitoredPotions : ["stardustPotion", "superStardustPotion"],
};
const CONFIG = JSON.parse(window.localStorage.getItem("dh2qol-config")) || DEFAULT_CONFIG;
const SMELTING = {
	lastCurrentAmount : -1,
	lastTimer : -1,
	lastRealTime : -1
};
const TREES = {
	"1" : {
		"id" : "1",
		"variable" : "tree",
		"name" : "Tree",
		"growTime" : 10800 // 3 hours
	},
	"2" : {
		"id" : "2",
		"variable" : "oakTree",
		"name" : "Oak Tree",
		"growTime" : 21600 // 6 hours
	},
	"3" : {
		"id" : "3",
		"variable" : "willowTree",
		"name" : "Willow Tree",
		"growTime" : 28800 // 8 hours
	},
	"4" : {
		"id" : 4,
		"variable" : "mapleTree",
		"name" : "Maple Tree",
		"growTime" : 43200 // 12 hours
	},
	"5" : {
		"id" : "5",
		"variable" : "stardustTree",
		"name" : "Stardust Tree",
		"growTime" : 57600 // 16 hours
	},
	"6" : {
		"id" : 6,
		"variable" : "essenceTree",
		"name" : "Essence Tree",
		"growTime" : 72000 // 20 hours
	}
};

const MONITORED_POTIONS = ["stardustPotion", "superStardustPotion"];
const STARDUST_MONITOR = {
	lastStardust : -1,
	monitorTicks : 0
};

(function init(triesLeft) {

	// Thanks /u/TheZorbing
	if (triesLeft > 0 && (!window.hasOwnProperty("webSocket") || window.webSocket.readyState !== WebSocket.OPEN || window.firstLoadGame === true)) {
		setTimeout(() => {
			init(--triesLeft);
		}, 100);
		return;
	}

	if (window.hasOwnProperty("webSocket") && window.webSocket.readyState === WebSocket.OPEN && !window.firstLoadGame) {
		console.log(`Launching DH2QoL. Welcome ${window.username}! Thanks for using DH2QoL`);
		processDormantTabsOnLoad();
		try {
			if (CONFIG.bool.enableBatchCommands) {
				enableRightClickFurnaceRepeat();
				//enableRightClickBrewAllPotion();
				enableRightClickCookAllFood();
				//enableRightClickEatAllFood();
				//enableRightClickCraftAll();
			}
			if (CONFIG.bool.disableUnequipInCombat)
				//disableUnequipInCombat();
			if (CONFIG.bool.disableLeftClickSellCraftingSupplies)
				disableLeftClickSellCraftingSupplies();
			if (CONFIG.bool.disableLeftClickSellGems)
				disableLeftClickSellGems();
		} catch (e) {
			console.log("DH2QoL: Failed to implement some left and right click functionality to certain elements:");
			console.log(e);
		}
		try {
			// Interface delight
			updateStyleSheets();
			//addSettingsElement();
			addNotificationElements();
			addOilTimerElements();
			addStardustMonitorElement();
		} catch (e) {
			console.log("DH2QoL: Failed update the stylesheet and document with new notifications:");
			console.log(e);
		}
		try {
			// Proxies
			proxyWebSocketOnMessage();
			proxyAddToChatBox();
			proxyConfirmDialogue();
			proxyProcessWoodcuttingTab();
			replaceDHNativeFormatTime();
		} catch(e) {
			console.log("DH2QoL: Failed to proxy some functions:");
			console.log(e);
		}
	} else {
		console.log(`Something went wrong, DH2QoL Failed to initialize. WebSockets: ${(window.hasOwnProperty("webSocket") && window.webSocket.readyState === WebSocket.OPEN)} First Load: ${window.firstLoadGame}`);
	}

	return;
})(100);

/*
	Actions performed following any game tick
*/
function postGameTick() {
	try {estimateSmeltingTimer();} catch (e) {console.log(e);} // This must come before smelting notification & timer updates
	try {updateNotificationElements();} catch (e) {console.log(e);}
	try {
		updateSmeltingTimer();
		updateWoodcuttingTimers();
	} catch (e) {console.log(e);}
	try {updateStardustMonitor();} catch (e) {console.log(e);}
	try {updateOilTimer()} catch (e) {console.log(e);}
}

/*
	Adds the DH2QoL settings button to the profile & settings page
*/
function addSettingsElement() {
	let node = document.getElementById("profile-toggleTable");
	if (node && node.parentNode) {
		let span = document.createElement("span");
		span.id = "dh2qol-settingsLink";
		span.style.cursor = "pointer";
		span.style.color = "white";
		span.textContent = "Open DH2QoL Settings";
		span.onclick = openSettings;
		node.parentNode.appendChild(document.createElement("br"));
		node.parentNode.appendChild(span);
		node.parentNode.appendChild(document.createElement("br"));
	}
}

function openSettings() {
	let newWindow = window.open();
	newWindow.CONFIG = CONFIG;
	newWindow.document.title = "DH2QoL Settings";
	newWindow.document.body.style.backgroundColor = "black";
	newWindow.document.body.style.color = "white";
	newWindow.document.body.innerHTML = `\<div>
	\<span style='font-size:22;'>DH2QoL Settings\</span>\<br>
	\<span>\<p>Most settings changes will require that Diamond Hunt be refreshed before taking effect. Clearing browser cache will reset settings to their default.\<p>\</span>
	\</div>
	\<div>`;

	const KEYS = Object.keys(newWindow.CONFIG.bool);
	KEYS.forEach((key) => {
		newWindow.document.body.innerHTML += `\<p>
		\<input type="checkbox" id="checkbox-${key}" checked="${newWindow.CONFIG.bool[key].value}" onchange="changeBoolSetting('${key}', this.checked);">
  		\<label for="checkbox-${key}">${newWindow.CONFIG.bool[key].text}\</label>
		\</p>`
	});

	newWindow.document.body.innerHTML += `\</div>`;

	newWindow.changeBoolSetting = function(settingPropertyName, val) {
		if (newWindow.CONFIG && newWindow.CONFIG.bool[settingPropertyName]) {
			newWindow.CONFIG.bool[settingPropertyName].value = val;
		}
	};

	newWindow.saveSettings = function() {
		newWindow.close();
	};
}

/*
	Loads initial data for unopened tabs
*/
function processDormantTabsOnLoad() {
	window.processCraftingTab();
	window.processBrewingTab();
}

/*
	Update the game-main-style.css styleSheet
	Delete then update the span.notif-box rule, and add our dhqol-notif-ready rule
*/
function updateStyleSheets() {
	for (let styleSheet of document.styleSheets) {
		if (styleSheet.href && styleSheet.href.indexOf("game-main-style.css") !== -1) {
			if (styleSheet.cssRules) {
				for (let index in styleSheet.cssRules) {
					let cssRule = styleSheet.cssRules[index];
					if (cssRule.selectorText && cssRule.selectorText.indexOf("span.notif-box") !== -1) {
						styleSheet.deleteRule(index); // Delete the original span.notif-box rule
						styleSheet.insertRule(`span.notif-box
							{
								display:inline-block;
								margin:5px 5px 5px 0px;
								color:white;
								border:1px solid silver;
								padding:5px 10px;
								font-size:12pt;
								background: -webkit-linear-gradient(#801A00, #C15033); /* For Safari 5.1 to 6.0 */
								background: -o-linear-gradient(#801A00, #C15033); /* For Opera 11.1 to 12.0 */
								background: -moz-linear-gradient(#801A00, #C15033); /* For Firefox 3.6 to 15 */
								background: linear-gradient(#801A00, #C15033); /* Standard syntax */
							}`, 0);
						styleSheet.insertRule(`span.dhqol-notif-ready
							{
								display:inline-block;
								margin:5px 5px 5px 0px;
								color:white;
								border:1px solid silver;
								padding:5px 5px;
								font-size:12pt;
								cursor:pointer;
								background: -webkit-linear-gradient(#801A00, #C15033);
								background: -o-linear-gradient(#801A00, #C15033);
								background: -moz-linear-gradient(#801A00, #C15033);
								background: linear-gradient(#161618, #48ab32);
							}`, 0);
					}
				}
			}
			break;
		}
	}
}

/*
	Create and add our custom DH2QoL persistent notification elements to the document
*/
function addNotificationElements() {
	const SPAN = document.createElement("span");
	SPAN.className = "dhqol-notif-ready";
	SPAN.style = "display:inline-block";
	SPAN.appendChild(document.createElement("img"));
	SPAN.children[0].className = "image-icon-50";
	SPAN.appendChild(document.createElement("span"));

	let notificationNode = document.getElementById("notifaction-area");
	let refNode = notificationNode.children[0] || null;

	// Create our new notification span elements
	let furnaceElement = SPAN.cloneNode(true);
	furnaceElement.id = "dhqol-notif-furnace";
	furnaceElement.children[0].setAttribute("src", "images/silverFurnace.png");
	let woodCuttingElement = SPAN.cloneNode(true);
	woodCuttingElement.id = "dhqol-notif-woodcutting";
	woodCuttingElement.children[0].setAttribute("src", "images/icons/woodcutting.png");
	let farmingElement = SPAN.cloneNode(true);
	farmingElement.id = "dhqol-notif-farming";
	farmingElement.children[0].setAttribute("src", "images/icons/watering-can.png");
	let combatElement = SPAN.cloneNode(true);
	combatElement.id = "dhqol-notif-combat";
	combatElement.children[0].setAttribute("src", "images/icons/combat.png");
	let rowBoatElement = SPAN.cloneNode(true);
	rowBoatElement.id = "dhqol-notif-rowboat";
	rowBoatElement.children[0].setAttribute("src", "images/rowBoat.png");
	let canoeElement = SPAN.cloneNode(true);
	canoeElement.id = "dhqol-notif-canoe";
	canoeElement.children[0].setAttribute("src", "images/canoe.png");
	let vialElement = SPAN.cloneNode(true);
	vialElement.id = "dhqol-notif-vial";
	vialElement.children[0].setAttribute("src", "images/vialOfWater.png");
	vialElement.setAttribute("onclick", "window.openTab('brewing');");
	/*
	vialElement.oncontextmenu = function() {
		drinkMonitoredPotions();
		return false;
	};
	*/
	// Insert our new elements into the document
	notificationNode.insertBefore(furnaceElement, refNode);
	notificationNode.insertBefore(woodCuttingElement, refNode);
	notificationNode.insertBefore(farmingElement, refNode);
	notificationNode.insertBefore(combatElement, refNode);
	notificationNode.insertBefore(rowBoatElement, refNode);
	notificationNode.insertBefore(canoeElement, refNode);
	notificationNode.insertBefore(vialElement, refNode);
}

function updateNotificationElements() {
	// Hide native DH2 notifications
 	const NOTIFICATION_IDS = ["notification-static-farming", "notification-static-woodcutting", "notification-static-combat", "notif-smelting", "notif-rowBoatTimer", "notif-canoeTimer"];
	NOTIFICATION_IDS.forEach((id) => {
			let node = document.getElementById(id);
			if (node)
				node.style.display = "none";
	});

	// Update DH2QoL Custom notifications based on gamestate
	let furnaceElement = document.getElementById("dhqol-notif-furnace");
	furnaceElement.style.display = window.craftingUnlocked == 1 ? "inline-block" : "none";
	if (window.smeltingBarType == 0) {
		furnaceElement.className = "dhqol-notif-ready";
		furnaceElement.children[0].setAttribute("src", "images/silverFurnace.png");
		furnaceElement.children[1].textContent = "";
		furnaceElement.onclick = function() {
			window.openTab("crafting");
			if (getBoundFurnace() !== "") {
				window.openFurnaceDialogue(getBoundFurnace());
			}
		};
		furnaceElement.oncontextmenu = function() {
			furnaceRepeat();
			return false;
		};
	} else {
		furnaceElement.className = "notif-box";
		furnaceElement.children[0].setAttribute("src", `images/${window.getBarFromId(window.smeltingBarType)}.png`);
		furnaceElement.children[1].textContent = `${formatTime(SMELTING.lastTimer / 1000)} (${window.smeltingPerc}%)`;
		furnaceElement.setAttribute("onclick", "");
	}
	let woodCuttingElement = document.getElementById("dhqol-notif-woodcutting");
	woodCuttingElement.style.display = window.woodcuttingUnlocked == 1 ? "inline-block;" : "none";
	if (window.treeStage1 == 4 || window.treeStage2 == 4 || window.treeStage3 == 4 || window.treeStage4 == 4 || window.treeStage5 == 4 || window.treeStage6 == 4) {
		woodCuttingElement.className = "dhqol-notif-ready";
		woodCuttingElement.children[1].textContent = "";
		woodCuttingElement.setAttribute("onclick", "window.openTab('woodcutting')");
		/*
		woodCuttingElement.oncontextmenu = function() {
			harvestTrees();
			return false;
		};
		*/
	} else {
		let gt = getSoonestWoodcuttingTimer();
		woodCuttingElement.className = "notif-box";
		woodCuttingElement.children[1].textContent = gt === null ? "" : formatTime(gt);
		woodCuttingElement.setAttribute("onclick", "");
	}
	let farmingElement = document.getElementById("dhqol-notif-farming");
	farmingElement.style.display = window.farmingUnlocked == 1 ? "inline-block" : "none";
	if (window.farmingPatchStage1 == 0 || window.farmingPatchStage1 == 4 || window.farmingPatchStage2 == 0 || window.farmingPatchStage2 == 4
		|| window.farmingPatchStage3 == 0 || window.farmingPatchStage3 == 4 || window.farmingPatchStage4 == 0 || window.farmingPatchStage4 == 4
		|| (window.donorFarmingPatch >= window.currentTimeMillis && (window.farmingPatchStage5 == 0 || window.farmingPatchStage5 == 4 || window.farmingPatchStage6 == 0 || window.farmingPatchStage6 == 4))) {
			farmingElement.className = "dhqol-notif-ready";
			farmingElement.setAttribute("onclick", "openTab('farming')");
			farmingElement.children[1].textContent = "";
			farmingElement.oncontextmenu = function() {
				if (window.planter == 1) {
					window.openFarmingPatchDialogue(-1);
					return false;
				}
			};
		} else {
			farmingElement.className = "notif-box";
			farmingElement.setAttribute("onclick", "");
			farmingElement.children[1].textContent = formatTime(getSoonestFarmingTimer());
		}

	let combatElement = document.getElementById("dhqol-notif-combat");
	combatElement.style.display = window.combatUnlocked == 1 ? "inline-block" : "none";
	if (window.combatGlobalCooldown == 0) {
		combatElement.className = "dhqol-notif-ready";
		combatElement.children[1].innerHTML = '<img src="images/steak.png" style="" class="image-icon-15">' + parseInt(window.energy, 10).toLocaleString("en-US");
		combatElement.setAttribute("onclick", "window.openTab('combat'); window.openFightMenu()");
	} else {
		combatElement.className = "notif-box";
		combatElement.children[1].innerHTML = formatTime(window.combatGlobalCooldown);
		combatElement.setAttribute("onclick", "");
	}
	let rowBoatElement = document.getElementById("dhqol-notif-rowboat");
	let canoeElement = document.getElementById("dhqol-notif-canoe");
	// Only display one notification if a boat is at sea since you can only send out 1 boat
	rowBoatElement.style.display = window.boundRowBoat == 0 ? "none" : window.boundCanoe == 0 || window.canoeTimer == 0 ? "inline-block" : "none";
	canoeElement.style.display = window.boundCanoe == 0 ? "none" : window.boundRowBoat == 0 || window.rowBoatTimer == 0 ? "inline-block" : "none";
	if (window.rowBoatTimer == 0) {
		rowBoatElement.className = "dhqol-notif-ready";
		rowBoatElement.children[1].innerHTML = '<img class="image-icon-15" src="images/fishingBait.png">' + window.fishingBait;
		rowBoatElement.setAttribute("onclick", "window.clicksBoat('rowBoat')");
	} else {
		rowBoatElement.className = "notif-box";
		rowBoatElement.children[1].innerHTML = formatTime(window.rowBoatTimer);
		rowBoatElement.setAttribute("onclick", "w");
	}
	if (window.canoeTimer == 0) {
		canoeElement.className = "dhqol-notif-ready";
		canoeElement.children[1].innerHTML = '<img class="image-icon-15" src="images/fishingBait.png">' + window.fishingBait;
		canoeElement.setAttribute("onclick", "window.clicksBoat('canoe')");
	} else {
		canoeElement.className = "notif-box";
		canoeElement.children[1].innerHTML = formatTime(window.canoeTimer);
		canoeElement.setAttribute("onclick", "");
	}

	processPotionHelper();
}

function processPotionHelper() {
	let potions = getMonitoredPotionsToDrink();
	let vialElement = document.getElementById("dhqol-notif-vial");
	vialElement.style.display = (window.brewingUnlocked == 1 ? (potions.length > 0 ? "inline-block" : "none") : "none");
}

function getMonitoredPotionsToDrink() {
	let potions = [];
	MONITORED_POTIONS.forEach((monitoredPotion) => {
		if (window[monitoredPotion] > 0 && (window[monitoredPotion + "Timer"] !== undefined && window[monitoredPotion + "Timer"] == 0)) {
			potions.push(monitoredPotion);
		}
	});
	return potions;
}

function drinkMonitoredPotions() {
	let timeout = 0;
	MONITORED_POTIONS.forEach((monitoredPotion) => {
		if (window[monitoredPotion] > 0 && (window[monitoredPotion + "Timer"] !== undefined && window[monitoredPotion + "Timer"] == 0)) {
			setTimeout(() => {
				if (window[monitoredPotion + "Timer"] !== undefined && window[monitoredPotion + "Timer"] == 0)
					window.sendBytes("DRINK=" + monitoredPotion);
			}, timeout * 500);
			timeout++;
		}
	});
}

function addStardustMonitorElement() {
	let sdNode = document.querySelector("[data-item-display=stardust]");
	if (sdNode) {
		let parentNode = sdNode.parentNode;
		if (parentNode) {
			let newNode = document.createElement("span");
			newNode.id = "dh2qol-stardustMonitor";
			newNode.style.color = "orange";
			parentNode.appendChild(newNode);
		}
	}
}

function updateStardustMonitor() {
	let node = document.getElementById("dh2qol-stardustMonitor");
	if (node) {
		if (STARDUST_MONITOR.lastStardust !== -1 && window.stardust > STARDUST_MONITOR.lastStardust) {
			node.textContent = `(+${window.stardust - STARDUST_MONITOR.lastStardust})`;
			STARDUST_MONITOR.lastStardust = window.stardust;
			STARDUST_MONITOR.monitorTicks = 5;
		} else if (STARDUST_MONITOR.lastStardust == window.stardust && STARDUST_MONITOR.monitorTicks > 0) {
			STARDUST_MONITOR.monitorTicks--;
		} else {
			node.textContent = "";
			STARDUST_MONITOR.lastStardust = window.stardust;
		}
	}
}

function harvestTrees() {
	for (let i = 1; i <= 6; i++) {
		if (window["treeStage" + i] == 4) {
			setTimeout(() => {
				window.sendBytes("CHOP_TREE=" + i);
			}, i * 250);
		}
	}
	window.processWoodcuttingTab();
}

function enableRightClickFurnaceRepeat() {
	let nodes = document.querySelectorAll("[onclick^=openFurnaceDialogue]");
	nodes.forEach((node) => {
		if (node instanceof Node) {
			node.oncontextmenu = function() {
				let amt = document.getElementById("input-smelt-bars-amount").value;
				if (window.smeltingBarType == 0 && amt > 0 && window.selectedBar !== "none") {
					window.smelt(amt);
				}
				return false;
			};
		}
	});
}

function furnaceRepeat() {
	let amt = document.getElementById("input-smelt-bars-amount").value;
	if (window.smeltingBarType == 0 && amt > 0 && window.selectedBar !== "none") {
		window.smelt(amt);
	}
}

function enableRightClickBrewAllPotion() {
	const KEYS = Object.keys(window.brewingRecipes);
	KEYS.forEach((key) => {
		if (key === "stardustCrystalPotion") {
			return;
		}
		let recipe = window.brewingRecipes[key];
		let node = document.getElementById("brewing-" + key);
		if (node) {
			node.oncontextmenu = () => {
				let vials = window.vialOfWater;
				let total = vials;
				for (let i = 0; i < recipe.recipe.length; i++) {
					total = (total <= Math.floor(window[recipe.recipe[i]] / recipe.recipeCost[i])) ? total : Math.floor(window[recipe.recipe[i]] / recipe.recipeCost[i]);
				}
				if (total > 0) {
					window.sendBytes(`BREW=${recipe.itemName}~${total}`); // Adds existing left click functionality to right click of the element
					window.processBrewingTab();
				}
				return false;
			};
		}
	});
}

function enableRightClickCookAllFood() {
	document.querySelectorAll("[onclick^=cookFoodDialogue]").forEach((foodNode) => {
		let food = foodNode.id.indexOf("item-box-") != -1 && foodNode.id.split("item-box-")[1];
		if (food !== -1) {
			foodNode.oncontextmenu = function() {
				let amt = Math.min(Math.floor(window.ovenHeat/window.getHeatNeeded(food)), window[food]);
				if (amt > 0) {
					window.cook(food, amt);
				}
				return false;
			};
		}
	});
}

function enableRightClickEatAllFood() {
	document.querySelectorAll("[onclick^=eatFood]").forEach((foodNode) => {
		let food = foodNode.id.indexOf("item-box-") != -1 && foodNode.id.split("item-box-")[1];
		if (food !== -1) {
			foodNode.oncontextmenu = function() {
				window.sendBytes(`CONSUME=${food}~${window[food]}`); // Adds existing left click functionality to right click of the element
				return false;
			};
		}
	});
}

function enableRightClickCraftAll() {
	let node = document.getElementById("crafting-vialOfWater");
	if (node) {
		node.oncontextmenu = function() {
			let amt = Math.floor(window.glass / 5);
			if (amt > 0) {
				window.sendBytes("MULTI_CRAFT=vialOfWater~" + amt);
			}
			return false;
		};
	}
}

function disableLeftClickSellGems() {
		document.getElementById("item-box-sapphire").onclick = null;
		document.getElementById("item-box-emerald").onclick = null;
		document.getElementById("item-box-ruby").onclick = null;
		document.getElementById("item-box-diamond").onclick = null;
		document.getElementById("item-box-bloodDiamond").onclick = null;
}

function disableLeftClickSellCraftingSupplies() {
	let node = document.getElementById("tab-sub-container-crafting");
	if (node) {
		node.querySelectorAll("[onclick^=openSellNPCDialogue]").forEach((n) => {
			n.onclick = null;
		});
	}
}

function disableUnequipInCombat() {
	let node = document.querySelector("[class=imageHero]");
	if (node && node.parentNode) {
		node.parentNode.onclick = function() {
			if (!window.isInCombat()) {
				window.sendBytes("UEQUIP_H"); // This is just overwriting existing functionality with the same functionality but with added restrictions (cannot be in combat)
			}
		};
	}
}

function getBoundFurnace() {
	let furnace = "";
	[].slice.call(document.querySelectorAll("[onclick^=openFurnaceDialogue]")).some((node) => {
		let boundFurnace = node.id.indexOf("item-box-bound") !== -1 && node.id.split("item-box-")[1];
		if (window[boundFurnace] > 0) {
			furnace = boundFurnace;
			return true;
		}
		return false;
	});
	return furnace;
}

function getOilCapacity() {
	return window.maxOil;
}

function getCurrentOil() {
	return window.oil;
}

function getNetOilConsumption() {
	return window.oilIn - window.oilOut;
}

/*****
*
* F U N C T I O N P R O X I E S
*
*****/

function proxyWebSocketOnMessage() {
	const PROXY = window.webSocket.onmessage;
	window.webSocket.onmessage = function(e) {
		PROXY.apply(this, arguments);
		postGameTick();
	};
}

function proxyAddToChatBox() {
	const PROXY = window.addToChatBox;
	window.addToChatBox = function(username, icon, tag, message, isPM) {
		//arguments[3] = linkify(arguments[3]);
		PROXY.apply(this, arguments);
	};
}

function proxyConfirmDialogue() {
	const PROXY = window.confirmDialogue;
	window.confirmDialogue = function(width, bodyText, buttonText1, buttonText2, sendBytes) {
		PROXY.apply(this, arguments);
	};
}

function proxyProcessWoodcuttingTab() {
	const PROXY = window.processWoodcuttingTab;
	window.processWoodcuttingTab = function() {
		PROXY.apply(this, arguments);
		// Immediately updates woodcutting timers upon opening/processing the tab to prevent sudden change of format a second after opening the tab
		updateWoodcuttingTimers();
	};
}

/*****
*
* T I M E R S & F O R M A T I N G
*
*****/

/*
	Check if a string of text can be a URL
*/
function isLink(text) {
	return text.test(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig);
}

/*
	Make links clickable
	No longer needed as it is finally DH Native
*/
/*function linkify(text) {
	return text.replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>");
}*/

function padLeft(value, padChar, length) {
	value = value.toString(); padChar = padChar.toString();
	return value.length < length ? padLeft(padChar + value, padChar, length) : value;
}

/*
	Formats a time (in seconds) as hh:mm:ss or mm:ss if no hours
*/
function formatTime(secs) {
	let seconds = Math.round(secs % 60);
	let minutes = Math.floor((secs % 3600) / 60);
	let hours = Math.floor(secs / 3600);

	return `${hours > 0 ? padLeft(hours, 0, 2) + ":" : ""}${padLeft(minutes, 0, 2)}:${padLeft(seconds, 0, 2)}`;
}
/*
	Overwrite Diamond Hunt 2's native formatTime functions with our own to achieve nicely formatted timers with no hassle
*/
function replaceDHNativeFormatTime() {
	window.formatTime = formatTime;
	window.formatTimeShort = formatTime;
	window.formatTimeShort2 = formatTime;
}

/*
	Adds and updates a smelting timer
*/
function updateSmeltingTimer() {
	let node = document.getElementById("notif-smelting");
	if (node && node.children.length > 1)
		node.children[1].textContent = `${formatTime(SMELTING.lastTimer / 1000)} (${window.smeltingPerc}%)`;
}

/*
	Tries to estimate smelting time remaining based on the time it takes to smelt a bar and the real time elapsed between each barType
	Times are converted to milliseconds for increased accuracy
*/
function estimateSmeltingTimer() {
	let smeltingTotalAmount = window.smeltingTotalAmount;
	let smeltingCurrentAmount = window.smeltingAmount;
	let smeltingTotalTime = window.smeltingPercD * 1000;
	let barType = window.smeltingBarType;
	let now = Date.now();

	if (barType > 0) {
		let barTime = window.getTimerPerBar(window.getBarFromId(barType));
		let roughTimeElapsed = (smeltingCurrentAmount * barTime) * 1000;
		if (SMELTING.lastCurrentAmount === smeltingCurrentAmount) {
			// Number of bars smelted hasn't changed so we'll use time-based estimations
			SMELTING.lastTimer = SMELTING.lastTimer - (now - SMELTING.lastRealTime);
			SMELTING.lastRealTime = now;
		} else {
			// Number of bars smelted HAS changed so we'll use a rough estimate of time remaining based on time to smelt each bar
			SMELTING.lastCurrentAmount = smeltingCurrentAmount;
			SMELTING.lastTimer = Math.max(smeltingTotalTime - roughTimeElapsed, 0);
			SMELTING.lastRealTime = now;
		}
	} else {
		// Reset everything since nothing is being smelted
		SMELTING.lastCurrentAmount = -1;
		SMELTING.lastTimer = -1;
		SMELTING.lastRealTime = -1;
	}
}

/*
	Adds and updates a woodcutting timers
*/
function updateWoodcuttingTimers() {
	// Update DH2 native woodcutting timers
	for (let i = 1; i <= 6; i++) {
		let node = document.getElementById("wc-div-tree-lbl-" + i);
		if (node) {
			if (window["treeUnlocked" + i] === 0) {
				node.style.color = "brown";
				node.textContent = "(Locked)";
			} else {
				node.textContent = window["treeStage" + i] == 0 ? "Waiting for tree to spawn..." :
					(window["treeStage" + i] == 4 ? `Ready To Harvest ${getTreeGrowingName(i)}!` : `${getTreeGrowingName(i)}: ${formatTime(getTreeGrowingTimer(i))}`);
			}
		}
	}
}

function getTreeGrowingTimer(patch) {
	let treeId = window["treeId" + patch];
	return Math.max(window.TREE_GROW_TIME[(treeId-1)] - window["treeGrowTimer" + patch], 0);
}

function getTreeGrowingName(patch) {
	let treeId = window["treeId" + patch];
	return (TREES[treeId] && TREES[treeId].name) || window.getTreeName(treeId);
}

/*
	Returns the first upcoming woodcutting timer
*/
function getSoonestWoodcuttingTimer() {
	let timer = null;
	for (let i = 1; i <= 6; i++) {
		if (window["treeId" + i] != 0 && TREES[window["treeId" + i]] !== undefined) {
			let gt = TREES[window["treeId" + i]].growTime - window["treeGrowTimer" + i];
			timer = timer === null ? gt : gt < timer ? gt : timer;
		}
	}
	return timer;
}
/*
	Returns the first upcoming farming timer
*/
function getSoonestFarmingTimer() {
	let timer = null;
	for (let i = 1; i <= (window.donorFarmingPatch > currentTimeMillis ? 6 : 4); i++) {
		let gt = window["farmingPatchGrowTime" + i] - window["farmingPatchTimer" + i];
		timer = timer === null ? gt : gt < timer ? gt : timer;
	}
	return timer;
}

/*
	Adds an oil timer & net oil consumption element
*/
function addOilTimerElements() {
	let oilFlowNode = document.getElementById("oil-flow-values");
	let netConsumptionNode = document.createElement("SPAN");
	netConsumptionNode.id = "dh2qol-oilNetConsumption";
	netConsumptionNode.style.color = "yellow";
	let oilTimerNode = document.createElement("SPAN");
	oilTimerNode.id = "dh2qol-oilTimer";
	oilTimerNode.style.color = "orange";
	if (oilFlowNode && oilFlowNode.parentNode) {
		let parent = oilFlowNode.parentNode;
		parent.appendChild(netConsumptionNode);
		parent.appendChild(oilTimerNode);
	}
}
/*
	Updates the oil timer & net oil consumption elements
*/
function updateOilTimer() {
 	let netConsumptionNode = document.getElementById("dh2qol-oilNetConsumption");
 	let oilTimerNode = document.getElementById("dh2qol-oilTimer");

	if (netConsumptionNode && oilTimerNode) {
		netConsumptionNode.textContent = ` (${getNetOilConsumption() > 0 ? "+" + getNetOilConsumption() : getNetOilConsumption()})`;
		oilTimerNode.textContent = getOilTimer();
	}
}
/*
	Calculates and formats an oilTimer based on oil, capacity, and consumption
*/
function getOilTimer() {
	let consumption = getNetOilConsumption();
	let capacity = getOilCapacity();
	let oil = getCurrentOil();
	if (consumption == 0) {
		return ``;
	} else if (consumption > 0) {
		// Despite the poorly labeled variable, consumption greater than 0 means we're producing a net positive amount of oil
		return ` (${formatTime((capacity - oil) / consumption)})`;
	} else {
		// Consuming more than producing
		consumption = Math.abs(consumption);
		return ` (${formatTime(oil / consumption)})`;
	}
}
})();
/*
function proxyWebsocketSend() {
	const PROXY = window.webSocket.send;
	window.webSocket.send = function send() {
		console.log(send.caller);
		PROXY.apply(this, arguments);
	};
}
*/
/*
//For the future
function Command() {
	let fncCaller;
	const PROXY = window.webSocket.send;
	window.webSocket.send = function send() {
		// If we attempt to load the market tab, log the function that calls WebSocket#send
		if (arguments[0] === "OPEN_MARKET_TAB") {
			fncCaller = send.caller; // We got it boys!
			return; // Don't actually send the command to the server because 'muh automation'
		}
		PROXY.apply(this, arguments); // Pretend like nothing ever happened
	}
	// Probe the playermarket to trigger a legitimate server call (but we won't actually send it!) and log the calling function
	window.processTab("playermarket");
	// Return WebSocket#send to normal
	window.webSocket.send = PROXY;

	this.send = fncCaller || webSocket.send;
	this.chopTree;

}
*/