// ==UserScript==
// @name DH2QoL
// @namespace https://greasyforks.org/
// @version 0.1.3
// @description Quality of Life tweaks for Diamond Hunt 2
// @author John / WhoIsYou / CodeCretin
// @match http://*.diamondhunt.co/game.php
// @match https://*.diamondhunt.co/game.php
// @run-at document-idle
// @grant none
// ==/UserScript==
'use strict';
const DH2_QOL_CONFIG = {
formatTimers : {
text : "Enable HH:MM:SS timer formatting?",
value : true
},
customTimers : {
text : "Enable custom timers?",
value : true
},
disableLeftClickSellGems : {
text : "Disable selling gems on left click?",
value : true
},
enableRightClickFurnaceRepeat : {
text : "Enable right clicking bound furnace to repeat last action?",
value : true
},
enableRightClickBrewAllPotion : {
text : "Enable right clicking to brew all of a potion type?",
value : true
},
enableRightClickCookAllFood : {
text : "Enable right clicking to cook food?",
value : true
},
enableRightClickEatAllFood : {
text : "Enable right clicking to eat food?",
value : true
}
};
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
}
};
const RAW_FOOD = ["uncookedBread", "uncookedCake", "rawChicken", "rawShrimp", "rawSardine", "rawTuna", "rawSwordfish", "rawShark", "rawWhale", "rawRainbowFish"];
const COOKED_FOOD = ["honey", "bread", "chicken", "shrimp", "sardine", "tuna", "swordfish", "shark", "whale", "rainbowFish"];
const MONITORED_POTIONS = ["stardustPotion", "superStardustPotion"];
(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;
}
console.log("Launching DH2-QoL. Welcome " + window.username);
if (window.hasOwnProperty("webSocket") && window.webSocket.readyState === WebSocket.OPEN)
// WebSocket proxy
proxyWebSocketOnMessage();
else
console.log("WebSocket failed to load. Some functionality is unavailable. Try refreshing.");
if (!window.firstLoadGame) {
processDormantTabsOnLoad();
enableRightClickFurnaceRepeat();
enableRightClickBrewAllPotion();
enableRightClickCookAllFood();
enableRightClickEatAllFood();
disableLeftClickSellGems();
disableUnequipInCombat();
// Additional proxies
proxyAddToChatBox();
proxyConfirmDialogue();
// Interface delight
updateStyleSheets();
addNotificationElements();
} else {
console.log("Script loaded before the game did. Some functionality may be missing. Lag? Try refreshing.");
}
return;
})(100);
/*
Actions performed prior to any game tick
*/
function preGameTick() {
}
/*
Actions performed following any game tick
*/
function postGameTick() {
updateNotificationElements();
updateSmeltingTimer();
updateWoodcuttingTimer();
updateOilTimer();
}
function openSettings() {
}
/*
Loads initial data for unopened tabs
*/
function processDormantTabsOnLoad() {
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.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() !== null) {
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(Math.max(window.smeltingPercD - window.smeltingPercN, 0))} (${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 != 0 && (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).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");
rowBoatElement.style.display = window.boundRowBoat == 1 ? "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");
}
let canoeElement = document.getElementById("dhqol-notif-canoe");
canoeElement.style.display = window.boundCanoe == 1 ? "inline-block" : "none";
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 * 150);
timeout++;
}
});
}
function addCtrlClickRecipeToHide() {
let nodes = document.querySelectorAll("[id^=crafting-]").concat(querySelectorAll("[id^=brewing-]"));
nodes.forEach((node) => {
node.addEventListener("click", function(e) {
if (e.ctrlKey) {
e.stopImmediatePropagation(); // Prevents any other events on this element from firing
this.style.display = "none";
}
}, true); // useCapture = true
});
}
function harvestTrees() {
for (let i = 1; i <= 6; i++) {
if (window["treeStage" + i] == 4) {
setTimeout(() => {
window.sendBytes("CHOP_TREE=" + i);
}, i * 25);
}
}
}
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}`);
return false;
};
}
});
}
function enableRightClickCookAllFood() {
RAW_FOOD.forEach((food) => {
let node = document.getElementById("item-box-" + food);
if (node) {
node.oncontextmenu = function() {
window.cook(food, window[food]);
return false;
};
}
});
}
function enableRightClickEatAllFood() {
COOKED_FOOD.forEach((food) => {
let node = document.getElementById("item-box-" + food);
if (node) {
node.oncontextmenu = function() {
window.sendBytes(`CONSUME=${food}~${window[food]}`);
return false;
};
}
});
}
function disableLeftClickSellGems() {
try {
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;
} catch (e) { console.log(e); }
}
function disableUnequipInCombat() {
let node = document.querySelector("[class=imageHero]");
if (node && node.parentNode) {
node.parentNode.onclick = function() {
if (!window.isInCombat()) {
window.sendBytes("UEQUIP_H");
}
};
}
}
function getBoundFurnace() {
return window.boundStoneFurnace !== 0 ? "boundStoneFurnace" : window.boundBronzeFurnace !== 0 ? "boundBronzeFurnace" :
window.boundIronFurnace !== 0 ? "boundIronFurnace" : window.boundSilverFurnace !== 0 ? "boundSilverFurnace" : window.boundGoldFurnace !== 0 ? "boundGoldFurnace" : null;
}
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);
};
}
/*****
*
* 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
*/
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(window.smeltingPercD - window.smeltingPercN)}|${window.smeltingPerc}`;
}
/*
Adds and updates a woodcutting timer
*/
function updateWoodcuttingTimer() {
// Add and update woodcutting patch timers
let node;
for (let i = 1; i <= 6; i++) {
if (i >= 5 && window.donorWoodcuttingPatch === 0)
break;
node = document.getElementById("wc-div-tree-" + i);
if (node) {
if (!document.getElementById("treeTimer" + i)) // Node doesn't exist so we'll create it
node.innerHTML = "<span id='treeTimer" + i + "' style='color:blue'></span><br>" + node.innerHTML;
if (window["treeId" + i] == 0) // The tree plot is empty
document.getElementById("treeTimer" + i).textContent = "Waiting for tree to spawn...";
else if (TREES[window["treeId" + i]].growTime - window["treeGrowTimer" + i] == 0) // Tree is fully grown
document.getElementById("treeTimer" + i).textContent = `Ready To Harvest ${TREES[window["treeId" + i]].name}!`;
else // A tree is growing
document.getElementById("treeTimer" + i).textContent = TREES[window["treeId" + i]].name + ": " + formatTime(TREES[window["treeId" + i]].growTime - window["treeGrowTimer" + i]);
}
}
}
function getSoonestWoodcuttingTimer() {
let timer = null;
for (let i = 1; i <= 6; i++) {
if (window["treeId" + i] != 0) {
let gt = TREES[window["treeId" + i]].growTime - window["treeGrowTimer" + i];
timer = timer === null ? gt : gt < timer ? gt : timer;
}
}
return timer;
}
function getSoonestFarmingTimer() {
let timer = null;
for (let i = 1; i <= (window.donorFarmingPatch ? 6 : 4); i++) {
let gt = window["farmingPatchGrowTime" + i] - window["farmingPatchTimer" + i];
timer = timer === null ? gt : gt < timer ? gt : timer;
}
return timer;
}
/*
Adds and updates an oil timer & net oil consumption
*/
function updateOilTimer() {
let oilFlowNode = document.getElementById("oil-flow-values");
let netConsumptionNode = document.getElementById("oilNetConsumption");
let oilTimerNode = document.getElementById("oilTimer");
if (oilFlowNode) {
if (!netConsumptionNode)
oilFlowNode.innerHTML += "<span id='oilNetConsumption' style='color:yellow'></span>";
else
netConsumptionNode.textContent = ` (${getNetOilConsumption() > 0 ? "+" + getNetOilConsumption() : getNetOilConsumption()})`;
if (!oilTimerNode)
oilFlowNode.innerHTML += "<span id='oilTimer' style='color: orange'></span>";
else
oilTimerNode.textContent = ` (${(getNetOilConsumption() > 0) ? formatTime((getOilCapacity() - getCurrentOil()) / getNetOilConsumption()) : formatTime(getCurrentOil() / getNetOilConsumption())})`;
}
}