// ==UserScript==
// @name Melvor Action Queue
// @version 0.2.3
// @description Adds an interface to queue up actions based on triggers you set
// @author 8992
// @match https://*.melvoridle.com/*
// @grant none
// @namespace http://tampermonkey.net/
// @noframes
// ==/UserScript==
let isVisible = false;
let currentActionIndex = 0;
let triggerCheckInterval = null;
let nameIncrement = 0;
let queueLoop = false;
let queuePause = false;
let manageMasteryInterval = null;
const actionQueueArray = [];
const shop = {};
function getBankQty(id) {
const item = bank.find((a) => a.id == id);
return item ? item.qty : 0;
}
function checkAmmoQty(id) {
const set = equipmentSets.find((a) => a.equipment[CONSTANTS.equipmentSlot.Quiver] == id);
return set ? set.ammo : 0;
}
function checkFoodQty(id) {
const food = equippedFood.find((a) => a.itemID == id);
return food ? food.qty : 0;
}
window.actionTab = function () {
if (!isVisible) {
changePage(3);
$("#settings-container").attr("class", "content d-none");
$("#header-title").text("Action Queue");
$("#header-icon").attr("src", "assets/media/skills/prayer/mystic_lore.svg");
$("#header-theme").attr("class", "content-header bg-combat");
$("#page-header").attr("class", "bg-combat");
document.getElementById("action-queue-container").style.display = "";
isVisible = true;
}
};
window.hideActionTab = function () {
if (isVisible) {
document.getElementById("action-queue-container").style.display = "none";
isVisible = false;
}
};
const triggers = {
"Item Quantity": {},
"Skill Level": {},
"Skill XP": {},
"Equipped Item Quantity": {},
"Mastery Level": {
Cooking: {},
Crafting: {},
Farming: {},
Firemaking: {},
Fishing: {},
Fletching: {},
Herblore: {},
Mining: {},
Runecrafting: {},
Smithing: {},
Thieving: {},
Woodcutting: {},
},
};
const actions = {
"Start Skill": {
Cooking: {},
Crafting: {},
Firemaking: {},
Fishing: {},
Fletching: {},
Herblore: {},
Magic: {},
Mining: {},
Runecrafting: {},
Smithing: {},
Thieving: {},
Woodcutting: {},
},
"Start Combat": {},
"Switch Equipment Set": { 1: null, 2: null, 3: null },
"Equip Item": {},
"Unequip Item": {},
"Buy Item": {},
"Sell Item": {},
};
class ShopItem {
constructor(gp, slayerCoins, req, buy) {
this.gp = gp;
this.slayerCoins = slayerCoins;
this.req = req;
this.buy = buy;
}
}
ShopItem.prototype.cost = function () {
return gp >= this.gp && slayerCoins >= this.slayerCoins && baseBankMax + bankMax > bank.length;
};
function setTrigger(category, name, greaterThan, masteryItem, number) {
const itemID = items.findIndex((a) => a.name == name);
number = parseInt(number, 10);
switch (category) {
case "Item Quantity":
if (greaterThan == "≥") {
return () => {
return getBankQty(itemID) >= number;
};
}
return () => {
return getBankQty(itemID) <= number;
};
case "Skill Level": {
const xp = exp.level_to_xp(number);
return () => {
return skillXP[CONSTANTS.skill[name]] >= xp;
};
}
case "Skill XP":
return () => {
return skillXP[CONSTANTS.skill[name]] >= number;
};
case "Equipped Item Quantity":
if (items.filter((a) => a.canEat).find((a) => a.name == name)) {
return () => {
return checkFoodQty(itemID) <= number;
};
}
return () => {
return checkAmmoQty(itemID) <= number;
};
case "Mastery Level":
return setMasteryTrigger(name, number, masteryItem);
}
}
function setMasteryTrigger(name, number, masteryItem) {
const itemID = items.findIndex((a) => a.name == masteryItem);
let masteryID = 0;
switch (name) {
case "Cooking":
masteryID = cookingItems.find((a) => a.itemID == itemID).cookingID;
break;
case "Crafting":
masteryID = items[itemID].masteryID[1];
break;
case "Farming":
masteryID = items[itemID].masteryID[1];
break;
case "Firemaking":
masteryID = itemID;
break;
case "Fishing":
masteryID = fishingItems.find((a) => a.itemID == itemID).fishingID;
break;
case "Fletching":
masteryID = fletchingItems.find((a) => a.itemID == itemID).fletchingID;
break;
case "Herblore":
masteryID = herbloreItemData.findIndex((a) => a.name == masteryItem);
break;
case "Mining":
masteryID = miningData.find((a) => a.ore == itemID).masteryID;
break;
case "Runecrafting":
masteryID = runecraftingItems.find((a) => a.itemID == itemID).runecraftingID;
break;
case "Smithing":
masteryID = smithingItems.find((a) => a.itemID == itemID).smithingID;
break;
case "Thieving":
masteryID = thievingNPC.findIndex((a) => a.name == masteryItem);
break;
case "Woodcutting":
masteryID = itemID;
}
return () => {
return getMasteryLevel(CONSTANTS.skill[name], masteryID) >= number;
};
}
function setAction(actionCategory, actionName, skillItem, skillItem2, qty) {
qty = parseInt(qty, 10);
const itemID = items.findIndex((a) => a.name == actionName);
switch (actionCategory) {
case "Start Skill":
return setSkillAction(actionName, skillItem, skillItem2);
case "Start Combat": {
const dungeonIndex = DUNGEONS.findIndex((a) => a.name == actionName);
const monsterIndex = MONSTERS.findIndex((a) => a.name == actionName);
return () => {
if (dungeonIndex >= 0) {
if (
DUNGEONS[dungeonIndex].requiresCompletion === undefined ||
dungeonCompleteCount[DUNGEONS[dungeonIndex].requiresCompletion] >= 1
) {
selectDungeon(dungeonIndex, true);
return true;
}
}
for (const area of combatAreas) {
if (area.monsters.includes(monsterIndex)) {
selectMonster(monsterIndex);
return true;
}
}
for (const area of slayerAreas) {
if (skillLevel[CONSTANTS.skill.Slayer] >= area.slayerLevel && area.monsters.includes(monsterIndex)) {
selectMonster(monsterIndex);
return true;
}
}
return false;
};
}
case "Switch Equipment Set": {
const equipSet = parseInt(actionName) - 1;
return () => {
if (equipmentSetCount >= equipSet) {
setEquipmentSet(equipSet);
return true;
}
return false;
};
}
case "Equip Item":
return () => {
const bankID = getBankId(itemID);
if (bankID === false) return false;
if (sellItemMode) toggleSellItemMode();
if (items[itemID].canEat) {
selectBankItem(itemID);
equipFoodQty = bank[bankID].qty;
equipFood();
return getBankQty(itemID) == 0; //returns false if there is any of the item left in bank (couldn't equip)
}
if (items[itemID].equipmentSlot == CONSTANTS.equipmentSlot.Quiver) {
equipItem(itemID, bank[bankID].qty, selectedEquipmentSet);
return getBankQty(itemID) == 0; //returns false if there is any of the item left in bank (couldn't equip)
}
equipItem(itemID, 1, selectedEquipmentSet);
return equippedItems[items[itemID].equipmentSlot] === itemID; //returns false if the item is not equipped
};
case "Unequip Item": {
return () => {
const i = equippedItems.findIndex((a) => a == itemID);
if (i >= 0) {
unequipItem(i);
return getBankQty(itemID) > 0; //returns false if there is 0 of the items in bank (no space to unequip)
}
return false;
};
}
case "Buy Item":
return () => {
if (!shop[actionName].req(qty) || !shop[actionName].cost()) return false;
shop[actionName].buy(qty);
return true;
};
case "Sell Item":
return () => {
if (!bank.find((a) => a.id == itemID)) return false;
if (sellItemMode) toggleSellItemMode();
selectBankItem(itemID);
sellItem();
if (showSaleNotifications) swal.clickConfirm();
return true;
};
}
}
function setSkillAction(actionName, skillItem, skillItem2) {
const itemID = items.findIndex((a) => a.name == skillItem);
let actionID = 0;
switch (actionName) {
case "Cooking":
actionID = cookingItems.find((a) => a.itemID == itemID).cookingID;
return () => {
if (!skillLevel[CONSTANTS.skill.Cooking] >= items[itemID].cookingLevel) return false;
if (selectedFood !== itemID) selectFood(itemID);
if (!isCooking) startCooking(0, false);
return true;
};
case "Crafting":
actionID = craftingItems.find((a) => a.itemID == itemID).craftingID;
return () => {
if (!skillLevel[CONSTANTS.skill.Crafting] >= craftingItems[actionID].craftingLevel) return false;
if (selectedCraft !== actionID) selectCraft(actionID);
if (!isCrafting) startCrafting(true);
return true;
};
case "Firemaking":
actionID = items[itemID].firemakingID;
return () => {
if (!skillLevel[CONSTANTS.skill.Firemaking] >= logsData[actionID].level) return false;
if (selectedLog !== actionID) selectLog(actionID);
if (isBurning) burnLog(false);
if (!isBurning) burnLog(false);
return true;
};
case "Fishing": {
const fishIndex = fishingItems.find((a) => a.itemID == itemID).fishingID;
const areaID = fishingAreas.findIndex((a) => a.fish.includes(fishIndex));
const fishID = fishingAreas[areaID].fish.findIndex((a) => a == fishIndex);
return () => {
if (
(!equippedItems.includes(CONSTANTS.item.Barbarian_Gloves) && areaID == 6) ||
(!secretAreaUnlocked && areaID == 7) ||
skillLevel[CONSTANTS.skill.Fishing] < fishingItems[fishIndex].fishingLevel
)
return false;
if (!isFishing) {
selectFish(areaID, fishID);
startFishing(areaID, fishID, true);
} else {
if (areaID != offline.action[0] || fishID != offline.action[1]) {
startFishing(offline.action[0], offline.action[1], true);
selectFish(areaID, fishID);
startFishing(areaID, fishID, true);
}
}
return true;
};
}
case "Fletching":
actionID = fletchingItems.find((a) => a.itemID == itemID).fletchingID;
return () => {
if (!skillLevel[CONSTANTS.skill.Fletching] >= fletchingItems[actionID].fletchingLevel) return false;
if (selectedFletch !== actionID) selectFletch(actionID);
if (!isFletching) startFletching(true);
return true;
};
case "Herblore":
actionID = herbloreItemData.findIndex((a) => a.name == skillItem);
return () => {
if (!skillLevel[CONSTANTS.skill.Herblore] >= herbloreItemData[actionID].herbloreLevel) return false;
if (selectedHerblore !== actionID) selectHerblore(actionID);
if (!isHerblore) startHerblore(true);
return true;
};
case "Mining":
actionID = miningData.findIndex((a) => a.ore == itemID);
return () => {
if (
(actionID === 9 && !canMineDragonite()) ||
skillLevel[CONSTANTS.skill.Mining] < miningData[actionID].level ||
rockData[actionID].depleted
)
return false;
if (!isMining || currentRock != actionID) mineRock(actionID, true);
return true;
};
case "Magic": {
actionID = ALTMAGIC.findIndex((a) => a.name == skillItem);
const magicItem = items.findIndex((a) => a.name == skillItem2);
return () => {
if (
skillLevel[CONSTANTS.skill.Magic] < ALTMAGIC[actionID].magicLevelRequired ||
skillLevel[CONSTANTS.skill.Smithing] < smithingItems.find((a) => a.smithingLevel == skillItem2)
)
return false;
if (selectedAltMagic !== actionID) selectMagic(actionID);
if (ALTMAGIC[actionID].selectItem >= 0 && selectedMagicItem[ALTMAGIC[actionID].selectItem] !== magicItem) {
if (getBankQty(magicItem) < 1 || lockedItems.includes(magicItem)) return false;
selectItemForMagic(ALTMAGIC[actionID].selectItem, magicItem, false);
}
if (!isMagic) castMagic(true);
return true;
};
}
case "Runecrafting":
actionID = runecraftingItems.findIndex((a) => a.itemID == itemID);
return () => {
if (!skillLevel[CONSTANTS.skill.Runecrafting] >= runecraftingItems[actionID].runecraftingLevel) return false;
if (selectedRunecraft !== actionID) selectRunecraft(actionID);
if (!isRunecrafting) startRunecrafting(true);
return true;
};
case "Smithing":
actionID = smithingItems.findIndex((a) => a.itemID == itemID);
return () => {
if (!skillLevel[CONSTANTS.skill.Smithing] >= smithingItems[actionID].smithingLevel) return false;
if (selectedSmith !== actionID) selectSmith(actionID);
if (!isSmithing) startSmithing(true);
return true;
};
case "Thieving":
actionID = thievingNPC.findIndex((a) => a.name == skillItem);
return () => {
if (!skillLevel[CONSTANTS.skill.Thieving] >= thievingNPC[actionID].level) return false;
if (isThieving) pickpocket(npcID);
pickpocket(actionID);
return true;
};
case "Woodcutting":
actionID = [itemID, items.findIndex((a) => a.name == skillItem2)];
return () => {
let result = true;
treeCuttingHandler.forEach((tree, i) => {
if (tree !== null && !actionID.slice(0, treeCutLimit).includes(i)) cutTree(i);
});
for (let i = 0; i < treeCutLimit; i++) {
if (treeCuttingHandler[actionID[i]] === null) {
if (skillLevel[CONSTANTS.skill.Woodcutting] >= trees[actionID[i]].level) {
cutTree(actionID[i]);
} else {
result = false;
}
}
}
return result;
};
}
}
class Action {
constructor(category, name, greaterThan, masteryItem, number, actionCategory, actionName, skillItem, skillItem2, qty) {
switch (category) {
case "Item Quantity":
this.description = `If ${name} ${greaterThan} ${number}, `;
break;
case "Skill Level":
this.description = `If ${name} ≥ level ${number}, `;
break;
case "Skill XP":
this.description = `If ${name} ≥ ${number}xp, `;
break;
case "Equipped Item Quantity": {
let plural = name;
if (!/s$/i.test(name)) plural += "s";
this.description = `If ≤ ${number} ${plural} equipped, `;
break;
}
case "Mastery Level":
this.description = `If ${name} ${masteryItem} mastery ≥ ${number}, `;
}
switch (actionCategory) {
case "Start Skill":
this.description += `start ${actionName} ${skillItem}`;
if (actionName == "Woodcutting") this.description += ` & ${skillItem2}`;
if (actionName == "Magic") {
this.description += ` with ${skillItem2}`;
if (!/s$/i.test(skillItem2)) this.description += "s";
}
break;
case "Start Combat":
this.description += `start fighting ${actionName}`;
break;
case "Switch Equipment Set":
this.description += `switch to equipment set ${actionName}`;
break;
case "Equip Item":
this.description += `equip ${actionName} to current set`;
break;
case "Unequip Item":
this.description += `unequip ${actionName} from current set`;
break;
case "Buy Item":
if (qty > 1) {
this.description += `buy ${qty} ${actionName} from shop`;
} else {
this.description += `buy ${actionName} from shop`;
}
break;
case "Sell Item":
this.description += `sell ${actionName}`;
}
this.data = [category, name, greaterThan, masteryItem, number, actionCategory, actionName, skillItem, skillItem2, qty];
this.elementID = `AQ${nameIncrement++}`;
this.trigger = setTrigger(category, name, greaterThan, masteryItem, number);
this.startAction = setAction(actionCategory, actionName, skillItem, skillItem2, qty); //function returns true if requirements were met and action executed otherwise false.
}
}
function resetForm() {
document.getElementById("aq-form").reset(); //reset form
[1, 2, 3, 4, 6, 7, 8, 9].forEach((a) => (document.getElementById(`aq-${a}`).type = "hidden")); //hide fields
for (let i = 0; i < 6; i++) document.getElementById(`aq-list-${i}`).innerHTML = ""; //empty dropdown lists
triggerTier = [null, null, null];
actionTier = [null, null, null, null];
}
window.submitForm = function (event) {
const arr = [];
for (let i = 0; i < 10; i++) {
let e = event.target.elements[`aq-${i}`].value;
if (document.getElementById(`aq-${i}`).type == "text" && !validateOption(i, e)) return false;
e == "" ? arr.push(null) : arr.push(e);
}
if (arr[9] === null) arr[9] = 1;
addToQueue(new Action(...arr));
resetForm();
return false;
};
function validateOption(number, value) {
const list = document.getElementById(`aq-${number}`).getAttribute("list");
if (list === null) return true;
for (const a of document.getElementById(list).options) {
if (a.value === value) return true;
}
return false;
}
let triggerTier = [null, null, null];
let actionTier = [null, null, null, null];
window.dropdowns = function (i, type, tier) {
let t = htmlChar(document.getElementById(`aq-${i}`).value);
let arr = [];
switch (type) {
case "trigger":
arr = [];
switch (tier) {
case 0:
arr = Object.keys(triggers);
break;
case 1:
arr = Object.keys(triggers[triggerTier[0]]);
break;
case 2:
arr = Object.keys(triggers[triggerTier[0]][triggerTier[1]]);
}
if (arr.includes(t) && triggerTier[tier] !== t) {
triggerTier[tier] = t;
if (triggerTier[0] == "Item Quantity") {
switch (tier) {
case 0:
if (document.getElementById(`aq-list-0`).childElementCount > 0) document.getElementById(`aq-list-0`).innerHTML = "";
Object.keys(triggers[t]).forEach((e) =>
document.getElementById(`aq-list-0`).insertAdjacentHTML("beforeend", `<option>${e}</option>`)
);
[2, 3, 4].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-1`).type = "text";
break;
case 1:
if (document.getElementById(`aq-list-1`).childElementCount > 0) document.getElementById(`aq-list-1`).innerHTML = "";
Object.keys(triggers[triggerTier[0]][t]).forEach((e) =>
document.getElementById(`aq-list-1`).insertAdjacentHTML("beforeend", `<option>${e}</option>`)
);
[3, 4].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-2`).type = "text";
break;
case 2:
[3].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-4`).type = "text";
}
} else if (triggerTier[0] == "Mastery Level") {
switch (tier) {
case 0:
if (document.getElementById(`aq-list-0`).childElementCount > 0) document.getElementById(`aq-list-0`).innerHTML = "";
Object.keys(triggers[t]).forEach((e) =>
document.getElementById(`aq-list-0`).insertAdjacentHTML("beforeend", `<option>${e}</option>`)
);
[2, 3, 4].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-1`).type = "text";
break;
case 1:
if (document.getElementById(`aq-list-2`).childElementCount > 0) document.getElementById(`aq-list-2`).innerHTML = "";
Object.keys(triggers[triggerTier[0]][t]).forEach((e) =>
document.getElementById(`aq-list-2`).insertAdjacentHTML("beforeend", `<option>${e}</option>`)
);
[2, 4].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-3`).type = "text";
break;
case 2:
[2].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-4`).type = "text";
}
} else {
switch (tier) {
case 0:
if (document.getElementById(`aq-list-0`).childElementCount > 0) document.getElementById(`aq-list-0`).innerHTML = "";
Object.keys(triggers[t]).forEach((e) =>
document.getElementById(`aq-list-0`).insertAdjacentHTML("beforeend", `<option>${e}</option>`)
);
[2, 3, 4].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-1`).type = "text";
break;
case 1:
[2, 3].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-4`).type = "text";
}
}
}
break;
case "action":
arr = [];
switch (tier) {
case 0:
arr = Object.keys(actions);
break;
case 1:
arr = Object.keys(actions[actionTier[0]]);
break;
case 2:
arr = Object.keys(actions[actionTier[0]][actionTier[1]]);
break;
}
if (arr.includes(t) && actionTier[tier] !== t) {
actionTier[tier] = t;
if (actionTier[0] == "Start Skill") {
switch (tier) {
case 0:
if (document.getElementById(`aq-list-3`).childElementCount > 0) document.getElementById(`aq-list-3`).innerHTML = "";
Object.keys(actions[t]).forEach((e) =>
document.getElementById(`aq-list-3`).insertAdjacentHTML("beforeend", `<option>${e}</option>`)
);
[7, 8, 9].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-6`).type = "text";
break;
case 1:
if (document.getElementById(`aq-list-4`).childElementCount > 0) document.getElementById(`aq-list-4`).innerHTML = "";
Object.keys(actions[actionTier[0]][t]).forEach((e) =>
document.getElementById(`aq-list-4`).insertAdjacentHTML("beforeend", `<option>${e}</option>`)
);
[8, 9].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-7`).type = "text";
break;
case 2:
if (actions[actionTier[0]][actionTier[1]][actionTier[2]] !== null) {
if (document.getElementById(`aq-list-5`).childElementCount > 0) document.getElementById(`aq-list-5`).innerHTML = "";
Object.keys(actions[actionTier[0]][actionTier[1]][t]).forEach((e) =>
document.getElementById(`aq-list-5`).insertAdjacentHTML("beforeend", `<option>${e}</option>`)
);
[9].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-8`).type = "text";
}
}
} else if (actionTier[0] == "Buy Item") {
switch (tier) {
case 0:
if (document.getElementById(`aq-list-3`).childElementCount > 0) document.getElementById(`aq-list-3`).innerHTML = "";
Object.keys(actions[t]).forEach((e) =>
document.getElementById(`aq-list-3`).insertAdjacentHTML("beforeend", `<option>${e}</option>`)
);
[7, 8, 9].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-6`).type = "text";
break;
case 1:
[7, 8].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-9`).type = "text";
}
} else {
if (tier === 0) {
if (document.getElementById(`aq-list-3`).childElementCount > 0) document.getElementById(`aq-list-3`).innerHTML = "";
Object.keys(actions[t]).forEach((e) =>
document.getElementById(`aq-list-3`).insertAdjacentHTML("beforeend", `<option>${e}</option>`)
);
[7, 8, 9].forEach((e) => (document.getElementById(`aq-${e}`).type = "hidden"));
document.getElementById(`aq-6`).type = "text";
}
}
}
}
};
const aqHTML = `<div class="content" id="action-queue-container" style="display: none">
<div class="row row-deck">
<div class="col-md-12">
<div class="block block-rounded block-link-pop border-top border-settings border-4x">
<div class="block-content">
<div>
<form onSubmit="return submitForm(event)" id="aq-form">
<div style="display: inline-block; margin-left: 20px; height: 180px; vertical-align: top">
<h3 class="aq-header">Trigger</h3>
<div>
<input
type="text"
class="aq-dropdown"
id="aq-0"
required
list="trigger-category"
placeholder="Category"
oninput="dropdowns(0, 'trigger', 0)"
/>
</div>
<div>
<input type="hidden" class="aq-dropdown" id="aq-1" list="aq-list-0" oninput="dropdowns(1, 'trigger', 1)" />
</div>
<div>
<input type="hidden" class="aq-dropdown" id="aq-2" list="aq-list-1" oninput="dropdowns(2, 'trigger', 2)" />
</div>
<div>
<input type="hidden" class="aq-dropdown" id="aq-3" list="aq-list-2" oninput="dropdowns(3, 'trigger', 2)" />
</div>
<div>
<input type="hidden" class="aq-dropdown" id="aq-4" pattern="^\\d{1,10}$" required placeholder="number" />
</div>
</div>
<div style="display: inline-block; margin-left: 20px; height: 180px; vertical-align: top">
<h3 class="aq-header">Action</h3>
<div>
<input
type="text"
class="aq-dropdown"
id="aq-5"
required
list="action-category"
placeholder="Category"
oninput="dropdowns(5, 'action', 0)"
/>
</div>
<div>
<input type="hidden" class="aq-dropdown" id="aq-6" list="aq-list-3" oninput="dropdowns(6, 'action', 1)" />
</div>
<div>
<input type="hidden" class="aq-dropdown" id="aq-7" list="aq-list-4" oninput="dropdowns(7, 'action', 2)" />
</div>
<div>
<input type="hidden" class="aq-dropdown" id="aq-8" list="aq-list-5" />
</div>
<div>
<input type="hidden" class="aq-dropdown" id="aq-9" pattern="^\\d{1,10}$" placeholder="number" />
</div>
</div>
<div style="margin-left: 20px">
<input type="submit" class="btn btn-sm aq-blue" value="Add to queue">
<button type="button" id="aq-pause" class="btn btn-sm aq-yellow" onclick="togglePause()">Pause</button>
</div>
</form>
</div>
<form onSubmit="return importActions()" style="margin: 10px 0 5px 20px">
<button type="button" class="btn btn-sm aq-grey" onclick="downloadActions()">Download Action List</button>
<input type="submit" class="btn btn-sm aq-grey" value="Import Action List" />
<input type="text" id="aq-pastebin" style="width: 236px;" required pattern="^\\[.*\\]$" placeholder="Paste data here" />
</form>
<div style="display: flex;justify-content: space-between;max-width: 550px;margin: 10px 0 0 25px;">
<p style="margin: 0;">Looping</p>
<div class="">
<div class="custom-control custom-radio custom-control-inline custom-control-lg">
<input type="radio" class="custom-control-input" id="aq-loop-enable" name="aq-looping" onclick="toggleLoop(true);">
<label class="custom-control-label" for="aq-loop-enable">Enable</label>
</div>
<div class="custom-control custom-radio custom-control-inline custom-control-lg">
<input type="radio" class="custom-control-input" id="aq-loop-disable" name="aq-looping" onclick="toggleLoop(false);" checked="">
<label class="custom-control-label" for="aq-loop-disable">Disable</label>
</div>
</div>
</div>
<div style="display: flex;justify-content: space-between;max-width: 550px;margin: 10px 0 0 25px;">
<p style="margin: 0;">Mastery Pool Management</p>
<div class="">
<div class="custom-control custom-radio custom-control-inline custom-control-lg">
<input type="radio" class="custom-control-input" id="aq-mastery-enable" name="aq-mastery" onclick="toggleMastery(true);">
<label class="custom-control-label" for="aq-mastery-enable">Enable</label>
</div>
<div class="custom-control custom-radio custom-control-inline custom-control-lg">
<input type="radio" class="custom-control-input" id="aq-mastery-disable" name="aq-mastery" onclick="toggleMastery(false);" checked="">
<label class="custom-control-label" for="aq-mastery-disable">Disable</label>
</div>
</div>
</div>
<h2 class="content-heading border-bottom mb-4 pb-2">Current Queue</h2>
<div style="min-height: 50px" id="aq-item-container"></div>
</div>
</div>
</div>
</div>
</div>
<datalist id="trigger-category"></datalist>
<datalist id="aq-list-0"></datalist>
<datalist id="aq-list-1"></datalist>
<datalist id="aq-list-2"></datalist>
<datalist id="action-category"></datalist>
<datalist id="aq-list-3"></datalist>
<datalist id="aq-list-4"></datalist>
<datalist id="aq-list-5"></datalist>
<style>
.aq-dropdown {
width: 260px;
}
.aq-header {
margin-bottom: 10px;
color: whitesmoke;
}
.aq-item {
display: flex;
justify-content: space-between;
background-color: #464646;
padding: 12px;
margin: 12px;
}
.aq-arrow {
font-size: 1.5rem;
padding: 0px 0.25rem;
line-height: 0px;
margin: 0.25rem 2px;
border-radius: 0.2rem;
}
.aq-delete {
font-size: 1rem;
padding: 0px 0.375rem;
line-height: 0px;
margin: 0.25rem 0.25rem 0.25rem 0.125rem;
border-radius: 0.2rem;
}
.aq-grey {
background-color: #676767;
}
.aq-grey:hover {
background-color: #848484;
}
.aq-blue {
background-color: #0083ff;
}
.aq-blue:hover {
background-color: #63b4ff;
}
.aq-green {
background-color: #5a9e00;
}
.aq-green:hover {
background-color: #7bd900;
}
.aq-yellow {
background-color: #e69721;
}
.aq-yellow:hover {
background-color: #ffb445;
}
</style>
`;
function loadAQ() {
for (const a of items) {
triggers["Item Quantity"][a.name] = { "≥": null, "≤": null };
actions["Sell Item"][a.name] = null;
}
Object.keys(CONSTANTS.skill).forEach((a) => {
triggers["Skill Level"][a] = null;
triggers["Skill XP"][a] = null;
});
{
cookingItems.forEach((item) => {
triggers["Mastery Level"]["Cooking"][items[item.itemID].name] = null;
actions["Start Skill"]["Cooking"][items[item.itemID].name] = null;
});
craftingItems.forEach((item) => {
triggers["Mastery Level"]["Crafting"][items[item.itemID].name] = null;
actions["Start Skill"]["Crafting"][items[item.itemID].name] = null;
});
items.forEach((item) => {
if (item.type == "Seeds") {
triggers["Mastery Level"]["Farming"][item.name] = null;
}
});
items.forEach((item) => {
if (item.type == "Logs") {
triggers["Mastery Level"]["Firemaking"][item.name] = null;
actions["Start Skill"]["Firemaking"][item.name] = null;
}
});
fishingItems.forEach((item) => {
triggers["Mastery Level"]["Fishing"][items[item.itemID].name] = null;
actions["Start Skill"]["Fishing"][items[item.itemID].name] = null;
});
fletchingItems.forEach((item) => {
triggers["Mastery Level"]["Fletching"][items[item.itemID].name] = null;
actions["Start Skill"]["Fletching"][items[item.itemID].name] = null;
});
herbloreItemData.forEach((item) => {
triggers["Mastery Level"]["Herblore"][item.name] = null;
actions["Start Skill"]["Herblore"][item.name] = null;
});
miningData.forEach((item) => {
triggers["Mastery Level"]["Mining"][items[item.ore].name] = null;
actions["Start Skill"]["Mining"][items[item.ore].name] = null;
});
runecraftingItems.forEach((item) => {
triggers["Mastery Level"]["Runecrafting"][items[item.itemID].name] = null;
actions["Start Skill"]["Runecrafting"][items[item.itemID].name] = null;
});
smithingItems.forEach((item) => {
triggers["Mastery Level"]["Smithing"][items[item.itemID].name] = null;
actions["Start Skill"]["Smithing"][items[item.itemID].name] = null;
});
thievingNPC.forEach((npc) => {
triggers["Mastery Level"]["Thieving"][npc.name] = null;
actions["Start Skill"]["Thieving"][npc.name] = null;
});
items.forEach((item) => {
if (item.type == "Logs") {
triggers["Mastery Level"]["Woodcutting"][item.name] = null;
actions["Start Skill"]["Woodcutting"][item.name] = {};
}
});
for (const log in actions["Start Skill"]["Woodcutting"]) {
Object.keys(actions["Start Skill"]["Woodcutting"]).forEach((a) => (actions["Start Skill"]["Woodcutting"][log][a] = null));
}
}
ALTMAGIC.forEach((spell) => {
actions["Start Skill"]["Magic"][spell.name] = {};
if (spell.selectItem === 0) {
for (const item of smithingItems) {
if (item.category === 0) {
actions["Start Skill"]["Magic"][spell.name][items[item.itemID].name] = null;
}
}
} else if (spell.isJunk) {
junkItems.forEach((a) => (actions["Start Skill"]["Magic"][spell.name][items[a].name] = null));
} else if (spell.selectItem === 1) {
actions["Start Skill"]["Magic"][spell.name] = actions["Sell Item"];
} else actions["Start Skill"]["Magic"][spell.name] = null;
});
for (const a of items.filter((a) => a.canEat)) {
triggers["Equipped Item Quantity"][a.name] = null;
actions["Equip Item"][a.name] = null;
}
for (const a of items.filter((a) => a.equipmentSlot == CONSTANTS.equipmentSlot.Quiver)) triggers["Equipped Item Quantity"][a.name] = null;
{
const monsterIDs = [];
for (const area of combatAreas) monsterIDs.push(...area.monsters);
for (const area of slayerAreas) monsterIDs.push(...area.monsters);
for (const monster of monsterIDs) actions["Start Combat"][MONSTERS[monster].name] = null;
for (const dungeon of DUNGEONS) actions["Start Combat"][dungeon.name] = null;
}
for (const a of items.filter((a) => a.hasOwnProperty("equipmentSlot"))) {
actions["Equip Item"][a.name] = null;
actions["Unequip Item"][a.name] = null;
}
{
shop["Upgrade Bank"] = new ShopItem(
0,
0,
() => {
if (currentGamemode === 1 && baseBankMax + bankMax >= 80) return false;
return gp >= Math.min(newNewBankUpgradeCost.level_to_gp(currentBankUpgrade + 1), 5000000);
},
() => upgradeBank(true)
);
shop["Upgrade Auto Eat"] = new ShopItem(
0,
0,
() => currentAutoEat < 3 && gp >= autoEatData[currentAutoEat].cost,
() => upgradeAutoEat(true)
);
shop["Equipment Set (gp)"] = new ShopItem(
equipmentSetData[0].cost,
0,
() => !equipmentSetsPurchased[0],
() => upgradeEquipmentSet(0, true)
);
shop["Equipment Set (slayer coins)"] = new ShopItem(
0,
equipmentSetData[1].cost,
() => !equipmentSetsPurchased[1],
() => upgradeEquipmentSet(1, true)
);
shop["Dungeon Equipment Swapping"] = new ShopItem(
equipmentSwapData[0].cost,
0,
() => !equipmentSetsPurchased[1],
() => upgradeEquipmentSwap(true)
);
shop["Multi-Tree"] = new ShopItem(
multiTreeCost[0],
0,
() => treeCutLimit < 2,
() => upgradeMultiTree(true)
);
godUpgradeData.forEach((upgrade, i) => {
shop[upgrade.name] = new ShopItem(
upgrade.cost,
0,
() => dungeonCompleteCount[upgrade.dungeonID] > 0 && !godUpgrade[i],
() => buyGodUpgrade(i, true)
);
});
shop["Upgrade Axe"] = new ShopItem(
0,
0,
() => currentAxe < 7 && gp >= axeCost[currentAxe + 1],
() => upgradeAxe(true)
);
shop["Upgrade Rod"] = new ShopItem(
0,
0,
() => currentRod < 7 && gp >= rodCost[currentRod + 1],
() => upgradeRod(true)
);
shop["Upgrade Pickaxe"] = new ShopItem(
0,
0,
() => currentPickaxe < 7 && gp >= pickaxeCost[currentPickaxe + 1],
() => upgradePickaxe(true)
);
shop["Upgrade Cooking Fire"] = new ShopItem(
0,
0,
() => {
return (
currentCookingFire < 9 &&
gp >= cookingFireData[currentCookingFire].costGP &&
skillLevel[CONSTANTS.skill.Firemaking] >= cookingFireData[currentCookingFire].fmLevel &&
getBankQty(cookingFireData[currentCookingFire].costLogs[0]) >= cookingFireData[currentCookingFire].costLogs[1]
);
},
() => upgradeCookingFire(true)
);
for (const itemID of slayerItems) {
shop[items[itemID].name] = new ShopItem(
0,
items[itemID].slayerCost,
() => true,
() => buySlayerItem(itemID, true)
);
}
gloveID.forEach((itemID, i) => {
shop[items[itemID].name] = new ShopItem(
glovesCost[i],
0,
() => true,
() => buyGloves(i, true)
);
});
skillcapeItems.forEach((itemID, i) => {
shop[items[itemID].name] = new ShopItem(
items[itemID].buysFor,
0,
() => true,
() => buySkillcape(i, true)
);
});
shop[items[CONSTANTS.item.Feathers].name] = new ShopItem(
0,
0,
(qty) => gp >= items[CONSTANTS.item.Feathers].buysFor * qty,
(qty) => {
updateBuyQty(qty);
buyFeathers(true);
}
);
shop[items[CONSTANTS.item.Compost].name] = new ShopItem(
0,
0,
(qty) => gp >= items[CONSTANTS.item.Compost].buysFor * qty,
(qty) => {
updateBuyQty(qty);
buyCompost(true);
}
);
shop[items[CONSTANTS.item.Weird_Gloop].name] = new ShopItem(
0,
0,
(qty) => {
for (const a of items[CONSTANTS.item.Weird_Gloop].buysForItems) {
if (getBankQty(a[0]) < qty * a[1]) return false;
}
return gp >= items[CONSTANTS.item.Weird_Gloop].buysFor * qty;
},
(qty) => {
updateBuyQty(qty);
buyItem(CONSTANTS.item.Weird_Gloop, true);
}
);
shop[items[CONSTANTS.item.Bowstring].name] = new ShopItem(
0,
0,
(qty) => gp >= items[CONSTANTS.item.Bowstring].buysFor * qty,
(qty) => {
updateBuyQty(qty);
buyBowstring(true);
}
);
shop[items[CONSTANTS.item.Leather].name] = new ShopItem(
0,
0,
(qty) => gp >= items[CONSTANTS.item.Leather].buysFor * qty,
(qty) => {
updateBuyQty(qty);
buyLeather(true);
}
);
[CONSTANTS.item.Green_Dragonhide, CONSTANTS.item.Blue_Dragonhide, CONSTANTS.item.Red_Dragonhide].forEach((itemID) => {
shop[items[itemID].name] = new ShopItem(
0,
0,
(qty) => gp >= items[itemID].buysFor * qty,
(qty) => {
updateBuyQty(qty);
buyDhide(itemID, true);
}
);
});
shop[items[CONSTANTS.item.Red_Party_Hat].name] = new ShopItem(
0,
0,
(qty) => gp >= items[CONSTANTS.item.Red_Party_Hat].buysFor * qty,
(qty) => {
updateBuyQty(qty);
buyPartyHat(true);
}
);
}
for (const name in shop) actions["Buy Item"][name] = null;
$("li.nav-main-item:contains(Bank)")[0].insertAdjacentHTML(
"afterend",
`
<li class="nav-main-item">
<a class="nav-main-link nav-compact" href="javascript:actionTab();">
<img class="nav-img" src="assets/media/skills/prayer/mystic_lore.svg">
<span class="nav-main-link-name">Action Queue</span>
<small id="current-queue" style="color: rgb(210, 106, 92);">inactive</small>
</a>
</li>`
);
const htmlCollection = $('div[onclick^="changePage"]');
for (let i = 0; i < htmlCollection.length; i++) htmlCollection[i].addEventListener("click", () => hideActionTab());
document.getElementById("main-container").insertAdjacentHTML("beforeend", aqHTML);
Object.keys(triggers).forEach((a) =>
document.getElementById("trigger-category").insertAdjacentHTML("beforeend", `<option>${a}</option>`)
);
Object.keys(actions).forEach((a) => document.getElementById("action-category").insertAdjacentHTML("beforeend", `<option>${a}</option>`));
loadLocalSave();
console.log("Action Queue loaded");
}
window.deleteAction = function (id) {
const i = actionQueueArray.findIndex((a) => a.elementID == id);
if (i < 0) return;
actionQueueArray.splice(i, 1);
if (currentActionIndex > i) currentActionIndex--;
const element = document.getElementById(id);
if (element) element.remove();
updateQueue();
};
const replaceChar = [
{ reg: "'", replace: "'" },
];
function htmlChar(string) {
let s = string;
replaceChar.forEach(function (obj) {
const regEx = new RegExp(obj.reg + "(?!([^<]+)?>)", "g");
s = s.replace(regEx, obj.replace);
});
return s;
}
function addToQueue(obj) {
actionQueueArray.push(obj);
const element = `<div class="aq-item" id="${obj.elementID}">
<p style="margin: auto 0">${obj.description}</p>
<div style="min-width: 170px; min-height: 39px; display: flex; justify-content: flex-end;">
<small style="display: none; margin: auto 0.25rem">action failed</small>
<button type="button" class="btn aq-arrow aq-grey" onclick="setCurrentAction('${obj.elementID}')" style="font-size: 0.875rem;">select</button>
<button type="button" class="btn aq-arrow aq-grey" onclick="moveAction('${obj.elementID}', 'up')">↑</button>
<button type="button" class="btn aq-arrow aq-grey" onclick="moveAction('${obj.elementID}', 'down')">↓</button>
<button type="button" class="btn aq-delete btn-danger" onclick="deleteAction('${obj.elementID}')">X</button>
</div>
</div>`;
document.getElementById("aq-item-container").insertAdjacentHTML("beforeend", element);
if (triggerCheckInterval === null && !queuePause) {
currentActionIndex = 0;
updateQueue();
triggerCheckInterval = setInterval(() => {
triggerCheck();
}, 1000);
updateTextColour("start");
}
}
function triggerCheck() {
let result = true;
if (currentActionIndex >= actionQueueArray.length) {
if (queueLoop && actionQueueArray.length > 0) {
currentActionIndex = 0;
updateQueue();
return;
} else {
clearInterval(triggerCheckInterval);
triggerCheckInterval = null;
updateTextColour("stop");
return;
}
}
if (actionQueueArray[currentActionIndex].trigger()) {
result = actionQueueArray[currentActionIndex].startAction();
document.getElementById(actionQueueArray[currentActionIndex].elementID).children[1].children[0].style.display = result ? "none" : "";
currentActionIndex + 1 >= actionQueueArray.length && queueLoop ? (currentActionIndex = 0) : currentActionIndex++;
updateQueue();
}
}
function updateTextColour(type) {
switch (type) {
case "start":
document.getElementById("current-queue").style.color = "#46c37b";
document.getElementById("current-queue").innerHTML = "running";
break;
case "stop":
document.getElementById("current-queue").style.color = "#d26a5c";
document.getElementById("current-queue").innerHTML = "inactive";
break;
case "pause":
document.getElementById("current-queue").style.color = "#f3b760";
document.getElementById("current-queue").innerHTML = "paused";
}
}
function updateQueue() {
actionQueueArray.forEach((action, index) => {
const element = document.getElementById(action.elementID);
if (index === currentActionIndex) {
element.children[1].children[0].style.display = "none";
element.style.backgroundColor = "#385a0b";
} else element.style.backgroundColor = "";
});
}
window.toggleLoop = function (start) {
queueLoop = start;
};
window.togglePause = function () {
queuePause = !queuePause;
if (queuePause) {
clearInterval(triggerCheckInterval);
triggerCheckInterval = null;
document.getElementById("aq-pause").innerHTML = "Unpause";
document.getElementById("aq-pause").classList.add("aq-green");
document.getElementById("aq-pause").classList.remove("aq-yellow");
updateTextColour("pause");
} else {
if (actionQueueArray.length > 0) {
updateQueue();
triggerCheckInterval = setInterval(() => {
triggerCheck();
}, 1000);
updateTextColour("start");
} else {
updateTextColour("stop");
}
document.getElementById("aq-pause").innerHTML = "Pause";
document.getElementById("aq-pause").classList.add("aq-yellow");
document.getElementById("aq-pause").classList.remove("aq-green");
}
};
let loadCheckInterval = setInterval(() => {
if (isLoaded) {
clearInterval(loadCheckInterval);
loadAQ();
}
}, 200);
function autoSave() {
const saveData = { index: currentActionIndex, data: [] };
for (const action of actionQueueArray) {
saveData.data.push(action.data);
}
window.localStorage.setItem("AQSAVE" + currentCharacter, JSON.stringify(saveData));
}
setInterval(() => {
autoSave();
}, 59550);
function loadLocalSave() {
const obj = JSON.parse(window.localStorage.getItem("AQSAVE" + currentCharacter));
if (obj === null) return;
if (obj.data.length > 0) togglePause();
currentActionIndex = obj.index;
for (const params of obj.data) addToQueue(new Action(...params));
updateQueue();
}
window.setCurrentAction = function (id) {
const index = actionQueueArray.findIndex((a) => a.elementID == id);
if (index >= 0) {
currentActionIndex = index;
updateQueue();
}
};
window.moveAction = function (id, direction) {
let index = actionQueueArray.findIndex((a) => a.elementID == id);
const element = document.getElementById(id);
const parent = element.parentNode;
currentActionIndex;
if (direction === "up" && element.previousElementSibling) {
parent.insertBefore(element, element.previousElementSibling);
if (currentActionIndex == index) {
index--;
} else if (currentActionIndex == index - 1) {
index++;
}
} else if (direction === "down" && element.nextElementSibling) {
parent.insertBefore(element, element.nextElementSibling.nextElementSibling);
if (currentActionIndex == index) {
index++;
} else if (currentActionIndex == index + 1) {
index--;
}
}
updateQueue();
};
window.moveAction = function (id, direction) {
const index = actionQueueArray.findIndex((a) => a.elementID == id);
const element = document.getElementById(id);
const parent = element.parentNode;
currentActionIndex;
if (direction === "up" && element.previousElementSibling) {
actionQueueArray.splice(index - 1, 0, ...actionQueueArray.splice(index, 1));
parent.insertBefore(element, element.previousElementSibling);
if (currentActionIndex == index) {
currentActionIndex--;
} else if (currentActionIndex == index - 1) {
currentActionIndex++;
}
} else if (direction === "down" && element.nextElementSibling) {
actionQueueArray.splice(index + 1, 0, ...actionQueueArray.splice(index, 1));
parent.insertBefore(element, element.nextElementSibling.nextElementSibling);
if (currentActionIndex == index) {
currentActionIndex++;
} else if (currentActionIndex == index + 1) {
currentActionIndex--;
}
}
updateQueue();
};
window.importActions = function () {
const string = document.getElementById("aq-pastebin").value;
if (!queuePause && actionQueueArray.length === 0) togglePause();
let arr = [];
try {
arr = JSON.parse(string.trim());
if (!Array.isArray(arr)) return false;
for (const params of arr) {
if (Array.isArray(params) && params.length == 10) {
try {
const action = new Action(...params);
if (
action.hasOwnProperty("description") &&
!action.description.includes("undefined") &&
!action.description.includes("null") &&
typeof action.trigger == "function" &&
typeof action.startAction == "function"
)
addToQueue(action);
} catch (err) {}
}
}
document.getElementById("aq-pastebin").value = "";
updateQueue();
return false;
} catch (err) {
return false;
}
};
window.downloadActions = function () {
const saveData = [];
for (const action of actionQueueArray) {
saveData.push(action.data);
}
let file = new Blob([JSON.stringify(saveData)], {
type: "text/plain",
});
if (window.navigator.msSaveOrOpenBlob) window.navigator.msSaveOrOpenBlob(file, "Melvor_Action_Queue.txt");
else {
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = "Melvor_Action_Queue.txt";
document.body.appendChild(a);
a.click();
setTimeout(function () {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
};
function manageMastery() {
for (const skill in MASTERY) {
let lowest = 0;
for (var i = 1; i < MASTERY[skill].xp.length; i++) {
if (MASTERY[skill].xp[i] < MASTERY[skill].xp[lowest]) lowest = i;
}
const masteryID = lowest;
const maxPool = MASTERY[skill].xp.length * 500000;
const bankID = getBankId(CONSTANTS.item[`Mastery_Token_${SKILLS[skill].name}`]);
if (bankID !== false && bank[bankID].qty > 0 && MASTERY[skill].pool < maxPool * 0.999) {
const maxTokens = Math.floor(((maxPool - MASTERY[skill].pool) * 1000) / maxPool);
{
let itemID = CONSTANTS.item[`Mastery_Token_${SKILLS[skill].name}`];
let qty = Math.min(bank[bankID].qty, maxTokens);
let qtyToUse = 0;
let xpToAdd = Math.floor(getMasteryPoolTotalXP(skill) * 0.001);
let totalPoolXP = getMasteryPoolTotalXP(skill);
let xpRemaining = totalPoolXP - MASTERY[skill].pool;
let tokensToFillPool = Math.floor(xpRemaining / xpToAdd);
let totalXpToAdd = 0;
if (qty >= tokensToFillPool) {
qtyToUse = tokensToFillPool;
totalXpToAdd = tokensToFillPool * xpToAdd;
} else {
totalXpToAdd = qty * xpToAdd;
qtyToUse = qty;
}
addMasteryXPToPool(skill, totalXpToAdd, false, true);
if (qty > tokensToFillPool) notifyPlayer(skill, "Maximum Mastery Pool XP reached at " + tokensToFillPool + " Tokens.", "info");
notifyPlayer(skill, numberWithCommas(totalXpToAdd) + " Mastery Pool XP granted.", "success");
updateItemInBank(bankID, itemID, -qtyToUse);
}
}
if (masteryPoolLevelUp > 1) masteryPoolLevelUp = 1;
if (
MASTERY[skill].xp[masteryID] < 13034431 &&
(MASTERY[skill].pool == maxPool || MASTERY[skill].pool - getMasteryXpForNextLevel(skill, masteryID) > 0.95 * maxPool)
) {
let xp = getMasteryXpForNextLevel(skill, masteryID);
if (MASTERY[skill].pool >= xp) {
{
let currentLevel = getMasteryLevel(skill, masteryID);
MASTERY[skill].xp[masteryID] += xp;
if (exp.xp_to_level(MASTERY[skill].xp[masteryID]) - 1 > currentLevel && currentLevel < 99) {
if (skill === CONSTANTS.skill.Woodcutting && currentLevel >= 98) updateWCRates();
}
updateMasteryProgress(skill, masteryID);
}
MASTERY[skill].pool -= xp;
updateMasteryPoolProgress(skill);
}
if (skill === CONSTANTS.skill.Fishing) {
for (let i = 0; i < fishingAreas.length; i++) {
for (let f = 0; f < fishingAreas[i].fish.length; f++) {
if (fishingAreas[i].fish[f] === masteryID) {
updateFishingMastery(i, f);
break;
}
}
}
}
}
}
}
window.toggleMastery = function (start) {
if (start && manageMasteryInterval === null) {
manageMasteryInterval = setInterval(() => {
manageMastery();
}, 1000);
} else if (!start) {
clearInterval(manageMasteryInterval);
manageMasteryInterval = null;
}
};