Melvor Action Queue

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

10.10.2020 itibariyledir. En son verisyonu görün.

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