Melvor TimeRemaining

Shows time remaining for completing a task with your current resources. Takes into account Mastery Levels and other bonuses.

目前为 2020-10-21 提交的版本。查看 最新版本

// ==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);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址