Melvor Show Modifiers

Adds a button to show all your modifiers

当前为 2021-12-06 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Melvor Show Modifiers
// @namespace   http://tampermonkey.net/
// @version     0.2.9
// @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',
                ],
                astrology: [],
                cooking: [
                    'ChanceSuccessfulCook',
                    'ChancePerfectCookGlobal',
                    'ChancePerfectCookFire',
                    'ChancePerfectCookFurnace',
                    'ChancePerfectCookPot',
                ],
                crafting: [],
                farming: [
                    'ChanceDoubleHarvest',
                    'FarmingYield',
                    'AllotmentSeedCost',
                ],
                firemaking: [
                    'ChanceForDiamondFiremaking',
                ],
                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(player.modifiers, '${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,
            });
        }
    }

    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(player.modifiers, \'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);
});