Powerline Server Switcher

Server switcher for powerline

// ==UserScript==
// @name         Powerline Server Switcher
// @author       Rumini - Discord: rumini & zaynbieber
// @description  Server switcher for powerline
// @version      1.0
// @match        *://powerline.io/*
// @icon         https://i.imgur.com/9k4SFr0.png
// @license      MIT
// @grant        none
// @run-at       document-start
// @namespace https://greasyfork.org/users/1356205
// ==/UserScript==

if (window.location.href === 'https://powerline.io/') {
    window.location.href = 'https://powerline.io/maindev.html';
}

(function () {
    'use strict';

    // Configuration
    const CONFIG = {
        regions: ['Europe', 'Asia', 'America'],
        countryCodes: { Europe: 'DE', Asia: 'JP', America: 'US' },
        euCountries: new Set(['AL', 'AD', 'AT', 'BY', 'BE', 'BA', 'BG', 'HR', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IS', 'IE', 'IT', 'LV', 'LI', 'LT', 'LU', 'MT', 'MC', 'NL', 'NO', 'PL', 'PT', 'RO', 'RU', 'SM', 'RS', 'SK', 'SI', 'ES', 'SE', 'CH', 'UA', 'GB', 'VA']),
        asiaCountries: new Set(['AF', 'AM', 'AZ', 'BH', 'BD', 'BT', 'BN', 'KH', 'CN', 'CY', 'GE', 'IN', 'ID', 'IR', 'IQ', 'IL', 'JP', 'JO', 'KZ', 'KW', 'KG', 'LA', 'LB', 'MY', 'MV', 'MN', 'MM', 'NP', 'KP', 'OM', 'PK', 'PS', 'PH', 'QA', 'SA', 'SG', 'KR', 'LK', 'SY', 'TW', 'TJ', 'TH', 'TR', 'TM', 'AE', 'UZ', 'VN', 'YE']),
        updateInterval: 1000,
        hideDelay: 1000,
        switchDelay: 1000,
        tipDuration: 2000,
    };

    // Utility functions
    const util = {
        waitForGame: (callback) => {
            if (typeof network !== 'undefined' && typeof countryCode !== 'undefined') {
                callback();
            } else {
                setTimeout(() => util.waitForGame(callback), 100);
            }
        },
        getRegion: () => {
            if (CONFIG.euCountries.has(countryCode)) return 'Europe';
            if (CONFIG.asiaCountries.has(countryCode)) return 'Asia';
            return 'America';
        },
        createElement: (tag, options = {}) => {
            const element = document.createElement(tag);
            Object.assign(element, options);
            if (options.style) element.style.cssText = options.style;
            return element;
        },
    };

    // Main class
    class ServerSwitcher {
        constructor() {
            this.elements = {};
            this.state = {
                isRegionSelectorOpen: false,
                isRoomCodeInputActive: false,
            };
            this.hideTimeout = null;
        }

        init() {
            this.createUI();
            this.attachEventListeners();
            this.updateServerInfo();
            setInterval(() => this.updateServerInfo(), CONFIG.updateInterval);
        }

        createUI() {
            this.elements = {
                container: util.createElement('div', {
                    id: 'server-info-container',
                    style: `
                        position: absolute;
                        top: 0px;
                        right: 0px;
                        display: flex;
                        align-items: center;
                        z-index: 1000;
                        opacity: 0;
                    `
                }),
                switcherContainer: util.createElement('div', {
                    id: 'region-switcher-container',
                    style: `
                        position: relative;
                        display: flex;
                        align-items: center;
                        margin-right: 10px;
                    `
                }),
                switcherButton: util.createElement('button', {
                    id: 'region-switcher',
                    innerHTML: `
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="30" height="30">
                            <path fill="#00ffff" d="M0 96C0 43 43 0 96 0L384 0l32 0c17.7 0 32 14.3 32 32l0 320c0 17.7-14.3 32-32 32l0 64c17.7 0 32 14.3 32 32s-14.3 32-32 32l-32 0L96 512c-53 0-96-43-96-96L0 96zM64 416c0 17.7 14.3 32 32 32l256 0 0-64L96 384c-17.7 0-32 14.3-32 32zM247.4 283.8c-3.7 3.7-6.2 4.2-7.4 4.2s-3.7-.5-7.4-4.2c-3.8-3.7-8-10-11.8-18.9c-6.2-14.5-10.8-34.3-12.2-56.9l63 0c-1.5 22.6-6 42.4-12.2 56.9c-3.8 8.9-8 15.2-11.8 18.9zm42.7-9.9c7.3-18.3 12-41.1 13.4-65.9l31.1 0c-4.7 27.9-21.4 51.7-44.5 65.9zm0-163.8c23.2 14.2 39.9 38 44.5 65.9l-31.1 0c-1.4-24.7-6.1-47.5-13.4-65.9zM368 192a128 128 0 1 0 -256 0 128 128 0 1 0 256 0zM145.3 208l31.1 0c1.4 24.7 6.1 47.5 13.4 65.9c-23.2-14.2-39.9-38-44.5-65.9zm31.1-32l-31.1 0c4.7-27.9 21.4 51.7 44.5 65.9c-7.3 18.3-12 41.1-13.4 65.9zm56.1-75.8c3.7-3.7 6.2-4.2 7.4-4.2s3.7 .5 7.4 4.2c3.8 3.7 8 10 11.8 18.9c6.2 14.5 10.8 34.3 12.2 56.9l-63 0c1.5-22.6 6-42.4 12.2-56.9c3.8-8.9 8-15.2 11.8-18.9z"/>
                        </svg>
                    `,
                    style: `
                        background: none;
                        border: none;
                        cursor: pointer;
                        padding: 5px;
                        display: flex;
                        align-items: center;
                    `
                }),
                roomCodeInput: util.createElement('input', {
                    type: 'text',
                    placeholder: 'Enter room code',
                    style: `
                        position: absolute;
                        top: 50%;
                        right: 0;
                        transform: translateY(-50%);
                        width: 0;
                        padding: 5px;
                        border: #05ffff solid;
                        border-width: 2px;
                        border-radius: 5px;
                        background-color: #003a3a;
                        color: #05ffff;
                        font-family: 'Arial', sans-serif;
                        font-size: 14px;
                        transition: width 0.3s ease-out, opacity 0.3s ease-out;
                        opacity: 0;
                        outline: none;
                    `
                }),
                infoDiv: util.createElement('div', {
                    id: 'server-info',
                    style: `
                        background-color: #003a3a;
                        border-radius: 5px;
                        padding: 3px 6px;
                        border: #05ffff solid;
                        border-width: 2px;
                        border-radius: 0 0 0 5px;
                        color: #05ffff;
                        font-family: 'Arial Black', sans-serif;
                        font-size: 14px;
                        user-select: none;
                        cursor: pointer;
                    `
                }),
                regionOptions: []
            };

            this.createRegionOptions();

            this.elements.switcherContainer.append(this.elements.switcherButton, this.elements.roomCodeInput);
            this.elements.container.append(this.elements.switcherContainer, this.elements.infoDiv);
            document.body.appendChild(this.elements.container);
        }

        createRegionOptions() {
            CONFIG.regions.forEach((region, index) => {
                const option = util.createElement('div', {
                    textContent: region,
                    style: `
                        position: absolute;
                        right: -70px;
                        top: 50%;
                        transform: translateY(-50%) translateX(0);
                        padding: 5px;
                        cursor: pointer;
                        color: #05ffff;
                        background: rgba(0, 58, 58, 0.7);
                        backdrop-filter: blur(5px);
                        border: 1px solid rgba(5, 255, 255, 0.3);
                        border-radius: 5px;
                        opacity: 0;
                        transition: opacity 0.3s ease-out, transform 0.3s ease-out;
                        z-index: ${1000 - index};
                        width: 70px;
                        text-align: center;
                    `
                });
                option.addEventListener('click', (e) => {
                    e.stopPropagation();
                    this.switchRegion(index);
                });
                this.elements.switcherContainer.appendChild(option);
                this.elements.regionOptions.push(option);
            });
        }

        attachEventListeners() {
            this.elements.infoDiv.addEventListener('click', () => this.copyRoomLink());
            this.elements.switcherContainer.addEventListener('mouseenter', () => this.handleContainerMouseEnter());
            this.elements.switcherContainer.addEventListener('mouseleave', () => this.handleContainerMouseLeave());
            this.elements.switcherButton.addEventListener('click', (e) => this.handleSwitcherButtonClick(e));
            this.elements.roomCodeInput.addEventListener('keydown', (e) => this.handleRoomCodeInputKeydown(e));
            document.addEventListener('click', (e) => this.handleDocumentClick(e));
        }

        updateServerInfo() {
            const roomCode = network.roomID ? `#${network.roomID}` : 'Not in a room';
            const region = util.getRegion();

            if (typeof localPlayer === 'undefined' || localPlayerID === 0) {
                this.elements.infoDiv.innerHTML = `Room: ${roomCode}<br>Region: ${region}`;
                this.elements.container.style.cssText += `
                    transition: opacity ease-in 0.25s;
                    transition-delay: 0.3s;
                    opacity: 1;
                `;
            } else {
                this.elements.container.style.cssText += `
                    transition: none;
                    opacity: 0;
                `;
            }
        }

        switchRegion(index) {
            const newRegion = CONFIG.regions[index];
            const newCC = CONFIG.countryCodes[newRegion];

            countryCode = newCC;
            window.localStorage.wingsCC = newCC;
            window.localStorage.wingsCCTime = Date.now();

            network.disconnect();
            setTimeout(() => network.getServerAndConnect(), CONFIG.switchDelay);

            hud.showTip(`Switched to region: ${newRegion}`, CONFIG.tipDuration);
        }

        toggleRegions() {
            this.state.isRegionSelectorOpen = !this.state.isRegionSelectorOpen;
            this.state.isRegionSelectorOpen ? this.showRegions() : this.hideRegions();
        }

        showRegions() {
            clearTimeout(this.hideTimeout);
            this.elements.regionOptions.forEach((option, index) => {
                option.style.cssText += `
                    opacity: 1;
                    transform: translateY(-50%) translateX(${-120 - index * 90}px);
                `;
            });
        }

        hideRegions() {
            this.elements.regionOptions.forEach((option) => {
                option.style.cssText += `
                    opacity: 0;
                    transform: translateY(-50%) translateX(-80%);
                `;
            });
        }

        showRoomCodeInput() {
            this.state.isRoomCodeInputActive = true;
            this.elements.roomCodeInput.style.cssText += `
                width: 120px;
                opacity: 1;
                pointer-events: auto;
            `;
            this.elements.switcherButton.style.cssText += `
                opacity: 0;
                pointer-events: none;
            `;
            this.elements.roomCodeInput.focus();
            this.hideRegions();
        }

        hideRoomCodeInput() {
            this.state.isRoomCodeInputActive = false;
            this.elements.roomCodeInput.style.cssText += `
                width: 0;
                opacity: 0;
                pointer-events: none;
            `;
            this.elements.switcherButton.style.cssText += `
                opacity: 1;
                pointer-events: auto;
            `;
            this.showRegions();
        }

        connectToRoom(roomCode) {
            if (roomCode) {
                network.disconnect();
                setTimeout(() => {
                    window.location.hash = roomCode;
                    network.getServerAndConnect();
                }, CONFIG.switchDelay);
                hud.showTip(`Connecting to room: ${roomCode}`, CONFIG.tipDuration);
                this.updateServerInfo();
            }
        }

        copyRoomLink() {
            const roomCode = network.roomID ? `#${network.roomID}` : '';
            const url = `http://powerline.io/${roomCode}`;
            navigator.clipboard.writeText(url)
                .then(() => console.log('Copied to clipboard:', url))
                .catch(err => console.error('Failed to copy to clipboard:', err));
        }

        handleContainerMouseEnter() {
            clearTimeout(this.hideTimeout);
            if (!this.state.isRegionSelectorOpen && !this.state.isRoomCodeInputActive) {
                this.showRegions();
            }
        }

        handleContainerMouseLeave() {
            if (!this.state.isRegionSelectorOpen && !this.state.isRoomCodeInputActive) {
                this.hideTimeout = setTimeout(() => this.hideRegions(), CONFIG.hideDelay);
            }
        }

        handleSwitcherButtonClick(e) {
            e.stopPropagation();
            this.state.isRoomCodeInputActive ? this.hideRoomCodeInput() : this.showRoomCodeInput();
        }

        handleRoomCodeInputKeydown(e) {
            if (e.key === 'Enter') {
                this.connectToRoom(this.elements.roomCodeInput.value);
                this.hideRoomCodeInput();
            } else if (e.key === 'Escape') {
                this.hideRoomCodeInput();
            }
        }

        handleDocumentClick(e) {
            if (!this.elements.switcherContainer.contains(e.target)) {
                if (this.state.isRegionSelectorOpen) {
                    this.toggleRegions();
                }
                if (this.state.isRoomCodeInputActive) {
                    this.hideRoomCodeInput();
                }
            }
        }
    }

    // Initialize the ServerSwitcher when the game is ready
    util.waitForGame(() => new ServerSwitcher().init());
})();