Greasy Fork

Greasy Fork is available in English.

Bubble.am+

Script that adds useful features to the game.

当前为 2021-07-14 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Bubble.am+
// @namespace    https://enderror.pl
// @version      21.07.02
// @description  Script that adds useful features to the game.
// @author       enderror
// @match        *://bubble.am/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

class Plus {
    constructor() {
        this.name = 'plus';
        this.version = '21.07.02';
        this.author = 'enderror';

        this.defaultSettings = { 
            controls: [
                { type: '1x', keycode: '32', disabled: false },
                { type: 'eject', keycode: '87', disabled: false },
                { type: 'setCamera', keycode: '81', disabled: false },
                { type: 'holdEject', keycode: '69', disabled: false },
                { type: '1x', keycode: '49', disabled: false },
                { type: '2x', keycode: '50', disabled: false },
                { type: '4x', keycode: '51', disabled: false },
                { type: '8x', keycode: '52', disabled: false },
                { type: '16x', keycode: '53', disabled: false },
                { type: 'holdSplit', keycode: '16', disabled: false },
                { type: 'movementUp', keycode: '81', disabled: false },
                { type: 'movementLeft', keycode: '65', disabled: false },
                { type: 'movementDown', keycode: '83', disabled: false },
                { type: 'movementRight', keycode: '68', disabled: false },
            ]
        };

        this.settings = {
            checkbox: {
                transparentVirus: true,
                noGrid: false,
                customColor: false,
                customSkin: false,
                bypassSkin: false,
                darkMenu: false,
                virusSplitCounter: true,
                cellsCounter: true,
                hideName: false,
                showMotherMass: false
            },

            values: {
                cellsColor: '#e31e24',
                skinUrl: null,
            },

            controls: this.defaultSettings.controls,
            chat: [],
            accounts: []
        }

        this.helpers = {
            skin: {
                skinInd: 1,
                gidCheck: false,
                gid: 0,
            },

            controls: {
                splitSwitch: false,
                splitInterval: undefined,
                ejectSwitch: false,
                ejectInterval: undefined,
                isActiveMovement: false,
                activeMovement: []
            }
        }

        this.run();
    }

    run() {
        console.log(`${this.name} ${this.version} created by ${this.author}`);

        if(location.pathname.includes('.txt') 
        || location.pathname.includes('.png')
        || location.pathname.includes('.jpg')
        || location.pathname.includes('.jpeg')) {
             return;
        }

        if((location.host === 'bubble.am' && location.pathname === '/') || location.pathname.length > 15) {
            window.stop();
            location.href = `${location.origin}/${this.name}${location.hash}`;
            return;
        }

        document.documentElement.innerHTML = '';

        const request = new XMLHttpRequest();
        const url = 'http://bubble.am';

        request.open('get', url, true);
        request.send();
        request.onload = (e) => {
            const newCore = this.modify(e.target.responseText);

            document.open();
            document.write(newCore);
            document.close();

            let documentCheck = setInterval(() => {
                if(document.readyState == 'complete') {
                    clearInterval(documentCheck);
                    
                    new UI();
                    this.addEventListeners();

                    window.modLoaded = true;
                }
            }, 100);
        }
    }

    modify(core) {
        core = core.replace(/(.*)(Ubuntu\:700)(.*)(\n.*){4}/gm, `
            <link rel='stylesheet' type='text/css' href='http://fonts.googleapis.com/css?family=Ubuntu:700'>
            <link rel="stylesheet" type="text/css" href="http://m.bubble.am/css/bootstrap.min.css?v=3">
            <link rel="stylesheet" type="text/css" href="http://bubble.am/css/style.css?v=0.2.50" />
            <link rel="stylesheet" type="text/css" href="http://bubble.am/css/font.awesome.css?v5" />

            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-colorpicker/2.5.3/css/bootstrap-colorpicker.css"/>
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
            <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/nano.min.css"/>

            <script src="//m.bubble.am/js/jquery.js"></script>
        `);

        core = core.replace(/(.*)(bootstrap\.min\.js)(.*)(\n.*){3}/gm, `
            <script src="/js/bootstrap.min.js"></script>
            <script src="//m.bubble.am/js/jquery.lazy.js"></script>
            <script src="/js/jquery.ui.js"></script>
            <script src="/js/bub.js?v=0.2.50"></script>

            <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-colorpicker/2.5.3/js/bootstrap-colorpicker.min.js" async></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/smooth-scrollbar/8.6.2/smooth-scrollbar.min.js" async></script>
            <script src="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js" async></script>
            <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11" async></script>
        `);

        core = core.replace(/(d\.onkeydown\s=\sf)([^]*?b\s=\s\!1)([^]*?\}\;)/gm, `
            d.split = function() {
                ba();
                K(17);
            }

            d.eject = function() {
                ba();
                K(21);
            }

            d.setCamera = function() {
                K(18);
            }

            d.onkeydown = function(n) {
                switch (n.keyCode) {
                    case 27: // quit
                        oa(true);
                        break;

                    case 13:
                        if (isTyping) {
                            isTyping = false;
                            document.getElementById("chat_textbox").blur();
                            chattxt = document.getElementById("chat_textbox").value;
                            if (chattxt.length > 0) sendChat(chattxt);
                            //document.getElementById("chat_textbox").value = "";

                        } else {
                            if (!hasOverlay) {
                                document.getElementById("chat_textbox").focus();
                                isTyping = true;
                            }
                        }
                    break;

                    case 17:
                            if (!hasOverlay && !isTyping) {
                                document.getElementById("chat_textbox").value = "/g ";
                                document.getElementById("chat_textbox").focus();
                                isTyping = true;
                            }
                    break;
                }
            };
        `);

        core = core.replace(/(\,\sI\:\s)(.*)/, `
            $&
            , counterCache: null
            , motherCache: null
        `);

        core = core.replace(/(f.+)(v\[d\]\.)(.*)/, `
            $&

            if(window.plus.settings.checkbox.cellsCounter && h.length !== 0) {
                f.restore();

                let maxCells = '';

                if(window.currentMode() === ':5') {
                    maxCells = '35';
                } else {
                    maxCells = '16';
                }

                const playerCells = h.length;
                const playerCellsText = new Ca(24, '#fff');
                playerCellsText.u(playerCells + '/' + maxCells);
                c = playerCellsText.F();
                a = c.width;
                
                f.globalAlpha = .2;
                f.fillStyle = "#000000";
                f.fillRect(10, 55, a + 10, 34);
                f.globalAlpha = 1;
                f.drawImage(c, 15, 60);
            }
        `);

        core = core.replace(/(bb\s\?)(.*)/, `
            if(bb) {
                a.fillStyle = "#FFFFFF";
                a.strokeStyle = "#AAAAAA";
                if(this.f && window.plus.settings.checkbox.transparentVirus === true) a.globalAlpha = 0.2;
            } else {
                a.fillStyle = this.color;
                a.strokeStyle = this.color;
                if(this.f && window.plus.settings.checkbox.transparentVirus === true) a.globalAlpha = 0.2;
            }
        `);

        core = core.replace(/(.*)(var\sa\s=\sl\s\/\sg)(.+)(\n.*\n.*)/gm, `
            if(window.plus.settings.checkbox.noGrid === false) {
                $&
            }
        `);

        core = core.replace(/(.*)(m\.color)(.*)/, `
            $&

            const amAlive = h.length > 0;

            const helpers = window.plus.helpers;
            const settings = window.plus.settings;
            
            if(amAlive && m.name === h[0].name && !m.f && settings.values.cellsColor !== undefined && settings.values.cellsColor !== null && settings.checkbox.customColor === true) {
                m.color = settings.values.cellsColor;
            }

            if(amAlive && !helpers.skin.gidCheck) {
                helpers.skin.gid = h[0].gid;
                helpers.skin.gidCheck = true;
            }

            if(settings.checkbox.customSkin === true && settings.values.skinUrl !== null && amAlive) {
                for(let i = 0; i < h.length; i++) {
                    const cell = h[i];

                    if(cell.gid !== helpers.skin.skinInd) {
                        cell.gid = helpers.skin.skinInd;

                        if(cell.name === '') cell.name = 'enderror';
                    }
                }
            }
            
            if(settings.checkbox.customSkin === false && amAlive && h[0].gid !== helpers.skin.gid) {
                for(let i = 0; i < h.length; i++) {
                    const cell = h[i];

                    cell.gid = helpers.skin.gid;
                }
            }

        `);

        core = core.replace(/(.*)(\(c\s\&\&\sFb.*)(\n.*){35}/gm, `
            const bypassSkin = (window.plus.settings.checkbox.customSkin && window.plus.settings.checkbox.bypassSkin)
            const customSkinGid = (this.gid == 1 || this.gid == 2);
            if (c && (Fb || bypassSkin) && !this.j && ':1' != V && ':3' != V && ':8' != V) {
                var loadBub = -1;

                if(Fb && this.gid && this.gid > 0) {
                    loadBub = this.gid;
                } else if(!Fb && this.gid && customSkinGid) {
                    loadBub = this.gid;
                } else if(Fb) {
                    if(-1 != Jb.indexOf(c)) {
                        loadBub = c;
                    }
                }
            
                if (loadBub != -1) {
                    if((Fb || bypassSkin && customSkinGid ) && (!$.hasOwnProperty(c) || $[c].skin != loadBub)) {
                        $[c] = new Image;

                        if(loadBub > 100000000 && Fb) {
                            $[c].src = "//bubble.am/skins/custom/" + loadBub + '.png?0.2.50';
                        } else if (loadBub >= 10 && loadBub <= 20 && Fb) {
                            $[c].src = "//bubble.am/img/battle_" + loadBub + '.png?0.2.50';
                        } else if(customSkinGid && (Fb || bypassSkin)) {
                            $[c].src = window.plus.settings.values.skinUrl;
                        } else if(Fb) {
                            $[c].src = "//m.bubble.am/skins/" + loadBub + '.png?0.2.50';
                        }

                        $[c].skin = loadBub;
                    }
                    if((Fb || bypassSkin) && (0 != $[c].width && $[c].complete)) {
                        d = $[c];
                    } else {
                        d = null;
                    }
                } else {
                    d = null;
                }
            } else {
                d = null;
            }
        `);

        core = core.replace(/(\,\sd\s\=\sthis\.I\,)(.*)/, `
            $&

            const settings = window.plus.settings;
            const isMovingMines = window.currentMode() == ':5';
            const isBattle = window.currentMode() == ':11' || window.currentMode() == ':12';
            const isExperimental = window.currentMode() == ':2';
            const isTeams = window.currentMode() == ':3';
            const isTest = window.currentMode() == ':13';

            let nc;
            if(settings.checkbox.virusSplitCounter && this.f && this.color !== '#cd5564') {
                if(this.counterCache === null) {
                    this.counterCache = new Ca(this.i(), '#FFFFFF', !0, "#000000");
                }

                nc = this.counterCache;

                if(isMovingMines || isExperimental || isTest) {
                    nc.u('0');
                } else if(isTeams) {
                    switch(true) {
                        case this.size >= 123 && this.size <= 126:
                            nc.u('7');
                            break;
                        
                        case this.size >= 126 && this.size <= 129:
                            nc.u('6');
                            break;

                        case this.size >= 129 && this.size <= 134:
                            nc.u('5');
                            break;

                        case this.size >= 134 && this.size <= 137:
                            nc.u('4');
                            break;

                        case this.size >= 137 && this.size <= 141:
                            nc.u('3');
                            break;

                        case this.size >= 142 && this.size <= 145:
                            nc.u('2');
                            break;

                        case this.size >= 145 && this.size <= 151:
                            nc.u('1');
                            break;
                        }
                } else if(isBattle) {
                    switch(true) {
                        case this.size >= 84 && this.size <= 89:
                            nc.u('7');
                            break;
                        
                        case this.size >= 89 && this.size <= 94:
                            nc.u('6');
                            break;

                        case this.size >= 94 && this.size <= 99:
                            nc.u('5');
                            break;

                        case this.size >= 99 && this.size <= 104:
                            nc.u('4');
                            break;

                        case this.size >= 104 && this.size <= 109:
                            nc.u('3');
                            break;

                        case this.size >= 109 && this.size <= 115:
                            nc.u('2');
                            break;

                        case this.size >= 115 && this.size <= 120:
                            nc.u('1');
                            break;
                        }
                } else {
                    switch(true) {
                        case this.size >= 114 && this.size <= 119:
                            nc.u('7');
                            break;
                        
                        case this.size >= 119 && this.size <= 123:
                            nc.u('6');
                            break;

                        case this.size >= 123 && this.size <= 127:
                            nc.u('5');
                            break;

                        case this.size >= 127 && this.size <= 132:
                            nc.u('4');
                            break;

                        case this.size >= 132 && this.size <= 136:
                            nc.u('3');
                            break;

                        case this.size >= 136 && this.size <= 141:
                            nc.u('2');
                            break;

                        case this.size >= 141 && this.size <= 145:
                            nc.u('1');
                            break;
                        }
                }

                nc.G(this.i());
                nc.U(4);

                const rn = nc.F();
                a.drawImage(rn, ~~this.x - ~~(rn.width / 2), ~~this.y - ~~(rn.height / 2));
            }
        `);

        core = core.replace(/(.*)(\~\~\(l\s\/\s2\)\,\sf\,\sl\)\;)/, `
            const settings = window.plus.settings;

            if(settings.checkbox.hideName && h.length > 0 && this.name === h[0].name) {
                
            } else {
                $&
            }
        `);

        core = core.replace(/(.*)(chatNick\s\+)(.*)/, `
            const settings = window.plus.settings;

            const rawNick = chatNick.replace(/(<([^>]+)>)/ig, '').replace(/(\\[.*?\\])/, '').trim().toLowerCase();
            if(!settings.chat.includes(rawNick)) {
                $&
            }
        `);

        core = core.replace(/(.*)(isUnlimitedZoom)(.*)(\n.*){7}/gm, ``);


        core = core.replace(/(.*)(\&\&\sGb)(.*)(\n.*){3}/gm, `
            if(0 < this.id && Gb && (d || (0 == h.length || Qd) && (!this.f || this.j) && 80 < this.size)) {
                if(null == this.I) {
                    this.I = new Ca(this.i() / 2, "#FFFFFF", true, "#000000");
                }
                d = this.I;
                d.G(this.i() / 2);
                d.u(~~(this.size * this.size / 100));
                c = Math.ceil(10 * g) / 10;
                d.U(c);
                e = d.F();
                f = ~~(e.width / c);
                l = ~~(e.height / c);
                a.drawImage(e, ~~this.x - ~~(f / 2), b - ~~(l / 2), f, l);
            }

            if(0 < this.id && window.plus.settings.checkbox.showMotherMass && this.color === '#cd5564') {
                if(this.motherCache == null) {
                    this.motherCache = new Ca(this.i() / 2, "#FFFFFF", true, "#000000");
                }

                d = this.motherCache;
                d.G(this.i());
                d.u(~~(this.size * this.size / 100));
                c = Math.ceil(10 * g) / 10;
                d.U(c);
                e = d.F();
                f = ~~(e.width / c);
                l = ~~(e.height / c);
                a.drawImage(e, ~~this.x - ~~(f / 2), b - ~~(l / 2), f, l);
            }
        `);

        core = core.replace(/(J\.onmousedown)(.*)(\n.*){20}/gm, `
            const helpers = window.plus.helpers;
            J.onmousedown = function(a) {
                if (db) {
                    var b = a.clientX - (5 + l / 5 / 2)
                        , c = a.clientY - (5 + l / 5 / 2);
                    if (Math.sqrt(b * b + c * c) <= l / 5 / 2) {
                        ba();
                        K(17);
                        return
                    }
                }
                if(!helpers.controls.isActiveMovement) {
                    ma = a.clientX;
                    na = a.clientY;
                    Ha();
                    ba()
                }
            };
            J.onmousemove = function(a) {
                if(!helpers.controls.isActiveMovement) {
                    ma = a.clientX;
                    na = a.clientY;

                    lastacti = Date.now();
                }
            };

            d.goTo = function(x, y) {
                ma = x;
                na = y;
            }
        `);

        return core;
    }

    keydown = (e) => {
        this.controlTypeDown(e);
    }

    keyup = (e) => {
        this.controlTypeUp(e);
    }

    mousedown = (e) => {
        this.controlTypeDown(e);
    }

    mouseup = (e) => {
        this.controlTypeUp(e);
    }

    controlTypeDown(e) {
        const settings = window.plus.settings;
        const helpers = window.plus.helpers;

        const key = e.which;
        const controls = settings.controls;
        const chat = document.querySelector('#chat_textbox');

        if(controls.length === 0) return;
        if(chat === document.activeElement) return;

        for(let i = 0; i < controls.length; i++) {
            const control = controls[i];

            if(control.keycode == key && !control.disabled) {
                switch(control.type) {
                    case '1x':
                        if(!helpers.controls.splitSwitch) {
                            helpers.controls.splitSwitch = true;
                            window.split();
                        }
                        break;

                    case 'eject':
                        if(!helpers.controls.ejectSwitch) {;
                            helpers.controls.ejectSwitch = true;
                            window.eject();
                        }
                        break;

                    case 'setCamera':
                        window.setCamera();
                        break;
        
                    case '2x':
                        if(!helpers.controls.splitSwitch) {
                            helpers.controls.splitSwitch = true;
                            this.split(2);
                        }
                        break;
        
                    case '4x':
                        if(!helpers.controls.splitSwitch) {
                            helpers.controls.splitSwitch = true;
                            this.split(4);
                        }
                        break;
        
                    case '8x':
                        if(!helpers.controls.splitSwitch) {
                            helpers.controls.splitSwitch = true;
                            this.split(8);
                        }
                        break;
        
                    case '16x':
                        if(!helpers.controls.splitSwitch) {
                            helpers.controls.splitSwitch = true;
                            this.split(16);
                        }
                        break;
        
                    case 'holdSplit':
                        if(!helpers.controls.splitSwitch) {
                            helpers.controls.splitSwitch = true;

                            helpers.controls.splitInterval = setInterval(() => {
                                window.split();
                            }, 4);
                        }
                        break;
        
                    case 'holdEject':
                        if(helpers.controls.ejectSwitch) break;
         
                        helpers.controls.ejectSwitch = true;
                        helpers.controls.ejectInterval = setInterval(() => {
                            window.eject();
                        }, 4);
                        break;
        
                    case 'mouseLeft':
                        this.split(1);
                        break;
        
                    case 'mouseRight':
                        this.split(1);
                        break;
        
                    case 'mouseMiddle':
                        this.split(1);
                        break;
        
                    case 'movementUp':
                        this.goTo(3, -0);

                        if(!helpers.controls.isActiveMovement) helpers.controls.isActiveMovement = true;
                        helpers.controls.activeMovement['up'] = true;
                        break;
        
                    case 'movementRight':
                        this.goTo(0, 5);

                        if(!helpers.controls.isActiveMovement) helpers.controls.isActiveMovement = true;
                        helpers.controls.activeMovement['right'] = true;
                        break;
        
                    case 'movementDown':
                        this.goTo(2, 0.6);

                        if(!helpers.controls.isActiveMovement) helpers.controls.isActiveMovement = true;
                        helpers.controls.activeMovement['down'] = true;
                        break;
        
                    case 'movementLeft':
                        this.goTo(-0, 8);

                        if(!helpers.controls.isActiveMovement) helpers.controls.isActiveMovement = true;
                        helpers.controls.activeMovement['left'] = true;
                        break;

                    case 'hashLogin':
                        this.hashLogin();
                        break;

                    case 'hashShow':
                        this.hashShow();
                        break;
                }

                if(control.type.includes('movement')) {
                    const keys = helpers.controls.activeMovement;

                    switch(true) {
                        case keys['left'] && keys['up']:
                            this.goTo(-0, -0);
                            break;
                
                        case keys['left'] && keys["down"]:
                            this.goTo(-0, 0);
                            break;
                
                        case keys['up'] && keys['right']:
                            this.goTo(0, -0);
                            break;
                
                        case keys['down'] && keys['right']:
                            this.goTo(0, 0);
                            break;
                    }
                }
            }
        }
    }

    controlTypeUp(e) {
        const settings = window.plus.settings;
        const helpers = window.plus.helpers;

        const key = e.which;
        const controls = settings.controls;
        const chat = document.querySelector('#chat_textbox');

        if(controls.length === 0) return;
        if(chat === document.activeElement && helpers.controls.splitSwitch === false && helpers.controls.ejectSwitch === false) return;

        for(let i = 0; i < controls.length; i++) {
            const control = controls[i];

            if(key == control.keycode && !control.disabled) {
                switch(control.type) {
                    case '1x':
                        helpers.controls.splitSwitch = false;
                        break;
        
                    case '2x':
                        helpers.controls.splitSwitch = false;
                        break;
        
                    case '4x':
                        helpers.controls.splitSwitch = false;
                        break;
        
                    case '8x':
                        helpers.controls.splitSwitch = false;
                        break;
        
                    case '16x':
                        helpers.controls.splitSwitch = false;
                        break;

                    case 'holdSplit':
                        clearInterval(helpers.controls.splitInterval);
                        helpers.controls.splitSwitch = false;
                        break;

                    case 'eject':
                        helpers.controls.ejectSwitch = false;
                        break;
        
                    case 'holdEject':
                        clearInterval(window.plus.helpers.controls.ejectInterval);
                        helpers.controls.ejectSwitch = false;
                        break;

                    case 'movementUp':
                        if(helpers.controls.isActiveMovement) helpers.controls.isActiveMovement = false;
                        delete helpers.controls.activeMovement['up'];
                        break;
        
                    case 'movementRight':
                        if(helpers.controls.isActiveMovement) helpers.controls.isActiveMovement = false;
                        delete helpers.controls.activeMovement['right'];
                        break;
        
                    case 'movementDown':
                        if(helpers.controls.isActiveMovement) helpers.controls.isActiveMovement = false;
                        delete helpers.controls.activeMovement['down'];
                        break;
        
                    case 'movementLeft':
                        if(helpers.controls.isActiveMovement) helpers.controls.isActiveMovement = false;
                        delete helpers.controls.activeMovement['left'];
                        break;
                }
            }
        }
    }

    split(times) {
        for(let i = 0; i < times; i++) {
            setTimeout(function() {
                window.split();
            }, 50 * i);
        }
    }

    goTo(x, y) {
        x = window.innerWidth / x; y = window.innerHeight / y;
        window.goTo(x, y);
    }
    
    getCookie(name) {
        let v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
        return v ? v[2] : null;
    }

    setCookie(name, value, days) {
        const d = new Date;
        d.setTime(d.getTime() + 24*60*60*1000*days);
        document.cookie = name + '=' + value + ';path=/;expires=' + d.toGMTString();
    }
     
    deleteCookie(name) {
        this.setCookie(name, '', -1);
    }

    setHash(hash) {
        this.deleteCookie('user_hash');
        this.deleteCookie('PHPSESSID');
        this.setCookie('user_hash', hash, 30);

        window.location.reload();
    }

    async hashLogin() {
        const { value: hash } = await Swal.fire({
            title: 'Enter your hash',
            input: 'text',
            inputValue: '',
            showCancelButton: true,
            inputValidator: (value) => {
              if(!value) {
                return 'You need to write something!'
              }

              if(value.length !== 40) {
                  return 'The hash length must be 40 characters.'
              }
            }
          })
          
          if(hash) {
            this.setHash(hash);
          }
    }

    hashShow() {
        const hash = this.getCookie('user_hash');

        if(hash === null) {
            return Swal.fire({
                icon: 'error',
                text: 'You must be logged in to your account to view its hash.'
              });
        }

        Swal.fire({
            icon: 'info',
            html: `The hash assigned to this account is: <strong>${hash}</strong>.<br><br>Remember to never give it to anyone!`
          });
    }

    addEventListeners() {
        document.addEventListener('keydown', this.keydown);
        document.addEventListener('keyup', this.keyup);
        document.addEventListener('mousedown', this.mousedown);
        document.addEventListener('mouseup', this.mouseup); 

        document.getElementById('canvas').addEventListener('contextmenu', function(e) {
            e.preventDefault();
        });
    }
}

class UI {
    constructor() {
        this.loadCSS();
        this.loadGUI();
        this.loadSettings();
    }

    loadCSS() {
        const css = `
            #plusSettingsBtn {
                float: right;
                width: 12%;
            }

            #plusSettings .modal-body {
                height: 300px;
                user-select: none;
            }

            #plusSettings .modal-footer {
                user-select: none;
            }

            #plusContent {
                height: 100%;
            }

            #general-content {
                display: flex;
                flex-flow: column wrap;
                height: 160px;
            }

            #plusContent .tab-pane {
                height: 100%;
            }

            #plusTabs {
                text-align: center;
                border-bottom: none !important;
                margin-top: 1em;
            }

            #plusTabs > li {
                float: none !important;
                display: inline-block;
            }

            #plusTabs>li.active>a, #plusTabs>li.active>a:hover, #plusTabs>li.active>a:focus {
                border: none !important;
                background: #efefef;
                color: #27272A;
            }

            #plusTabs>li>a {
                border: none !important;
                border-radius: 4px;
                color: #5f5f67;
            }

            #pLabel {
                text-align: center;
            }

            .plus-checkbox {
                padding-bottom: 0.5em;
                width: 50%;
            }

            .plus-checkbox label {
                max-width: 100%;
                position: relative;
                display: inline-block;
                padding-left: 20px;
                margin-bottom: 0;
                font-weight: 400;
                vertical-align: middle;
                cursor: pointer;
            }

            .plus-checkbox input[type=radio], .plus-checkbox input[type=checkbox] {
                position: absolute;
                margin-left: -20px;
            }

            #skinPanel {
                float: left;
                margin-left: 5px;
            }

            .skinBtn {
                margin: 0;
                padding: 2px 8px;
            }

            .plusControls {
                display: block;
                height: 210px;
            }

            .plusBtn {
                display: flex;
                justify-content: space-between;
                margin-top: 8px;
            }

            .plusBtn button {
                width: 100px;
            }

            .plusHeader {
                display: flex;
                justify-content: center;
                width: 100%;
            }

            .plusContent {
                display: flex;
                flex-flow: column nowrap;
                height: 210px;
                padding: 0;
                margin: 0;
            }

            .plusContent li {
                display: flex;
                margin: 0 auto;
                padding: 0.75rem;
                width: 300px;
                height: 34px;
                align-items: center;
                justify-content: space-between;
                border-radius: 0.375rem;
                background: #FAFAFA;
                margin-top: 0.525rem;
            }

            .controlsContent {
                display: flex;
                flex-flow: column nowrap;
                height: 210px;
                padding: 0;
                margin: 0;
            }

            .controlsContent li {
                display: flex;
                justify-content: space-around;
                align-items: center;
                margin-top: 5px;
            }

            .emptyPanel {
                text-align: center;
                padding: 0.5em;
                margin: 0.5em;
            }

            .delete {
                cursor: pointer;
            }

            html, 
            body {
                height: 100%;
            }

            .main-panel {
                margin: 0 2px;
                box-shadow: 0 4px 6px -1px rgb(0 0 0 / 10%), 0 2px 4px -1px rgb(0 0 0 / 6%);
            }

            .friends-online {
                border-radius: 10px;
                box-shadow: 0 4px 6px -1px rgb(0 0 0 / 10%), 0 2px 4px -1px rgb(0 0 0 / 6%);
                border: none;
            }

            #playBtn {
                width: 74.8% !important;
            }
            
            #playBtn.has-spinner {
                width: 62.9% !important;
            }

            #playBtn.btn-danger {
                width: 75.5% !important;
            }

            #spectateBtn {
                height: 34px;
            }

            table.chat-table {
                margin-bottom: 34px !important;
            }

            .btn:focus, .btn:active:focus, .btn.active:focus, .btn.focus, .btn:active.focus, .btn.active.focus {
                outline: none;
            }

            .btn-primary {
                background-color: #3B82F6;
                border: none;
            }
         
            .btn-primary:hover {
                background-color: #2563EB;
            }

            .btn-primary:hover, .btn-primary:focus, .btn-primary.focus, .btn-primary:active, .btn-primary.active, .open>.dropdown-toggle.btn-primary {
                background-color: #2563EB;
            }
         
            .btn-success {
                background-color: #22C55E;
                border: none;
            }
         
            .btn-success:hover {
                background-color: #16A34A;
            }

            .btn-success:hover, .btn-success:focus, .btn-success.focus, .btn-success:active, .btn-success.active, .open>.dropdown-toggle.btn-success {
                background-color: #16A34A;
            }
         
            .btn-settings {
                float: none !important;
                display: inline-block !important;
                width: auto !important;
                height: auto !important;
                border: none;
                background-color: #06B6D4;
            }
         
            .btn-settings:hover {
                background-color: #0891B2;
                border: none;
            }

            .btn-info:hover, .btn-info:focus, .btn-info.focus, .btn-info:active, .btn-info.active, .open>.dropdown-toggle.btn-info {
                background-color: #0891B2;
            }
         
            .btn-warning {
                background-color: #EAB308;
                border: none;
            }
         
            .btn-warning:hover {
                background-color: #CA8A04;
            }

            .btn-warning:hover, .btn-warning:focus, .btn-warning.focus, .btn-warning:active, .btn-warning.active, .open>.dropdown-toggle.btn-warning {
                background-color: #CA8A04;
            }
         
            .btn-danger {
                background-color: #F43F5E;
                border: none;
            }
         
            .btn-danger:hover {
                background-color: #E11D48;
            }

            .btn-danger:hover, .btn-danger:focus, .btn-danger.focus, .btn-danger:active, .btn-danger.active, .open>.dropdown-toggle.btn-danger {
                background-color: #E11D48;
            }

            .form-control  {
                border: 1px solid #ced4da;
                box-shadow: none;
            }

            #radio_mode .gm-s {
                border: 2px solid #D1D5DB;
            }

            #chat_textbox {
                border-radius: 0.3em;
            }

            .exp-bar {
                border: 2px solid #3B82F6;
            }

            .exp-bar .progress-bar {
                background-color: #60A5FA;
            }

            .modal-content {
                border: none;
            }

            .swal2-popup {
                font-size: 1.5rem !important;
            }

            .swal2-styled.swal2-confirm {
                background-color: #3B82F6 !important;
            }

            .swal2-styled.swal2-confirm:focus {
                box-shadow: 0 0 0 3px rgb(59 130 246 / 50%) !important;
            }

            .pickr {
                float: left;
                margin-left: 5px;
            }

            .pickr .pcr-button {
                height: 1.8em;
                width: 1.8em;
            }

            .pickr .pcr-button:after, .pickr .pcr-button:before {
                border: 1px solid #80808060;
            }

            .hashLogin {
                margin: 0 5px;
            }

            li {
                list-style-type: none;
            }

            @media (min-width: 768px) {
                .modal-content {
                    -webkit-box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1),0 4px 6px -2px rgba(0,0,0,0.05);
                    box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1),0 4px 6px -2px rgba(0,0,0,0.05);
                }
            }

            body.dark-mode .plusColor {
                background: #27272A;
            }

            body.dark-mode .plusColor .pcr-result {
                background: #36363a;
                color: #c6c6c8;
            }

            body.dark-mode .main-panel {
                background: #18181B;
                color: #c6c6c8;
            }

            body.dark-mode .form-control[disabled], body.dark-mode .form-control[readonly], body.dark-mode fieldset[disabled] .form-control {
                background: #27272A;
            }

            body.dark-mode .form-control {
                background: #27272A;
                border: none;
                color: #c6c6c8;
            }

            body.dark-mode #radio_mode .gm-s {
                border: 2px solid #5f5f60;
                background: #27272A;
            }

            body.dark-mode .bb-panel {
                background: #18181B;
            }

            body.dark-mode .progress {
                background: #27272A;
            }

            body.dark-mode .dropdown-menu>li>a {
                color: #c6c6c8;
            }

            body.dark-mode .dropdown-menu {
                background: #27272A;
            }

            body.dark-mode hr {
                border-top: 1px solid #5f5f60;
            }

            body.dark-mode .table-striped>tbody>tr:nth-child(odd) {
                background: #1f1f21 !important;
            }

            body.dark-mode .table-striped>tbody>tr:nth-child(even) {
                background-color: #18181B !important;
            }
         
            body.dark-mode .table>thead>tr>th {
                border-bottom: 2px solid #5f5f60;
            }

            body.dark-mode .table>thead>tr>th, body.dark-mode .table>tbody>tr>th, body.dark-mode .table>tfoot>tr>th, body.dark-mode .table>thead>tr>td, body.dark-mode .table>tbody>tr>td, body.dark-mode .table>tfoot>tr>td {
                border-top: none;
            }

            body.dark-mode .modal-content {
                background: #18181B;
                color: #c6c6c8;
            }

            body.dark-mode .modal-header {
                border-bottom: 1px solid #5f5f60;
            }

            body.dark-mode .bub-table-list {
                border: 1px solid #353636;
            }

            body.dark-mode .modal-footer {
                border-top: 1px solid #5f5f60;
            }

            body.dark-mode .dropdown-menu>li>a:hover, body.dark-mode .dropdown-menu>li>a:focus {
                background: #202023;
            }

            body.dark-mode .user-notif div.info {
                background: #1f1f21;
            }

            body.dark-mode .user-notif div {
                border-bottom: none;
            }

            body.dark-mode .user-notif div.warning {
                background: none;
                border-left: 1px solid #F59E0B;            
            }

            body.dark-mode .guild-members2 {
                border-bottom: 1px solid #5f5f60;
                border-top: 1px solid #5f5f60;
            }

            body.dark-mode .table>thead>tr>td.warning, body.dark-mode .table>tbody>tr>td.warning, body.dark-mode .table>tfoot>tr>td.warning, body.dark-mode .table>thead>tr>th.warning, body.dark-mode .table>tbody>tr>th.warning, body.dark-mode .table>tfoot>tr>th.warning, body.dark-mode .table>thead>tr.warning>td, body.dark-mode .table>tbody>tr.warning>td, body.dark-mode .table>tfoot>tr.warning>td, body.dark-mode .table>thead>tr.warning>th, body.dark-mode .table>tbody>tr.warning>th, body.dark-mode .table>tfoot>tr.warning>th {
                color: #FDE047;
                background: none;
            }

            body.dark-mode .nav-tabs {
                border-bottom: none;
            }

            body.dark-mode .nav-tabs>li.active>a, body.dark-mode .nav-tabs>li.active>a:hover, body.dark-mode .nav-tabs>li.active>a:focus {
                color: #c6c6c8;
                background: #2a2a2d;
                border: 1px solid transparent;
            }

            body.dark-mode .nav-tabs>li>a {
                border-radius: 4px;
            }

            body.dark-mode .nav-tabs>li>a:hover {
                border-color: transparent;
            }

            body.dark-mode .nav>li>a:hover, body.dark-mode .nav>li>a:focus {
                background: #212123;
            }

            body.dark-mode .guild-members {
                border-bottom: 1px solid #5f5f60;
            }

            body.dark-mode #tournament-modal .panel-heading {
                color: #c6c6c8 !important;
                border: none;
            }

            body.dark-mode .panel-default>.panel-heading {
                background: #18181B;
            }

            body.dark-mode .panel {
                background: #1f1f21;
            }

            body.dark-mode .panel-default {
                border-color: transparent;
            }

            body.dark-mode #connecting > div {
                background: #18181B !important;
                color: #c6c6c8;
            }

            body.dark-mode #statsChartText, body.dark-mode #statsText {
                color: #c6c6c8;
            }
         
            body.dark-mode #statsSubtext {
                color: #a5a5a5;
            }

            body.dark-mode input.chat {
                background: #18181B;
                border: none;
            }

            body.dark-mode input:focus-visible {
                outline: none;
                color: #c6c6c8;
            }

            body.dark-mode .swal2-popup {
                background: #18181B;
            }

            body.dark-mode .swal2-title {
                color: #c6c6c8;
            }

            body.dark-mode .swal2-html-container {
                color: #B4B4B5;
            }

            body.dark-mode .swal2-input-label {
                color: #B4B4B5;
            }

            body.dark-mode .swal2-validation-message {
                background: #27272A;
                color: #c6c6c8;
            }

            body.dark-mode .scrollbar-track {
                background: transparent;
            }

            body.dark-mode .scrollbar-track .show {
                opacity: 0;
            }

            body.dark-mode .chatUsers li {
                background: #27272A;
            }

            body.dark-mode .accounts li {
                background: #27272A;
            }

            body.dark-mode #plusTabs>li.active>a, body.dark-mode #plusTabs>li.active>a:hover, body.dark-mode #plusTabs>li.active>a:focus {
                border: none !important;
                background: #27272A;
                color: #c6c6c8;
            }

            body.dark-mode #plusTabs>li>a {
                border: none !important;
                border-radius: 4px;
                color: #a2a2ad;
            }

            body.dark-mode .text-muted {
                color: #a2a2a2;
            }

            body.dark-mode .close {
                color: #c6c6c8;
                text-shadow: none;
            }

            body.dark-mode .scrollbar-thumb {
                background: #6B7280;
            }

            body.dark-mode ::-webkit-scrollbar {
                width: 5px;
                height: 5px;
            }
         
            body.dark-mode ::-webkit-scrollbar-button {
                width: 0px;
                height: 0px;
            }
         
            body.dark-mode ::-webkit-scrollbar-thumb {
                background: #71717A;
                border: 0px none #ffffff;
                border-radius: 50px;
            }
         
            body.dark-mode ::-webkit-scrollbar-thumb:hover {
                background: #52525B;
            }
         
            body.dark-mode ::-webkit-scrollbar-thumb:active {
                background: #52525B;
            }
         
            body.dark-mode ::-webkit-scrollbar-track {
                background: transparent;
                border: 0px none #ffffff;
                border-radius: 50px;
            }
            
            body.dark-mode ::-webkit-scrollbar-track:hover {
                background: transparent;
            }
         
            body.dark-mode ::-webkit-scrollbar-track:active {
                background: transparent;
            }
         
            body.dark-mode ::-webkit-scrollbar-corner {
                background: transparent;
            }

        `;

        const style = document.createElement('style');
        style.textContent = css;
        document.head.append(style);
    }

    loadGUI() {
        $('#formStd h2').html('Bubble.am+').css({'display': 'inline-block', 'margin-right': '0.3em'});
        $('#formStd h2').after(`<p style="display: inline-block; vertical-align: middle;">${window.plus.version}</p>`)
        $('.btn-settings').after(`
            <button id="plusSettingsBtn" onclick="return false;" class="btn btn-danger" data-toggle="modal" data-target=".bs-example-modal-lg">
                <i class="fa fa-plus"></i>
            </button>
        `);
        $('#chat_textbox').attr('maxlength', '99');

        $('#overlays').before(`
        <div id="plusSettings" class="modal fade" tabindex="-1" role="dialog">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
                        <h4 class="modal-title" id="pLabel">Bubble.am+ ${window.plus.version}</h4>
                        <ul id="plusTabs" class="nav nav-tabs" role="tablist">
                            <li role="presentation" class="active"><a href="#general" aria-controls="general" role="tab" data-toggle="tab">General</a></li>
                            <li role="presentation"><a href="#controls" aria-controls="controls" role="tab" data-toggle="tab">Controls</a></li>
                            <li role="presentation"><a href="#chat" aria-controls="chat" role="tab" data-toggle="tab">Chat</a></li>
                            <li role="presentation"><a href="#accounts" aria-controls="accounts" role="tab" data-toggle="tab">Accounts</a></li>
                        </ul>
                    </div>
                    <div class="modal-body">
                        <div id="plusContent" class="tab-content">
                            <div role="tabpanel" class="tab-pane active" id="general"></div>
                            <div role="tabpanel" class="tab-pane" id="controls"></div>
                            <div role="tabpanel" class="tab-pane" id="chat"></div>
                            <div role="tabpanel" class="tab-pane" id="accounts"></div>
                        </div>
                    </div>
                    <div class="modal-footer" style="text-align: center;">
                        <a href="http://enderror.pl" target="_blank">
                            <img id="enderror-logo" src="https://i.imgur.com/W7FkCgA.png" style="width: 3em;">
                        </a>
                    </div>
                </div>
            </div>
        </div>
        `);

        const plusSettingsBtn = document.querySelector('#plusSettingsBtn');
        plusSettingsBtn.addEventListener('click', function() {
            $('#plusSettings').modal('toggle');
        });

        this.modsGUI();
        this.controlsGUI();
        this.chatGUI();
        this.accountsGUI();

        $('#plusSettings').on('shown.bs.modal', () => {
            $(document).off('focusin.modal');
        });

        $('#plusSettings').on('hidden.bs.modal', () => {
            const settings = Store.getSettings();

            this.displayControls(settings.controls);
            this.displayUsers(settings.chat);
            this.displayAccounts(settings.accounts);
        });

        const pickr = new Pickr({
            el: '#colorMod',
            theme: 'nano',
            container: 'body',
            default: Store.getSettings().values.cellsColor,
            position: 'right-start',
            appClass: 'plusColor',

            swatches: null,
            components: {
                preview: true,
                opacity: true,
                hue: true,
        
                interaction: {
                    hex: false,
                    rgba: false,
                    hsla: false,
                    hsva: false,
                    cmyk: false,
                    input: true,
                    clear: false,
                    save: true
                }
            }
        });
     
        pickr.on('save', (color, instance) => {
            window.plus.settings.values.cellsColor = color.toRGBA().toString(3);
            Store.setSettings();
        });

        window.Scrollbars = Scrollbar.initAll();
    }

    modsGUI() {
        $('#general').append(`
            <div id="general-content">
                <div class="plus-checkbox">
                    <label>
                        <input id="plus_transparentVirus" type="checkbox" onchange="setTransparentVirus($(this).is(':checked'));"> Transparent virus
                    </label>
                </div>
                <div class="plus-checkbox">
                    <label>
                        <input id="plus_noGrid" type="checkbox" onchange="setNoGrid($(this).is(':checked'));"> No grid
                    </label>
                </div>
                <div class="plus-checkbox" style="display: flex; align-items: center;">
                    <label style="float: left;">
                        <input id="plus_customColor" type="checkbox" onchange="setCustomColor($(this).is(':checked'));"> Custom cells color
                    </label>
                    <div id="colorMod" style="float: left;"></div>
                </div>
                <div class="plus-checkbox" style="display: flex; align-items: center;">
                    <label style="float: left;">
                        <input id="plus_customSkin" type="checkbox" onchange="setCustomSkin($(this).is(':checked'));"> Custom skin
                    </label>
                    <div id="skinPanel">
                        <button id="setSkinBtn" class="btn btn-success skinBtn"><i class="fa fa-plus"></i></button>
                        <button id="showSkinBtn" class="btn btn-primary skinBtn"><i class="fa fa-eye"></i></button>
                    </div>
                </div>
                <div class="plus-checkbox">
                    <label>
                        <input id="plus_bypassSkin" type="checkbox" onchange="setBypassSkin($(this).is(':checked'));"> Bypass "No skins"
                    </label>
                </div>
                <div class="plus-checkbox">
                    <label>
                        <input id="plus_darkMenu" type="checkbox" onchange="setDarkMenu($(this).is(':checked'));"> Dark theme menu
                    </label>
                </div>
                <div class="plus-checkbox">
                    <label>
                        <input id="plus_virusSplitCounter" type="checkbox" onchange="setVirusSplitCounter($(this).is(':checked'));"> Virus split counter
                    </label>
                </div>
                <div class="plus-checkbox">
                    <label>
                        <input id="plus_cellsCounter" type="checkbox" onchange="setCellsCounter($(this).is(':checked'));"> Cells counter
                    </label>
                </div>
                <div class="plus-checkbox">
                    <label>
                        <input id="plus_hideName" type="checkbox" onchange="setHideName($(this).is(':checked'));"> Hide your name
                    </label>
                </div>
                <div class="plus-checkbox">
                    <label>
                        <input id="plus_showMotherMass" type="checkbox" onchange="setShowMotherMass($(this).is(':checked'));"> Show mother mass
                    </label>
                </div>
        </div>
        `);

        document.querySelector('#setSkinBtn').addEventListener('click', async () => {
            const { value: skinValue } = await Swal.fire({
                title: 'Enter skin url (png, jpg or jpeg)',
                input: 'text',
                showCancelButton: true,
                inputValidator: (value) => {
                  if(!value) {
                    return 'You need to write something.'
                  }

                  if(!this.isUriImage(value)) {
                      return 'Your skin url is wrong.'
                  }
                }
              });
              
              if(skinValue) {
                Swal.fire({
                    title: 'Skin has been successfully set.',
                    imageUrl: skinValue,
                    imageAlt: 'Skin',
                });

                const settings = window.plus.settings;
                const helpers = window.plus.helpers;
                  
                settings.values.skinUrl = skinValue;
                helpers.controls.skinInd == 1 ? helpers.skin.skinInd++ : helpers.skin.skinInd--;
                
                Store.setSettings();
              }
        });

        document.querySelector('#showSkinBtn').addEventListener('click', async function() {
            const settings = window.plus.settings;

            if(typeof(settings.values.skinUrl) === 'string') {
                Swal.fire({
                    title: 'Current skin',
                    imageUrl: settings.values.skinUrl,
                    imageAlt: 'Skin',
                });
            } else {
                Swal.fire({
                    title: 'Current skin',
                    text: 'No skin has been set yet.',
                });
            }
        });
    }

    controlsGUI() {
        $('#controls').append(`
            <div class="plusHeader" style="text-align: center; justify-content: space-around;">
                <strong style="width: 240px;">Type</strong>
                <strong style="width: 200px; padding-left: 10px;">Key</strong>
                <strong style="width: 60px;">Disabled</strong>
                <strong style="width: 40px;"></strong>
            </div>
            <div class="plusContent-wrapper" data-scrollbar>
                <ul class="controlsContent controls"></ul>
            </div>
            <div class="plusBtn">
                <button class="btn btn-warning controlsDefault">Load default</button>
                <button class="btn btn-danger controlsDelete">Delete all</button>
                <button class="btn btn-success controlsNew">Add new</button>
                <button class="btn btn-primary controlsSave">Save</button>
            </div>
        `);

        $('.controls').on('click', (e) => {
            if(!e.target.className.includes('delete')) return;

            const controlsLength = $('.controls li').length;
            const hasEmpty = $('.controls').find('.emptyPanel').length === 1;

            if(controlsLength === 1) {
                this.displayEmpty('.controls', 'controls');
            }

            if(controlsLength !== 1 && hasEmpty) {
                $('.controls .emptyPanel').remove();
            }

            e.target.parentElement.remove();
        });

        $('.controlsDefault').on('click', () => {
            const defaultSettings = window.plus.defaultSettings;

            $('.plusControls tbody').html('');

            this.displayControls(defaultSettings.controls);
        });

        $('.controlsDelete').on('click', () => {
            this.displayEmpty('.controls', 'controls');
        });

        $('.controlsNew').on('click', () => {
            this.addControl();
        });

        $('.controlsSave').on('click', () => {
            const newControls = [];
            const actualControls = document.querySelectorAll('.controls li');
            const settings = window.plus.settings;

            for(let i = 0; i < actualControls.length; i++) {
                const li = actualControls[i];
                const select = li.querySelectorAll('select');
                const checkbox = li.querySelector('input');

                const controlType = select[0].value;
                const controlKey = select[1].value;
                const controlDisabled = checkbox.checked;

                newControls.push({
                    type: controlType,
                    keycode: controlKey,
                    disabled: controlDisabled
                });
            }

            settings.controls = newControls;
            Store.setSettings();

            Swal.fire({
                icon: 'success',
                title: 'Success!',
                text: 'Your new controls configuration has been successfully saved.'
          });
        });
    }

    addControl() {
        const controlsTemplate = `
            <li>
                <select class="form-control" style="border-radius:4px; width: 15em;">
                    <option value="1x" selected="selected">Split</option>
                    <option value="eject">Eject</option>
                    <option value="setCamera">Lock/unlock camera [spectate]</option>
                    <option value="2x">2x split</option>
                    <option value="4x">4x split</option>
                    <option value="8x">8x split</option>
                    <option value="16x">16x split</option>
                    <option value="holdSplit">Hold split</option>
                    <option value="holdEject">Hold eject</option>
                    <option value="movementUp">Movement up</option>
                    <option value="movementRight">Movement right</option>
                    <option value="movementDown">Movement down</option>
                    <option value="movementLeft">Movement left</option>
                    <option value="hashLogin">Login by hash</option>
                    <option value="hashShow">Show hash</option>
                </select>
                <select class="form-control" style="border-radius:4px; width: 12em;">
                    <option value="1">Left Click</option>
                    <option value="2">Scroll Click</option>
                    <option value="3">Right Click</option>
                    <option value="9">Tab</option>
                    <option value="12">Clear</option>
                    <option value="13">Enter</option>
                    <option value="16">Shift</option>
                    <option value="17">Ctrl</option>
                    <option value="18">Alt</option>
                    <option value="27">Esc</option>
                    <option value="32" selected="selected">Space</option>
                    <option value="33">Page Up</option>
                    <option value="34">Page Down</option>
                    <option value="35">End</option>
                    <option value="36">Home</option>
                    <option value="37">Left Arrow</option>
                    <option value="38">Up Arrow</option>
                    <option value="39">Right Arrow</option>
                    <option value="40">Down Arrow</option>
                    <option value="45">Insert</option>
                    <option value="46">Delete</option>
                    <option value="48">0</option>
                    <option value="49">1</option>
                    <option value="50">2</option>
                    <option value="51">3</option>
                    <option value="52">4</option>
                    <option value="53">5</option>
                    <option value="54">6</option>
                    <option value="55">7</option>
                    <option value="56">8</option>
                    <option value="57">9</option>
                    <option value="65">A</option>
                    <option value="66">B</option>
                    <option value="67">C</option>
                    <option value="68">D</option>
                    <option value="69">E</option>
                    <option value="70">F</option>
                    <option value="71">G</option>
                    <option value="72">H</option>
                    <option value="73">I</option>
                    <option value="74">J</option>
                    <option value="75">K</option>
                    <option value="76">L</option>
                    <option value="77">M</option>
                    <option value="78">N</option>
                    <option value="79">O</option>
                    <option value="80">P</option>
                    <option value="81">Q</option>
                    <option value="82">R</option>
                    <option value="83">S</option>
                    <option value="84">T</option>
                    <option value="85">U</option>
                    <option value="86">V</option>
                    <option value="87">W</option>
                    <option value="88">X</option>
                    <option value="89">Y</option>
                    <option value="90">Z</option>
                    <option value="96">Numpad 0</option>
                    <option value="97">Numpad 1</option>
                    <option value="98">Numpad 2</option>
                    <option value="99">Numpad 3</option>
                    <option value="100">Numpad 4</option>
                    <option value="101">Numpad 5</option>
                    <option value="102">Numpad 6</option>
                    <option value="103">Numpad 7</option>
                    <option value="104">Numpad 8</option>
                    <option value="105">Numpad 9</option>
                    <option value="106">Numpad *</option>
                    <option value="107">Numpad +</option>
                    <option value="109">Numpad -</option>
                    <option value="110">Numpad .</option>
                    <option value="111">Numpad /</option>
                    <option value="112">F1</option>
                    <option value="113">F2</option>
                    <option value="114">F3</option>
                    <option value="115">F4</option>
                    <option value="116">F5</option>
                    <option value="117">F6</option>
                    <option value="118">F7</option>
                    <option value="119">F8</option>
                    <option value="120">F9</option>
                    <option value="121">F10</option>
                    <option value="122">F11</option>
                    <option value="123">F12</option>
                    <option value="124">F13</option>
                    <option value="125">F14</option>
                    <option value="126">F15</option>
                    <option value="127">F16</option>
                    <option value="128">F17</option>
                    <option value="129">F18</option>
                    <option value="130">F19</option>
                    <option value="131">F20</option>
                    <option value="132">F21</option>
                    <option value="133">F22</option>
                    <option value="134">F23</option>
                    <option value="135">F24</option>
                    <option value="186">;</option>
                    <option value="187">=</option>
                    <option value="188">,</option>
                    <option value="189">-</option>
                    <option value="190">.</option>
                    <option value="191">/</option>
                    <option value="192">&#96;</option>
                    <option value="219">[</option>
                    <option value="220">&#92;</option>
                    <option value="221">]</option>
                    <option value="222">'</option>
                </select>
                <input type="checkbox" style="margin: 0;">
                <i class="fa fa-remove delete"></i>
            </li>
        `;

        const hasEmpty = $('.controls').find('.emptyPanel').length === 1;
        if(hasEmpty) {
            $('.controls .emptyPanel').remove();
        }

        $('.controls').append(controlsTemplate);
        this.fixScrollbar(window.Scrollbars[0]);
    }

    displayControls(settings) {
        if(settings.length === 0) {
            return this.displayEmpty('.controls', 'controls');
        };

        $('.controls').html('');

        for(let i = 0; i < settings.length; i++) {
            const setting = settings[i];
            
            this.addControl();

            const li = document.querySelectorAll('.controls li')[i];
            const select = li.querySelectorAll('select');
            const checkbox = li.querySelector('input');

            select[0].value = setting.type;
            select[1].value = setting.keycode;
            checkbox.checked = setting.disabled;
        }
    }

    chatGUI() {
        $('#chat').append(`
            <div class="plusHeader">
                <strong>Blocked users</strong>
            </div>
            <div class="plusContent-wrapper" data-scrollbar>
                <ul class="plusContent chatUsers"></ul>
            </div>
            <div class="plusBtn">
                <button class="btn btn-danger chatDelete">Delete all</button>
                <button class="btn btn-success chatNew">Add new</button>
                <button class="btn btn-primary chatSave">Save</button>
            </div>
        `);

        $('.chatUsers').on('click', (e) => {
            if(!e.target.className.includes('delete')) return;

            const chatLength = $('.chatUsers li').length;
            const hasEmpty = $('.chatUsers').find('.emptyPanel').length === 1;

            if(chatLength === 1) {
                this.displayEmpty('.chatUsers', 'users');
            }

            if(chatLength !== 1 && hasEmpty) {
                $('.chatUsers .emptyPanel').remove();
            }

            e.target.parentElement.remove();
        });

        $('.chatDelete').on('click', () => {
            this.displayEmpty('.chatUsers', 'users');
        });

        $('.chatNew').on('click', async () => {
            let { value: playerNick } = await Swal.fire({
                title: 'Player nickname',
                text: 'You don\'t need to specify a clan tag.',
                input: 'text',
                inputValue: '',
                showCancelButton: true,
                inputValidator: (value) => {
                  if(!value) {
                    return 'You need to write something!'
                  }
    
                  if(value.length > 15) {
                      return 'Player nickname cannot be longer than 15 characters.'
                  }
    
                  const currentUsers = this.getCurrentUsers();
                  if(currentUsers.includes(value.toLowerCase())) {
                    return 'This user is already on the list.';
                  }
                }
              });
              
            if(playerNick) {
                this.addUser(playerNick);
            }
        });

        $('.chatSave').on('click', () => {
            const newChat = [];
            const actualChat = document.querySelectorAll('.chatUsers li');
            const settings = window.plus.settings;

            for(let i = 0; i < actualChat.length; i++) {
                const li = actualChat[i];
                const playerNick = li.querySelectorAll('span')[0].innerHTML;

                newChat.push(playerNick.toLowerCase());
            }

            settings.chat = newChat;
            Store.setSettings();

            Swal.fire({
                icon: 'success',
                title: 'Success!',
                text: 'Your new chat configuration has been successfully saved.'
          });
        });
    }

    addUser(name) {
        const hasEmpty = $('.chatUsers').find('.emptyPanel').length === 1;
        if(hasEmpty) {
            $('.chatUsers .emptyPanel').remove();
        }

        $('.chatUsers').append(`
            <li>
                <span>${name.toLowerCase()}</span>
                <i class="fa fa-remove delete"></i>
            </li>
        `);

        this.fixScrollbar(window.Scrollbars[1]);
    }

    getCurrentUsers() {
        const currentUsers = document.querySelectorAll('.chatUsers li');
        const nicks = [];

        for(let i = 0; i < currentUsers.length; i++) {
            const user = currentUsers[i];
            const nick = user.querySelectorAll('span')[0].innerHTML.toLowerCase();

            nicks.push(nick);
        }

        return nicks;
    }

    displayUsers(users) {
        if(users.length === 0) {
            return this.displayEmpty('.chatUsers', 'users');
        }

        $('.chatUsers').html('');

        for(let i = 0; i < users.length; i++) {
            const playerNick = users[i];

            this.addUser(playerNick);
        }
    }

    accountsGUI() {
        $('#accounts').append(`
            <div class="plusHeader">
                <strong>Your accounts</strong>
            </div>
            <div class="plusContent-wrapper" data-scrollbar>
                <ul class="plusContent accounts"></ul>
            </div>
            <div class="plusBtn">
                <button class="btn btn-danger accountsDelete">Delete all</button>
                <button class="btn btn-success accountsNew">Add new</button>
                <button class="btn btn-primary accountsSave">Save</button>
            </div>
        `);

        $('.accounts').on('click', (e) => {
            if(!e.target.className.includes('delete')) return;

            const accountsLength = $('.accounts li').length;
            const hasEmpty = $('.accounts').find('.emptyPanel').length === 1;

            if(accountsLength === 1) {
                this.displayEmpty('.accounts', 'accounts');
            }

            if(accountsLength !== 1 && hasEmpty) {
                $('.accounts .emptyPanel').remove();
            }

            e.target.parentElement.remove();
        });

        $('.accountsDelete').on('click', () => {
            this.displayEmpty('.accounts', 'accounts');
        });

        $('.accountsNew').on('click', async () => {
            const {value: account} = await Swal.fire({
                title: 'Add account',
                html: `
                  <input id="name" class="swal2-input" placeholder="Name" maxlength="15">
                  <input id="hash" class="swal2-input" placeholder="Hash" maxlength="40">
                `,
                focusConfirm: false,
                preConfirm: () => {
                    const name = document.getElementById('name').value.trim();
                    const hash = document.getElementById('hash').value.trim();
    
                    const currentAccounts = this.getCurrentAccounts();
                    const currentNames = currentAccounts.names;
                    const currentHashes = currentAccounts.hashes;
    
                    if(name.length === 0 || hash.length === 0) {
                        return Swal.showValidationMessage('You must complete all fields.');
                    }
    
                    if(hash.length !== 40) {
                        return Swal.showValidationMessage('The hash length must be 40 characters.');
                    }
    
                    if(currentNames.includes(name.toLowerCase()) || currentHashes.includes(hash)) {
                        return Swal.showValidationMessage('The name or hash is already on the list.');
                    }
    
                    return {
                        name,
                        hash
                    }
                }
              });
    
            if(account) {
                this.addAccount(account.name, account.hash);
            }
        });

        $('.accountsSave').on('click', () => {
            const newAccounts = [];
            const actualAccounts = document.querySelectorAll('.accounts li');
            const settings = window.plus.settings;

            for(let i = 0; i < actualAccounts.length; i++) {
                const li = actualAccounts[i];
                const accountName = li.querySelectorAll('span')[0].innerHTML;
                const accountHash = li.dataset.hash;

                newAccounts.push({
                    name: accountName,
                    hash: accountHash
                });
            }

            settings.accounts = newAccounts;
            Store.setSettings();

            Swal.fire({
                icon: 'success',
                title: 'Success!',
                text: 'Your new accounts configuration has been successfully saved.'
          });
        });
    }

    addAccount(name, hash) {
        const hasEmpty = $('.accounts').find('.emptyPanel').length === 1;
        if(hasEmpty) {
            $('.accounts .emptyPanel').remove();
        }

        $('.accounts').append(`
            <li data-hash="${hash}">
                <span>${name}</span>
                <div>
                    <span class="hashLogin" style="cursor: pointer;" onclick="window.plus.setHash($(this).parent().parent().data('hash'));">
                        <i class="fa fa-arrow-right"></i>
                    </span>
                    <span class="delete" style="cursor: pointer;" onclick="$(this).parent().parent().remove();">
                        <i class="fa fa-remove"></i>
                    </span>
                </div>
            </li>
        `);

        this.fixScrollbar(window.Scrollbars[2]);
    }

    displayAccounts(accounts) {
        if(accounts.length === 0) {
            return this.displayEmpty('.accounts', 'accounts');
        }

        $('.accounts').html('');

        for(const account in accounts) {
            const { name, hash } = accounts[account];
            
            this.addAccount(name, hash);
        }
    }

    getCurrentAccounts() {
        const currentAccounts = document.querySelectorAll('.accounts li');

        const accounts = [];
        const names = [];
        const hashes = [];

        for(let i = 0; i < currentAccounts.length; i++) {
            const account = currentAccounts[i];
            const name = account.querySelectorAll('span')[0].innerHTML.toLowerCase();
            const hash = account.dataset.hash;

            accounts.push({
                name,
                hash
            });

            names.push(name);
            hashes.push(hash);
        }

        return {
            accounts,
            names,
            hashes
        }
    }

    loadSettings() {
        const settings = Store.getSettings();

        window.plus.settings = settings;

        for(const checkbox in settings.checkbox) {
            const name = checkbox;
            const value = settings.checkbox[checkbox];

            $(`#plus_${name}`).prop('checked', value).change();
        }

        this.displayControls(settings.controls);
        this.displayUsers(settings.chat);
        this.displayAccounts(settings.accounts);
    }

    displayEmpty(element, text) {
        $(element).html(`<div class="emptyPanel">There are currently no ${text} in this panel.</div>`);
    }

    fixScrollbar(scrollbar) {
        scrollbar.update();

        const limitY = scrollbar.limit.y;
        scrollbar.setPosition(0, limitY);
    }

    isUriImage = function(uri) {
        uri = uri.split('?')[0];
        const parts = uri.split('.');
        const extension = parts[parts.length - 1];
        const imageTypes = ['png', 'jpg', 'jpeg'];
        if(imageTypes.indexOf(extension) !== -1) {
            return true;   
        }
    }
}

class Store {
    static getSettings() {
        let settings;
        if(localStorage.getItem('plus_settings') === null) {
            this.setSettings();
        }

        settings = JSON.parse(localStorage.getItem('plus_settings'));

        return settings;
    }

    static setSettings() {
        localStorage.setItem('plus_settings', JSON.stringify(window.plus.settings));
    }
}

window.modLoaded = false;

window.setCustomColor = function(a) {
    window.plus.settings.checkbox.customColor = a;
    Store.setSettings();
}
 
window.setTransparentVirus = function(a) {
    window.plus.settings.checkbox.transparentVirus = a;
    Store.setSettings();
}

window.setCustomSkin = function(a) {
    window.plus.settings.checkbox.customSkin = a;
    Store.setSettings();
}

window.setBypassSkin = function(a) {
    window.plus.settings.checkbox.bypassSkin = a;
    Store.setSettings();
}
 
window.setNoGrid = function(a) {
    window.plus.settings.checkbox.noGrid = a;
    Store.setSettings();
}
 
window.setDarkMenu = function(a) {
    window.plus.settings.checkbox.darkMenu = a;
    if(a === true) {
        $('body').addClass('dark-mode');
        $('#enderror-logo').attr('src', 'https://i.imgur.com/ewMCLSe.png'); 
    } else {
        $('body').removeClass('dark-mode')
        $('#enderror-logo').attr('src', 'https://i.imgur.com/W7FkCgA.png'); 
    }

    Store.setSettings();
}

window.setVirusSplitCounter = function(a) {
    window.plus.settings.checkbox.virusSplitCounter = a;
    Store.setSettings();
}

window.setCellsCounter = function(a) {
    window.plus.settings.checkbox.cellsCounter = a;
    Store.setSettings();
}

window.setHideName = function(a) {
    window.plus.settings.checkbox.hideName = a;
    Store.setSettings();
}

window.setShowMotherMass = function(a) {
    window.plus.settings.checkbox.showMotherMass = a;
    Store.setSettings();
}

window.plus = new Plus();