Greasy Fork is available in English.

Melvor Corruption Roller

Bloop Bleep. I roll for corruptions.

// ==UserScript==
// @name         Melvor Corruption Roller
// @namespace    http://tampermonkey.net/
// @version      0.0.5
// @description  Bloop Bleep. I roll for corruptions.
// @author       NotCorgan#1234
// @match        https://melvoridle.com/*
// @match        https://www.melvoridle.com/*
// @match        https://test.melvoridle.com/*
// @exclude      https://melvoridle.com/update/*
// @exclude      https://www.melvoridle.com/update/*
// @exclude      https://test.melvoridle.com/update/*
// @grant        none
// @noframes
// ==/UserScript==

// Made for version 0.19.2
(function() {
    function injectScript(main) {
        var script = document.createElement('script');
        script.textContent = `try {(${main})();} catch (e) {console.log(e);}`;
        document.body.appendChild(script).parentNode.removeChild(script);
    }

    function script() {
        if (currentGamemode === 3) {
            items.forEach((item, i) => item.itemID = i);

            let __notifyPlayer = notifyPlayer;
            notifyPlayer = function() {
                let [skill, message, type = "success"] = [...arguments];
                if (skill == CONSTANTS.skill.Attack && message == 'Your item was destroyed :(' && type == "danger")
                    return;
                __notifyPlayer.apply(window, arguments);
            }

            let bannedModifiers = ["golbinRaidWaveSkipCostReduction", "golbinRaidIncreasedMinimumFood", "golbinRaidIncreasedMaximumAmmo", "golbinRaidIncreasedMaximumRunes", "golbinRaidPrayerUnlocked", "golbinRaidIncreasedPrayerLevel", "golbinRaidIncreasedPrayerPointsStart", "golbinRaidIncreasedPrayerPointsWave", "golbinRaidPassiveSlotUnlocked", "golbinRaidIncreasedStartingRuneCount", "golbinRaidStartingWeapon", "freeBonfires", "autoSlayerUnlocked", "increasedEquipmentSets", "dungeonEquipmentSwapping", "increasedTreeCutLimit", "increasedAttackRolls", "decreasedAttackRolls", "increasedBankSpaceShop", "decreasedBankSpaceShop", "increasedGPFromSales", "decreasedGPFromSales", ];
            let activeModifiers = Object.fromEntries(Object.entries(playerModifiersTemplate).filter(([mod, val]) => !bannedModifiers.includes(mod)));

            corruptionRollerSettings = new Proxy(JSON.parse(localStorage.getItem(`corruptionRoller-${currentCharacter}`) || JSON.stringify(Array.from(Array(11)).map((_, i) => []))), {
                get: function(target, id) {
                    return target[id];
                },
                set: function(target, id, value) {
                    target[id] = value
                    localStorage.setItem(`corruptionRoller-${currentCharacter}`, JSON.stringify(target))
                }
            });

            corruptionRollerCount = new Proxy(JSON.parse(localStorage.getItem(`corruptionRollerCount-${currentCharacter}`) || JSON.stringify(Array.from(Array(11)).fill(0))), {
                get: function(target, id) {
                    return target[id];
                },
                set: function(target, id, value) {
                    target[id] = value
                    localStorage.setItem(`corruptionRollerCount-${currentCharacter}`, JSON.stringify(target))
                }
            });

            let levelRequired = (item) => Math.max(item.attackLevelRequired || 0, item.defenceLevelRequired || 0, item.rangedLevelRequired || 0, item.magicLevelRequired || 0, item.slayerLevelRequired || 0)
            let itemSub = (item) => [
                ['Attack', 'attackLevelRequired'],
                ['Defence', 'defenceLevelRequired'],
                ['Ranged', 'rangedLevelRequired'],
                ['Magic', 'magicLevelRequired'],
                ['Slayer', 'slayerLevelRequired']
            ].map(([name, stat]) => item[stat] != undefined && `<br>Requires ${name} <small class='text-warning'>${item[stat]}</small>`).filter(Boolean)

            class CorruptionModal {
                constructor() {
                    this.modal = document.createElement('div');
                    this.modal.id = 'modal-corruption-items';
                    this.modal.className = 'modal';
                    this.modalDialog = document.createElement('div');
                    this.modalDialog.className = 'modal-dialog modal-xl';
                    this.modal.appendChild(this.modalDialog);
                    this.modalContent = document.createElement('div');
                    this.modalContent.className = 'modal-content';
                    this.modalDialog.appendChild(this.modalContent);
                    this.modalHeader = $(`<div class="block block-themed block-transparent mb-0"><div class="block-header bg-primary-dark"><h3 class="block-title">Corruption Items</h3><div class="block-options"><button type="button" class="btn-block-option" data-dismiss="modal" aria-label="Close"><i class="fa fa-fw fa-times"></i></button></div></div></div>`);
                    $(this.modalContent).append(this.modalHeader);
                    this.modalBody = $(`<div class="row"></div>`);
                    this.modalWrap = $(`<div class="block-content"></div`);
                    $(this.modalWrap).append(this.modalBody);
                    $(this.modalContent).append(this.modalWrap);
                    this.tts = [];
                    document.getElementById('page-container').appendChild(this.modal);
                }

                show = (i) => {
                    $(this.modalBody).html("");
                    if (this.tts.length > 0) this.tts.forEach(tt => tt.destroy());
                    this.tts = [];
                    items.filter(item => item.equipmentSlot !== undefined && item.sellsFor >= [0, 200, 10000, 400000, Infinity][i] && item.sellsFor < [0, 200, 10000, 400000, Infinity][i + 1]).sort((a, b) => (a.equipmentSlot == b.equipmentSlot ? levelRequired(a) - levelRequired(b) : a.equipmentSlot - b.equipmentSlot)).forEach(item => {
                        $(this.modalBody).append('<img class="skill-icon-sm" id="corruption-item-img-' + item.itemID + '" src="' + getItemMedia(item.itemID) + '">');
                        let tt = "<div class='text-center'>" + item.name + "<small class='text-info'>" + itemSub(item) + "</small></div>";
                        this.tts = this.tts.concat(tippy("#corruption-item-img-" + item.itemID, {
                            content: tt,
                            placement: "bottom",
                            allowHTML: true,
                            interactive: false,
                            animation: false,
                        }));
                    });
                    $(this.modal).modal("show");
                };
            }

            class AutoComplete {
                constructor(parent, db, [inputValue, valueValue] = []) {
                    this.parent = parent;
                    this.db = db;
                    this.inputElement = document.createElement('input');
                    this.valueElement = document.createElement('input');
                    this.skillElement = document.createElement('select');
                    this.removeElement = document.createElement('div');

                    let anyOpt = document.createElement('option');
                    anyOpt.value = -1;
                    anyOpt.innerHTML = "Any";
                    this.skillElement.appendChild(anyOpt);

                    Object.entries(SKILLS).forEach(([id, skill]) => {
                        let opt = document.createElement('option');
                        opt.value = parseInt(id);
                        opt.innerHTML = skill.name;
                        this.skillElement.appendChild(opt);
                    });

                    if (inputValue != undefined)
                        this.inputElement.value = inputValue;
                    if (valueValue != undefined) {
                        if (Array.isArray(valueValue)) {
                            this.skillElement.style.setProperty('display', 'inline-block');
                            this.skillElement.value = valueValue[0];
                            this.valueElement.value = valueValue[1];
                        } else {
                            this.skillElement.style.setProperty('display', 'none');
                            this.valueElement.value = valueValue;
                        }
                    } else {
                        this.skillElement.style.setProperty('display', 'none');
                    }
                    this.resultsElement = document.createElement('div');

                    this.parent.style.setProperty('text-align', 'left');

                    this.removeElement.style.setProperty('height', '30px');
                    this.removeElement.style.setProperty('display', 'inline-block');
                    this.removeElement.style.setProperty('padding', '5px');
                    this.removeElement.appendChild(document.createTextNode('X'));

                    this.inputElement.style.setProperty('height', '30px');
                    this.inputElement.style.setProperty('width', '350px');

                    this.skillElement.style.setProperty('height', '30px');

                    this.valueElement.style.setProperty('width', '40px');
                    this.valueElement.style.setProperty('height', '30px');

                    this.resultsElement.style.setProperty('position', 'relative');
                    this.resultsElement.style.setProperty('top', '30px;');
                    this.resultsElement.style.setProperty('min-height', '0px');
                    this.resultsElement.style.setProperty('max-height', '100px');
                    this.resultsElement.style.setProperty('transition', 'height 350ms ease');
                    this.resultsElement.style.setProperty('overflow-y', 'hidden');
                    this.resultsElement.style.setProperty('z-index', '99');
                    this.resultsElement.style.setProperty('background-color', '#222');
                    this.resultsElement.style.setProperty('border-radius', '5px');

                    this.parent.appendChild(this.inputElement);
                    this.parent.appendChild(this.valueElement);
                    this.parent.appendChild(this.skillElement);
                    this.parent.appendChild(this.removeElement);
                    this.parent.appendChild(this.resultsElement);

                    this.inputElement.addEventListener("keyup", this.updateAutoComplete);
                    this.inputElement.addEventListener("change", this.updateAutoComplete);
                    this.inputElement.addEventListener("focusin", this.updateAutoComplete);
                    this.inputElement.addEventListener("blur", (e) => {
                        if (e.relatedTarget != null && this.db.findIndex(mod => this.inputElement.value.toLowerCase() == mod.toLowerCase()) == -1) {
                            this.inputElement.value = '';
                        }
                        this.updateAutoComplete();
                    });
                    this.removeElement.addEventListener("mousedown", (e) => this.parent.parentNode.removeChild(this.parent));
                }

                getConfig = () => {
                    let idx = this.db.findIndex(mod => this.inputElement.value.toLowerCase() == mod.toLowerCase());
                    let mod = this.db[idx];

                    if (activeModifiers[mod] !== undefined && this.parent.parentNode != null) {
                        if (Array.isArray(activeModifiers[mod])) {
                            return [mod, [parseInt(this.skillElement.value) || 0, parseInt(this.valueElement.value) || 0]];
                        } else {
                            return [mod, parseInt(this.valueElement.value) || 0];
                        }
                    }
                }

                updateSkill = () => {
                    let idx = this.db.findIndex(mod => this.inputElement.value.toLowerCase() == mod.toLowerCase());
                    let mod = this.db[idx];
                    if (mod) {
                        if (activeModifiers[mod] !== undefined) {
                            if (Array.isArray(activeModifiers[mod])) {
                                this.skillElement.style.setProperty('display', 'inline-block');
                            } else {
                                this.skillElement.style.setProperty('display', 'none');
                            }
                        }
                    }
                }

                updateAutoComplete = e => {
                    this.updateSkill();
                    this.resultsElement.innerHTML = '';
                    this.resultsElement.style.height = '0px';
                    if (!this.inputElement.value || this.db.findIndex(mod => this.inputElement.value == mod) != -1) return;

                    var search = new RegExp(this.inputElement.value, "i");
                    let frag = document.createDocumentFragment();
                    this.db.forEach(mod => {
                        if (search.test(mod)) {
                            var div = document.createElement('div');
                            div.style.setProperty('height', '20px');
                            div.style.setProperty('width', '100%');
                            div.style.setProperty('text-align', 'left');
                            div.appendChild(document.createTextNode(mod));
                            div.addEventListener('mousedown', (e) => (this.inputElement.value = mod, this.resultsElement.innerHTML = '', this.resultsElement.style.height = '0px', this.inputElement.dispatchEvent(new Event('change'))));
                            frag.appendChild(div);
                        }
                    });

                    if (frag.children.length > 1 || frag.children.length == 1 && frag.firstElementChild.innerText != this.inputElement.value)
                        (this.resultsElement.style.height = '100px', this.resultsElement.appendChild(frag))
                }
            }

            Object.entries(corruptionRollerSettings).forEach(([slot, settings]) => {
                let el = $(`#corruption-equipment-slot-${slot}`)[0];
                if (!el) return;

                let btn = document.createElement('button');
                btn.className = 'btn btn-sm btn-dual text-combat-smoke';
                btn.id = `corruption-equipment-slot-${slot}-mods`


                /*

                    */
                tippy(btn, {
                    allowHTML: true,
                    onShow: (instance) => {
                        let count = Math.min(corruptionRollerCount[slot] || 0, corruptionRollerSettings[slot].length);
                        let html = `<h5 class="font-w600 font-size-sm mb-2">Corrupting for ${count} Modifier${count!=1?'s':''}</h5>`;
                        html += corruptionRollerSettings[slot].map(([modifier, value]) => printPlayerModifier(modifier, value))
                            .map(mod => `<h5 class="font-w400 font-size-sm mb-1 ${mod[1]}">${mod[0]}</h5>`).join('');

                        instance.setContent(html);
                    }
                })

                btn.addEventListener('click', (e) => {
                    e.preventDefault()
                    e.stopPropagation();
                    let mods = [];

                    let html = '' +
                        '<div class="corruption-mods">' +
                        ('<div class="corruption-mod-selector"></div>'.repeat(corruptionRollerSettings[slot].length)) +
                        '</div>' +
                        '<div class="corruption-mods-add">Add New</div>';
                    Swal.fire({
                        html: html,
                        width: 700,
                        onBeforeOpen: () => {
                            mods = [...$('.corruption-mod-selector')].map((el, i) => new AutoComplete(el, Object.keys(activeModifiers), corruptionRollerSettings[slot][i]));
                            $('.corruption-mods-add')[0].addEventListener('click', (e) => {
                                let el = document.createElement('div');
                                el.className = 'corruption-mod-selector';
                                $('.corruption-mods')[0].appendChild(el);

                                mods.push(new AutoComplete(el, Object.keys(activeModifiers)))
                            });
                        },
                        preConfirm: () => {
                            if (mods)
                                return mods;
                        }
                    }).then(data => {
                        if (data.isConfirmed) {
                            corruptionRollerSettings[slot] = data.value.map(value => value.getConfig()).filter(v => v != null)
                        }
                    });
                });

                let img = document.createElement('img');
                img.className = 'skill-icon-xxs';
                img.src = 'assets/media/main/corruption.svg';
                btn.appendChild(img);

                let count = document.createElement('select');
                Array.from(Array(5)).map(count => document.createElement('option')).map((el, i) => (el.value = i, el.innerHTML = i, el)).forEach(el => count.appendChild(el));
                count.value = corruptionRollerCount[slot];

                count.addEventListener('click', (e) => (e.preventDefault(), e.stopPropagation()))
                count.addEventListener('change', (e) => corruptionRollerCount[slot] = parseInt(e.target.value))
                btn.appendChild(count);

                //el.parentNode.firstElementChild.insertBefore(count, el.parentNode.firstElementChild.firstElementChild);
                el.parentNode.appendChild(btn)
                el.parentNode.appendChild(count);
            });

            let hasMod = (slot, mod, value = 0) => Object.entries(randomModifiers.equipment[slot]).reduce((acc, [rMod, val]) => {
                let modCount = getRandomModifierMaxValue(Object.keys(randomModifiers.equipment[slot]).length) - 1; // Guess the tier from number of mods on the item?

                if (rMod != mod)
                    return acc;
                if (Array.isArray(value) && Array.isArray(val) && value[0] != val[0][0] && value[0] != -1) // Wrong skill
                    return acc;
                if (Math.min(modCount, (Array.isArray(value) ? value[1] : value)) > (Array.isArray(val) ? val[0][1] : val)) // Rolled value is too low
                    return acc;
                return true;
            }, false);

            let hasQty = (slot) => equippedItems[slot] > 0 && getItemQtyRandomModifier(equippedItems[slot])[0] > 1;
            let hasGP = (slot) => gp >= getRandomModifierCost(slot) && equippedItems[slot] > 0

            let corruptEquipment = () => {
                equippedItems.forEach((item, slot) => {
                    if (randomModifiers.equipment[slot] == undefined || corruptionRollerSettings[slot] == undefined || corruptionRollerSettings[slot].length == 0 || corruptionRollerCount[slot] == 0)
                        return;

                    let modCount = corruptionRollerSettings[slot].map(([mod, val]) => hasMod(slot, mod, val)).filter(Boolean).length;
                    if (modCount >= Math.min(corruptionRollerCount[slot] || 0, corruptionRollerSettings[slot].length))
                        return;
                    if (hasQty(slot) && hasGP(slot))
                        getEquipmentCorruption(slot)
                })
            };

            setInterval(corruptEquipment, 100);

            let modal = new CorruptionModal();
            [...$('#aprilfools2021-container .block-content > div.row > .col-12')].forEach((el, i) => el.addEventListener('click', () => modal.show(i)))
        }
    }

    function loadScript() {
        if ((window.isLoaded && !window.currentlyCatchingUp) || (typeof unsafeWindow !== 'undefined' && unsafeWindow.isLoaded && !unsafeWindow.currentlyCatchingUp)) { // Only load script after game has opened
            clearInterval(scriptLoader);
            injectScript(script);
        }
    }

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