Melvor DungeonTimer

Displays different statistics relating to dungeon completion (Completion count since the script has been installed,previous time, best time, average time)

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name		Melvor DungeonTimer
// @namespace	http://tampermonkey.net/
// @version		1.0.0.1
// @description	Displays different statistics relating to dungeon completion (Completion count since the script has been installed,previous time, best time, average time)
// @description	Please report issues via message to Chrono#1840 on Discord
// @author		Chrono
// @match		https://*.melvoridle.com/*
// @exclude		https://wiki.melvoridle.com*
// @noframes
// ==/UserScript==

var DISPLAY_CLEAR_BUTTONS = false;

function script() {
    if (window.DungeonTimer !== undefined) {
        console.error('DungeonTimer is already loaded!');
    } else {
        createDungeonTimerData();
        createDungeonTimer();
        loadDungeonTimer();
    }

    function createDungeonTimerData() {

        //get the player key (allow multi account) and define local storage key name
        var playerStorageKey = getKeysForCharacter(currentCharacter) +'DungeonTimerDataV2' ;

        //Check if there is already a key matching this script in local storage
        if (localStorage.getItem(playerStorageKey)!== null)
        {
            window.DungeonTimerData = JSON.parse(localStorage.getItem(playerStorageKey))
            console.log("DungeonTimerData Key found - Data retrieved");
        }
        else
        {
            //check if previous version of the local storage exists
            if (localStorage.getItem('DungeonTimerDataV2')!== null)
            {
                window.DungeonTimerData = JSON.parse(localStorage.getItem('DungeonTimerDataV2'))
                console.log("DungeonTimerData Key found - Data retrieved");
                localStorage.removeItem('DungeonTimerDataV2');
            }
            else
            {
                window.DungeonTimerData = []

                if (localStorage.getItem('DungeonTimerData')!== null)
                {
                    window.DungeonTimerData = JSON.parse(localStorage.getItem('DungeonTimerData'))

                    for (i=0; i<window.DungeonTimerData.length; i++)
                    {
                        window.DungeonTimerData[i][3] = [window.DungeonTimerData[i][3]];
                    }

                    localStorage.removeItem('DungeonTimerData');
                }
            }
        }

        //If the key is missing dungeon entries, add them to the end of the list
        var i
        for (i=window.DungeonTimerData.length;i<DUNGEONS.length;i++)
        {
            //comp count, last time, best time, average times (last 20)
            window.DungeonTimerData.push([0,0,0,[]])
        }

        // save settings to local storage
        window.DungeonTimerSettings = {
            save: () => {
                window.localStorage[playerStorageKey] = window.JSON.stringify(window.DungeonTimerData);
            }
        };

        //Check for the dungeon completion key

        window.DungeonCompCount = []

        if (dungeonCompleteCount!== null)
        {
            window.DungeonCompCount = dungeonCompleteCount;
        }
        else
        {
            for (i=0;i<DUNGEONS.length;i++)
            {
                window.DungeonCompCount.push(window.DungeonTimerData[i][0])
            }
        }
    }

    function createDungeonTimer() {
        // global object
        window.DungeonTimer = {};

        DungeonTimer.log = function (...args) {
            console.log("Melvor DungeonTimer:", ...args)
        }

        DungeonTimer.createSettingsMenu = () => {
            // set names
            DungeonTimer.menuItemID = 'DungeonTimerButton';
            DungeonTimer.modalID = 'DungeonTimerModal';

            // clean up in case elements already exist
            destroyMenu(DungeonTimer.menuItemID, DungeonTimer.modalID);

            // create wrappers and access point
            DungeonTimer.radio = document.createElement('div');
            DungeonTimer.content = document.createElement('div');
            DungeonTimer.content.className = 'mdtGridContainer';

            addMenuItem('Dungeon Timers', 'https://cdn.melvor.net/core/v018/assets/media/skills/combat/dungeon.svg', DungeonTimer.menuItemID, 'DungeonTimerModal')
            addModal('Dungeon Timers', DungeonTimer.modalID, [DungeonTimer.radio,DungeonTimer.content])

            // create the dungeon cards
            DungeonTimer.addDungeonData();

            // log
            DungeonTimer.log('added Timer menu!')
        }

        //the dungeonID order does not match the order shown in the dungeon combat menu, so artificially change dungeon order
        //if a new dungeon is added, revert order until manually fixed
        var i
        var dungeonOrder = [0,1,6,7,2,13,4,3,12,5,14,8,9,10,11,15]
        if (dungeonOrder.length!=DUNGEONS.length)
        {
            dungeonOrder = []
            for (i=0;i<DUNGEONS.length;i++)
            {
                dungeonOrder.push(i)
            }
        }

        //for each dungeon, add a card to the modal menu that includes the dungeon image, its name and the data from local storage
        DungeonTimer.addDungeonData = () => {
            DungeonTimer.toggleCard = new Card(DungeonTimer.radio, '', '150px', true);
            DungeonTimer.toggleCard.addRadio('Display Timer Clear Buttons   ', 25, 'ClearButtonToggle', ['Show','Hide'], [ShowMDTButtons,HideMDTButtons], 1)

            for (i=0;i<DUNGEONS.length;i++)
            {
                DungeonTimer.DungeonCard = new Card(DungeonTimer.content, '166px', '105px', true);

                DungeonTimer.DungeonCard.addImage(DUNGEONS[dungeonOrder[i]].media, 30,'');
                DungeonTimer.DungeonCard.addSectionTitle(DUNGEONS[dungeonOrder[i]].name, '')
                DungeonTimer.DungeonCard.addNumberOutput('Completion Count ', dungeonCompleteCount[dungeonOrder[i]], 20,'', 'Completion'+dungeonOrder[i])
                DungeonTimer.DungeonCard.addNumberOutput('Previous Time ', formatTime(window.DungeonTimerData[dungeonOrder[i]][1]), 20,'', 'PreviousTime'+dungeonOrder[i])
                DungeonTimer.DungeonCard.addNumberOutput('Best Time ', formatTime(window.DungeonTimerData[dungeonOrder[i]][2]), 20,'', 'BestTime'+dungeonOrder[i])
                DungeonTimer.DungeonCard.addNumberOutput('Average Time ', formatTime(AverageTimeCalc(window.DungeonTimerData[dungeonOrder[i]][3])), 20,'', 'AverageTime'+dungeonOrder[i])
                DungeonTimer.DungeonCard.addClearButtons('Clear Best', 'Clear Avg', deleteBestTime, deleteAverageTime,i)

            };
        }

        ////////////////////
        //internal methods//
        ////////////////////

        function HideMDTButtons() {
            var clearButtons = document.getElementsByClassName("mdtbutton");
            var grid = document.getElementsByClassName('mdtGridContainer')[0];
            var cards = grid.getElementsByClassName('mdtCardContainer');

            clearButtons.forEach(button => button.style.display = "none");
            grid.style.gridTemplateRows = "167px 167px 167px 167px 167px";
            cards.forEach(card => card.style.height = '166px');
        }

        function ShowMDTButtons() {
            var clearButtons = document.getElementsByClassName("mdtbutton");
            var grid = document.getElementsByClassName('mdtGridContainer')[0];
            var cards = grid.getElementsByClassName('mdtCardContainer');

            clearButtons.forEach(button => button.style.display = "block");
            grid.style.gridTemplateRows = "207px 207px 207px 207px 207px";
            cards.forEach(card => card.style.height = '206px');
        }

        function deleteBestTime(index) {
            console.log('Best Time Cleared.');
            window.DungeonTimerData[dungeonOrder[index]][2] = 0
            UpdateMenu(index)
        }

        function deleteAverageTime(index) {
            console.log('Average Time Cleared.');
            window.DungeonTimerData[dungeonOrder[index]][3] = []
            UpdateMenu(index)
        }

        function AverageTimeCalc(list) {
            if (list.length >0)
            {
                return list.reduce((a, b) => a + b, 0)/list.length
            }
            else
            {
                return 0
            }
        }

        // Convert milliseconds to minutes/seconds and format them
        function formatTime(ms) {
            let seconds = Math.round(ms / 100)/10;
            // split minutes and seconds
            let m = Math.floor(seconds / 60);
            let s = ('0000'+Number(seconds- m*60).toFixed(1)).slice(-4) ;

            var formattedTime = m + "min " + s + "s";

            // append m and s etc then concat and return
            if (ms == 0)
            {
                return "-";
            }
            else
            {
                return formattedTime;
            }
        }

        //declare global variables that will be used to measure time
        var dungeonTickStopwatch = [];
        var dungeonStartTick;
        var dungeonIconElement = document.getElementById("combat-dungeon-img");

        // Store a reference to the game's selectDungeon function
        combatManager._selectDungeon = combatManager.selectDungeon;
        // Overwrite the game's selectDungeon function to inject timer function and tooltip generation function
        combatManager.selectDungeon = (dungeonID) => {
            combatManager._selectDungeon(dungeonID);
            try {
                //start the timer
                dungeonStartTick = combatManager.tickCount;
                generateTooltip(dungeonID);
            } catch (e) {
                console.error(e);
            }
        };

        // Store a reference to the game's pauseDungeon function
        combatManager._pauseDungeon = combatManager.pauseDungeon;
        // Overwrite the game's pauseDungeon function to stop the stopwatch when the dungeon is paused
        combatManager.pauseDungeon = () => {
            try {
                //stop the watch and add the time to the array
                dungeonTickStopwatch.push(combatManager.tickCount - dungeonStartTick);
            } catch (e) {
                console.error(e);
            } finally {
                combatManager._pauseDungeon();
            }
        };

        // Store a reference to the game's resumeDungeon function
        combatManager._resumeDungeon = combatManager.resumeDungeon;
        // Overwrite the game's resumeDungeon function to restart the stopwatch when the dungeon is resumed
        combatManager.resumeDungeon = () => {
            try {
                //end the timer and process the output
                dungeonStartTick = combatManager.tickCount;
            } catch (e) {
                console.error(e);
            } finally {
                combatManager._resumeDungeon();
            }
        };

        combatManager._stopCombat = combatManager.stopCombat;
        // Overwrite the game's stopCombat function to empty the stopwatch array in case of death or if running away, and destroy the tooltip
        combatManager.stopCombat = () => {
            try {
                if (dungeonIconElement._tippy)
                    dungeonIconElement._tippy.destroy();
                dungeonTickStopwatch = []
            } catch (e) {
                console.error(e);
            } finally {
                combatManager._stopCombat();
            }
        };

        const _rollForPetDungeonPet = rollForPetDungeonPet;
        // Overwrite the game's rollForPetDungeonPet function to tally timers, update dungeon data and destroy tooltip when the dungeon is completed
        rollForPetDungeonPet = (petID, dungeonID) => {
            try {
                //end the timer and process the output
                var dungeonTimeTick = combatManager.tickCount - dungeonStartTick;
                if (!isNaN(dungeonTimeTick))
                {
                    dungeonTickStopwatch.forEach(tickCount => dungeonTimeTick += tickCount);
                    dungeonTickStopwatch = []
                    FillLocalStorageValues(dungeonID,dungeonTimeTick*50);

                    if (loadingOfflineProgress == false)
                    {
                        UpdateMenu(dungeonID);
                    }
                    //console.log(dungeonTimeTick*50);
                }

                if (dungeonIconElement._tippy)
                {
                    dungeonIconElement._tippy.destroy();
                }
                generateTooltip(dungeonID);

                dungeonStartTime = Date.now();
                dungeonStartTick = combatManager.tickCount;


            } catch (e) {
                console.error(e);
            } finally {
                _rollForPetDungeonPet(petID, dungeonID);
            }
        };

        //update the data of the selected dungeon in local storage
        function FillLocalStorageValues(dungeonID,dungeonTime) {
            //Dungeon Completion count
            window.DungeonTimerData[dungeonID][0]++; //used for average time
            //window.DungeonCompCount[dungeonID]++; //used for display
            //Latest time
            window.DungeonTimerData[dungeonID][1] = dungeonTime;
            //Best time
            if (window.DungeonTimerData[dungeonID][2] == 0 || window.DungeonTimerData[dungeonID][2] > window.DungeonTimerData[dungeonID][1])
            {
                window.DungeonTimerData[dungeonID][2] = window.DungeonTimerData[dungeonID][1]
            }
            //Average time
            //store 20 times and average
            if (window.DungeonTimerData[dungeonID][3].length === 20)
            {
                window.DungeonTimerData[dungeonID][3].splice(0, 1);
            }

            window.DungeonTimerData[dungeonID][3].push(dungeonTime);

        }

        //Update the values for the completed dungeon in the script menu
        function UpdateMenu(dungeonID){
            document.getElementById('Completion'+dungeonID).innerText = window.DungeonCompCount[dungeonID]
            document.getElementById('PreviousTime'+dungeonID).innerText = formatTime(window.DungeonTimerData[dungeonID][1])
            document.getElementById('BestTime'+dungeonID).innerText = formatTime(window.DungeonTimerData[dungeonID][2])
            document.getElementById('AverageTime'+dungeonID).innerText = formatTime(AverageTimeCalc(window.DungeonTimerData[dungeonID][3]))
        }

        //create tooltip and populate its content
        function generateTooltip(dungeonID) {
            // Generate progression Tooltips
            if (!dungeonIconElement._tippy) {
                tippy(dungeonIconElement, {
                    allowHTML: true,
                    interactive: false,
                    animation: false,
                });
            }
            var tooltip = '' ;
            if (window.DungeonTimerData[dungeonID][0] == 0)
            {
                tooltip = "No time on record";
            }
            else
            {
                tooltip += 'Previous Time : ' + formatTime(window.DungeonTimerData[dungeonID][1]) + '<br>';
                tooltip += 'Best Time : ' + formatTime(window.DungeonTimerData[dungeonID][2]) + '<br>';
                tooltip += 'Average Time : ' + formatTime(AverageTimeCalc(window.DungeonTimerData[dungeonID][3]));
            }
            // wrap and return
            dungeonIconElement._tippy.setContent(`<div>${tooltip}</div>`);
        }
    }

    function loadDungeonTimer() {
        // Loading script
        DungeonTimer.log('loading...');

        DungeonTimer.log('loaded!');
        setTimeout(DungeonTimer.createSettingsMenu, 50);

        // regularly save settings to local storage
        setInterval(window.DungeonTimerSettings.save, 1000)
    }
}

// inject the script
(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);
})();


//all the next functions are taken from Melvor Idle Combat Simulator (https://github.com/visua0/Melvor-Idle-Combat-Simulator-Reloaded) and are used to create the sidebar menu

destroyMenu = (menuItemId, modalID, menuID = 'mdtToolsMenu') => {
    // remove the MICSR tab access point
    const tab = document.getElementById(menuItemId);
    if (tab !== null) {
        window.menuTabs = window.menuTabs.filter(x => x !== tab);
        tab.remove();
    }
    // remove the tools menu if it is empty
    const menu = document.getElementById(menuID);
    if (menu !== null && menu.length === 0) {
        menu.remove();
    }
    // hide and remove the modal
    const modal = document.getElementById(modalID);
    if (modal !== null) {
        $(modal).modal('hide');
        $(modal).modal('dispose');
        modal.remove();
    }
}

addMenuItem = (itemTitle, iconSrc, accessID, modalID, menuTitle = 'Tools', menuID = 'mcsToolsMenu', eyeID = 'mdtHeadingEye') => {
    createMenu(menuTitle, menuID, eyeID);
    if (window.menuTabs === undefined) {
        window.menuTabs = [];
    }

    const tabDiv = document.createElement('li');
    window.menuTabs.push(tabDiv);
    tabDiv.id = accessID;
    tabDiv.style.cursor = 'pointer';
    tabDiv.className = 'nav-main-item mdtNoSelect';

    const menuButton = document.createElement('div');
    menuButton.className = 'nav-main-link nav-compact';
    menuButton.dataset.toggle = 'modal';
    menuButton.dataset.target = '#' + modalID;
    tabDiv.appendChild(menuButton);

    const icon = document.createElement('img');
    icon.className = 'nav-img';
    icon.src = iconSrc;
    menuButton.appendChild(icon);

    const menuText = document.createElement('span');
    menuText.className = 'nav-main-link-name';
    menuText.textContent = itemTitle;
    menuButton.appendChild(menuText);

    document.getElementById(menuID).after(tabDiv);

    // return access point
    return tabDiv;
}

addModal = (title, id, content) => {
    // create modal
    const modal = document.createElement('div');
    modal.id = id;
    modal.className = 'modal';

    // create dialog
    const modalDialog = document.createElement('div');
    modalDialog.className = 'modal-dialog';
    modal.appendChild(modalDialog);

    // create content wrapper
    const modalContent = document.createElement('div');
    modalContent.className = 'modal-content mdtmodal-content';
    modalDialog.appendChild(modalContent);

    // create header
    const modalHeader = $(`<div class="block block-themed block-transparent mb-0"><div class="block-header bg-primary-dark">
        <h3 class="block-title">${title}</h3>
        <div class="block-options"><button type="button" class="btn-block-option" data-dismiss="modal" aria-label="Close">
        <i class="fa fa-fw fa-times"></i></button></div></div></div>`);
    $(modalContent).append(modalHeader);

    // add content
    content.forEach(x => modalContent.appendChild(x));

    // insert modal
    document.getElementById('page-container').appendChild(modal);

    // return modal
    return modal;
}

createMenu = (title, menuID, eyeID) => {
    // check if tools menu already exists
    let menu = document.getElementById(menuID);
    if (menu !== null) {
        return menu;
    }

    // Create new tools menu
    menu = document.createElement('li');
    menu.id = menuID;
    menu.className = 'nav-main-heading mdtNoSelect';
    menu.textContent = title;

    // Create heading eye
    const headingEye = document.createElement('i');
    headingEye.className = 'far fa-eye text-muted ml-1';
    headingEye.id = eyeID;
    headingEye.onclick = () => headingEyeOnClick(eyeID);
    headingEye.style.cursor = 'pointer';
    menu.appendChild(headingEye);
    window.eyeHidden = false;

    // insert menu before Minigames
    document.getElementsByClassName('nav-main-heading').forEach(heading => {
        if (heading.textContent === 'Minigame') {
            heading.parentElement.insertBefore(menu, heading);
        }
    });
}

headingEyeOnClick = (eyeID) => {
    const headingEye = document.getElementById(eyeID);
    if (window.eyeHidden) {
        headingEye.className = 'far fa-eye text-muted ml-1';
        window.menuTabs.forEach(tab => tab.style.display = '');
        window.eyeHidden = false;
    } else {
        headingEye.className = 'far fa-eye-slash text-muted ml-1';
        window.menuTabs.forEach(tab => tab.style.display = 'none');
        window.eyeHidden = true;
    }
}


Card = class {
    constructor(parentElement, height, inputWidth, outer = false) {
        this.outerContainer = document.createElement('div');
        this.outerContainer.className = `mdtCardContainer${outer ? ' mdtOuter block block-rounded border-top border-combat border-4x bg-combat-inner-dark' : ''}`;
        if (height !== '') {
            this.outerContainer.style.height = height;
        }
        this.container = document.createElement('div');
        this.container.className = 'mdtCardContentContainer';
        this.outerContainer.appendChild(this.container);
        parentElement.appendChild(this.outerContainer);
        this.inputWidth = inputWidth;
        this.dropDowns = [];
        this.numOutputs = [];
    }

    addImage(imageSource, imageSize, imageID = '') {
        const newImage = document.createElement('img');
        newImage.style.width = `${imageSize}px`;
        newImage.style.height = `${imageSize}px`;
        newImage.id = imageID;
        newImage.src = imageSource;
        const div = document.createElement('div');
        div.className = 'mb-1';
        div.style.textAlign = 'center';
        div.appendChild(newImage);
        this.container.appendChild(div);
        return newImage;
    }

    addNumberOutput(labelText, initialValue, height, imageSrc, outputID, setLabelID = false) {
        if (!outputID) {
            outputID = `mdt ${labelText} Output`;
        }
        const newCCContainer = this.createCCContainer();
        if (imageSrc && imageSrc !== '') {
            newCCContainer.appendChild(this.createImage(imageSrc, height));
        }
        const newLabel = this.createLabel(labelText, outputID, setLabelID);
        if (setLabelID) {
            newLabel.id = `mdt ${labelText} Label`;
        }
        newCCContainer.appendChild(newLabel);
        const newOutput = document.createElement('span');
        newOutput.className = 'mdtNumberOutput';
        newOutput.style.width = this.inputWidth;
        newOutput.textContent = initialValue;
        newOutput.id = outputID;
        newCCContainer.appendChild(newOutput);


        this.container.appendChild(newCCContainer);
        this.numOutputs.push(newOutput);
    }

    addSectionTitle(titleText, titleID) {
        const newSectionTitle = document.createElement('div');
        if (titleID) {
            newSectionTitle.id = titleID;
        }
        newSectionTitle.textContent = titleText;
        newSectionTitle.className = 'mdtSectionTitle';
        const titleContainer = document.createElement('div');
        titleContainer.className = 'd-flex justify-content-center';
        titleContainer.appendChild(newSectionTitle);
        this.container.appendChild(titleContainer);
    }

    addClearButtons(buttonText1,buttonText2, onclickCallback1, onclickCallback2, index) {
        const newButton1 = document.createElement('button');
        newButton1.type = 'button';
        newButton1.id = `MCS ${buttonText1} Button`;
        newButton1.className = 'btn btn-danger m-1 mdtbutton';
        newButton1.style.width = `50%`;
        newButton1.style.color = `#fff`;
        newButton1.textContent = buttonText1;
        newButton1.onclick = () => onclickCallback1(index);

        const newButton2 = document.createElement('button');
        newButton2.type = 'button';
        newButton2.id = `MCS ${buttonText2} Button`;
        newButton2.className = 'btn btn-danger m-1 mdtbutton';
        newButton2.style.width = `50%`;
        newButton2.style.color = `#fff`;
        newButton2.textContent = buttonText2;
        newButton2.onclick = () => onclickCallback2(index);

        const buttonContainer = document.createElement('div');
        buttonContainer.className = 'd-flex';
        buttonContainer.appendChild(newButton1);
        buttonContainer.appendChild(newButton2);
        this.container.appendChild(buttonContainer);
    }

    createCCContainer() {
        const newCCContainer = document.createElement('div');
        newCCContainer.className = 'mdtCCContainer';
        return newCCContainer;
    }

    createLabel(labelText, referenceID) {
        const newLabel = document.createElement('label');
        newLabel.className = 'mdtLabel';
        newLabel.textContent = labelText;
        newLabel.for = referenceID;
        return newLabel;
    }

    createImage(imageSrc, height) {
        const newImage = document.createElement('img');
        newImage.style.height = `${height}px`;
        newImage.src = imageSrc;
        return newImage;
    }

    addRadio(labelText, height, radioName, radioLabels, radioCallbacks, initialRadio, imageSrc = '') {
        const newCCContainer = this.createCCContainer();
        if (imageSrc && imageSrc !== '') {
            newCCContainer.appendChild(this.createImage(imageSrc, height));
        }
        newCCContainer.appendChild(this.createLabel(labelText, ''));
        newCCContainer.id = `MCS ${labelText} Radio Container`;
        const radioContainer = document.createElement('div');
        radioContainer.className = 'mcsRadioContainer';
        newCCContainer.appendChild(radioContainer);
        // Create Radio elements with labels
        for (let i = 0; i < radioLabels.length; i++) {
            radioContainer.appendChild(this.createRadio(radioName, radioLabels[i], `MCS ${labelText} Radio ${radioLabels[i]}`, initialRadio === i, radioCallbacks[i]));
        }
        this.container.appendChild(newCCContainer);
    }

    createRadio(radioName, radioLabel, radioID, checked, radioCallback) {
        const newDiv = document.createElement('div');
        newDiv.className = 'custom-control custom-radio custom-control-inline';
        const newRadio = document.createElement('input');
        newRadio.type = 'radio';
        newRadio.id = radioID;
        newRadio.name = radioName;
        newRadio.className = 'custom-control-input';
        if (checked) {
            newRadio.checked = true;
        }
        newRadio.addEventListener('change', radioCallback);
        newDiv.appendChild(newRadio);
        const label = this.createLabel(radioLabel, radioID);
        label.className = 'custom-control-label';
        label.setAttribute('for', radioID);
        newDiv.appendChild(label);
        return newDiv;
    }
}



//define GM_addStyle
if (typeof GM_addStyle == 'undefined') {
    this.GM_addStyle = (aCss) => {
        'use strict';
        let head = document.getElementsByTagName('head')[0];
        if (head) {
            let style = document.createElement('style');
            style.setAttribute('type', 'text/css');
            style.textContent = aCss;
            head.appendChild(style);
            return style;
        }
        return null;
    };
}

//CSS Style for the grid container
GM_addStyle ( "                                                   \
.mdtGridContainer {                                               \
display:                   grid;                                  \
grid-template-columns:     265px 265px 265px;                     \
grid-template-rows:        167px 167px 167px 167px 167px 167px;   \
column-gap:                5px;                                   \
row-gap:                   5px;                                   \
justify-content:           center;                                \
align-items:               center;                                \
}                                                                 \
.mdtmodal-content {                                               \
width:                     fit-content;                           \
padding:                   0px 10px;                              \
}                                                                 \
#mdtModal.show {                                                  \
display:                   flex !important;                       \
}                                                                 \
#mdtModal .modal-dialog {                                         \
max-width:                 95%;                                   \
display:                   inline-block;                          \
}                                                                 \
.mdtSectionTitle {                                                \
text-align:                center;                                \
font-size:                 18px;                                  \
max-width:                 240px;                                 \
margin-top:                0.25rem;                               \
margin-bottom:             0.25rem;                               \
}                                                                 \
.mdtNoSelect {                                                    \
user-select:               none;                                  \
}                                                                 \
.mdtOuter {                                                       \
padding:                   0.25rem;                               \
margin:                    0.25rem !important;                    \
min-width:                 250px;                                 \
}                                                                 \
.mdtNumberOutput {                                                \
text-align:                right;                                 \
border-bottom:             1px solid;                             \
line-height:               1.15;                                  \
padding:                   0 2px;                                 \
}                                                                 \
.mdtSectionTitle {                                                \
text-align:                center;                                \
font-size:                 18px;                                  \
max-width:                 240px;                                 \
margin-top:                0.25rem;                               \
margin-bottom:             0.25rem;                               \
}                                                                 \
.mdtLabel {                                                       \
text-align:                right;                                 \
margin-right:              4px;                                   \
margin-bottom:             0px;                                   \
white-space:               nowrap;                                \
font-weight:               unset;                                 \
}                                                                 \
.mdtCCContainer {                                                 \
display:                   flex;                                  \
align-items:               center;                                \
justify-content:           flex-end;                              \
line-height:               20px;                                  \
padding:                   0 0.25rem;                             \
}                                                                 \
.mdtbutton {                                                      \
display:                   none;                                  \
}                                                                 \
" );