Melvor Action Queue

Adds an interface to queue up actions based on triggers you set

La data de 17-10-2020. Vezi ultima versiune.

// ==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: "&apos;" },
];
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;
  }
};