Melvor Show Modifiers

Adds a button to show all your modifiers

目前為 2021-08-13 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Melvor Show Modifiers
// @version      0.1.1
// @description  Adds a button to show all your modifiers
// @author       GMiclotte
// @match        https://*.melvoridle.com/*
// @exclude      https://wiki.melvoridle.com/*
// @grant        none
// @namespace    http://tampermonkey.net/
// @noframes
// ==/UserScript==


function script() {

    class ShowModifiers {

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

            this.singletonModifiers = {
                misc: {
                    autoSlayerUnlocked: 0,
                    dungeonEquipmentSwapping: 0,
                    increasedEquipmentSets: 0,
                },
                skilling: {
                    allowSignetDrops: 0,
                    increasedMasteryPoolProgress: 0,
                },
                combat: {
                    meleeProtection: 0,
                    rangedProtection: 0,
                    magicProtection: 0,
                    curseImmunity: 0,
                    stunImmunity: 0,
                    sleepImmunity: 0,
                    burnImmunity: 0,
                    poisonImmunity: 0,
                    bleedImmunity: 0,
                    debuffImmunity: 0,
                    increasedRebirthChance: 0,
                    decreasedDragonBreathDamage: 0,
                    increasedMeleeStunThreshold: 0,
                    increasedRuneProvision: 0,
                    increasedChanceToConvertSeedDrops: 0,
                    bonusCoalOnDungeonCompletion: 0,
                    bypassSlayerItems: 0,
                    itemProtection: 0,
                    autoLooting: 0,
                    autoBurying: 0,
                    increasedCombatStoppingThreshold: 0,
                    increasedGPMultiplierPer1MGP: 0,
                    increasedGPMultiplierCap: 0,
                    increasedGPMultiplierMin: 0,
                    allowAttackAugmentingMagic: 0,
                },
                prayer: {
                    increasedRedemptionPercent: 0,
                    increasedRedemptionThreshold: 0,
                },
                nonCombat: {
                    increasedOffItemChance: 0,
                },
                // specific skills
                cooking: {
                    decreasedFoodBurnChance: 0,
                    decreasedSecondaryFoodBurnChance: 0,
                },
                farming: {
                    freeCompost: 0,
                    increasedCompostPreservationChance: 0,
                },
                firemaking: {
                    freeBonfires: 0,
                    increasedFiremakingCoalChance: 0,
                },
                mining: {
                    increasedMiningGemChance: 0,
                    doubleOresMining: 0,
                    increasedBonusCoalMining: 0,
                },
                smithing: {
                    decreasedSmithingCoalCost: 0,
                },
                thieving: {
                    increasedThievingSuccessRate: 0,
                    increasedThievingSuccessCap: 0,
                },
                woodcutting: {
                    increasedTreeCutLimit: 0,
                },
                // golbin raid modifiers
                golbinRaid: {
                    golbinRaidIncreasedMaximumAmmo: 0,
                    golbinRaidIncreasedMaximumRunes: 0,
                    golbinRaidIncreasedMinimumFood: 0,
                    golbinRaidIncreasedPrayerLevel: 0,
                    golbinRaidIncreasedPrayerPointsStart: 0,
                    golbinRaidIncreasedPrayerPointsWave: 0,
                    golbinRaidIncreasedStartingRuneCount: 0,
                    golbinRaidPassiveSlotUnlocked: 0,
                    golbinRaidPrayerUnlocked: 0,
                    golbinRaidStartingWeapon: 0,
                    golbinRaidWaveSkipCostReduction: 0,
                },
                // summoning synergy modifiers, should be moved to the appropriate location, IF they are here to stay
                summoning: {
                    summoningSynergy_0_1: 0,
                    summoningSynergy_0_6: 0,
                    summoningSynergy_0_7: 0,
                    summoningSynergy_0_8: 0,
                    summoningSynergy_0_12: 0,
                    summoningSynergy_0_13: 0,
                    summoningSynergy_0_14: 0,
                    summoningSynergy_0_15: 0,
                    summoningSynergy_1_2: 0,
                    summoningSynergy_1_8: 0,
                    summoningSynergy_1_12: 0,
                    summoningSynergy_1_13: 0,
                    summoningSynergy_1_14: 0,
                    summoningSynergy_1_15: 0,
                    summoningSynergy_2_6: 0,
                    summoningSynergy_2_7: 0,
                    summoningSynergy_2_8: 0,
                    summoningSynergy_2_12: 0,
                    summoningSynergy_2_13: 0,
                    summoningSynergy_2_14: 0,
                    summoningSynergy_2_15: 0,
                    summoningSynergy_3_4: 0,
                    summoningSynergy_3_5: 0,
                    summoningSynergy_3_9: 0,
                    summoningSynergy_3_10: 0,
                    summoningSynergy_3_11: 0,
                    summoningSynergy_3_16: 0,
                    summoningSynergy_3_17: 0,
                    summoningSynergy_3_18: 0,
                    summoningSynergy_3_19: 0,
                    summoningSynergy_4_5: 0,
                    summoningSynergy_4_9: 0,
                    summoningSynergy_4_10: 0,
                    summoningSynergy_4_11: 0,
                    summoningSynergy_4_16: 0,
                    summoningSynergy_4_17: 0,
                    summoningSynergy_4_18: 0,
                    summoningSynergy_4_19: 0,
                    summoningSynergy_5_9: 0,
                    summoningSynergy_5_10: 0,
                    summoningSynergy_5_11: 0,
                    summoningSynergy_5_16: 0,
                    summoningSynergy_5_17: 0,
                    summoningSynergy_5_18: 0,
                    summoningSynergy_6_7: 0,
                    summoningSynergy_6_8: 0,
                    summoningSynergy_6_12: 0,
                    summoningSynergy_6_13: 0,
                    summoningSynergy_6_14: 0,
                    summoningSynergy_6_15: 0,
                    summoningSynergy_7_8: 0,
                    summoningSynergy_7_12: 0,
                    summoningSynergy_7_13: 0,
                    summoningSynergy_7_14: 0,
                    summoningSynergy_7_15: 0,
                    summoningSynergy_8_12: 0,
                    summoningSynergy_8_13: 0,
                    summoningSynergy_8_14: 0,
                    summoningSynergy_9_10: 0,
                    summoningSynergy_9_11: 0,
                    summoningSynergy_9_16: 0,
                    summoningSynergy_9_17: 0,
                    summoningSynergy_9_18: 0,
                    summoningSynergy_9_19: 0,
                    summoningSynergy_10_11: 0,
                    summoningSynergy_10_16: 0,
                    summoningSynergy_10_17: 0,
                    summoningSynergy_10_18: 0,
                    summoningSynergy_10_19: 0,
                    summoningSynergy_11_16: 0,
                    summoningSynergy_11_17: 0,
                    summoningSynergy_11_18: 0,
                    summoningSynergy_11_19: 0,
                    summoningSynergy_12_13: 0,
                    summoningSynergy_12_14: 0,
                    summoningSynergy_12_15: 0,
                    summoningSynergy_13_14: 0,
                    summoningSynergy_13_15: 0,
                    summoningSynergy_14_15: 0,
                    summoningSynergy_16_17: 0,
                    summoningSynergy_16_18: 0,
                    summoningSynergy_16_19: 0,
                    summoningSynergy_17_18: 0,
                    summoningSynergy_17_19: 0,
                    summoningSynergy_18_19: 0,
                },
                aprilFools: {
                    aprilFoolsIncreasedMovementSpeed: 0,
                    aprilFoolsDecreasedMovementSpeed: 0,
                    aprilFoolsIncreasedTeleportCost: 0,
                    aprilFoolsDecreasedTeleportCost: 0,
                    aprilFoolsIncreasedUpdateDelay: 0,
                    aprilFoolsDecreasedUpdateDelay: 0,
                    aprilFoolsIncreasedLemonGang: 0,
                    aprilFoolsDecreasedLemonGang: 0,
                    aprilFoolsIncreasedCarrotGang: 0,
                    aprilFoolsDecreasedCarrotGang: 0,
                }
            }

            this.knownModifiers = {};
            Object.getOwnPropertyNames(this.creasedModifiers).forEach(subset => {
                Object.getOwnPropertyNames(this.creasedModifiers[subset]).forEach(modifier => {
                    this.knownModifiers[`increased${modifier}`] = true;
                    this.knownModifiers[`decreased${modifier}`] = true;
                });
            });
            Object.getOwnPropertyNames(this.singletonModifiers).forEach(subset => {
                Object.getOwnPropertyNames(this.singletonModifiers[subset]).forEach(modifier => {
                    this.knownModifiers[modifier] = true;
                });
            });

            // check for unknown modifiers
            let hasUnknownModifiers = false;
            [
                ...Object.getOwnPropertyNames(player.modifiers),
                ...player.modifiers.skillModifiers.keys()
            ].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!')
            }

            this.relevantModifiers = {};

            // all
            this.relevantModifiers.all = {
                names: [
                    ...Object.getOwnPropertyNames(this.creasedModifiers).map(tag => this.creasedModifiers[tag]),
                    ...Object.getOwnPropertyNames(this.singletonModifiers).map(tag => this.singletonModifiers[tag]),
                ],
                skillIDs: Object.getOwnPropertyNames(SKILLS).map(x => Number(x)),
            };

            // misc
            this.relevantModifiers.misc = {
                names: [
                    this.creasedModifiers.misc,
                    this.singletonModifiers.misc,
                ],
                skillIDs: [],
            };

            // golbin raid
            this.relevantModifiers.golbin = {
                names: [this.singletonModifiers.golbinRaid],
                skillIDs: [],
            };

            // all combat
            this.relevantModifiers.combat = {
                names: [
                    this.creasedModifiers.skilling,
                    this.creasedModifiers.combat,
                    this.creasedModifiers.hitpoints,
                    this.creasedModifiers.defence,
                    this.creasedModifiers.melee,
                    this.creasedModifiers.ranged,
                    this.creasedModifiers.magic,
                    this.creasedModifiers.slayer,
                    this.creasedModifiers.prayer,
                ],
                skillIDs: [
                    CONSTANTS.skill.Attack,
                    CONSTANTS.skill.Strength,
                    CONSTANTS.skill.Ranged,
                    CONSTANTS.skill.Magic,
                    CONSTANTS.skill.Defence,
                    CONSTANTS.skill.Hitpoints,
                    CONSTANTS.skill.Prayer,
                    CONSTANTS.skill.Slayer,
                    CONSTANTS.skill.Summoning,
                ],
            };

            // melee combat
            this.relevantModifiers.melee = {
                names: [
                    this.creasedModifiers.skilling,
                    this.creasedModifiers.combat,
                    this.creasedModifiers.hitpoints,
                    this.creasedModifiers.defence,
                    this.creasedModifiers.melee,
                    this.creasedModifiers.slayer,
                    this.creasedModifiers.prayer,
                ],
                skillIDs: [
                    CONSTANTS.skill.Attack,
                    CONSTANTS.skill.Strength,
                    CONSTANTS.skill.Defence,
                    CONSTANTS.skill.Hitpoints,
                    CONSTANTS.skill.Prayer,
                    CONSTANTS.skill.Slayer,
                    CONSTANTS.skill.Summoning,
                ],
            };

            // ranged combat
            this.relevantModifiers.ranged = {
                names: [
                    this.creasedModifiers.skilling,
                    this.creasedModifiers.combat,
                    this.creasedModifiers.hitpoints,
                    this.creasedModifiers.defence,
                    this.creasedModifiers.ranged,
                    this.creasedModifiers.slayer,
                    this.creasedModifiers.prayer,
                ],
                skillIDs: [
                    CONSTANTS.skill.Ranged,
                    CONSTANTS.skill.Defence,
                    CONSTANTS.skill.Hitpoints,
                    CONSTANTS.skill.Prayer,
                    CONSTANTS.skill.Slayer,
                    CONSTANTS.skill.Summoning,
                ],
            };

            // magic combat
            this.relevantModifiers.magic = {
                names: [
                    this.creasedModifiers.skilling,
                    this.creasedModifiers.combat,
                    this.creasedModifiers.hitpoints,
                    this.creasedModifiers.defence,
                    this.creasedModifiers.magic,
                    this.creasedModifiers.slayer,
                    this.creasedModifiers.prayer,
                ],
                skillIDs: [
                    CONSTANTS.skill.Magic,
                    CONSTANTS.skill.Defence,
                    CONSTANTS.skill.Hitpoints,
                    CONSTANTS.skill.Prayer,
                    CONSTANTS.skill.Slayer,
                    CONSTANTS.skill.Summoning,
                ],
            };

            // slayer
            this.relevantModifiers.slayer = {
                names: [
                    this.creasedModifiers.skilling,
                    this.creasedModifiers.slayer,
                ],
                skillIDs: [
                    CONSTANTS.skill.Slayer,
                ],
            };

            // gathering skills
            this.gatheringSkills = ['Woodcutting', 'Fishing', 'Mining', 'Thieving', 'Farming', 'Agility'];
            this.gatheringSkills.forEach(name => {
                this.relevantModifiers[name] = {
                    names: [
                        this.creasedModifiers.skilling,
                        this.creasedModifiers.nonCombat,
                        this.creasedModifiers.mastery,
                    ],
                    skillIDs: [
                        CONSTANTS.skill[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 => {
                this.relevantModifiers[name] = {
                    names: [
                        this.creasedModifiers.skilling,
                        this.creasedModifiers.nonCombat,
                        this.creasedModifiers.production,
                        this.creasedModifiers.mastery,
                    ],
                    skillIDs: [
                        CONSTANTS.skill[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]);
                }
            });

            // whatever alt magic is
            this.relevantModifiers.altMagic = {
                names: [
                    this.creasedModifiers.skilling,
                    this.creasedModifiers.nonCombat,
                    this.creasedModifiers.altMagic,
                ],
                skillIDs: [],
            };

            // golbin raid
            this.relevantModifiers.golbinRaid = {
                names: [this.singletonModifiers.golbinRaid],
                skillIDs: [],
            };
        }

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

        printUniqueModifier(modifier, value) {
            if (!value) {
                return [];
            }
            return [printPlayerModifier(modifier, value)];
        }

        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
            const increased = modifiers['increased' + modifier];
            const decreased = modifiers['decreased' + modifier];
            return (increased | 0) - (decreased | 0);
        }

        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') {
                return map.get(skillID) | 0;
            }
            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 | 0);
                }
                // creased
                return this.printDiffModifier(modifier, value | 0);
            }
            // skillModifiers
            return skillIDs.map(skillID => {
                const value = this.getSkillModifier(modifiers, modifier, skillID);
                if (this.isUniqueModifier(modifier)) {
                    // unique
                    return this.printUniqueModifier(modifier, value | 0);
                }
                // creased
                return this.printDiffModifier(modifier, value | 0, 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(names => Object.getOwnPropertyNames(names).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,
            });
        }
    }

    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(CONSTANTS.item.Event_Clue_1)}">`
            + '</button>'
            + '</div>';
    }

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


(function () {
    function injectScript(main) {
        const scriptElement = document.createElement('script');
        scriptElement.textContent = `try {(${main})();} catch (e) {console.log(e);}`;
        document.body.appendChild(scriptElement).parentNode.removeChild(scriptElement);
    }

    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);
})();