Melvor Action Queue

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

当前为 2020-10-10 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Melvor Action Queue
// @version      0.1.9
// @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;
window.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;
  }
};

//trigger options for dropdown menus
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: {},
  },
};

//action options for dropdown menus
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 {
  /**
   * Creates a shop item
   * @param {number} gp gp cost
   * @param {number} slayerCoins slayer coins cost
   * @param {function} req function that checks non-price requirements and returns boolean
   * @param {function} buy function that buys the item with qty as parameter where applicable
   */
  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;
      return () => {
        return cookingMastery[masteryID].mastery >= number;
      };
    case "Crafting":
      masteryID = craftingItems.find((a) => a.itemID == itemID).craftingID;
      return () => {
        return craftingMastery[masteryID].mastery >= number;
      };
    case "Farming":
      masteryID = items[itemID].masteryID;
      return () => {
        return farmingMastery[masteryID].mastery >= number;
      };
    case "Firemaking":
      masteryID = itemID;
      return () => {
        return logsMastery[masteryID].mastery >= number;
      };
    case "Fishing":
      masteryID = fishingItems.find((a) => a.itemID == itemID).fishingID;
      return () => {
        return fishMastery[masteryID].mastery >= number;
      };
    case "Fletching":
      masteryID = fletchingItems.find((a) => a.itemID == itemID).fletchingID;
      return () => {
        return fletchingMastery[masteryID].mastery >= number;
      };
    case "Herblore":
      masteryID = herbloreItemData.findIndex((a) => a.name == masteryItem);
      return () => {
        return herbloreMastery[masteryID].mastery >= number;
      };
    case "Mining":
      masteryID = miningData.find((a) => a.ore == itemID).masteryID;
      return () => {
        return miningOreMastery[masteryID].mastery >= number;
      };
    case "Runecrafting":
      masteryID = runecraftingItems.find((a) => a.itemID == itemID).runecraftingID;
      return () => {
        return runecraftingMastery[masteryID].mastery >= number;
      };
    case "Smithing":
      masteryID = smithingItems.find((a) => a.itemID == itemID).smithingID;
      return () => {
        return smithingMastery[masteryID].mastery >= number;
      };
    case "Thieving":
      masteryID = thievingNPC.findIndex((a) => a.name == masteryItem);
      return () => {
        return thievingMastery[masteryID].mastery >= number;
      };
    case "Woodcutting":
      masteryID = itemID;
      return () => {
        return treeMasteryData[masteryID].mastery >= 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 (items[itemID].canEat) {
          equipFood(bankID, itemID, bank[bankID].qty);
          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(bankID, itemID, bank[bankID].qty, -1);
          return getBankQty(itemID) > 0; //returns false if there is any of the item left in bank (couldn't equip)
        }
        equipItem(bankID, 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;
        sellItem(itemID);
        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 !== targetLog) selectLog(targetLog);
        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;
        mineRock(actionID, 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 {
  /**
   * Create an action object with trigger
   * @param {string} category (Tier 1 option) category for trigger
   * @param {string} name (Tier 2 option) skill/item name
   * @param {string} greaterThan (Tier 3 option) either ≥ or ≤
   * @param {string} masteryItem (Tier 3 option) target name for mastery
   * @param {string} number (Tier 4 option) target number for skill/mastery/item
   * @param {string} actionCategory (Tier 1 option) category for action
   * @param {string} actionName (Tier 2 option) skill/monster/item/set name
   * @param {string} skillItem (Tier 3 option) name for skilling action
   * @param {string} skillItem2 (Tier 4 option) name of second tree to cut or alt.magic item
   * @param {string} qty (Tier 4 option) amount of item to buy if applicable
   */
  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 to validate user input
 * @param {number} number element number e.g. 0 for aq-0
 * @param {string} value value of element
 * @returns true if value matches any of the values in the relevant dropdown list else 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];

/**
 * Function that deals with dropdown boxes
 * @param {number} i element number (starts from 0)
 * @param {string} type "trigger" or "action"
 * @param {number} tier tier of option
 */
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" style="background-color: #e69621" onclick="togglePause()">Pause</button>
              <button type="button" class="btn btn-sm aq-grey" onclick="toggleLoop()">Toggle Looping</button>
              <span id="aq-loop" style="font-size: 0.875rem; margin-left: 3px;">Looping disabled</span>
            </div>
          </form>
        </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;
}
</style>
`;

function loadAQ() {
  //add item names
  for (const a of items) {
    triggers["Item Quantity"][a.name] = { "≥": null, "≤": null };
    actions["Sell Item"][a.name] = null;
  }

  //add skill names
  Object.keys(CONSTANTS.skill).forEach((a) => {
    triggers["Skill Level"][a] = null;
    triggers["Skill XP"][a] = null;
  });

  //add mastery/action names for each skill
  {
    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));
    }
  }

  //add altmagic names
  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;
  });

  //add food and ammo names
  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;

  //add monster/dungeon names
  {
    //collect monster IDs
    const monsterIDs = [];
    for (const area of combatAreas) monsterIDs.push(...area.monsters);
    for (const area of slayerAreas) monsterIDs.push(...area.monsters);
    //add names to array
    for (const monster of monsterIDs) actions["Start Combat"][MONSTERS[monster].name] = null;
    for (const dungeon of DUNGEONS) actions["Start Combat"][dungeon.name] = null;
  }

  //add equippable items
  for (const a of items.filter((a) => a.hasOwnProperty("equipmentSlot"))) {
    actions["Equip Item"][a.name] = null;
    actions["Unequip Item"][a.name] = null;
  }

  //fill shop object with ShopItems
  {
    //bank upgrade
    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)
    );

    //autoeat upgrade
    shop["Upgrade Auto Eat"] = new ShopItem(
      0,
      0,
      () => currentAutoEat < 3 && gp >= autoEatData[currentAutoEat].cost,
      () => upgradeAutoEat(true)
    );

    //equipsets
    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)
    );

    //dungeon swap
    shop["Dungeon Equipment Swapping"] = new ShopItem(
      equipmentSwapData[0].cost,
      0,
      () => !equipmentSetsPurchased[1],
      () => upgradeEquipmentSwap(true)
    );

    //multitree
    shop["Multi-Tree"] = new ShopItem(
      multiTreeCost[0],
      0,
      () => treeCutLimit < 2,
      () => upgradeMultiTree(true)
    );

    //godupgrades
    godUpgradeData.forEach((upgrade, i) => {
      shop[upgrade.name] = new ShopItem(
        upgrade.cost,
        0,
        () => dungeonCompleteCount[upgrade.dungeonID] > 0 && !godUpgrade[i],
        () => buyGodUpgrade(i, true)
      );
    });

    //skill upgrades
    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)
    );

    //slayer equipment
    for (const itemID of slayerItems) {
      shop[items[itemID].name] = new ShopItem(
        0,
        items[itemID].slayerCost,
        () => true,
        () => buySlayerItem(itemID, true)
      );
    }

    //gloves
    gloveID.forEach((itemID, i) => {
      shop[items[itemID].name] = new ShopItem(
        glovesCost[i],
        0,
        () => true,
        () => buyGloves(i, true)
      );
    });

    //skillcapes
    skillcapeItems.forEach((itemID, i) => {
      shop[items[itemID].name] = new ShopItem(
        items[itemID].buysFor,
        0,
        () => true,
        () => buySkillcape(i, true)
      );
    });

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

  //add array of shop items
  for (const name in shop) actions["Buy Item"][name] = null;

  //add in sidebar item
  $("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>`
  );

  document.getElementsByClassName("nav-main-link").forEach((element) => {
    if (element.href && element.href.includes("changePage")) {
      element.addEventListener("click", () => hideActionTab());
    }
  });
  document.getElementsByClassName("btn btn-sm btn-light btn-combat-minibar-hp")[0].addEventListener("click", () => hideActionTab());

  //add main html
  document.getElementById("main-container").insertAdjacentHTML("beforeend", aqHTML);

  //fills category lists
  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>`));

  //load locally stored action queue if it exists
  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: "&amp;" },
  { reg: '"', replace: "&quot;" },
  { reg: "£", replace: "&pound;" },
  { reg: "€", replace: "&euro;" },
  { reg: "é", replace: "&eacute;" },
  { reg: "–", replace: "&ndash;" },
  { reg: "®", replace: "&reg;" },
  { reg: "™", replace: "&trade;" },
  { reg: "‘", replace: "&lsquo;" },
  { reg: "’", replace: "&rsquo;" },
  { reg: "“", replace: "&ldquo;" },
  { reg: "”", replace: "&rdquo;" },
  { reg: "#", replace: "&#35;" },
  { reg: "©", replace: "&copy;" },
  { reg: "@", replace: "&commat;" },
  { reg: "$", replace: "&dollar;" },
  { reg: "\\(", replace: "&#40;" },
  { reg: "\\)", replace: "&#41;" },
  { reg: "<", replace: "&lt;" },
  { reg: ">", replace: "&gt;" },
  { reg: "…", replace: "&hellip;" },
  { reg: "-", replace: "&#45;" },*/
  { reg: "'", replace: "&apos;" },
  /* { reg: "\\*", replace: "&#42;" },
  { reg: ",", replace: "&sbquo;" },*/
];

//replaces special characters with their html equivalent
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();
  }
}

/**
 * Updates colour and text in sidebar
 * @param {string} type ("start" || "stop" || "pause")
 */
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 () {
  queueLoop = !queueLoop;
  if (queueLoop) {
    document.getElementById("aq-loop").innerHTML = "Looping enabled";
  } else {
    document.getElementById("aq-loop").innerHTML = "Looping disabled";
  }
};

window.togglePause = function () {
  queuePause = !queuePause;
  if (queuePause) {
    clearInterval(triggerCheckInterval);
    triggerCheckInterval = null;
    document.getElementById("aq-pause").innerHTML = "Unpause";
    document.getElementById("aq-pause").style.backgroundColor = "#5a9e00";
    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").style.backgroundColor = "#e69621";
  }
};

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));
}

//autosave every ~minute
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();
};