Map Cycler - Bonk.io

Cycles through maps that are shown in the Maps list

Ekde 2021/10/07. Vidu La ĝisdata versio.

// ==UserScript==
// @name         Map Cycler - Bonk.io
// @version      1.0.0
// @description  Cycles through maps that are shown in the Maps list
// @author       Excigma (Original idea and implementation by LEGENDBOSS123)
// @match        https://bonk.io/gameframe-release.html
// @run-at       document-end
// @grant        GM_addStyle
// @namespace https://greasyfork.org/users/416480
// ==/UserScript==

// Credits to LEGENDBOSS123 for their idea and initial implementation

// I need to set up eslint AAAAA;;;;;;;;;;;;
// The code is so bad aaaaaaaaa

// What this does
// - Changes map after every x rounds
(() => {
    // This keeps track of the number of rounds played
    let enabled = false;
    let temporarilyDisabled = false;
    let roundCounter = 0;
    let mapList = [];

    // Get buttons and such... this is kinda messy... if someone knows a better way then tell me :)
    const ingamewinner = document.getElementById("ingamewinner")
    const ingamewinner_scores_right = document.getElementById("ingamewinner_scores_right")
    const newbonklobby_roundsinput = document.getElementById("newbonklobby_roundsinput")
    const newbonklobby_editorbutton = document.getElementById("newbonklobby_editorbutton")
    const mapeditor_midbox_testbutton = document.getElementById("mapeditor_midbox_testbutton")
    const mapeditor_close = document.getElementById("mapeditor_close")
    const pretty_top_exit = document.getElementById("pretty_top_exit")
    const maploadwindowmapscontainer = document.getElementById("maploadwindowmapscontainer")
    const maploadtypedropdown = document.getElementById("maploadtypedropdown")
    const newbonklobby_settingsbox = document.getElementById("newbonklobby_settingsbox");
    const newbonklobby_startbutton = document.getElementById("newbonklobby_startbutton")
    const mapeditorcontainer = document.getElementById("mapeditorcontainer")
    // Add a new button to CYCLE through maps
    const newbonklobby_cyclebutton = newbonklobby_settingsbox.appendChild(document.createElement("div"))

    const sleep = time => new Promise(resolve => setTimeout(resolve, time));

    newbonklobby_cyclebutton.id = "newbonklobby_cyclebutton";

    // Copy the classes over so it steals the "START" button's styles lol
    newbonklobby_cyclebutton.classList = newbonklobby_startbutton.classList
    newbonklobby_cyclebutton.classList.add("brownButtonDisabled") // Make the button disabled

    newbonklobby_cyclebutton.innerText = "CYCLE"

    // Hardcoding CSS go brr
    GM_addStyle(`
    /* Reduce the CYCLE button width, and fit it to the right */
    #newbonklobby_cyclebutton {
        width: 58px;
        position: absolute;
        right: 15px;
        bottom: 15px;
    }

    /* Reduce the START button's width and make sure it stays on the left */
    #newbonklobby_startbutton {
        width: 58px !important;
        right: 73px !important;
    }
    `);

    new MutationObserver(async mutationsList => {
        if (enabled) {
            // The "ingamewinner"is shown. This means someone has won the round.
            if (ingamewinner.style.visibility === "inherit") {
                // It's technically split by \r\n, but in case somehow they switch to unix
                // line endings, this will break. parseInt will ignore the \r anyway
                const scores = ingamewinner_scores_right.textContent?.split("\n")

                // If someone wins and there is a next map, then..
                if (scores && parseInt(scores[0]) === parseInt(newbonklobby_roundsinput.value) && mapList.length !== 0 && !temporarilyDisabled) {
                    // Change maps after a delay
                    ++roundCounter;
                    temporarilyDisabled = true; // Temporarily disable whilst switching to the next map
                    await sleep(1000);

                    ingamewinner.style.display = "none"
                    // Choose the next map. I added sleeping here just to possibly improve the reliability
                    // It shouldn't be needed but shouldn't hurt, either
                    mapList[roundCounter % mapList.length].click();
                    await sleep(100);

                    // Open and close the editor
                    // Adding a delay here will cause an uncomfortable flicker so there is none
                    newbonklobby_editorbutton.click()
                    // Start the game using the button from the editor
                    mapeditor_midbox_testbutton.click()
                }
            } else {
                // Disable until the thing is hidden
                temporarilyDisabled = false
                ingamewinner.style.display = ""
            }
        }
    }).observe(ingamewinner, {
        attributeFilter: ["style"],
        attributeOldValue: true,
    });

    // Detect when the maps list is populated
    new MutationObserver(mutationsList => {
        mapList = Array.from(maploadwindowmapscontainer?.children ?? [])

        if (mapList.length !== 0) {
            shuffle(mapList);
            // Change state of CYCLE once maps are loaded
            if (!newbonklobby_startbutton.classList.contains("brownButtonDisabled")) {
                newbonklobby_cyclebutton.classList.remove("brownButtonDisabled")
            }
        }
    }).observe(maploadwindowmapscontainer, {
        childList: true
    });


    // Detect when the host button is not disabled
    new MutationObserver(mutationsList => {
        if (newbonklobby_startbutton.classList.contains("brownButtonDisabled")) {
            // No host :(
            newbonklobby_cyclebutton.classList.add("brownButtonDisabled")
        } else {
            // Got host (or round ended, apparently)
            if (mapList.length !== 0) {
                newbonklobby_cyclebutton.classList.remove("brownButtonDisabled")
            }
        }
    }).observe(newbonklobby_startbutton, {
        attributeFilter: ["class"]
    });

    // When you exit the game, bonk will make you go to the editor since
    // you clicked the test button in the editor. This closes the editor to return to the menu.
    pretty_top_exit.addEventListener("click", async () => {
        if (enabled) {
            if (mapeditorcontainer.style.display === "block") {
                await sleep(10);
                mapeditor_close.click()
            }
        }
    })

    // Picked a new thing from map picker
    maploadtypedropdown.addEventListener("click", () => {
        if (enabled) roundCounter = 0
    })

    newbonklobby_cyclebutton.addEventListener("click", async () => {
        newbonklobby_startbutton.click();
        enabled = true;
    })

    newbonklobby_startbutton.addEventListener("click", () => {
        enabled = false;
    })

    // Taken from https://stackoverflow.com/a/6274381
    function shuffle(a) {
        let j, x, i;
        for (i = a.length - 1; i > 0; i--) {
            j = Math.floor(Math.random() * (i + 1));
            x = a[i];
            a[i] = a[j];
            a[j] = x;
        }
    }
})()