// ==UserScript==
// @name Melvor TimeRemaining
// @namespace http://tampermonkey.net/
// @version 0.5.0
// @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/*
// @match https://www.melvoridle.com/*
// @match https://melvoridle.com/*
// @match https://test.melvoridle.com/*
// @grant none
// ==/UserScript==
/* jshint esversion: 9 */
// Note that this script is made for Melvor Idle version 0.17
// Later versions might break parts of this script
// Big thanks to Xhaf#6478 and Visua#9999 for helping with parts of the code and troubleshooting
(function () {
function injectScript(main) {
var script = document.createElement('script');
script.textContent = `try {(${main})();} catch (e) {console.log(e);}`;
document.body.appendChild(script).parentNode.removeChild(script);
}
function script() {
// Loading script
console.log('Melvor TimeRemaining Loaded');
// 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.timeLeftCurrent === 0) {
notify("Task Done");
console.log('Melvor TimeRemaining: task done');
let ding = new Audio("https://www.myinstants.com/media/sounds/ding-sound-effect.mp3");
ding.volume=0.1;
ding.play();
}
}
// Create timeLeft containers
let TempContainer = ['<div class="font-size-sm font-w600 text-uppercase text-center text-muted"><small id ="','" class="mb-2" style="display:block;clear:both;white-space: pre-line" data-toggle="tooltip" data-placement="top" data-html="true" title="" data-original-title=""></small></div>'];
let TempContainerAlt = ['<div class="font-size-sm text-uppercase text-muted"><small id ="','" class="mt-2" style="display:block;clear:both;white-space: pre-line" data-toggle="tooltip" data-placement="top" data-html="true" title="" data-original-title=""></small></div>'];
$("#smith-item-have").after(TempContainer[0] + "timeLeftSmithing" + TempContainer[1]);
$("#fletch-item-have").after(TempContainer[0] + "timeLeftFletching" + TempContainer[1]);
$("#runecraft-item-have").after(TempContainer[0] + "timeLeftRunecrafting" + TempContainer[1]);
$("#craft-item-have").after(TempContainer[0] + "timeLeftCrafting" + TempContainer[1]);
$("#herblore-item-have").after(TempContainer[0] + "timeLeftHerblore" + TempContainer[1]);
$("#skill-cooking-food-selected-qty").after(TempContainerAlt[0] + "timeLeftCooking" + TempContainerAlt[1]);
$("#skill-fm-logs-selected-qty").after(TempContainerAlt[0] + "timeLeftFiremaking" + TempContainerAlt[1]);
$("#magic-item-have-and-div").after(TempContainer[0] + "timeLeftMagic" + TempContainer[1]);
// Funtion to get unformatted number for Qty
function getQtyUnformat(itemID) {
for (let i = 0; i < bank.length; i++) {
if (bank[i].id === itemID) {
return bank[i].qty;
}
}
return 0;
}
// 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;
}
// Add seconds to date
function AddSecondsToDate(date, seconds) {
return new Date(date.getTime() + seconds*1000);
}
// Format date
function DateFormat(date,time){
let days = Math.floor(time / 86400);
days = days > 0 ? ' + ' + days + ' days': '';
let hours = date.getHours();
hours = hours < 10 ? '0' + hours : hours;
let minutes = date.getMinutes();
minutes = minutes < 10 ? '0' + minutes : minutes;
let strTime = hours + ':' + minutes + days;
return strTime;
}
// Convert level to XP needed to reach that level
function convertLvlToXP(level) {
if(level === Infinity) { return Infinity; }
let xp = 0;
if (level === 1) {return xp;}
xp = exp.level_to_xp(level)+1;
return xp;
}
// Convert XP value to level
function convertXPToLvl(xp, noCap = false) {
let level = exp.xp_to_level(xp) - 1;
if (level < 1) level = 1;
if(!noCap) {
if (level > 99) level = 99;
}
return level;
}
// Main function
function timeRemaining(currentSkill){
// Reset variables
var masteryID = 0;
var skillInterval = 0; // Update interval of skill
var rhaelyxCharge = 0;
var chargeUses = 0;
var itemXP = 0;
var item = 0;
var initialTotalMasteryPoolXP = 0;
var masteryPoolMaxXP = 0;
var initialTotalMasteryLevelForSkill = 0;
var initialTotalMasteryXP = 0; // Current amount of Mastery experience
var masteryLim = []; // Xp needed to reach next level
var skillLim = []; // Xp needed to reach next level
var poolLim = []; // Xp need to reach next pool checkpoint
var skillReq = []; // Needed items for craft and their quantities
var itemCraft = []; // Amount of items craftable for each resource requirement
var recordCraft = Infinity; // Amount of craftable items for limiting resource
// Generate default values for script
var timeLeftID = "timeLeft".concat(currentSkill); // Field for generating timeLeft HTML
var masteryLimLevel = Array.from({ length: 98 }, (_, i) => i + 2); //Breakpoints for mastery bonuses - default all levels starting at 2 to 99, followed by Infinity
masteryLimLevel.push(Infinity);
var skillLimLevel = Array.from({ length: 98 }, (_, i) => i + 2); //Breakpoints for mastery bonuses - default all levels starting at 2 to 99, followed by Infinity
skillLimLevel.push(Infinity);
var poolLimCheckpoints = [10,25,50,95,Infinity]; //Breakpoints for mastery pool bonuses followed by Infinity
var chanceToKeep = Array.from({ length: 99 }, (_, i) => i *0.002); // Chance to keep at breakpoints - default 0.2% per level
chanceToKeep[98] += 0.05; // Level 99 Bonus
var now = new Date(); // Current time and day
var skillID = skillName.indexOf(currentSkill); // ID of skill
var initialSkillXP = skillXP[skillID]; // Current skill XP
// Set current skill and pull matching variables from game with script
if (currentSkill == "Smithing") {
item = smithingItems[selectedSmith].itemID;
itemXP = items[item].smithingXP;
skillInterval = 2000;
if (godUpgrade[3]) skillInterval *= 0.8;
for (let i of items[item].smithReq) {
skillReq.push(i);
}
masteryLimLevel = [20,40,60,80,99,Infinity]; // Smithing Mastery Limits
chanceToKeep = [0,0.05,0.10,0.15,0.20,0.30]; //Smithing Mastery bonus percentages
if(petUnlocked[5]) chanceToKeep = chanceToKeep.map(n => n + PETS[5].chance/100); // Add Pet Bonus
}
if (currentSkill == "Fletching") {
item = fletchingItems[selectedFletch].itemID;
itemXP = items[item].fletchingXP;
skillInterval = 2000;
if (godUpgrade[0]) skillInterval *= 0.8;
if (petUnlocked[8]) skillInterval -= 200;
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") {
item = runecraftingItems[selectedRunecraft].itemID;
itemXP = items[item].runecraftingXP;
skillInterval = 2000;
if (godUpgrade[1]) skillInterval *= 0.8;
for (let i of items[item].runecraftReq) {
skillReq.push(i);
}
masteryLimLevel = [Infinity]; // Runecrafting has no Mastery bonus
chanceToKeep = [0]; //Thus no chance to keep
if (equippedItems.includes(CONSTANTS.item.Runecrafting_Skillcape) || equippedItems.includes(CONSTANTS.item.Max_Skillcape) || equippedItems.includes(CONSTANTS.item.Cape_of_Completion)) chanceToKeep[0] += 0.35;
if (petUnlocked[10]) chanceToKeep[0] += PETS[10].chance/100;
}
if (currentSkill == "Crafting") {
item = craftingItems[selectedCraft].itemID;
itemXP = items[item].craftingXP;
skillInterval = 3000;
if (godUpgrade[0]) skillInterval *= 0.8;
if (equippedItems.includes(CONSTANTS.item.Crafting_Skillcape) || equippedItems.includes(CONSTANTS.item.Max_Skillcape) || equippedItems.includes(CONSTANTS.item.Cape_of_Completion)) skillInterval -= 500;
if (petUnlocked[9]) skillInterval -= 200;
for (let i of items[item].craftReq) {
skillReq.push(i);
}
}
if (currentSkill == "Herblore") {
item = herbloreItemData[selectedHerblore].itemID[getHerbloreTier(selectedHerblore)];
itemXP = herbloreItemData[selectedHerblore].herbloreXP;
skillInterval = 2000;
if (godUpgrade[1]) skillInterval *= 0.8;
for (let i of items[item].herbloreReq) {
skillReq.push(i);
}
}
if (currentSkill == "Cooking") {
item = selectedFood;
itemXP = items[item].cookingXP;
if (currentCookingFire > 0) {
itemXP *= (1 + cookingFireData[currentCookingFire - 1].bonusXP / 100);
}
skillInterval = 3000;
if (godUpgrade[3]) skillInterval *= 0.8;
skillReq = [{id: item, qty: 1}];
masteryLimLevel = [Infinity]; //Cooking has no Mastery bonus
chanceToKeep = [0]; //Thus no chance to keep
}
if (currentSkill == "Firemaking") {
item = selectedLog;
let bonfireBonus = logsData[selectedLog].bonfireBonus;
itemXP = logsData[selectedLog].xp + logsData[selectedLog].xp * (bonfireBonus / 100);
skillInterval = logsData[selectedLog].interval;
if (godUpgrade[3]) skillInterval *= 0.8;
skillReq = [{id: item, qty: 1}];
chanceToKeep.fill(0); // Firemaking Mastery does not provide preservation chance
}
if (currentSkill == "Magic") {
skillInterval = 2000;
//Find need runes for spell
altMagicID = selectedAltMagic;
if (ALTMAGIC[selectedAltMagic].runesRequiredAlt !== undefined && useCombinationRunes) {
for (let i of ALTMAGIC[selectedAltMagic].runesRequiredAlt) {
skillReq.push({...i});
}
}
else {
for (let i of ALTMAGIC[selectedAltMagic].runesRequired) {
skillReq.push({...i});
}
}
// Get Rune discount
for (let i = 0; i < skillReq.length; i++) {
if (items[equippedItems[CONSTANTS.equipmentSlot.Weapon]].providesRune !== undefined) {
if (items[equippedItems[CONSTANTS.equipmentSlot.Weapon]].providesRune.includes(skillReq[i].id)) {
let capeMultiplier = 1;
if (equippedItems.includes(CONSTANTS.item.Magic_Skillcape) || equippedItems.includes(CONSTANTS.item.Max_Skillcape) || equippedItems.includes(CONSTANTS.item.Cape_of_Completion)) capeMultiplier = 2; // Add cape multiplier
skillReq[i].qty -= items[equippedItems[CONSTANTS.equipmentSlot.Weapon]].providesRuneQty * capeMultiplier;
}
}
}
skillReq = skillReq.filter(item => item.qty > 0); // Remove all runes with 0 cost
//Other items
if (ALTMAGIC[selectedAltMagic].selectItem == 1 && selectedMagicItem[1] !== null) { // Spells that just use 1 item
skillReq.push({id: selectedMagicItem[1], qty: 1});
}
if (ALTMAGIC[selectedAltMagic].selectItem == -1) { // Spells that dont require you to select an item
if (ALTMAGIC[selectedAltMagic].needCoal) { // Rags to Riches II
skillReq.push({id: 48, qty: 1});
}
}
if (selectedMagicItem[0] !== null && ALTMAGIC[selectedAltMagic].selectItem == 0) { // SUPERHEAT
for (let i of items[selectedMagicItem[0]].smithReq) {
skillReq.push({...i});
}
if (ALTMAGIC[selectedAltMagic].ignoreCoal) {
skillReq = skillReq.filter(item => item.id !== 48);
}
}
masteryLimLevel = [Infinity]; //AltMagic has no Mastery bonus
chanceToKeep = [0]; //Thus no chance to keep
}
// Configure initial mastery values for all skills with masteries
if (currentSkill != "Magic") {
initialTotalMasteryPoolXP = MASTERY[skillID].pool;
masteryPoolMaxXP = getMasteryPoolTotalXP(skillID);
initialTotalMasteryLevelForSkill = getCurrentTotalMasteryLevelForSkill(skillID);
masteryID = items[item].masteryID[1];
initialTotalMasteryXP = MASTERY[skillID].xp[masteryID];
}
// Apply itemXP Bonuses from gear and pets
itemXP = addXPBonuses(skillID, itemXP, true);
// Populate masteryLim from masteryLimLevel
for (let i = 0; i < masteryLimLevel.length; i++) {
masteryLim[i] = convertLvlToXP(masteryLimLevel[i]);
}
// Populate skillLim from skillLimLevel
for (let i = 0; i < skillLimLevel.length; i++) {
skillLim[i] = convertLvlToXP(skillLimLevel[i]);
}
// Populate poolLim from masteryCheckpoints
for (let i = 0; i < poolLimCheckpoints.length; i++) {
poolLim[i] = masteryPoolMaxXP * poolLimCheckpoints[i] / 100;
}
// Check for Crown of Rhaelyx
var RhaelyxChance = 0.15;
if (equippedItems.includes(CONSTANTS.item.Crown_of_Rhaelyx) && currentSkill != "Magic") {
for (let i = 0; i < masteryLimLevel.length; i++) {
chanceToKeep[i] += 0.10; // Add base 10% chance
}
rhaelyxCharge = getQtyUnformat(CONSTANTS.item.Charge_Stone_of_Rhaelyx);
chargeUses = rhaelyxCharge * 1000; // Estimated uses from Rhaelyx Charge Stones
}
// Get Item Requirements and Current Requirements
for (let i = 0; i < skillReq.length; i++) {
let itemReq = skillReq[i].qty;
//Check how many of required resourse in Bank
let 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, chanceToRefTable){
let chanceTo = chanceToRefTable;
if (masteryEXP >= masteryLim[0]) {
for (let i = 0; i < masteryLim.length; i++) {
if (masteryLim[i] <= masteryEXP && masteryEXP < masteryLim[i+1]) {
return chanceTo[i+1];
}
}
} else {return chanceTo[0];}
}
// Adjust interval based on unlocked bonuses
function intervalAdjustment(currentPoolMasteryXP, currentMasteryXP) {
let adjustedInterval = skillInterval;
if (currentSkill == "Smithing") {
//none
}
if (currentSkill == "Fletching") {
if (currentPoolMasteryXP >= poolLim[3]) adjustedInterval -= 200;
}
if (currentSkill == "Runecrafting") {
//none
}
if (currentSkill == "Crafting") {
//none
}
if (currentSkill == "Herblore") {
//none
}
if (currentSkill == "Cooking") {
//none
}
if (currentSkill == "Firemaking") {
if (currentPoolMasteryXP >= poolLim[1]) adjustedInterval *= 0.9;
let descreasedBurnInterval = 1 - convertXPToLvl(currentMasteryXP) * 0.001;
adjustedInterval *= descreasedBurnInterval;
}
return adjustedInterval;
}
// Adjust preservation chance based on unlocked bonuses
function preservationAdjustment(currentPoolMasteryXP) {
let adjustedPreservation = 0;
if (currentSkill == "Smithing") {
if (currentPoolMasteryXP >= poolLim[1]) adjustedPreservation += 5;
if (currentPoolMasteryXP >= poolLim[2]) adjustedPreservation += 5;
}
if (currentSkill == "Fletching") {
//none
}
if (currentSkill == "Runecrafting") {
if (currentPoolMasteryXP >= poolLim[2]) adjustedPreservation += 10;
}
if (currentSkill == "Crafting") {
//none
}
if (currentSkill == "Herblore") {
if (currentPoolMasteryXP >= poolLim[2]) adjustedPreservation += 5;
}
if (currentSkill == "Cooking") {
if (currentPoolMasteryXP >= poolLim[2]) adjustedPreservation += 10;
}
if (currentSkill == "Firemaking") {
}
return adjustedPreservation / 100;
}
// Adjust skill XP based on unlocked bonuses
function skillXPAdjustment(currentPoolMasteryXP, currentMasteryXP) {
let xpMultiplier = 1;
if (currentSkill == "Smithing") {
//none
}
if (currentSkill == "Fletching") {
//none
}
if (currentSkill == "Runecrafting") {
if (currentPoolMasteryXP >= poolLim[1]) xpMultiplier += 1.5;
}
if (currentSkill == "Crafting") {
//none
}
if (currentSkill == "Herblore") {
//if (currentPoolMasteryXP >= poolLim[1]) xpMultiplier += 0.03;
}
if (currentSkill == "Cooking") {
let burnchance = calcburnChance(currentMasteryXP);
let cookXP = itemXP * (1 - burnchance);
let burnXP = 1 * burnchance;
return cookXP + burnXP;
}
if (currentSkill == "Firemaking") {
// none
}
return itemXP * xpMultiplier;
}
// Calculate total number of unlocked items for skill based on current skill level
function calcTotalUnlockedItems(currentTotalSkillXP) {
let count = 0;
let currentSkillLevel = convertXPToLvl(currentTotalSkillXP);
for (let i = 0; i < MILESTONES[skillName[skillID]].length; i++) {
if (currentSkillLevel >= MILESTONES[skillName[skillID]][i].level) count++;
}
return count;
}
// Calculate mastery xp based on unlocked bonuses
function calcMasteryXpToAdd(timePerAction, currentTotalSkillXP, currentMasteryXP, currentPoolMasteryXP, currentTotalMasteryLevelForSkill) {
let xpModifier = 1;
let xpToAdd = (((calcTotalUnlockedItems(skillID, currentTotalSkillXP) * currentTotalMasteryLevelForSkill) / getTotalMasteryLevelForSkill(skillID) + convertXPToLvl(currentMasteryXP) * (getTotalItemsInSkill(skillID) / 10)) * (timePerAction / 1000)) / 2; // General Mastery XP formula
if (currentPoolMasteryXP >= poolLim[0]) xpModifier += 0.05;
// If current skill is Firemaking, we need to apply mastery progression from actions and use updated currentPoolMasteryXP values
if (currentSkill == "Firemaking") {
if (currentPoolMasteryXP >= poolLim[3]) xpModifier += 0.05;
for (let i = 0; i < MASTERY[CONSTANTS.skill.Firemaking].xp.length; i++) {
// The logs you are not burning
if (masteryID != i) {
if (getMasteryLevel(CONSTANTS.skill.Firemaking, i) >= 99) xpModifier += 0.0025;
}
}
// The log you are burning
if (convertXPToLvl(currentMasteryXP) >= 99) xpModifier += 0.0025;
}
// For all other skills, you use the game function to grab your FM mastery progression
else {
if (getMasteryPoolProgress(CONSTANTS.skill.Firemaking) >= poolLim[3]) xpModifier += 0.05;
for (let i = 0; i < MASTERY[CONSTANTS.skill.Firemaking].xp.length; i++) {
if (getMasteryLevel(CONSTANTS.skill.Firemaking, i) >= 99) xpModifier += 0.0025;
}
}
if (petUnlocked[21]) xpModifier += 0.03;
if (equippedItems.includes(CONSTANTS.item.Ancient_Ring_Of_Mastery)) xpModifier += items[CONSTANTS.item.Ancient_Ring_Of_Mastery].bonusMasteryXP;
xpToAdd *= xpModifier;
if (xpToAdd < 1) xpToAdd = 1;
// Burnchance affects mastery XP
if (currentSkill == "Cooking") {
let burnchance = calcburnChance(currentMasteryXP);
xpToAdd *= (1 - burnchance);
}
return xpToAdd;
}
// Calculate pool XP based on mastery XP
function calcPoolXPToAdd(currentTotalSkillXP, masteryXP) {
if (convertXPToLvl(currentTotalSkillXP) >= 99) {return masteryXP / 2; }
else { return masteryXP / 4; }
}
// Calculate burn chance based on mastery level
function calcburnChance(currentMasteryXP) {
let burnChance = 0;
if (equippedItems.includes(CONSTANTS.item.Cooking_Skillcape) || equippedItems.includes(CONSTANTS.item.Max_Skillcape) || equippedItems.includes(CONSTANTS.item.Cape_of_Completion)) {
return burnChance;
}
if (equippedItems.includes(CONSTANTS.item.Cooking_Gloves)) {
return burnChance;
}
let primaryBurnChance = (30 - convertXPToLvl(currentMasteryXP) * 0.6) / 100;
let secondaryBurnChance = 0.01;
if (primaryBurnChance <= 0) {
return secondaryBurnChance;
}
burnChance = 1 - (1 - primaryBurnChance) * (1 - secondaryBurnChance);
return burnChance;
}
// adjusting totalChanceToUse base on Rhaelyx charges and number of actions
function adjustTotalChanceToUseRhaelyx(totalChanceToUse, expectedActions, resources) {
if (chargeUses >= expectedActions || chargeUses >= resources / (totalChanceToUse - RhaelyxChance)) {
return totalChanceToUse - RhaelyxChance; //if we have excess uses, then we simply use better chance to keep and move on as usual
} else {
return resources / (chargeUses / (totalChanceToUse - RhaelyxChance) + (resources - chargeUses) / totalChanceToUse); //the denominator is the "real" expectedXP with Rhaelyx, so the "real" chanceToKeep is essentially found through resources/(resources/chanceToKeep)
}
}
// Calculates expected time, taking into account Mastery Level advancements during the craft
function calcExpectedTime(resources){
let sumTime = 0;
let currentTotalMasteryXP = initialTotalMasteryXP;
let currentTotalSkillXP = initialSkillXP;
let currentTotalPoolXP = initialTotalMasteryPoolXP;
let currentTotalMasteryLevelForSkill = initialTotalMasteryLevelForSkill;
while (resources > 0) {
// Adjustments
let currentPreservationAdjustment = preservationAdjustment(currentTotalPoolXP);
let totalChanceToUse = 1 - masteryChance(currentTotalMasteryXP,chanceToKeep) - currentPreservationAdjustment;
let currentInterval = intervalAdjustment(currentTotalPoolXP, currentTotalMasteryXP);
// Current Limits
let currentMasteryLim = masteryLim.find(element => element > currentTotalMasteryXP);
let currentSkillLim = skillLim.find(element => element > currentTotalSkillXP);
let currentPoolLim = poolLim.find(element => element > currentTotalPoolXP);
// Current XP
let currentMasteryXP = calcMasteryXpToAdd(currentInterval, currentTotalSkillXP, currentTotalMasteryXP, currentTotalPoolXP, currentTotalMasteryLevelForSkill);
let currentSkillXP = skillXPAdjustment(currentTotalPoolXP, currentMasteryXP);
let currentPoolXP = calcPoolXPToAdd(currentTotalSkillXP, currentMasteryXP);
// Distance to Limits
let masteryXPToLimit = currentMasteryLim - currentTotalMasteryXP;
let skillXPToLimit = currentSkillLim - currentTotalSkillXP;
let poolXPToLimit = currentPoolLim - currentTotalPoolXP;
// Actions to limits
let masteryXPActions = masteryXPToLimit / currentMasteryXP;
let skillXPActions = skillXPToLimit / currentSkillXP;
let poolXPActions = poolXPToLimit / currentPoolXP;
let resourceActions = Math.round(resources / adjustTotalChanceToUseRhaelyx(totalChanceToUse, resources, resources));
// Mininum actions based on limits
let expectedActions = Math.ceil(Math.min(masteryXPActions, skillXPActions, poolXPActions, resourceActions));
// Take away resources based on expectedActions
if (expectedActions == resourceActions) {
resources = 0; // No more limits
} else {
resources -= Math.round(expectedActions*adjustTotalChanceToUseRhaelyx(totalChanceToUse, expectedActions, resources));
}
// Update time and XP
sumTime += expectedActions * currentInterval;
currentTotalMasteryXP += currentMasteryXP*expectedActions;
currentTotalSkillXP += currentSkillXP*expectedActions;
currentTotalPoolXP += currentPoolXP*expectedActions;
// Update remaining Rhaelyx Charge uses
chargeUses -= expectedActions;
if ( chargeUses < 0 ) chargeUses = 0;
// Level up mastery if hitting Mastery limit
if ( masteryXPActions == expectedActions ) currentTotalMasteryLevelForSkill++;
}
return {"timeLeft" : Math.round(sumTime), "finalSkillLevel" : convertXPToLvl(currentTotalSkillXP,true), "finalMasteryLevel" : convertXPToLvl(currentTotalMasteryXP), "finalPoolPercentage" : Math.min((currentTotalPoolXP/masteryPoolMaxXP)*100,100).toFixed(2)};
}
var results = calcExpectedTime(recordCraft);
//Time left
var timeLeft = 0;
if (currentSkill == "Magic") {
timeLeft = Math.round(recordCraft * skillInterval / 1000);
} else {
timeLeft = Math.round(results.timeLeft / 1000);
}
//Global variables to keep track of when a craft is complete
window.timeLeftLast = window.timeLeftCurrent;
window.timeLeftCurrent = timeLeft;
//Inject timeLeft HTML
let timeLeftElement = document.getElementById(timeLeftID);
if(timeLeftElement !== null) {
if (timeLeft !== 0) {
let finishedTime = AddSecondsToDate(now,timeLeft);
timeLeftElement.textContent = "Will take: " + secondsToHms(timeLeft) + "\r\n Expected finished: " + DateFormat(finishedTime,timeLeft);
timeLeftElement.style.display = "block";
} else {
// empty and reset if no time
timeLeftElement.style.display = "none";
}
}
// Generate progression Tooltips for all skills that aren't Magic
if (currentSkill != "Magic") {
if (!timeLeftElement._tippy) {
tippy(timeLeftElement, {
allowHTML: true,
interactive: false,
animation: false,
});
};
let lightMode = "";
if (!darkMode) lightMode = ' style="color:white;"';
let wrapper = ['<div class="row"><div class="col-8" style="white-space: nowrap;"><h3 class="block-title m-1"' + lightMode + ' >','</h3></div><div class="col-4" style="white-space: nowrap; text-align:right;"><h3 class="block-title m-1"' + lightMode + '><span class="p-1 bg-',' rounded" style="text-align:center; display: inline-block;line-height: normal;width: 70px;">','</span></h3></div></div>'];
let finalSkillLevel = wrapper[0] + 'Final Skill Level ' + wrapper[1] + 'success' + wrapper[2] + results.finalSkillLevel + ' / 99' + wrapper[3];
let finalMasteryLevel = wrapper[0] + 'Final Mastery Level ' + wrapper[1] + 'info' + wrapper[2] + results.finalMasteryLevel + ' / 99' + wrapper[3];
let finalPoolPercentage = wrapper[0] + 'Final Mastery Pool ' + wrapper[1] + 'warning' + wrapper[2] + results.finalPoolPercentage + '%' + wrapper[3];
let tooltip = '<div class="col-12 mt-1">' + finalSkillLevel + finalMasteryLevel + finalPoolPercentage + '</div>';
timeLeftElement._tippy.setContent(tooltip);
}
}
// ## SMITHING ##
var selectSmithRef = selectSmith;
window.selectSmith = function(...args) {
selectSmithRef(...args);
try {
timeRemaining("Smithing");
} catch (e) {
console.error(e);
}
};
var startSmithingRef = startSmithing;
window.startSmithing = function(...args) {
startSmithingRef(...args);
try {
timeRemaining("Smithing");
taskComplete();
} catch (e) {
console.error(e);
}
};
// ## FLETCHING ##
var selectFletchRef = selectFletch;
window.selectFletch = function(...args) {
selectFletchRef(...args);
try {
timeRemaining("Fletching");
} catch (e) {
console.error(e);
}
};
var startFletchingRef = startFletching;
window.startFletching = function(...args) {
startFletchingRef(...args);
try {
timeRemaining("Fletching");
taskComplete();
} catch (e) {
console.error(e);
}
};
// ## RUNECRAFTING ##
var selectRunecraftRef = selectRunecraft;
window.selectRunecraft = function(...args) {
selectRunecraftRef(...args);
try {
timeRemaining("Runecrafting");
} catch (e) {
console.error(e);
}
};
var startRunecraftingRef = startRunecrafting;
window.startRunecrafting = function(...args) {
startRunecraftingRef(...args);
try {
timeRemaining("Runecrafting");
taskComplete();
} catch (e) {
console.error(e);
}
};
// ## CRAFTING ##
var selectCraftRef = selectCraft;
window.selectCraft = function(...args) {
selectCraftRef(...args);
try {
timeRemaining("Crafting");
} catch (e) {
console.error(e);
}
};
var startCraftingRef = startCrafting;
window.startCrafting = function(...args) {
startCraftingRef(...args);
try {
timeRemaining("Crafting");
taskComplete();
} catch (e) {
console.error(e);
}
};
// ## HERBLORE ##
var selectHerbloreRef = selectHerblore;
window.selectHerblore = function(...args) {
selectHerbloreRef(...args);
try {
timeRemaining("Herblore");
} catch (e) {
console.error(e);
}
};
var startHerbloreRef = startHerblore;
window.startHerblore = function(...args) {
startHerbloreRef(...args);
try {
timeRemaining("Herblore");
taskComplete();
} catch (e) {
console.error(e);
}
};
// ## COOKING ##
var selectFoodRef = selectFood;
window.selectFood = function(...args) {
selectFoodRef(...args);
try {
timeRemaining("Cooking");
} catch (e) {
console.error(e);
}
};
var startCookingRef = startCooking;
window.startCooking = function(...args) {
startCookingRef(...args);
try {
timeRemaining("Cooking");
taskComplete();
} catch (e) {
console.error(e);
}
};
// ## FIREMAKING ##
var selectLogRef = selectLog;
window.selectLog = function(...args) {
selectLogRef(...args);
try {
timeRemaining("Firemaking");
} catch (e) {
console.error(e);
}
};
var burnLogRef = burnLog;
window.burnLog = function(...args) {
burnLogRef(...args);
try {
timeRemaining("Firemaking");
taskComplete();
} catch (e) {
console.error(e);
}
};
// ## ALT MAGIC ##
var selectMagicRef = selectMagic;
window.selectMagic = function(...args) {
selectMagicRef(...args);
try {
timeRemaining("Magic");
} catch (e) {
console.error(e);
}
};
var selectItemForMagicRef = selectItemForMagic;
window.selectItemForMagic = function(...args) {
selectItemForMagicRef(...args);
try {
timeRemaining("Magic");
} catch (e) {
console.error(e);
}
};
var castMagicRef = castMagic;
window.castMagic = function(...args) {
castMagicRef(...args);
try {
timeRemaining("Magic");
taskComplete();
} catch (e) {
console.error(e);
}
};
}
function loadScript() {
if ((window.isLoaded && !window.currentlyCatchingUp) || (typeof unsafeWindow !== 'undefined' && unsafeWindow.isLoaded && !unsafeWindow.currentlyCatchingUp)) { // Only load script after game has opened
clearInterval(scriptLoader);
injectScript(script);
}
}
const scriptLoader = setInterval(loadScript, 200);
})();