Greasy Fork 支持简体中文。

Better HAXBALL

cool stuff, "was before HAXBALL GEO CHANGER"

// ==UserScript==
// @name         Better HAXBALL
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  cool stuff, "was before HAXBALL GEO CHANGER"
// @author       lovelysaske (discord)
// @match        https://www.haxball.com/play
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const countryCodes = {
        'us': 'United States',
        'pe': 'Peru',
        'fr': 'France',
        'ad': 'Andorra',
        'ae': 'United Arab Emirates',
        'af': 'Afghanistan',
        'ag': 'Antigua and Barbuda',
        'ai': 'Anguilla',
        'al': 'Albania',
        'am': 'Armenia',
        'ao': 'Angola',
        'aq': 'Antarctica',
        'ar': 'Argentina',
        'as': 'American Samoa',
        'at': 'Austria',
        'au': 'Australia',
        'aw': 'Aruba',
        'ax': 'Åland Islands',
        'az': 'Azerbaijan',
        'ba': 'Bosnia and Herzegovina',
        'bb': 'Barbados',
        'bd': 'Bangladesh',
        'be': 'Belgium',
        'bf': 'Burkina Faso',
        'bg': 'Bulgaria',
        'bh': 'Bahrain',
        'bi': 'Burundi',
        'bj': 'Benin',
        'bl': 'Saint Barthélemy',
        'bm': 'Bermuda',
        'bn': 'Brunei Darussalam',
        'bo': 'Bolivia',
        'bq': 'Bonaire, Sint Eustatius and Saba',
        'br': 'Brazil',
        'bs': 'Bahamas',
        'bt': 'Bhutan',
        'bv': 'Bouvet Island',
        'bw': 'Botswana',
        'by': 'Belarus',
        'bz': 'Belize',
        'ca': 'Canada',
        'cc': 'Cocos (Keeling) Islands',
        'cd': 'Congo, The Democratic Republic of the',
        'cf': 'Central African Republic',
        'cg': 'Congo',
        'ch': 'Switzerland',
        'ci': 'Côte D\'Ivoire',
        'ck': 'Cook Islands',
        'cl': 'Chile',
        'cm': 'Cameroon',
        'cn': 'China',
        'co': 'Colombia',
        'cr': 'Costa Rica',
        'cu': 'Cuba',
        'cv': 'Cabo Verde',
        'cw': 'Curaçao',
        'cx': 'Christmas Island',
        'cy': 'Cyprus',
        'cz': 'Czech Republic',
        'de': 'Germany',
        'dj': 'Djibouti',
        'dk': 'Denmark',
        'dm': 'Dominica',
        'do': 'Dominican Republic',
        'dz': 'Algeria',
        'ec': 'Ecuador',
        'ee': 'Estonia',
        'eg': 'Egypt',
        'eh': 'Western Sahara',
        'er': 'Eritrea',
        'es': 'Spain',
        'et': 'Ethiopia',
        'fi': 'Finland',
        'fj': 'Fiji',
        'fk': 'Falkland Islands (Malvinas)',
        'fm': 'Micronesia, Federated States of',
        'fo': 'Faroe Islands',
        'fr': 'France',
        'ga': 'Gabon',
        'gb': 'United Kingdom',
        'gd': 'Grenada',
        'ge': 'Georgia',
        'gf': 'French Guiana',
        'gg': 'Guernsey',
        'gh': 'Ghana',
        'gi': 'Gibraltar',
        'gl': 'Greenland',
        'gm': 'Gambia',
        'gn': 'Guinea',
        'gp': 'Guadeloupe',
        'gq': 'Equatorial Guinea',
        'gr': 'Greece',
        'gs': 'South Georgia and the South Sandwich Islands',
        'gt': 'Guatemala',
        'gu': 'Guam',
        'gw': 'Guinea-Bissau',
        'gy': 'Guyana',
        'hk': 'Hong Kong',
        'hm': 'Heard Island and McDonald Islands',
        'hn': 'Honduras',
        'hr': 'Croatia',
        'ht': 'Haiti',
        'hu': 'Hungary',
        'id': 'Indonesia',
        'ie': 'Ireland',
        'il': 'Israel',
        'im': 'Isle of Man',
        'in': 'India',
        'io': 'British Indian Ocean Territory',
        'iq': 'Iraq',
        'ir': 'Iran, Islamic Republic of',
        'is': 'Iceland',
        'it': 'Italy',
        'je': 'Jersey',
        'jm': 'Jamaica',
        'jo': 'Jordan',
        'jp': 'Japan',
        'ke': 'Kenya',
        'kg': 'Kyrgyzstan',
        'kh': 'Cambodia',
        'ki': 'Kiribati',
        'km': 'Comoros',
        'kn': 'Saint Kitts and Nevis',
        'kp': 'Korea, Democratic People\'s Republic of',
        'kr': 'Korea, Republic of',
        'kw': 'Kuwait',
        'ky': 'Cayman Islands',
        'kz': 'Kazakhstan',
        'la': 'Lao People\'s Democratic Republic',
        'lb': 'Lebanon',
        'lc': 'Saint Lucia',
        'li': 'Liechtenstein',
        'lk': 'Sri Lanka',
        'lr': 'Liberia',
        'ls': 'Lesotho',
        'lt': 'Lithuania',
        'lu': 'Luxembourg',
        'lv': 'Latvia',
        'ly': 'Libya',
        'ma': 'Morocco',
        'mc': 'Monaco',
        'md': 'Moldova, Republic of',
        'me': 'Montenegro',
        'mf': 'Saint Martin (French part)',
        'mg': 'Madagascar',
        'mh': 'Marshall Islands',
        'mk': 'North Macedonia',
        'ml': 'Mali',
        'mm': 'Myanmar',
        'mn': 'Mongolia',
        'mo': 'Macao',
        'mp': 'Northern Mariana Islands',
        'mq': 'Martinique',
        'mr': 'Mauritania',
        'ms': 'Montserrat',
        'mt': 'Malta',
        'mu': 'Mauritius',
        'mv': 'Maldives',
        'mw': 'Malawi',
        'mx': 'Mexico',
        'my': 'Malaysia',
        'mz': 'Mozambique',
        'na': 'Namibia',
        'nc': 'New Caledonia',
        'ne': 'Niger',
        'nf': 'Norfolk Island',
        'ng': 'Nigeria',
        'ni': 'Nicaragua',
        'nl': 'Netherlands',
        'no': 'Norway',
        'np': 'Nepal',
        'nr': 'Nauru',
        'nu': 'Niue',
        'nz': 'New Zealand',
        'om': 'Oman',
        'pa': 'Panama',
        'pe': 'Peru',
        'pf': 'French Polynesia',
        'pg': 'Papua New Guinea',
        'ph': 'Philippines',
        'pk': 'Pakistan',
        'pl': 'Poland',
        'pm': 'Saint Pierre and Miquelon',
        'pn': 'Pitcairn',
        'pr': 'Puerto Rico',
        'ps': 'Palestine, State of',
        'pt': 'Portugal',
        'pw': 'Palau',
        'py': 'Paraguay',
        'qa': 'Qatar',
        're': 'Réunion',
        'ro': 'Romania',
        'rs': 'Serbia',
        'ru': 'Russian Federation',
        'rw': 'Rwanda',
        'sa': 'Saudi Arabia',
        'sb': 'Solomon Islands',
        'sc': 'Seychelles',
        'sd': 'Sudan',
        'se': 'Sweden',
        'sg': 'Singapore',
        'sh': 'Saint Helena, Ascension and Tristan da Cunha',
        'si': 'Slovenia',
        'sj': 'Svalbard and Jan Mayen',
        'sk': 'Slovakia',
        'sl': 'Sierra Leone',
        'sm': 'San Marino',
        'sn': 'Senegal',
        'so': 'Somalia',
        'sr': 'Suriname',
        'ss': 'South Sudan',
        'st': 'Sao Tome and Principe',
        'sv': 'El Salvador',
        'sx': 'Sint Maarten (Dutch part)',
        'sy': 'Syrian Arab Republic',
        'sz': 'Eswatini',
        'tc': 'Turks and Caicos Islands',
        'td': 'Chad',
        'tf': 'French Southern Territories',
        'tg': 'Togo',
        'th': 'Thailand',
        'tj': 'Tajikistan',
        'tk': 'Tokelau',
        'tl': 'Timor-Leste',
        'tm': 'Turkmenistan',
        'tn': 'Tunisia',
        'to': 'Tonga',
        'tr': 'Turkey',
        'tt': 'Trinidad and Tobago',
        'tv': 'Tuvalu',
        'tw': 'Taiwan, Province of China',
        'tz': 'Tanzania, United Republic of',
        'ua': 'Ukraine',
        'ug': 'Uganda',
        'um': 'United States Minor Outlying Islands',
        'us': 'United States',
        'uy': 'Uruguay',
        'uz': 'Uzbekistan',
        'va': 'Holy See (Vatican City State)',
        'vc': 'Saint Vincent and the Grenadines',
        've': 'Venezuela, Bolivarian Republic of',
        'vg': 'Virgin Islands, British',
        'vi': 'Virgin Islands, U.S.',
        'vn': 'Viet Nam',
        'vu': 'Vanuatu',
        'wf': 'Wallis and Futuna',
        'ws': 'Samoa',
        'xk': 'Kosovo',
        'ye': 'Yemen',
        'yt': 'Mayotte',
        'za': 'South Africa',
        'zm': 'Zambia',
        'zw': 'Zimbabwe'
    };

    const fluentStyles = `
        :root {
            --background-color: rgba(32, 32, 32, 0.9);
            --accent-color: #0078D4;
            --text-color: #FFFFFF;
            --border-radius: 4px;
        }

        .fluent-ui {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: var(--background-color);
            color: var(--text-color);
            border-radius: var(--border-radius);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            backdrop-filter: blur(10px);
            transition: all 0.3s ease;
        }

        .fluent-button {
            background-color: var(--accent-color);
            color: var(--text-color);
            border: none;
            padding: 8px 16px;
            border-radius: var(--border-radius);
            cursor: pointer;
            transition: all 0.2s ease;
        }

        .fluent-button:hover {
            background-color: #106EBE;
        }

        .fluent-input {
            background-color: rgba(255, 255, 255, 0.1);
            border: none;
            border-bottom: 2px solid transparent;
            color: var(--text-color);
            padding: 8px;
            width: 100%;
            transition: all 0.2s ease;
            box-sizing: border-box;
        }

        .fluent-input:focus {
            outline: none;
            background-color: rgba(255, 255, 255, 0.2);
            border-bottom-color: var(--accent-color);
        }

        .fluent-toggle {
            position: relative;
            display: inline-block;
            width: 60px;
            height: 34px;
        }

        .fluent-toggle input {
            opacity: 0;
            width: 0;
            height: 0;
        }

        .fluent-toggle-slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #ccc;
            transition: .4s;
            border-radius: 34px;
        }

        .fluent-toggle-slider:before {
            position: absolute;
            content: "";
            height: 26px;
            width: 26px;
            left: 4px;
            bottom: 4px;
            background-color: white;
            transition: .4s;
            border-radius: 50%;
        }

        .fluent-toggle input:checked + .fluent-toggle-slider {
            background-color: var(--accent-color);
        }

        .fluent-toggle input:checked + .fluent-toggle-slider:before {
            transform: translateX(26px);
        }
    `;

    const styleElement = document.createElement('style');
    styleElement.textContent = fluentStyles;
    document.head.appendChild(styleElement);

    const gui = document.createElement('div');
    gui.className = 'fluent-ui';
    gui.style = `
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        padding: 20px;
        width: 300px;
        display: none;
        opacity: 0;
        transition: opacity 0.3s ease, transform 0.3s ease;
    `;

    let isDragging = false;
    let previousMousePosition = { x: 0, y: 0 };

    gui.addEventListener('mousedown', function(event) {
        isDragging = true;
        previousMousePosition = { x: event.clientX, y: event.clientY };
    });

    window.addEventListener('mousemove', function(event) {
        if (isDragging) {
            const dx = event.clientX - previousMousePosition.x;
            const dy = event.clientY - previousMousePosition.y;
            gui.style.left = `${gui.offsetLeft + dx}px`;
            gui.style.top = `${gui.offsetTop + dy}px`;
            previousMousePosition = { x: event.clientX, y: event.clientY };
        }
    });

    window.addEventListener('mouseup', function() {
        isDragging = false;
    });

    const title = document.createElement('h2');
    title.textContent = 'Better HAX';
    title.style.marginBottom = '15px';
    title.style.fontSize = '18px';
    gui.appendChild(title);

    const geoSection = document.createElement('div');
    geoSection.style.marginBottom = '20px';

    const textBox = document.createElement('input');
    textBox.className = 'fluent-input';
    textBox.setAttribute('type', 'text');
    textBox.setAttribute('placeholder', 'Enter GEO code');
    textBox.style.marginBottom = '10px';
    textBox.style.fontSize = '14px';
    textBox.addEventListener('input', function() {
        textBox.value = textBox.value.toLowerCase();
        if (textBox.value) {
            textBox.style.borderBottomColor = 'var(--accent-color)';
        } else {
            textBox.style.borderBottomColor = 'transparent';
        }
    });
    geoSection.appendChild(textBox);

    const changeButton = document.createElement('button');
    changeButton.className = 'fluent-button';
    changeButton.textContent = 'Apply';
    changeButton.style.width = '100%';
    changeButton.style.marginBottom = '10px';

    changeButton.onclick = () => {
        const previousGeo = JSON.parse(localStorage.getItem('geo')) || {};
        const geo = { lat: previousGeo.lat, lon: previousGeo.lon, code: textBox.value };
        localStorage.setItem('geo', JSON.stringify(geo));
        selectedCountry.textContent = countryCodes[textBox.value]
            ? `GEO Value: ${countryCodes[textBox.value]}`
            : 'GEO Value: Country not found';
        selectedCountry.style.opacity = '0';
        setTimeout(() => {
            selectedCountry.style.opacity = '1';
        }, 50);
    };

    geoSection.appendChild(changeButton);

    const selectedCountry = document.createElement('div');
    selectedCountry.style.fontWeight = 'bold';
    selectedCountry.style.textAlign = 'center';
    selectedCountry.textContent = 'No country selected.';
    selectedCountry.style.transition = 'opacity 0.3s ease';
    selectedCountry.style.fontSize = '14px';
    geoSection.appendChild(selectedCountry);

    gui.appendChild(geoSection);

    const fastLeaveSection = document.createElement('div');
fastLeaveSection.style.marginBottom = '20px';
fastLeaveSection.style.display = 'flex';
fastLeaveSection.style.justifyContent = 'space-between';
fastLeaveSection.style.alignItems = 'center';

const fastLeaveLabel = document.createElement('span');
fastLeaveLabel.textContent = 'Fast Leave "TESTING"';
fastLeaveLabel.style.fontSize = '14px';
fastLeaveSection.appendChild(fastLeaveLabel);

const fastLeaveToggle = document.createElement('label');
fastLeaveToggle.className = 'fluent-toggle';
const fastLeaveToggleInput = document.createElement('input');
fastLeaveToggleInput.type = 'checkbox';
const fastLeaveToggleSlider = document.createElement('span');
fastLeaveToggleSlider.className = 'fluent-toggle-slider';
fastLeaveToggle.appendChild(fastLeaveToggleInput);
fastLeaveToggle.appendChild(fastLeaveToggleSlider);
fastLeaveSection.appendChild(fastLeaveToggle);

gui.appendChild(fastLeaveSection);

const fastLeaveKeybindSection = document.createElement('div');
fastLeaveKeybindSection.style.marginBottom = '20px';

const fastLeaveKeybindLabel = document.createElement('label');
fastLeaveKeybindLabel.textContent = 'Fast Leave Keybind:';
fastLeaveKeybindLabel.style.display = 'block';
fastLeaveKeybindLabel.style.marginBottom = '5px';
fastLeaveKeybindLabel.style.fontSize = '14px';
fastLeaveKeybindSection.appendChild(fastLeaveKeybindLabel);

const fastLeaveKeybindInput = document.createElement('input');
fastLeaveKeybindInput.className = 'fluent-input';
fastLeaveKeybindInput.setAttribute('type', 'text');
fastLeaveKeybindInput.setAttribute('maxlength', '1');
fastLeaveKeybindInput.value = 'L';
fastLeaveKeybindInput.style.fontSize = '14px';
fastLeaveKeybindInput.addEventListener('input', function() {
    this.value = this.value.toUpperCase();
});
fastLeaveKeybindSection.appendChild(fastLeaveKeybindInput);

gui.appendChild(fastLeaveKeybindSection);

function clickButtonByXPath(xpath) {
    const button = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    if (button) button.click();
}

let fastLeaveEnabled = false;
fastLeaveToggleInput.addEventListener('change', function() {
    fastLeaveEnabled = this.checked;
});

document.addEventListener('keydown', function(event) {
    if (fastLeaveEnabled) {
        const currentKeybind = fastLeaveKeybindInput.value.toUpperCase();
        if (event.key.toUpperCase() === currentKeybind) {
            event.preventDefault();
            clickButtonByXPath("/html/body/div/div/div[4]/button[1]");
            clickButtonByXPath("/html/body/div/div/div[2]/div/div/div[1]/button[3]");
            clickButtonByXPath("/html/body/div/div/div[5]/div/div/button[2]");
        }
    }
});

    const avatarSection = document.createElement('div');
    avatarSection.style.marginBottom = '20px';
    avatarSection.style.display = 'flex';
    avatarSection.style.justifyContent = 'space-between';
    avatarSection.style.alignItems = 'center';

    const avatarLabel = document.createElement('span');
    avatarLabel.textContent = 'Avatar Changing (not working rn)';
    avatarLabel.style.fontSize = '14px';
    avatarSection.appendChild(avatarLabel);

    const avatarToggle = document.createElement('label');
    avatarToggle.className = 'fluent-toggle';
    const toggleInput = document.createElement('input');
    toggleInput.type = 'checkbox';
    const toggleSlider = document.createElement('span');
    toggleSlider.className = 'fluent-toggle-slider';
    avatarToggle.appendChild(toggleInput);
    avatarToggle.appendChild(toggleSlider);
    avatarSection.appendChild(avatarToggle);

    gui.appendChild(avatarSection);

    // Emojis section
    const emojiSection = document.createElement('div');
    emojiSection.style.marginBottom = '20px';

    const emojiLabel = document.createElement('label');
    emojiLabel.textContent = 'Emojis:';
    emojiLabel.style.display = 'block';
    emojiLabel.style.marginBottom = '5px';
    emojiLabel.style.fontSize = '14px';
    emojiSection.appendChild(emojiLabel);

    const emojiContainer = document.createElement('div');
    emojiContainer.id = 'emoji-container';
    emojiSection.appendChild(emojiContainer);

    const addEmojiButton = document.createElement('button');
    addEmojiButton.className = 'fluent-button';
    addEmojiButton.textContent = 'Add Emoji Set';
    addEmojiButton.style.width = '100%';
    addEmojiButton.style.marginTop = '10px';
    addEmojiButton.onclick = addEmojiInput;
    emojiSection.appendChild(addEmojiButton);

    gui.appendChild(emojiSection);

    let avatarLoop = null;
    let emojiSets = [['😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾']];

    function sendMessage(input, message) {
        input.value = message;
        input.dispatchEvent(new Event('input', { bubbles: true }));
        const keydown = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter' });
        const keyup = new KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: 'Enter' });
        input.dispatchEvent(keydown);
        input.dispatchEvent(keyup);
    }

    function findInputElement() {
        return new Promise((resolve) => {
            const checkForInput = () => {
                const input = document.querySelector('.input');
                if (input) {
                    resolve(input);
                } else {
                    setTimeout(checkForInput, 1000);
                }
            };
            checkForInput();
        });
    }

    toggleInput.addEventListener('change', function() {
        if (this.checked) {
            findInputElement().then(input => {
                avatarLoop = setInterval(() => {
                    if (input) {
                        const randomSet = emojiSets[Math.floor(Math.random() * emojiSets.length)];
                        const randomEmoji = randomSet[Math.floor(Math.random() * randomSet.length)];
                        sendMessage(input, `/avatar ${randomEmoji}`);
                    }
                }, 1000);
            });
        } else {
            clearInterval(avatarLoop);
            avatarLoop = null;
        }
    });

    function addEmojiInput() {
        const inputContainer = document.createElement('div');
        inputContainer.style.position = 'relative';
        inputContainer.style.marginBottom = '10px';

        const emojiInput = document.createElement('input');
        emojiInput.className = 'fluent-input';
        emojiInput.setAttribute('type', 'text');
        emojiInput.setAttribute('placeholder', 'Enter 2 emojis/chars (e.g., 😀😃)');
        emojiInput.setAttribute('maxlength', '2');
        emojiInput.style.fontSize = '14px';
        emojiInput.style.paddingRight = '30px'; // Make room for the remove button
        emojiInput.addEventListener('input', function() {
            this.value = [...this.value].slice(0, 2).join('');
            const index = Array.from(this.parentNode.parentNode.children).indexOf(this.parentNode);
            emojiSets[index] = [...this.value];
        });

        const removeButton = document.createElement('button');
        removeButton.className = 'fluent-button';
        removeButton.innerHTML = '×'; // Use × symbol
        removeButton.style.position = 'absolute';
        removeButton.style.right = '-10px';
        removeButton.style.top = '-10px';
        removeButton.style.width = '20px';
        removeButton.style.height = '20px';
        removeButton.style.borderRadius = '50%';
        removeButton.style.padding = '0';
        removeButton.style.display = 'flex';
        removeButton.style.justifyContent = 'center';
        removeButton.style.alignItems = 'center';
        removeButton.style.fontSize = '16px';
        removeButton.style.lineHeight = '1';
        removeButton.style.cursor = 'pointer';
        removeButton.onclick = function() {
            const index = Array.from(this.parentNode.parentNode.children).indexOf(this.parentNode);
            emojiSets.splice(index, 1);
            this.parentNode.remove();
        };

        inputContainer.appendChild(emojiInput);
        inputContainer.appendChild(removeButton);
        document.getElementById('emoji-container').appendChild(inputContainer);
        emojiSets.push([]);
    }

    const openButton = document.createElement('button');
    openButton.className = 'fluent-button';
    openButton.textContent = 'Mod';
    openButton.style = `
        position: fixed;
        bottom: 20px;
        right: 20px;
        padding: 10px 20px;
        font-size: 16px;
        border-radius: 17px;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
        transition: all 0.3s ease;
    `;

    openButton.onmouseover = () => {
        openButton.style.transform = 'scale(1.05)';
    };

    openButton.onmouseout = () => {
        openButton.style.transform = 'scale(1)';
    };

    openButton.onclick = () => {
        if (gui.style.display === 'none') {
            gui.style.display = 'block';
            setTimeout(() => {
                gui.style.opacity = '1';
                gui.style.transform = 'translate(-50%, -50%) scale(1)';
            }, 50);
            openButton.textContent = 'CLOSE';
            let geo = localStorage.getItem('geo');
            if (geo) {
                geo = JSON.parse(geo);
                if (countryCodes[geo.code]) {
                    selectedCountry.textContent = `GEO Value: ${countryCodes[geo.code]}`;
                } else {
                    selectedCountry.textContent = 'GEO Value: Country not found';
                }
            }
        } else {
            gui.style.opacity = '0';
            gui.style.transform = 'translate(-50%, -50%) scale(0.95)';
            setTimeout(() => {
                gui.style.display = 'none';
            }, 300);
            openButton.textContent = 'Mod';
        }
    };

    document.body.appendChild(gui);
    document.body.appendChild(openButton);
})();