Guest User

Melvor Action Queue update

a guest
Mar 11th, 2022
83
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         Melvor Action Queue
  3. // @version      1.2.0
  4. // @description  Adds an interface to queue up actions based on triggers you set
  5. // @author       8992
  6. // @match        https://*.melvoridle.com/*
  7. // @exclude      https://wiki.melvoridle.com/*
  8. // @grant        none
  9. // @namespace    http://tampermonkey.net/
  10. // @noframes
  11. // ==/UserScript==
  12.  
  13. let isVisible = false;
  14. let currentActionIndex = 0;
  15. let triggerCheckInterval = null;
  16. let nameIncrement = 0;
  17. let queueLoop = false;
  18. let queuePause = false;
  19. let manageMasteryInterval = null;
  20. let masteryConfigChanges = { skill: null };
  21. const lvlIndex = {};
  22. const masteryClone = {};
  23. const masteryConfig = {};
  24. const actionQueueArray = [];
  25. const shop = {};
  26. const tooltips = {};
  27. const validInputs = {
  28.   A: [null, null, null],
  29.   B: [null, null, null, null],
  30.   C: [null, null, null, null],
  31. };
  32. let currentlyEditing = { id: null, type: null };
  33. const pageIndex = {
  34.   Woodcutting: 0,
  35.   Fishing: 7,
  36.   Firemaking: 8,
  37.   Cooking: 9,
  38.   Mining: 10,
  39.   Smithing: 11,
  40.   Combat: 13,
  41.   Thieving: 14,
  42.   Farming: 15,
  43.   Fletching: 16,
  44.   Crafting: 17,
  45.   Runecrafting: 18,
  46.   Herblore: 19,
  47.   Agility: 20,
  48.   Summoning: 28,
  49.   Astrology: 31,
  50. };
  51.  
  52. function checkAmmoQty(id) {
  53.   const set = player.equipmentSets.find((a) => a.slots.Quiver.item.id == id);
  54.   return set ? set.slots.Quiver.quantity : 0;
  55. }
  56.  
  57. function checkFoodQty(id) {
  58.   const food = player.food.slots.find((a) => a.item.id == id);
  59.   return food ? food.quantity : 0;
  60. }
  61.  
  62. function actionTab() {
  63.   if (!isVisible) {
  64.     changePage(3);
  65.     $("#settings-container").attr("class", "content d-none");
  66.     $("#header-title").text("Action Queue");
  67.     $("#header-icon").attr("src", "assets/media/skills/prayer/mystic_lore.svg");
  68.     $("#header-theme").attr("class", "content-header bg-combat");
  69.     $("#page-header").attr("class", "bg-combat");
  70.     document.getElementById("action-queue-container").style.display = "";
  71.     isVisible = true;
  72.   }
  73. }
  74.  
  75. function hideActionTab() {
  76.   if (isVisible) {
  77.     document.getElementById("action-queue-container").style.display = "none";
  78.     isVisible = false;
  79.   }
  80. }
  81.  
  82. const options = {
  83.   //trigger options for dropdown menus
  84.   triggers: {
  85.     Idle: null,
  86.     "Item Quantity": {},
  87.     "Skill Level": {},
  88.     "Skill XP": {},
  89.     "Mastery Level": {
  90.       Agility: {},
  91.       Astrology: {},
  92.       Cooking: {},
  93.       Crafting: {},
  94.       Farming: {},
  95.       Firemaking: {},
  96.       Fishing: {},
  97.       Fletching: {},
  98.       Herblore: {},
  99.       Mining: {},
  100.       Runecrafting: {},
  101.       Smithing: {},
  102.       Summoning: {},
  103.       Thieving: {},
  104.       Woodcutting: {},
  105.     },
  106.     "Mastery Pool %": {},
  107.     "Pet Unlocked": {},
  108.     "Equipped Item Quantity": {},
  109.     "Prayer Points": { "≥": "num", "≤": "num" },
  110.     "Potion Depleted": {
  111.       Agility: null,
  112.       Astrology: null,
  113.       Combat: null,
  114.       Cooking: null,
  115.       Crafting: null,
  116.       Farming: null,
  117.       Firemaking: null,
  118.       Fishing: null,
  119.       Fletching: null,
  120.       Herblore: null,
  121.       Mining: null,
  122.       Runecrafting: null,
  123.       Smithing: null,
  124.       Summoning: null,
  125.       Thieving: null,
  126.       Woodcutting: null,
  127.     },
  128.     "Enemy in Combat": {},
  129.   },
  130.   //action options for dropdown menus
  131.   actions: {
  132.     "Start Skill": {
  133.       Agility: null,
  134.       Astrology: {},
  135.       Cooking: {},
  136.       Crafting: {},
  137.       Firemaking: {},
  138.       Fishing: {},
  139.       Fletching: {},
  140.       Herblore: {},
  141.       Magic: {},
  142.       Mining: {},
  143.       Runecrafting: {},
  144.       Smithing: {},
  145.       Summoning: {},
  146.       Thieving: {},
  147.       Woodcutting: {},
  148.     },
  149.     "Start Combat": {
  150.       "Slayer Task": null,
  151.     },
  152.     "Change Attack Style": { "Select Spell": { Normal: {}, Curse: {}, Aurora: {}, Ancient: {} } },
  153.     "Switch Equipment Set": { 1: null, 2: null, 3: null, 4: null },
  154.     "Equip Item": {},
  155.     "Equip Passive": {},
  156.     "Unequip Item": {},
  157.     "Buy Item": {},
  158.     "Sell Item": {},
  159.     "Use Potion": {},
  160.     "Activate Prayers": {},
  161.     "Build Agility Obstacle": {},
  162.     "Remove Agility Obstacle": {},
  163.   },
  164. };
  165.  
  166. function setTrigger(category, name, greaterThan, masteryItem, number) {
  167.   const itemID = items.findIndex((a) => a.name == name);
  168.   number = parseInt(number, 10);
  169.   switch (category) {
  170.     case "Idle":
  171.       return () => {
  172.         return !combatManager.isInCombat && offline.skill == null;
  173.       };
  174.     case "Item Quantity":
  175.       if (greaterThan == "≥") {
  176.         return () => {
  177.           return getBankQty(itemID) >= number;
  178.         };
  179.       }
  180.       return () => {
  181.         return getBankQty(itemID) <= number;
  182.       };
  183.     case "Prayer Points":
  184.       if (name == "≥") {
  185.         return () => {
  186.           return player.prayerPoints >= number;
  187.         };
  188.       }
  189.       return () => {
  190.         return player.prayerPoints <= number;
  191.       };
  192.     case "Skill Level": {
  193.       const xp = exp.level_to_xp(number);
  194.       return () => {
  195.         return skillXP[CONSTANTS.skill[name]] >= xp;
  196.       };
  197.     }
  198.     case "Skill XP":
  199.       return () => {
  200.         return skillXP[CONSTANTS.skill[name]] >= number;
  201.       };
  202.     case "Equipped Item Quantity":
  203.       if (items.filter((a) => a.canEat).find((a) => a.name == name)) {
  204.         return () => {
  205.           return checkFoodQty(itemID) <= number;
  206.         };
  207.       }
  208.       if (items.filter((a) => a.type == "Familiar").find((a) => a.name == name)) {
  209.         return () => {
  210.           let summonSlots = player.equipment.slotArray.slice(-2);
  211.           let slot = summonSlots.findIndex((a) => a.item.id == itemID);
  212.           return (slot >= 0 ? summonSlots[slot].quantity : 0) <= number;
  213.         };
  214.       }
  215.       return () => {
  216.         return checkAmmoQty(itemID) <= number;
  217.       };
  218.     case "Mastery Level":
  219.       let masteryID = fetchMasteryID(name, masteryItem);
  220.       return () => {
  221.         return getMasteryLevel(CONSTANTS.skill[name], masteryID) >= number;
  222.       };
  223.     case "Pet Unlocked": {
  224.       const petID = PETS.findIndex((pet) => {
  225.         const re = new RegExp(`^${name.split(" (")[0]}`, "g");
  226.         return re.test(pet.name);
  227.       });
  228.       return () => {
  229.         return petUnlocked[petID];
  230.       };
  231.     }
  232.     case "Mastery Pool %": {
  233.       const skill = CONSTANTS.skill[name];
  234.       return () => {
  235.         return getMasteryPoolProgress(skill) >= number;
  236.       };
  237.     }
  238.     case "Potion Depleted":
  239.       return () => {
  240.         return herbloreBonuses[pageIndex[name]].charges <= 0;
  241.       };
  242.     case "Enemy in Combat": {
  243.       const monsterID = MONSTERS.findIndex((a) => a.name == name);
  244.       return () => {
  245.         return combatManager.isInCombat && combatManager.selectedMonster == monsterID;
  246.       };
  247.     }
  248.   }
  249. }
  250.  
  251. function fetchMasteryID(skillName, itemName) {
  252.   const itemID = items.findIndex((a) => a.name == itemName);
  253.   let masteryID = itemID >= 0 && items[itemID].masteryID ? items[itemID].masteryID[1] : null;
  254.   switch (skillName) {
  255.     case "Cooking":
  256.       masteryID = Cooking.recipes.find((a) => a.itemID == itemID).masteryID;
  257.       break;
  258.     case "Herblore":
  259.       masteryID = Herblore.potions.find((a) => a.name == itemName).masteryID;
  260.       break;
  261.     case "Thieving":
  262.       masteryID = Thieving.npcs.find((a) => a.name == itemName).id;
  263.       break;
  264.     case "Agility":
  265.       masteryID = Agility.obstacles.findIndex((a) => a.name == itemName);
  266.       break;
  267.     case "Astrology":
  268.       masteryID = Astrology.constellations.findIndex((a) => a.name == itemName);
  269.       break;
  270.     case "Mining":
  271.       masteryID = Mining.rockData.findIndex((a) => a.name == itemName);
  272.       break;
  273.   }
  274.   return masteryID;
  275. }
  276.  
  277. function setAction(actionCategory, actionName, skillItem, skillItem2, qty) {
  278.   qty = parseInt(qty, 10);
  279.   const itemID = items.findIndex((a) => a.name == actionName);
  280.   switch (actionCategory) {
  281.     case "Start Skill":
  282.       return setSkillAction(actionName, skillItem, skillItem2);
  283.     case "Start Combat": {
  284.       //slayer task selection
  285.       if (actionName == "Slayer Task") {
  286.         return () => {
  287.           if (combatManager.slayerTask.killsLeft > 0) {
  288.             const mID = combatManager.slayerTask.monster.id;
  289.             const areaData = [...areaMenus.combat.areas, ...areaMenus.slayer.areas].find((a) =>
  290.               a.monsters.includes(mID)
  291.             );
  292.             if (combatManager.isInCombat && combatManager.selectedMonster == mID) return true;
  293.             combatManager.stopCombat();
  294.             combatManager.selectMonster(mID, areaData);
  295.             return true;
  296.           }
  297.           return false;
  298.         };
  299.       }
  300.       //dungeon selection
  301.       const dungeonIndex = DUNGEONS.findIndex((a) => a.name == actionName);
  302.       if (dungeonIndex >= 0) {
  303.         return () => {
  304.           if (
  305.             DUNGEONS[dungeonIndex].requiresCompletion === undefined ||
  306.             dungeonCompleteCount[DUNGEONS[dungeonIndex].requiresCompletion] >= 1
  307.           ) {
  308.             combatManager.selectDungeon(dungeonIndex);
  309.             return true;
  310.           }
  311.           return false;
  312.         };
  313.       }
  314.       //regular monster selection
  315.       const monsterIndex = MONSTERS.findIndex((a) => a.name == actionName);
  316.       const areaData = [...areaMenus.combat.areas, ...areaMenus.slayer.areas].find((a) =>
  317.         a.monsters.includes(monsterIndex)
  318.       );
  319.       return () => {
  320.         if (checkRequirements(areaData.entryRequirements)) {
  321.           if (!(combatManager.isInCombat && combatManager.selectedMonster == monsterIndex))
  322.             combatManager.selectMonster(monsterIndex, areaData);
  323.           return true;
  324.         }
  325.         return false;
  326.       };
  327.     }
  328.     case "Change Attack Style": {
  329.       if (actionName == "Select Spell") {
  330.         switch (skillItem) {
  331.           case "Normal":
  332.             return () => {
  333.               const spellID = SPELLS.findIndex((a) => a.name == skillItem2);
  334.               //check level req
  335.               if (skillLevel[CONSTANTS.skill.Magic] < SPELLS[spellID].magicLevelRequired) return false;
  336.               player.toggleSpell(spellID);
  337.               return true;
  338.             };
  339.           case "Curse":
  340.             return () => {
  341.               const spellID = CURSES.findIndex((a) => a.name == skillItem2);
  342.               //check level req
  343.               if (skillLevel[CONSTANTS.skill.Magic] < CURSES[spellID].magicLevelRequired) return false;
  344.               player.toggleCurse(spellID);
  345.               return true;
  346.             };
  347.           case "Aurora":
  348.             return () => {
  349.               const spellID = AURORAS.findIndex((a) => a.name == skillItem2);
  350.               //check level req
  351.               if (skillLevel[CONSTANTS.skill.Magic] < AURORAS[spellID].magicLevelRequired) return false;
  352.               //check item req
  353.               if (
  354.                 AURORAS[spellID].requiredItem >= 0 &&
  355.                 !player.equipment.slotArray.map((a) => a.item.id).includes(AURORAS[spellID].requiredItem)
  356.               )
  357.                 return false;
  358.               player.toggleAurora(spellID);
  359.               return true;
  360.             };
  361.           case "Ancient":
  362.             return () => {
  363.               const spellID = ANCIENT.findIndex((a) => a.name == skillItem2);
  364.               //check level req
  365.               if (skillLevel[CONSTANTS.skill.Magic] < ANCIENT[spellID].magicLevelRequired) return false;
  366.               //check dungeon req
  367.               if (
  368.                 dungeonCompleteCount[ANCIENT[spellID].requiredDungeonCompletion[0]] <
  369.                 ANCIENT[spellID].requiredDungeonCompletion[1]
  370.               )
  371.                 return false;
  372.               player.toggleSpellAncient(spellID);
  373.               return true;
  374.             };
  375.         }
  376.       }
  377.       const style = CONSTANTS.attackStyle[actionName];
  378.       return () => {
  379.         if (player.attackType == "magic") {
  380.           if (style < 6) return false;
  381.         } else if (player.attackType === "ranged") {
  382.           if (style > 5 || style < 3) return false;
  383.         } else {
  384.           if (style > 2) return false;
  385.         }
  386.         player.setAttackStyle(player.attackType, actionName);
  387.         return true;
  388.       };
  389.     }
  390.     case "Switch Equipment Set": {
  391.       const equipSet = parseInt(actionName) - 1;
  392.       return () => {
  393.         if (player.equipmentSets.length > equipSet) {
  394.           player.changeEquipmentSet(equipSet);
  395.           return true;
  396.         }
  397.         return false;
  398.       };
  399.     }
  400.     case "Equip Item":
  401.       return () => {
  402.         const bankID = getBankId(itemID);
  403.         if (bankID === -1) return false;
  404.         if (sellItemMode) toggleSellItemMode();
  405.         if (items[itemID].canEat) {
  406.           selectBankItem(itemID);
  407.           equipFoodQty = bank[bankID].qty;
  408.           equipFood();
  409.           return getBankQty(itemID) == 0; //returns false if there is any of the item left in bank (couldn't equip)
  410.         }
  411.         if (items[itemID].type == "Familiar") {
  412.           let slot = player.equipment.slotArray.slice(-3).findIndex((a) => a.item.id == itemID);
  413.           if (slot >= 0) {
  414.             player.equipItem(itemID, player.selectedEquipmentSet, `Summon${slot}`, bank[bankID].qty);
  415.           } else if (player.equipment.slotArray[12].quantity == 0) {
  416.             player.equipItem(itemID, player.selectedEquipmentSet, "Summon1", bank[bankID].qty);
  417.           } else if (player.equipment.slotArray[13].quantity == 0) {
  418.             player.equipItem(itemID, player.selectedEquipmentSet, "Summon2", bank[bankID].qty);
  419.           }
  420.           return getBankQty(itemID) == 0;
  421.         }
  422.         if (items[itemID].validSlots[0] == "Quiver") {
  423.           player.equipItem(itemID, player.selectedEquipmentSet, "Quiver", bank[bankID].qty);
  424.           return getBankQty(itemID) == 0; //returns false if there is any of the item left in bank (couldn't equip)
  425.         }
  426.         player.equipItem(itemID, player.selectedEquipmentSet);
  427.         return player.equipment.slots[items[itemID].validSlots[0]].item.id === itemID; //returns false if the item is not equipped
  428.       };
  429.     case "Equip Passive":
  430.       return () => {
  431.         if (dungeonCompleteCount[15] < 1 || getBankId(itemID) === -1) return false;
  432.         player.equipCallback(itemID, "Passive");
  433.         return player.equipment.slots.Passive.item.id === itemID;
  434.       };
  435.     case "Unequip Item": {
  436.       return () => {
  437.         for (const i in player.equipment.slots) {
  438.           if (player.equipment.slots[i].item.id == itemID) {
  439.             player.unequipCallback(i)();
  440.             return getBankQty(itemID) > 0; //returns false if there is 0 of the items in bank (no space to unequip)
  441.           }
  442.         }
  443.         return false;
  444.       };
  445.     }
  446.     case "Buy Item":
  447.       return () => {
  448.         return shop[actionName].buy(qty);
  449.       };
  450.     case "Sell Item":
  451.       return () => {
  452.         if (!bank.find((a) => a.id == itemID)) return false;
  453.         if (sellItemMode) toggleSellItemMode();
  454.         selectBankItem(itemID);
  455.         sellItem();
  456.         if (showSaleNotifications) swal.clickConfirm();
  457.         return true;
  458.       };
  459.     case "Use Potion":
  460.       return () => {
  461.         if (!bank.find((a) => a.id == itemID)) return false;
  462.         usePotion(itemID);
  463.         return true;
  464.       };
  465.     case "Activate Prayers": {
  466.       let choice = [];
  467.       if (actionName != "None") {
  468.         choice.push(PRAYER.findIndex((a) => a.name == actionName));
  469.         if (skillItem != "None") {
  470.           choice.push(PRAYER.findIndex((a) => a.name == skillItem));
  471.         }
  472.       }
  473.       return () => {
  474.         const validPrayers = choice.filter((a) => skillLevel[CONSTANTS.skill.Prayer] > PRAYER[a].prayerLevel);
  475.         changePrayers(validPrayers);
  476.         return true;
  477.       };
  478.     }
  479.     case "Build Agility Obstacle": {
  480.       const obstacleID = Agility.obstacles.findIndex((a) => a.name == skillItem);
  481.       const obstacleNumber = parseInt(actionName) - 1;
  482.       return () => {
  483.         if (chosenAgilityObstacles[obstacleNumber] == obstacleID) return true;
  484.         if (
  485.           !canIAffordThis(
  486.             Agility.obstacles[obstacleID].cost,
  487.             Agility.obstacles[obstacleID].skillRequirements,
  488.             obstacleID
  489.           )
  490.         )
  491.           return false;
  492.         if (chosenAgilityObstacles[obstacleNumber] >= 0) destroyAgilityObstacle(obstacleNumber, true);
  493.         buildAgilityObstacle(obstacleID, true);
  494.         return true;
  495.       };
  496.     }
  497.     case "Remove Agility Obstacle": {
  498.       const obstacleNumber = parseInt(actionName) - 1;
  499.       return () => {
  500.         if (chosenAgilityObstacles[obstacleNumber] >= 0) destroyAgilityObstacle(obstacleNumber, true);
  501.         return true;
  502.       };
  503.     }
  504.   }
  505. }
  506.  
  507. function setSkillAction(actionName, skillItem, skillItem2) {
  508.   const itemID = items.findIndex((a) => a.name == skillItem);
  509.   let actionID = 0;
  510.   switch (actionName) {
  511.     case "Agility":
  512.       return () => {
  513.         if (game.activeSkill !== globalThis.ActiveSkills.AGILITY) game.agility.start();
  514.         return true;
  515.       };
  516.     case "Astrology": {
  517.       const constellation = Astrology.constellations.find((a) => a.name == skillItem); // Find constellation
  518.       return () => {
  519.         if (skillLevel[CONSTANTS.skill.Astrology] < constellation.level) return false;
  520.         if (game.activeSkill !== globalThis.ActiveSkills.ASTROLOGY || game.astrology.activeConstellation.id != constellation.id) game.astrology.studyConstellationOnClick(constellation.id);
  521.         return true;
  522.       };
  523.     }
  524.     case "Cooking": {
  525.       const itemID = items.findIndex((a) => a.name == skillItem2);
  526.       const category = Cooking.recipes.find((a) => a.itemID == itemID).cookingCategory;
  527.       if (skillItem == "Active") {
  528.         return () => {
  529.           [0, 1, 2].forEach((category) => collectFromStockpile(category));
  530.           if (skillLevel[CONSTANTS.skill.Cooking] < Cooking.recipes.find((a) => a.itemID == itemID).level)
  531.             return false;
  532.           if (offline.skill == CONSTANTS.skill.Cooking && offline.action.active == itemID) return true;
  533.           selectCookingRecipe(category, itemID);
  534.           toggleActiveCook(category);
  535.           const passives = passiveCooking.reduce((arr, item, index) => {
  536.             return index != category && item >= 0 ? [...arr, index] : arr;
  537.           }, []);
  538.           passives.forEach((a) => togglePassiveCook(a));
  539.           return true;
  540.         };
  541.       } else {
  542.         return () => {
  543.           [0, 1, 2].forEach((category) => collectFromStockpile(category));
  544.           if (skillLevel[CONSTANTS.skill.Cooking] < Cooking.recipes.find((a) => a.itemID == itemID).level) return false;
  545.           if (passiveCooking[category] == itemID) return true;
  546.           selectCookingRecipe(category, itemID);
  547.           togglePassiveCook(category);
  548.           return true;
  549.         };
  550.       }
  551.     }
  552.     case "Crafting":
  553.       actionID = Crafting.recipes.find((a) => a.itemID == itemID).masteryID;
  554.       return () => {
  555.         if (skillLevel[CONSTANTS.skill.Crafting] < Crafting.recipes[actionID].level) return false;
  556.         if (game.crafting.selectedRecipeID !== actionID) game.crafting.selectRecipeOnClick(actionID);
  557.         if (game.activeSkill !== globalThis.ActiveSkills.CRAFTING) game.crafting.start();
  558.         return true;
  559.       };
  560.     case "Firemaking":
  561.       actionID = Firemaking.recipes.findIndex((x) => x.logID == itemID);
  562.       return () => {
  563.         if (skillLevel[CONSTANTS.skill.Firemaking] < Firemaking.recipes[actionID].levelRequired) return false;
  564.         if (!game.firemaking.activeRecipe || game.firemaking.activeRecipe.logID !== actionID)
  565.           game.firemaking.selectLog(actionID);
  566.         if (!game.firemaking.isActive) game.firemaking.burnLog();
  567.         return true;
  568.       };
  569.     case "Fishing": {
  570.       const fishIndex = Fishing.data.find((a) => a.itemID == itemID).masteryID;
  571.       const areaID = Fishing.areas.findIndex((a) => a.fish.includes(fishIndex));
  572.       const fishID = Fishing.areas[areaID].fish.findIndex((a) => a == fishIndex);
  573.       return () => {
  574.         if (
  575.           (!player.equipment.slotArray.map((a) => a.item.id).includes(CONSTANTS.item.Barbarian_Gloves) &&
  576.             areaID == 6) ||
  577.           (!secretAreaUnlocked && areaID == 7) ||
  578.           skillLevel[CONSTANTS.skill.Fishing] < Fishing.data[fishIndex].level
  579.         )
  580.           return false;
  581.         if (game.activeSkill !== globalThis.ActiveSkills.FISHING) {
  582.           selectFish(areaID, fishID);
  583.           startFishing(areaID, fishID, true);
  584.         } else {
  585.           if (areaID != offline.action[0] || fishID != offline.action[1]) {
  586.             startFishing(offline.action[0], offline.action[1], true);
  587.             selectFish(areaID, fishID);
  588.             startFishing(areaID, fishID, true);
  589.           }
  590.         }
  591.         return true;
  592.       };
  593.     }
  594.     case "Fletching":
  595.       actionID = Fletching.recipes.find((a) => a.itemID == itemID).masteryID;
  596.       const log = items.findIndex((a) => a.name == skillItem2);
  597.       if (skillItem != "Arrow Shafts") {
  598.         return () => {
  599.           if (skillLevel[CONSTANTS.skill.Fletching] < Fletching.recipes[actionID].level) return false;
  600.           if (game.fletching.selectedRecipeID !== actionID) game.fletching.selectRecipeOnClick(actionID);
  601.           if (game.activeSkill !== globalThis.ActiveSkills.FLETCHING) game.fletching.start();
  602.           return true;
  603.         };
  604.       }
  605.       return () => {
  606.         if (skillLevel[CONSTANTS.skill.Fletching] < Fletching.recipes[actionID].level || !checkBankForItem(log))
  607.           return false;
  608.         if (selectedFletchLog != log || selectedFletch !== actionID) game.fletching.selectAltRecipeOnClick(actionID);
  609.         if (game.activeSkill !== globalThis.ActiveSkills.FLETCHING) game.fletching.start();
  610.         return true;
  611.       };
  612.     case "Herblore":
  613.       actionID = Herblore.potions.findIndex((a) => a.name == skillItem);
  614.       return () => {
  615.         if (skillLevel[CONSTANTS.skill.Herblore] < Herblore.potions[actionID].level) return false;
  616.         if (game.herblore.selectedRecipeID !== actionID) game.herblore.selectRecipeOnClick(actionID);
  617.         if (!game.herblore.isActive) game.herblore.start();
  618.         return true;
  619.       };
  620.     case "Mining":
  621.       actionID = Mining.rockData.findIndex((a) => a.name == skillItem);
  622.       return () => {
  623.         if (
  624.           (actionID === 9 && !game.mining.canMineDragonite) ||
  625.           skillLevel[CONSTANTS.skill.Mining] < Mining.rockData[actionID].levelRequired
  626.         )
  627.           return false;
  628.         if (!game.mining.isActive || game.mining.selectedRockId != actionID) game.mining.onRockClick(actionID);
  629.         return true;
  630.       };
  631.     case "Magic": {
  632.       actionID = AltMagic.spells.findIndex((a) => a.name == skillItem);
  633.       const magicItem = items.findIndex((a) => a.name == skillItem2);
  634.       return () => {
  635.         if (skillLevel[CONSTANTS.skill.Magic] < AltMagic.spells[actionID].level) return false;
  636.         if (game.altMagic.selectedSpellID !== actionID) game.altMagic.selectSpellOnClick(actionID);
  637.         switch (AltMagic.spells[actionID].consumes) {
  638.           case 3:
  639.             const bar = Smithing.recipes.find((a) => a.itemID == magicItem);
  640.             if (skillLevel[CONSTANTS.skill.Smithing] < bar.level) return false;
  641.             if (game.altMagic.selectedSmithingRecipe != bar) game.altMagic.selectBarOnClick(bar);
  642.             break;
  643.           case 1:
  644.           case 0:
  645.             if (game.altMagic.selectedConversionItem != magicItem) game.altMagic.selectItemOnClick(magicItem);
  646.             break;
  647.         }
  648.         if (!game.altMagic.isActive) game.altMagic.start();
  649.         return true;
  650.       };
  651.     }
  652.     case "Runecrafting":
  653.       actionID = Runecrafting.recipes.findIndex((a) => a.itemID == itemID);
  654.       return () => {
  655.         if (skillLevel[CONSTANTS.skill.Runecrafting] < Runecrafting.recipes[actionID].level) return false;
  656.         if (game.runecrafting.selectedRecipeID !== actionID) game.runecrafting.selectRecipeOnClick(actionID);
  657.         if (game.activeSkill !== globalThis.ActiveSkills.RUNECRAFTING) game.runecrafting.start();
  658.         return true;
  659.       };
  660.     case "Smithing":
  661.       actionID = Smithing.recipes.findIndex((a) => a.itemID == itemID);
  662.       return () => {
  663.         if (skillLevel[CONSTANTS.skill.Smithing] < Smithing.recipes[actionID].level) return false;
  664.         if (game.smithing.selectedRecipeID !== actionID) game.smithing.selectRecipeOnClick(actionID);
  665.         if (!game.smithing.isActive) game.smithing.start();
  666.         return true;
  667.       };
  668.     case "Summoning": {
  669.       const summonID = Summoning.marks.find((a) => a.itemID == itemID).masteryID;
  670.       let recipeID = 0;
  671.       if (options.actions["Start Skill"]["Summoning"][skillItem] != null) {
  672.         const ingredientID = items.findIndex((a) => a.name == skillItem2);
  673.         recipeID = Summoning.marksByItemID.get(itemID).nonShardItemCounts.findIndex(ingredient => ingredient == ingredientId);
  674.       }
  675.       return () => {
  676.         //exit if low level
  677.         if (skillLevel[CONSTANTS.skill.Summoning] < Summoning.marks.find((a) => a.summoningID == summonID).level)
  678.           return false;
  679.         //if summon is not selected, choose it
  680.         if (game.summoning.selectedRecipeID != summonID) {
  681.           game.summoning.selectRecipeOnClick(recipeID);
  682.         }
  683.         if (game.summoning.selectedAltRecipe !== recipeID){
  684.           game.summoning.selectAltRecipeOnClick(recipeID);
  685.         }
  686.         if (game.activeSkill !== globalThis.ActiveSkills.SUMMONING) game.summoning.start();
  687.         return game.activeSkill === globalThis.ActiveSkills.SUMMONING;
  688.       };
  689.     }
  690.     case "Thieving": {
  691.       const npc = Thieving.npcs.find((a) => a.name == skillItem);
  692.       const area = Thieving.areas.find((a) => a.npcs.includes(npc.id));
  693.       const panel = thievingMenu.areaPanels.find((a) => a.area == area);
  694.       return () => {
  695.         if (skillLevel[CONSTANTS.skill.Thieving] < npc.level) return false;
  696.         if (offline.skill == CONSTANTS.skill.Thieving && game.thieving.currentNPC == npc) return true;
  697.         thievingMenu.selectNPCInPanel(npc, panel);
  698.         game.thieving.startThieving(area, npc);
  699.         return true;
  700.       };
  701.     }
  702.     case "Woodcutting":
  703.       actionID = [itemID, items.findIndex((a) => a.name == skillItem2)].slice(
  704.         0,
  705.         playerModifiers.increasedTreeCutLimit + 1
  706.       );
  707.       return () => {
  708.         let result = true;
  709.         let currentTrees = [];
  710.         game.woodcutting.activeTrees.forEach((a) => currentTrees.push(a.id));
  711.         currentTrees.forEach((tree) => {
  712.           if (actionID.includes(tree)) {
  713.             actionID.splice(
  714.               actionID.findIndex((a) => a == tree),
  715.               1
  716.             );
  717.           } else {
  718.             actionID.unshift(tree);
  719.           }
  720.         });
  721.  
  722.         actionID.forEach((i) => {
  723.           if (skillLevel[CONSTANTS.skill.Woodcutting] >= Woodcutting.trees[i].levelRequired) {
  724.             game.woodcutting.selectTree(Woodcutting.trees[i]);
  725.           } else {
  726.             result = false;
  727.           }
  728.         });
  729.         return result;
  730.       };
  731.   }
  732. }
  733.  
  734. class Action {
  735.   /**
  736.    * Create an action object with trigger
  737.    * @param {string} category (Tier 1 option) category for trigger
  738.    * @param {string} name (Tier 2 option) skill/item name
  739.    * @param {string} greaterThan (Tier 3 option) either ≥ or ≤
  740.    * @param {string} masteryItem (Tier 3 option) target name for mastery
  741.    * @param {string} number (Tier 4 option) target number for skill/mastery/item
  742.    * @param {string} actionCategory (Tier 1 option) category for action
  743.    * @param {string} actionName (Tier 2 option) skill/monster/item/set name
  744.    * @param {string} skillItem (Tier 3 option) name for skilling action
  745.    * @param {string} skillItem2 (Tier 4 option) name of second tree to cut or alt.magic item
  746.    * @param {string} qty (Tier 4 option) amount of item to buy if applicable
  747.    */
  748.   constructor(
  749.     category,
  750.     name,
  751.     greaterThan,
  752.     masteryItem,
  753.     number,
  754.     actionCategory,
  755.     actionName,
  756.     skillItem,
  757.     skillItem2,
  758.     qty
  759.   ) {
  760.     switch (category) {
  761.       case "Idle":
  762.         this.description = `If no active skills/combat:`;
  763.         break;
  764.       case "Item Quantity":
  765.         this.description = `If ${name} ${greaterThan} ${number}:`;
  766.         break;
  767.       case "Prayer Points":
  768.         this.description = `If prayer points ${greaterThan} ${number}:`;
  769.         break;
  770.       case "Skill Level":
  771.         this.description = `If ${name} ≥ level ${number}:`;
  772.         break;
  773.       case "Skill XP":
  774.         this.description = `If ${name} ≥ ${number}xp:`;
  775.         break;
  776.       case "Equipped Item Quantity": {
  777.         let plural = name;
  778.         if (!/s$/i.test(name)) plural += "s";
  779.         this.description = `If ≤ ${number} ${plural} equipped:`;
  780.         break;
  781.       }
  782.       case "Mastery Level":
  783.         this.description = `If ${name} ${masteryItem} mastery ≥ ${number}:`;
  784.         break;
  785.       case "Pet Unlocked":
  786.         this.description = `If ${name} unlocked:`;
  787.         break;
  788.       case "Mastery Pool %":
  789.         this.description = `If ${name} mastery pool ≥ ${number}%:`;
  790.         break;
  791.       case "Potion Depleted":
  792.         this.description = `If ${name} potion has depleted:`;
  793.         break;
  794.       case "Enemy in Combat":
  795.         this.description = `If fighting ${name}:`;
  796.     }
  797.     this.data = [category, name, greaterThan, masteryItem, number];
  798.     this.elementID = `AQ${nameIncrement++}`;
  799.     this.trigger = setTrigger(category, name, greaterThan, masteryItem, number);
  800.     if (typeof actionCategory == "string") {
  801.       this.action = [
  802.         {
  803.           elementID: `AQ${nameIncrement++}`,
  804.           data: [actionCategory, actionName, skillItem, skillItem2, qty],
  805.           start: setAction(actionCategory, actionName, skillItem, skillItem2, qty),
  806.           description: actionDescription(actionCategory, actionName, skillItem, skillItem2, qty),
  807.         },
  808.       ];
  809.     } else {
  810.       this.action = [];
  811.     }
  812.   }
  813. }
  814.  
  815. function actionDescription(actionCategory, actionName, skillItem, skillItem2, qty) {
  816.   let description = "";
  817.   switch (actionCategory) {
  818.     case "Start Skill":
  819.       description += `start ${actionName} ${skillItem}`;
  820.       if (actionName == "Summoning") {
  821.         description = `start creating ${skillItem} tablets`;
  822.         if (options.actions["Start Skill"]["Summoning"][skillItem] != null) {
  823.           description += ` from ${skillItem2}`;
  824.         }
  825.       }
  826.       if (actionName == "Astrology") description = `Start studying ${skillItem} in ${actionName}`;
  827.       if (actionName == "Cooking") description = `Start ${skillItem} Cooking ${skillItem2}`;
  828.       if (actionName == "Woodcutting") description += ` & ${skillItem2}`;
  829.       if (skillItem == "Arrow Shafts") description += ` from ${skillItem2}`;
  830.       if (actionName == "Magic") {
  831.         description += ` with ${skillItem2}`;
  832.         if (!/s$/i.test(skillItem2)) description += "s";
  833.       }
  834.       break;
  835.     case "Start Combat":
  836.       description += `start fighting ${actionName}`;
  837.       break;
  838.     case "Change Attack Style":
  839.       if (actionName == "Select Spell") {
  840.         description += `select ${skillItem2} spell`;
  841.       } else {
  842.         description += `change attack style to ${actionName}`;
  843.       }
  844.       break;
  845.     case "Switch Equipment Set":
  846.       description += `switch to equipment set ${actionName}`;
  847.       break;
  848.     case "Equip Item":
  849.       description += `equip ${actionName} to current set`;
  850.       break;
  851.     case "Equip Passive":
  852.       description += `equip ${actionName} to passive slot`;
  853.       break;
  854.     case "Unequip Item":
  855.       description += `unequip ${actionName} from current set`;
  856.       break;
  857.     case "Buy Item":
  858.       if (qty > 1) {
  859.         description += `buy ${qty} ${actionName} from shop`;
  860.       } else {
  861.         description += `buy ${actionName} from shop`;
  862.       }
  863.       break;
  864.     case "Sell Item":
  865.       description += `sell ${actionName}`;
  866.       break;
  867.     case "Use Potion":
  868.       description += `use ${actionName} potion`;
  869.       break;
  870.     case "Activate Prayers":
  871.       {
  872.         if (actionName == "None") {
  873.           description += `turn off prayers`;
  874.         } else {
  875.           description += `turn on ${actionName}`;
  876.           if (skillItem != "None") description += ` and ${skillItem}`;
  877.         }
  878.       }
  879.       break;
  880.     case "Build Agility Obstacle":
  881.       description += `build ${skillItem}`;
  882.       break;
  883.     case "Remove Agility Obstacle":
  884.       description += `remove obstacle ${actionName}`;
  885.       break;
  886.   }
  887.   return description;
  888. }
  889.  
  890. function resetForm(arr) {
  891.   arr.forEach((menu) => {
  892.     document.getElementById(`aq-num${menu}`).type = "hidden";
  893.     document.getElementById(`aq-num${menu}`).value = "";
  894.     validInputs[menu].forEach((a, i) => {
  895.       if (i != 0) {
  896.         document.getElementById(`aq-text${menu}${i}`).type = "hidden"; //hide all except first
  897.         document.getElementById(`aq-list${menu}${i}`).innerHTML = ""; //empty datalists
  898.       }
  899.       document.getElementById(`aq-text${menu}${i}`).value = ""; //clear values
  900.       validInputs[menu][i] = null;
  901.     });
  902.   });
  903. }
  904.  
  905. function submitForm() {
  906.   try {
  907.     //create array of the input values
  908.     const arr = [];
  909.     ["A", "B"].forEach((menu) => {
  910.       for (let i = 0; i < validInputs[menu].length; i++) arr.push(document.getElementById(`aq-text${menu}${i}`).value);
  911.       arr.push(document.getElementById(`aq-num${menu}`).value);
  912.     });
  913.     arr.splice(["≥", "≤", ""].includes(arr[2]) ? 3 : 2, 0, "");
  914.  
  915.     //validate trigger and action
  916.     if (
  917.       validateInput(
  918.         options.triggers,
  919.         arr.slice(0, 5).filter((a) => a !== "")
  920.       ) &&
  921.       validateInput(
  922.         options.actions,
  923.         arr.slice(5).filter((a) => a !== "")
  924.       )
  925.     ) {
  926.       addToQueue(new Action(...arr));
  927.       resetForm(["A", "B"]);
  928.     }
  929.   } catch (e) {
  930.     console.error(e);
  931.   }
  932.   return false;
  933. }
  934.  
  935. /**
  936.  * Function to validate user input
  937.  * @param {Object} obj options object to check against
  938.  * @param {Array} tier array of user inputs
  939.  * @param {number} n leave blank (used for recursion)
  940.  * @returns {boolean} true if input is valid
  941.  */
  942. function validateInput(obj, tier, n = 0) {
  943.   if (!obj.hasOwnProperty([tier[n]])) return false;
  944.   if (obj[tier[n]] === null || (obj[tier[n]] === "num" && /^\d{1,10}$/.test(tier[n + 1]))) return true;
  945.   return validateInput(obj[tier[n]], tier, n + 1);
  946. }
  947.  
  948. /**
  949.  * Updates input text boxes
  950.  * @param {string} menu ('A'||'B'||'C')
  951.  */
  952. function dropdowns(menu) {
  953.   let obj;
  954.   if (menu == "A") {
  955.     obj = options.triggers;
  956.   } else if (menu == "B") {
  957.     obj = options.actions;
  958.   } else {
  959.     obj = options[currentlyEditing.type == "triggers" ? "triggers" : "actions"];
  960.   }
  961.   for (let i = 0; i < validInputs[menu].length; i++) {
  962.     const value = document.getElementById(`aq-text${menu}${i}`).value;
  963.     if (Object.keys(obj).includes(value)) {
  964.       if (obj[value] == "num") {
  965.         document.getElementById(`aq-num${menu}`).type = "text";
  966.         break;
  967.       }
  968.       if (obj[value] == null) break;
  969.       obj = obj[value];
  970.       if (validInputs[menu][i] != value) {
  971.         validInputs[menu][i] = value;
  972.         document.getElementById(`aq-text${menu}${i + 1}`).type = "text";
  973.         document.getElementById(`aq-list${menu}${i + 1}`).innerHTML = "";
  974.         Object.keys(obj).forEach((e) => {
  975.           document.getElementById(`aq-list${menu}${i + 1}`).insertAdjacentHTML("beforeend", `<option>${e}</option>`);
  976.         });
  977.       }
  978.     } else {
  979.       validInputs[menu][i] = null;
  980.       for (i++; i < validInputs[menu].length; i++) {
  981.         try {
  982.           validInputs[menu][i] = null;
  983.           document.getElementById(`aq-text${menu}${i}`).type = "hidden";
  984.           document.getElementById(`aq-text${menu}${i}`).value = "";
  985.         } catch {}
  986.       }
  987.       document.getElementById(`aq-num${menu}`).type = "hidden";
  988.       document.getElementById(`aq-num${menu}`).value = "";
  989.     }
  990.   }
  991. }
  992.  
  993. const aqHTML = `<div class="content" id="action-queue-container" style="display: none">
  994. <div class="row row-deck">
  995.   <div class="col-md-12">
  996.     <div class="block block-rounded block-link-pop border-top border-settings border-4x">
  997.       <form class="aq-mastery-config" id="aq-mastery-config-container" style="display: none">
  998.         <div style="display: inline-block; margin-left: 20px; height: 180px; vertical-align: top">
  999.           <h3 class="aq-header">Mastery Config</h3>
  1000.           <div>
  1001.             <select id="aq-skill-list" class="aq-select"></select>
  1002.           </div>
  1003.           <div>
  1004.             <select id="aq-checkpoint-list" class="aq-select">
  1005.               <option value="0">0%</option>
  1006.               <option value="0.1">10%</option>
  1007.               <option value="0.25">25%</option>
  1008.               <option value="0.5">50%</option>
  1009.               <option value="0.95">95%</option>
  1010.             </select>
  1011.           </div>
  1012.           <div>
  1013.             <select id="aq-mastery-strategy" class="aq-select">
  1014.               <option value="false">Lowest mastery</option>
  1015.               <option value="true">Custom priority</option>
  1016.             </select>
  1017.           </div>
  1018.           <div>
  1019.             <select id="aq-base" class="aq-select"></select>
  1020.           </div>
  1021.           <div>
  1022.             <input type="text" id="aq-mastery-array" class="aq-select"/>
  1023.           </div>
  1024.         </div>
  1025.         <div style="margin-left: 20px">
  1026.           <button type="button" class="btn btn-sm aq-green" id="aq-config-close">Done</button>
  1027.         </div>
  1028.       </form>
  1029.       <form class="aq-popup" id="aq-edit-container" style="display: none;">
  1030.         <div style="display: inline-block; margin-left: 20px; height: 180px; vertical-align: top">
  1031.           <h3 id="aq-edit-form" class="aq-header">Action</h3>
  1032.           <div>
  1033.             <input type="text" class="aq-dropdown" id="aq-textC0" required list="aq-listC0" placeholder="Category" />
  1034.           </div>
  1035.           <div>
  1036.             <input type="hidden" class="aq-dropdown" id="aq-textC1" list="aq-listC1" />
  1037.           </div>
  1038.           <div>
  1039.             <input type="hidden" class="aq-dropdown" id="aq-textC2" list="aq-listC2" />
  1040.           </div>
  1041.           <div>
  1042.             <input type="hidden" class="aq-dropdown" id="aq-textC3" list="aq-listC3" />
  1043.           </div>
  1044.           <div>
  1045.             <input type="hidden" class="aq-dropdown" id="aq-numC" pattern="^\\d{1,10}$" placeholder="number" title="Positive integer"/>
  1046.           </div>
  1047.         </div>
  1048.         <div style="margin-left: 20px">
  1049.           <button type="button" id="aq-save-edit" class="btn btn-sm aq-blue">Save</button>
  1050.           <button type="button" id="aq-cancel" class="btn btn-sm btn-danger">Cancel</button>
  1051.         </div>
  1052.       </form>
  1053.       <div class="block-content">
  1054.         <div>
  1055.           <form id="aq-form">
  1056.             <div style="display: inline-block; margin-left: 20px; height: 180px; vertical-align: top">
  1057.               <h3 class="aq-header">Trigger</h3>
  1058.               <div>
  1059.                 <input type="text" class="aq-dropdown" id="aq-textA0" required list="aq-listA0" placeholder="Category" />
  1060.               </div>
  1061.               <div>
  1062.                 <input type="hidden" class="aq-dropdown" id="aq-textA1" list="aq-listA1" />
  1063.               </div>
  1064.               <div>
  1065.                 <input type="hidden" class="aq-dropdown" id="aq-textA2" list="aq-listA2" />
  1066.               </div>
  1067.               <div>
  1068.                 <input type="hidden" class="aq-dropdown" id="aq-numA" pattern="^\\d{1,10}$" placeholder="number" title="Positive integer"/>
  1069.               </div>
  1070.             </div>
  1071.             <div style="display: inline-block; margin-left: 20px; height: 180px; vertical-align: top">
  1072.               <h3 class="aq-header">Action</h3>
  1073.               <div>
  1074.                 <input type="text" class="aq-dropdown" id="aq-textB0" required list="aq-listB0" placeholder="Category" />
  1075.               </div>
  1076.               <div>
  1077.                 <input type="hidden" class="aq-dropdown" id="aq-textB1" list="aq-listB1" />
  1078.               </div>
  1079.               <div>
  1080.                 <input type="hidden" class="aq-dropdown" id="aq-textB2" list="aq-listB2" />
  1081.               </div>
  1082.               <div>
  1083.                 <input type="hidden" class="aq-dropdown" id="aq-textB3" list="aq-listB3" />
  1084.               </div>
  1085.               <div>
  1086.                 <input type="hidden" class="aq-dropdown" id="aq-numB" pattern="^\\d{1,10}$" placeholder="number" title="Positive integer"/>
  1087.               </div>
  1088.             </div>
  1089.             <div style="margin-left: 20px">
  1090.               <input type="submit" class="btn btn-sm aq-blue" value="Add to queue">
  1091.               <button type="button" id="aq-pause" class="btn btn-sm aq-yellow">Pause</button>
  1092.             </div>
  1093.           </form>
  1094.         </div>
  1095.         <form style="margin: 10px 0 5px 20px">
  1096.           <button type="button" class="btn btn-sm aq-grey" id="aq-download">Download Action List</button>
  1097.           <input type="submit" class="btn btn-sm aq-grey" value="Import Action List" />
  1098.           <input type="text" id="aq-pastebin" style="width: 236px;" required pattern="^\\[.*\\]$" placeholder="Paste data here" />
  1099.         </form>
  1100.         <div style="display: flex;justify-content: space-between;max-width: 550px;margin: 10px 0 0 25px;">
  1101.           <p style="margin: 0;">Looping</p>
  1102.           <div class="">
  1103.             <div class="custom-control custom-radio custom-control-inline custom-control-lg">
  1104.                 <input type="radio" class="custom-control-input" id="aq-loop-enable" name="aq-looping">
  1105.                 <label class="custom-control-label" for="aq-loop-enable">Enable</label>
  1106.             </div>
  1107.             <div class="custom-control custom-radio custom-control-inline custom-control-lg">
  1108.                 <input type="radio" class="custom-control-input" id="aq-loop-disable" name="aq-looping" checked="">
  1109.                 <label class="custom-control-label" for="aq-loop-disable">Disable</label>
  1110.             </div>
  1111.           </div>
  1112.         </div>
  1113.         <div style="display: flex;justify-content: space-between;max-width: 550px;margin: 10px 0 0 25px;">
  1114.           <p style="margin: 0;">Mastery Pool Management</p>
  1115.           <div class="">
  1116.             <div class="custom-control custom-radio custom-control-inline custom-control-lg">
  1117.                 <input type="radio" class="custom-control-input" id="aq-mastery-enable" name="aq-mastery">
  1118.                 <label class="custom-control-label" for="aq-mastery-enable">Enable</label>
  1119.             </div>
  1120.             <div class="custom-control custom-radio custom-control-inline custom-control-lg">
  1121.                 <input type="radio" class="custom-control-input" id="aq-mastery-disable" name="aq-mastery" checked="">
  1122.                 <label class="custom-control-label" for="aq-mastery-disable">Disable</label>
  1123.             </div>
  1124.           </div>
  1125.         </div>
  1126.         <div style="margin: 10px 0 0 25px; display: flex; justify-content: space-between">
  1127.           <button type="button" class="btn btn-sm aq-grey" id="aq-mastery-config">Advanced Mastery Options</button>
  1128.           <button type="button" style="font-size: 0.875rem" class="btn aq-delete btn-danger" id="aq-delete-all">delete all</button>
  1129.         </div>
  1130.         <h2 class="content-heading border-bottom mb-4 pb-2">Current Queue</h2>
  1131.         <div style="min-height: 50px" id="aq-item-container"></div>
  1132.       </div>
  1133.     </div>
  1134.   </div>
  1135. </div>
  1136. </div>
  1137. <datalist id="aq-listA0"></datalist>
  1138. <datalist id="aq-listA1"></datalist>
  1139. <datalist id="aq-listA2"></datalist>
  1140. <datalist id="aq-listB0"></datalist>
  1141. <datalist id="aq-listB1"></datalist>
  1142. <datalist id="aq-listB2"></datalist>
  1143. <datalist id="aq-listB3"></datalist>
  1144. <datalist id="aq-listC0"></datalist>
  1145. <datalist id="aq-listC1"></datalist>
  1146. <datalist id="aq-listC2"></datalist>
  1147. <datalist id="aq-listC3"></datalist>
  1148. <style>
  1149. .aq-dropdown {
  1150.   width: 260px;
  1151. }
  1152. .aq-header {
  1153.   margin-bottom: 10px;
  1154.   color: whitesmoke;
  1155. }
  1156. .aq-arrow {
  1157.   font-size: 2rem;
  1158.   padding: 0px 0.25rem;
  1159.   line-height: 0px;
  1160.   margin: 0.25rem 2px;
  1161.   border-radius: 0.2rem;
  1162. }
  1163. .aq-item {
  1164.   background-color: #464646;
  1165.   padding: 12px;
  1166.   margin: 12px;
  1167.   cursor: move;
  1168. }
  1169. .aq-item-inner {
  1170.   display: flex;
  1171.   justify-content: space-between;
  1172.   cursor: move;
  1173. }
  1174. .aq-delete {
  1175.   font-size: 1.2rem;
  1176.   padding: 0px 0.36rem;
  1177.   line-height: 0px;
  1178.   margin: 0.25rem 0.25rem 0.25rem 0.125rem;
  1179.   border-radius: 0.2rem;
  1180. }
  1181. .aq-grey {
  1182.   background-color: #676767;
  1183. }
  1184. .aq-grey:hover {
  1185.   background-color: #848484;
  1186. }
  1187. .aq-blue {
  1188.   background-color: #0083ff;
  1189. }
  1190. .aq-blue:hover {
  1191.   background-color: #63b4ff;
  1192. }
  1193. .aq-green {
  1194.   background-color: #5a9e00;
  1195. }
  1196. .aq-green:hover {
  1197.   background-color: #7bd900;
  1198. }
  1199. .aq-yellow {
  1200.   background-color: #e69721;
  1201. }
  1202. .aq-yellow:hover {
  1203.   background-color: #ffb445;
  1204. }
  1205. .t-drag {
  1206.   opacity: 0.5;
  1207. }
  1208. .a-drag {
  1209.   opacity: 0.5;
  1210. }
  1211. .aq-popup {
  1212.   position: fixed;
  1213.   right: 5%;
  1214.   top: 20%;
  1215.   z-index: 2;
  1216.   background-color: #3f4046;
  1217.   padding: 20px 20px 20px 0;
  1218.   border-top: 1px solid #e1e6e9;
  1219.   border-radius: 0.25rem;
  1220.   border-width: 4px;
  1221. }
  1222. .aq-mastery-config {
  1223.   position: fixed;
  1224.   right: 67%;
  1225.   top: 18%;
  1226.   z-index: 3;
  1227.   background-color: #3f4046;
  1228.   padding: 20px 20px 20px 0;
  1229.   border-top: 1px solid #e1e6e9;
  1230.   border-radius: 0.25rem;
  1231.   border-width: 4px;
  1232. }
  1233. .aq-select {
  1234.   width: 200px;
  1235. }
  1236. </style>
  1237. `;
  1238.  
  1239. function loadAQ() {
  1240.   //add item names
  1241.   for (const a of items) {
  1242.     options.triggers["Item Quantity"][a.name] = { "≥": "num", "≤": "num" };
  1243.     options.actions["Sell Item"][a.name] = null;
  1244.     if (a.validSlots && a.validSlots.includes("Passive")) options.actions["Equip Passive"][a.name] = null;
  1245.   }
  1246.  
  1247.   //add attack styles
  1248.   for (const s in CONSTANTS.attackStyle) {
  1249.     if (isNaN(s)) options.actions["Change Attack Style"][s] = null;
  1250.   }
  1251.   //add normal spells
  1252.   for (const s of SPELLS) {
  1253.     options.actions["Change Attack Style"]["Select Spell"]["Normal"][s.name] = null;
  1254.   }
  1255.   //add curse spells
  1256.   for (const s of CURSES) {
  1257.     options.actions["Change Attack Style"]["Select Spell"]["Curse"][s.name] = null;
  1258.   }
  1259.   //add aurora spells
  1260.   for (const s of AURORAS) {
  1261.     options.actions["Change Attack Style"]["Select Spell"]["Aurora"][s.name] = null;
  1262.   }
  1263.   //add ancient spells
  1264.   for (const s of ANCIENT) {
  1265.     options.actions["Change Attack Style"]["Select Spell"]["Ancient"][s.name] = null;
  1266.   }
  1267.  
  1268.   //add pet names
  1269.   for (const pet of PETS) {
  1270.     const name = `${pet.name.split(",")[0]} (${pet.acquiredBy})`;
  1271.     options.triggers["Pet Unlocked"][name] = null;
  1272.   }
  1273.  
  1274.   //add skill names
  1275.   Object.keys(CONSTANTS.skill).forEach((a) => {
  1276.     if (isNaN(a)) options.triggers["Skill Level"][a] = "num";
  1277.   });
  1278.   options.triggers["Skill XP"] = options.triggers["Skill Level"];
  1279.   Object.keys(options.triggers["Mastery Level"]).forEach(
  1280.     (skill) => (options.triggers["Mastery Pool %"][skill] = "num")
  1281.   );
  1282.  
  1283.   //add mastery/action names for each skill
  1284.   {
  1285.     Astrology.constellations.forEach((item) => {
  1286.       options.triggers["Mastery Level"]["Astrology"][item.name] = "num";
  1287.       options.actions["Start Skill"]["Astrology"][item.name] = null;
  1288.     });
  1289.     Cooking.recipes.forEach((item) => {
  1290.       options.triggers["Mastery Level"]["Cooking"][items[item.itemID].name] = "num";
  1291.     });
  1292.     options.actions["Start Skill"]["Cooking"]["Active"] = Cooking.recipes.reduce((obj, a) => {
  1293.       obj[items[a.itemID].name] = null;
  1294.       return obj;
  1295.     }, {});
  1296.     options.actions["Start Skill"]["Cooking"]["Passive"] = options.actions["Start Skill"]["Cooking"]["Active"];
  1297.  
  1298.     Crafting.recipes.forEach((item) => {
  1299.       options.triggers["Mastery Level"]["Crafting"][items[item.itemID].name] = "num";
  1300.       options.actions["Start Skill"]["Crafting"][items[item.itemID].name] = null;
  1301.     });
  1302.  
  1303.     items.forEach((item) => {
  1304.       if (item.type == "Seeds") {
  1305.         options.triggers["Mastery Level"]["Farming"][item.name] = "num";
  1306.       }
  1307.     });
  1308.  
  1309.     items.forEach((item) => {
  1310.       if (item.type == "Logs") {
  1311.         options.triggers["Mastery Level"]["Firemaking"][item.name] = "num";
  1312.         options.actions["Start Skill"]["Firemaking"][item.name] = null;
  1313.       }
  1314.     });
  1315.  
  1316.     Fishing.data.forEach((item) => {
  1317.       options.triggers["Mastery Level"]["Fishing"][items[item.itemID].name] = "num";
  1318.       options.actions["Start Skill"]["Fishing"][items[item.itemID].name] = null;
  1319.     });
  1320.  
  1321.     items.forEach((item) => {
  1322.       if (item.type == "Logs") {
  1323.         options.triggers["Mastery Level"]["Woodcutting"][item.name] = "num";
  1324.         options.actions["Start Skill"]["Woodcutting"][item.name] = {};
  1325.       }
  1326.     });
  1327.     for (const log in options.actions["Start Skill"]["Woodcutting"]) {
  1328.       Object.keys(options.actions["Start Skill"]["Woodcutting"]).forEach(
  1329.         (a) => (options.actions["Start Skill"]["Woodcutting"][log][a] = null)
  1330.       );
  1331.     }
  1332.  
  1333.     Fletching.recipes.forEach((item) => {
  1334.       options.triggers["Mastery Level"]["Fletching"][items[item.itemID].name] = "num";
  1335.       options.actions["Start Skill"]["Fletching"][items[item.itemID].name] = null;
  1336.     });
  1337.     options.actions["Start Skill"]["Fletching"]["Arrow Shafts"] = {};
  1338.     for (const log in options.actions["Start Skill"]["Woodcutting"]) {
  1339.       options.actions["Start Skill"]["Fletching"]["Arrow Shafts"][log] = null;
  1340.     }
  1341.  
  1342.     Herblore.potions.forEach((item) => {
  1343.       options.triggers["Mastery Level"]["Herblore"][item.name] = "num";
  1344.       options.actions["Start Skill"]["Herblore"][item.name] = null;
  1345.     });
  1346.  
  1347.     Agility.obstacles.forEach((item) => {
  1348.       options.triggers["Mastery Level"]["Agility"][item.name] = "num";
  1349.     });
  1350.  
  1351.     Mining.rockData.forEach((item) => {
  1352.       options.triggers["Mastery Level"]["Mining"][item.name] = "num";
  1353.       options.actions["Start Skill"]["Mining"][item.name] = null;
  1354.     });
  1355.  
  1356.     Runecrafting.recipes.forEach((item) => {
  1357.       options.triggers["Mastery Level"]["Runecrafting"][items[item.itemID].name] = "num";
  1358.       options.actions["Start Skill"]["Runecrafting"][items[item.itemID].name] = null;
  1359.     });
  1360.  
  1361.     Smithing.recipes.forEach((item) => {
  1362.       options.triggers["Mastery Level"]["Smithing"][items[item.itemID].name] = "num";
  1363.       options.actions["Start Skill"]["Smithing"][items[item.itemID].name] = null;
  1364.     });
  1365.  
  1366.     Thieving.npcs.forEach((npc) => {
  1367.       options.triggers["Mastery Level"]["Thieving"][npc.name] = "num";
  1368.       options.actions["Start Skill"]["Thieving"][npc.name] = null;
  1369.     });
  1370.  
  1371.     Summoning.marks.forEach((mark) => {
  1372.       options.triggers["Mastery Level"]["Summoning"][items[mark.itemID].name] = "num";
  1373.       if (mark.nonShardItemCosts.length == 1) {
  1374.         options.actions["Start Skill"]["Summoning"][items[mark.itemID].name] = null;
  1375.       } else {
  1376.         options.actions["Start Skill"]["Summoning"][items[mark.itemID].name] = {};
  1377.         mark.nonShardItemCosts.forEach((ingredient) => {
  1378.           options.actions["Start Skill"]["Summoning"][items[mark.itemID].name][items[ingredient].name] = null;
  1379.         });
  1380.       }
  1381.     });
  1382.   }
  1383.  
  1384.   //agility obstacles
  1385.   options.actions["Build Agility Obstacle"] = Agility.obstacles.reduce((obj, a) => {
  1386.     if (!obj.hasOwnProperty(a.category + 1)) obj[a.category + 1] = {};
  1387.     obj[a.category + 1][a.name] = null;
  1388.     return obj;
  1389.   }, {});
  1390.   Object.keys(options.actions["Build Agility Obstacle"]).forEach(
  1391.     (a) => (options.actions["Remove Agility Obstacle"][a] = null)
  1392.   );
  1393.  
  1394.   //potions
  1395.   options.actions["Use Potion"] = items.reduce((obj, item) => {
  1396.     if (item.type == "Potion") obj[item.name] = null;
  1397.     return obj;
  1398.   }, {});
  1399.  
  1400.   //prayer actions
  1401.   PRAYER.forEach((prayer) => (options.actions["Activate Prayers"][prayer.name] = {}));
  1402.   for (const prayer1 in options.actions["Activate Prayers"]) {
  1403.     PRAYER.forEach((prayer) => (options.actions["Activate Prayers"][prayer1][prayer.name] = null));
  1404.     options.actions["Activate Prayers"][prayer1]["None"] = null;
  1405.   }
  1406.   options.actions["Activate Prayers"]["None"] = null;
  1407.  
  1408.   //add altmagic names
  1409.   AltMagic.spells.forEach((spell) => {
  1410.     options.actions["Start Skill"]["Magic"][spell.name] = {};
  1411.     if (spell.consumes === 3) {
  1412.       for (const item of AltMagic.smithingBarRecipes) {
  1413.         options.actions["Start Skill"]["Magic"][spell.name][items[item.itemID].name] = null;
  1414.       }
  1415.     } else if (spell.consumes === 1) {
  1416.       Fishing.junkItems.forEach((a) => (options.actions["Start Skill"]["Magic"][spell.name][items[a].name] = null));
  1417.     } else if (spell.consumes === 0) {
  1418.       options.actions["Start Skill"]["Magic"][spell.name] = options.actions["Sell Item"];
  1419.     } else options.actions["Start Skill"]["Magic"][spell.name] = null;
  1420.   });
  1421.  
  1422.   //add food and ammo names
  1423.   for (const a of items.filter((a) => a.canEat)) {
  1424.     options.triggers["Equipped Item Quantity"][a.name] = "num";
  1425.     options.actions["Equip Item"][a.name] = null;
  1426.   }
  1427.   for (const a of items.filter(
  1428.     (a) => a.hasOwnProperty("validSlots") && (a.validSlots[0] == "Quiver" || a.validSlots[0] == "Summon1")
  1429.   ))
  1430.     options.triggers["Equipped Item Quantity"][a.name] = "num";
  1431.  
  1432.   //edit buyShopItem  function so that it returns boolean based on if it met requirements
  1433.   eval(
  1434.     buyShopItem
  1435.       .toString()
  1436.       .replace(/}\s*}$/, "return canBuy}}")
  1437.       .replace(/^function (\w+)/, "window.$1 = function")
  1438.   );
  1439.  
  1440.   //add shop items
  1441.   for (const category in SHOP) {
  1442.     SHOP[category].forEach((item, id) => {
  1443.       let name = item.name;
  1444.       if (name == "Extra Equipment Set") {
  1445.         item.cost.gp > 0 ? (name += " (GP)") : (name += " (SC)");
  1446.       }
  1447.       if (category == "Materials" || category == "Gloves") {
  1448.         shop[name] = {
  1449.           buy: (qty) => {
  1450.             updateBuyQty(qty);
  1451.             return buyShopItem(category, id, true);
  1452.           },
  1453.         };
  1454.         options.actions["Buy Item"][name] = "num";
  1455.       } else {
  1456.         shop[name] = { buy: () => buyShopItem(category, id, true) };
  1457.         options.actions["Buy Item"][name] = null;
  1458.       }
  1459.     });
  1460.   }
  1461.  
  1462.   //add monster/dungeon names
  1463.   {
  1464.     //collect monster IDs
  1465.     const monsterIDs = [];
  1466.     for (const area of combatAreas) monsterIDs.push(...area.monsters);
  1467.     for (const area of slayerAreas) monsterIDs.push(...area.monsters);
  1468.     //add names to array
  1469.     for (const monster of monsterIDs) options.actions["Start Combat"][MONSTERS[monster].name] = null;
  1470.     for (const dungeon of DUNGEONS) options.actions["Start Combat"][dungeon.name] = null;
  1471.     for (const monster of MONSTERS) options.triggers["Enemy in Combat"][monster.name] = null;
  1472.   }
  1473.  
  1474.   //add equippable items
  1475.   for (const a of items.filter((a) => a.hasOwnProperty("validSlots"))) {
  1476.     options.actions["Equip Item"][a.name] = null;
  1477.     options.actions["Unequip Item"][a.name] = null;
  1478.   }
  1479.  
  1480.   //add in sidebar item
  1481.   document.getElementsByClassName("bank-space-nav")[0].parentNode.parentNode.insertAdjacentHTML(
  1482.     "afterend",
  1483.     `<li class="nav-main-item">
  1484.   <a class="nav-main-link nav-compact" style="cursor: pointer;">
  1485.     <img class="nav-img" src="assets/media/skills/prayer/mystic_lore.svg">
  1486.     <span class="nav-main-link-name">Action Queue</span>
  1487.     <small id="current-queue" style="color: rgb(210, 106, 92);">inactive</small>
  1488.   </a>
  1489. </li>`
  1490.   );
  1491.   //add click for sidebar item
  1492.   $("li.nav-main-item:contains(Action Queue)")[0].addEventListener("click", () => actionTab());
  1493.  
  1494.   const htmlCollection = $('div[onclick^="changePage"]');
  1495.   for (let i = 0; i < htmlCollection.length; i++) htmlCollection[i].addEventListener("click", () => hideActionTab());
  1496.  
  1497.   //add main html
  1498.   document.getElementById("main-container").insertAdjacentHTML("beforeend", aqHTML);
  1499.  
  1500.   //add button clicks
  1501.   document.getElementById("aq-pause").addEventListener("click", () => togglePause());
  1502.   document.getElementById("aq-download").addEventListener("click", () => downloadActions());
  1503.   document.getElementById("aq-mastery-config").addEventListener("click", () => masteryPopup(true));
  1504.   document.getElementById("aq-loop-enable").addEventListener("click", () => toggleLoop(true));
  1505.   document.getElementById("aq-loop-disable").addEventListener("click", () => toggleLoop(false));
  1506.   document.getElementById("aq-mastery-enable").addEventListener("click", () => toggleMastery(true));
  1507.   document.getElementById("aq-mastery-disable").addEventListener("click", () => toggleMastery(false));
  1508.   document.getElementById("aq-cancel").addEventListener("click", () => cancelEdit());
  1509.   document.getElementById("aq-delete-all").addEventListener("click", () => clearQueue());
  1510.   document.getElementById("aq-save-edit").addEventListener("click", () => submitEdit());
  1511.   document.getElementById("aq-form").addEventListener("submit", (e) => {
  1512.     e.preventDefault();
  1513.     submitForm();
  1514.   });
  1515.   document.getElementById("aq-item-container").parentNode.children[1].addEventListener("submit", (e) => {
  1516.     e.preventDefault();
  1517.     importActions();
  1518.   });
  1519.   for (const menu in validInputs) {
  1520.     validInputs[menu].forEach((a, i) => {
  1521.       document.getElementById(`aq-text${menu}${i}`).addEventListener("input", () => {
  1522.         dropdowns(menu);
  1523.       });
  1524.     });
  1525.   }
  1526.   document.getElementById("aq-skill-list").addEventListener("change", () => updateMasteryConfig());
  1527.   document.getElementById("aq-checkpoint-list").addEventListener("change", () => updateMasteryConfig(false));
  1528.   document.getElementById("aq-mastery-strategy").addEventListener("change", () => updateMasteryConfig(false));
  1529.   document.getElementById("aq-base").addEventListener("change", () => updateMasteryConfig(false));
  1530.   document.getElementById("aq-config-close").addEventListener("click", () => masteryPopup(false));
  1531.   document.getElementById(`aq-mastery-array`).addEventListener("input", () => updateMasteryConfig(false));
  1532.  
  1533.   //fills category lists
  1534.   Object.keys(options.triggers).forEach((a) =>
  1535.     document.getElementById("aq-listA0").insertAdjacentHTML("beforeend", `<option>${a}</option>`)
  1536.   );
  1537.   Object.keys(options.actions).forEach((a) =>
  1538.     document.getElementById("aq-listB0").insertAdjacentHTML("beforeend", `<option>${a}</option>`)
  1539.   );
  1540.  
  1541.   //add event listener for dragging trigger blocks
  1542.   const triggerContainer = document.getElementById("aq-item-container");
  1543.   triggerContainer.addEventListener("dragover", (e) => {
  1544.     e.preventDefault();
  1545.     const draggable = document.querySelector(".t-drag");
  1546.     if (!draggable || document.querySelector(".a-drag") != null) return;
  1547.     const afterElement = getDragAfterElement(triggerContainer, e.clientY, ".aq-item");
  1548.     if (afterElement == null) {
  1549.       triggerContainer.appendChild(draggable);
  1550.     } else {
  1551.       triggerContainer.insertBefore(draggable, afterElement);
  1552.     }
  1553.   });
  1554.  
  1555.   //mastery stuff
  1556.   for (let i = 1; i < 100; i++) {
  1557.     lvlIndex[i] = exp.level_to_xp(i) + 1;
  1558.   }
  1559.   for (const skill in MASTERY) {
  1560.     masteryConfig[skill] = {
  1561.       checkpoint: 0.95,
  1562.       prio: false,
  1563.       base: 87,
  1564.       arr: [],
  1565.     };
  1566.     masteryClone[skill] = {
  1567.       pool: MASTERY[skill].pool,
  1568.       lvl: [],
  1569.     };
  1570.     updateMasteryLvl(skill);
  1571.   }
  1572.  
  1573.   for (const name in CONSTANTS.skill) {
  1574.     if (Object.keys(MASTERY).includes(`${CONSTANTS.skill[name]}`))
  1575.       document
  1576.         .getElementById("aq-skill-list")
  1577.         .insertAdjacentHTML("beforeend", `<option value="${CONSTANTS.skill[name]}">${name}</option>`);
  1578.   }
  1579.   for (let i = 60; i < 88; i++)
  1580.     document.getElementById("aq-base").insertAdjacentHTML("beforeend", `<option value="${i}">${i}</option>`);
  1581.   tooltips.masteryConfig = [
  1582.     tippy(document.getElementById("aq-checkpoint-list"), {
  1583.       content: "Minimum pool % to maintain",
  1584.       animation: false,
  1585.     }),
  1586.     tippy(document.getElementById("aq-mastery-strategy"), {
  1587.       content: "Mastery pool spending strategy",
  1588.       animation: false,
  1589.     }),
  1590.     tippy(document.getElementById("aq-base"), {
  1591.       content: "Target mastery level for custom priority list",
  1592.       animation: false,
  1593.     }),
  1594.   ];
  1595.  
  1596.   window.masteryIDs = {};
  1597.   for (const skillName in options.triggers["Mastery Level"]) {
  1598.     masteryIDs[skillName] = {};
  1599.     for (const name in options.triggers["Mastery Level"][skillName])
  1600.       masteryIDs[skillName][name] = fetchMasteryID(skillName, name);
  1601.   }
  1602.  
  1603.   //load locally stored action queue if it exists
  1604.   loadLocalSave();
  1605.   console.log("Action Queue loaded");
  1606. }
  1607.  
  1608. function triggerCheck() {
  1609.   let result = true;
  1610.   if (currentActionIndex >= actionQueueArray.length) {
  1611.     if (queueLoop && actionQueueArray.length > 0) {
  1612.       currentActionIndex = 0;
  1613.       updateQueue();
  1614.       return;
  1615.     } else {
  1616.       clearInterval(triggerCheckInterval);
  1617.       triggerCheckInterval = null;
  1618.       updateTextColour("stop");
  1619.       return;
  1620.     }
  1621.   }
  1622.   if (actionQueueArray[currentActionIndex].trigger()) {
  1623.     actionQueueArray[currentActionIndex].action.forEach((action, i) => {
  1624.       result = action.start();
  1625.       document.getElementById(actionQueueArray[currentActionIndex].elementID).children[1].children[
  1626.         i
  1627.       ].children[1].children[0].style.display = result ? "none" : "";
  1628.     });
  1629.     currentActionIndex + 1 >= actionQueueArray.length && queueLoop ? (currentActionIndex = 0) : currentActionIndex++;
  1630.     updateQueue();
  1631.   }
  1632. }
  1633.  
  1634. /**
  1635.  * Updates colour and text in sidebar
  1636.  * @param {string} type ("start" || "stop" || "pause")
  1637.  */
  1638. function updateTextColour(type) {
  1639.   switch (type) {
  1640.     case "start":
  1641.       document.getElementById("current-queue").style.color = "#46c37b";
  1642.       document.getElementById("current-queue").innerHTML = "running";
  1643.       break;
  1644.     case "stop":
  1645.       document.getElementById("current-queue").style.color = "#d26a5c";
  1646.       document.getElementById("current-queue").innerHTML = "inactive";
  1647.       break;
  1648.     case "pause":
  1649.       document.getElementById("current-queue").style.color = "#f3b760";
  1650.       document.getElementById("current-queue").innerHTML = "paused";
  1651.   }
  1652. }
  1653.  
  1654. function updateQueue() {
  1655.   actionQueueArray.forEach((action, index) => {
  1656.     const element = document.getElementById(action.elementID);
  1657.     if (index === currentActionIndex) {
  1658.       for (let i = 0; i < element.children[1].children.length; i++) {
  1659.         element.children[1].children[i].children[1].children[0].style.display = "none";
  1660.       }
  1661.       element.style.backgroundColor = "#385a0b";
  1662.     } else element.style.backgroundColor = "";
  1663.   });
  1664. }
  1665.  
  1666. function toggleLoop(start) {
  1667.   queueLoop = start;
  1668. }
  1669.  
  1670. function togglePause() {
  1671.   queuePause = !queuePause;
  1672.   if (queuePause) {
  1673.     clearInterval(triggerCheckInterval);
  1674.     triggerCheckInterval = null;
  1675.     document.getElementById("aq-pause").innerHTML = "Unpause";
  1676.     document.getElementById("aq-pause").classList.add("aq-green");
  1677.     document.getElementById("aq-pause").classList.remove("aq-yellow");
  1678.     updateTextColour("pause");
  1679.   } else {
  1680.     if (actionQueueArray.length > 0) {
  1681.       updateQueue();
  1682.       triggerCheckInterval = setInterval(() => {
  1683.         triggerCheck();
  1684.       }, 1000);
  1685.       updateTextColour("start");
  1686.     } else {
  1687.       updateTextColour("stop");
  1688.     }
  1689.     document.getElementById("aq-pause").innerHTML = "Pause";
  1690.     document.getElementById("aq-pause").classList.add("aq-yellow");
  1691.     document.getElementById("aq-pause").classList.remove("aq-green");
  1692.   }
  1693. }
  1694.  
  1695. let loadCheckInterval = setInterval(() => {
  1696.   if (isLoaded) {
  1697.     clearInterval(loadCheckInterval);
  1698.     loadAQ();
  1699.   }
  1700. }, 200);
  1701.  
  1702. function autoSave() {
  1703.   const saveData = {
  1704.     index: currentActionIndex,
  1705.     data: [],
  1706.     loop: queueLoop,
  1707.     mastery: manageMasteryInterval === null ? false : true,
  1708.   };
  1709.   for (const action of actionQueueArray) {
  1710.     let actionList = [];
  1711.     action.action.forEach((a) => actionList.push(a.data));
  1712.     saveData.data.push([...action.data, actionList]);
  1713.   }
  1714.   window.localStorage.setItem("AQSAVE" + currentCharacter, JSON.stringify(saveData));
  1715.   window.localStorage.setItem("AQMASTERY", JSON.stringify(masteryConfig));
  1716. }
  1717.  
  1718. //autosave every ~minute
  1719. setInterval(() => {
  1720.   autoSave();
  1721. }, 59550);
  1722.  
  1723. function loadLocalSave() {
  1724.   const obj = JSON.parse(window.localStorage.getItem("AQSAVE" + currentCharacter));
  1725.   const config = JSON.parse(window.localStorage.getItem("AQMASTERY"));
  1726.   if (config != null) {
  1727.     for (const skill in config) {
  1728.       masteryConfig[skill] = config[skill];
  1729.     }
  1730.   }
  1731.  
  1732.   if (obj === null) return;
  1733.   if (obj.loop) {
  1734.     toggleLoop(true);
  1735.     document.getElementById("aq-loop-enable").checked = true;
  1736.   }
  1737.   if (obj.mastery) {
  1738.     toggleMastery(true);
  1739.     document.getElementById("aq-mastery-enable").checked = true;
  1740.   }
  1741.   if (obj.data.length > 0) togglePause();
  1742.   currentActionIndex = obj.index;
  1743.   for (const params of obj.data) {
  1744.     if (params.length == 6) {
  1745.       const newAction = new Action(...params.slice(0, 5));
  1746.       params[5].forEach((data) => {
  1747.         newAction.action.push({
  1748.           elementID: `AQ${nameIncrement++}`,
  1749.           data,
  1750.           start: setAction(...data),
  1751.           description: actionDescription(...data),
  1752.         });
  1753.       });
  1754.       addToQueue(newAction);
  1755.     } else {
  1756.       addToQueue(new Action(...params));
  1757.     }
  1758.   }
  1759.   updateQueue();
  1760. }
  1761.  
  1762. function setCurrentAction(id) {
  1763.   const index = actionQueueArray.findIndex((a) => a.elementID == id);
  1764.   if (index >= 0) {
  1765.     currentActionIndex = index;
  1766.     updateQueue();
  1767.   }
  1768. }
  1769.  
  1770. function importActions() {
  1771.   const string = document.getElementById("aq-pastebin").value;
  1772.   if (!queuePause && actionQueueArray.length === 0) togglePause();
  1773.   let arr = [];
  1774.   try {
  1775.     arr = JSON.parse(string.trim());
  1776.     if (!Array.isArray(arr)) return false;
  1777.     for (const params of arr) {
  1778.       try {
  1779.         let newAction = null;
  1780.         if (params.length == 10) {
  1781.           newAction = new Action(...params);
  1782.         } else if (params.length == 6) {
  1783.           newAction = new Action(...params.slice(0, 5));
  1784.           params[5].forEach((data) => {
  1785.             newAction.action.push({
  1786.               elementID: `AQ${nameIncrement++}`,
  1787.               data,
  1788.               start: setAction(...data),
  1789.               description: actionDescription(...data),
  1790.             });
  1791.           });
  1792.         }
  1793.         if (
  1794.           !newAction.description.includes("undefined") &&
  1795.           !newAction.description.includes("null") &&
  1796.           typeof newAction.trigger == "function" &&
  1797.           newAction.action.every((a) => {
  1798.             return (
  1799.               !a.description.includes("undefined") && !a.description.includes("null") && typeof a.start == "function"
  1800.             );
  1801.           })
  1802.         )
  1803.           addToQueue(newAction);
  1804.       } catch {}
  1805.     }
  1806.     document.getElementById("aq-pastebin").value = "";
  1807.     updateQueue();
  1808.   } catch (e) {
  1809.     console.error(e);
  1810.   } finally {
  1811.     return false;
  1812.   }
  1813. }
  1814.  
  1815. function downloadActions() {
  1816.   const saveData = [];
  1817.   for (const action of actionQueueArray) {
  1818.     let actionList = [];
  1819.     action.action.forEach((a) => actionList.push(a.data));
  1820.     saveData.push([...action.data, actionList]);
  1821.   }
  1822.   let file = new Blob([JSON.stringify(saveData)], {
  1823.     type: "text/plain",
  1824.   });
  1825.   if (window.navigator.msSaveOrOpenBlob) window.navigator.msSaveOrOpenBlob(file, "Melvor_Action_Queue.txt");
  1826.   else {
  1827.     var a = document.createElement("a"),
  1828.       url = URL.createObjectURL(file);
  1829.     a.href = url;
  1830.     a.download = "Melvor_Action_Queue.txt";
  1831.     document.body.appendChild(a);
  1832.     a.click();
  1833.     setTimeout(function () {
  1834.       document.body.removeChild(a);
  1835.       window.URL.revokeObjectURL(url);
  1836.     }, 0);
  1837.   }
  1838. }
  1839.  
  1840. function updateMasteryLvl(skill) {
  1841.   MASTERY[skill].xp.forEach((xp, i) => {
  1842.     let level = 1;
  1843.     while (xp >= lvlIndex[level + 1]) level++;
  1844.     masteryClone[skill].lvl[i] = level;
  1845.   });
  1846.   masteryClone[skill].completed = masteryClone[skill].lvl.every((a) => a == 99);
  1847.   masteryClone[skill].pool = MASTERY[skill].pool;
  1848. }
  1849.  
  1850. function manageMastery() {
  1851.   for (const skill in MASTERY) {
  1852.     //token claiming
  1853.     const maxPool = MASTERY[skill].xp.length * 500000;
  1854.     const bankID = getBankId(CONSTANTS.item[`Mastery_Token_${SKILLS[skill].name}`]);
  1855.     if (bankID !== -1 && bank[bankID].qty > 0 && MASTERY[skill].pool < maxPool * 0.999) {
  1856.       const maxTokens = Math.floor(((maxPool - MASTERY[skill].pool) * 1000) / maxPool);
  1857.       {
  1858.         let itemID = CONSTANTS.item[`Mastery_Token_${SKILLS[skill].name}`];
  1859.         let qtyToUse = Math.min(bank[bankID].qty, maxTokens);
  1860.         let totalXpToAdd = Math.floor(getMasteryPoolTotalXP(skill) * 0.001) * qtyToUse;
  1861.         addMasteryXPToPool(skill, totalXpToAdd, false, true);
  1862.         updateItemInBank(bankID, itemID, -qtyToUse);
  1863.       }
  1864.     }
  1865.  
  1866.     //exit if pool unchanged
  1867.     if (
  1868.       masteryClone[skill].pool == MASTERY[skill].pool &&
  1869.       MASTERY[skill].pool < maxPool * masteryConfig[skill].checkpoint
  1870.     )
  1871.       continue;
  1872.  
  1873.     //exit if maxed mastery
  1874.     updateMasteryLvl(skill);
  1875.     if (masteryClone[skill].completed) continue;
  1876.  
  1877.     //choose masteryID
  1878.     let masteryID = 0;
  1879.     if (!masteryConfig[skill].prio || masteryClone[skill].lvl.some((a) => a < 60)) {
  1880.       for (let i = 1; i < MASTERY[skill].xp.length; i++) {
  1881.         if (MASTERY[skill].xp[i] < MASTERY[skill].xp[masteryID]) masteryID = i;
  1882.       }
  1883.     } else {
  1884.       const noncompletedPrio = masteryConfig[skill].arr.filter(
  1885.         (a) => masteryClone[skill].lvl[a] < masteryConfig[skill].base
  1886.       );
  1887.       if (noncompletedPrio.length == 0) {
  1888.         //choose lowest of nonprio or lowest of prio if nonprio maxed
  1889.         let arr = MASTERY[skill].xp.map((a, i) => i);
  1890.         for (const x of masteryConfig[skill].arr) arr[x] = null;
  1891.         arr = arr.filter((a) => a != null);
  1892.         if (arr.every((a) => masteryClone[skill].lvl[a] >= 99)) arr = [...masteryConfig[skill].arr];
  1893.         arr.sort((a, b) => MASTERY[skill].xp[a] - MASTERY[skill].xp[b]);
  1894.         masteryID = arr[0];
  1895.       } else {
  1896.         for (const id of masteryConfig[skill].arr) {
  1897.           if (MASTERY[skill].xp[id] == lvlIndex[masteryConfig[skill].base]) {
  1898.             //choose lowest of [nonprio, noncompleted prio]
  1899.             let arr = MASTERY[skill].xp.map((a, i) => i);
  1900.             for (const x of masteryConfig[skill].arr) arr[x] = null;
  1901.             arr = arr.filter((a) => a != null);
  1902.             arr.push(...noncompletedPrio);
  1903.             arr.sort((a, b) => MASTERY[skill].xp[a] - MASTERY[skill].xp[b]);
  1904.             masteryID = arr[0];
  1905.             break;
  1906.           } else if (masteryClone[skill].lvl[id] < masteryConfig[skill].base) {
  1907.             masteryID = id;
  1908.             break;
  1909.           }
  1910.         }
  1911.       }
  1912.     }
  1913.     if (masteryID == undefined) continue;
  1914.  
  1915.     //level up chosen mastery
  1916.     if (
  1917.       MASTERY[skill].xp[masteryID] < 13034432 &&
  1918.       (MASTERY[skill].pool == maxPool ||
  1919.         MASTERY[skill].pool - lvlIndex[masteryClone[skill].lvl[masteryID] + 1] + MASTERY[skill].xp[masteryID] >
  1920.           masteryConfig[skill].checkpoint * maxPool)
  1921.     ) {
  1922.       if (masteryPoolLevelUp > 1) masteryPoolLevelUp = 1;
  1923.       let xp = lvlIndex[masteryClone[skill].lvl[masteryID] + 1] - MASTERY[skill].xp[masteryID];
  1924.       if (MASTERY[skill].pool >= xp) {
  1925.         addMasteryXP(skill, masteryID, 0, true, xp, false);
  1926.         addMasteryXPToPool(skill, -xp, false, true);
  1927.         updateSpendMasteryScreen(skill, masteryID);
  1928.         //showSpendMasteryXP(skill);
  1929.       }
  1930.       if (skill === CONSTANTS.skill.Fishing) {
  1931.         for (let i = 0; i < Fishing.areas.length; i++) {
  1932.           for (let f = 0; f < Fishing.areas[i].fish.length; f++) {
  1933.             if (Fishing.areas[i].fish[f] === masteryID) {
  1934.               updateFishingMastery(i, f);
  1935.               break;
  1936.             }
  1937.           }
  1938.         }
  1939.       }
  1940.     }
  1941.   }
  1942. }
  1943.  
  1944. function toggleMastery(start) {
  1945.   for (const skill in MASTERY) {
  1946.     masteryClone[skill] = {
  1947.       pool: MASTERY[skill].pool,
  1948.       lvl: [],
  1949.     };
  1950.     updateMasteryLvl(skill);
  1951.   }
  1952.   if (start && manageMasteryInterval === null) {
  1953.     manageMasteryInterval = setInterval(() => {
  1954.       manageMastery();
  1955.     }, 1000);
  1956.   } else if (!start) {
  1957.     clearInterval(manageMasteryInterval);
  1958.     manageMasteryInterval = null;
  1959.   }
  1960. }
  1961.  
  1962. function addToQueue(obj) {
  1963.   actionQueueArray.push(obj);
  1964.   document.getElementById("aq-item-container").insertAdjacentHTML(
  1965.     "beforeend",
  1966.     `<div class="aq-item" id="${obj.elementID}" draggable="true">
  1967.   <div class="aq-item-inner">
  1968.     <p style="margin: auto 0">${obj.description}</p>
  1969.     <div style="min-width: 170px; min-height: 39px; display: flex; justify-content: flex-end;">
  1970.       <button type="button" class="btn aq-arrow aq-grey" style="font-size: 0.875rem;">select</button>
  1971.       <button type="button" class="btn aq-arrow aq-grey" style="padding: 0 0.1rem 0.2rem 0.1rem;">+</button>
  1972.       <button type="button" class="btn aq-arrow aq-grey" style="padding:0 0.09rem;">
  1973.         <svg width="22" height="22" viewBox="0 0 24 24">
  1974.           <path fill-rule="evenodd" clip-rule="evenodd" d="M19.2929 9.8299L19.9409 9.18278C21.353 7.77064 21.353 5.47197 19.9409 4.05892C18.5287 2.64678 16.2292 2.64678 14.817 4.05892L14.1699 4.70694L19.2929 9.8299ZM12.8962 5.97688L5.18469 13.6906L10.3085 18.813L18.0201 11.0992L12.8962 5.97688ZM4.11851 20.9704L8.75906 19.8112L4.18692 15.239L3.02678 19.8796C2.95028 20.1856 3.04028 20.5105 3.26349 20.7337C3.48669 20.9569 3.8116 21.046 4.11851 20.9704Z" fill="currentColor"></path>
  1975.         </svg>
  1976.       </button>
  1977.       <button type="button" class="btn aq-delete btn-danger">X</button>
  1978.     </div>
  1979.   </div>
  1980.   <div style="min-height: 39px; padding-left: 10px;"></div>
  1981. </div>`
  1982.   );
  1983.  
  1984.   //add button clicks
  1985.   const buttons = document.getElementById(obj.elementID).children[0].children[1].children;
  1986.   buttons[0].addEventListener("click", () => setCurrentAction(obj.elementID));
  1987.   buttons[1].addEventListener("click", () => editQueue(obj.elementID, "add"));
  1988.   buttons[2].addEventListener("click", () => editQueue(obj.elementID, "triggers"));
  1989.   buttons[3].addEventListener("click", () => deleteAction(obj.elementID, "trigger"));
  1990.  
  1991.   //add tooltips
  1992.   tooltips[obj.elementID] = [
  1993.     tippy(document.getElementById(obj.elementID).children[0].children[1].children[0], {
  1994.       content: "Set as current trigger",
  1995.       animation: false,
  1996.     }),
  1997.     tippy(document.getElementById(obj.elementID).children[0].children[1].children[1], {
  1998.       content: "Add action",
  1999.       animation: false,
  2000.     }),
  2001.     tippy(document.getElementById(obj.elementID).children[0].children[1].children[2], {
  2002.       content: "Edit trigger",
  2003.       animation: false,
  2004.     }),
  2005.     tippy(document.getElementById(obj.elementID).children[0].children[1].children[3], {
  2006.       content: "Delete trigger & actions",
  2007.       animation: false,
  2008.     }),
  2009.   ];
  2010.   //add eventlisteners for dragging trigger block
  2011.   const element = document.getElementById(obj.elementID);
  2012.   element.addEventListener("dragstart", () => {
  2013.     element.classList.add("t-drag");
  2014.   });
  2015.   element.addEventListener("dragend", () => {
  2016.     reorderQueue();
  2017.     element.classList.remove("t-drag");
  2018.   });
  2019.  
  2020.   //append html for each action
  2021.   obj.action.forEach((action) => {
  2022.     document.getElementById(obj.elementID).children[1].insertAdjacentHTML(
  2023.       "beforeend",
  2024.       `<div id='${action.elementID}' class="aq-item-inner" draggable="true">
  2025.   <p style="margin: auto 0">${action.description}</p>
  2026.   <div style="min-width: 170px; min-height: 39px; display: flex; justify-content: flex-end;">
  2027.     <small style="display: none; margin: auto 0.25rem">action failed</small>
  2028.     <button type="button" class="btn aq-arrow aq-grey" style="padding:0 0.09rem;">
  2029.       <svg width="22" height="22" viewBox="0 0 24 24">
  2030.         <path fill-rule="evenodd" clip-rule="evenodd" d="M19.2929 9.8299L19.9409 9.18278C21.353 7.77064 21.353 5.47197 19.9409 4.05892C18.5287 2.64678 16.2292 2.64678 14.817 4.05892L14.1699 4.70694L19.2929 9.8299ZM12.8962 5.97688L5.18469 13.6906L10.3085 18.813L18.0201 11.0992L12.8962 5.97688ZM4.11851 20.9704L8.75906 19.8112L4.18692 15.239L3.02678 19.8796C2.95028 20.1856 3.04028 20.5105 3.26349 20.7337C3.48669 20.9569 3.8116 21.046 4.11851 20.9704Z" fill="currentColor"></path>
  2031.       </svg>
  2032.     </button>
  2033.     <button type="button" class="btn aq-delete btn-danger">X</button>
  2034.   </div>
  2035. </div>`
  2036.     );
  2037.     //add tooltips
  2038.     tooltips[action.elementID] = [
  2039.       tippy(document.getElementById(action.elementID).children[1].children[1], {
  2040.         content: "Edit Action",
  2041.         animation: false,
  2042.       }),
  2043.       tippy(document.getElementById(action.elementID).children[1].children[2], {
  2044.         content: "Delete Action",
  2045.         animation: false,
  2046.       }),
  2047.     ];
  2048.     //add eventlisteners for dragging actions
  2049.     const element = document.getElementById(action.elementID);
  2050.     element.addEventListener("dragstart", () => {
  2051.       element.classList.add("a-drag");
  2052.     });
  2053.     element.addEventListener("dragend", () => {
  2054.       element.classList.remove("a-drag");
  2055.     });
  2056.     //add button clicks
  2057.     const buttons = element.children[1].children;
  2058.     buttons[1].addEventListener("click", () => editQueue(action.elementID, "actions"));
  2059.     buttons[2].addEventListener("click", () => deleteAction(action.elementID, "action"));
  2060.   });
  2061.  
  2062.   //add eventlistener for dragging actions within trigger blocks
  2063.   const container = document.getElementById(obj.elementID).children[1];
  2064.   container.addEventListener("dragover", (e) => {
  2065.     e.preventDefault();
  2066.     const draggable = document.querySelector(".a-drag");
  2067.     if (!draggable) return;
  2068.     const afterElement = getDragAfterElement(container, e.clientY, ".aq-item-inner");
  2069.     if (afterElement == null) {
  2070.       container.appendChild(draggable);
  2071.     } else {
  2072.       container.insertBefore(draggable, afterElement);
  2073.     }
  2074.   });
  2075.  
  2076.   if (triggerCheckInterval === null && !queuePause) {
  2077.     currentActionIndex = 0;
  2078.     updateQueue();
  2079.     triggerCheckInterval = setInterval(() => {
  2080.       triggerCheck();
  2081.     }, 1000);
  2082.     updateTextColour("start");
  2083.   }
  2084. }
  2085.  
  2086. function deleteAction(id, type) {
  2087.   tooltips[id].forEach((a) => a.destroy());
  2088.   delete tooltips[id];
  2089.   if (type == "trigger") {
  2090.     const i = actionQueueArray.findIndex((a) => a.elementID == id);
  2091.     if (i < 0) return;
  2092.     for (const action of actionQueueArray[i].action) {
  2093.       tooltips[action.elementID].forEach((a) => a.destroy());
  2094.       delete tooltips[action.elementID];
  2095.     }
  2096.     //remove from array
  2097.     actionQueueArray.splice(i, 1);
  2098.     if (currentActionIndex > i) currentActionIndex--;
  2099.     updateQueue();
  2100.   } else if (type == "action") {
  2101.     let i = 0;
  2102.     for (const trigger of actionQueueArray) {
  2103.       i = trigger.action.findIndex((a) => a.elementID == id);
  2104.       if (i < 0) continue;
  2105.       trigger.action.splice(i, 1);
  2106.       break;
  2107.     }
  2108.   }
  2109.   //remove html
  2110.   const element = document.getElementById(id);
  2111.   if (element) element.remove();
  2112. }
  2113.  
  2114. function addAction(id, actionCategory, actionName, skillItem, skillItem2, qty) {
  2115.   let elementID = `AQ${nameIncrement++}`;
  2116.   let description = actionDescription(actionCategory, actionName, skillItem, skillItem2, qty);
  2117.   actionQueueArray
  2118.     .find((a) => a.elementID == id)
  2119.     .action.push({
  2120.       elementID,
  2121.       data: [actionCategory, actionName, skillItem, skillItem2, qty],
  2122.       start: setAction(actionCategory, actionName, skillItem, skillItem2, qty),
  2123.       description,
  2124.     });
  2125.   document.getElementById(id).children[1].insertAdjacentHTML(
  2126.     "beforeend",
  2127.     `<div id='${elementID}' class="aq-item-inner" draggable="true">
  2128.   <p style="margin: auto 0">${description}</p>
  2129.   <div style="min-width: 170px; min-height: 39px; display: flex; justify-content: flex-end;">
  2130.     <small style="display: none; margin: auto 0.25rem">action failed</small>
  2131.     <button type="button" class="btn aq-arrow aq-grey" style="padding:0 0.09rem;">
  2132.       <svg width="22" height="22" viewBox="0 0 24 24">
  2133.         <path fill-rule="evenodd" clip-rule="evenodd" d="M19.2929 9.8299L19.9409 9.18278C21.353 7.77064 21.353 5.47197 19.9409 4.05892C18.5287 2.64678 16.2292 2.64678 14.817 4.05892L14.1699 4.70694L19.2929 9.8299ZM12.8962 5.97688L5.18469 13.6906L10.3085 18.813L18.0201 11.0992L12.8962 5.97688ZM4.11851 20.9704L8.75906 19.8112L4.18692 15.239L3.02678 19.8796C2.95028 20.1856 3.04028 20.5105 3.26349 20.7337C3.48669 20.9569 3.8116 21.046 4.11851 20.9704Z" fill="currentColor"></path>
  2134.       </svg>
  2135.     </button>
  2136.     <button type="button" class="btn aq-delete btn-danger">X</button>
  2137.   </div>
  2138. </div>`
  2139.   );
  2140.   const element = document.getElementById(elementID);
  2141.   //add tooltips
  2142.   tooltips[elementID] = [
  2143.     tippy(element.children[1].children[1], {
  2144.       content: "Edit Action",
  2145.       animation: false,
  2146.     }),
  2147.     tippy(element.children[1].children[2], {
  2148.       content: "Delete Action",
  2149.       animation: false,
  2150.     }),
  2151.   ];
  2152.  
  2153.   //add eventlisteners for dragging
  2154.   element.addEventListener("dragstart", () => {
  2155.     element.classList.add("a-drag");
  2156.   });
  2157.   element.addEventListener("dragend", () => {
  2158.     element.classList.remove("a-drag");
  2159.   });
  2160.   //add button clicks
  2161.   const buttons = element.children[1].children;
  2162.   buttons[1].addEventListener("click", () => editQueue(elementID, "actions"));
  2163.   buttons[2].addEventListener("click", () => deleteAction(elementID, "action"));
  2164. }
  2165.  
  2166. function getDragAfterElement(container, y, type) {
  2167.   const not = type == "aq-item" ? ".t-drag" : ".a-drag";
  2168.   const elements = [...container.querySelectorAll(`${type}:not(${not})`)];
  2169.   return elements.reduce(
  2170.     (closest, child) => {
  2171.       const box = child.getBoundingClientRect();
  2172.       const offset = y - box.top - box.height / 2;
  2173.       if (offset < 0 && offset > closest.offset) {
  2174.         return { offset: offset, element: child };
  2175.       } else {
  2176.         return closest;
  2177.       }
  2178.     },
  2179.     { offset: Number.NEGATIVE_INFINITY }
  2180.   ).element;
  2181. }
  2182.  
  2183. function reorderQueue() {
  2184.   const targetIndex = actionQueueArray[currentActionIndex].elementID;
  2185.   //remove stray dragging classes
  2186.   document
  2187.     .getElementById("aq-item-container")
  2188.     .querySelectorAll(".a-drag")
  2189.     .forEach((a) => a.classList.remove("a-drag"));
  2190.   document
  2191.     .getElementById("aq-item-container")
  2192.     .querySelectorAll(".t-drag")
  2193.     .forEach((a) => a.classList.remove("t-drag"));
  2194.   //sort triggers
  2195.   const triggerOrder = {};
  2196.   [...document.getElementById("aq-item-container").children].forEach((item, index) => (triggerOrder[item.id] = index));
  2197.   actionQueueArray.sort((a, b) => triggerOrder[a.elementID] - triggerOrder[b.elementID]);
  2198.   //sort actions
  2199.   const actionList = actionQueueArray.reduce((a, b) => a.concat(b.action), []);
  2200.   let increment = -1;
  2201.   [...document.getElementById("aq-item-container").querySelectorAll(".aq-item-inner")].forEach((item) => {
  2202.     if (item.id == "") {
  2203.       increment++;
  2204.       actionQueueArray[increment].action = [];
  2205.     } else {
  2206.       actionQueueArray[increment].action.push(actionList.find((a) => a.elementID == item.id));
  2207.     }
  2208.   });
  2209.   //reorder currentActionIndex
  2210.   currentActionIndex = actionQueueArray.findIndex((a) => a.elementID == targetIndex);
  2211. }
  2212.  
  2213. function editQueue(id, type) {
  2214.   cancelEdit(); //reset form
  2215.   currentlyEditing = { id, type };
  2216.   let inputArray = [];
  2217.   let obj = options[type];
  2218.   //set inputArray
  2219.   if (type == "triggers") {
  2220.     inputArray = actionQueueArray.find((a) => a.elementID == id).data.filter((a) => a !== null && a !== "");
  2221.     document.getElementById("aq-edit-form").innerHTML = "Edit Trigger";
  2222.   } else if (type == "actions") {
  2223.     document.getElementById("aq-edit-form").innerHTML = "Edit Action";
  2224.     for (const item of actionQueueArray) {
  2225.       if (item.action.find((a) => a.elementID == id)) {
  2226.         inputArray = item.action.find((a) => a.elementID == id).data.filter((a) => a !== null && a !== "");
  2227.         break;
  2228.       }
  2229.     }
  2230.   } else {
  2231.     document.getElementById("aq-edit-form").innerHTML = "New Action";
  2232.     obj = options.actions;
  2233.   }
  2234.   //set datalist for category
  2235.   document.getElementById("aq-listC0").innerHTML = "";
  2236.   Object.keys(obj).forEach((e) => {
  2237.     document.getElementById("aq-listC0").insertAdjacentHTML("beforeend", `<option>${e}</option>`);
  2238.   });
  2239.  
  2240.   //populate text boxes to edit
  2241.   for (let i = 0; i < inputArray.length; i++) {
  2242.     let textBox;
  2243.     if (i == inputArray.length - 1 && /^\d{1,10}$/.test(inputArray[i])) {
  2244.       textBox = document.getElementById("aq-numC");
  2245.     } else {
  2246.       textBox = document.getElementById(`aq-textC${i}`);
  2247.       //set datalist
  2248.       document.getElementById(`aq-listC${i}`).innerHTML = "";
  2249.       Object.keys(obj).forEach((e) => {
  2250.         document.getElementById(`aq-listC${i}`).insertAdjacentHTML("beforeend", `<option>${e}</option>`);
  2251.       });
  2252.       obj = obj[inputArray[i]];
  2253.     }
  2254.     textBox.value = inputArray[i];
  2255.     textBox.type = "text";
  2256.     validInputs.C[i] = inputArray[i];
  2257.   }
  2258.   const y = Math.max(document.getElementById(id).getBoundingClientRect().top - 255, 0);
  2259.   document.getElementById("aq-edit-container").style.top = `${y}px`;
  2260.   document.getElementById("aq-edit-container").style.display = "";
  2261. }
  2262.  
  2263. function cancelEdit() {
  2264.   document.getElementById("aq-edit-container").style.display = "none";
  2265.   resetForm(["C"]);
  2266. }
  2267.  
  2268. function submitEdit() {
  2269.   const a = document.getElementById("aq-edit-form").innerHTML.includes("Trigger");
  2270.   const arr = [];
  2271.   for (let i = 0; i < validInputs.C.length; i++) arr.push(document.getElementById(`aq-textC${i}`).value);
  2272.   if (a) arr.pop();
  2273.   arr.push(document.getElementById(`aq-numC`).value);
  2274.   if (a) arr.splice(["≥", "≤", ""].includes(arr[2]) ? 3 : 2, 0, "");
  2275.  
  2276.   if (
  2277.     validateInput(
  2278.       a ? options.triggers : options.actions,
  2279.       arr.filter((a) => a !== "")
  2280.     )
  2281.   ) {
  2282.     if (currentlyEditing.type == "add") {
  2283.       addAction(currentlyEditing.id, ...arr);
  2284.     } else if (currentlyEditing.type == "triggers") {
  2285.       const action = new Action(...arr);
  2286.       const i = actionQueueArray.findIndex((a) => a.elementID == currentlyEditing.id);
  2287.       actionQueueArray[i].description = action.description;
  2288.       actionQueueArray[i].trigger = action.trigger;
  2289.       actionQueueArray[i].data = action.data;
  2290.       document.getElementById(currentlyEditing.id).children[0].children[0].innerHTML = action.description;
  2291.     } else {
  2292.       const description = actionDescription(...arr);
  2293.       const start = setAction(...arr);
  2294.       for (const item of actionQueueArray) {
  2295.         const action = item.action.find((a) => a.elementID == currentlyEditing.id);
  2296.         if (action) {
  2297.           action.data = arr;
  2298.           action.start = start;
  2299.           action.description = description;
  2300.           document.getElementById(currentlyEditing.id).children[0].innerHTML = description;
  2301.           break;
  2302.         }
  2303.       }
  2304.     }
  2305.     cancelEdit();
  2306.   }
  2307.   return false;
  2308. }
  2309.  
  2310. function updateMasteryConfig(changeSkill = true) {
  2311.   if (changeSkill) {
  2312.     if (masteryConfigChanges.skill != null) {
  2313.       updateMasteryPriority();
  2314.       let arr =
  2315.         masteryConfigChanges.arr == null
  2316.           ? [...masteryConfig[masteryConfigChanges.skill].arr]
  2317.           : [...masteryConfigChanges.arr];
  2318.       masteryConfig[masteryConfigChanges.skill] = {
  2319.         checkpoint: masteryConfigChanges.checkpoint,
  2320.         prio: masteryConfigChanges.prio,
  2321.         base: masteryConfigChanges.base,
  2322.         arr,
  2323.       };
  2324.     }
  2325.     const skill = document.getElementById("aq-skill-list").value;
  2326.     document.getElementById("aq-checkpoint-list").value = masteryConfig[skill].checkpoint.toString();
  2327.     document.getElementById("aq-mastery-strategy").value = masteryConfig[skill].prio.toString();
  2328.     document.getElementById("aq-base").disabled = !masteryConfig[skill].prio;
  2329.     document.getElementById("aq-mastery-array").disabled = !masteryConfig[skill].prio;
  2330.     document.getElementById("aq-base").value = masteryConfig[skill].base.toString();
  2331.     document.getElementById("aq-mastery-array").value = JSON.stringify(masteryConfig[skill].arr);
  2332.     masteryConfigChanges.skill = null;
  2333.   } else {
  2334.     masteryConfigChanges.skill = document.getElementById("aq-skill-list").value;
  2335.     masteryConfigChanges.checkpoint = parseFloat(document.getElementById("aq-checkpoint-list").value);
  2336.     masteryConfigChanges.prio = JSON.parse(document.getElementById("aq-mastery-strategy").value);
  2337.     masteryConfigChanges.base = parseInt(document.getElementById("aq-base").value);
  2338.     updateMasteryPriority();
  2339.     document.getElementById("aq-base").disabled = !JSON.parse(document.getElementById("aq-mastery-strategy").value);
  2340.     document.getElementById("aq-mastery-array").disabled = !JSON.parse(
  2341.       document.getElementById("aq-mastery-strategy").value
  2342.     );
  2343.   }
  2344. }
  2345.  
  2346. function updateMasteryPriority() {
  2347.   const string = document.getElementById("aq-mastery-array").value;
  2348.   let arr = null;
  2349.   try {
  2350.     arr = JSON.parse(string.trim());
  2351.   } catch {}
  2352.   if (!Array.isArray(arr)) return (masteryConfigChanges.arr = null);
  2353.   arr = [...new Set(arr)].filter((a) => typeof MASTERY[masteryConfigChanges.skill].xp[a] == "number");
  2354.   masteryConfigChanges.arr = [...arr];
  2355. }
  2356.  
  2357. function masteryPopup(open) {
  2358.   if (open) {
  2359.     masteryConfigChanges.skill = null;
  2360.     updateMasteryConfig();
  2361.     document.getElementById("aq-mastery-config-container").style.display = "";
  2362.   } else {
  2363.     updateMasteryConfig();
  2364.     document.getElementById("aq-mastery-config-container").style.display = "none";
  2365.   }
  2366. }
  2367.  
  2368. /**
  2369.  * Function to change active prayers
  2370.  * @param {Array} choice array of prayer IDs
  2371.  */
  2372. function changePrayers(choice = []) {
  2373.   for (const i of player.activePrayers.entries()) {
  2374.     choice.includes(i[0])
  2375.       ? choice.splice(
  2376.           choice.findIndex((a) => a == i[0]),
  2377.           1
  2378.         )
  2379.       : choice.unshift(i[0]);
  2380.   }
  2381.   for (const prayer of choice) player.togglePrayer(prayer);
  2382. }
  2383.  
  2384. /**
  2385.  * Function to delete every action
  2386.  */
  2387. function clearQueue() {
  2388.   for (let i = actionQueueArray.length - 1; i >= 0; i--) {
  2389.     deleteAction(actionQueueArray[i].elementID, "trigger");
  2390.   }
  2391. }
  2392.  
Add Comment
Please, Sign In to add comment