Melvor Show Modifiers

Adds a button to show all your modifiers

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        Melvor Show Modifiers
// @namespace   http://tampermonkey.net/
// @version     0.2.13
// @description Adds a button to show all your modifiers
// @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);
})(() => {

    class ShowModifiers {

        constructor(name, logName, check = true) {
            this.name = name;
            this.logName = logName;
            // increased - decreased
            this.creasedModifiers = {
                // modifiers that do not directly relate to skilling
                misc: [
                    'BankSpace',
                    'BankSpaceShop',
                ],
                // modifiers that relate to both combat and non-combat skilling
                skilling: [
                    'ChanceToPreservePotionCharge',
                    'ChanceToDoubleItemsGlobal',
                    'GPFromSales',
                    'GPGlobal',
                    'GlobalSkillXP',
                    'HiddenSkillLevel',
                    'PotionChargesFlat',
                    'SkillXP',
                    'SummoningChargePreservation',
                ],
                // modifiers that only relate to combat and are not classified in a finer group
                combat: [
                    'AttackRolls',
                    'ChanceToDoubleLootCombat',
                    'DamageToAllMonsters',
                    'DamageToBosses',
                    'DamageToCombatAreaMonsters',
                    'DamageToDungeonMonsters',
                    'GPFromMonsters',
                    'GPFromMonstersFlat',
                    'GlobalAccuracy',
                    'MaxHitFlat',
                    'MaxHitPercent',
                    'MaxHitpoints',
                    'MinHitBasedOnMaxHit',
                    'MonsterRespawnTimer',
                    'AttackInterval',
                    'AttackIntervalPercent',
                    'ChanceToApplyBurn',
                    'GPOnEnemyHit',
                    'BleedLifesteal',
                    'BurnLifesteal',
                    'PoisonLifesteal',
                    'FlatMinHit',
                    'DamageTaken',
                    'GlobalEvasion',
                ],
                // modifiers that relate to healing
                hitpoints: [
                    'AutoEatEfficiency',
                    'AutoEatHPLimit',
                    'AutoEatThreshold',
                    'FoodHealingValue',
                    'HPRegenFlat',
                    'HitpointRegeneration',
                    'Lifesteal',
                    'FlatMaxHitpoints',
                ],
                // modifiers that relate to defence
                defence: [
                    'DamageReduction',
                    'MagicEvasion',
                    'MeleeEvasion',
                    'RangedEvasion',
                    'ReflectDamage',
                    'FlatReflectDamage',
                    'RolledReflectDamage',
                    'DamageReductionPercent',
                ],
                // modifiers that relate to using melee attacks
                attack: [],
                strength: [],
                melee: [
                    'MeleeAccuracyBonus',
                    'MeleeStrengthBonus',
                    'MeleeMaxHit',
                    'MeleeLifesteal',
                    'MeleeCritChance',
                ],
                // modifiers that relate to using ranged attacks
                ranged: [
                    'AmmoPreservation',
                    'RangedAccuracyBonus',
                    'RangedStrengthBonus',
                    'RangedMaxHit',
                    'RangedLifesteal',
                    'RangedCritChance',
                ],
                // modifiers that relate to using magic attacks
                magic: [
                    'MagicAccuracyBonus',
                    'MagicDamageBonus',
                    'MinAirSpellDmg',
                    'MaxAirSpellDmg',
                    'MinEarthSpellDmg',
                    'MaxEarthSpellDmg',
                    'MinFireSpellDmg',
                    'MaxFireSpellDmg',
                    'MinWaterSpellDmg',
                    'MaxWaterSpellDmg',
                    'RunePreservation',
                    'MagicMaxHit',
                    'MagicLifesteal',
                    'MagicCritChance',
                ],
                // modifiers that relate to slayer tasks, areas, or monsters
                slayer: [
                    'DamageToSlayerAreaMonsters',
                    'DamageToSlayerTasks',
                    'SlayerAreaEffectNegationFlat',
                    'SlayerCoins',
                    'SlayerTaskLength',
                ],
                // modifiers that relate to prayer
                prayer: [
                    'ChanceToPreservePrayerPoints',
                    'FlatPrayerCostReduction',
                    'PrayerCost',
                ],
                // modifiers that apply to general non-combat skilling
                nonCombat: [
                    'ChanceToDoubleItemsSkill',
                    'SkillInterval',
                    'SkillIntervalPercent',
                    'ChanceAdditionalSkillResource',
                ],
                production: [
                    'GlobalPreservationChance',
                    'SkillPreservationChance',
                ],
                mastery: [
                    'GlobalMasteryXP',
                    'MasteryXP',
                ],
                // specific skills
                agility: [
                    'GPFromAgility',
                    'AgilityObstacleCost',
                ],
                altMagic: [
                    'AltMagicSkillXP',
                    'AltMagicRunePreservation',
                    'RunePreservation',
                ],
                astrology: [],
                cooking: [
                    'ChanceSuccessfulCook',
                    'ChancePerfectCookGlobal',
                    'ChancePerfectCookFire',
                    'ChancePerfectCookFurnace',
                    'ChancePerfectCookPot',
                ],
                crafting: [],
                farming: [
                    'ChanceDoubleHarvest',
                    'FarmingYield',
                    'AllotmentSeedCost',
                ],
                firemaking: [
                    'ChanceForDiamondFiremaking',
                    'GPFromFiremaking',
                ],
                fishing: [
                    'FishingSpecialChance',
                ],
                fletching: [],
                herblore: [
                    'ChanceRandomPotionHerblore',
                ],
                mining: [
                    'ChanceNoDamageMining',
                    'ChanceToDoubleOres',
                    'MiningNodeHP',
                ],
                runecrafting: [
                    'ChanceForElementalRune',
                    'ElementalRuneGain',
                    'AdditionalRunecraftCountRunes',
                ],
                smithing: [
                    'SeeingGoldChance',
                ],
                summoning: [
                    'SummoningMaxHit',
                ],
                nonCBSummoning: [
                    'SummoningShardCost',
                    'SummoningCreationCharges',
                ],
                thieving: [
                    'ChanceToDoubleLootThieving',
                    'GPFromThieving',
                    'GPFromThievingFlat',
                    'ThievingStealth',
                    'MinThievingGP',
                ],
                woodcutting: [
                    'BirdNestDropRate',
                ],
                // golbin raid
                golbinRaid: [],
                // aprilFools
                aprilFools: [],
                // modifiers that are not actually implemented in the game
                unimplemented: [
                    'MaxStamina',
                    'StaminaCost',
                    'StaminaPerObstacle',
                    'StaminaPreservationChance',
                ],
            }

            // unique modifiers, i.e. not in+de creased
            this.singletonModifiers = {
                misc: [
                    'autoSlayerUnlocked',
                    'dungeonEquipmentSwapping',
                    'increasedEquipmentSets',
                ],
                skilling: [
                    'allowSignetDrops',
                    'increasedMasteryPoolProgress',
                ],
                combat: [
                    'meleeProtection',
                    'rangedProtection',
                    'magicProtection',
                    'meleeImmunity',
                    'rangedImmunity',
                    'magicImmunity',
                    'otherStyleImmunity',
                    'curseImmunity',
                    'stunImmunity',
                    'sleepImmunity',
                    'burnImmunity',
                    'poisonImmunity',
                    'bleedImmunity',
                    'debuffImmunity',
                    'slowImmunity',
                    'frostBurnImmunity',
                    'masteryToken',
                    'autoEquipFoodUnlocked',
                    'autoSwapFoodUnlocked',
                    'masteryToken',
                    'freeProtectItem',
                    'globalEvasionHPScaling',
                    'increasedRebirthChance',
                    'decreasedDragonBreathDamage',
                    'increasedMeleeStunThreshold',
                    'increasedChanceToIncreaseStunDuration',
                    'increasedRuneProvision',
                    'increasedChanceToConvertSeedDrops',
                    'increasedAfflictionChance',
                    'increasedChanceToApplyPoison',
                    'increasedChanceToApplyFrostburn',
                    'increasedEndOfTurnHealing2',
                    'increasedEndOfTurnHealing3',
                    'increasedEndOfTurnHealing5',
                    'increasedTotalBleedDamage',
                    'increasedOnHitSlowMagnitude',
                    'increasedNonMagicPoisonChance',
                    'increasedFrostburn',
                    'increasedPoisonReflectChance',
                    'increasedBleedReflectChance',
                    'bonusCoalOnDungeonCompletion',
                    'bypassSlayerItems',
                    'itemProtection',
                    'autoLooting',
                    'autoBurying',
                    'increasedGPMultiplierPer1MGP',
                    'increasedGPMultiplierCap',
                    'increasedGPMultiplierMin',
                    'allowAttackAugmentingMagic',
                    'summoningSynergy_0_1',
                    'summoningSynergy_0_6',
                    'summoningSynergy_0_7',
                    'summoningSynergy_0_8',
                    'summoningSynergy_0_12',
                    'summoningSynergy_0_13',
                    'summoningSynergy_0_14',
                    'summoningSynergy_0_15',
                    'summoningSynergy_1_2',
                    'summoningSynergy_1_8',
                    'summoningSynergy_1_12',
                    'summoningSynergy_1_13',
                    'summoningSynergy_1_14',
                    'summoningSynergy_1_15',
                    'summoningSynergy_2_13',
                    'summoningSynergy_2_15',
                    'summoningSynergy_6_13',
                    'summoningSynergy_7_13',
                    'summoningSynergy_8_13',
                    'summoningSynergy_12_13',
                    'summoningSynergy_12_14',
                    'summoningSynergy_13_14',
                    'increasedChanceToPreserveFood',
                    'allowLootContainerStacking',
                    'infiniteLootContainer',
                ],
                hitpoints: [
                    'decreasedRegenerationInterval',
                ],
                defence: [],
                attack: [],
                strength: [],
                melee: [
                    'increasedMeleeStunChance',
                    'summoningSynergy_6_7',
                    'summoningSynergy_6_12',
                    'summoningSynergy_6_14',
                    'summoningSynergy_6_15',
                ],
                ranged: [
                    'summoningSynergy_7_8',
                    'summoningSynergy_7_12',
                    'summoningSynergy_7_14',
                    'summoningSynergy_7_15',
                ],
                magic: [
                    'increasedConfusion',
                    'increasedDecay',
                    'summoningSynergy_6_8',
                    'summoningSynergy_8_14',
                    'increasedMinNatureSpellDamageBasedOnMaxHit',
                    'increasedSurgeSpellAccuracy',
                    'increasedSurgeSpellMaxHit',
                    'increasedElementalEffectChance',
                ],
                slayer: [
                    'summoningSynergy_2_12',
                    'summoningSynergy_8_12',
                ],
                prayer: [
                    'increasedRedemptionPercent',
                    'increasedRedemptionThreshold',
                ],
                nonCombat: [
                    'increasedOffItemChance',
                    'doubleItemsSkill',
                ],
                production: [],
                mastery: [],
                // specific skills
                agility: [],
                altMagic: [],
                astrology: [
                    'increasedBaseStardustDropQty',
                ],
                cooking: [
                    'decreasedSecondaryFoodBurnChance',
                    'summoningSynergy_3_9',
                    'summoningSynergy_4_9',
                    'summoningSynergy_9_17',
                    'summoningSynergy_9_18',
                    'summoningSynergy_9_19',
                ],
                crafting: [
                    'summoningSynergy_5_16',
                    'summoningSynergy_9_16',
                    'summoningSynergy_10_16',
                    'summoningSynergy_16_17',
                    'summoningSynergy_16_18',],
                farming: [
                    'freeCompost',
                    'increasedCompostPreservationChance',
                ],
                firemaking: [
                    'freeBonfires',
                    'increasedFiremakingCoalChance',
                    'summoningSynergy_3_19',
                    'summoningSynergy_4_19',
                    'summoningSynergy_9_19',
                    'summoningSynergy_16_19',
                    'summoningSynergy_18_19',
                ],
                fishing: [
                    'summoningSynergy_3_5',
                    'summoningSynergy_4_5',
                    'summoningSynergy_5_9',
                    'summoningSynergy_5_18',],
                fletching: [],
                herblore: [],
                mining: [
                    'increasedMiningGemChance',
                    'doubleOresMining',
                    'increasedBonusCoalMining',
                    'summoningSynergy_4_5',
                    'summoningSynergy_4_10',
                    'summoningSynergy_4_16',
                    'summoningSynergy_4_17',
                    'summoningSynergy_4_18',
                ],
                runecrafting: [
                    'increasedRunecraftingEssencePreservation',
                    'summoningSynergy_3_10',
                    'summoningSynergy_5_10',
                    'summoningSynergy_10_17',
                    'summoningSynergy_10_18',
                    'summoningSynergy_10_19',],
                smithing: [
                    'decreasedSmithingCoalCost',
                    'summoningSynergy_5_17',
                    'summoningSynergy_9_17',
                    'summoningSynergy_10_17',
                    'summoningSynergy_17_18',
                    'summoningSynergy_17_19',
                ],
                summoning: [],
                nonCBSummoning: [],
                thieving: [
                    'increasedThievingSuccessRate',
                    'increasedThievingSuccessCap',
                    'summoningSynergy_3_11',
                    'summoningSynergy_4_11',
                    'summoningSynergy_5_11',
                    'summoningSynergy_9_11',
                    'summoningSynergy_10_11',
                    'summoningSynergy_11_16',
                    'summoningSynergy_11_17',
                    'summoningSynergy_11_18',
                    'summoningSynergy_11_19',
                ],
                woodcutting: [
                    'increasedTreeCutLimit',
                    'summoningSynergy_3_4',
                    'summoningSynergy_3_16',
                    'summoningSynergy_3_17',
                    'summoningSynergy_3_18',
                    'summoningSynergy_3_19',
                ],
                // golbin raid modifiers
                golbinRaid: [
                    'golbinRaidIncreasedMaximumAmmo',
                    'golbinRaidIncreasedMaximumRunes',
                    'golbinRaidIncreasedMinimumFood',
                    'golbinRaidIncreasedPrayerLevel',
                    'golbinRaidIncreasedPrayerPointsStart',
                    'golbinRaidIncreasedPrayerPointsWave',
                    'golbinRaidIncreasedStartingRuneCount',
                    'golbinRaidPassiveSlotUnlocked',
                    'golbinRaidPrayerUnlocked',
                    'golbinRaidStartingWeapon',
                    'golbinRaidWaveSkipCostReduction',
                ],
                // chaos mode modifiers
                aprilFools: [
                    'aprilFoolsIncreasedMovementSpeed',
                    'aprilFoolsDecreasedMovementSpeed',
                    'aprilFoolsIncreasedTeleportCost',
                    'aprilFoolsDecreasedTeleportCost',
                    'aprilFoolsIncreasedUpdateDelay',
                    'aprilFoolsDecreasedUpdateDelay',
                    'aprilFoolsIncreasedLemonGang',
                    'aprilFoolsDecreasedLemonGang',
                    'aprilFoolsIncreasedCarrotGang',
                    'aprilFoolsDecreasedCarrotGang',
                ],
                unimplemented: [],
            }

            if (check) {
                this.checkUnknownModifiers();
            }

            // map of relevant modifiers per tag
            this.relevantModifiers = {};

            // all
            this.relevantModifiers.all = this.getModifierNames(
                Object.getOwnPropertyNames(this.creasedModifiers),
                Object.getOwnPropertyNames(SKILLS).map(x => Number(x)),
            );

            // misc
            this.relevantModifiers.misc = this.getModifierNames(['misc'], []);

            // golbin raid
            this.relevantModifiers.golbin = this.getModifierNames(['golbinRaid'], []);

            // all combat
            this.relevantModifiers.combat = this.getModifierNames(
                [
                    'skilling',
                    'combat',
                ],
                [
                    Skills.Attack,
                    Skills.Strength,
                    Skills.Ranged,
                    Skills.Magic,
                    Skills.Defence,
                    Skills.Hitpoints,
                    Skills.Prayer,
                    Skills.Slayer,
                    Skills.Summoning,
                ],
            );

            // melee combat
            this.relevantModifiers.melee = this.getModifierNames(
                [
                    'skilling',
                    'combat',
                ],
                [
                    Skills.Attack,
                    Skills.Strength,
                    Skills.Defence,
                    Skills.Hitpoints,
                    Skills.Prayer,
                    Skills.Slayer,
                    Skills.Summoning,
                ],
            );

            // ranged combat
            this.relevantModifiers.ranged = this.getModifierNames(
                [
                    'skilling',
                    'combat',
                ],
                [
                    Skills.Ranged,
                    Skills.Defence,
                    Skills.Hitpoints,
                    Skills.Prayer,
                    Skills.Slayer,
                    Skills.Summoning,
                ],
            );

            // magic combat
            this.relevantModifiers.magic = this.getModifierNames(
                [
                    'skilling',
                    'combat',
                    'hitpoints',
                ],
                [
                    Skills.Magic,
                    Skills.Defence,
                    Skills.Hitpoints,
                    Skills.Prayer,
                    Skills.Slayer,
                    Skills.Summoning,
                ],
            );

            // slayer
            this.relevantModifiers.slayer = this.getModifierNames(
                [
                    'skilling',
                ],
                [
                    Skills.Slayer,
                ],
            );

            // gathering skills
            this.gatheringSkills = ['Woodcutting', 'Fishing', 'Mining', 'Thieving', 'Farming', 'Agility', 'Astrology'];
            this.gatheringSkills.forEach(name => {
                this.relevantModifiers[name] = this.getModifierNames(
                    [
                        'skilling',
                        'nonCombat',
                        'mastery',
                    ],
                    [
                        Skills[name]
                    ],
                );
                const lname = name.toLowerCase();
                if (this.creasedModifiers[lname] !== undefined) {
                    this.relevantModifiers[name].names.push(this.creasedModifiers[lname]);
                }
                if (this.singletonModifiers[lname] !== undefined) {
                    this.relevantModifiers[name].names.push(this.singletonModifiers[lname]);
                }
            });

            // production skills
            this.productionSkills = ['Firemaking', 'Cooking', 'Smithing', 'Fletching', 'Crafting', 'Runecrafting', 'Herblore', 'Summoning'];
            this.productionSkills.forEach(name => {
                const setNames = [
                    'skilling',
                    'nonCombat',
                    'production',
                    'mastery',
                ];
                if (name === 'Summoning') {
                    setNames.push('nonCBSummoning');
                }
                this.relevantModifiers[name] = this.getModifierNames(
                    setNames,
                    [
                        Skills[name]
                    ],
                );
            });

            // whatever alt magic is
            this.relevantModifiers.altMagic = this.getModifierNames(
                [
                    'skilling',
                    'nonCombat',
                    'altMagic',
                ],
                [],
            );

            // golbin raid
            this.relevantModifiers.golbinRaid = this.getModifierNames(
                ['golbinRaid'],
                [],
            );
        }

        log(...args) {
            console.log(`${this.logName}:`, ...args);
        }

        checkUnknownModifiers() {
            // list of known modifiers
            this.knownModifiers = {};
            for (const subset in this.creasedModifiers) {
                this.creasedModifiers[subset].forEach(modifier => {
                    this.knownModifiers[`increased${modifier}`] = true;
                    this.knownModifiers[`decreased${modifier}`] = true;
                });
            }
            for (const subset in this.singletonModifiers) {
                this.singletonModifiers[subset].forEach(modifier => {
                    this.knownModifiers[modifier] = true;
                });
            }

            // check for unknown modifiers
            const modifierNames = [
                ...Object.getOwnPropertyNames(player.modifiers),
                // player.modifiers.skillModifiers
                ...Object.getOwnPropertyNames(modifierData).filter(x => modifierData[x].isSkill),
            ];
            let hasUnknownModifiers = false;
            modifierNames.forEach(modifier => {
                if (modifier === 'skillModifiers') {
                    return;
                }
                if (this.knownModifiers[modifier]) {
                    return;
                }
                hasUnknownModifiers = true;
                this.log(`unknown modifier ${modifier}`);
            });
            if (!hasUnknownModifiers) {
                this.log('no unknown modifiers detected!')
            }

            // check for non-existent modifiers
            let hasNonExistentModifiers = false;
            for (const modifier in this.knownModifiers) {
                if (!modifierNames.includes(modifier)) {
                    hasNonExistentModifiers = true;
                    this.log(`non-existent modifier ${modifier}`);
                }
            }
            if (!hasNonExistentModifiers) {
                this.log('no non-existent modifiers detected!')
            }
        }

        getModifierNames(setNames, skillIDs) {
            // add skill based on skillID
            skillIDs.forEach(id => {
                if (!setNames.includes(Skills[id])) {
                    setNames.push(Skills[id].toLowerCase());
                }
            });
            // add melee based on att/str skillID
            if (skillIDs.includes(Skills.Attack) || skillIDs.includes(Skills.Strength)) {
                if (!setNames.includes('melee')) {
                    setNames.push('melee');
                }
            }

            // gather modifiers
            return {
                names: [...new Set([
                    ...setNames.map(name => this.creasedModifiers[name]).reduce((a, x) => [...a, ...x], []),
                    ...setNames.map(name => this.singletonModifiers[name]).reduce((a, x) => [...a, ...x], []),
                ])],
                skillIDs: skillIDs,
            };
        }

        printUniqueModifier(modifier, value, skillID) {
            if (!value) {
                return [];
            }
            // convert to array if required
            const valueToPrint = skillID !== undefined ? [skillID, value] : value;
            return [printPlayerModifier(modifier, valueToPrint)];
        }

        printDiffModifier(modifier, value, skillID = undefined) {
            // compute difference
            if (!value) {
                return [];
            }
            // store if value is positive or negative
            const positive = value > 0;
            // take absolute value
            let valueToPrint = positive ? value : -value;
            // convert to array if required
            valueToPrint = skillID !== undefined ? [skillID, valueToPrint] : valueToPrint;
            // print increased or decreased
            if (positive) {
                return [printPlayerModifier('increased' + modifier, valueToPrint)];
            }
            return [printPlayerModifier('decreased' + modifier, valueToPrint)];
        }

        getModifierValue(modifiers, modifier, skillID = undefined) {
            if (!this.isSkillModifier(modifier)) {
                return this.getSimpleModifier(modifiers, modifier);
            }
            return this.getSkillModifier(modifiers, modifier, skillID);
        }

        getSimpleModifier(modifiers, modifier) {
            // unique
            if (this.isUniqueModifier(modifier)) {
                return modifiers[modifier];
            }
            // creased
            let increased = modifiers['increased' + modifier];
            if (!increased) {
                increased = 0;
            }
            let decreased = modifiers['decreased' + modifier];
            if (!decreased) {
                decreased = 0;
            }
            return increased - decreased;
        }

        getSkillModifier(modifiers, modifier, skillID) {
            const skillModifiers = modifiers.skillModifiers ? modifiers.skillModifiers : modifiers;
            // unique
            if (this.isUniqueModifier(modifier)) {
                const map = this.skillModifierMapAux(skillModifiers, modifier);
                return this.skillModifierAux(map, skillID);
            }
            // creased
            const increased = this.skillModifierMapAux(skillModifiers, 'increased' + modifier);
            const decreased = this.skillModifierMapAux(skillModifiers, 'decreased' + modifier);
            return this.skillModifierAux(increased, skillID) - this.skillModifierAux(decreased, skillID);
        }

        skillModifierMapAux(map, skillID) {
            if (!map) {
                return [];
            }
            let tmp;
            if (map.constructor.name === 'Map') {
                tmp = map.get(skillID);
            } else {
                tmp = map[skillID];
            }
            return tmp ? tmp : [];
        }

        skillModifierAux(map, skillID) {
            if (!map || map.length === 0) {
                return 0;
            }
            if (map.constructor.name === 'Map') {
                const value = map.get(skillID);
                if (!value) {
                    return 0
                }
                return value;
            }
            return map.filter(x => x[0] === skillID)
                .map(x => x[1])
                .reduce((a, b) => a + b, 0);
        }

        printModifier(modifiers, modifier, skillIDs) {
            if (!this.isSkillModifier(modifier)) {
                const value = this.getSimpleModifier(modifiers, modifier);
                if (this.isUniqueModifier(modifier)) {
                    // unique
                    return this.printUniqueModifier(modifier, value);
                }
                // creased
                return this.printDiffModifier(modifier, value);
            }
            // skillModifiers
            return skillIDs.map(skillID => {
                const value = this.getSkillModifier(modifiers, modifier, skillID);
                if (this.isUniqueModifier(modifier)) {
                    // unique
                    return this.printUniqueModifier(modifier, value, skillID);
                }
                // creased
                return this.printDiffModifier(modifier, value, skillID);
            }).reduce((a, b) => a.concat(b), []);
        }

        isUniqueModifier(modifier) {
            return modifierData[modifier] !== undefined;
        }

        isSkillModifier(modifier) {
            if (this.isUniqueModifier(modifier)) {
                return modifierData[modifier].isSkill;
            }
            const data = modifierData['increased' + modifier];
            if (data === undefined) {
                // this.log(`Unknown modifier ${modifier}`);
                return false;
            }
            return data.isSkill;
        }

        printRelevantModifiers(modifiers, tag) {
            const relevantNames = this.relevantModifiers[tag].names;
            const skillIDs = this.relevantModifiers[tag].skillIDs;
            const toPrint = [];
            relevantNames.forEach(name => {
                this.printModifier(modifiers, name, skillIDs).forEach(result => toPrint.push(result));
            });
            return toPrint;
        }

        makeTagButton(tag, text, icon) {
            return '<div class="dropdown d-inline-block ml-2">'
                + '<button type="button" '
                + 'class="btn btn-sm btn-dual text-combat-smoke" '
                + 'id="page-header-modifiers" '
                + `onclick="window.${this.name}.replaceRelevantModifiersHtml(window.${this.name}.getModifiers(), '${text}', '${tag}');" `
                + 'aria-haspopup="true" '
                + 'aria-expanded="true">'
                + `<img class="skill-icon-xxs" src="${icon}">`
                + '</button>'
                + '</div>';
        }

        replaceRelevantModifiersHtml(modifiers, text, tag) {
            $('#show-modifiers').replaceWith(this.printRelevantModifiersHtml(modifiers, text, tag));
        }

        printRelevantModifiersHtml(modifiers, text, tag, id = 'show-modifiers') {
            let passives = `<div id="${id}"><br/>`;
            passives += `<h5 class=\"font-w400 font-size-sm mb-1\">${text}</h5><br/>`;
            this.printRelevantModifiers(modifiers, tag).forEach(toPrint => {
                passives += `<h5 class=\"font-w400 font-size-sm mb-1 ${toPrint[1]}\">${toPrint[0]}</h5>`;
            });
            passives += '</div>';
            return passives;
        }

        showRelevantModifiers(modifiers, text, tag = 'all') {
            let passives = `<h5 class=\"font-w600 font-size-sm mb-1 text-combat-smoke\">${text}</h5><h5 class=\"font-w600 font-size-sm mb-3 text-warning\"></h5>`;
            passives += `<h5 class="font-w600 font-size-sm mb-3 text-warning"><small>(Does not include non-modifier effects)</small></h5>`;
            passives += this.makeTagButton('all', 'All Modifiers', 'assets/media/main/completion_log.svg');
            passives += this.makeTagButton('golbinRaid', 'Golbin Raid', 'assets/media/main/raid_coins.svg');
            passives += this.makeTagButton('combat', 'Combat', 'assets/media/skills/combat/combat.svg');
            passives += this.makeTagButton('melee', 'Melee', 'assets/media/skills/attack/attack.svg');
            passives += this.makeTagButton('ranged', 'Ranged', 'assets/media/skills/ranged/ranged.svg');
            passives += this.makeTagButton('magic', 'Combat Magic', 'assets/media/skills/combat/spellbook.svg');
            passives += this.makeTagButton('slayer', 'Slayer', 'assets/media/skills/slayer/slayer.svg');
            passives += '<br/>';
            this.gatheringSkills.forEach(skill => passives += this.makeTagButton(skill, skill, `assets/media/skills/${skill.toLowerCase()}/${skill.toLowerCase()}.svg`));
            passives += '<br/>';
            this.productionSkills.forEach(skill => passives += this.makeTagButton(skill, skill, `assets/media/skills/${skill.toLowerCase()}/${skill.toLowerCase()}.svg`));
            passives += this.makeTagButton('altMagic', 'Alt. Magic', 'assets/media/skills/magic/magic.svg');
            passives += this.printRelevantModifiersHtml(modifiers, 'All Modifiers', tag);
            Swal.fire({
                html: passives,
            });
        }

        getModifiers() {
            return game.isGolbinRaid ? game.golbinRaid.player.modifiers : player.modifiers;
        }
    }

    function startShowModifiers() {
        const name = 'melvorShowModifiers';
        window[name] = new ShowModifiers(name, 'Show Modifiers');
        let modifierButton = () => {
            return '<div class="dropdown d-inline-block ml-2">'
                + '<button type="button" '
                + 'class="btn btn-sm btn-dual text-combat-smoke" '
                + 'id="page-header-modifiers" '
                + `onclick="window.${name}.showRelevantModifiers(window.${name}.getModifiers(), \'Active Modifiers\');" `
                + 'aria-haspopup="true" '
                + 'aria-expanded="true">'
                + `<img class="skill-icon-xxs" src="${getItemMedia(Items.Event_Clue_1)}">`
                + '</button>'
                + '</div>';
        }

        let node = document.getElementById('page-header-potions-dropdown').parentNode;
        node.parentNode.insertBefore($(modifierButton().trim())[0], node);
    }

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

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