Melvor Astrology Reroller

Automates constellation modifier rerolling based on desired targets. Console only, no user interface. See the `astrologyRerollerExample` function for a usage example.

// ==UserScript==
// @name		Melvor Astrology Reroller
// @namespace	http://tampermonkey.net/
// @version		0.0.1
// @description	Automates constellation modifier rerolling based on desired targets. Console only, no user interface. See the `astrologyRerollerExample` function for a usage example.
// @author		GMiclotte
// @include		https://melvoridle.com/*
// @include		https://*.melvoridle.com/*
// @exclude		https://melvoridle.com/index.php
// @exclude		https://*.melvoridle.com/index.php
// @exclude		https://wiki.melvoridle.com/*
// @exclude		https://*.wiki.melvoridle.com/*
// @inject-into page
// @noframes
// @grant		none
// ==/UserScript==


((main) => {
    const script = document.createElement('script');
    script.textContent = `try { (${main})(); } catch (e) { console.log(e); }`;
    document.body.appendChild(script).parentNode.removeChild(script);
})(() => {
    window.AstrologyReroller = class {
        constructor() {
            this.index = undefined;
            this.constellation = undefined;
            this.limit = Infinity;
        }

        setConstellation(index) {
            this.index = index;
            this.constellation = Astrology.constellations[this.index];
            this.constellation = Astrology.constellations[this.index];
        }

        template(index) {
            const constellation = Astrology.constellations[index];
            const t = {
                standardMin: 0,
                standardMods: [],
                uniqueMin: 0,
                uniqueMods: [],
            };
            for (let i = 0; i < 2; i++) {
                const skill = constellation.skills[i];
                constellation.standardModifiers[i].forEach(m => t.standardMods.push([m, Skills[skill]]));
                constellation.uniqueModifiers[i].forEach(m => t.standardMods.push([m, Skills[skill]]));
            }
            return t;
        }

        isValidTarget(targets) {
            if (this.constellation === undefined) {
                return false;
            }
            if (targets.standardMin > 5) {
                console.error(`Invalid standard modifier target% ${targets.standardMin}`);
                return false
            }
            for (const mod of targets.standardMods) {
                let skillIndex = this.constellation.skills.indexOf(mod[1]);
                if (skillIndex === -1) {
                    skillIndex = this.constellation.skills.indexOf(Skills[mod[1]]);
                }
                if (skillIndex === -1) {
                    console.error(`Invalid skill id in standard modifier ${mod} of constellation ${this.constellation.name}`);
                    return false
                }
                if (!this.constellation.standardModifiers[skillIndex].includes(mod[0])) {
                    console.error(`Invalid standard modifier ${mod} of constellation ${this.constellation.name}`);
                    return false;
                }
            }
            if (targets.uniqueMin > 5) {
                console.error(`Invalid unique modifier target% ${targets.uniqueMin}`);
                return false
            }
            for (const mod of targets.uniqueMods) {
                let skillIndex = this.constellation.skills.indexOf(mod[1]);
                if (skillIndex === -1) {
                    skillIndex = this.constellation.skills.indexOf(Skills[mod[1]]);
                }
                if (skillIndex === -1) {
                    console.error(`Invalid skill id in unique modifier ${mod} of constellation ${this.constellation.name}`);
                    return false
                }
                if (!this.constellation.uniqueModifiers[skillIndex].includes(mod[0])) {
                    console.error(`Invalid unique modifier ${mod} of constellation ${this.constellation.name}`);
                    return false;
                }
            }
            return true;
        }

        reroll(targets) {
            if (targets === undefined) {
                targets = this.targets[this.index];
            }
            const rerolls = {
                oneStandard: 0,
                oneUnique: 0,
                // reroll all until at least one hit
                ...this.rerollAll(targets),
            }
            if (!this.isValidTarget(targets)) {
                return rerolls;
            }
            // reroll single standard until hit
            let notMet = this.checkStandard(targets);
            while (notMet.length > 0) {
                // check dust
                const dust = Items.Stardust;
                const costs = new Costs();
                const qty = game.astrology.rerollIndividualQuantity * notMet.length;
                costs.addItem(dust, qty);
                if (!costs.checkIfOwned()) {
                    console.warn(`Insufficient ${items[dust].name} for ${notMet.length} individual rerolls, need ${qty}`);
                    break;
                }
                // reroll
                notMet.forEach(modID => game.astrology.rerollSpecificStandardModifier(this.constellation, modID));
                rerolls.oneStandard += notMet.length;
                notMet = this.checkStandard(targets);
                if (rerolls.oneStandard > this.limit) {
                    break;
                }
            }
            // reroll single unique until hit
            notMet = this.checkUnique(targets);
            while (notMet.length > 0) {
                // check dust
                const dust = Items.Golden_Stardust;
                const costs = new Costs();
                const qty = game.astrology.rerollIndividualQuantity * notMet.length;
                costs.addItem(dust, qty);
                if (!costs.checkIfOwned()) {
                    console.warn(`Insufficient ${items[dust].name} for ${notMet.length} individual rerolls, need ${qty}`);
                    break;
                }
                // reroll
                notMet.forEach(modID => game.astrology.rerollSpecificUniqueModifier(this.constellation, modID));
                rerolls.oneUnique += notMet.length;
                notMet = this.checkUnique(targets);
                if (rerolls.oneUnique > this.limit) {
                    break;
                }
            }
            // compute star dust used
            rerolls.Stardust = rerolls.oneStandard * game.astrology.rerollIndividualQuantity + rerolls.allStandard * Astrology.rerollAllQuantity;
            rerolls.Golden_Stardust = rerolls.oneUnique * game.astrology.rerollIndividualQuantity + rerolls.allUnique * Astrology.rerollAllQuantity;
            // return number of rerolls
            return rerolls;
        }

        rerollAll(targets) {
            if (targets === undefined) {
                targets = this.targets[this.index];
            }
            const rerolls = {
                allStandard: 0,
                allUnique: 0,
            }
            if (!this.isValidTarget(targets)) {
                return rerolls;
            }
            // reroll all standard until at least one hit
            let notMet = this.checkStandard(targets);
            while (notMet.length === game.astrology.constellationModifiers.get(this.constellation).standard.length) {
                // check dust
                const dust = Items.Stardust;
                const costs = new Costs();
                costs.addItem(dust, Astrology.rerollAllQuantity);
                if (!costs.checkIfOwned()) {
                    console.warn(`Insufficient ${items[dust].name} to reroll all, need ${Astrology.rerollAllQuantity}`);
                    break;
                }
                // reroll
                game.astrology.rerollAllStandardModifiers(this.constellation);
                rerolls.allStandard++;
                notMet = this.checkStandard(targets);
                if (rerolls.allStandard > this.limit) {
                    break;
                }
            }
            // reroll all unique until at least one hit.
            notMet = this.checkUnique(targets);
            while (notMet.length === game.astrology.constellationModifiers.get(this.constellation).unique.length) {
                // check dust
                const dust = Items.Golden_Stardust;
                const costs = new Costs();
                costs.addItem(dust, Astrology.rerollAllQuantity);
                if (!costs.checkIfOwned()) {
                    console.warn(`Insufficient ${items[dust].name} to reroll all, need ${Astrology.rerollAllQuantity}`);
                    break;
                }
                // reroll
                game.astrology.rerollAllUniqueModifiers(this.constellation);
                rerolls.allUnique++;
                notMet = this.checkUnique(targets);
                if (rerolls.allUnique > this.limit) {
                    break;
                }
            }
            // return number of rerolls
            return rerolls;
        }

        checkStandard(targets) {
            const cmods = game.astrology.constellationModifiers.get(this.constellation).standard;
            return this.checkAllTarget(cmods, targets.standardMin, targets.standardMods);
        }

        checkUnique(targets) {
            const cmods = game.astrology.constellationModifiers.get(this.constellation).unique;
            return this.checkAllTarget(cmods, targets.uniqueMin, targets.uniqueMods);
        }

        checkAllTarget(cmods, min, mods) {
            let notMet = [];
            for (let i = 0; i < cmods.length; i++) {
                if (!this.checkTarget(cmods[i], min, mods)) {
                    notMet.push(i);
                }
            }
            return notMet;
        }

        checkTarget(cmod, min, mods) {
            if (mods.length === 0) {
                return true;
            }
            for (const t of mods) {
                if (cmod.key === t[0]) {
                    if (cmod.value !== undefined) {
                        return cmod.value >= min;
                    }
                    if (cmod.values[0][0] === Skills[t[1]] || cmod.values[0][0] === t[1]) { //skill id and skill name are both checked
                        return cmod.values[0][1] >= min;
                    }
                }
            }
            return false;
        }
    }


    // example illustrating how to use this
    window.astrologyRerollerExample = function () {
        // create object
        const reroller = new AstrologyReroller();

        // set the constellation to reroll
        reroller.setConstellation(5);

        // return targets template
        reroller.template(reroller.index);

        // set targets
        reroller.targets = {};
        reroller.targets[reroller.index] = {
            // minimal target to stop rolling
            standardMin: 5,
            // list of standard mods you want to roll, format is [modifier name, skill name]
            standardMods: [
                ["increasedHiddenSkillLevel", "Ranged"], // you can pass the skill name
                ["increasedGlobalAccuracy", Skills.Ranged], // you can pass the skill ID
            ],
            // minimal target % to stop rolling
            uniqueMin: 5,
            // list of unique mods you want to roll, format is [modifier name, skill name]
            uniqueMods: [
                ["increasedRangedStrengthBonus", "Ranged"],
            ]
        }

        // reroll until targets met
        return reroller.reroll();
    }

    function loadScript() {
        if (typeof confirmedLoaded !== typeof undefined && confirmedLoaded) {
            // Only load script after game has opened
            clearInterval(scriptLoader);
        }
    }

    const scriptLoader = setInterval(loadScript, 200);
});