Automates farming
// ==UserScript== // @name Melvor Idle - AutoFarm // @description Automates farming // @version 1.5 // @namespace Visua // @match https://*.melvoridle.com/* // @exclude https://wiki.melvoridle.com* // @noframes // @grant none // ==/UserScript== /* jshint esversion: 6 */ ((main) => { var script = document.createElement('script'); script.textContent = `try { (${main})(); } catch (e) { console.log(e); }`; document.body.appendChild(script).parentNode.removeChild(script); })(() => { 'use strict'; function startAutoFarm() { const utils = { equipFromBank: function (itemId, quantity = 1) { if (!checkBankForItem(itemId)) { return false; } try { return combatManager.player.equipItem(itemId, combatManager.player.selectedEquipmentSet, 'Default', quantity); } catch (e) { console.error('AutoFarm: Error trying to equip item.', e); return false; } }, equipSwapState: { Ring: {}, Cape: {}, Gloves: {}, Quiver: {}, }, equipSwap: function (slotName, itemId) { if (this.equipSwapState[slotName].swapped) { return; } const currentlyEquippedItemId = combatManager.player.equipment.slots[slotName].item.id; const currentQuantity = combatManager.player.equipment.slots[slotName].quantity; let quantityToEquip = slotName === 'Quiver' ? getBankQty(itemId) : 1; if (this.equipFromBank(itemId, quantityToEquip)) { this.equipSwapState[slotName].originalId = currentlyEquippedItemId; this.equipSwapState[slotName].quantity = currentQuantity; this.equipSwapState[slotName].swapped = true; } }, equipSwapBack: function (slotName) { if (this.equipSwapState[slotName].swapped && (this.equipSwapState[slotName].originalId === -1 || this.equipFromBank(this.equipSwapState[slotName].originalId, this.equipSwapState[slotName].quantity))) { this.equipSwapState[slotName].originalId = undefined; this.equipSwapState[slotName].swapped = false; } }, }; const id = 'auto-farm'; const settingsVersion = 2; const patchTypes = ['allotments', 'herbs', 'trees']; const toPatchType = { ALLOTMENT: patchTypes[0], HERB: patchTypes[1], TREE: patchTypes[2] }; const priorityTypes = { custom: { id: 'custom', description: 'Custom priority', tooltip: 'Drag seeds to change their priority' }, mastery: { id: 'mastery', description: 'Highest mastery', tooltip: 'Seeds with maxed mastery are excluded<br>Click seeds to disable/enable them', }, replant: { id: 'replant', description: 'Replant', tooltip: 'Lock patches to their current seeds' }, quantity: { id: 'quantity', description: 'Lowest quantity', tooltip: 'Crops with the lowest quantity in the bank are planted', }, }; const allSeeds = { allotments: [...allotmentSeeds].sort((a, b) => b.level - a.level).map((s) => s.itemID), herbs: [...herbSeeds].sort((a, b) => b.level - a.level).map((s) => s.itemID), trees: [...treeSeeds].sort((a, b) => b.level - a.level).map((s) => s.itemID), }; const compostShopId = isItemInShop(CONSTANTS.item.Compost); let observer; let settings = { version: settingsVersion, disabledSeeds: {}, swapEquipment: true, }; patchTypes.forEach((patchType) => { settings[patchType] = { enabled: false, priorityType: priorityTypes.custom.id, priority: allSeeds[patchType], lockedPatches: {}, useGloop: true, }; }); function findNextSeed(patch, patchId) { // Find next seed in bank according to priority const patchType = toPatchType[patch.type]; const patchTypeSettings = settings[patchType]; const lockedSeed = patchTypeSettings.lockedPatches[patchId]; let priority = []; if (lockedSeed !== undefined) { priority = [lockedSeed]; } else if (patchTypeSettings.priorityType === priorityTypes.custom.id) { priority = patchTypeSettings.priority; } else if (patchTypeSettings.priorityType === priorityTypes.mastery.id) { priority = allSeeds[patchType] .filter((s) => !settings.disabledSeeds[s] && getSeedMasteryLevel(s) < 99) .sort((a, b) => getSeedMastery(b) - getSeedMastery(a)); } else if (patchTypeSettings.priorityType === priorityTypes.quantity.id) { priority = allSeeds[patchType] .filter((s) => !settings.disabledSeeds[s]) .sort((a, b) => getSeedCropQuantity(a) - getSeedCropQuantity(b)); } let nextSeed = -1; for (let k = 0; k < priority.length; k++) { const seedId = priority[k]; if (seedId !== -1 && skillLevel[CONSTANTS.skill.Farming] >= items[seedId].farmingLevel) { const bankId = getBankId(seedId); if (bankId !== -1 && bank[bankId].qty >= items[seedId].seedsRequired) { nextSeed = seedId; break; } } } return nextSeed; } function handlePatch(areaId, patchId) { const patch = newFarmingAreas[areaId].patches[patchId]; if (!settings[toPatchType[patch.type]].enabled || !patch.unlocked || !(patch.hasGrown || !patch.seedID)) { // AutoFarm disabled for patch type or patch not unlocked or still growing return; } if (patch.hasGrown) { // Harvest let grownId = items[patch.seedID].grownItemID; let bankId = getBankId(grownId); if (bankId === -1 && bank.length >= getMaxBankSpace()) { return; } harvestSeed(areaId, patchId); } const nextSeed = findNextSeed(patch, patchId); if (nextSeed === -1) { // No seeds available return; } if (!patch.gloop) { if (settings[toPatchType[patch.type]].useGloop && getBankQty(CONSTANTS.item.Weird_Gloop) > 1) { addGloop(areaId, patchId); } else if ( getSeedMasteryLevel(nextSeed) < 50 && getMasteryPoolProgress(CONSTANTS.skill.Farming) < masteryCheckpoints[1] ) { if (!playerModifiers.freeCompost) { buyCompost(); } addCompost(areaId, patchId, 5); } } selectedPatch = [areaId, patchId]; selectedSeed = nextSeed; plantSeed(); } function autoFarm() { let anyPatchReady = false; for (let i = 0; i < newFarmingAreas.length; i++) { for (let j = 0; j < newFarmingAreas[i].patches.length; j++) { const patch = newFarmingAreas[i].patches[j]; if ( settings[toPatchType[patch.type]].enabled && patch.unlocked && (patch.hasGrown || (!patch.seedID && findNextSeed(patch, j) !== -1)) ) { anyPatchReady = true; break; } } } if (anyPatchReady) { swapToFarmingEquipment(); for (let i = 0; i < newFarmingAreas.length; i++) { for (let j = 0; j < newFarmingAreas[i].patches.length; j++) { handlePatch(i, j); } } swapBackEquipment(); } patchTypes.forEach((patchType) => { if (settings[patchType].priorityType === priorityTypes.mastery.id) { orderMasteryPriorityMenu(patchType); } else if (settings[patchType].priorityType === priorityTypes.quantity.id) { orderQuantityPriorityMenu(patchType); } }); } function equipIfNotEquipped(slotName, itemId) { if (combatManager.player.equipment.slotMap.has(itemId)) { return true; } if (checkRequirements(items[itemId].equipRequirements) && checkBankForItem(itemId)) { utils.equipSwap(slotName, itemId); return true; } return false; } function swapToFarmingEquipment() { if (!settings.swapEquipment) { return; } equipIfNotEquipped('Ring', CONSTANTS.item.Aorpheats_Signet_Ring); equipIfNotEquipped('Cape', CONSTANTS.item.Cape_of_Completion) || equipIfNotEquipped('Cape', CONSTANTS.item.Max_Skillcape) || equipIfNotEquipped('Cape', CONSTANTS.item.Farming_Skillcape); equipIfNotEquipped('Gloves', CONSTANTS.item.Bobs_Gloves); equipIfNotEquipped('Quiver', CONSTANTS.item.Seed_Pouch); } function swapBackEquipment() { if (!settings.swapEquipment) { return; } Object.keys(utils.equipSwapState).forEach(slot => utils.equipSwapBack(slot)); } function buyCompost() { const qty = getBankQty(CONSTANTS.item.Compost); if (qty < 5) { buyQty = 5 - qty; if (gp > buyQty * items[CONSTANTS.item.Compost].buysFor) { buyShopItem(Object.keys(SHOP)[compostShopId[0]], compostShopId[1], true); } } } function getSeedMastery(seedId) { return MASTERY[CONSTANTS.skill.Farming].xp[items[seedId].masteryID[1]]; } function getSeedCropQuantity(seedId) { return getBankQty(items[seedId].grownItemID); } function getSeedMasteryLevel(seedId) { return getMasteryLevel(CONSTANTS.skill.Farming, items[seedId].masteryID[1]); } function orderMasteryPriorityMenu(patchType) { const menu = $(`#${id}-${patchType}-prioritysettings-mastery`); menu.children() .toArray() .filter((e) => getSeedMasteryLevel($(e).data('seed-id')) >= 99) .forEach((e) => $(e).remove()); const sortedMenuItems = menu .children() .toArray() .sort((a, b) => getSeedMastery($(b).data('seed-id')) - getSeedMastery($(a).data('seed-id'))); menu.append(sortedMenuItems); } function orderQuantityPriorityMenu(patchType) { const menu = $(`#${id}-${patchType}-prioritysettings-quantity`); const sortedMenuItems = menu .children() .toArray() .sort((a, b) => getSeedCropQuantity($(a).data('seed-id')) - getSeedCropQuantity($(b).data('seed-id'))); menu.append(sortedMenuItems); } function injectGUI() { if ($(`#${id}`).length) { return; } const disabledOpacity = 0.25; function saveSettings() { localStorage.setItem(`${id}-config-${currentCharacter}`, JSON.stringify(settings)); } function loadSettings() { const storedSettings = JSON.parse(localStorage.getItem(`${id}-config-${currentCharacter}`)); if (!storedSettings) { return; } settings = { ...settings, ...storedSettings }; // Update old settings if (settings.version === 1) { patchTypes.forEach((patchType) => { settings[patchType].useGloop = true; }); } settings.version = settingsVersion; saveSettings(); } loadSettings(); window.AUTOFARM_SETTINGS = settings; function createPatchTypeDiv(patchType) { function createSeedDiv(seedId) { const grownItem = items[items[seedId].grownItemID]; return ` <div class="btn btn-outline-secondary ${id}-priority-selector" data-seed-id="${seedId}" data-tippy-content="${grownItem.name}" style="margin: 2px; padding: 6px; float: left;"> <img src="${grownItem.media}" width="30" height="30"> </div>`; } function createPriorityTypeSelector(priorityType) { const prefix = `${id}-${patchType}-prioritytype`; const elementId = `${prefix}-${priorityType.id}`; return ` <div class="custom-control custom-radio custom-control-inline"> <input class="custom-control-input" type="radio" id="${elementId}" name="${prefix}" value="${priorityType.id }"${settings[patchType].priorityType === priorityType.id ? ' checked' : ''}> <label class="custom-control-label" for="${elementId}" data-tippy-content="${priorityType.tooltip }">${priorityType.description}</label> </div>`; } const prefix = `${id}-${patchType}`; const prioritySettings = `${prefix}-prioritysettings`; const seedDivs = allSeeds[patchType].map(createSeedDiv).join(''); return ` <div id="${prefix}" class="col-12 col-md-6 col-xl-4"> <div class="block block-rounded block-link-pop border-top border-farming border-4x" style="padding-bottom: 12px;"> <div class="block-header border-bottom"> <h3 class="block-title">AutoFarm ${patchType}</h3> <div class="custom-control custom-switch"> <input type="checkbox" class="custom-control-input" id="${prefix}-enabled" name="${prefix}-enabled"${settings[patchType].enabled ? ' checked' : '' }> <label class="custom-control-label" for="${prefix}-enabled">Enable</label> </div> </div> <div class="block-content" style="padding-top: 12px"> ${Object.values(priorityTypes).map(createPriorityTypeSelector).join('')} </div> <div class="block-content" style="padding-top: 12px"> <div id="${prioritySettings}-custom"> ${seedDivs} <button id="${prioritySettings}-reset" class="btn btn-primary locked" data-tippy-content="Reset order to default (highest to lowest level)" style="margin: 5px 0 0 2px; float: right;">Reset</button> </div> <div id="${prioritySettings}-mastery" class="${id}-seed-toggles"> ${seedDivs} </div> <div id="${prioritySettings}-quantity" class="${id}-seed-toggles"> ${seedDivs} </div> </div> </div> </div>`; } const autoFarmDiv = ` <div id="${id}" class="row row-deck gutters-tiny"> ${patchTypes.map(createPatchTypeDiv).join('')} </div>`; $('#farming-container .row:first').after($(autoFarmDiv)); function addStateChangeHandler(patchType) { $(`#${id}-${patchType}-enabled`).change((event) => { settings[patchType].enabled = event.currentTarget.checked; saveSettings(); loadFarmingArea(patchTypes.indexOf(patchType)); }); } patchTypes.forEach(addStateChangeHandler); function showSelectedPriorityTypeSettings(patchType) { for (const priorityType of Object.values(priorityTypes)) { $(`#${id}-${patchType}-prioritysettings-${priorityType.id}`).toggle( priorityType.id === settings[patchType].priorityType ); } } patchTypes.forEach(showSelectedPriorityTypeSettings); function lockPatch(patchType, patchId, seedId) { if (seedId !== undefined) { settings[patchType].lockedPatches[patchId] = seedId; } else { delete settings[patchType].lockedPatches[patchId]; } } function addPriorityTypeChangeHandler(patchType) { function lockAllPatches(auto = false) { const area = newFarmingAreas[patchTypes.indexOf(patchType)]; for (let i = 0; i < area.patches.length; i++) { lockPatch(patchType, i, auto ? undefined : area.patches[i].seedID || -1); } $(`.${id}-seed-selector`).remove(); addSeedSelectors(); } $(`#${id} input[name="${id}-${patchType}-prioritytype"]`).change((event) => { if (settings[patchType].priorityType === priorityTypes.replant.id) { lockAllPatches(true); } settings[patchType].priorityType = event.currentTarget.value; if (event.currentTarget.value === priorityTypes.replant.id) { lockAllPatches(); } showSelectedPriorityTypeSettings(patchType); saveSettings(); }); } patchTypes.forEach(addPriorityTypeChangeHandler); function makeSortable(patchType) { const elementId = `${id}-${patchType}-prioritysettings-custom`; Sortable.create(document.getElementById(elementId), { animation: 150, filter: '.locked', onMove: (event) => { if (event.related) { return !event.related.classList.contains('locked'); } }, onEnd: () => { settings[patchType].priority = [...$(`#${elementId} .${id}-priority-selector`)].map( (x) => +$(x).data('seed-id') ); saveSettings(); }, }); } patchTypes.forEach(makeSortable); function orderCustomPriorityMenu(patchType) { const priority = settings[patchType].priority; if (!priority.length) { return; } const menu = $(`#${id}-${patchType}-prioritysettings-custom`); const menuItems = [...menu.children()]; function indexOfOrInf(el) { let i = priority.indexOf(+el); return i === -1 ? Infinity : i; } const sortedMenu = menuItems.sort( (a, b) => indexOfOrInf($(a).data('seed-id')) - indexOfOrInf($(b).data('seed-id')) ); menu.append(sortedMenu); } function addPriorityResetClickHandler(patchType) { $(`#${id}-${patchType}-prioritysettings-reset`).on('click', () => { settings[patchType].priority = allSeeds[patchType]; orderCustomPriorityMenu(patchType); saveSettings(); }); } patchTypes.forEach(addPriorityResetClickHandler); $(`.${id}-seed-toggles div`).each((_, e) => { const toggle = $(e); const seedId = toggle.data('seed-id'); if (settings.disabledSeeds[seedId]) { toggle.css('opacity', disabledOpacity); } }); $(`.${id}-seed-toggles div`).on('click', (event) => { const toggle = $(event.currentTarget); const seedId = toggle.data('seed-id'); if (settings.disabledSeeds[seedId]) { delete settings.disabledSeeds[seedId]; } else { settings.disabledSeeds[seedId] = true; } const opacity = settings.disabledSeeds[seedId] ? disabledOpacity : 1; toggle.fadeTo(200, opacity); saveSettings(); }); patchTypes.forEach((patchType) => { orderCustomPriorityMenu(patchType); orderMasteryPriorityMenu(patchType); orderQuantityPriorityMenu(patchType); }); function createDropdown(patchType) { function createDropdownItem(name, icon, seedId) { return ` <button class="dropdown-item"${seedId !== undefined ? ` data-seed-id="${seedId}"` : '' } style="outline: none;"> <span style="margin-right: 12px; vertical-align: bottom;">${icon}</span>${name} </button>`; } return ` <div class="dropdown ${id}-seed-selector"> <button type="button" class="btn btn-outline-secondary dropdown-toggle" data-toggle="dropdown" style="padding-left: 8px; padding-right: 8px;"><span class="${id}-seed-selector-icon" style="margin-right: 6px; vertical-align: text-bottom; margin-top: 1px;"></span><span class="${id}-seed-selector-text"></span></button> <div class="dropdown-menu font-size-sm" style="border-color: #6c757d; border-radius: 0.25rem; padding: 0.25rem 0;"> ${createDropdownItem( 'Auto', '<img src="assets/media/main/settings_header.svg" width="20" height="20">' )} ${allSeeds[patchType] .map((seedId) => createDropdownItem( items[items[seedId].grownItemID].name, `<img src="${items[items[seedId].grownItemID].media}" width="20" height="20">`, seedId ) ) .join('')} ${createDropdownItem( 'None', '<i class="fa fa-ban" style="width: 20px; font-size: 20px; color: #c81f1f; vertical-align: middle;"></i>', -1 )} </div> </div>`; } function addSeedSelectors() { function updateDropdownSelection(patchType, patchId, dropdown) { dropdown.find('.dropdown-item.active').removeClass('active'); const button = dropdown.children('button'); const selectedSeed = settings[patchType].lockedPatches[patchId]; let selected; if (selectedSeed !== undefined) { selected = dropdown.find(`.dropdown-item[data-seed-id="${selectedSeed}"]`); button.find(`.${id}-seed-selector-text`).text(''); } else { selected = dropdown.find('.dropdown-item:not([data-seed-id])'); button.find(`.${id}-seed-selector-text`).text('Auto'); } selected.addClass('active'); button.find(`.${id}-seed-selector-icon`).html(selected.find('span').html()); } $('#farming-area-container h3').each((patchId, e) => { const header = $(e); if (header.siblings(`.${id}-seed-selector`).length) { // Seed selector already exists return; } const patchType = toPatchType[header.text().toUpperCase()]; if (patchType === undefined) { // Locked patch return; } if (settings[patchType].enabled) { // Remove game's seed picker header.siblings('.block-options').remove(); } const dropdown = $(createDropdown(patchType, patchId)); updateDropdownSelection(patchType, patchId, dropdown); dropdown.find('.dropdown-item').on('click', (event) => { lockPatch(patchType, patchId, $(event.currentTarget).data('seed-id')); saveSettings(); updateDropdownSelection(patchType, patchId, dropdown); }); header.after(dropdown); }); } addSeedSelectors(); if (observer) { observer.disconnect(); } observer = new MutationObserver(addSeedSelectors); observer.observe(document.getElementById('farming-area-container'), { childList: true }); tippy(`#${id} [data-tippy-content]`, { animation: false, allowHTML: true }); } injectGUI(); setInterval(autoFarm, 15000); } // function removeGUI() { // if (observer) { // observer.disconnect(); // } // $(`#${id} [data-tippy-content]`).each((_, e) => e._tippy.destroy()); // $(`#${id}`).remove(); // $(`.${id}-seed-selector`).remove(); // } function loadScript() { if (typeof confirmedLoaded !== 'undefined' && confirmedLoaded) { clearInterval(interval); console.log('Loading AutoFarm'); startAutoFarm(); } } const interval = setInterval(loadScript, 1000); });