您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows time remaining for completing a task with your current resources. Takes into account Mastery Levels and other bonuses.
// ==UserScript== // @name Melvor TimeRemaining // @namespace http://tampermonkey.net/ // @version 0.6.2.3 // @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, Visua#9999 and TinyCoyote#1769 for helping with parts of the code and troubleshooting // settings can be toggled from the console, or edited here window.timeRemainingSettings = { // true for 12h clock (AM/PM), false for 24h clock IS_12H_CLOCK: false, // true for short clock `xxhxxmxxs`, false for long clock `xx hours, xx minutes and xx seconds` IS_SHORT_CLOCK: false, // true for alternative main display with xp/h, mastery xp/h and action count SHOW_XP_RATE: false, // true to allow final pool percentage > 100% UNCAP_POOL: false, }; (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 check if task is complete function taskComplete(skillID) { if (window.timeLeftLast > 1 && window.timeLeftCurrent === 0) { notifyPlayer(skillID,"Task Done","danger"); 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]); // Mastery Pool progress for(let id in SKILLS) { if(SKILLS[id].hasMastery) { let bar = $(`#mastery-pool-progress-${id}`)[0]; $(bar).after(`<div id="mastery-pool-progress-end-${id}" class="progress-bar bg-warning" role="progressbar" style="width: 0%; background-color: #e5ae679c !important;"></div>`); } } // Mastery Progress bars for(let id in SKILLS) { if(SKILLS[id].hasMastery) { let name = skillName[id].toLowerCase(); let bar = $(`#${name}-mastery-progress`)[0]; $(bar).after(`<div id="${id}-mastery-pool-progress-end" class="progress-bar bg-info" role="progressbar" style="width: 0%; background-color: #5cace59c !important;"></div>`); } } // Mastery Skill progress for(let id in SKILLS) { if(SKILLS[id].hasMastery) { let bar = $(`#skill-progress-bar-${id}`)[0]; $(bar).after(`<div id="skill-progress-bar-end-${id}" class="progress-bar bg-info" role="progressbar" style="width: 0%; background-color: #5cace59c !important;"></div>`); } } // Function to get unformatted number for Qty function getQtyOfItem(itemID) { for (let i = 0; i < bank.length; i++) { if (bank[i].id === itemID) { return bank[i].qty; } } return 0; } let appendName = (t, name, isShortClock) => { if (t === 0) { return ""; } if (isShortClock) { return t + name[0]; } let result = t + " " + name; if (t === 1) { return result; } return result + "s"; }; // Convert seconds to hours/minutes/seconds and format them function secondsToHms(d, isShortClock = timeRemainingSettings.IS_SHORT_CLOCK) { d = Number(d); // split seconds in hours, minutes and seconds let h = Math.floor(d / 3600); let m = Math.floor(d % 3600 / 60); let s = Math.floor(d % 3600 % 60); // no comma in short form // ` and ` if hours and minutes or hours and seconds // `, ` if hours and minutes and seconds let hDisplayComma = " "; if (!isShortClock && h > 0) { if ((m === 0 && s > 0) || (s === 0 && m > 0)) { hDisplayComma = " and "; } else if (s > 0 && m > 0) { hDisplayComma = ", "; } } // no comma in short form // ` and ` if minutes and seconds let mDisplayComma = " "; if (!isShortClock && m > 0 && s > 0) { mDisplayComma = " and "; } // append h/hour/hours etc depending on isShortClock, then concat and return return appendName(h, "hour", isShortClock) + hDisplayComma + appendName(m, "minute", isShortClock) + mDisplayComma + appendName(s, "second", isShortClock); } // Add seconds to date function AddSecondsToDate(date, seconds) { return new Date(date.getTime() + seconds*1000); } function daysBetween(now, then) { const startOfDayNow = new Date(now.getFullYear(), now.getMonth(), now.getDate()); return Math.floor((then - startOfDayNow) / 1000 / 60 / 60 / 24 + (startOfDayNow.getTimezoneOffset() - then.getTimezoneOffset()) / (60 * 24)); } // Format date 24 hour clock function DateFormat(now, then, is12h = timeRemainingSettings.IS_12H_CLOCK, isShortClock = timeRemainingSettings.IS_SHORT_CLOCK){ let days = daysBetween(now, then); days = (days == 0) ? "" : (days == 1) ? " tomorrow" : ` + ${days}` + (isShortClock ? "d" : " days"); let hours = then.getHours(); let minutes = then.getMinutes(); // convert to 12h clock if required let amOrPm = ''; if (is12h) { amOrPm = hours >= 12 ? 'pm' : 'am'; hours = (hours % 12) || 12; } // pad numbers hours = hours < 10 ? '0' + hours : hours; minutes = minutes < 10 ? '0' + minutes : minutes; // concat and return remaining time return hours + ':' + minutes + amOrPm + days; } // Level to XP Array const lvlToXp = Array.from({ length: 200 }, (_, i) => exp.level_to_xp(i)); // 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 = lvlToXp[level] + 1; return xp; } // Convert XP value to level function convertXPToLvl(xp, noCap = false) { let level = 1; while (lvlToXp[level] < xp) { level++; } level--; if (level < 1) { level = 1; } else if (!noCap && level > 99) { level = 99; } return level; } // Get Mastery Level of given Skill and Mastery ID function getMasteryLevel(skill, masteryID) { return convertXPToLvl(MASTERY[skill].xp[masteryID]); } // Progress in current level function getPercentageInLevel(currentXP, finalXP, type, bar = false) { let currentLevel = convertXPToLvl(currentXP, true); if (currentLevel >= 99 && (type == "mastery" || bar == true)) return 0; let currentLevelXP = convertLvlToXP(currentLevel); let nextLevelXP = convertLvlToXP(currentLevel+1); let diffLevelXP = nextLevelXP - currentLevelXP; let currentLevelPercentage = (currentXP - currentLevelXP) / diffLevelXP * 100; if (bar == true) { let finalLevelPercentage = ((finalXP - currentXP) > (nextLevelXP - currentXP)) ? 100 - currentLevelPercentage : ((finalXP - currentXP)/diffLevelXP*100).toFixed(4); return finalLevelPercentage; } else { return currentLevelPercentage; } } // Main function function timeRemaining(skillID) { // Reset variables var masteryID = 0; var skillInterval = 0; 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(skillName[skillID]); // 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,100,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 initialSkillXP = skillXP[skillID]; // Current skill XP // Set current skill and pull matching variables from game with script switch (skillID) { case CONSTANTS.skill.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 break; case CONSTANTS.skill.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 (window.selectedFletchLog === undefined) {window.selectedFletchLog = 0;} skillReq = [skillReq[window.selectedFletchLog]]; } break; case CONSTANTS.skill.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 = [99,Infinity]; // Runecrafting has no Mastery bonus chanceToKeep = [0,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; chanceToKeep[1] = chanceToKeep[0]; break; case CONSTANTS.skill.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); } break; case CONSTANTS.skill.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); } break; case CONSTANTS.skill.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 = [99,Infinity]; //Cooking has no Mastery bonus chanceToKeep = [0,0]; //Thus no chance to keep item = items[item].cookedItemID; break; case CONSTANTS.skill.Firemaking: item = selectedLog; itemXP = logsData[selectedLog].xp * (1 + 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 break; case CONSTANTS.skill.Magic: skillInterval = 2000; //Find need runes for spell 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}); } else if (ALTMAGIC[selectedAltMagic].selectItem == -1) { // Spells that doesn't require you to select an item if (ALTMAGIC[selectedAltMagic].needCoal) { // Rags to Riches II skillReq.push({id: 48, qty: 1}); } } else 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 break; } // Configure initial mastery values for all skills with masteries if (skillID != CONSTANTS.skill.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) && skillID != CONSTANTS.skill.Magic) { for (let i = 0; i < masteryLimLevel.length; i++) { chanceToKeep[i] += 0.10; // Add base 10% chance } rhaelyxCharge = getQtyOfItem(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 resource in Bank let itemQty = getQtyOfItem(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; switch (skillID) { case CONSTANTS.skill.Fletching: if (currentPoolMasteryXP >= poolLim[3]) adjustedInterval -= 200; break; case CONSTANTS.skill.Firemaking: { if (currentPoolMasteryXP >= poolLim[1]) adjustedInterval *= 0.9; let decreasedBurnInterval = 1 - convertXPToLvl(currentMasteryXP) * 0.001; adjustedInterval *= decreasedBurnInterval; break; } } return adjustedInterval; } // Adjust preservation chance based on unlocked bonuses function preservationAdjustment(currentPoolMasteryXP) { let adjustedPreservation = 0; switch (skillID) { case CONSTANTS.skill.Smithing: if (currentPoolMasteryXP >= poolLim[1]) adjustedPreservation += 5; if (currentPoolMasteryXP >= poolLim[2]) adjustedPreservation += 5; break; case CONSTANTS.skill.Runecrafting: if (currentPoolMasteryXP >= poolLim[2]) adjustedPreservation += 10; break; case CONSTANTS.skill.Herblore: if (currentPoolMasteryXP >= poolLim[2]) adjustedPreservation += 5; break; case CONSTANTS.skill.Cooking: if (currentPoolMasteryXP >= poolLim[2]) adjustedPreservation += 10; break; } return adjustedPreservation / 100; } // Adjust skill XP based on unlocked bonuses function skillXPAdjustment(currentPoolMasteryXP, currentMasteryXP) { let xpMultiplier = 1; switch (skillID) { case CONSTANTS.skill.Runecrafting: if (currentPoolMasteryXP >= poolLim[1] && items[item].type === "Rune") xpMultiplier += 1.5; break; case CONSTANTS.skill.Cooking: { let burnChance = calcBurnChance(currentMasteryXP); let cookXP = itemXP * (1 - burnChance); let burnXP = 1 * burnChance; return cookXP + burnXP; } } 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; // General Mastery XP formula let xpToAdd = (((calcTotalUnlockedItems(currentTotalSkillXP) * currentTotalMasteryLevelForSkill) / getTotalMasteryLevelForSkill(skillID) + convertXPToLvl(currentMasteryXP) * (getTotalItemsInSkill(skillID) / 10)) * (timePerAction / 1000)) / 2; // Skill specific mastery pool modifier if (currentPoolMasteryXP >= poolLim[0]) { xpModifier += 0.05; } // Firemaking pool and log modifiers if (skillID === CONSTANTS.skill.Firemaking) { // If current skill is Firemaking, we need to apply mastery progression from actions and use updated currentPoolMasteryXP values 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; } } else { // For all other skills, you use the game function to grab your FM mastery progression if (getMasteryPoolProgress(CONSTANTS.skill.Firemaking) >= masteryCheckpoints[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; } } } // Ty modifier if (petUnlocked[21]) { xpModifier += 0.03; } // AroM modifier if (equippedItems.includes(CONSTANTS.item.Ancient_Ring_Of_Mastery)) { xpModifier += items[CONSTANTS.item.Ancient_Ring_Of_Mastery].bonusMasteryXP; } // Combine base and modifiers xpToAdd *= xpModifier; if (xpToAdd < 1) { xpToAdd = 1; } // BurnChance affects average mastery XP if (skillID === CONSTANTS.skill.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; } // Calculates expected time, taking into account Mastery Level advancements during the craft function calcExpectedTime(resources){ let sumTotalTime = 0; let maxPoolTime = 0; let maxMasteryTime = 0; let maxSkillTime = 0; let maxPoolReached = false; let maxMasteryReached = false; let maxSkillReached = false; let maxXP = convertLvlToXP(99); if (initialTotalMasteryPoolXP >= masteryPoolMaxXP) maxPoolReached = true; if (initialTotalMasteryXP >= maxXP) maxMasteryReached = true; if (initialSkillXP >= maxXP) maxSkillReached = true; let currentTotalMasteryXP = initialTotalMasteryXP; let currentTotalSkillXP = initialSkillXP; let currentTotalPoolXP = initialTotalMasteryPoolXP; let currentTotalMasteryLevelForSkill = initialTotalMasteryLevelForSkill; // compute current xp/h and mxp/h let initialInterval = intervalAdjustment(initialTotalMasteryPoolXP, initialTotalMasteryXP); let xph = skillXPAdjustment(initialTotalMasteryPoolXP, initialTotalMasteryXP) / initialInterval * 1000 * 3600; // compute current mastery xp / h using the getMasteryXpToAdd from the game let masteryXPh = getMasteryXpToAdd(skillID, masteryID, initialInterval) / initialInterval * 1000 * 3600; // alternative: compute through the calcMasteryXpToAdd method from this script, they should be the same ! // let masteryXPh = calcMasteryXpToAdd(initialInterval, currentTotalSkillXP, currentTotalMasteryXP, currentTotalPoolXP, currentTotalMasteryLevelForSkill) / initialInterval * 1000 * 3600; // counter for estimated number of actions let actions = 0; 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, currentTotalMasteryXP); 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; // estimate amount of actions // number of actions with rhaelyx charges let resourceActions = Math.min(chargeUses, resources / (totalChanceToUse - RhaelyxChance)); // remaining resources let resWithoutCharge = Math.max(0, resources - chargeUses); // add number of actions without rhaelyx charges resourceActions += x / totalChanceToUse; // Minimum 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 { let resUsed = 0; if (expectedActions < chargeUses) { // won't run out of charges yet resUsed = expectedActions * (totalChanceToUse - RhaelyxChance); } else { // first use charges resUsed = chargeUses * (totalChanceToUse - RhaelyxChance); // remaining actions are without charges resUsed += (expectedActions - chargeUses) * totalChanceToUse; } resources = Math.round(resources - resUsed); } // time for current loop let timeToAdd = expectedActions * currentInterval; // Update time and XP sumTotalTime += timeToAdd; currentTotalMasteryXP += currentMasteryXP*expectedActions; currentTotalSkillXP += currentSkillXP*expectedActions; currentTotalPoolXP += currentPoolXP*expectedActions; // Time for max pool, 99 Mastery and 99 Skill if (!maxPoolReached && currentTotalPoolXP >= masteryPoolMaxXP) { maxPoolTime = sumTotalTime; maxPoolReached = true; } if (!maxMasteryReached && maxXP <= currentTotalMasteryXP) { maxMasteryTime = sumTotalTime; maxMasteryReached = true; } if (!maxSkillReached && maxXP <= currentTotalSkillXP) { maxSkillTime = sumTotalTime; maxSkillReached = true; } // Update remaining Rhaelyx Charge uses chargeUses -= expectedActions; if ( chargeUses < 0 ) chargeUses = 0; // Level up mastery if hitting Mastery limit if ( masteryXPActions == expectedActions ) currentTotalMasteryLevelForSkill++; // estimate total remaining actions actions += expectedActions; } return { "timeLeft" : Math.round(sumTotalTime), "actions": actions, "finalSkillXP" : currentTotalSkillXP, "finalMasteryXP" : currentTotalMasteryXP, "finalPoolPercentage" : Math.min((currentTotalPoolXP/masteryPoolMaxXP) * 100, timeRemainingSettings.UNCAP_POOL ? Infinity : 100).toFixed(2), "maxPoolTime" : maxPoolTime, "maxMasteryTime" : maxMasteryTime, "maxSkillTime" : maxSkillTime, "masteryXPh": masteryXPh, "xph" : xph, }; } //Time left var results = 0; var timeLeft = 0; var timeLeftPool = 0; var timeLeftMastery = 0; var timeLeftSkill = 0; if (skillID == CONSTANTS.skill.Magic) { timeLeft = Math.round(recordCraft * skillInterval / 1000); } else { results = calcExpectedTime(recordCraft); timeLeft = Math.round(results.timeLeft / 1000); timeLeftPool = Math.round(results.maxPoolTime / 1000); timeLeftMastery = Math.round(results.maxMasteryTime / 1000); timeLeftSkill = Math.round(results.maxSkillTime / 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); if (timeRemainingSettings.SHOW_XP_RATE) { timeLeftElement.textContent = "XP/h: " + formatNumber(Math.floor(results.xph)) + "\r\nMXP/h: " + formatNumber(Math.floor(results.masteryXPh)) + "\r\nActions: " + formatNumber(results.actions) + "\r\nTime: " + secondsToHms(timeLeft) + "\r\nFinish: " + DateFormat(now, finishedTime); } else { timeLeftElement.textContent = "Will take: " + secondsToHms(timeLeft) + "\r\n Expected finished: " + DateFormat(now, finishedTime); } timeLeftElement.style.display = "block"; } else { // empty and reset if no time timeLeftElement.style.display = "none"; } } if (skillID != CONSTANTS.skill.Magic) { // Generate progression Tooltips if (!timeLeftElement._tippy) { tippy(timeLeftElement, { allowHTML: true, interactive: false, animation: false, }); } let wrapper = ['<div class="row"><div class="col-6" style="white-space: nowrap;"><h3 class="block-title m-1" style="color:white;" >','</h3></div><div class="col-6" style="white-space: nowrap;"><h3 class="block-title m-1 pl-1"><span class="p-1 bg-',' rounded" style="text-align:center; display: inline-block;line-height: normal;width: 70px;color:white;">','</span>','</h3></div></div>']; let percentageSkill = (getPercentageInLevel(results.finalSkillXP,results.finalSkillXP,"skill")).toFixed(1); let percentageSkillElement = (percentageSkill == 0) ? '' : ` +${percentageSkill}%`; let finalSkillLevelElement = wrapper[0] + 'Final Skill Level ' + wrapper[1] + 'success' + wrapper[2] + convertXPToLvl(results.finalSkillXP,true) + ' / 99' + wrapper[3] + percentageSkillElement + wrapper[4]; let timeLeftSkillElement = ''; if (timeLeftSkill > 0){ let finishedTimeSkill = AddSecondsToDate(now,timeLeftSkill); timeLeftSkillElement = '<div class="row"><div class="col-12 font-size-sm text-uppercase text-muted mb-1" style="text-align:center"><small style="display:inline-block;clear:both;white-space:pre-line;color:white;">Time to 99: ' + secondsToHms(timeLeftSkill) + '<br> Expected finished: ' + DateFormat(now,finishedTimeSkill) + '</small></div></div>'; } let percentageMastery = (getPercentageInLevel(results.finalMasteryXP,results.finalMasteryXP,"mastery")).toFixed(1); let percentageMasteryElement = (percentageMastery == 0) ? '' : ` +${percentageMastery}%`; let finalMasteryLevelElement = wrapper[0] + 'Final Mastery Level ' + wrapper[1] + 'info' + wrapper[2] + convertXPToLvl(results.finalMasteryXP) + ' / 99' + wrapper[3] + percentageMasteryElement + wrapper[4]; let timeLeftMasteryElement = ''; if (timeLeftMastery > 0){ let finishedTimeMastery = AddSecondsToDate(now,timeLeftMastery); timeLeftMasteryElement = '<div class="row"><div class="col-12 font-size-sm text-uppercase text-muted mb-1" style="text-align:center"><small style="display:inline-block;clear:both;white-space:pre-line;color:white;">Time to 99: ' + secondsToHms(timeLeftMastery) + '<br> Expected finished: ' + DateFormat(now,finishedTimeMastery) + '</small></div></div>'; } let finalPoolPercentageElement = wrapper[0] + 'Final Mastery Pool ' + wrapper[1] + 'warning' + wrapper[2] + results.finalPoolPercentage + '%' + wrapper[3] + wrapper[4]; let timeLeftPoolElement = ''; if (timeLeftPool > 0){ let finishedTimePool = AddSecondsToDate(now,timeLeftPool); timeLeftPoolElement = '<div class="row"><div class="col-12 font-size-sm text-uppercase text-muted mb-1" style="text-align:center"><small class="" style="display:inline-block;clear:both;white-space:pre-line;color:white;">Time to 100%: ' + secondsToHms(timeLeftPool) + '<br> Expected finished: ' + DateFormat(now,finishedTimePool) + '</small></div></div>'; } let tooltip = '<div class="col-12 mt-1">' + finalSkillLevelElement + timeLeftSkillElement + finalMasteryLevelElement + timeLeftMasteryElement + finalPoolPercentageElement + timeLeftPoolElement +'</div>'; timeLeftElement._tippy.setContent(tooltip); let poolProgress = (results.finalPoolPercentage > 100) ? 100 - ((initialTotalMasteryPoolXP / masteryPoolMaxXP)*100) : (results.finalPoolPercentage - ((initialTotalMasteryPoolXP / masteryPoolMaxXP)*100)).toFixed(4); $(`#mastery-pool-progress-end-${skillID}`).css("width", poolProgress + "%"); let masteryProgress = getPercentageInLevel(initialTotalMasteryXP,results.finalMasteryXP,"mastery",true); $(`#${skillID}-mastery-pool-progress-end`).css("width", masteryProgress + "%"); let skillProgress = getPercentageInLevel(initialSkillXP,results.finalSkillXP,"skill",true); $(`#skill-progress-bar-end-${skillID}`).css("width", skillProgress + "%"); } } // select and start craft overrides var selectRef = {}; var startRef = {}; [ // skill name, select names, < start name > ["Smithing", ["Smith"]], ["Fletching", ["Fletch"]], ["Runecrafting", ["Runecraft"]], ["Crafting", ["Craft"]], ["Herblore", ["Herblore"]], ["Cooking", ["Food"]], ["Firemaking", ["Log"], "burnLog"], ["Magic", ["Magic", "ItemForMagic"], "castMagic"], ].forEach(skill => { let long = skill[0]; let shorts = skill[1]; let start = "start" + long; if (skill.length > 2) { start = skill[2]; } // selects shorts.forEach(short => { selectRef[short] = window["select" + short]; window["select" + short] = function(...args) { selectRef[short](...args); try { timeRemaining(CONSTANTS.skill[long]); } catch (e) { console.error(e); } }; }); // start startRef[long] = window[start]; window[start] = function(...args) { startRef[long](...args); try { timeRemaining(CONSTANTS.skill[long]); taskComplete(CONSTANTS.skill[long]); } 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或关注我们的公众号极客氢云获取最新地址