Melvor Action Queue

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

Versão de: 08/10/2020. Veja: a última versão.

// ==UserScript==
// @name         Melvor Action Queue
// @version      0.1.0
// @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;
const actionQueueArray = [];

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

function actionTab() {
  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;
  }
}

function hideActionTab() {
  if (isVisible) {
    document.getElementById("action-queue-container").style.display = "none";
    isVisible = false;
  }
}
const triggers = {
  "Item Quantity": {},
  "Skill Level": {},
  "Skill XP": {},
  "Equipped Item Quantity": {},
  "Mastery Level": {
    Cooking: {},
    Crafting: {},
    Farming: {},
    Firemaking: {},
    Fishing: {},
    Fletching: {},
    Herblore: {},
    Mining: {},
    Runecrafting: {},
    Smithing: {},
    Thieving: {},
    Woodcutting: {},
  },
};
const actions = {
  "Start Skill": {
    Cooking: {},
    Crafting: {},
    Firemaking: {},
    Fishing: {},
    Fletching: {},
    Herblore: {},
    Magic: {},
    Mining: {},
    Runecrafting: {},
    Smithing: {},
    Thieving: {},
    Woodcutting: {},
  },
  "Start Combat": {},
  "Switch Equipment Set": { 1: null, 2: null, 3: null },
  "Equip Item": {},
  "Unequip Item": {},
  "Buy Item": {},
  "Sell Item": {},
};
for (const a of items) {
  triggers["Item Quantity"][a.name] = { "≥": null, "≤": null };
  actions["Sell Item"][a.name] = null;
}
Object.keys(CONSTANTS.skill).forEach((a) => {
  triggers["Skill Level"][a] = null;
  triggers["Skill XP"][a] = null;
});
{
  cookingItems.forEach((item) => {
    triggers["Mastery Level"]["Cooking"][items[item.itemID].name] = null;
    actions["Start Skill"]["Cooking"][items[item.itemID].name] = null;
  });

  craftingItems.forEach((item) => {
    triggers["Mastery Level"]["Crafting"][items[item.itemID].name] = null;
    actions["Start Skill"]["Crafting"][items[item.itemID].name] = null;
  });

  items.forEach((item) => {
    if (item.type == "Seeds") {
      triggers["Mastery Level"]["Farming"][item.name] = null;
    }
  });

  items.forEach((item) => {
    if (item.type == "Logs") {
      triggers["Mastery Level"]["Firemaking"][item.name] = null;
      actions["Start Skill"]["Firemaking"][item.name] = null;
    }
  });

  fishingItems.forEach((item) => {
    triggers["Mastery Level"]["Fishing"][items[item.itemID].name] = null;
    actions["Start Skill"]["Fishing"][items[item.itemID].name] = null;
  });

  fletchingItems.forEach((item) => {
    triggers["Mastery Level"]["Fletching"][items[item.itemID].name] = null;
    actions["Start Skill"]["Fletching"][items[item.itemID].name] = null;
  });

  herbloreItemData.forEach((item) => {
    triggers["Mastery Level"]["Herblore"][item.name] = null;
    actions["Start Skill"]["Herblore"][item.name] = null;
  });

  miningData.forEach((item) => {
    triggers["Mastery Level"]["Mining"][items[item.ore].name] = null;
    actions["Start Skill"]["Mining"][items[item.ore].name] = null;
  });

  runecraftingItems.forEach((item) => {
    triggers["Mastery Level"]["Runecrafting"][items[item.itemID].name] = null;
    actions["Start Skill"]["Runecrafting"][items[item.itemID].name] = null;
  });

  smithingItems.forEach((item) => {
    triggers["Mastery Level"]["Smithing"][items[item.itemID].name] = null;
    actions["Start Skill"]["Smithing"][items[item.itemID].name] = null;
  });

  thievingNPC.forEach((npc) => {
    triggers["Mastery Level"]["Thieving"][npc.name] = null;
    actions["Start Skill"]["Thieving"][npc.name] = null;
  });

  items.forEach((item) => {
    if (item.type == "Logs") {
      triggers["Mastery Level"]["Woodcutting"][item.name] = null;
      actions["Start Skill"]["Woodcutting"][item.name] = {};
    }
  });
  for (const log in actions["Start Skill"]["Woodcutting"]) {
    Object.keys(actions["Start Skill"]["Woodcutting"]).forEach((a) => (actions["Start Skill"]["Woodcutting"][log][a] = null));
  }
}
ALTMAGIC.forEach((spell) => {
  actions["Start Skill"]["Magic"][spell.name] = {};
  if (spell.selectItem === 0) {
    for (item of smithingItems) {
      if (item.category === 0) {
        actions["Start Skill"]["Magic"][spell.name][items[item.itemID].name] = null;
      }
    }
  } else if (spell.isJunk) {
    junkItems.forEach((a) => (actions["Start Skill"]["Magic"][spell.name][items[a].name] = null));
  } else if (spell.selectItem === 1) {
    actions["Start Skill"]["Magic"][spell.name] = actions["Sell Item"];
  } else actions["Start Skill"]["Magic"][spell.name] = null;
});
for (const a of items.filter((a) => a.canEat)) {
  triggers["Equipped Item Quantity"][a.name] = null;
  actions["Equip Item"][a.name] = null;
}
for (const a of items.filter((a) => a.equipmentSlot == CONSTANTS.equipmentSlot.Quiver)) triggers["Equipped Item Quantity"][a.name] = null;
{
  const monsterIDs = [];
  for (const area of combatAreas) monsterIDs.push(...area.monsters);
  for (const area of slayerAreas) monsterIDs.push(...area.monsters);
  for (const monster of monsterIDs) actions["Start Combat"][MONSTERS[monster].name] = null;
  for (const dungeon of DUNGEONS) actions["Start Combat"][dungeon.name] = null;
}
for (const a of items.filter((a) => a.hasOwnProperty("equipmentSlot"))) {
  actions["Equip Item"][a.name] = null;
  actions["Unequip Item"][a.name] = null;
}

class ShopItem {
  constructor(gp, slayerCoins, req, buy) {
    this.gp = gp;
    this.slayerCoins = slayerCoins;
    this.req = req;
    this.buy = buy;
  }
}
ShopItem.prototype.cost = function () {
  return gp >= this.gp && slayerCoins >= this.slayerCoins && baseBankMax + bankMax > bank.length;
};
const shop = {};
{
  shop["Upgrade Bank"] = new ShopItem(
    0,
    0,
    () => {
      if (currentGamemode === 1 && baseBankMax + bankMax >= 80) return false;
      return gp >= Math.min(newNewBankUpgradeCost.level_to_gp(currentBankUpgrade + 1), 5000000);
    },
    () => upgradeBank(true)
  );
  shop["Upgrade Auto Eat"] = new ShopItem(
    0,
    0,
    () => currentAutoEat < 3 && gp >= autoEatData[currentAutoEat].cost,
    () => upgradeAutoEat(true)
  );
  shop["Equipment Set (gp)"] = new ShopItem(
    equipmentSetData[0].cost,
    0,
    () => !equipmentSetsPurchased[0],
    () => upgradeEquipmentSet(0, true)
  );
  shop["Equipment Set (slayer coins)"] = new ShopItem(
    0,
    equipmentSetData[1].cost,
    () => !equipmentSetsPurchased[1],
    () => upgradeEquipmentSet(1, true)
  );
  shop["Dungeon Equipment Swapping"] = new ShopItem(
    equipmentSwapData[0].cost,
    0,
    () => !equipmentSetsPurchased[1],
    () => upgradeEquipmentSwap(true)
  );
  shop["Multi-Tree"] = new ShopItem(
    multiTreeCost[0],
    0,
    () => treeCutLimit < 2,
    () => upgradeMultiTree(true)
  );
  godUpgradeData.forEach((upgrade, i) => {
    shop[upgrade.name] = new ShopItem(
      upgrade.cost,
      0,
      () => dungeonCompleteCount[upgrade.dungeonID] > 0 && !godUpgrade[i],
      () => buyGodUpgrade(i, true)
    );
  });
  shop["Upgrade Axe"] = new ShopItem(
    0,
    0,
    () => currentAxe < 7 && gp >= axeCost[currentAxe + 1],
    () => upgradeAxe(true)
  );

  shop["Upgrade Rod"] = new ShopItem(
    0,
    0,
    () => currentRod < 7 && gp >= rodCost[currentRod + 1],
    () => upgradeRod(true)
  );

  shop["Upgrade Pickaxe"] = new ShopItem(
    0,
    0,
    () => currentPickaxe < 7 && gp >= pickaxeCost[currentPickaxe + 1],
    () => upgradePickaxe(true)
  );

  shop["Upgrade Cooking Fire"] = new ShopItem(
    0,
    0,
    () => {
      return (
        currentCookingFire < 9 &&
        gp >= cookingFireData[currentCookingFire].costGP &&
        skillLevel[CONSTANTS.skill.Firemaking] >= cookingFireData[currentCookingFire].fmLevel &&
        getBankQty(cookingFireData[currentCookingFire].costLogs[0]) >= cookingFireData[currentCookingFire].costLogs[1]
      );
    },
    () => upgradeCookingFire(true)
  );
  for (const itemID of slayerItems) {
    shop[items[itemID].name] = new ShopItem(
      0,
      items[itemID].slayerCost,
      () => true,
      () => buySlayerItem(itemID, true)
    );
  }
  gloveID.forEach((itemID, i) => {
    shop[items[itemID].name] = new ShopItem(
      glovesCost[i],
      0,
      () => true,
      () => buyGloves(i, true)
    );
  });
  skillcapeItems.forEach((itemID, i) => {
    shop[items[itemID].name] = new ShopItem(
      items[itemID].buysFor,
      0,
      () => true,
      () => buySkillcape(i, true)
    );
  });
  shop[items[CONSTANTS.item.Feathers].name] = new ShopItem(
    0,
    0,
    (qty) => gp >= items[CONSTANTS.item.Feathers].buysFor * qty,
    (qty) => {
      updateBuyQty(qty);
      buyFeathers(true);
    }
  );

  shop[items[CONSTANTS.item.Compost].name] = new ShopItem(
    0,
    0,
    (qty) => gp >= items[CONSTANTS.item.Compost].buysFor * qty,
    (qty) => {
      updateBuyQty(qty);
      buyCompost(true);
    }
  );

  shop[items[CONSTANTS.item.Weird_Gloop].name] = new ShopItem(
    0,
    0,
    (qty) => {
      for (const a of items[CONSTANTS.item.Weird_Gloop].buysForItems) {
        if (getBankQty(a[0]) < qty * a[1]) return false;
      }
      return gp >= items[CONSTANTS.item.Weird_Gloop].buysFor * qty;
    },
    (qty) => {
      updateBuyQty(qty);
      buyItem(CONSTANTS.item.Weird_Gloop, true);
    }
  );

  shop[items[CONSTANTS.item.Bowstring].name] = new ShopItem(
    0,
    0,
    (qty) => gp >= items[CONSTANTS.item.Bowstring].buysFor * qty,
    (qty) => {
      updateBuyQty(qty);
      buyBowstring(true);
    }
  );

  shop[items[CONSTANTS.item.Leather].name] = new ShopItem(
    0,
    0,
    (qty) => gp >= items[CONSTANTS.item.Leather].buysFor * qty,
    (qty) => {
      updateBuyQty(qty);
      buyLeather(true);
    }
  );

  [CONSTANTS.item.Green_Dragonhide, CONSTANTS.item.Blue_Dragonhide, CONSTANTS.item.Red_Dragonhide].forEach((itemID) => {
    shop[items[itemID].name] = new ShopItem(
      0,
      0,
      (qty) => gp >= items[itemID].buysFor * qty,
      (qty) => {
        updateBuyQty(qty);
        buyDhide(itemID, true);
      }
    );
  });

  shop[items[CONSTANTS.item.Red_Party_Hat].name] = new ShopItem(
    0,
    0,
    (qty) => gp >= items[CONSTANTS.item.Red_Party_Hat].buysFor * qty,
    (qty) => {
      updateBuyQty(qty);
      buyPartyHat(true);
    }
  );
}
for (const name in shop) actions["Buy Item"][name] = null;

function setTrigger(catagory, name, greaterThan, masteryItem, number) {
  const itemID = items.findIndex((a) => a.name == name);
  number = parseInt(number, 10);
  switch (catagory) {
    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 >= number;
      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(actionCatagory, actionName, skillItem, skillItem2, qty) {
  qty = parseInt(qty, 10);
  const itemID = items.findIndex((a) => a.name == actionName);
  switch (actionCatagory) {
    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 fishID = fishingItems.find((a) => a.itemID == itemID).fishingID;
      const areaID = fishingAreas.findIndex((a) => a.fish.includes(fishID));
      return () => {
        if (
          (!equippedItems.includes(CONSTANTS.item.Barbarian_Gloves) && areaID == 6) ||
          (!secretAreaUnlocked && areaID == 7) ||
          !skillLevel[CONSTANTS.skill.Fishing] >= fishingItems[fishID].fishingLevel
        )
          return false;
        if (!isFishing) {
          selectFish(areaID, fishID);
          startFishing(areaID, fishID, true);
        }
        if (isFishing && (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 {
  constructor(catagory, name, greaterThan, masteryItem, number, actionCatagory, actionName, skillItem, skillItem2, qty) {
    switch (catagory) {
      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 (actionCatagory) {
      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.elementID = `AQ${nameIncrement}`;
    nameIncrement++;
    this.trigger = setTrigger(catagory, name, greaterThan, masteryItem, number);
    this.startAction = setAction(actionCatagory, 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];
}

function submitForm(event) {
  const arr = [];
  for (let i = 0; i < 10; i++) {
    let e = event.target.elements[`aq-${i}`].value;
    if (document.getElementById(`aq-${i}`).type == "text" && !validateOption(i, e)) return false;
    e == "" ? arr.push(null) : arr.push(e);
  }
  if (arr[9] === null) arr[9] = 1;
  addToQueue(new Action(...arr));
  resetForm();
  return false;
}
function validateOption(number, value) {
  const list = document.getElementById(`aq-${number}`).getAttribute("list");
  if (list === null) return true;
  for (const a of document.getElementById(list).options) {
    if (a.value === value) return true;
  }
  return false;
}

let triggerTier = [null, null, null];
let actionTier = [null, null, null, null];
function dropdowns(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-2`).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]] !== 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";
          }
        }
      }
  }
}
$("li.nav-main-item:contains(Bank)")[0].insertAdjacentHTML(
  "afterend",
  `
<li class="nav-main-item">
  <a class="nav-main-link nav-compact" onclick="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.includes("changePage")) {
    element.addEventListener("click", () => hideActionTab());
  }
});
document.getElementsByClassName("btn btn-sm btn-light btn-combat-minibar-hp")[0].addEventListener("click", () => hideActionTab());

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-catagory"
                  placeholder="Catagory"
                  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-catagory"
                  placeholder="Catagory"
                  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" style="background-color: #0083ff" 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" style="background-color: #676767" 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-catagory"></datalist>
<datalist id="aq-list-0"></datalist>
<datalist id="aq-list-1"></datalist>
<datalist id="aq-list-2"></datalist>
<datalist id="action-catagory"></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;
}
</style>
`;

document.getElementById("main-container").insertAdjacentHTML("beforeend", aqHTML);
Object.keys(triggers).forEach((a) => document.getElementById("trigger-catagory").insertAdjacentHTML("beforeend", `<option>${a}</option>`));
Object.keys(actions).forEach((a) => document.getElementById("action-catagory").insertAdjacentHTML("beforeend", `<option>${a}</option>`));

function deleteAction(id) {
  const i = actionQueueArray.findIndex((a) => a.elementID == id);
  if (i < 0) return;
  actionQueueArray.splice(i, 1);
  if (currentActionIndex > i) currentActionIndex--;
  const element = document.getElementById(id);
  if (element) element.remove();
  updateQueue();
}

const replaceChar = [
  { reg: "'", replace: "&apos;" },
];
function htmlChar(string) {
  let s = string;
  replaceChar.forEach(function (obj) {
    const regEx = new RegExp(obj.reg + "(?!([^<]+)?>)", "g");
    s = s.replace(regEx, obj.replace);
  });
  return s;
}

function addToQueue(obj) {
  actionQueueArray.push(obj);
  const element = `<div class="aq-item" id="${obj.elementID}">
  <p style="margin: auto 0;">${obj.description}</p>
  <div>
    <small style="display: none; margin: auto;">failed requirement</small>
   <button type="button" class="btn btn-sm btn-danger m-1" onclick="deleteAction('${obj.elementID}')">delete</button>
  </div>
</div>`;
  document.getElementById("aq-item-container").insertAdjacentHTML("beforeend", element);
  if (triggerCheckInterval === null && !queuePause) {
    currentActionIndex = 0;
    updateQueue();
    triggerCheckInterval = setInterval(() => {
      triggerCheck();
    }, 1000);
    updateTextColour("start");
  }
}

function triggerCheck() {
  let result = true;
  if (currentActionIndex >= actionQueueArray.length) {
    if (queueLoop && actionQueueArray.length > 0) {
      currentActionIndex = 0;
      updateQueue();
      return;
    } else {
      clearInterval(triggerCheckInterval);
      triggerCheckInterval = null;
      updateTextColour("stop");
      return;
    }
  }
  if (actionQueueArray[currentActionIndex].trigger()) {
    result = actionQueueArray[currentActionIndex].startAction();
    document.getElementById(actionQueueArray[currentActionIndex].elementID).children[1].children[0].style.display = result ? "none" : "";
    currentActionIndex + 1 >= actionQueueArray.length && queueLoop ? (currentActionIndex = 0) : currentActionIndex++;
    updateQueue();
  }
}
function updateTextColour(type) {
  switch (type) {
    case "start":
      document.getElementById("current-queue").style.color = "#46c37b";
      document.getElementById("current-queue").innerHTML = "running";
      break;
    case "stop":
      document.getElementById("current-queue").style.color = "#d26a5c";
      document.getElementById("current-queue").innerHTML = "inactive";
      break;
    case "pause":
      document.getElementById("current-queue").style.color = "#f3b760";
      document.getElementById("current-queue").innerHTML = "paused";
  }
}

function updateQueue() {
  actionQueueArray.forEach((action, index) => {
    if (index === currentActionIndex) {
      document.getElementById(action.elementID).style.backgroundColor = "#385a0b";
    } else document.getElementById(action.elementID).style.backgroundColor = "";
  });
}

function toggleLoop() {
  queueLoop = !queueLoop;
  if (queueLoop) {
    document.getElementById("aq-loop").innerHTML = "Looping enabled";
  } else {
    document.getElementById("aq-loop").innerHTML = "Looping disabled";
  }
}

function togglePause() {
  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";
  }
}