Skip to content

Instantly share code, notes, and snippets.

@jwhett
Last active October 7, 2020 16:18
Show Gist options
  • Save jwhett/825b3b058f706ed632a42577a42d8ef3 to your computer and use it in GitHub Desktop.
Save jwhett/825b3b058f706ed632a42577a42d8ef3 to your computer and use it in GitHub Desktop.
Melvor QOL Combined
// ==UserScript==
// @name MelvorQOL
// @namespace jwhett
// @version 1.1
// @description MelvorIdle QOL improvements
// @author Josh Whetton
// @match https://*.melvoridle.com/*
// @grant none
// ==/UserScript==
/* eslint-disable func-names, no-console */
function Watcher(fn, interval, type, ...args) {
this.fn = fn;
this.interval = interval;
this.type = type;
this.args = args;
}
Watcher.prototype.start = function () {
if (this.intervalID != null) return; // either null or undefined
this.intervalID = setInterval(this.fn, this.interval, ...this.args);
this.startTime = new Date().getTime();
};
Watcher.prototype.stop = function () {
if (this.intervalID == null) return; // either null or undefined
clearInterval(this.intervalID);
this.intervalID = null;
console.log(`Ran for ${this.duration()} seconds`);
};
Watcher.prototype.restart = function () {
this.stop();
this.start();
};
Watcher.prototype.duration = function () {
const d = Math.floor((new Date().getTime() - this.startTime) / 1000);
/* eslint-disable no-restricted-globals */
if (isNaN(d)) {
return 0;
}
return d;
/* eslint-enable no-restricted-globals */
};
/* eslint-disable no-undef, no-console, max-len, no-plusplus, no-unused-vars */
function MyFood(id, name, qty) {
this.id = id;
this.name = name;
this.qty = qty;
this.MAX = 6969696969;
}
function allOfTypeInItems(t) {
return items.filter((i) => i.type === t);
}
function allCraftingItems() {
return items.filter((i) => items[i].craftingID !== undefined);
}
function allOfTypeInBank(t) {
return bank.filter((i) => (i.type === t && !i.locked));
}
function itemInBank(id) {
return bank.filter((i) => i.id === id).pop();
}
function sellAllOfType(t) {
const things = allOfTypeInBank(t);
things.forEach((thing) => {
try {
sellItem(thing.id);
} catch (err) {
console.error(`oops! hit an error when selling an item: ${err}`);
}
});
}
function sellAllOfNameSubstring(s) {
const things = bank.filter((i) => i.name.includes(s));
things.forEach((thing) => {
try {
sellItem(thing.id);
} catch (err) {
console.error(`oops! hit an error when selling an item: ${err}`);
}
});
}
function learnTokens() {
const tokens = allOfTypeInBank('Token');
tokens.forEach((token) => {
for (let i = 0; i < token.qty; i++) {
try {
claimBankToken(currentBank, token.id);
} catch (err) {
console.error(`oops! hit an error when learning a token: ${err}`);
}
}
});
}
function buryBones() {
const bones = allOfTypeInBank('Bones');
bones.forEach((bone) => {
if (bone.qty >= 10) {
let cname = bone.name.replaceAll(' ', '_');
try {
buryItem(currentBank, CONSTANTS.item[cname], 6969696969);
} catch (err) {
console.error(`oops! hit an error when burying bones: ${err}`);
}
}
});
}
function haveMaterialsForCrafting(item) {
const required = [];
let haveAllRequired;
item.craftReq.forEach((req) => {
required.push(checkBankForItem(req.id));
});
if (required.some((thing) => thing === false)) {
haveAllRequired = false;
} else {
haveAllRequired = true;
}
/* eslint-disable object-shorthand */
return { haveAllRequired: haveAllRequired, forItem: item };
/* eslint-enable object-shorthand */
}
function findFood() {
const foodList = [];
const allFoodItems = allOfTypeInBank('Food');
allFoodItems.forEach((food) => {
foodList.push(new MyFood(food.id, food.name, food.qty));
});
return foodList;
}
function isOutOfEquippedFood() {
return equippedFood[0].itemID === equippedFood[1].itemID && equippedFood[0].itemID === equippedFood[2].itemID;
}
function getEquippedFoodCount() { return equippedFood[currentCombatFood].qty; }
function equipNextFood() {
for (let i = 0; i < equippedFood.length; i++) {
try {
if (equippedFood[i].qty > 0) {
selectEquippedFood(i);
return true;
}
} catch (err) {
console.error(`oops! we hit an error equipping next food: ${err}`);
}
}
return false;
}
function foodTracker() {
if (!isInCombat) return; // we're not in combat, don't need to watch food
if (getEquippedFoodCount() > 0) return; // we have equipped food to eat
const foodList = findFood();
if (isOutOfEquippedFood() && foodList.length === 0) { // completely out of food
try {
stopCombat(false, true, true); // death, stopDungeon, runAway
console.log(`Dropped out of combat due to lack of food at ${new Date()}`);
} catch (err) {
console.error(`oops! hit an error when stopping combat: ${err}`);
}
} else if (equipNextFood()) { // we have food in pocket, but need to equip it
/* eslint-disable no-useless-return */
return; // We successfully swapped to equipped food
/* eslint-enable no-useless-return */
} else {
const f = foodList.pop();
try {
equipFood(currentBank, f.id, f.qty);
} catch (err) {
console.error(`oops! hit an error when equipping food from bank: ${err}`);
}
}
}
/* eslint-disable no-undef, no-console, no-unused-vars, no-restricted-syntax, max-len, no-plusplus */
function getSeeds() {
return items.filter((item) => item.type === 'Seeds');
}
function patchHasGrown(p) {
return p.hasGrown && p.seedID !== 0;
}
function patchIsEmpty(p) {
return !p.hasGrown && p.seedID === 0;
}
function patchIsReady(p) {
return ((patchHasGrown(p)) || (patchIsEmpty(p)));
}
function getConstantIDByName(n) {
const cname = n.replaceAll(' ', '_').substring(0, n.length - 1);
if (CONSTANTS.item[cname] === undefined) {
/* eslint-disable prefer-template */
return CONSTANTS.item[cname + 's'];
/* eslint-enable prefer-template */
}
return CONSTANTS.item[cname];
}
function getNextSeedIDByTier(t) {
/* eslint-disable arrow-body-style */
for (let s of allOfTypeInBank('Seeds').sort((a, b) => { return b.qty - a.qty; })) {
/* eslint-enable arrow-body-style */
if (items[getConstantIDByName(s.name)].tier === t.substring(0, t.length - 1)) return getConstantIDByName(s.name);
}
return undefined;
}
function reapAndSow() {
for (let locationID = 0; locationID < newFarmingAreas.length; locationID++) {
for (let patchID = 0; patchID < newFarmingAreas[locationID].patches.length; patchID++) {
const patch = newFarmingAreas[locationID].patches[patchID];
selectedPatch = [newFarmingAreas[locationID].id, patchID]; // Melvor global
selectedSeed = 0; // Melvor global; seed 0 is empty
// can we plant?
/* eslint-disable no-continue */
if (!patch.unlocked) continue;
if (!patchIsReady(patch)) continue;
/* eslint-enable no-continue */
// what seed do i plant?
if (patchIsEmpty(patch)) {
// no harvest in this block
selectedSeed = getNextSeedIDByTier(newFarmingAreas[locationID].areaName);
} else {
// try to plant the same seed
// we'll need to harvest before leaving block
if (checkBankForItem(patch.seedID)) {
selectedSeed = patch.seedID;
} else {
// don't have the same seed to plant
selectedSeed = getNextSeedIDByTier(newFarmingAreas[locationID].areaName);
}
try {
harvestSeed(locationID, patchID);
} catch (err) {
console.error(`oops! hit an issue harvesting: ${err}`);
}
}
if (selectedSeed === undefined) {
return; // don't have a seed to plant
}
try {
addGloop(locationID, patchID);
plantSeed();
} catch (err) {
console.error(`oops! hit an issue planting seeds: ${err}`);
}
}
}
}
/* eslint-disable no-unused-vars, no-undef */
function combatAutoEat() {
const missingHP = maxHitpoints - combatData.player.hitpoints;
if (equippedFood[currentCombatFood].qty > 0 && combatData.player.hitpoints < maxHitpoints) {
if (missingHP >= items[equippedFood[currentCombatFood].itemID].healsFor * 10) {
eatFood();
}
}
if (equippedFood[currentCombatFood].qty === 0) {
lootAll();
stopCombat();
}
}
/* eslint-disable no-unused-vars, no-undef, no-console */
function fireWatcher() {
if (!isCooking || cookingFireActive) return;
try {
lightCookingFire();
} catch (err) {
console.error(`oops! couldn't start a cooking fire: ${err}`);
}
}
/* eslint-disable no-unused-vars, no-undef */
function saveAndDownload() {
downloadSave(); // download locally
forceSync(); // save to the cloud
}
/* eslint-disable no-undef, no-restricted-syntax, no-unused-vars, no-console */
const watchers = {};
for (let t of ['Junk', 'Gem', 'Havest', 'Logs']) {
watchers[t] = new Watcher(sellAllOfType, 120000, t, t);
}
watchers.Bones = new Watcher(buryBones, 15000, 'Bury bones');
watchers.Sort = new Watcher(sortBank, 60000, "Bank Sorting");
watchers.Looting = new Watcher(lootAll, 15000, 'Auto-looting');
// watchers.Bonfire = new Watcher(lightBonfire, 1000, "XP Bonfire");
watchers.Token = new Watcher(learnTokens, 15000, 'Learn tokens');
watchers.Farming = new Watcher(reapAndSow, 60000, 'Auto-farming');
// watchers.Fire = new Watcher(lightCookingFire, 250, 'Cooking fire');
watchers.Eating = new Watcher(combatAutoEat, 1500, 'Combat eating');
// watchers.Food = new Watcher(foodTracker, 10000, 'Combat food swap');
// watchers.Save = new Watcher(saveAndDownload, 1600000, 'Game saving');
watchers.Burnt = new Watcher(sellAllOfNameSubstring, 5000, 'Burnt food seller', 'Burnt');
watchers.Feathers = new Watcher(sellAllOfNameSubstring, 5000, 'Feather seller', 'Feathers');
function showWatchers() {
console.log(`Status as of ${new Date()}:`);
for (let w in watchers) {
if (watchers[w].duration() > 0) {
console.log(` => [ RUNNING ] ${watchers[w].type} running for ${watchers[w].duration()} seconds.`);
} else {
console.log(` => [ OFFLINE ] ${watchers[w].type}`);
}
}
}
for (let watcher in watchers) {watchers[watcher].start()}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment