Gota.io Features by Donut + Linesplit

Linesplit up, down, left and right • Diagonal linesplit with just one key • Hotkeys to show/hide skins, names, mass, food, chat, minimap, score panel, party panel and leaderboard • Auto respawn in Team Scrimmage • Leave a scrimmage match before it ends • You can change the keys as you wish

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Gota.io Features by Donut + Linesplit
// @namespace    https://www.youtube.com/channel/UCIpCflcKEN9YgaO9qDahpRg
// @version      1.7.12
// @description  Linesplit up, down, left and right • Diagonal linesplit with just one key • Hotkeys to show/hide skins, names, mass, food, chat, minimap, score panel, party panel and leaderboard • Auto respawn in Team Scrimmage • Leave a scrimmage match before it ends • You can change the keys as you wish
// @match        *://gota.io/web*
// @author       Donut
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function(){
    if (window.top != window.self) return;

    if (!('code' in KeyboardEvent.prototype)) {
        return alert("Sorry, your browser is incompatible with Gota.io Features by Donut. You're recommended to install the newest version of Google Chrome or Mozilla Firefox browsers.");
    }

    function validateVersion(v) {
        return !!v && !v.split('.').some(function(part) {
            return !/^\d+$/.test(part);
        });
    }

    function compareVersions(v1, v2) {
        var v1parts = v1.split('.').map(Number);
        var v2parts = v2.split('.').map(Number);

        while (v1parts.length < v2parts.length) v1parts.push(0);
        while (v2parts.length < v1parts.length) v2parts.push(0);

        for (var i = 0; i < v1parts.length; ++i) {
            if (v1parts[i] > v2parts[i]) return 1;
            else if (v1parts[i] < v2parts[i]) return -1;
        }
        return 0;
    }

    var importantUpdates = ['1.5.3', '1.6', '1.6.3', '1.7.0', '1.7.7'];
    importantUpdates.sort(compareVersions);

    var version = '1.7.12', storageVersion = localStorage['donut-version'];
    var storageVersionValid = validateVersion(storageVersion);
    var notify = !storageVersionValid || compareVersions(importantUpdates[importantUpdates.length - 1], storageVersion) == 1;
    if (!notify) localStorage['donut-version'] = version;

    var styles = {
        '.g-recaptcha, .grecaptcha-badge': {
            'display': 'none !important'
        },
        '.bottom-btn': {
            'height': '32px'
        },
        '.donut-features-table': {
            'margin': 'auto',
            'width': 'max-content',
            'border-collapse': 'collapse'
        },
        '.donut-features-table td:nth-child(1)': {
            'text-align': 'left'
        },
        '.donut-features-table th, .donut-features-table td': {
            'position': 'relative',
            'padding': '2px 8px'
        },
        '.donut-features-table input[type="text"]': {
            'box-sizing': 'border-box',
            'width': '104px',
            'height': '22px',
            'text-align': 'center'
        },
        '.donut-features-table select': {
            'position': 'absolute',
            'top': '2px',
            'bottom': '2px',
            'left': '8px',
            'box-sizing': 'border-box',
            'padding': '0',
            'width': '104px',
            'height': '22px',
            'font-size': '0',
            'background': 'transparent',
            'border': 'none',
            'outline': '0'
        },
        '.donut-features-table option': {
            'font-size': '13.3333px'
        },
        '.donut-features-div': {
            'margin': 'auto',
            'padding-top': '4px',
            'width': 'max-content'
        },
        '.donut-checkbox': {
            'position': 'relative',
            'top': '1px',
            'margin': '0 5px'
        },
        '.donut-channel-link': {
            'box-sizing': 'border-box',
            'display': 'block',
            'margin': '3px auto 0',
            'padding': '4px 8px 4px 5.5px',
            'width': '85px',
            'height': '24px',
            'color': '#fefefe',
            'font-family': 'Arial, Helvetica, sans-serif',
            'font-size': '12px',
            'line-height': 'normal',
            'text-align': 'center',
            'text-decoration': 'none',
            'background-color': '#e62117',
            'border': 'solid 1px transparent',
            'border-radius': '2px',
            'white-space': 'nowrap',
            'vertical-align': 'middle',
            'box-shadow': '0 1px 0 rgba(0,0,0,0.05)'
        },
        '.donut-channel-link::before': {
            'content': '""',
            'position': 'relative',
            'top': '-1px',
            'display': 'inline-block',
            'margin-right': '6px',
            'width': '16px',
            'height': '12px',
            'background': 'no-repeat url(//s.ytimg.com/yts/imgbin/www-hitchhiker-vfl-Nn88d.png) -721px -88px',
            'background-size': 'auto',
            'vertical-align': 'middle'
        },
        '.donut-channel-link>span': {
            'display': 'inline-block',
            '-moz-box-sizing': 'border-box',
            'box-sizing': 'border-box'
        },
        '.donut-channel-link:hover': {
            'background-color': '#cc181e'
        },
        '.donut-channel-link:active': {
            'background-color': '#b31217'
        }
    };

    if (notify) {
        styles['.donut-features-btn::before'] = {
            'content': '"!"',
            'position': 'absolute',
            'top': '-4px',
            'right': '-4px',
            'width': '15px',
            'height': '15px',
            'color': 'white',
            'font-family': 'Open Sans',
            'font-size': '12px',
            'text-align': 'center',
            'line-height': '15px',
            'background-color': 'red',
            'border-radius': '50%',
            'overflow': 'hidden',
            '-webkit-user-select': 'none',
            '-moz-user-select': 'none',
            '-ms-user-select': 'none',
            'user-select': 'none',
        };
    }

    function insertRule(selector) {
        var rule = selector + '{';
        for (var property in styles[selector]) rule += property + ':' + styles[selector][property] + ';';
        rule += '}';
        stylesheet.insertRule(rule, stylesheet.cssRules.length);
    }

    var style = document.createElement('style');
    document.head.appendChild(style);
    var stylesheet = style.sheet;
    for (var selector in styles) insertRule(selector);

    var gota = {
        main: document.getElementById('main'),
        left: document.getElementsByClassName('main-bottom-left')[0],
        right: document.getElementsByClassName('main-bottom-right')[0],
        stats: document.getElementsByClassName('main-bottom-stats')[0],
        btnPlay: document.getElementById('btn-play'),
        btnSpec: document.getElementById('btn-spec'),
        scorePanel: document.getElementById('score-panel'),
        partyPanel: document.getElementById('party-panel'),
        leaderboardPanel: document.getElementById('leaderboard-panel'),
        extraPanel: document.getElementById('extra-panel'),
        chatPanel: document.getElementById('chat-panel'),
        chatInput: document.getElementById('chat-input'),
        mainScrimmage: document.getElementById('main-scrimmage'),
        scrimmageModeSelect: document.getElementById('scrimmage-mode-select'),
        btnQueue: document.getElementById('btn-queue'),
        scrimmageBtnLeave: document.getElementById('scrimmage-btn-leave'),
        btnLeaveMatch: document.getElementById('btn-leave-match'),
        playerCells: document.getElementById('playerCells'),
        scoreMouse: document.getElementById('score-mouse'),
        sShowSkins: document.getElementById('sShowSkins'),
        sShowNames: document.getElementById('sShowNames'),
        cShowMass: document.getElementById('cShowMass'),
        cHideFood: document.getElementById('cHideFood'),
        cHideChat: document.getElementById('cHideChat'),
        cHideMinimap: document.getElementById('cHideMinimap'),
        cAutoDecline: document.getElementById('cAutoDecline'),
        cAutoRespawn: document.getElementById('cAutoRespawn')
    };

    gota.stats.style.width = '140px';
    gota.stats.style.height = '143px';

    var leftButton = document.createElement('button');
    leftButton.className = 'gota-btn bottom-btn';
    leftButton.style.margin = '0 3px';
    leftButton.style.float = 'none';
    leftButton.style.setProperty('border-radius', '10px 3px 3px 10px', 'important');

    var rightButton = leftButton.cloneNode();
    rightButton.style.setProperty('border-radius', '3px 10px 10px 3px', 'important');

    var btn = rightButton.cloneNode();
    btn.className += ' donut-features-btn';
    btn.style.position = 'relative';
    btn.style.boxSizing = 'border-box';
    btn.style.margin = '5px 0';
    btn.style.setProperty('border-radius', '3px 10px 10px 3px', 'important');
    btn.style.whiteSpace = 'nowrap';
    btn.style.overflow = 'visible';
    btn.style.float = 'left';
    btn.innerText = 'Features by Donut';
    btn.addEventListener('click', function() {
        blackout.style.opacity = '0';
        blackout.style.display = 'block';
        resize();
        blackout.style.opacity = '1';
        if (notify) {
            localStorage['donut-version'] = version;
            stylesheet.insertRule('.donut-features-btn::before{content:none;}', stylesheet.cssRules.length);
        }
    });
    gota.right.appendChild(btn);

    var blackout = document.createElement('div');
    blackout.className = 'donut-blackout';
    blackout.style.position = 'fixed';
    blackout.style.top = '0';
    blackout.style.right = '0';
    blackout.style.bottom = '0';
    blackout.style.left = '0';
    blackout.style.display = 'none';
    blackout.style.background = 'rgba(0,0,0,.5)';
    blackout.style.overflow = 'auto';
    blackout.style.zIndex = '100';
    document.body.appendChild(blackout);

    var win = document.createElement('div');
    win.style.position = 'relative';
    win.style.left = '50%';
    win.style.padding = '15px';
    win.style.width = 'max-content';
    win.style.color = 'white';
    win.style.fontFamily = 'Arial, Helvetica, sans-serif';
    win.style.fontSize = '16px';
    win.style.lineHeight = '22px';
    win.style.textAlign = 'center';
    win.style.backgroundColor = '#363636';
    win.style.borderRadius = '10px';
    win.innerHTML = `<table class='donut-features-table'><tbody><tr><th>Feature</th><th>Default</th><th>Custom key</th></tr>
<tr><td>Show/hide skins</td><td>K</td><td><input type="text" spellcheck="false" data-donut-feature="skins"></td></tr>
<tr><td>Show/hide names</td><td>N</td><td><input type="text" spellcheck="false" data-donut-feature="names"></td></tr>
<tr><td>Show/hide mass</td><td>M</td><td><input type="text" spellcheck="false" data-donut-feature="mass"></td></tr>
<tr><td>Show/hide food</td><td>F</td><td><input type="text" spellcheck="false" data-donut-feature="food"></td></tr>
<tr><td>Show/hide chat</td><td>H</td><td><input type="text" spellcheck="false" data-donut-feature="chat"></td></tr>
<tr><td colspan="2">Triple split (8x)</td><td><input type="text" spellcheck="false" data-donut-feature="tripleSplit"></td></tr>
<tr><td colspan="2">Hexa split (64x)</td><td><input type="text" spellcheck="false" data-donut-feature="hexaSplit"></td></tr>
<tr><td colspan="2">Show/hide minimap</td><td><input type="text" spellcheck="false" data-donut-feature="minimap"></td></tr>
<tr><td colspan="2">Show/hide score panel</td><td><input type="text" spellcheck="false" data-donut-feature="scorePanel"></td></tr>
<tr><td colspan="2">Show/hide party panel</td><td><input type="text" spellcheck="false" data-donut-feature="partyPanel"></td></tr>
<tr><td colspan="2">Show/hide leaderboard</td><td><input type="text" spellcheck="false" data-donut-feature="leaderboard"></td></tr>
<tr><td colspan="2" style="padding-right:32px;">Toggle <em>Decline party invites</em></td><td><input type="text" spellcheck="false" data-donut-feature="autoDecline"></td></tr>
<tr><td colspan="2">Toggle <em>Auto respawn</em></td><td><input type="text" spellcheck="false" data-donut-feature="autoRespawn"></td></tr>
<tr><th colspan="3">Linesplit</th></tr>
<tr><td>Freeze in the middle</td><td>S</td><td><input type="text" spellcheck="false" data-donut-feature="freeze"></td></tr>
<tr><td colspan="2">Freeze to linesplit up</td><td><input type="text" spellcheck="false" data-donut-feature="freezeUp"></td></tr>
<tr><td colspan="2">Freeze to linesplit down</td><td><input type="text" spellcheck="false" data-donut-feature="freezeDown"></td></tr>
<tr><td colspan="2"><div style="display:inline-block;text-decoration:underline dotted;cursor:help;" title="Horizontal linesplits are not guaranteed to work. Use at your own risk!">Freeze to linesplit left</div></td><td><input type="text" spellcheck="false" data-donut-feature="freezeLeft"></td></tr>
<tr><td colspan="2"><div style="display:inline-block;text-decoration:underline dotted;cursor:help;" title="Horizontal linesplits are not guaranteed to work. Use at your own risk!">Freeze to linesplit right</div></td><td><input type="text" spellcheck="false" data-donut-feature="freezeRight"></td></tr>
<tr><td colspan="2">Diagonal linesplit (8x)</td><td><input type="text" spellcheck="false" data-donut-feature="linesplit"></td></tr>
<tr><td colspan="2">Freeze mode</td><td><input type="text" spellcheck="false" readonly><select id="donut-freezeMode" data-donut-feature="freezeMode"><option value="hold">Hold down</option><option value="toggle">Toggle</option></select></td></tr>
<tr><th colspan="3">Team Scrimmage</th></tr>
<tr><td>Leave a match</td><td>L</td><td><input type="text" spellcheck="false" data-donut-feature="leave"></td></tr>
<tr><td colspan="2">Queue</td><td><input type="text" spellcheck="false" data-donut-feature="queue"></td></tr>
<tr><td colspan="2">Default gamemode</td><td><input type="text" spellcheck="false" readonly><select id="donut-scrimmageMode" data-donut-feature="scrimmageMode"><option value="0">Team 2v2</option><option value="1">Team 2v2 [MegaSplit]</option><option value="2">Team 2v2 [Extreme]</option><option value="3">Duel 1v1</option></select></td></tr></tbody></table>
<div class="donut-features-div">You should only use the <em>Leave a match</em> hotkey<br>when playing with random teammates.</div>
<div class="donut-features-div">If you want to disable a hotkey, type Delete.</div>
<div class="donut-features-div"><label><input type="checkbox" class="donut-checkbox" id="donut-hideScorePanel" data-donut-feature="hideScorePanel">Hide Score Panel</label><br>
<label><input type="checkbox" class="donut-checkbox" id="donut-hidePartyPanel" data-donut-feature="hidePartyPanel">Hide Party Panel</label><br>
<label><input type="checkbox" class="donut-checkbox" id="donut-hideLeaderboard" data-donut-feature="hideLeaderboard">Hide Leaderboard</label><br>
<label><input type="checkbox" class="donut-checkbox" id="donut-scrimmageAutoRespawn" data-donut-feature="scrimmageAutoRespawn">Scrimmage Auto Respawn</label><br>
<label><input type="checkbox" class="donut-checkbox" id="donut-startWithScrimmage" data-donut-feature="startWithScrimmage">Join Scrimmage as I open the game</label><br>
<label><input type="checkbox" class="donut-checkbox" id="donut-leaveExperimental" data-donut-feature="leaveExperimental">Use my <em>Leave a match</em> hotkey to leave<br>my team in the other gamemodes</label></div>
<div class="donut-features-div">You can support the developer by checking out<br>his YouTube channel:</div>
<a href="https://www.youtube.com/channel/UCIpCflcKEN9YgaO9qDahpRg" target="_blank" class="donut-channel-link"><span>YouTube</span></a>`;
    if (storageVersionValid && notify) {
        if (compareVersions(storageVersion, '1.7.0') < 0) win.innerHTML = `<div class="donut-features-div" style="padding:0 0 4px 0;font:italic 18px 'Open Sans';">Check out the new linesplit features below!</div>` + win.innerHTML;
        else if (compareVersions(storageVersion, '1.7.7') < 0) {
            win.innerHTML = `<div style="padding-bottom:12px;border-bottom:1px solid #b3b3b3;margin-bottom:12px;font-family:'Segoe UI', 'Open Sans', sans-serif;">
<div class="donut-features-div" style="padding:0 0 4px 0;">You can now select <strong>Team 2v2 [Extreme]</strong><br>as your default scrimmage gamemode.</div>
<div class="donut-features-div" style="padding:0 0 4px 0;">Your freeze mode (see Linesplit section below)<br>now applies to the <strong>Freeze Mouse</strong> keybind<br>you can set in Gota's Options menu.</div>
</div>` + win.innerHTML;
        }
    }
    blackout.appendChild(win);

    function resize() {
        if (blackout.style.display == 'block') {
            if (window.innerHeight < win.offsetHeight + 20) {
                win.style.top = '0';
                win.style.margin = '10px';
                win.style.transform = 'translate(-50%, 0%)';
            } else {
                win.style.top = '50%';
                win.style.margin = '0';
                win.style.transform = 'translate(-50%, -50%)';
            }
        }
    }
    window.addEventListener('resize', resize);

    function close() {
        blackout.style.display = 'none';
        win.style.display = 'block';
        reportWin.style.display = 'none';
    }

    document.addEventListener('click', function(e) {
        if (blackout.style.display == 'block'
            && e.target != win && !win.contains(e.target)
            && e.target != reportWin && !reportWin.contains(e.target)
        ) close();
    }, true);

    var footer = document.createElement('div');
    footer.style.marginTop = '12px';
    win.appendChild(footer);

    var done = leftButton.cloneNode();
    done.style.setProperty('border-radius', '10px 3px 3px 10px', 'important');
    done.innerText = 'Done';
    done.addEventListener('click', function() {
        blackout.style.display = 'none';
    });
    footer.appendChild(done);

    var reportBtn = rightButton.cloneNode();
    reportBtn.style.setProperty('border-radius', '3px 10px 10px 3px', 'important');
    reportBtn.innerText = 'Report bug';
    reportBtn.addEventListener('click', function() {
        win.style.display = 'none';
        reportWin.style.display = 'block';
    });
    footer.appendChild(reportBtn);

    var reportWin = win.cloneNode();
    reportWin.style.top = '50%';
    reportWin.style.display = 'none';
    reportWin.style.margin = '0';
    reportWin.style.width = '500px';
    reportWin.style.transform = 'translate(-50%, -50%)';
    reportWin.style.fontFamily = '"Segoe UI", "Open Sans", Arial, Helvetica, sans-serif';
    reportWin.innerHTML = `<p style="margin:0.12em 0 0.72em;">You will be redirected to a channel in Gota's official Discord server where you can report issues and bugs you come across in the game, including those caused by use of Gota.io Features by Donut.</p>
<p style="margin:0.72em 0 0;font-size:18px;font-weight:bold;">Please include the following sentence in your report:</p><pre style="margin-top:0.1em;user-select:text;">I am using Features by Donut version ${version}.</pre>`;
    blackout.appendChild(reportWin);

    var reportFooter = footer.cloneNode();
    reportWin.appendChild(reportFooter);

    var reportGoBack = leftButton.cloneNode();
    reportGoBack.innerText = 'Go Back';
    reportGoBack.addEventListener('click', function() {
        reportWin.style.display = 'none';
        win.style.display = 'block';
    });
    reportFooter.appendChild(reportGoBack);

    var reportContinue = rightButton.cloneNode();
    reportContinue.innerText = 'Continue';
    reportContinue.addEventListener('click', function() {
        window.open('https://discord.gg/QJknuQF', '_blank');
    });
    reportFooter.appendChild(reportContinue);

    var processedKeyCodes = {
        Escape: 'Esc',
        Minus: '-',
        Equal: '=',
        BracketLeft: '[',
        BracketRight: ']',
        Control: 'Ctrl',
        Semicolon: ';',
        Quote: "'",
        Backquote: '`',
        Backslash: '\\',
        Comma: ',',
        Period: '.',
        Slash: '/',
        NumpadMultiply: 'Numpad *',
        CapsLock: 'Caps Lock',
        ScrollLock: 'Scroll Lock',
        Numpad7: 'Numpad 7',
        Numpad8: 'Numpad 8',
        Numpad9: 'Numpad 9',
        NumpadSubtract: 'Numpad -',
        Numpad4: 'Numpad 4',
        Numpad5: 'Numpad 5',
        Numpad6: 'Numpad 6',
        NumpadAdd: 'Numpad +',
        Numpad1: 'Numpad 1',
        Numpad2: 'Numpad 2',
        Numpad3: 'Numpad 3',
        Numpad0: 'Numpad 0',
        NumpadDecimal: 'Numpad .',
        NumpadEqual: 'Numpad =',
        NumpadEnter: 'Enter',
        NumpadDivide: 'Numpad /',
        NumLock: 'Num Lock',
        ArrowUp: 'Arrow Up',
        PageUp: 'Page Up',
        ArrowLeft: 'Arrow Left',
        ArrowRight: 'Arrow Right',
        ArrowDown: 'Arrow Down',
        PageDown: 'Page Down',
        Meta: 'Win / \u2318',
        OS: 'Win / \u2318'
    };

    function processKeyCode(code) {
        if (code.indexOf('Arrow') && code.indexOf('Bracket')) code = code.replace(/Key|Digit|Left|Right/, '');
        if (code in processedKeyCodes) return processedKeyCodes[code];
        return code;
    }

    var defaultKeys = {
        skins: 'K',
        names: 'N',
        mass: 'M',
        food: 'F',
        chat: 'H',
        tripleSplit: null,
        hexaSplit: null,
        minimap: null,
        scorePanel: null,
        partyPanel: null,
        leaderboard: null,
        autoDecline: null,
        autoRespawn: null,
        freeze: 'S',
        freezeUp: null,
        freezeDown: null,
        freezeLeft: null,
        freezeRight: null,
        linesplit: null,
        leave: 'L',
        queue: null
    };

    var inputs = document.querySelectorAll('.donut-features-table input[type="text"][data-donut-feature]'), errorKeys = 'Gota.io Features by Donut: An error occurred. We had to reset your custom keys.';
    function fillInputs(keys) {
        for (var i = 0; i < inputs.length; i++) {
            var feature = inputs[i].dataset.donutFeature, key = keys[feature];
            if (typeof key == 'undefined' && feature in defaultKeys) {
                key = keys[feature] = defaultKeys[feature];
                localStorage['donut-keys'] = JSON.stringify(keys);
            }
            if (key === null) continue;
            inputs[i].value = key;
        }
    }

    function tryLocalStorage(index, defaultObj, func, error) {
        var obj; index = 'donut-' + index;
        try {
            if (!localStorage[index]) throw null;
            obj = JSON.parse(localStorage[index]);
            if (func) func(obj);
        } catch (e) {
            obj = JSON.parse(JSON.stringify(defaultObj));
            if (e && error) console.error(error);
            localStorage[index] = JSON.stringify(obj);
            if (func) func(obj);
        }
        return obj;
    }

    var keys = tryLocalStorage('keys', defaultKeys, fillInputs, errorKeys);

    function handleInputKeydown(e) {
        e.preventDefault();
        e.stopPropagation();
        var feature = this.dataset.donutFeature, code = processKeyCode(e.code);
        if (code && code != keys[feature] && code != 'Unidentified') {
            if (code == 'Delete') {
                this.value = '';
                keys[feature] = null;
            } else {
                for (var k in keys) {
                    if (keys[k] == code) {
                        keys[k] = null;
                        for (var l = 0; l < inputs.length; l++) {
                            if (inputs[l].dataset.donutFeature == k) {
                                inputs[l].value = '';
                                break;
                            }
                        }
                    }
                }
                this.value = keys[feature] = code;
            }
            localStorage['donut-keys'] = JSON.stringify(keys);
            this.blur();
        }
    }

    for (var j = 0; j < inputs.length; j++) {
        inputs[j].addEventListener('keydown', handleInputKeydown);
    }

    function selectOption(select) {
        var index = select.selectedIndex;
        select.selectedIndex = (index + (index == 1) + 1) % select.options.length;
        $(select).change();
    }

    function triggerCheckbox(checkbox) {
        $(checkbox).prop('checked', !$(checkbox).prop('checked')).change();
    }

    function triggerDonutCheckbox(checkbox) {
        checkbox.checked = !checkbox.checked;
        checkbox.dispatchEvent(new Event('change'));
    }

    function fadeOutMain() {
        if (gota.main.style.display == 'block') window.onkeydown({keyCode: 27, which: 27});
    }

    var alertWin = win.cloneNode();
    alertWin.style.top = '10px';
    alertWin.style.display = 'none';
    alertWin.style.lineHeight = '20px';
    alertWin.style.backgroundColor = 'rgba(42,42,42,.9)';
    alertWin.style.boxShadow = '0 2px 3px rgba(0,0,0,.25)';
    alertWin.style.transform = 'translate(-50%, 0%)';
    alertWin.style.zIndex = '3';
    document.body.appendChild(alertWin);

    var alertFadeOutTimer = 0;
    function Alert(html, timeout) {
        alertWin.innerHTML = html;
        $(alertWin).fadeIn(500);
        if (alertFadeOutTimer) clearTimeout(alertFadeOutTimer);
        alertFadeOutTimer = setTimeout(function() {
            $(alertWin).fadeOut(500);
            alertFadeOutTimer = 0;
        }, timeout);
    }

    var freezeKeys = {
        freeze: {
            id: 1,
            x: 0.5,
            y: 0.5
        },
        freezeUp: {
            id: 2,
            x: 0.5,
            y: 0
        },
        freezeDown: {
            id: 3,
            x: 0.5,
            y: 1
        },
        freezeLeft: {
            id: 4,
            x: 0,
            y: 0.5
        },
        freezeRight: {
            id: 5,
            x: 1,
            y: 0.5
        }
    };

    function findFreezeKey(code) {
        var found = Object.keys(freezeKeys).find(function(val) {
            return code == keys[val];
        });
        return found ? freezeKeys[found] : undefined;
    }

    var x = 0, y = 0, originalMousemove, freezeState = 0, freezeKeyDown = false;
    setTimeout(function delay() {
        if (window.onmousemove) {
            originalMousemove = window.onmousemove;
            window.onmousemove = function(e) {
                x = e.clientX; y = e.clientY;
                if (freezeState == 0) originalMousemove(e);
            };
        } else {
            setTimeout(delay, 200);
        }
    }, 200);

    function split(times = 1) {
        for (var i = 0; i < times; i++) {
            $(window).trigger($.Event('keydown', {keyCode: 32, which: 32}));
        }
    }

    function isModifier(e, code) {
        return (
            (code == 'Alt' && !e.ctrlKey && !e.metaKey && !e.shiftKey)
            || (code == 'Ctrl' && !e.altKey && !e.metaKey && !e.shiftKey)
            || (code == 'Win / \u2318' && !e.altKey && !e.ctrlKey && !e.shiftKey)
            || (code == 'Shift' && !e.altKey && !e.ctrlKey && !e.metaKey)
       );
    }

    window.addEventListener('keydown', function(e) {
        if (e.code == undefined) return;
        var code = processKeyCode(e.code), modifier = isModifier(e, code);
        if (modifier || !e.altKey && !e.ctrlKey && !e.metaKey) {
            if (!modifier) {
                var key = e.which || e.keyCode || 0;
                if (key == 13 && document.activeElement && document.activeElement.id == 'name-box') team2v2();
                else if (key == 27) {
                    if (!firstEscape) firstEscape = true;
                    if (alertWin.offsetHeight) $(alertWin).fadeOut(500);
                }
            }
            if ((modifier || !e.shiftKey) && document.activeElement.tagName != 'INPUT' && document.activeElement.tagName != 'TEXTAREA') {
                switch (code) {
                    case keys.skins:
                        selectOption(gota.sShowSkins);
                        break;
                    case keys.names:
                        selectOption(gota.sShowNames);
                        break;
                    case keys.mass:
                        triggerCheckbox(gota.cShowMass);
                        break;
                    case keys.food:
                        triggerCheckbox(gota.cHideFood);
                        break;
                    case keys.chat:
                        triggerCheckbox(gota.cHideChat);
                        break;
                    case keys.tripleSplit:
                        split(3);
                        break;
                    case keys.hexaSplit:
                        split(6);
                        break;
                    case keys.minimap:
                        triggerCheckbox(gota.cHideMinimap);
                        break;
                    case keys.scorePanel:
                        triggerDonutCheckbox(checkboxes.hideScorePanel);
                        break;
                    case keys.partyPanel:
                        triggerDonutCheckbox(checkboxes.hidePartyPanel);
                        break;
                    case keys.leaderboard:
                        triggerDonutCheckbox(checkboxes.hideLeaderboard);
                        break;
                    case keys.autoDecline:
                        triggerCheckbox(gota.cAutoDecline);
                        Alert('Auto Decline Party Invites: <strong>' + (gota.cAutoDecline.checked ? 'On' : 'Off') + '</strong>', 2500);
                        break;
                    case keys.autoRespawn:
                        var checkbox = done2v2 ? checkboxes.scrimmageAutoRespawn : gota.cAutoRespawn;
                        if (done2v2) triggerDonutCheckbox(checkbox);
                        else triggerCheckbox(checkbox);
                        Alert('Auto Respawn' + (done2v2 ? ' in Team Scrimmage' : '') + ': <strong>' + (checkbox.checked ? 'On' : 'Off') + '</strong>', 2500);
                        break;
                    case keys.linesplit:
                        split(3);
                        if (gota.main.style.display == 'none') {
                            gota.main.style.visibility = 'hidden';
                            gota.main.style.display = 'block';
                            setTimeout(function() {
                                gota.main.style.display = 'none';
                                gota.main.style.visibility = 'visible';
                            }, 1050);
                        }
                        break;
                    case keys.queue:
                        if (gota.scrimmageBtnLeave.style.display == 'block') $(gota.btnLeaveMatch).click();
                        else if (gota.mainScrimmage.style.display == 'block') $(gota.btnQueue).click();
                        fadeOutMain();
                        break;
                    case keys.leave:
                        if (switches.leaveExperimental || scrimmage) {
                            var hidden = gota.chatPanel.style.display == 'none';
                            if (hidden) {
                                gota.chatPanel.style.opacity = '0';
                                gota.chatPanel.style.display = 'block';
                            }
                            $(gota.chatInput).val('/leave').focus().trigger($.Event('keyup', {keyCode: 13, which: 13})).blur();
                            scrimmage = false;
                            if (hidden) {
                                gota.chatPanel.style.display = 'none';
                                gota.chatPanel.style.opacity = '1';
                            }
                            fadeOutMain();
                        }
                        break;
                    default:
                        if (originalMousemove) {
                            var freezeKey = findFreezeKey(code);
                            if (freezeKey) {
                                if (freezeState != freezeKey.id) {
                                    freezeState = freezeKey.id;
                                    freezeKeyDown = true;
                                    originalMousemove({
                                        clientX: window.innerWidth / 2,
                                        clientY: window.innerHeight / 2
                                    });
                                    setTimeout(function() {
                                        originalMousemove({
                                            clientX: window.innerWidth * freezeKey.x,
                                            clientY: window.innerHeight * freezeKey.y
                                        });
                                    }, 100);
                                } else if (options.freezeMode == 'toggle' && !freezeKeyDown) {
                                    freezeState = 0;
                                    originalMousemove({clientX: x, clientY: y});
                                }
                            }
                        }
                }
            }
        }
    });

    window.addEventListener('keyup', function(e) {
        if (e.code == undefined) return;
        var code = processKeyCode(e.code);
        if ((isModifier(e, code) || !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) && document.activeElement.tagName != 'INPUT' && document.activeElement.tagName != 'TEXTAREA') {
            if (originalMousemove) {
                if (options.freezeMode == 'hold') {
                    var freezeKey = findFreezeKey(code);
                    if (freezeKey && freezeState == freezeKey.id) {
                        freezeState = 0;
                        originalMousemove({clientX: x, clientY: y});
                    }
                } else {
                    freezeKeyDown = false;
                }
            }
        }
    });

    function isMouseFrozen() {
        return gota.scoreMouse.style.display == 'block';
    }

    var originalKeydown;
    setTimeout(function delay() {
        if (window.onkeydown) {
            originalKeydown = window.onkeydown;
            window.onkeydown = function(e) {
                try {
                    if (options.freezeMode == 'toggle' || !isMouseFrozen() || e.which != JSON.parse(localStorage.keybinds).kFreezeMouse) {
                        originalKeydown(e);
                    }
                } catch (err) {
                    originalKeydown(e);
                }
            };

            window.addEventListener('keyup', function(e) {
                 if (options.freezeMode == 'hold' && isMouseFrozen() && e.which == JSON.parse(localStorage.keybinds).kFreezeMouse) {
                     originalKeydown(e);
                 }
            });
        } else {
            setTimeout(delay, 200);
        }
    }, 200);

    var defaultSwitches = {
        hideScorePanel: false,
        hidePartyPanel: false,
        hideLeaderboard: false,
        scrimmageAutoRespawn: true,
        startWithScrimmage: false,
        leaveExperimental: false
    };

    var checkboxes = {};
    for (var feature in defaultSwitches) {
        checkboxes[feature] = document.getElementById('donut-' + feature);
    }

    var errorSwitches = 'Gota.io Features by Donut: An error occurred. We had to reset your settings.';
    function fillCheckboxes(switches) {
        for (var feature in checkboxes) {
            var Switch = switches[feature];
            if (typeof Switch == 'undefined') {
                Switch = switches[feature] = defaultSwitches[feature];
                localStorage['donut-switches'] = JSON.stringify(switches);
            }
            if (typeof Switch == 'boolean') checkboxes[feature].checked = Switch;
            else throw errorSwitches;
        }
    }

    var switches = tryLocalStorage('switches', defaultSwitches, fillCheckboxes, errorSwitches);

    var anchorObd = document.querySelector('.options-table>thead:nth-of-type(6)'), obd = anchorObd.cloneNode(true);
    obd.firstElementChild.firstElementChild.textContent = 'Options by Donut';
    anchorObd.parentNode.insertBefore(obd, anchorObd);

    var tbody = document.createElement('tbody');
    anchorObd.parentNode.insertBefore(tbody, anchorObd);

    function duplicateCheckbox(feature, text) {
        var td = document.createElement('td');
        td.colSpan = '4';
        td.style.position = 'relative';
        td.style.paddingRight = '22px';
        td.style.whiteSpace = 'normal';
        td.innerHTML = text;

        var checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.className = 'checkbox-options';
        checkbox.dataset.donutFeature = feature;
        checkbox.style.position = 'absolute';
        checkbox.style.top = '1px';
        checkbox.style.right = '1px';
        td.appendChild(checkbox);

        var tr = document.createElement('tr');
        tr.appendChild(td);
        tbody.appendChild(tr);

        checkbox.checked = switches[feature];
        return checkbox;
    }

    var duplicates = {};
    for (feature in defaultSwitches) {
        var bro = checkboxes[feature], text = bro.parentNode.innerText;
        if (feature == 'leaveExperimental') text = 'Use my <em>Leave a match</em> hotkey to leave my team in the other gamemodes';
        duplicates[feature] = duplicateCheckbox(feature, text);
    }

    function handleCheckboxChange(e) {
        var feature = this.dataset.donutFeature;
        switches[feature] = this.checked;
        localStorage['donut-switches'] = JSON.stringify(switches);
        checkboxes[feature].checked = duplicates[feature].checked = this.checked;
        switch (feature) {
            case 'hideScorePanel':
                if (this.checked) hideScorePanel();
                else {
                    gota.partyPanel.style.position = 'relative';
                    gota.partyPanel.style.top = '10px';
                    gota.scorePanel.style.opacity = '1';
                }
                break;
            case 'hidePartyPanel':
                visibility(gota.partyPanel, !this.checked);
                break;
            case 'hideLeaderboard':
                visibility(gota.leaderboardPanel, !this.checked);
                visibility(gota.extraPanel, !this.checked);
                break;
            case 'scrimmageAutoRespawn':
                if (this.checked && scrimmage) scrimRespawn();
        }
    }

    for (feature in checkboxes) {
        checkboxes[feature].addEventListener('change', handleCheckboxChange);
        duplicates[feature].addEventListener('change', handleCheckboxChange);
    }

    function hideScorePanel() {
        gota.scorePanel.style.opacity = '0';
        gota.partyPanel.style.position = 'absolute';
        gota.partyPanel.style.top = '0';
    }
    if (switches.hideScorePanel) hideScorePanel();

    function visibility(element, visible) {
        element.style.visibility = visible ? 'visible' : 'hidden';
    }
    if (switches.hideLeaderboard) {
        visibility(gota.leaderboardPanel, false);
        visibility(gota.extraPanel, false);
    }
    if (switches.hidePartyPanel) visibility(gota.partyPanel, false);

    var info = tryLocalStorage('info', {
        scrimmageModeAdjusted: storageVersionValid && compareVersions(storageVersion, '1.7.11') < 0 ? 0 : 1
    });

    function adjustScrimmageMode() {
        info.scrimmageModeAdjusted++;
        localStorage['donut-info'] = JSON.stringify(info);
    }

    var defaultOptions = {
        freezeMode: 'hold',
        scrimmageMode: '2'
    };

    var selects = [document.getElementById('donut-freezeMode'), document.getElementById('donut-scrimmageMode')];
    function fillSelects(options) {
        for (var i = 0; i < selects.length; i++) {
            var feature = selects[i].dataset.donutFeature, option = options[feature];
            var scrimmageModeAdjustRequired = feature == 'scrimmageMode' && info.scrimmageModeAdjusted === 0;
            if (scrimmageModeAdjustRequired) {
                info.scrimmageModeAdjusted++;
                localStorage['donut-info'] = JSON.stringify(info);
            }
            if (typeof option == 'undefined' && feature in defaultOptions) {
                scrimmageModeAdjustRequired = false;
                option = options[feature] = defaultOptions[feature];
                localStorage['donut-options'] = JSON.stringify(options);
            }
            if (scrimmageModeAdjustRequired && storageVersionValid && compareVersions(storageVersion, '1.7.11') < 0 && +option > 0) {
                option = options[feature] = +option - 1 + '';
                localStorage['donut-options'] = JSON.stringify(options);
            }
            var el = selects[i].querySelector('option[value="' + option + '"]');
            if (el) {
                selects[i].selectedIndex = el.index;
                selects[i].previousElementSibling.value = el.textContent;
            } else throw null;
        }
    }

    var options = tryLocalStorage('options', defaultOptions, fillSelects);

    for (var l = 0; l < selects.length; l++) {
        selects[l].addEventListener('change', function(e) {
            var feature = this.dataset.donutFeature;
            options[feature] = this.options[this.selectedIndex].value;
            localStorage['donut-options'] = JSON.stringify(options);
            this.previousElementSibling.value = this.options[this.selectedIndex].textContent;
        });
    }

    var scrimmage = false, respawnCheckInterval = 0, respawnTimer = 0;
    gota.btnQueue.addEventListener('click', function() {
        scrimmage = true;
        if (switches.scrimmageAutoRespawn) scrimRespawn();
    });

    function scrimmageFalse() {
        scrimmage = false;
        if (respawnTimer) {
            clearTimeout(respawnTimer);
            respawnTimer = respawnCheckInterval = 0;
        }
    }
    gota.btnLeaveMatch.addEventListener('click', scrimmageFalse);

    function scrimRespawn() {
        var cells = gota.playerCells, cellsNum = parseInt(cells.innerText, 10);
        if (respawnCheckInterval) clearInterval(respawnCheckInterval);
        respawnCheckInterval = setInterval(function() {
            if (switches.scrimmageAutoRespawn && scrimmage) {
                var temp = parseInt(cells.innerText, 10);
                if (temp != cellsNum) {
                    cellsNum = temp;
                    if (cellsNum === 0) {
                        if (gota.mainScrimmage.style.display == 'none') {
                            respawnTimer = setTimeout(function() {
                                if (switches.scrimmageAutoRespawn && scrimmage) {
                                    $(gota.btnPlay).click();
                                    respawnTimer = 0;
                                    scrimRespawn();
                                }
                            }, 10000);
                        }
                        clearInterval(respawnCheckInterval);
                        respawnCheckInterval = 0;
                    }
                }
            } else {
                clearInterval(respawnCheckInterval);
                respawnCheckInterval = 0;
            }
        }, 500);
    }

    var firstEscape = false;
    function startScrimmage() {
        var region = document.getElementsByClassName('server-active')[0].getAttribute('region');
            var id = region == 'eu' ? 's_Beta' : region == 'na' ? 's_Jet' : 's_Citrus';
            var interval = setInterval(function() {
                if (document.getElementsByClassName('server-selected').length) {
                    clearInterval(interval);
                    var server = document.getElementById(id);
                    if (!server) return;
                    $(server).click();
                    $(gota.btnPlay).click();
                    setTimeout(function() {
                        if (!firstEscape && !done2v2) Alert('It looks like the server is not responding.<br>You may want to press <strong>Esc</strong> to go back to the game menu.', 20000);
                    }, 5000);
                }
            }, 250);
    }
    if (switches.startWithScrimmage) startScrimmage();

    var scrimmageModeSet = false;
    function isTeamScrimmage() {
        var server = document.getElementsByClassName('server-selected')[0];
        return server && server.lastElementChild.textContent == 'Scrimmage';
    }

    var done2v2 = false, checkigTean2v2 = false;
    function team2v2() {
        if (!checkigTean2v2) {
            if (!isTeamScrimmage()) {
                scrimmageFalse();
                done2v2 = false;
            } else if (!done2v2) {
                checkigTean2v2 = true;
                var interval = setInterval(function() {
                    if (gota.mainScrimmage.style.display == 'block') {
                        if (!scrimmageModeSet) {
                            $(gota.scrimmageModeSelect).val(options.scrimmageMode).change();
                            scrimmageModeSet = true;
                        }
                        clearTimeout(timeout);
                        clearInterval(interval);
                        done2v2 = true;
                        checkigTean2v2 = false;
                        if (switches.startWithScrimmage && alertWin.offsetHeight) alertWin.style.display = 'none';
                    }
                }, 100), timeout = setTimeout(function() {
                    clearInterval(interval);
                    checkigTean2v2 = false;
                }, 25000);
            }
        }
    }

    gota.btnPlay.addEventListener('click', team2v2);
    gota.btnSpec.addEventListener('click', team2v2);
})();