// ==UserScript==
// @name Melvor TimeRemaining
// @namespace http://tampermonkey.net/
// @version 0.2.6
// @description Shows time remaining for completing a task with your current resources. Takes into account Mastery Levels and other bonuses.
// @author Breindahl#2660
// @match https://*.melvoridle.com/*
// @grant none
// ==/UserScript==
// Note that this script is made for MelvorIdle version 0.14.1
// Later versions might break parts of this script
// Big thanks to Xhaf#6478 for helping with parts of the code and troubleshooting
// Loading script
console.log('Loading Melvor TimeRemaining');
// Function to send notifications
function notify(msg) {
One.helpers('notify', {
type: 'dark',
from: 'bottom',
align: 'center',
message: msg
});
}
// Funtion to check if task is complete
function taskComplete() {
if (window.timeLeftLast > 1 && window.timeLeftGlobal === 0) {
notify("Task Done");
console.log('task done');
new Audio("https://www.myinstants.com/media/sounds/ding-sound-effect.mp3").play();
}
}
// Create containers
document.getElementById("smith-item-have").outerHTML += '<br><small id ="timeLeftSmithing" class="js-tooltip-enabled timeLeftSmithingOffline" data-toggle="tooltip" data-placement="top" data-html="true" title="" data-original-title="">'+''+'<small>';
document.getElementById("fletch-item-have").outerHTML += '<br><small id ="timeLeftFletching" class="js-tooltip-enabled timeLeftFletchingOffline" data-toggle="tooltip" data-placement="top" data-html="true" title="" data-original-title="">'+''+'<small>';
document.getElementById("runecraft-item-have").outerHTML += '<br><small id ="timeLeftRunecrafting" class="js-tooltip-enabled timeLeftRunecraftingOffline" data-toggle="tooltip" data-placement="top" data-html="true" title="" data-original-title="">'+''+'<small>';
document.getElementById("craft-item-have").outerHTML += '<br><small id ="timeLeftCrafting" class="js-tooltip-enabled timeLeftCraftingOffline" data-toggle="tooltip" data-placement="top" data-html="true" title="" data-original-title="">'+''+'<small>';
document.getElementById("herblore-item-have").outerHTML += '<br><small id ="timeLeftHerblore" class="js-tooltip-enabled timeLeftHerbloreOffline" data-toggle="tooltip" data-placement="top" data-html="true" title="" data-original-title="">'+''+'<small>';
document.getElementById("skill-cooking-food-selected-qty").outerHTML += '<small id ="timeLeftCooking" class="js-tooltip-enabled timeLeftCookingOffline" style="margin-left:5px" data-toggle="tooltip" data-placement="top" data-html="true" title="" data-original-title="" style>'+''+'<small>';
document.getElementById("skill-fm-logs-selected-qty").outerHTML += '<small id ="timeLeftFiremaking" class="js-tooltip-enabled timeLeftFiremakingOffline" style="margin-left:5px" data-toggle="tooltip" data-placement="top" data-html="true" title="" data-original-title="" style>'+''+'<small>';
// Funtion to get unformatted number for Qty
function getQtyUnformat(itemID) {
let qty = 0;
for (let i = 0; i < bank.length; i++) {
if (bank[i].id === itemID) {
qty += bank[i].qty;
}
}
return qty;
}
// Convert seconds to hours/minutes/seconds and format them
function secondsToHms(d) {
d = Number(d);
let h = Math.floor(d / 3600);
let m = Math.floor(d % 3600 / 60);
let s = Math.floor(d % 3600 % 60);
let sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : "";
let mDisplay = m > 0 ? m + (m == 1 ? " minute" : " minutes") : "";
let mDisplayComma = m > 0 && s > 0 ? " and " : "";
let hDisplay = h > 0 ? h + (h == 1 ? " hour" : " hours") : "";
let hDisplayComma = h > 0 ? ((m == 0 && s > 0)||(s == 0 && m > 0) ? " and " : ((s > 0 || m > 0) ? ", " : "")) : "";
return hDisplay + hDisplayComma + mDisplay + mDisplayComma + sDisplay;
}
// Main function
function timeRemaining(item,currentSkill){
//console.log("Current Skill is "+currentSkill);
//console.log("ItemID"+item);
// Reset variables
var skillInterval = null; // Update interval of skill
var skillID = null; // skillID of item (this is different than itemID)
var skillMastery = null; // Current amount of Mastery experience
var containerID = ""; // Field for generating HTML
var skillReq = []; // Needed items for craft and their quantities
var masteryLim = [6,14,23,32,42,54,66,80,96,113,132,152,175,200,228,259,293,331,372,418,468,524,585,653,728,810,902,1002,1113,1236,1371,1520,1685,1867,2067,2289,2534,2804,3102,3430,3794,4194,4637,5126,5665,6260,6917,7643,8444,9328,10305,11382,12572,13886,15336,16937,18705,20657,22811,25190,27817,30716,33917,37452,41354,45662,50419,55670,61468,67870,74938,82741,91356,100868,111370,122965,135766,149900,165505,182734,201757,222759,245947,271549,299816,331024,365481,403524,445527,491902,543104,599635,662051,730963,807048,891052,983800,1086202, Infinity]; // This array contain the thresholds at which a new chanceToKeep comes into effect
var chanceToKeep = [0.0000,0.0025,0.0050,0.0075,0.0100,0.0125,0.0150,0.0175,0.0200,0.0225,0.0250,0.0275,0.0300,0.0325,0.0350,0.0375,0.0400,0.0425,0.0450,0.0475,0.0500,0.0525,0.0550,0.0575,0.0600,0.0625,0.0650,0.0675,0.0700,0.0725,0.0750,0.0775,0.0800,0.0825,0.0850,0.0875,0.0900,0.0925,0.0950,0.0975,0.1000,0.1025,0.1050,0.1075,0.1100,0.1125,0.1150,0.1175,0.1200,0.1225,0.1250,0.1275,0.1300,0.1325,0.1350,0.1375,0.1400,0.1425,0.1450,0.1475,0.1500,0.1525,0.1550,0.1575,0.1600,0.1625,0.1650,0.1675,0.1700,0.1725,0.1750,0.1775,0.1800,0.1825,0.1850,0.1875,0.1900,0.1925,0.1950,0.1975,0.2000,0.2025,0.2050,0.2075,0.2100,0.2125,0.2150,0.2175,0.2200,0.2225,0.2250,0.2275,0.2300,0.2325,0.2350,0.2375,0.2400,0.2425,0.2450]; //Percentage chance of keeping item
var itemCraft = []; // Amount of items craftable for each resource requirement
var recordCraft = Infinity; // Amount of craftable items for limiting resource
// Set current skill and pull match variables from game with script
if (currentSkill == "Smithing") {
skillInterval = smithInterval;
skillID = smithingItems[selectedSmith].smithingID;
skillMastery = smithingMastery[skillID].masteryXP;
containerID = "timeLeftSmithing";
for (let i of items[item].smithReq) {
skillReq.push(i);
}
masteryLim = [372,3102,22811,165505,Infinity]; //Smithing Mastery limiters
chanceToKeep = [0,0.1,0.2,0.3,0.4]; //Smithing Mastery bonus percentages
}
if (currentSkill == "Fletching") {
skillInterval = fletchInterval;
skillID = fletchingItems[selectedFletch].fletchingID;
skillMastery = fletchingMastery[skillID].masteryXP;
containerID = "timeLeftFletching";
for (let i of items[item].fletchReq) {
skillReq.push(i);
}
//Special Case for Arrow Shafts
if (item == 276) {
if (selectedFletchLog === undefined) {selectedFletchLog = 0;}
skillReq = [skillReq[selectedFletchLog]];
}
}
if (currentSkill == "Runecrafting") {
skillInterval = runecraftInterval;
skillID = runecraftingItems[selectedRunecraft].runecraftingID;
skillMastery = runecraftingMastery[skillID].masteryXP;
containerID = "timeLeftRunecrafting";
for (let i of items[item].runecraftReq) {
skillReq.push(i);
}
masteryLim = [Infinity]; //Runecrafting has no Mastery bonus for time
chanceToKeep = [0]; //Thus gives no extra items
}
if (currentSkill == "Crafting") {
skillInterval = craftInterval;
skillID = craftingItems[selectedCraft].craftingID;
skillMastery = craftingMastery[skillID].masteryXP;
containerID = "timeLeftCrafting";
for (let i of items[item].craftReq) {
skillReq.push(i);
}
}
if (currentSkill == "Herblore") {
skillInterval = herbloreInterval;
skillID = herbloreItems[selectedHerblore].id;
skillMastery = herbloreMastery[skillID].masteryXP;
containerID = "timeLeftHerblore";
for (let i of items[item].herbloreReq) {
skillReq.push(i);
}
}
if (currentSkill == "Cooking") {
skillInterval = 3000;
skillID = items[selectedFood].cookingID;
skillMastery = cookingMastery[skillID].masteryXP;
containerID = "timeLeftCooking";
skillReq = [{id: item, qty: 1}];
masteryLim = [Infinity]; //Cooking has no Mastery bonus for time
chanceToKeep = [0]; //Thus gives no extra items
}
if (currentSkill == "Firemaking") {
skillInterval = logsData[item].interval;
skillID = items[selectedLog].firemakingID;
skillMastery = logsMastery[skillID].masteryXP;
containerID = "timeLeftFiremaking";
skillReq = [{id: item, qty: 1}];
masteryLim = [Infinity]; //Cooking has no Mastery bonus for time
chanceToKeep = [0]; //Thus gives no extra items
}
// Get Item Requirements and Current Requirements
for (let i = 0; i < skillReq.length; i++) {
var itemReq;
//Special Case: Check for Smithing Cape
if (equippedItems[CONSTANTS.equipmentSlot.Cape] === CONSTANTS.item.Smithing_Skillcape && skillReq[i].id == 48) {
itemReq = Math.floor(skillReq[i].qty / 2);
} else {
itemReq = skillReq[i].qty;
}
//Check how many of required resourse in Bank
var itemQty = getQtyUnformat(skillReq[i].id);
// Calculate max items you can craft for each itemReq
itemCraft[i] = Math.floor(itemQty/itemReq);
// Calculate limiting factor and set new record
if(itemCraft[i] < recordCraft) {
recordCraft = itemCraft[i];
}
}
//Return the chanceToKeep for any mastery EXP
function masteryChance(masteryEXP){
if (masteryEXP >= masteryLim[0]) {
for (let i = 0; i < masteryLim.length; i++) {
if (masteryLim[i] <= masteryEXP && masteryEXP < masteryLim[i+1]) {return chanceToKeep[i+1];}
}
} else {return chanceToKeep[0];}
}
// Calculates expected time, taking into account Mastery Level advancements during the craft
function expectedActions(resources){
let finalResult = 0;
let currentMastery = skillMastery;
let currentMasteryLim = currentMastery;
while (resources > 0) {
currentMastery = currentMasteryLim;
currentMasteryLim = masteryLim.find(element => element > currentMastery);
let masteryBonus = 1-masteryChance(currentMastery);
let expectedXP = Math.floor(resources/masteryBonus);
let xpToLimit = currentMasteryLim - currentMastery
if (xpToLimit > expectedXP) {
finalResult += expectedXP;
resources -= resources;
} else {
finalResult += xpToLimit;
resources -= Math.ceil(xpToLimit*masteryBonus);
}
}
return finalResult;
}
//Time left if online (with Mastery progression)
var timeLeft = Math.floor(expectedActions(recordCraft)*skillInterval/1000);
console.log("timeLeft: "+timeLeft);
//Time left if offline (no Mastery progression)
var timeLeftOffline = Math.floor(recordCraft * smithInterval / 1000 /(1-masteryChance(skillMastery)));
//Global variables to keep track of when a craft is complete
window.timeLeftLast = window.timeLeftGlobal;
window.timeLeftGlobal = timeLeft;
//Inject HTML
var elementToChange = document.getElementById(containerID);
if (timeLeft !== 0) {
if(elementToChange !== null) {
elementToChange.innerHTML = secondsToHms(timeLeft) + " remaining";
// Tooltip with time left if offline
$('.'+containerID+'Offline').attr('data-original-title', 'Offline: '+secondsToHms(timeLeftOffline));
// Refreshes tooltip if hovering
if ($('.'+containerID+'Offline').attr('aria-describedby') !== undefined) {
$('.'+containerID+'Offline').tooltip('show');
}
}
} else {
if(elementToChange !== null) {
elementToChange.innerHTML = "";
$('.'+containerID+'Offline').attr('data-original-title', '');
}
}
}
// ## SMITHING ##
var selectSmithRef = selectSmith;
window.selectSmith = function(smithingID) {
selectSmithRef(smithingID);
timeRemaining(smithingItems[selectedSmith].itemID,"Smithing");
};
var startSmithingRef = startSmithing;
window.startSmithing = function() {
startSmithingRef(true);
timeRemaining(smithingItems[selectedSmith].itemID,"Smithing");
taskComplete();
};
// ## FLETCHING ##
var selectFletchRef = selectFletch;
window.selectFletch = function(fletchingID, log, update = false) {
selectFletchRef(fletchingID, log, update = false);
timeRemaining(fletchingItems[selectedFletch].itemID,"Fletching");
};
var startFletchingRef = startFletching;
window.startFletching = function() {
startFletchingRef(true);
timeRemaining(fletchingItems[selectedFletch].itemID,"Fletching");
taskComplete();
};
// ## RUNECRAFTING ##
var selectRunecraftRef = selectRunecraft;
window.selectRunecraft = function(runecraftingID, update = false) {
selectRunecraftRef(runecraftingID, update = false);
timeRemaining(runecraftingItems[selectedRunecraft].itemID,"Runecrafting");
};
var startRunecraftingRef = startRunecrafting;
window.startRunecrafting = function() {
startRunecraftingRef(true);
timeRemaining(runecraftingItems[selectedRunecraft].itemID,"Runecrafting");
taskComplete();
};
// ## CRAFTING ##
var selectCraftRef = selectCraft;
window.selectCraft = function(craftingID, update = false) {
selectCraftRef(craftingID, update = false);
timeRemaining(craftingItems[selectedCraft].itemID,"Crafting");
};
var startCraftingRef = startCrafting;
window.startCrafting = function() {
startCraftingRef(true);
timeRemaining(craftingItems[selectedCraft].itemID,"Crafting");
taskComplete();
};
// ## HERBLORE ##
var selectHerbloreRef = selectHerblore;
window.selectHerblore = function(herbloreID, update = false) {
selectHerbloreRef(herbloreID, update = false);
timeRemaining(herbloreItemData[selectedHerblore].itemID[getHerbloreTier(selectedHerblore)],"Herblore");
};
var startHerbloreRef = startHerblore;
window.startHerblore = function() {
startHerbloreRef(true);
timeRemaining(herbloreItemData[selectedHerblore].itemID[getHerbloreTier(selectedHerblore)],"Herblore");
taskComplete();
};
// ## COOKING ##
var selectFoodRef = selectFood;
window.selectFood = function(FoodID) {
selectFoodRef(FoodID);
timeRemaining(selectedFood,"Cooking");
};
var startCookingRef = startCooking;
window.startCooking = function(qty, ignore = true) {
startCookingRef(0, ignore);
timeRemaining(selectedFood,"Cooking");
taskComplete();
};
// ## FIREMAKING ##
var selectLogRef = selectLog;
window.selectLog = function(id) {
selectLogRef(id);
timeRemaining(selectedLog,"Firemaking");
};
var burnLogRef = burnLog;
window.burnLog = function(ignore = true) {
burnLogRef(ignore);
timeRemaining(selectedLog,"Firemaking");
taskComplete();
};