fsfb scripts

a script for agma.io with features such as Copy Chat, Linesplit Toggle, Anti-AFK, Show Portal Mass, Change Food/Virus Color, and more!

// ==UserScript==
// @name         fsfb scripts
// @namespace    http://tampermonkey.net/
// @version      0.35
// @description  a script for agma.io with features such as Copy Chat, Linesplit Toggle, Anti-AFK, Show Portal Mass, Change Food/Virus Color, and more!
// @author       fishy & firebone
// @match        *://agma.io/*
// @run-at       document-start
// @icon         https://i.imgur.com/8AASK55.png
// @license      GPL-3.0-or-later
// @grant        unsafeWindow
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

/* settings that you can't change in UI but might interfere with other scripts */
let hideAds = true,
    improvedShop = true,
    extraBotPacks = true,
    rightClickCopyChat = true,
    rightClickCopyInfo = true,
    showRemainingAbilityTime = true,
    unlockFreeSkins = true,
    hoverShowSkinID = true,
    coinXPstats = true,
    saveStatsBoxPosition = true,
    showXPdecimals = true,
    whiteBorder4BlackCells = true,
    overrideFastsplitDelay = false, // delays are set automatically, set to "true" if you want custom delays
    fastSplitDelay1 = 60, // delay before first freeze (milliseconds)
    fastSplitDelay2 = 60, // delay before last freeze (milliseconds)
    sortWearablesByOwned = true,
    linesplitClosestSide = false; // make the linespli go to the closest side (adjusted) instead of closest bubble



let settings = {
    hotkeys: [
        {title: "Shoot 7 Ejected", id: "fsfb-key7Feed", key: 0, active: false},
        {title: "Linesplit Lock", id: "fsfb-linesplit", key: 0, active: false},
        {title: "Macro Split Bots", id: "fsfb-MacroSplitBots", key: 0, active: false},
        {title: "Hide UI", id: "fsfb-hideUI", key: 0, active: false}
    ],
    fastsplit_hotkeys: [
        {title: "Fast Onesplit", id: "fsfb-fsOne", keyName: "", keyCode: 0, active: false},
        {title: "Fast Doublesplit", id: "fsfb-fsTwo", keyName: "", keyCode: 0, active: false}
    ],
    checkboxes: [
        {title: "Chat Copy/Cut/Paste", id: "fsfb-copycutpaste", active: false}, // 0
        {title: "Anti-AFK", id: "fsfb-antiAFK", active: false}, // 1
        {title: "Anti-Invis", id: "fsfb-anticloak", active: false}, // 2
        {title: "Linesplit Toggle", id: "fsfb-linetoggle", active: false}, // 3
        {title: "Change Page Title", id: "fsfb-changetitle", active: false}, // 4
        {title: "Hide Shouts", id: "fsfb-hideshouts", active: false}, // 5
        {title: "Hold To Spam Rec/Spd", id: "fsfb-recospeed", active: false}, // 6
        {title: "Show Portal Mass", id: "fsfb-portalmass", active: false}, // 7
        {title: "Power Spawns Overlay", id: "fsfb-pwsoverlay", active: false}, // 8
        {title: "Quick Buy", id: "fsfb-qBuy", active: false}, // 9
    ],
    slowFeed: [
        {title: "Toggle Feed", id: "fsfb-slowFeed", key: 0, active: false},
        {title: "Feed Speed", id: "fsfb-slowfeedtimer", val: 100, active: false}
    ],
    quickSettings: [
        {id: "fsfb-quick-hotkey1", id1: "fsfb-quick-select1", set: "cSkins", key: 0, active: false},
        {id: "fsfb-quick-hotkey2", id1: "fsfb-quick-select2", set: "cWearables", key: 0, active: false},
        {id: "fsfb-quick-hotkey3", id1: "fsfb-quick-select3", set: "cFood", key: 0, active: false},
        {id: "fsfb-quick-hotkey4", id1: "fsfb-quick-select4", set: "cBubbleCells", key: 0, active: false}
    ],
    uiScaling: [
        // {title: "Chat Size", id: "fsfb-chatSize", level: 5},
        {title: "Inventory Size", id: "fsfb-invSize", level: 5},
        {title: "Statsbox Size", id: "fsfb-statsSize", level: 5}
    ],
    theme: [
        {title: "Food Color", id: "fsfb-check-foodcolor", id1: "fsfb-color-foodcolor", color: "#FFFFFF", active: false},
        {title: "Virus Color", id: "fsfb-check-viruscolor", id1: "fsfb-color-viruscolor", color: "#00ff00", active: false},
        {title: "Virus Stroke", id: "fsfb-check-virusstroke", id1: "fsfb-color-virusstroke", color: "#00ff00", active: false},
        {title: "Mothercell Color", id: "fsfb-check-msColor", id1: "fsfb-color-msColor", color: "#cd5564", active: false},
        {title: "Mothercell Stroke", id: "fsfb-check-msStroke", id1: "fsfb-color-msStroke", color: "#cd5564", active: false}
    ],
    theme_boxes: [
        {title: "Fancy Bubble Cells", id: "fsfb-bublecell", active: false}, // 0
        {title: "Show Player Mass", id: "fsfb-showmass", active: false}, // 1
        {title: "Only My Skin", id: "fsfb-myskins", active: false}, // 2
        {title: "Only Party Skins", id: "fsfb-partyskins", active: false}, // 3
        {title: "Only My Nick", id: "fsfb-mynick", active: false}, // 4
        {title: "Only Party Nicks", id: "fsfb-partynicks", active: false}, // 5
        {title: "Spiked Cells", id: "fsfb-spikedcells", active: false} // 6
    ],
    export_import: [
        {title: "Game Settings", id: "fsfb-game-settings", active: false},
        {title: "Game Controls", id: "fsfb-game-controls", active: false},
        {title: "Script Settings", id: "fsfb-script-settings", active: false},
        {title: "Script Theme", id: "fsfb-theme-settings", active: false}
    ]
}

const txtMappings = { "": 0, "BACKSPACE": 8, "TAB": 9, "ENTER": 13, "SHIFT": 16, "CTRL": 17, "ALT": 18, "PAUSE": 19, "CAPSLOCK": 20, "ESC": 27, "SPACE": 32, "PAGEUP": 33, "PAGEDOWN": 34, "END": 35, "HOME": 36, "LEFT": 37, "UP": 38, "RIGHT": 39, "DOWN": 40, "PRTSCN": 44, "INS": 45, "DEL": 46, "WIN": 91, "CONTEXTMENU": 93, "NUM 0": 96, "NUM 1": 97, "NUM 2": 98, "NUM 3": 99, "NUM 4": 100, "NUM 5": 101, "NUM 6": 102, "NUM 7": 103, "NUM 8": 104, "NUM 9": 105, "NUM *": 106, "NUM +": 107, "NUM -": 109, "NUM .": 110, "NUM /": 111, "F1": 112, "F2": 113, "F3": 114, "F4": 115, "F5": 116, "F6": 117, "F7": 118, "F8": 119, "F9": 120, "F10": 121, "F11": 122, "F12": 123, "F13": 124, "F14": 125, "F15": 126, "F16": 127, "F17": 128, "F18": 129, "F19": 130, "F20": 131, "F21": 132, "F22": 133, "F23": 134, "F24": 135, "NUMLOCK": 144, "SCROLLLOCK": 145, ";": 186, "=": 187, ",": 188, "-": 189, ".": 190, "/": 191, "`": 192, "[": 219, "\\": 220, "]": 221, "'": 222 }
const keyCodeMappings = { 0: "", 8: "BACKSPACE", 9: "TAB", 12: "CLEAR", 13: "ENTER", 16: "SHIFT", 17: "CTRL", 18: "ALT", 19: "PAUSE", 20: "CAPSLOCK", 27: "ESC", 32: "SPACE", 33: "PAGEUP", 34: "PAGEDOWN", 35: "END", 36: "HOME", 37: "LEFT", 38: "UP", 39: "RIGHT", 40: "DOWN", 44: "PRTSCN", 45: "INS", 46: "DEL", 91: "WIN", 92: "WIN", 93: "CONTEXTMENU", 96: "NUM 0", 97: "NUM 1", 98: "NUM 2", 99: "NUM 3", 100: "NUM 4", 101: "NUM 5", 102: "NUM 6", 103: "NUM 7", 104: "NUM 8", 105: "NUM 9", 106: "NUM *", 107: "NUM +", 109: "NUM -", 110: "NUM .", 111: "NUM /", 112: "F1", 113: "F2", 114: "F3", 115: "F4", 116: "F5", 117: "F6", 118: "F7", 119: "F8", 120: "F9", 121: "F10", 122: "F11", 123: "F12", 124: "F13", 125: "F14", 126: "F15", 127: "F16", 128: "F17", 129: "F18", 130: "F19", 131: "F20", 132: "F21", 133: "F22", 134: "F23", 135: "F24", 144: "NUMLOCK", 145: "SCROLLLOCK", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'"};

const set = (name, obj) => typeof GM_setValue != "function" ? localStorage.setItem(name, JSON.stringify(obj)) : GM_setValue(name, obj),
      get = (name, default_obj) => typeof GM_getValue != "function" ? localStorage.getItem(name) != null ? JSON.parse(localStorage.getItem(name)) : set(name, default_obj) : GM_getValue(name, default_obj);

if(typeof unsafeWindow === 'undefined') unsafeWindow = window;
const saveSettings = () => set("fsfb-scripts", settings);
const getSettings = () => {
    let settingsPrev = get("fsfb-scripts", settings);
    for(let i in settingsPrev) {
        for(let j in settingsPrev[i]) {
            for(let x in settings){
                for(let y in settings[x]){
                    if(settingsPrev[i][j].id == settings[x][y].id) settings[x][y] = settingsPrev[i][j];
                }
            }
        }
    }
}
getSettings();

const getKey = id => {
    const name = $("#" + id).text();
    return txtMappings[name] == null ? name.charCodeAt(0) : txtMappings[name];
}

const getName = key => keyCodeMappings[key] == null ? String.fromCharCode(key) : keyCodeMappings[key];

const levelSum = lvl => lvl * (lvl - 1) / 2,
      range = arr => Math.max(...arr) - Math.min(...arr),
      sigma = arr => arr.reduce((a, b) => a + b),
      mean = arr => sigma(arr) / arr.length,
      variance = arr => arr.reduce((a, b) => a + (b - mean(arr)) ** 2, 0) / arr.length,
      standardDeviation = arr => Math.sqrt(variance(arr)),
      ascending = arr => arr.sort((a, b) => a - b),
      getIQR = arr => quartile(arr, .75) - quartile(arr, .25),
      round = (num, places) => Math.round(num * +("1e" + places)) / +("1e" + places);
const median = arr => {
    const mid = ~~(arr.length / 2),
          asc = ascending(arr);
    return arr.length % 2 !== 0 ? asc[mid] : (asc[mid - 1] + asc[mid]) / 2;
}
const getProperty = (arr, property) => {
    let newArr = [];
    for (let i of arr) newArr.push(i[property]);
    return newArr;
}
const quartile = (arr, q) => {
    const sorted = ascending(arr),
          pos = (sorted.length - 1) * q,
          base = ~~pos,
          rest = pos - base;
    return sorted[base + 1] !== null ? sorted[base] + rest * (sorted[base + 1] - sorted[base]) : sorted[base]
}
const checkOutliers = (arr, returnOutliers) => {
    let nonOutliers = [], outliers = [];
    const IQR = getIQR(arr),
          Q1 = quartile(arr, .25),
          Q3 = quartile(arr, .75);
    for (let i of arr) i < Q1 - 1.5 * IQR || i > Q3 + 1.5 * IQR ? outliers.push(i) : nonOutliers.push(i);
    return returnOutliers ? outliers : nonOutliers;
}
const msToTime = ms => {
    let sec = ~~((ms / 1e3) % 60),
        min = ~~((ms / 6e4) % 60),
        hr = ~~(ms / 36e5);
    return (hr < 10 ? "0" + hr : hr) + ":" + (min < 10 ? "0" + min : min) + ":" + (sec < 10 ? "0" + sec : sec);
}
const changeTitle = title => {
    if(document.title != title) document.title = title;
};

if(!settings.checkboxes[5].active) changeTitle('Agma.io');

if(unlockFreeSkins){
    const ytSkins = ["", 56, 1657, 2281, 2282, 2297, 2331, 2529, 2626, 2683, 2816, 2832];
    for (let i of ytSkins) localStorage.setItem('ytSkin' + i, '1');
    localStorage.setItem('fbSkin', '1');
}

['paste', 'copy', 'cut'].forEach(a => {
    unsafeWindow.addEventListener(a, e => {
        if(!$('#fsfb-copycutpaste').is(':checked')) return;
        e.stopImmediatePropagation();
    }, true)
});

const afterLoaded = () => {
    // attempt to prevent the script from being active on subpages of agma.io
    if($('#friendResizer').length < 1 || $('#megaholder').length < 1 || $('#preroll').length < 1) return;

    $('.setting-tablink').css({'width' : '30%', "font-size" : "font-size: calc(0.3vw + 7.5px);"});
    $('#settingTab2').after(`<button id="settingTab4" class="setting-tablink" onclick="openSettingPage(4);" style="width: 9%; font-size: calc(0.3vw + 7.5px);"><div class="fa fa-cogs fa-lg" style="font-size: 1.25em; color: lightgray;"></div></button>`);
    $('#settingPage3').after(`<div id="settingPage4" class="setting-tabcontent"><div class="row"><div class="col-md-10 col-md-offset-1 stng" id="fsfb-settings-main" style="padding:0"><div id="fsfb-settings-left"><section id="fsfb-sect-checkbox" class="padbot10 fsfb-sect-ch"></section><section id="fsfb-sect-theme" class="fsfb-sect-ch"></section></div><div id="fsfb-settings-right"><section id="fsfb-sect-hotkeys" class="padbot10"></section><section id="fsfb-sect-slowfeed" class="padbot10"></section><section id="fsfb-sect-quickSettings" class="padbot10"></section><section id="fsfb-sect-uiScale" class="padbot10"></section><section id="fsfb-sect-imexport" class="fsfb-sect-ch"></section></div></div></div></div>`);
    $('.container').eq(0).css("max-width", "1250px");
    $('#fsfb-sect-checkbox').append(`<p class="hotkey-paragraph">Script Features</p>`);

    // add checkbox HTML
    for(let i of settings.checkboxes){
        $('#fsfb-sect-checkbox').append(`<label><input id="${i.id}" type="checkbox"><p> ${i.title} </p></label>`);
        $( "#" + i.id).change(function() {
            changeSettings(this.id, $(this).is(':checked'));
        });
    }

    // add import/export HTML
    $('#fsfb-sect-imexport').append(`<p class="hotkey-paragraph">Import/Export</p>`);
    for(let i of settings.export_import){
        $('#fsfb-sect-imexport').append(`<label><input id="${i.id}" type="checkbox"><p> ${i.title} </p></label>`);
        $( "#" + i.id).change(function() {
            changeSettings(this.id, $(this).is(':checked'));
        });
    }
    $('#fsfb-sect-imexport').append(`<div id="fsfb-ximport-cont"><div id="fsfb-export-btn" class="fsfb-eximport">Export</div><div id="fsfb-import-btn" class="fsfb-eximport">Import</div></div>`);

    $('#fsfb-sect-theme').append(`<p class="hotkey-paragraph">Game Theme</p`)
    for(let i of settings.theme){
        $('#fsfb-sect-theme').append(`<label><input id="${i.id}" type="checkbox"><p> ${i.title}</p><div style="background-color: black;"><input id="${i.id1}"type="color"></div></label>`);
        $( "#" + i.id).change(function() {
            changeSettings(this.id, $(this).is(':checked'));
        });
        $( "#" + i.id1).change(function() {
            changeSettings(this.id, this.value);
            $(this).parent().css('background-color', this.value); // bcs the regular [input="color"] looks rly shit, change color of overlayed div instead
        });
    }
    for(let i of settings.theme_boxes){
        $('#fsfb-sect-theme').append(`<label><input id="${i.id}" type="checkbox"><p> ${i.title} </p></label>`);
        $( "#" + i.id).change(function() {
            changeSettings(this.id, $(this).is(':checked'));
        });
    }

    $('#fsfb-sect-hotkeys').append(`<p class="hotkey-paragraph">Script Hotkeys</p>`);

    const checkHotkeyClicked = (e, thing) => {
        if (e.target.id == thing.id && !thing.active){
            $('#' + thing.id).addClass('selected');
            thing.active = true;
            keysChanging = true; // prevent features from triggering when setting hotkey
        } else {
            thing.active = false;
            $('#' + thing.id).removeClass('selected');
        }
    }
    const checkNewHotkey = (e, thing) => {
        if (!thing.active) return;
        thing.key = e.keyCode;
        $('#' + thing.id).text(getName(e.keyCode));
        $('#' + thing.id).removeClass('selected');
        thing.active = false;
        saveSettings();
        e.preventDefault();
    }
    const checkFsHotkey = (e, thing) => {
        if (!thing.active) return;
        thing.key = e.keyCode;
        thing.keyName = e.key;
        $('#' + thing.id).text(getName(e.keyCode));
        $('#' + thing.id).removeClass('selected');
        thing.active = false;
        saveSettings();
        e.preventDefault();
    }

    const checkPowerupClicked = e => {
        if(!quickBuying || $(e.target).attr('class') == 'purchase-btn confirmation' || $(e.target).attr('class') == 'megaphone-btn') return;
        let pwID;
        switch (e.target.id) {
            case 'invWall':
                pwID = 33;
                break;
            case 'invAntiFreeze':
                pwID = 35;
                break;
            case 'invAntiRecombine':
                pwID = 34;
                break;
            case 'invShield':
                pwID = 38;
                break;
            case 'invFrozenVirus':
                pwID = 36;
                break;
            case 'invRecombine':
                pwID = 1;
                break;
            case 'invSpeed':
                pwID = 2;
                break;
            case 'invGrowth':
                pwID = 6;
                break;
            case 'invSpawnVirus':
                pwID = 7;
                break;
            case 'invSpawnMothercell':
                pwID = 8;
                break;
            case 'invSpawnPortal':
                pwID = 9;
                break;
            case 'invSpawnGoldOre':
                pwID = 10;
                break;
            case 'invFreeze':
                pwID = 5;
                break;
            case 'inv360Shot':
                pwID = 30;
                break;
            case 'inv360Shot':
                pwID = 30;
                break;
            case 'fsfb-minionNuker':
                pwID = 39;
                break;
            case 'fsfb-quickMega':
                pwID = 14;
                break;
            case 'fsfb-quickbuy-img':
                quickBuying = true;
                return;
            default:
                quickBuying = false;
                $('.inventory-box').removeClass('fsfb-shown').find('p').css('display', 'block');
                $('#fsfb-quickbuy').removeClass('activatedInv')
                curserMsg('Quick buy deactivated.', 'red');
                return;
        }
        if(pwID == 14) $('.megaphone-btn')[0].click();
        else $('.purchase-btn.confirmation[item="' + pwID + '"]')[0].click();

        $('.inventory-box').removeClass('fsfb-shown').find('p').css('display', 'block');
        $('#fsfb-quickbuy').removeClass('activatedInv')
        quickBuying = false;
    }

    const slowfeedhotkey = settings.slowFeed[0];

    for(let i of settings.hotkeys){
        $('#fsfb-sect-hotkeys').append(`<br><p>${i.title}</p><div id="${i.id}" class="fsfb-hotkey"></div>`);
        $('#' + i.id).on('contextmenu', e => {
            $('#' + i.id).text('');
            i.key = 0;
            saveSettings();
            e.preventDefault();
        });
    }
    for(let i of settings.fastsplit_hotkeys){
        $('#fsfb-sect-hotkeys').append(`<br><p>${i.title}</p><div id="${i.id}" class="fsfb-hotkey"></div>`);
        $('#' + i.id).on('contextmenu', e => {
            $('#' + i.id).text('');
            i.key = 0;
            i.keyName = '';
            saveSettings();
            e.preventDefault();
        });
    }

    const typing = () => $('input, textarea').is(':focus');
    const sleep = ms => new Promise(r => setTimeout(r, ms));
    const press = key => {
        unsafeWindow.onkeydown({ keyCode: key });
        unsafeWindow.onkeyup({ keyCode: key });
    }
    let fsCheck;
    const fastSplit = async(a) => {
        if(!fsCheck) return void(fsCheck = !0);
        const fps = +$('#fps').text();
        let delay1, delay2; // i think slower cpu speeds can make the keypress times funky so this tries to make it fail less if cpu is slow (low fps)
        if(fps > 55){ // 55+ fps
            delay1 = 60;
            delay2 = 60;
        } else if(fps > 30 && fps < 55){ // 31-54 fps
            delay1 = 65;
            delay2 = 65;
        } else if(fps > 10 && fps < 31){ // 11 - 30 fps
            delay1 = 70;
            delay2 = 70;
        } else if(fps < 11){ // less than 11 fps
            delay1 = 80;
            delay2 = 80;
        }
        if(overrideFastsplitDelay){
            delay1 = fastSplitDelay1;
            delay2 = fastSplitDelay1;
        }
        if([39, 37, 2, 4, 6].indexOf(currentServerId) != -1 || !a) await sleep(delay1);
        press(getKey("keyFreezeSelf"));
        await sleep(delay2);
        press(getKey("keyFreezeSelf"));
    }
    setTimeout(fastSplit, 120);

    Object.defineProperty(KeyboardEvent.prototype, 'keyCode', {
        get: function() {
            switch (this.key.toLowerCase()) {
                case settings.fastsplit_hotkeys[0].keyName.toLowerCase(): return getKey("keySplit");
                case settings.fastsplit_hotkeys[1].keyName.toLowerCase(): return getKey("keyDoubleSplit");
                default: return this.which;
            }
        }
    });

    let slowfeeding = !1, linesplitting = !1, hiddenUI = !1, splittingbots = !1, spamRec = !1, spamSpeed = !1;
    const pressed = e => {
        const key = e.which ? e.which : e.keyCode;
        if($('textarea').is(':focus')) e.stopImmediatePropagation();
        if(typing() || keysChanging) return;
        if(key == settings.hotkeys[0].key){ // 7 feed
            let i = 1;
            let interval = setInterval(() => {
                press(getKey("keyMacroFeed"));
                if(++i > 7) clearInterval(interval);
            }, 80);
            e.preventDefault();
        }
        if(key == settings.hotkeys[1].key && !$('#fsfb-linetoggle').is(':checked')){ // linesplit lock
            linesplitting = true;
            linesplit();
            $("#linesplit-markers div").css('display', 'block');
            e.preventDefault();
        }
        if(key == settings.hotkeys[1].key && $('#fsfb-linetoggle').is(':checked')){ // linesplit lock
            linesplitting = !linesplitting;
            if(linesplitting){
                $("#linesplit-markers div").css('display', 'block');
                linesplit();
            } else {
                $("#linesplit-markers div").css('display', 'none');
                $('#canvas').trigger($.Event('mousemove', {clientX: mosX, clientY: mosY})); // return mouse to where the cursor is
            }
            e.preventDefault();
        }
        if(key == settings.hotkeys[2].key){ // macro split bots
            splittingbots = true;
            const splittingBots = () => {
                if(!splittingbots) return;
                press(getKey("keySplitBots"));
                setTimeout(splittingBots, 40);
            }
            splittingBots();
            e.preventDefault();
        }

        if(key == settings.hotkeys[3].key){ // hide ui
            hiddenUI = !hiddenUI;
            hiddenUI ? _replaceCSS('hideUI-css', '.hideUI{ display: none !important;}') : _replaceCSS('hideUI-css', ''); // replacing CSS bcs class is less likely to be overriden
            e.preventDefault();
        }
        if(e.key.toLowerCase() == settings.fastsplit_hotkeys[0].keyName.toLowerCase() && e.keyCode != 0){ // fast onesplit
            fastSplit(!0);
        }
        if(e.key.toLowerCase() == settings.fastsplit_hotkeys[1].keyName.toLowerCase() && e.keyCode != 0){ // fast doublesplit
            fastSplit(!1);
        }
        if(key == settings.slowFeed[0].key){ // toggle feed
            slowfeeding = !slowfeeding;
            const feeding = () => {
                if(!slowfeeding) return;
                press(getKey("keyMacroFeed"));
                setTimeout(feeding, settings.slowFeed[1].val);
            }
            feeding();
            e.preventDefault();
        }
        if(key == settings.quickSettings[0].key){ // quick settings 1
            let el = $('#' + settings.quickSettings[0].set);
            if($('#fsfb-settings-main')[0].contains(el[0])) el.prop('checked', !el.prop('checked')).trigger('change');
            else el.unbind().click();
            e.preventDefault();
        }
        if(key == settings.quickSettings[1].key){ // quick settings 2
            let el = $('#' + settings.quickSettings[1].set);
            if($('#fsfb-settings-main')[0].contains(el[0])) el.prop('checked', !el.prop('checked')).trigger('change');
            else el.unbind().click();
            e.preventDefault();
        }
        if(key == settings.quickSettings[2].key){ //  quick settings 3
            let el = $('#' + settings.quickSettings[2].set);
            if($('#fsfb-settings-main')[0].contains(el[0])) el.prop('checked', !el.prop('checked')).trigger('change');
            else el.unbind().click();
            e.preventDefault();
        }
        if(key == settings.quickSettings[3].key){ // quick settings 4
            let el = $('#' + settings.quickSettings[3].set);
            if($('#fsfb-settings-main')[0].contains(el[0])) el.prop('checked', !el.prop('checked')).trigger('change');
            else el.unbind().click();
            e.preventDefault();
        }
        if($('#fsfb-recospeed').is(':checked') && key == getKey("keyRecombine")){
            spamRec = true;
            const spammingRec = () => {
                if(!spamRec) return;
                press(getKey("keyRecombine"));
                setTimeout(spammingRec, 10);
            }
            spammingRec();
            e.preventDefault();
        }
        if($('#fsfb-recospeed').is(':checked') && key == getKey("keySpeed")){
            spamSpeed = true;
            const spammingSpeed = () => {
                if(!spamSpeed) return;
                press(getKey("keySpeed"));
                setTimeout(spammingSpeed, 10);
            }
            spammingSpeed();
            e.preventDefault();
        }
    }

    const released = key => {
        if(typing() || keysChanging) return;
        if(key == settings.hotkeys[2].key) splittingbots = false; // macro split bots
        if(key == settings.hotkeys[1].key && !$('#fsfb-linetoggle').is(':checked')){ // linesplit lock
            linesplitting = false;
            $('#canvas').trigger($.Event('mousemove', {clientX: mosX, clientY: mosY})); // return mouse to where the cursor is
            $("#linesplit-markers div").css('display', 'none');
        }
        if(key == getKey("keyRecombine")) spamRec = false;
        if(key == getKey("keySpeed")) spamSpeed = false;
    }
    let pwSpawnCheck = false;
    const changeSettings = (ID, a) => {
        for(let i of settings.uiScaling) if(i.id == ID) i.level = a;
        for(let i of settings.checkboxes) if(i.id == ID) i.active = a;
        for(let i of settings.export_import) if(i.id == ID) i.active = a;
        for(let i of settings.theme_boxes) if(i.id == ID) i.active = a;
        for(let i of settings.theme){
            if(i.id == ID) i.active = a
            if(i.id1 == ID) i.color = a;
        }
        customCells = $("#fsfb-sect-theme>label>input, #fsfb-portalmass, #fsfb-pwsoverlay, #fsfb-anticloak, #fsfb-myskins, #fsfb-mynick, #fsfb-partyskins, #fsfb-partynicks").is(":checked");
        if(ID == "fsfb-hideshouts") a ? $('#megaholder').addClass('hideMegaphone') : $('#megaholder').removeClass('hideMegaphone');
        if(ID == "fsfb-qBuy") a ? $('#fsfb-quickbuy').css('display', 'flex') : $('#fsfb-quickbuy').css('display', 'none');
        if(ID == "fsfb-bublecell" && !$("#cBubbleCells").is(":checked") && a) $('#cBubbleCells').unbind().click();
        if(ID == "fsfb-showmass" && !$("#cMass").is(":checked") && a) $('#cMass').unbind().click();
        if((ID == "fsfb-myskins" || ID == "fsfb-partyskins") && !$("#cSkins").is(":checked") && a) $('#cSkins').unbind().click();
        if((ID == "fsfb-mynick" || ID == "fsfb-partynicks") && !$("#cNames").is(":checked") && a) $('#cNames').unbind().click();
        if(ID == "fsfb-myskins" && a) $('#fsfb-partyskins').prop('checked', false).trigger('change');
        if(ID == "fsfb-partyskins" && a) $('#fsfb-myskins').prop('checked', false).trigger('change');
        if(ID == "fsfb-mynick" && a) $('#fsfb-partynicks').prop('checked', false).trigger('change');
        if(ID == "fsfb-partynicks" && a) $('#fsfb-mynick').prop('checked', false).trigger('change');
        if(ID == "fsfb-pwsoverlay"){
            if(!a && pwSpawnCheck) curserMsg('To hide powerup spawns, you need to respawn or switch servers.', 'red');
            pwSpawnCheck = true;
        }
        if(ID == "fsfb-pwsoverlay") svSwitch = true;
        let zoomLvl = "100%";
        switch(+a) {
            case 1:
                zoomLvl = "50%";
                break;
            case 2:
                zoomLvl = "70%";
                break;
            case 3:
                zoomLvl = "80%";
                break;
            case 4:
                zoomLvl = "90%";
                break;
            case 5:
                zoomLvl = "100%";
                break;
            case 6:
                zoomLvl = "110%";
                break;
            case 7:
                zoomLvl = "125%";
                break;
            case 8:
                zoomLvl = "150%";
                break;
            case 9:
                zoomLvl = "200%";
                break;
            default:
                zoomLvl = "100%";
        }
        if(ID == "fsfb-invSize") $('#inventory').css('zoom', zoomLvl);
        if(ID == "fsfb-statsSize") $('#stats-container').css('zoom', zoomLvl);
        saveSettings();
    }
    // add slowfeed HTML
    $('#fsfb-sect-slowfeed').append(`<p class="hotkey-paragraph">Slow-Feed</p>`);
    $('#fsfb-sect-slowfeed').append(`<br><p>${slowfeedhotkey.title}</p><div id="${slowfeedhotkey.id}" class="fsfb-hotkey"></div>`);
    $('#fsfb-sect-slowfeed').append(`<br><p>${settings.slowFeed[1].title}</p><input id="${settings.slowFeed[1].id}" class="fsfb-hotkey" onkeypress="return onlyNumberKey(event)" maxlength="3"></input>`);

    $('#' + slowfeedhotkey.id).on('contextmenu', e => {
        $('#' + slowfeedhotkey.id).text('');
        slowfeedhotkey.key = 0;
        saveSettings();
        e.preventDefault();
    });

    document.getElementById(settings.slowFeed[1].id).addEventListener("keypress", function(e){
        setTimeout(() => { // goes too fast or smth
            settings.slowFeed[1].val = +$('#' + settings.slowFeed[1].id).val();
            if($('#' + settings.slowFeed[1].id).val() == "") settings.slowFeed[1].val = 0;
            saveSettings();
        }, 5);
    });

    // add quick settings HTML
    $('#fsfb-sect-quickSettings').append(`<p class="hotkey-paragraph">Quick Settings</p>`);

    for(let i of settings.quickSettings){
$('#fsfb-sect-quickSettings').append(`<select id="${i.id1}" class="fsfb-quickchange"><option value="cDark">Dark Theme</option><option value="cFancyGrid">Fancy Grid</option><option value="cSectionGrid">Section Grid</option><option value="cGrid">Gridlines</option><option value="cSkins">Skins</option><option value="cWearables">Wearables</option><option value="cNames">Show Names</option><option value="cMinionNames">Minion Names</option><option value="cLargeNames">Large Names</option><option value="cNameOutlines">Name Outline</option><option value="cMass">Show Mass</option><option value="cFood">Show Food</option><option value="cCellAnimations">Cell Anim</option><option value="cSkinAnimations">Skin Anim</option><option value="cMapBorder">Map Border</option><option value="cCustomBack">Custom BG</option><option value="aCustomBack">Sounds</option><option value="cZoom">Infinite Zoom</option><option value="cFixedZoom">Fixed Zoom</option><option value="cSlowMotion">Slow-Motion</option><option value="cMinionUi">Minion Panel</option><option value="cLeaderboard">Leaderboard</option><option value="cChat">Chat</option><option value="cMinimap">Minimap</option><option value="cFPS">FPS/Ping</option><option value="cColors">Cell Colors</option><option value="cCellBorders">Cell Borders</option><option value="cCellSpikes">Cell Spikes</option><option value="cClassicViruses">Classic Virus</option><option value="cPolygonShapes">Polygon Cells</option><option value="cLineShapes">Line Cells</option><option value="cBubbleCells">Bubble Cells</option><option value="cVisibilityStatus">Prof Visiblity</option><option value="cAllowPartyInvite">Party Inv</option><option value="cAllowPartyAnimations">Party Anim</option><option value="cIconDRank">Dono Icon</option><option value="cGoldCrownChat">Gold Icon</option></select><div id="${i.id}" class="fsfb-hotkey"></div>`);       $('#' + i.id).on('contextmenu', e =>{
            $('#' + i.id).text('');
            i.key = 0;
            saveSettings();
            e.preventDefault();
        });
    };

    $('.fsfb-quickchange').on("change", function(){
        settings.quickSettings[+this.id.replace('fsfb-quick-select', '') - 1].set = this.value;
        saveSettings();
    });

    // add UI scaling
    $('#fsfb-sect-uiScale').append(`<p class="hotkey-paragraph">UI Scaling</p>`);
    for(let i of settings.uiScaling){
        $('#fsfb-sect-uiScale').append(`<div class="fsfb-slider"><p>${i.title}</p><input id="${i.id}" class="fsfb-slider" type="range" min="1" max="9" value="5"></input></div>`);
        $( "#" + i.id).change(function() {
            changeSettings(this.id, $(this).val());
        });
    };

    // add event listeners
    let mosX, mosY,
        keys = {},
        keysChanging = false;

    $(document).on('click', e => {
        for(let i = 0; i < settings.hotkeys.length; i++) checkHotkeyClicked(e, settings.hotkeys[i]);
        for(let i = 0; i < settings.fastsplit_hotkeys.length; i++) checkHotkeyClicked(e, settings.fastsplit_hotkeys[i]);
        for(let i = 0; i < settings.quickSettings.length; i++) checkHotkeyClicked(e, settings.quickSettings[i]);
        checkHotkeyClicked(e, settings.slowFeed[0]);
        checkPowerupClicked(e);
    })
        .on('mousemove', e => {
        if(e.originalEvent == null || !e.originalEvent.isTrusted) return;
        ({clientX: mosX, clientY: mosY} = e);
        if (linesplitting) linesplit();
    })

        .on("keydown", e => {
        if(e.originalEvent == null || !e.originalEvent.isTrusted) return;
        for(let i = 0; i < settings.hotkeys.length; i++) checkNewHotkey(e, settings.hotkeys[i]);
        for(let i = 0; i < settings.fastsplit_hotkeys.length; i++) checkFsHotkey(e, settings.fastsplit_hotkeys[i]);
        for(let i = 0; i < settings.quickSettings.length; i++) checkNewHotkey(e, settings.quickSettings[i]);
        checkNewHotkey(e, settings.slowFeed[0]);
        if (!(e.keyCode in keys)){
            keys[e.keyCode] = !0;
            pressed(e);
        }
        keysChanging = false;
    })
        .on("keyup", e => {
        if(e.originalEvent == null || !e.originalEvent.isTrusted) return;
        delete keys[e.keyCode];
        released(e.keyCode);
    });

    $('#fsfb-export-btn').on('click', () => {
        let script_settings = {},
            script_themes = {};
        for (let i in settings) { // put the settings & themes into different objects
            if (i == "theme" || i == "theme_boxes") script_themes[i] = settings[i];
            else if(i == "export_import") {}
            else script_settings[i] = settings[i];
        }
        let obj = {
            selected: {
                game_settings: $('#fsfb-game-settings').is(':checked'),
                game_controls: $('#fsfb-game-controls').is(':checked'),
                script_settings: $('#fsfb-script-settings').is(':checked'),
                script_theme: $('#fsfb-theme-settings').is(':checked')
            },
            game_settings: localStorage.settings,
            game_controls: localStorage.hotkeys,
            script_settings: script_settings,
            script_theme: script_themes
        }
        // if the setting is turned off, then dont need to export it - set it to null
        if(!obj.selected.game_settings) obj.game_settings = null;
        if(!obj.selected.game_controls) obj.game_controls = null;
        if(!obj.selected.script_settings) obj.script_settings = null;
        if(!obj.selected.script_theme) obj.script_theme = null;

        const a = document.createElement('a'),
              file = new Blob([JSON.stringify(obj)], {type: "text/plain"});
        a.href = URL.createObjectURL(file), a.download = "fishy & firebone script settings", a.click(), URL.revokeObjectURL(a.href); // download a txt file of exported settings
    });

    $('#fsfb-import-btn').on('click', () => {
        swal({
            title: "Import Settings",
            text: "Add your exported settings below and press OK to continue.",
            type: "input",
            showCancelButton: true,
            closeOnConfirm: false,
            animation: "slide-from-top",
            inputPlaceholder: "Paste exported settings here"
        },
             function(inputVal){
            if (inputVal == null) return false;
            if (inputVal == "") {
                swal.showInputError("Please don't leave the input empty!");
                return false
            }
            try {
                let val = JSON.parse(inputVal);
                if(val.selected.script_settings && $('#fsfb-script-settings').is(':checked')){
                    for (let i in val.script_settings) {
                        for (let j in val.script_settings[i]) {
                            for(let x in settings){
                                for(let y in settings[x]){
                                    if(val.script_settings[i][j].id == settings[x][y].id) settings[x][y] = val.script_settings[i][j];
                                }
                            }
                        }
                    }
                }
                if(val.selected.script_theme && $('#fsfb-theme-settings').is(':checked')){
                    for (let i in val.script_theme) {
                        for (let j in val.script_theme[i]) {
                            for(let x in settings){
                                for(let y in settings[x]){
                                    if(val.script_theme[i][j].id == settings[x][y].id) settings[x][y] = val.script_theme[i][j];
                                }
                            }
                        }
                    }
                }
                if(val.selected.game_settings && $('#fsfb-game-settings').is(':checked')) localStorage.setItem('settings', val.game_settings);
                if(val.selected.game_controls && $('#fsfb-game-controls').is(':checked')) localStorage.setItem('hotkeys', val.game_controls);
                // if any agma controls were imported, need to refresh bc it's set using localstorage
                if((val.selected.game_settings && $('#fsfb-game-settings').is(':checked')) || (val.selected.game_controls && $('#fsfb-game-controls').is(':checked'))){
                    swal({
                        title: "Please Refresh!",
                        text: "Refreshing the page is required for your imported agma settings to take effect",
                        type: "warning",
                    });
                } else swal({ title: "Settings Successfully Imported!", type: "success" });
                saveSettings();
                updateScriptSettingsUI();

            } catch (error){
                swal({
                    title: "Something went wrong!",
                    text: "Please make sure you've entered in valid settings",
                    type: "error"
                });
            }
        });

    });

    $('#fsfb-settings-right').append(`<div class="fa fa-2x fa-info-circle" id="fsfb-extra-info" data-toggle="modal" data-target=".fsfb-bug-modal"></div>`)
    // PUT MODAL HERE


    localStorage.ad_l_time = "9e99"; // smth like time since last ad
    $('[id^="agma-io_"], [id^="adWrapper"], #preroll').addClass("fuckAds"); // move ads way off the screen

    let width = unsafeWindow.innerWidth, height = unsafeWindow.innerHeight;
    $(unsafeWindow).on('resize', function(){
        width = unsafeWindow.innerWidth, height = unsafeWindow.innerHeight;
    });

    let pointMove;
    const linesplit = () => {
        if(!linesplitting) return;
        let closest, points = [{n: "#linesplit-top", x: width / 2, y: 0, nx: width / 2, ny: -10e6}, {n: "#linesplit-bottom", x: width / 2, y: height, nx: width / 2, ny: 10e6}, {n: "#linesplit-left", x: 0, y: height / 2, nx: -10e6, ny: height / 2}, {n: "#linesplit-right", x: width, y: height / 2, nx: 10e6, ny: height / 2}];
        if(linesplitClosestSide){
            let closestSide = [mosY / height, (height - mosY) / height, mosX / width, (width - mosX) / width]; // top, bottom, left, right
            closest = points[closestSide.indexOf(Math.min(...closestSide))];
        } else {
            let distance = p => Math.sqrt(Math.pow(mosX - p.x, 2) + Math.pow(mosY - p.y, 2)),
                closestPoint = points.reduce((a, b) => distance(a) < distance(b) ? a : b);
            for (let i = 0; i < points.length; i++) {
                if (closestPoint.x == points[i].x && closestPoint.y == points[i].y) closest = points[i]
            }
        }
        pointMove = {x: closest.nx, y: closest.ny};
        $('canvas').trigger($.Event('mousemove', { clientX: closest.nx, clientY: closest.ny }));
        $("#linesplit-markers div").css('background-color', 'transparent');
        $(closest.n).css('background-color', '#e25615');
    }

    let confBtns = document.getElementsByClassName('purchase-btn confirmation');
    const btnsArr = Array.from(document.getElementsByClassName('purchase-btn confirmation'));

    const changeHTML = (index, price, id, name) => {
        setTimeout(() => {
            const amtDropdown = document.getElementById('shopAmountDropdown')
            document.getElementsByClassName('sweet-alert showSweetAlert')[0].childNodes[7].firstChild.textContent = 'If you click "Buy", you will purchase this item. It will cost ' + price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + ' in total.';
            const dropdownChange = () => {
                if (amtDropdown.value == "custom") return buyCstmAmt(price, id, name);
                let priceSum = (amtDropdown.value * price).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
                document.getElementsByClassName('sweet-alert showSweetAlert')[0].childNodes[7].firstChild.textContent = 'If you click "Buy", you will purchase this item. It will cost ' + priceSum + ' in total.';
            };
            try { // try to remove old event listener before adding new one so they don't stack (no idea if this does anything bcs the event listener might be deleted anyway)
                amtDropdown.removeEventListener('change', dropdownChange);
                amtDropdown.addEventListener('change', dropdownChange);
            }
            catch (error){
                amtDropdown.addEventListener('change', dropdownChange);
            };
        }, 30);
    };
    const buyCstmAmt = (price, id, name) => {
        swal({
            title: "Enter Purchase Amount",
            text: "<span>How many <b>" + name + "</b> would you like to buy?</span>",
            html: true,
            type: "input",
            showCancelButton: true,
            closeOnConfirm: false,
            inputPlaceholder: "Input amount here"
        },
             function(input){
            let pwAmt = +input,
                priceTotal = pwAmt * price;
            if (input == null) return false;
            if (input == "") {
                swal.showInputError("Please don't leave the input empty!");
                return false
            }
            if(!/^[0-9]+$/.test(input) || !(+input >>> 0 === parseFloat(+input))){
                swal.showInputError("Please only enter positive integers!");
                return false
            }
            swal({
                title: 'Confirm',
                text: '<p>If you click "Buy", you will purchase this item. It will cost ' + priceTotal.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + ' in total.<br><small>You chose to purchase ' + pwAmt.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + ' <b>' + name + '</b></small></p>',
                html: true,
                type: 'warning',
                showCancelButton: true,
                closeOnConfirm: false,
                confirmButtonColor: '#4CAF50',
                confirmButtonText: 'Yes, confirm purchase',
                cancelButtonText: 'No, cancel purchase'
            }, function(confirmed){
                if(confirmed) buyPw(id, pwAmt);
            })

        });
    };
    // buy a certain amount of powers including > 255
    const buyPw = (shopID, pwAmt) => {
        if (pwAmt < 1) return;
        if (pwAmt > 255) {
            for (let i = 0; i < ~~(pwAmt / 255); i++) setTimeout(() => purchaseItem(shopID, 255), 250 * i + 250);
            purchaseItem(shopID, pwAmt % 255);
        } else purchaseItem(shopID, pwAmt);
    };


    if(improvedShop){
        // add event listeners to dropdown buttons
        for (let i = 0; i < confBtns.length; i++) {
            confBtns[i].onclick = function () {
                setTimeout(() => {
                    if (document.getElementById('shopAmountDropdown') != null) document.getElementById('shopAmountDropdown').innerHTML += '<option value="20">20</option> <option value="50">50</option> <option value="100">100</option> <option value="250">250</option> <option value="custom">Pick</option>';
                }, 5);
            }
        }
        // add event listeners to shop buttons
        for (let i = 0; i < 16; i++) {
            if (i != 10 && i != 11){
                btnsArr[i].addEventListener('click', () => {
                    let index = i;
                    changeHTML(index, btnsArr[index].getAttribute('price'), btnsArr[index].getAttribute('item'), $('.purchase-btn.confirmation[item="' + btnsArr[index].getAttribute('item') + '"]').prev().find('h3').eq(0).text());
                });
            }
        }
    }


    // add extra bot packs
    const newBots = [
        {title: "100 Bots", time: "24 HOURS", price: "700,000", id: 9},
        {title: "125 Bots", time: "48 HOURS", price: "900,000", id: 10},
        {title: "300 Bots", time: "24 HOURS", price: "900,000", id: 7},
        {title: "100 MASS Bots", time: "1 HOURS", price: "800,000", id: 8},
        {title: "300 Bots", time: "72 HOURS", price: "2,000,000", id: 11},
        {title: "100 MASS Bots", time: "24 HOURS", price: "2,600,000", id: 12}
    ]

    const minul = document.getElementsByClassName('tab-container-section minion scroll')[0].children[0].children[0]
    $('.confirm-minion[item=7], .confirm-minion[item=8]').parent().hide();
    document.getElementById('extraTab').childNodes[0].setAttribute('onclick', '');

    function createEl(i) {
        const botEl = document.createElement('li');
        botEl.setAttribute('class', 'masterTooltip extra-min');
        botEl.setAttribute('title', 'Spawns bots/minions which suicide into your playercell to make you big in no time! Minions follow your mouse and split upon your command! Minions start immediately after you buy them.');
        minul.appendChild(botEl);
        botEl.innerHTML = `
        <div class="title_prch">
        <img src="img/store/minions/minions_tab.png" width="70px" height="60px">
        <div class="minionDescription">
        <h4 style="font-size: 18px;">` + i.title + `</h4>
        <h3 style="margin-top:10px;color:white;"> ` + i.time + `</h3>
        </div> <span class="win-price">` + i.price + `</span>
        </div>
        <a href="#" price="` + i.price + `" item="` + i.id + `" class="purchase-btn2 confirm-minion extra-min">Buy</a>
        `;
    }
    if(extraBotPacks){
        for(let i of newBots) createEl(i);
    }

    // $('.confirm-minion.extra-min').click(function(e) {
    //     e.preventDefault(); // Prevent the href from redirecting directly
    //     var linkURL = $(this).attr("href");
    //     var priceK = $(this).attr("price");
    //     var itemId = $(this).attr("item");
    //     setMinionUi(true);
    //     warnBeforeMinion(linkURL,priceK,itemId);
    // });

    function warnBeforeMinion(linkURL, priceK,itemId) {
        swal({
            title: "Confirm",
            text: 'If you click "Buy", you will purchase these minions. They cost ' + priceK,
            type: "warning",
            showCancelButton: true,
            confirmButtonColor: "#4CAF50",
            confirmButtonText: "Yes, confirm purchase",
            cancelButtonText: "No, cancel purchase"
        }, function() {
            purchaseMinion(itemId);
        });
    }


    // context menu: click on skin -> skin ID to clipboard, click on name -> name to clipboard
    $('#contextPlayer').on('click', e => {
        if(!rightClickCopyInfo) return;
        if($('#contextPlayerSkin').width() + $('#contextPlayerSkin').offset().left + 5 > e.pageX){ // bcs #contextMenu event :dog:
            // cell was clicked
            if($('#contextPlayerSkin').css('background-image') != "none"){
                let skinID = $('#contextPlayerSkin').css('background-image').match(/\/skins\/[0-9]+/g)[0].substring(7); // get the skin image, then match only the skin's ID
                navigator.clipboard.writeText(skinID).then(function() {
                    curserMsg('Skin ID of ' + skinID + ' was copied to your clipboard.', 'green');
                }, function() {
                    curserMsg('Something went wrong. Nothing was added to your clipboard.', 'red');
                });
            } else if($('#contextPlayerSkin').css('background-color') == 'rgb(51, 51, 51)') curserMsg('No player selected. Nothing was added to your clipboard.', 'red');
            else curserMsg('No skin equipped. Nothing was added to your clipboard.', 'red');
        } else { // name was clicked
            if($('#contextPlayerSkin').css('background-color') == 'rgb(51, 51, 51)') curserMsg('No player selected. Nothing was added to your clipboard.', 'red');
            else {
                navigator.clipboard.writeText($('#contextPlayerName').text()).then(function() {
                    curserMsg('Nickname: "' + $('#contextPlayerName').text() + '" was copied to your clipboard', 'green');
                }, function() {
                    curserMsg("Something went wrong. Nothing was added to your clipboard.", "red");
                });
            }
        }
    });


    // little bar at the top of the screen that tells u stuff
    const curserMsg = (msg, color) => {
        if(color == "green") color = "rgb(0, 192, 0)";
        if(color == "red") color = "rgb(255, 0, 0)";
        if(color == "gray") color = "rgb(153, 153, 153)";
        $('#curser').text(msg).show().css('color', color)
        setTimeout(() => $('#curser').fadeOut(400), 4e3);
    }


    // ability time remaining
    let acc_abil = get("fsfb-abil", {});
    let lastClickedPrice, currentPrice, lastID;

    const confirmClicked = () => {
        setTimeout(() => {
            if($('.sweet-alert.showSweetAlert.visible h2').text() != "Success!" || lastClickedPrice != currentPrice) return;
            acc_abil[currentUser] = {...acc_abil[currentUser], ...{[lastID] : Date.now()}};
            set("fsfb-abil", acc_abil);
        }, 925);
    };

    [18, 20, 22, 23].forEach(el => {
        if(!showRemainingAbilityTime) return;
        let h5 = $(`.purchase-btn.confirmation[item="${el}"]`).parents().eq(0).find('div h5');
        h5.clone().insertAfter(h5).addClass('fsfb-fake').hide();
        $(`.purchase-btn[item="${el}"]`).on('click', function () {
            lastClickedPrice = this.getAttribute('price');
            lastID = this.getAttribute('item');
            $('.confirm').attr('disabled', 'true'); // disable so user doesn't buy early - swal is slow to load text
            setTimeout(() => {
                $('.confirm').removeAttr('disabled');
                currentPrice = $('.sweet-alert.showSweetAlert.visible p').eq(0).text().replaceAll(/\D+/g, '');
            }, 750);
            setTimeout(() => $('.confirm')[0].addEventListener('click', confirmClicked), 500);
        })
    });


    // sort wearables by owned
    const waitUntil = condition => new Promise(resolve => {
        let interval = setInterval(() => {
            condition() && (clearInterval(interval), resolve());
        }, 100);
    });
    if(sortWearablesByOwned){
        $('#wearablesTab').on('click', async()=> {
            await waitUntil(() => $('#phpWearables li').length > 55);
            for(let i = 0; i < 2; i++){
                $('[id^="wearableUseBtn"]').each(function(){
                    $($(this).parents().get(2)).insertBefore($("#phpWearables li:eq(0)"));
                })
            }
        });
    }
    // https://stackoverflow.com/questions/2424191/how-do-i-make-an-element-draggable-in-jquery
    $.fn.draggable = function(){
        var $this = this,
            ns = 'draggable_'+(Math.random()+'').replace('.',''),
            mm = 'mousemove.'+ns,
            mu = 'mouseup.'+ns,
            $w = $(window),
            isFixed = ($this.css('position') === 'fixed'),
            adjX = 0, adjY = 0;

        $this.mousedown(function(ev){
            var pos = $this.offset();
            if (isFixed) {
                adjX = $w.scrollLeft(); adjY = $w.scrollTop();
            }
            var ox = (ev.pageX - pos.left), oy = (ev.pageY - pos.top);
            $this.data(ns,{ x : ox, y: oy });
            $w.on(mm, function(ev){
                ev.preventDefault();
                ev.stopPropagation();
                if (isFixed) {
                    adjX = $w.scrollLeft(); adjY = $w.scrollTop();
                }
                var offset = $this.data(ns);
                $this.css({left: ev.pageX - adjX - offset.x, top: ev.pageY - adjY - offset.y});
            });
            $w.on(mu, function(){
                $w.off(mm + ' ' + mu).removeData(ns);
            });
        });
        return this;
    };


    /* xp/coins statistics */

    const updateTimeXP = 12e3; // xp bar updates every 12 seconds, don't change
    let lastMinXP = [];
    let lastHrXP = [];
    let currentPercent, currentLevel, currentXP, currentCoins;

    unsafeWindow.logStatsScriptXP = !1;
    unsafeWindow.logStatsScriptCoins = !1;
    let scriptStartCoins = Date.now();
    let scriptStartXP = Date.now();
    let accounts = {};
    const guiDisplay = "none";
    let coinsHTMLactive = false;

    const updateTimeCoins = 6e3;
    let lastMinCoins = [];
    let lastHrCoins = [];

    // add stats box html

    const statsBody = document.createElement('div');
    statsBody.setAttribute('id', 'stats-container');
    statsBody.style.display = guiDisplay;
    statsBody.innerHTML = `<div id="stats-main"><div id="stats-title"><title id="stats-extra-info">XP Stats - Updating Every 12s</title><div title="Reset Stats" class="fa fa-refresh" id="stats-reset-btn"></div></div><div><div><section id="stats-info"><div><p class="stats-label">Lvl Completed:</p><span>0/0</span></div><div><p class="stats-label">Remaining:</p><span>0</span></div><div><p class="stats-label">Projected (hr):</p><span>0</span></div><div><p class="stats-label">Last Hour:</p><span>0</span><span class="stats-completed">(0/60)</span></div><div><p class="stats-label">Last Minute:</p><span>0</span><span class="stats-completed">(0/5)</span></div><div><p class="stats-label">Last 12s:</p><span>0</span></div><div><p class="stats-label">Minute Mean:</p><span>0</span></div><div><p class="stats-label">Minute Median:</p><span>0</span></div><div><p class="stats-label">Minute Sd:</p><span>0</span></div><div style="display:none"><p class="stats-label">Latest Outliers:</p><span style="display:none">0, 200, 500</span></div><div><p class="stats-label">Session XP:</p><span>0</span></div><div><p class="stats-label">Session Length:</p><span>00:00:00</span></div><div><p class="stats-label" id="stats-sesh-length">Lifetime XP:</p><span>0</span></div><p style="font-size:0" id="last-updated">0</p></section></div></div>`;
    document.querySelector('body').append(statsBody)

    $("#stats-container").draggable();
    let statsboxPos = get("fsfb-statsPos", {top: $("#stats-container")[0].style.top, left: $("#stats-container")[0].style.left});
    if(saveStatsBoxPosition && statsboxPos) $("#stats-container").css({'top' : statsboxPos.top, 'left' : statsboxPos.left});

    $('#stats-reset-btn').on('click', () => {
        if(coinsHTMLactive){
            lastMinCoins = [];
            lastHrCoins = [];
            for(let i in accounts) accounts[i].coins = 0;
            if(accounts[currentUser] !== null && currentUser !== 'Please Login First') accounts[currentUser].coins = currentCoins;
            scriptStartCoins = Date.now();
        } else {
            lastMinXP = [];
            lastHrXP = [];
            for(let i in accounts) accounts[i].xp = 0;
            if(accounts[currentUser] !== null && currentUser !== 'Please Login First') accounts[currentUser].xp = currentXP;
            scriptStartXP = Date.now();
        }
        updateUI();
    });

    class record {
        constructor(val, user, arr) {
            // this.type = this.findWhich(arr); // mostly for debugging
            this.id = this.getID(arr);
            this.user = user;
            this.amount = val;
            this.gained = this.calcGain(val, arr, this.id);
            // this.timestamp = Date.now(); // mostly for debugging
        }
        findWhich(arr) {
            return ((arr == lastMinXP || arr == lastHrXP ? "xp " : "coins " ) + (arr == lastHrXP || arr == lastHrCoins ? "hour" : "minute"));
        }
        calcGain(val, arr, id){
            const prevObj = arr[arr.length - 1];
            if(prevObj && arr == lastHrXP && id == 1) return round(sigma(getProperty(lastMinXP, "gained")), 3);
            if(prevObj && arr == lastHrCoins && id == 1) return round(sigma(getProperty(lastMinCoins, "gained")), 3);
            return prevObj && val - prevObj.amount >= 0 && prevObj.user == this.user && prevObj.amount != 0 ? round(val - prevObj.amount, 3) : 0;
        }
        getID(arr) {
            return arr[arr.length - 1] ? arr[arr.length - 1].id + 1 : 1;
        }

    }

    const xpInfo = () => {
        currentPercent = $('.progress-bar[role=progressbar]')[0].style.width.slice(0, -1) / 100;
        currentLevel = +$('#level.user-level')[0].textContent;
        currentXP = (levelSum(currentLevel) + currentLevel * currentPercent) * 1e3;
    }
    const coinsInfo = () => {
        currentCoins = +($('#coinsDash')[0].textContent.replaceAll(' ', ''));
    }

    const updateUI = () => {
        if(!coinsHTMLactive){
            const xpHr = lastHrXP.length,
                  xpMin = lastMinXP.length,
                  lvlCompleted = currentPercent ? String(Math.round(currentPercent * currentLevel * 1e3, 1)).replace(/\B(?=(\d{3})+(?!\d))/g, ",") + "/" + (currentLevel * 1e3).toLocaleString('en-US') : "0/0",
                  xpRemaining = currentLevel ? Math.round(currentLevel * 1e3 - currentPercent * currentLevel * 1e3) : 0,
                  projectedHr = xpHr > 0 ? Math.round(sigma(getProperty(lastHrXP.slice(-5), "gained")) * 12) : 0,
                  lastHr = xpHr > 0 ? Math.round(sigma(getProperty(lastHrXP, "gained"))) : 0,
                  lastHrCompleted = xpHr > 0 ? xpHr : 0,
                  lastMin = xpMin > 0 ? Math.round(sigma(getProperty(lastMinXP, "gained"))) : 0,
                  lastMinCompleted = xpMin > 0 ? xpMin : 0,
                  lastMinTotal = 6e4 / updateTimeXP,
                  last12sec = xpMin > 0 ? Math.round(lastMinXP[lastMinXP.length - 1].gained) : 0,
                  xBar = xpHr > 0 ? Math.round(mean(getProperty(lastHrXP, "gained"))) : 0,
                  xTilde = xpHr > 0 ? Math.round(median(getProperty(lastHrXP, "gained"))) : 0,
                  standardDev = xpHr > 0 ? Math.round(standardDeviation(getProperty(lastHrXP, "gained"))) : 0,
                  outliers = xpHr > 0 ? checkOutliers(getProperty(lastHrXP, "gained")) : 0,
                  sessionXP = currentXP && accounts[currentUser] ? Math.round(currentXP - accounts[currentUser].xp): 0,
                  sessionLength = msToTime(Date.now() - scriptStartCoins),
                  lifetimeXP = currentXP ? Math.round(currentXP) : 0,
                  updateTime = updateTimeXP;
            document.getElementById('stats-info').innerHTML = `<div><p class="stats-label">Lvl Completed:</p><span>${lvlCompleted.toLocaleString('en-US')}</span></div><div><p class="stats-label">Remaining:</p><span>${xpRemaining.toLocaleString('en-US')}</span></div><div><p class="stats-label">Projected (hr):</p><span>${projectedHr.toLocaleString('en-US')}</span></div><div><p class="stats-label">Last Hour:</p><span>${lastHr.toLocaleString('en-US')}</span><span class="stats-completed">(${lastHrCompleted}/60)</span></div><div><p class="stats-label">Last Minute:</p><span>${lastMin.toLocaleString('en-US')}</span><span class="stats-completed">(${lastMinCompleted + "/" + lastMinTotal})</span></div><div><p class="stats-label">Last 12s:</p><span>${last12sec.toLocaleString('en-US')}</span></div><div><p class="stats-label">Minute Mean:</p><span>${xBar.toLocaleString('en-US')}</span></div><div><p class="stats-label">Minute Median:</p><span>${xTilde.toLocaleString('en-US')}</span></div><div><p class="stats-label">Minute Sd:</p><span>${standardDev.toLocaleString('en-US')}</span></div><div><p class="stats-label" style="display:none">Latest Outliers:</p><span style="display:none">${outliers}</span></div><div><p class="stats-label">Session XP:</p><span>${sessionXP.toLocaleString('en-US')}</span></div><div><p class="stats-label">Session Length:</p><span id="stats-sesh-length">${sessionLength}</span></div><div><p class="stats-label">Lifetime XP:</p><span>${lifetimeXP.toLocaleString('en-US')}</span></div>`;
            $('#stats-extra-info').text(`XP Stats - Updating Every ${updateTime / 1e3}s`).css('color', '#00bbff');
            $('#stats-sesh-length').text(msToTime(Date.now() - scriptStartXP));
        } else {
            const coinsHr = lastHrCoins.length,
                  coinsMin = lastMinCoins.length,
                  coinGoalCompleted = currentCoins ? currentCoins.toLocaleString('en-US') + "/" + Math.ceil(currentCoins / 25e4).toLocaleString('en-US') * 25e4 : 0,
                  coinsRemaining = currentCoins ? 25e4 - currentCoins % 25e4 : 0,
                  projectedHr = coinsHr > 5 ? Math.round(sigma(getProperty(lastHrCoins.slice(-5), "gained")) * 12) : 0,
                  lastHr = coinsHr > 0 ? Math.round(sigma(getProperty(lastHrCoins, "gained"))) : 0,
                  lastHrCompleted = coinsHr > 0 ? coinsHr : 0,
                  lastMin = coinsMin > 0 ? Math.round(sigma(getProperty(lastMinCoins, "gained"))) : 0,
                  lastMinCompleted = coinsMin > 0 ? coinsMin : 0,
                  lastMinTotal = 6e4 / updateTimeCoins,
                  last12sec = coinsMin > 0 ? getProperty(lastHrCoins.slice(-5), "amount")[0] : 0,
                  xBar = coinsHr > 0 ? Math.round(mean(getProperty(lastHrCoins, "gained"))) : 0,
                  xTilde = coinsHr > 0 ? Math.round(median(getProperty(lastHrCoins, "gained"))) : 0,
                  standardDev = coinsHr > 0 ? Math.round(standardDeviation(getProperty(lastHrCoins, "gained"))) : 0,
                  outliers = coinsHr > 0 ? checkOutliers(getProperty(lastHrCoins, "gained")) : 0,
                  sessionXP = currentCoins && accounts[currentUser] ? Math.round(currentCoins - accounts[currentUser].coins): 0,
                  sessionLength = msToTime(Date.now() - scriptStartXP),
                  updateTime = updateTimeCoins;
            document.getElementById('stats-info').innerHTML = `<div style="display:none"><p class="stats-label">Coins Completed:</p><span>${coinGoalCompleted}</span></div><div><p class="stats-label">Remaining:</p><span>${coinsRemaining.toLocaleString('en-US')}</span></div><div><p class="stats-label">Projected (hr):</p><span>${projectedHr.toLocaleString('en-US')}</span></div><div><p class="stats-label">Last Hour:</p><span>${lastHr.toLocaleString('en-US')}</span><span class="stats-completed">(${lastHrCompleted}/60)</span></div><div><p class="stats-label">Last Minute:</p><span>${lastMin.toLocaleString('en-US')}</span><span class="stats-completed">(${lastMinCompleted + "/" + lastMinTotal})</span></div><div style="display:none"><p class="stats-label">Last 12s:</p><span></span></div><div><p class="stats-label">Minute Mean:</p><span>${xBar.toLocaleString('en-US')}</span></div><div><p class="stats-label">Minute Median:</p><span>${xTilde.toLocaleString('en-US')}</span></div><div><p class="stats-label">Minute Sd:</p><span>${standardDev.toLocaleString('en-US')}</span></div><div><p class="stats-label" style="display:none">Latest Outliers:</p><span style="display:none">${outliers}</span></div><div><p class="stats-label">Session Coins:</p><span>${sessionXP.toLocaleString('en-US')}</span></div><div><p class="stats-label">Session Length:</p><span id="stats-sesh-length">${sessionLength}</span></div>`;
            $('#stats-extra-info').text(`Coin Stats - Updating Every ${updateTime / 1e3}s`).css('color', '#ffc800');
            $('#stats-sesh-length').text(msToTime(Date.now() - scriptStartCoins));
        }
    }

    $('.progress-bar').eq(1).parent()[0].style.cursor = "pointer";

    if(coinXPstats){
        $('.progress-bar').eq(1).parent().on("click", () => {
            const statsCont = $('#stats-container');
            if(statsCont[0].style.display == "none") statsCont.fadeIn(400);
            else if(!coinsHTMLactive) statsCont.fadeOut(400);
            coinsHTMLactive = false;
            updateUI();
        }), [$(".dash-coin.dcTopBar").eq(0), $("#coinsTopLeft"), $(".progress-bar-coins").eq(1)].forEach(el => {
            el.on('click', (e) => {
                const statsCont = $('#stats-container');
                if(statsCont[0].style.display == "none") statsCont.fadeIn(400);
                else if(coinsHTMLactive) statsCont.fadeOut(400);
                coinsHTMLactive = true;
                updateUI();
                e.stopImmediatePropagation();
            });
        });
    }

    // copy chat msgs
    if(rightClickCopyChat){
        $('#contextSpectate').after(`<li id="contextCopyChat" class="contextmenu-item enabled"><div class="fa fa-clipboard fa-2x context-icon"></div><p>Copy Chat Messages</p></li>`)
        $('#contextCopyChat').on('click', () => {
            let arr = chatmsgs, str = "";
            if(arr != null){
                for(let i of arr.reverse()) str += ((new Date(i.time).toLocaleTimeString()) + " " + i.name + i.cache.oe + "\n")
                navigator.clipboard.writeText(str).then(function() {
                    curserMsg('Chat messages were successfully added to clipboard.', 'green');
                }, function() {
                    curserMsg('Something went wrong. Nothing was added to your clipboard.', 'red');
                });
            }
            $('#contextMenu').fadeOut(100);
        });
    }

    let quickBuying = false;
    $('#inv360Shot').after(`<div class="inventory-box" id="fsfb-quickbuy" style="display: none;"><img id="fsfb-quickbuy-img" height="80%" src="https://i.imgur.com/tAjoEyU.png"></div>`);
    $('#invCloak').after(`<div class="inventory-box" id="fsfb-quickMega" style="background-image: url(../img/store/megaphone_shout.png); display: none;"></div>`);
    $('#invCloak').after(`<div class="inventory-box" id="fsfb-minionNuker" style="background-image: url(../img/store/minion_nuker.png); display: none;"></div>`);
    $('#fsfb-quickbuy').on('click', function(){
        quickBuying = !quickBuying;
        if (quickBuying){
            $(this).addClass('activatedInv');
            curserMsg('Quick buy activated, click the powerup you would like to buy.', 'green');
            $('.inventory-box').addClass('fsfb-shown').find('p').css('display', 'none');
            $('#invCloak').removeClass('fsfb-shown');
            $('#fsfb-quickbuy').removeClass('fsfb-shown');
        } else {
            $(this).removeClass('activatedInv');
            curserMsg('Quick buy deactivated.', 'red');
            $('.inventory-box').removeClass('fsfb-shown').find('p').css('display', 'block');
        }
    });

    // add linesplit bubbles
    $('body').append(`<div id="linesplit-markers"><div id="linesplit-top"></div><div id="linesplit-right"></div><div id="linesplit-bottom"></div><div id="linesplit-left"></div></div>`); // linesplit html
    // add class to all elements that need to behidden
    $('#inventory, #chat, #minionUi, #infection_remain_zombie, #party, #challengeInfoBox, #gamemodeBox, #infoBox, #brGameContainer, #infGameContainer, #curser, #leaderboard, #minimap, #btnFriends, .innerBoxDashboard2, #fpsBox, #settingsBtn, #megaholder').addClass("hideUI");


    let pushFn = Array.prototype.push,
        spliceFn = Array.prototype.splice,
        prop = null,
        customCells = !0,
        specialCells = !0,
        customDc = false,
        cellProto,
        avgFps = 0,
        fpsArr = [],
        svSwitch = false,
        entArr = null,
        chatmsgs;

    let r1Portal = {
        portal: null,
        lastMass: 0,
        lastMassChange: 0,
        lastValue: 0,
        room: 1
    }, r2Portal = {
        portal: null,
        lastMass: 0,
        lastMassChange: 0,
        lastValue: 0,
        room: 2
    };

    let svInfo = {
        "default": {
            ejPortalMass: 12,
            r1Id: 1,
            r2Id: 7,
            r3Id: null,
            r1StartMass: 500,
            r2StartMass: 500,
            entities: []
        },
        1: { // POPSPLIT
            entities: [
                // A1
                {type: 1, x: 2500, y: 2500, size: 45},
                // r1
                {type: 0, x: 3400, y: 15300, size: 195},
                {type: 1, x: 2500, y: 15000, size: 45},
                {type: 2, x: 3000, y: 14500, size: 35},
                {type: 3, x: 2700, y: 14600, size: 29},
                // r2
                {type: 0, x: 12000, y: 15500, size: 195},
                {type: 1, x: 11000, y: 15000, size: 29},
                {type: 2, x: 12600, y: 14900, size: 35},
                {type: 2, x: 12000, y: 15200, size: 35},
                {type: 2, x: 11300, y: 14900, size: 35},
                {type: 3, x: 12800, y: 14500, size: 29}
            ]
        },
        2: { // SLOWSPLIT
            ejPortalMass: 20,
            r1Id: 1,
            r2Id: 14, // rightmost portal
            r3Id: 6,
            r1StartMass: 500,
            r2StartMass: 500,
            entities: [
                // A1
                {type: 1, x: 2500, y: 2500, size: 45},
                // r1
                {type: 0, x: 7400, y: 21300, size: 195},
                {type: 1, x: 3500, y: 21000, size: 45},
                {type: 2, x: 5000, y: 20500, size: 35},
                {type: 3, x: 3700, y: 20600, size: 29},
                // r2
                {type: 0, x: 14000, y: 22000, size: 195},
                {type: 1, x: 11000, y: 21000, size: 29},
                {type: 2, x: 11300, y: 20900, size: 35},
                {type: 2, x: 12000, y: 21200, size: 35},
                {type: 2, x: 12600, y: 20900, size: 35},
                {type: 3, x: 12800, y: 20500, size: 29},
                // r2 (2nd?)
                {type: 0, x: 22000, y: 21500, size: 195},
                {type: 2, x: 21300, y: 21000, size: 45}
            ]
        },
        4: { // FASTSPLIT
            ejPortalMass: 20,
            r1Id: 1,
            r2Id: 14, // rightmost portal
            r3Id: 6,
            r1StartMass: 500,
            r2StartMass: 500,
            entities: [
                // A1
                {type: 1, x: 2500, y: 2500, size: 45},
                // r1
                {type: 0, x: 7400, y: 21300, size: 195},
                {type: 1, x: 3500, y: 21000, size: 45},
                {type: 2, x: 5000, y: 20500, size: 35},
                {type: 3, x: 3700, y: 20600, size: 29},
                // r2
                {type: 0, x: 14000, y: 22000, size: 195},
                {type: 1, x: 11000, y: 21000, size: 29},
                {type: 2, x: 11300, y: 20900, size: 35},
                {type: 2, x: 12000, y: 21200, size: 35},
                {type: 2, x: 12600, y: 20900, size: 35},
                {type: 3, x: 12800, y: 20500, size: 29},
                // r2 (2nd?)
                {type: 0, x: 22000, y: 21500, size: 195},
                {type: 2, x: 21300, y: 21000, size: 35}
            ]
        },
        5: { // SPLITRUN
            entities: [
                // A1
                {type: 1, x: 2500, y: 2500, size: 45},
                // r1
                {type: 0, x: 3400, y: 15300, size: 195},
                {type: 1, x: 2500, y: 15000, size: 45},
                {type: 2, x: 3000, y: 14500, size: 35},
                {type: 3, x: 2700, y: 14600, size: 29},
                // r2
                {type: 0, x: 12000, y: 15500, size: 195},
                {type: 1, x: 11000, y: 15000, size: 29},
                {type: 2, x: 12600, y: 14900, size: 35},
                {type: 2, x: 11300, y: 14900, size: 35},
                {type: 2, x: 12000, y: 15200, size: 35},
                {type: 3, x: 12800, y: 14500, size: 29}
            ]
        },
        6: { // XINSTA
            ejPortalMass: 12,
            r1Id: 1,
            r2Id: 6,
            r1StartMass: 500,
            r2StartMass: 500,
            entities: [
                // A1
                {type: 1, x: 2500, y: 2500, size: 45},
                // r1
                {type: 0, x: 3400, y: 26300, size: 195},
                {type: 1, x: 2500, y: 26000, size: 45},
                {type: 2, x: 3000, y: 25500, size: 45},
                {type: 3, x: 2700, y: 25600, size: 29},
                // r2
                {type: 0, x: 15000, y: 26500, size: 195},
                {type: 1, x: 14000, y: 26000, size: 29},
                {type: 2, x: 14300, y: 25900, size: 45},
                {type: 2, x: 15000, y: 26200, size: 45},
                {type: 2, x: 15600, y: 25900, size: 45},
                {type: 3, x: 15800, y: 25500, size: 29}
            ]
        },
        7: { // XY
            entities: [
                // A1
                {type: 1, x: 2500, y: 2500, size: 45},
                // r1
                {type: 0, x: 3400, y: 15300, size: 195},
                {type: 1, x: 2500, y: 15000, size: 45},
                {type: 2, x: 3000, y: 14500, size: 35},
                {type: 3, x: 2700, y: 14600, size: 29},
                // r2
                {type: 0, x: 12000, y: 15500, size: 195},
                {type: 1, x: 11000, y: 15000, size: 29},
                {type: 2, x: 11300, y: 14900, size: 35},
                {type: 2, x: 12000, y: 15200, size: 35},
                {type: 2, x: 12600, y: 14900, size: 35},
                {type: 3, x: 12800, y: 14500, size: 29}
            ]
        },
        8: { // INSTANT EU
            entities: [
                // A1
                {type: 1, x: 2500, y: 2500, size: 45},
                // r1
                {type: 0, x: 3400, y: 15300, size: 195},
                {type: 1, x: 2500, y: 15000, size: 45},
                {type: 2, x: 3000, y: 14500, size: 35},
                {type: 3, x: 2700, y: 14600, size: 29},
                // r2
                {type: 0, x: 12000, y: 15500, size: 195},
                {type: 1, x: 11000, y: 15000, size: 29},
                {type: 2, x: 11300, y: 14900, size: 35},
                {type: 2, x: 12000, y: 15200, size: 35},
                {type: 2, x: 12600, y: 14900, size: 35},
                {type: 3, x: 12800, y: 14500, size: 29}
            ]
        },
        9: { // CR EU
            entities: [
                // A1
                {type: 1, x: 2500, y: 2500, size: 45},
                // r1
                {type: 0, x: 3400, y: 27300, size: 195},
                {type: 1, x: 2500, y: 27000, size: 45},
                {type: 2, x: 3000, y: 26500, size: 35},
                {type: 3, x: 2700, y: 26600, size: 29},
                // r2
                {type: 0, x: 12000, y: 27500, size: 195},
                {type: 1, x: 11000, y: 27000, size: 29},
                {type: 2, x: 11300, y: 26900, size: 35},
                {type: 2, x: 12000, y: 27200, size: 35},
                {type: 2, x: 12600, y: 26900, size: 35},
                {type: 3, x: 12800, y: 26500, size: 29}
            ]
        },
        11: { // GIGANTIC 1
            entities: [
                {type: 1, x: 2500, y: 2500, size: 45},
                {type: 3, x: 5000, y: 33000, size: 45},
                {type: 1, x: 18000, y: 18000, size: 45},
                {type: 3, x: 24000, y: 12000, size: 45}
            ]
        },
        12: { // GIANT NA
            entities: [
                {type: 1, x: 2500, y: 2500, size: 45},
                {type: 3, x: 5000, y: 33000, size: 45},
                {type: 1, x: 18000, y: 18000, size: 45},
                {type: 3, x: 24000, y: 12000, size: 45}
            ]
        },
        13: { // SS EU
            ejPortalMass: 13.5,
            r1Id: 12, //Lower room
            r2Id: 11,
            r1StartMass: 500,
            r2StartMass: 500,
            entities: [
                // r1
                {type: 0, x: 1500, y: 27500, size: 195},
                {type: 1, x: 14000, y: 32000, size: 45},
                {type: 3, x: 13200, y: 33500, size: 45},
                {type: 4, x: 12500, y: 32200, size: 142},
                {type: 4, x: 14500, y: 32200, size: 142},
                // r2
                {type: 0, x: 16000, y: 33500, size: 195},
                {type: 1, x: 500, y: 24000, size: 45},
                {type: 3, x: 2500, y: 24000, size: 45},
                {type: 4, x: 900, y: 22000, size: 142},
                {type: 4, x: 500, y: 15000, size: 224},
                // r3 (?)
                {type: 4, x: 1200, y: 5500, size: 224},
                {type: 4, x: 2000, y: 3000, size: 224},
                {type: 4, x: 4000, y: 6000, size: 224},
                {type: 4, x: 4500, y: 3000, size: 224},
                // on map
                {type: 4, x: 8000, y: 3500, size: 224},
                {type: 4, x: 10000, y: 4500, size: 224},
                {type: 4, x: 16000, y: 3000, size: 224},
                {type: 4, x: 15400, y: 2400, size: 224},
                {type: 4, x: 7500, y: 19000, size: 224},
                {type: 4, x: 19200, y: 14000, size: 224},
                {type: 4, x: 24400, y: 24000, size: 224},
                {type: 4, x: 17500, y: 14000, size: 142},
                {type: 4, x: 30000, y: 16000, size: 142}
            ]
        },
        14: { // SS NA
            ejPortalMass: 13.5,
            r1Id: 12, //Lower room
            r2Id: 11,
            r1StartMass: 500,
            r2StartMass: 500,
            entities: [
                // r1
                {type: 0, x: 1500, y: 27500, size: 195},
                {type: 1, x: 14000, y: 32000, size: 45},
                {type: 3, x: 13200, y: 33500, size: 45},
                {type: 4, x: 12500, y: 32200, size: 142},
                {type: 4, x: 14500, y: 32200, size: 142},
                // r2
                {type: 0, x: 16000, y: 33500, size: 195},
                {type: 1, x: 500, y: 24000, size: 45},
                {type: 3, x: 2500, y: 24000, size: 45},
                {type: 4, x: 900, y: 22000, size: 142},
                {type: 4, x: 500, y: 15000, size: 224},
                // r3 (?)
                {type: 4, x: 1200, y: 5500, size: 224},
                {type: 4, x: 2000, y: 3000, size: 224},
                {type: 4, x: 4000, y: 6000, size: 224},
                {type: 4, x: 4500, y: 3000, size: 224},
                // on map
                {type: 4, x: 8000, y: 3500, size: 224},
                {type: 4, x: 10000, y: 4500, size: 224},
                {type: 4, x: 16000, y: 3000, size: 224},
                {type: 4, x: 15400, y: 2400, size: 224},
                {type: 4, x: 7500, y: 19000, size: 224},
                {type: 4, x: 19200, y: 14000, size: 224},
                {type: 4, x: 24400, y: 24000, size: 224},
                {type: 4, x: 17500, y: 14000, size: 142},
                {type: 4, x: 30000, y: 16000, size: 142}
            ]
        },
        17: { // CR AS
            entities: [
                // A1
                {type: 1, x: 2500, y: 2500, size: 45},
                // r1
                {type: 0, x: 3400, y: 27300, size: 195},
                {type: 1, x: 2500, y: 27000, size: 45},
                {type: 2, x: 3000, y: 26500, size: 35},
                {type: 3, x: 2700, y: 26600, size: 29},
                // r2
                {type: 0, x: 12000, y: 27500, size: 195},
                {type: 1, x: 11000, y: 27000, size: 29},
                {type: 2, x: 11300, y: 26900, size: 35},
                {type: 2, x: 12000, y: 27200, size: 35},
                {type: 2, x: 12600, y: 26900, size: 35},
                {type: 3, x: 12800, y: 26500, size: 29}
            ]
        },
        18: { // GIGA 1
            entities: [
                {type: 1, x: 2500, y: 2500, size: 45},
                {type: 3, x: 5000, y: 33000, size: 45},
                {type: 1, x: 18000, y: 18000, size: 45},
                {type: 3, x: 24000, y: 12000, size: 45}
            ]
        },
        19: { // GIGANTIC 2
            entities: [
                {type: 1, x: 2500, y: 2500, size: 45},
                {type: 3, x: 5000, y: 33000, size: 45},
                {type: 1, x: 18000, y: 18000, size: 45},
                {type: 3, x: 24000, y: 12000, size: 45}
            ]
        },
        20: { // CR NA
            entities: [
                // A1
                {type: 1, x: 2500, y: 2500, size: 45},
                // r1
                {type: 0, x: 3400, y: 27300, size: 195},
                {type: 1, x: 2500, y: 27000, size: 45},
                {type: 2, x: 3000, y: 26500, size: 35},
                {type: 3, x: 2700, y: 26600, size: 29},
                // r2
                {type: 0, x: 12000, y: 27500, size: 195},
                {type: 1, x: 11000, y: 27000, size: 29},
                {type: 2, x: 11300, y: 26900, size: 35},
                {type: 2, x: 12000, y: 27200, size: 35},
                {type: 2, x: 12600, y: 26900, size: 35},
                {type: 3, x: 12800, y: 26500, size: 29}
            ]
        },
        23: { // GIGANTIC 3
            entities: [
                {type: 1, x: 2500, y: 2500, size: 45},
                {type: 3, x: 5000, y: 33000, size: 45},
                {type: 1, x: 18000, y: 18000, size: 45},
                {type: 3, x: 24000, y: 12000, size: 45}
            ]
        },
        24: { // GIGANTIC 4
            entities: [
                {type: 1, x: 2500, y: 2500, size: 45},
                {type: 3, x: 5000, y: 33000, size: 45},
                {type: 1, x: 18000, y: 18000, size: 45},
                {type: 3, x: 24000, y: 12000, size: 45}
            ]
        },
        25: { // GIANT 2 NA
            entities: [
                {type: 1, x: 2500, y: 2500, size: 45},
                {type: 3, x: 5000, y: 33000, size: 45},
                {type: 1, x: 18000, y: 18000, size: 45},
                {type: 3, x: 24000, y: 12000, size: 45}
            ]
        },
        26: { // GIGA 2
            entities: [
                {type: 1, x: 2500, y: 2500, size: 45},
                {type: 3, x: 5000, y: 33000, size: 45},
                {type: 1, x: 18000, y: 18000, size: 45},
                {type: 3, x: 24000, y: 12000, size: 45}
            ]
        },
        38: { // Solo Agf
            ejPortalMass: 12,
            r1Id: 1,
            r2Id: 6,
            r1StartMass: 500,
            r2StartMass: 400,
            entities: [
                // A1
                {type: 1, x: 2500, y: 2500, size: 45},
                // room 1
                {type: 0, x: 3500, y: 11500, size: 195},
                {type: 1, x: 2500, y: 11500, size: 45},
                {type: 2, x: 3000, y: 11000, size: 35},
                {type: 3, x: 2700, y: 11100, size: 29},
                // room 2
                {type: 0, x: 9000, y: 11800, size: 180},
                {type: 1, x: 8200, y: 10500, size: 29},
                {type: 2, x: 8300, y: 10900, size: 35},
                {type: 2, x: 9000, y: 11200, size: 35},
                {type: 2, x: 9600, y: 10900, size: 35},
                {type: 3, x: 9800, y: 10500, size: 29}
            ]
        },
        39: { // MEGASPLIT AS
            entities: [
            ]
        },
        42: { // GIANT 3 NA
            entities: [
                {type: 1, x: 2500, y: 2500, size: 45},
                {type: 3, x: 5000, y: 33000, size: 45},
                {type: 1, x: 18000, y: 18000, size: 45},
                {type: 3, x: 24000, y: 12000, size: 45}
            ]
        },
        43: { // Instant AS
            ejPortalMass: 11, //might have to reevaluate
            r1Id: 1,
            r2Id: 13, // rightmost portal
            r3Id: 7,
            r1StartMass: 1004,
            r2StartMass: 250,
            r3StartMass: 1004,
            entities: [
                // A1
                {type: 1, x: 2500, y: 2500, size: 45},
                // r1
                {type: 0, x: 3400, y: 20300, size: 275},
                {type: 1, x: 2500, y: 20000, size: 45},
                {type: 2, x: 3000, y: 19500, size: 35},
                {type: 3, x: 2700, y: 19600, size: 29},
                // r2
                {type: 0, x: 13000, y: 21000, size: 150},
                {type: 1, x: 11000, y: 20000, size: 45},
                {type: 2, x: 11300, y: 19900, size: 35},
                {type: 2, x: 12000, y: 20200, size: 35},
                {type: 2, x: 12600, y: 19900, size: 35},
                {type: 3, x: 12800, y: 19500, size: 45},
                // r2 (2nd?)
                {type: 0, x: 30000, y: 21000, size: 275},
                {type: 1, x: 28000, y: 19900, size: 55},
                {type: 2, x: 28300, y: 19900, size: 45},
                {type: 2, x: 28450, y: 20200, size: 45},
                {type: 2, x: 28600, y: 19900, size: 45},
                {type: 3, x: 28900, y: 19900, size: 55}
            ]
        }
    };
    let noPortalSvIdList = [11, 19, 23, 24, 37, 36, 31, 29, 40, 41, 16, 15, 21, 35, 12, 25, 42, 28, 32, 22, 18, 26, 30, 39];
    let currentServerId = 0;

    try{
        for(let i of JSON.parse(localStorage.gameservers)){
            if(i.isCurrent) currentServerId = i.id;
        }
    } catch {};

    setTimeout(() => {
        svSwitch = true;
    }, 250);

    let ss = unsafeWindow.setserver;
    unsafeWindow.setserver = (sv, sn) => {
        currentServerId = parseInt($(".server-tabmenu").find(".active")[0].id.slice(9));
        r1Portal.portal = null;
        r2Portal.portal = null;
        svSwitch = true;
        ss(sv, sn);
    }

    function getServerValue(value) {
        return (svInfo[currentServerId] && svInfo[currentServerId][value]) ? svInfo[currentServerId][value] : svInfo.default[value]
    }

    function createCell(posX, posY, type, nSize){
        if(!cellProto) return null;

        let color,
            colorDimmed = "#FFFFFF",
            size = 0,
            imageId = 0,
            spikes = null;

        switch(type){
            case 0:
                color = "#622373";
                colorDimmed = "#4e1c5c";
                size = nSize ? nSize : 200;
                imageId = 1;
                break;
            case 1:
                color = "#ff0000";
                colorDimmed = "#cc0001";
                size = nSize ? nSize : 32;
                spikes = {x: posX, y: posY, s: size, p: size};
                imageId = 2;
                break;
            case 2:
                color = "#76ff54";
                colorDimmed = "#66b319";
                size = nSize ? nSize : 35;
                imageId = 3;
                break;
            case 3:
                color = "#ffd000";
                colorDimmed = "#ccb300";
                size = nSize ? nSize : 32;
                spikes = {x: posX, y: posY, s: size, p: size};
                imageId = 4;
                break;
            case 4:
                color = "#00a2e8";
                colorDimmed = "#0081b9";
                size = nSize ? nSize : 150;
                imageId = 5;
                break;
            default:
                color = "#FFFFFF";
                size = 500;
        };

        let cell = new cellProto.constructor();
        cell[prop[41]] = imageId;
        cell[prop[19]] = null;
        cell[prop[50]] = 0;
        cell[prop[40]] = spikes ? 1 : 0;
        cell[prop[26]] = null;
        cell[prop[52]] = false;
        cell[prop[53]] = false;
        cell[prop[39]] = [];
        cell[prop[57]] = 0;
        cell[prop[45]] = false;
        cell[prop[37]] = true;
        cell[prop[24]] = null;
        cell[prop[44]] = false;
        cell[prop[56]] = false;
        cell[prop[47]] = false;
        cell[prop[25]] = null;
        cell[prop[35]] = Date.now();
        cell[prop[51]] = 0;
        cell[prop[42]] = null;
        cell.clanCache = null;
        cell.clanPart = null;
        cell.color = color;
        cell[prop[46]] = 9;
        cell[prop[38]] = spikes;
        cell[prop[33]] = 1;
        cell[prop[36]] = 0;
        cell[prop[31]] = posX;
        cell[prop[32]] = posY;
        cell[prop[3]] = 0;
        cell.id = 1e9;
        cell[prop[21]] = null;
        cell[prop[20]] = null;
        cell.massCache = null;
        cell[prop[11]] = size;
        cell.nSize = size;
        cell.name = null;
        cell.namePart = null;
        cell.nameSize = 0;
        cell.oid = 0;
        cell.ox = posX;
        cell.oy = posY;
        cell[prop[58]] = 0;
        cell.rotation = 0;
        cell.shape = 0;
        cell.size = size;
        cell.skinId = 0;
        cell.strokeSize = size + 4;
        cell.textDrawn = null;
        cell.transform = null;
        cell[prop[18]] = true;
        cell[prop[2]] = colorDimmed ? colorDimmed : dimmColor(color);
        cell[prop[17]] = null;
        cell.x = posX;
        cell[prop[23]] = null;
        cell.y = posY;
        cell[prop[22]] = null;
        cell[prop[54]] = false;
        cell[prop[34]] = Date.now();
        cell[prop[55]] = true;
        return cell;
    }

    function customize(c){
        cellProto = c.__proto__;
        let dc = c.__proto__.de;
        c.__proto__.oldDrawCell = dc;
        c.__proto__.de = function(){
            let cell = this,
                cellType = cell[prop[46]];
            if(cellType == 0){
                if(settings.theme_boxes[1].active){
                    if(cell.oOwnCell === undefined) cell.oOwnCell = cell[prop[45]];
                    cell[prop[45]] = true;
                }
                if(settings.theme_boxes[6].active){
                    if(cell.oSpiked === undefined) cell.oSpiked = cell[prop[56]];
                    cell[prop[56]] = settings.theme_boxes[6].active;
                } else {
                    if(cell.oSpiked != undefined) cell[prop[56]] = cell.oSpiked;
                }
                if(settings.theme_boxes[2].active){
                    if(cell.oHasImage === undefined) cell.oHasImage = cell[prop[18]];
                    cell[prop[18]] = (cell.oOwnCell === undefined ? cell[prop[45]] : cell.oOwnCell) ? cell[prop[18]] : false;
                } else {
                    if(cell.oHasImage != undefined && !settings.theme_boxes[3].active) cell[prop[18]] = cell.oHasImage;
                }
                if(settings.theme_boxes[4].active){
                    if(cell.oName === undefined) cell.oName = cell.name;
                    cell.name = (cell.oOwnCell === undefined ? cell[prop[45]] : cell.oOwnCell) ? cell.name : "";
                } else {
                    if(cell.oName != undefined && !settings.theme_boxes[3].active) cell.name = cell.oName;
                }
                if(settings.theme_boxes[3].active){
                    if(cell.oHasImage === undefined) cell.oHasImage = cell[prop[18]];
                    cell[prop[18]] = cell[prop[53]] ? cell[prop[18]] : false;
                } else {
                    if(cell.oHasImage != undefined && !settings.theme_boxes[2].active) cell[prop[18]] = cell.oHasImage;
                }
                if(settings.theme_boxes[5].active){
                    if(cell.oName === undefined) cell.oName = cell.name;
                    cell.name = cell[prop[53]] ? cell.name : "";
                } else {
                    if(cell.oName != undefined && !settings.theme_boxes[4].active) cell.name = cell.oName;
                }
            }
            if(cellType == 1){
                if(cell.color == "#00ff00") cell.color = "#00ff01";
                if(settings.theme[0].active){
                    if(!cell.oColor) cell.oColor = cell.color;
                    cell.color = settings.theme[0].color;
                } else {
                    if(cell.oColor) cell.color = cell.oColor;
                }
            }
            return dc.apply(this, arguments);
        }
        customDc = true;
    }

    function dimmColor(color) {
        var r = (~~(parseInt(color.substr(1, 2), 16) * 0.5)).toString(16),
            g = (~~(parseInt(color.substr(3, 2), 16) * 0.5)).toString(16),
            b = (~~(parseInt(color.substr(5, 2), 16) * 0.5)).toString(16);
        if (r.length == 1) r = "0" + r;
        if (g.length == 1) g = "0" + g;
        if (b.length == 1) b = "0" + b;
        return "#" + r + g + b
    }

    let sn = unsafeWindow.setNick;
    unsafeWindow.setNick = function(name, rspwn){
        setTimeout(() => {svSwitch = true}, 1000);
        sn(name, rspwn);
    };

    Array.prototype.push = function(){
        if(this.length && typeof this[0].message === 'string') chatmsgs = this;
        if(this.length && typeof this[0].id == "number" && typeof this[0].color == "string"){
            if(customCells){
                let cell = this[this.length - 1];
                let pushedCell = arguments[0];
                if(!prop){
                    prop = Object.keys(cell);
                    if(prop.length != 59 || prop[28] != "massCache"){
                        // unsafeWindow.swal({
                        //     title: "FSFB scripts experienced an error, please contact authors",
                        //     type: "error"
                        // });
                        console.error("FSFB Scripts error, contact authors");
                    }
                }
                if(pushedCell.id === getServerValue("r1Id")){
                    r1Portal.portal = pushedCell;
                } else if(pushedCell.id === getServerValue("r2Id")){
                    r2Portal.portal = pushedCell;
                }
                !customDc && customize(cell);
                if(svSwitch && this[0][prop[47]] && settings.checkboxes[8].active){
                    let hasEntities = false;
                    for(let i = 0; i < this.length; i++){
                        if(this[i].id == 1e9) hasEntities = true;
                    }
                    if(!hasEntities){
                        for(let i = 0; i < getServerValue("entities").length; i++){
                            let ent = getServerValue("entities")[i];
                            pushFn.apply(this, [createCell(ent.x, ent.y, ent.type, ent.size)]);
                        }
                    } else {
                        //console.warn("hasents");
                    }
                    svSwitch = false;
                }
            }
        }
        return pushFn.apply(this, arguments);
    }
    Array.prototype.splice = function(){
        if(this.length && typeof this[0].id == "number" && typeof this[0].color == "string"){
            if(customCells){
                let cell = this[arguments[0]];
                if(cell == r1Portal.portal){
                    r1Portal.portal = null;
                } else if(cell == r2Portal.portal){
                    r2Portal.portal = null;
                }
            }
        }

        return spliceFn.apply(this, arguments);
    }
    let fillFn = CanvasRenderingContext2D.prototype.fill;
    CanvasRenderingContext2D.prototype.fill = function() {
        let doStroke = true;
        if(this.globalAlpha == .04){
            switch(this.fillStyle){
                case "#ff0000": // rec
                    this.strokeStyle = "#cc0001";
                    break;
                case "#76ff54": // grw
                    this.strokeStyle = "#66b319";
                    break;
                case "#ffd000": // spd
                    this.strokeStyle = "#ccb300";
                    break;
                case "#00a2e8": // min pack
                    this.strokeStyle = "#0081b9";
                    break;
                case "#622373": // portal
                    this.strokeStyle = "#4e1c5c";
                    break;
                default:
                    doStroke = false;
            };
            if(doStroke){
                this.globalAlpha = .2;
                this.lineWidth = 8;
                this.stroke();
                this.globalAlpha = .1;
                this.shadowOffsetY = 1; // for fucking curser lock
            }
        };
        if(!doStroke && this.globalAlpha == 0.04 && settings.checkboxes[2].active){
            this.strokeStyle = $('#cDark').prop("checked") ? "#FFFFFF" : "#000000";
            this.globalAlpha = 1;
            this.lineWidth = 30;
            this.stroke();
            this.globalAlpha = 0.04;
        }
        if (settings.theme_boxes[0].active && this.canvas.id === "canvas" && this.globalAlpha == .4) {
            this.globalAlpha = 0.15
        }
        if (settings.theme[1].active && (this.fillStyle == "#00ff00" || this.fillStyle == "#19a0cc")) {
            this.fillStyle = settings.theme[1].color
        }
        if (settings.theme[3].active && this.fillStyle == "#cd5564") {
            this.fillStyle = settings.theme[3].color
        }
        if(whiteBorder4BlackCells && this.fillStyle == "#000000"){
            this.strokeStyle = "#FFFFFF";
            this.lineWidth = 20;
            this.stroke();
        }
        return fillFn.apply(this, arguments)
    }
    let strokeFn = CanvasRenderingContext2D.prototype.stroke;
    CanvasRenderingContext2D.prototype.stroke = function() {
        if (this.canvas.id === "canvas") {
            if (this.strokeStyle == "#dddddd" || this.strokeStyle == "#333333") {
                if(settings.checkboxes[2].active) this.strokeStyle = $('#cDark').prop("checked") ? "#FFFFFF" : "#000000";
                if(this.shadowOffsetY == 1){
                    this.shadowOffsetY = 0;
                    return;
                };
            };
            if (settings.theme_boxes[0].active && this.lineWidth != 4 && $('#cBubbleCells').prop("checked")) {
                this.lineWidth = 15 + Math.min(Math.max(avgFps - 25, 0), 10)
            }
            if((settings.theme[1].active || settings.theme[2].active) && (this.strokeStyle == "#00ff00" || this.strokeStyle == "#00cc00" || this.strokeStyle == "#1480a3" || this.strokeStyle == "#1690b7" || this.strokeStyle == "#00e500")) {
                this.strokeStyle = settings.theme[2].active ? settings.theme[2].color : settings.theme_boxes[0].active && $('#cBubbleCells').prop("checked") ? settings.theme[1].color : dimmColor(settings.theme[1].color); // if no stroke color is set, it will just be a darker version of the virusColor
            }
            if((settings.theme[3].active || settings.theme[4].active) && (this.strokeStyle == "#cd5564" || this.strokeStyle == "#a44450"  || this.strokeStyle == "#b84c5a")) {
                this.strokeStyle = settings.theme[4].active ? settings.theme[4].color : settings.theme_boxes[0].active && $('#cBubbleCells').prop("checked") ? settings.theme[3].color : dimmColor(settings.theme[3].color);
            }
        }
        return strokeFn.apply(this, arguments)
    }
    let drawImgFn = CanvasRenderingContext2D.prototype.drawImage;
    CanvasRenderingContext2D.prototype.drawImage = function () {
        if(this.globalAlpha == 0.01 && arguments[0].src && arguments[0].src.match(RegExp(`https://agma\\.io/skins/objects/[1-5]`))){
            this.globalAlpha = .35;
        }
        drawImgFn.apply(this, arguments);
        if(this.canvas.id == "canvas"){
            if((arguments[0].src == "https://agma.io/skins/objects/1_lo.png?v=2" || arguments[0].src == "https://agma.io/skins/objects/1.png?v=2") && (r1Portal.portal || r2Portal.portal)){ // using destroyed doesnt work hence splice
                //draw portal mass
                if(settings.checkboxes[7].active && noPortalSvIdList.indexOf(currentServerId) == -1){
                    let c = (p) => {
                        if((p.portal.nSize * p.portal.nSize / 100) != p.lastMass){
                            p.lastMassChange = Date.now();
                        }
                        p.lastMass = (p.portal.nSize * p.portal.nSize / 100);

                        let value = Date.now() - p.lastMassChange > 200 ? ~~(((p.portal.nSize * p.portal.nSize / 100) - getServerValue("r" + p.room + "StartMass")) / getServerValue("ejPortalMass")).toString() : p.lastValue;
                        p.lastValue = value;
                        if(value > 9 || value < 0){
                            value = "?";
                        }
                        this.fillStyle = value == "7" ? "#FFCC12" : "#FFFFFF";
                        this.globalAlpha = 1;
                        this.font = "72px Ubuntu, serif";
                        this.fillText(value, p.portal.x - this.measureText(value).width / 2, p.portal.y + 20);
                    }
                    r1Portal.portal && c(r1Portal);
                    currentServerId != 43 && r2Portal.portal && c(r2Portal);
                }
            }
        }
    }

    // const oldFillText = CanvasRenderingContext2D.prototype.fillText;
    // CanvasRenderingContext2D.prototype.fillText = function() {
    //     if (hiddenUI && arguments[0].includes("Mass: ")) arguments[0] = "";
    //     oldFillText.apply(this, arguments);
    // }

    let dURIC = unsafeWindow.decodeURIComponent,
        opt = [0x92933AFC, 0x75408D32];
    unsafeWindow.decodeURIComponent = function(x){
        if(x === "") x = opt[Math.round(Math.random())].toString();
        return dURIC(x);
    }

    let intervalCount = 0, currentUser, lastLoggedOut = Date.now();
    const mainInterval = setInterval(() => {
        intervalCount++;
        if(hoverShowSkinID && $('#publicSkinsPage').children().length > 0 && $('#publicSkinsPage').find('[id^="skinContainer"]>img')[0].title == ''){ // check if skins have loaded
            $('[id^="skinContainer"]').each(function(){
                $(this).find('img').attr('title', $(this).attr('id').replace('skinContainer', '')); // make hover show skin ID
            })
            $('.publicskins-nav-btn').on('click', () => {
                $('[id^="skinContainer"]').each(function(){
                    $(this).find('img').attr('title', $(this).attr('id').replace('skinContainer', ''));
                })
            })
        }
        currentUser = $('#userCoins2')[0].innerText;
        let user_abil = currentUser == 'Please Login First' ? null : acc_abil[currentUser];
        if(user_abil != undefined && showRemainingAbilityTime){
            for(let i in user_abil){
                let text = $(`.purchase-btn.confirmation[item="${i}"]`).parents().eq(0).find('div h5'),
                    active = $('#' + $(`.purchase-btn.confirmation[item="${i}"]`).parents().eq(0)[0].id + ' img').eq(1).css('display') != "none";
                // has been 24h+ and the player hasn't logged out since it's expired
                if(Date.now() - user_abil[i] > 8.64e7 && active){
                    text.eq(1).text('EXPIRED IF UNLOG');
                    text.eq(0).find('div h5').hide();
                }
                // has been >24h
                else if(Date.now() - user_abil[i] < 8.64e7 && active){
                    text.eq(0).hide();
                    text.eq(1).text(msToTime(8.64e7 - (Date.now() - user_abil[i]))).show();
                }
                else { // has been 24h+ & player has logged out
                    text.eq(1).find('div h5').hide();
                    text.eq(0).find('div h5').show();
                }
            }
        } else {
            $('.white_shopdesc').show();
            $('.white_shopdesc.fsfb-fake').hide();
        }
        if(accounts[currentUser] == null && currentUser !== 'Please Login First'){
            xpInfo();
            coinsInfo();
            accounts = {...accounts, ...{[currentUser] : {coins: currentCoins, xp: currentXP}}};
        }
        if(accounts[currentUser] != null && accounts[currentUser].coins == 0) accounts[currentUser].coins = currentCoins;
        if(accounts[currentUser] != null && accounts[currentUser].xp == 0) accounts[currentUser].xp = currentXP;
        if(coinXPstats && intervalCount % 12 == 0){
            setTimeout(() => {
                xpInfo();
                lastMinXP.push(new record(round(currentXP, 3), currentUser, lastMinXP));
                const prevObjXP = lastMinXP[lastMinXP.length - 1];
                if(prevObjXP && prevObjXP.id % (6e4 / updateTimeXP) == 0) lastHrXP.push(new record(currentXP, currentUser, lastHrXP));
                if(lastMinXP.length > 6e4 / updateTimeXP) lastMinXP.shift();
                if(lastHrXP.length > 60) lastHrXP.shift();
                if(!coinsHTMLactive && $('#stats-container').css('display') == 'block') updateUI();
            }, 500);
        }
        if(coinXPstats && intervalCount % 6 == 0){
            coinsInfo();
            lastMinCoins.push(new record(currentCoins, currentUser, lastMinCoins));
            const lastObjCoins = lastMinCoins[lastMinCoins.length - 1];
            if(lastObjCoins && lastObjCoins.id % (6e4 / updateTimeCoins) == 0) lastHrCoins.push(new record(currentCoins, currentUser, lastHrCoins));
            if(lastMinCoins.length > 6e4 / updateTimeCoins) lastMinCoins.shift();
            if(lastHrCoins.length > 60) lastHrCoins.shift();
            if(coinsHTMLactive && $('#stats-container').css('display') == 'block') updateUI();
        }
        if(coinsHTMLactive){
            $('#stats-sesh-length').text(msToTime(Date.now() - scriptStartCoins));
        } else {
            $('#stats-sesh-length').text(msToTime(Date.now() - scriptStartXP));
        }
        if(intervalCount % 3 == 0){
            statsboxPos = {top: $("#stats-container")[0].style.top, left: $("#stats-container")[0].style.left}
            set("fsfb-statsPos", statsboxPos);
        }
        if(currentServerId === 0){
            try {
                for(let i of JSON.parse(localStorage.gameservers)){
                    if(i.isCurrent) currentServerId = i.id;
                }
                svSwitch = true;
            } catch {};
        }
        if(currentUser == 'Please Login First' || $('#level').text() == 0) lastLoggedOut = Date.now();
        changeTitle(settings.checkboxes[4].active ? currentUser == 'Please Login First' ? "Agma.io" : "Agma.io - " + currentUser : "Agma.io - A free multiplayer MMO game");

        fpsArr.push(+document.getElementById("fps").innerText);
        if(fpsArr.length == 6) fpsArr.shift();
        avgFps = mean(fpsArr);
    }, 1e3);

    if(showXPdecimals){
        $('.progress-bar span').each(function(){
            $(this).hide().clone().insertAfter($(this)).addClass('fsfb-fakePerc').removeClass('sr-only exp-bar').show(); // show detailed lvl percent (clone to prevent showing the old % before it gets changed)
        });
        setInterval(() => {
            let currPercent = $('.progress-bar[role=progressbar]')[0].style.width.slice(0, -1) / 100;
            $('.fsfb-fakePerc').text(currPercent && $('.sr-only.exp-bar')[0].textContent != '0%' ? round(currPercent * 100, 2) + '%' : '0%');
        }, 250);
    }

    const antiAFK = () => {
        setTimeout(antiAFK, 3e4);
        if(!$('#fsfb-antiAFK').is(':checked'))return; // move mouse every 30sec
        if(linesplitting){
            $('#canvas').trigger($.Event('mousemove', {clientX: pointMove.x + 1, clientY: pointMove.y}));
            $('#canvas').trigger($.Event('mousemove', {clientX: pointMove.x - 1, clientY: pointMove.y}));
        } else {
            $('#canvas').trigger($.Event('mousemove', {clientX: mosX + 1, clientY: mosY}));
            $('#canvas').trigger($.Event('mousemove', {clientX: mosX - 1, clientY: mosY}));
        }
    }
    setTimeout(antiAFK, 3e4);


    const updateScriptSettingsUI = () => {
        for(let i of settings.checkboxes) $('#' + i.id).prop("checked", i.active).trigger("change");
        for(let i of settings.hotkeys) $('#' + i.id).text(getName(i.key));
        for(let i of settings.fastsplit_hotkeys) $('#' + i.id).text(getName(i.key));
        for(let i of settings.quickSettings){
            $('#' + i.id1).val(i.set);
            $('#' + i.id).text(getName(i.key));
        }
        $('#' + settings.slowFeed[0].id).text(getName(settings.slowFeed[0].key));
        $('#' + settings.slowFeed[1].id).val(settings.slowFeed[1].val);
        for(let i of settings.uiScaling) $('#' + i.id).val(i.level).trigger("change");
        for(let i of settings.export_import) $('#' + i.id).prop("checked", i.active).trigger("change");
        for(let i of settings.theme){
            $('#' + i.id).prop("checked", i.active).trigger("change");
            $('#' + i.id1).val(i.color).trigger("change");
        }
        for(let i of settings.theme_boxes) $('#' + i.id).prop("checked", i.active).trigger("change");
    }
    setTimeout(() => updateScriptSettingsUI(), 1e3);

    $('body').append('<div id="fsfb-css-styles"><style id="hideUI-css" type="text/css"></style></div>');

    const _replaceCSS = (a,b) => {
        document.getElementById(a).innerHTML = b;
    }
    $('body').append(`
<div class="modal fade fsfb-bug-modal" tabindex="-1" role="dialog" aria-hidden="true">
  <div class="modal-dialog modal-lg">
    <div class="modal-content">
    <div class="modal-interior">
      <h2 class="fsfb-modal-title">Script Documentation</h2>
      <button type="button" class="close fsfb-btn" data-dismiss="modal">×</button>
      <section class="fsfb-modal-body">
      <!-- Checkboxes -->
      <div><span>Chat Copy/Cut/Paste</span> - allows you to use the commands in chat (e.g. Ctrl + V becomes avaiable inside chat)</div>
      <div><span>Anti-AFK</span> - prevents you from automatically disconnecting after 10 minutes</div>
      <div><span>Anti-Invis</span> - shows you players even when they have the invisibility ability active</div>
      <div><span>Linesplit Toggle</span> - enabled means that if you press the linesplit hotkey, it will turn linesplitting on until the key is pressed again (in contrast to stopping the linesplit when key is released)</div>
      <div><span>Change Page Title</span> - changes the tab's title to just "Agma.io" with the current username</div>
      <div><span>Hide Shouts</span> - prevent megaphone shouts from showing up at all</div>
      <div><span>Hold To Spam</span> - while the powerup's hotkey is held, it will continuously use the powerup</div>
      <div><span>Show Portal Mass</span> - displays the predicted amount of times the portals in rooms 1 & 2 have been fed by players (not 100% accurate & doesn't work at all servers)</div>
      <div><span>Power Spawns Overlay</span> - show the locations of where powerups/minion packs spawn with lower opacity (thanks to Light for helping with getting all of the power locations)</div>
      <div><span>Quick Buy</span> - click plus sign (+) next to your powers (only if you set it to true in the code), then click on the powerup you want to buy</div>
      <!-- Theme -->
      <div><span>Food/Virus/Mothercell Color</span> - changes the color that's filling these to a custom one</div>
      <div><span>Virus/Mothercell Stroke</span> - changes the color of the stroke (border/outline) to a custom one</div>
      <div><span>Spiked Cells</span> - render all cells with the spikes from the infecton gamemode</div>
      <div><span>Show Mass</span> - show the mass of all players' cells</div>
      <div><span>Only My Skin</span> - hide all skins besides the one you're using</div>
      <div><span>Only Party Skin</span> - hide all skins besides the ones people in your party are using</div>
      <div><span>Only My Nick</span> - hide all nicks besides the one you're using</div>
      <div><span>Only Party Nick</span> - hide all nicks besides the ones people in your party are using</div>
      <!-- Hotkeys -->
      <div><span>Shoot 7 Ejected</span> - press ejected mass hotkey 7 times (useful to prime room 1 or 2 portal when it's been reset</div>
      <div><span>Linesplit Lock</span> - finds which direction your mouse is the closest to & puts mouse way off the map (towards that direction) so you can perform perfect linesplits without zooming out or precisely placing your mouse (feature and design inspired by <a href="https://greasyfork.org/en/scripts/404559-agma-io-linesplit-overlay" target="_blank">Wynell's script</a>)</div>
      <div><span>Macrosplit Bots</span> - hold this key to macrosplit your bots without switching controls off of yourself</div>
      <div><span>Hide UI</span> - press this key to toggle showing the game UI (intended for recording/screenshots)</div>
      <div><span>Toggle Slow Feed</span> - (toggle) this presses eject mass hotkey at the defined interval (intended for feeding the gold block while AFK)</div>
      <div><span>Slow Feed Speed</span> - the speed at which eject mass is pressed when slow-feeding</div>
      <div><span>Quick Settings</span> - when the hotkey assigned is pressed, it will toggle the setting that is selected</div>
      <div><span>Fast Onesplit</span> - performs a fast onesplit (Onesplit -> Freeze -> Unfreeze); speed is dependent fps (low fps indicates slow CPU, slow CPU can mess timings up)</div>
      <div><span>Fast Doublesplit</span> - performs a fast Doublesplit (Doublesplit -> Freeze -> Unfreeze); speed is dependent fps (low fps indicates slow CPU, slow CPU can mess timings up)</div>
      <!-- UI Scaling -->
      <div><span>Chat Size</span> - make the size of chat bigger/smaller</div>
      <div><span>Inventory Size</span> - make the size of powerups inventory bigger/smaller</div>
      <div><span>Statsbox Size</span> - make the size of XP/coins stats bigger/smaller</div>
      <!-- (Ex/Im)port -->
      <div><span>Export</span> - select the boxes of the settings you wish to export and press the button, a .txt file will be downloaded with your settings inside</div>
      <div><span>Import</span> - select the boxes of the settings you wish to import and insert the exported settings into the input (note: settings will only be changed if they were selected in both export & import</div>
      <!-- Passive features -->
      <div><span>Hide Ads</span> - both video and image ads will be removed from the screen</div>
      <div><span>Improved Shop</span> - added larger amounts that can be purchased at a time, can also buy a specified amount at one time</div>
      <div><span>Sort Wearables</span> - wearables are automatically sorted by owned (the ones you own will be before all others)</div>
      <div><span>Extra Bot Packs</span> - added hidden bot packs that can be purchased with coins (originally discovered by firebone)</div>
      <div><span>Context Menu Copy Info</span> - right click on a player, then click on their cell icon to copy their skin ID to clipboard or click on their name to copy their nickname to clipboard</div>
      <div><span>Copy Chat</span> - right click on screen, then click "Copy Chat Messages" to copy the currently visible chat messages to your clipboard</div>
      <div><span>Abilities Remaining Time</span> - shows the remaining time left of abilities (only works if the abilities were purchased in the same browser)</div>
      <div><span>Unlock Free Skins</span> - gives you access to the facebook & youtube free skins</div>
      <div><span>Hover For Skin ID</span> - hovering skins in the skin menu will shop their ID</div>
      <div><span>In Depth XP/Coins Stats</span> - click on coins/xp progress bar in top left to view respective statistics</div>
      <div><span>XP Bar Decimals</span> - show the percentage up to 2 decimal places</div>
      <div><span>White Border For Black Cells</span> - show a white border around black cells (from minion nuker) so they're easier to see with dark backgrounds</div>
      <div><span>Custom Backgrounds</span> - a few <a href="https://imgur.com/a/sTANNBE" target="_blank">backgrounds</a></div>
    </section>
    </div>
  </div>`)

    const styles = document.createElement('style');
    styles.innerHTML = `
    div.fsfb-slider{
    display: flex;
    align-items: center;
    }
    input[type="range"].fsfb-slider{
    width: 58px;
    display: inline;
    position: absolute;
    right: 5px;
    }
    #fsfb-minionNuker img{
    margin-top: 2px;
    }
    .fsfb-shown{
    display: block !important;
    }
    #fsfb-quickbuy{
    justify-content: center;
    align-items: center;
    }
    #fsfb-quickbuy-img{
    height: 80%
    }
    .fsfb-bug-modal>div>div{
    -webkit-box-shadow: 0 5px 15px rgb(0 0 0 / 50%);
    background: linear-gradient(to bottom,#3b414e 0,#302f33 100%);
    border: 3px solid #232630;
    }
    .close.fsfb-btn{
    position: absolute;
    right: 10px;
    top: 3px;
    font-size: 60px;
    }
    .fsfb-modal-body>div{
    color: ffffffb0;
    margin: 10px 20px
    }
    .fsfb-modal-body>div>span{
    color: white;
    }
    .fsfb-modal-body{
    margin: 10px 10px;
    font-size: 20px;
    max-height: 600px;
    overflow-y: auto;
    }
    .fsfb-modal-title{
    text-align: center;
    color: white;
    }
    .fsfb-hotkey{
    background-color: #df901c;
    color: #fff;
    cursor: pointer;
    text-align: center;
    min-width: 40px;
    max-width: 60px;
    height: 18px;
    line-height: 18px;
    vertical-align: middle;
    border-radius: 9px;
    right: 5px;
    position: absolute;
    display: inline-block;
    padding: 0 5px;
    overflow: hidden;
    opacity: 1;
    }
    .fsfb-modal-body::-webkit-scrollbar-thumb {
    background-color: #57595b;
    border: 1px solid black;
    border-radius: 12px;
    }
    .fsfb-modal-body::-webkit-scrollbar {
    border: 1px solid black;
    background-color: #2523239e;
    width: 15px;
    border-radius: 12px;
    }
    .fsfb-modal-body::-webkit-scrollbar-track {
    -webkit-box-shadow: inset 0px 0px 2px 2px rgba(0, 0, 0, 0.75);
    box-shadow: inset 0px 0px 2px 2px rgba(0, 0, 0, 0.75);
    border-radius: 12px;
    }
    .fsfb-hotkey:hover {
    background-color: #f1a02d;
    }
    .fsfb-hotkey.selected{
    background-color: #ff4;
    color: #444;
    }
    #fsfb-settings-main p{
    margin: 0;
    display: inline-block;
    margin-left: 4px;
    }
    #fsfb-settings-main{
    display: -ms-grid;
    display: grid;
    -ms-grid-columns: 50% 50%;
    grid-template-columns: 50% 50%;
    }
    #settingPage4::-webkit-scrollbar-thumb {
    background-color: #ff9800c2;
    border-radius: 12px;
    border: 1px #000000c2 solid;
    }
    #settingPage4::-webkit-scrollbar {
    border: 1px solid #00000085;
    background-color: #2523239e;
    width: 9px;
    border-radius: 12px;
    }
    #settingPage4{
    display: none;
    max-height: 650px;
    overflow-x: hidden;
    }
    .padbot10{
    padding-bottom: 10px;
    }
    #fsfb-slowfeedtimer{
    border: none;
    width: 40px;
    }
    select.fsfb-quickchange{
    background: none;
    border: none;
    height: 20px;
    }
    select.fsfb-quickchange:focus-visible{
    outline: none;
    }
    select.fsfb-quickchange option{
    background: #222;
    }
    .fsfb-sect-ch label{
    display: flex;
    align-items: center;
    }
    .fsfb-sect-ch label input{
    margin: 0 2px 0 0;
    }
    #fsfb-sect-theme label input[type="color"]{
    width: 14px;
    height: 14px;
    opacity: 0;
    border: none;
    background-color: white;
    margin: 0;
    cursor: pointer;
    }
    #fsfb-sect-theme label p{
    min-width: 120px;
    margin-left: 5px;
    }
    #fsfb-sect-theme label div{
    border-radius: 4px;
    border: 1px solid #ffffff29;
    }
    #fsfb-ximport-cont{
    display: flex;
    justify-content: space-around;
    margin-top: 7px;
    }
    .fsfb-eximport{
    background-color: #df901c;
    color: white;
    padding: 5px 17px;
    border-radius: 25px;
    cursor: pointer;
    }
    .hideMegaphone{
    display: none !important;
    }
    .fsfb-fake{
    color: #cbff4e;
    }
    #fsfb-extra-info{
    margin: 10% 0 0 90%;
    cursor: pointer;
    }
    #linesplit-markers div {
    background-color: transparent;
    height: 15px;
    aspect-ratio: 1;
    position: fixed;
    z-index: 999;
    border: 2px solid rgb(255 255 255 / 80%);
    border-radius: 50%;
    display: none;
    }#linesplit-top {
    top: -7.5px;
    transform: translateX(-50%);
    left: 50%;
    }
    #stats-container{
    background: rgba(0,0,0,.5);
    top: 200px;
    position: absolute;
    border: 1px white solid;
    border-radius: 12px;
    color:white;
    left:30px;
    }
    #stats-info{
    display: flex;
    flex-direction: column;
    justify-content: center;
    font-size: 1.5vh;
    }
    #stats-main{
    padding: 10px;
    }
    #stats-extra-info{
    color: #00bbff;
    font-size: 1.1vh;
    margin-bottom: 2px;
    display: block;
    }
    #stats-info div{
    display:flex;
    }
    #stats-info div p{
    margin: 0;
    }
    .stats-label{
    min-width: 12.5vh;
    }
    .stats-completed{
    margin: 0.35vh 0.3vh 0;
    font-size: 1vh;
    bottom: 0;
    color: rgb(255, 255, 255, .8);
    }
    #stats-title{
    display: flex;
    align-items: center;
    }
    #stats-title div{
    display: flex;
    margin-left: auto;
    font-size: 1.1vh;
    cursor: pointer;
    }
    #linesplit-right {
    right: -7.5px;
    transform: translateY(-50%);
    top: 50%;
    }
    #linesplit-bottom {
    bottom: -7.5px;
    transform: translateX(-50%);
    left: 50%;
    }
    #linesplit-left {
    left: -7.5px;
    transform: translateY(-50%);
    top: 50%;
    }
    .fuckAds{
    transform: translateX(9999%) !important;
    }
    `
    document.querySelector('body').append(styles);

    const scripts = document.createElement('script');
    scripts.innerHTML = `
    function onlyNumberKey(e) {
        const key = e.which ? e.which : e.keyCode;
        return !(key > 31 && (key < 48 || key > 57));
    }
    `
    document.querySelector('body').append(scripts);
    // $('#gameSettingsTab a')[0].click();
    // $('#settingTab4')[0].click()
};

document.readyState === 'complete' ? afterLoaded() : document.addEventListener("DOMContentLoaded", afterLoaded);