Auto perk selector

Automatically selects your perk for you

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Auto perk selector
// @namespace    http://tampermonkey.net/
// @version      2026-03-04
// @description  Automatically selects your perk for you
// @author       particle
// @match        https://gats.io/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gats.io
// @grant        none
// ==/UserScript==

function formatParts(parts) {
    switch(parts[0]) {
        case 'a':
            return {
                'code': parts[0],
                'id': parts[1],
                'class': (parts[2]),
                'color': (parts[3]),
                'x': parts[4],
                'y': parts[5],
                'radius': parts[6],
                'playerAngle': parts[7],
                'armorAmount': parts[8],
                'currentBullets': parts[9],
                'maxBullets': parts[10],
                'armor': parts[11],
                'hp': parts[12],
                'camera': {
                    'width': parts[13],
                    'height': parts[14]
                },
                'hpMax': parts[15],
                'mapWidth': parts[16],
                'mapHeight': parts[17],
                'username': parts[18],
                'invincible': parts[19],
                'isLeader': parts[20],
                'isPremiumMember': parseInt(parts[21]),
                'teamCode': parseInt(parts[22]),
                'isolatedUsername': parts[23]
            };
        case 'b':
            return {
                'code': parts[0],
                'id': parts[1],
                'x': parts[2],
                'y': parts[3],
                'spdX': parts[4],
                'spdY': parts[5],
                'playerAngle': parts[6]
            };
        case 'c':
            return {
                'code': parts[0],
                'id': parts[1],
                'currentBullets': parts[2],
                'shooting': parts[3],
                'reloading': parts[4],
                'hp': parts[5],
                'beingHit': parts[6],
                'armorAmount': parts[7],
                'radius': parts[8],
                'ghillie': parts[9],
                'maxBullets': parts[10],
                'invincible': parts[11],
                'dashing': parts[12],
                'chatBoxOpen': parts[13],
                'isLeader': parts[14],
                'color': (parts[15]),
                'chatMessage': parts[16]
            };
        case 'd':
            return {
                'code': parts[0],
                'id': parts[1],
                'class': (parts[2]),
                'color': (parts[3]),
                'x': parts[4],
                'y': parts[5],
                'radius': parts[6],
                'playerAngle': parts[7],
                'armorAmount': parts[8],
                'hp': parts[9],
                'maxBullets': parts[10],
                'username': parts[11],
                'ghillie': parts[12],
                'invincible': parts[13],
                'isLeader': parts[14],
                'isPremiumMember': parts[15],
                'teamCode': parts[16],
                'chatBoxOpen': parseInt(parts[17])
            };
        case 'e':
            return {
                'code': parts[0],
                'id': parts[1]
            };
        case 'f':
            return {
                'code': parts[0],
                'currentBullets': parts[1],
                'score': parts[2],
                'kills': parts[3],
                'rechargeTimer': parts[4],
                'maxBullets': parts[5],
                'camera': parts[6],
                'thermal': parts[7],
                'numExplosivesLeft': parts[8]
            };
        case 'g':
            return {
                'code': parts[0],
                'id': parts[1],
                'x': parts[2],
                'y': parts[3],
                'length': parts[4],
                'width': parts[5],
                'angle': parts[6],
                'spdX': parts[7],
                'spdY': parts[8],
                'silenced': parts[9],
                'isKnife': parts[10],
                'isShrapnel': parts[11],
                'ownerId': parts[12],
                'teamCode': parts[13]
            };
        case 'h':
            return {
                'code': parts[0],
                'id': parts[1],
                'x': parts[2],
                'y': parts[3]
            };
        case 'i':
            return {
                'code': parts[0],
                'id': parts[1]
            };
        case 'j':
            return {
                'code': parts[0],
                'id': parts[1],
                'type': parts[2],
                'x': parts[3],
                'y': parts[4],
                'angle': parts[5],
                'parentId': parts[6],
                'hp': parts[7],
                'maxHp': parts[8],
                'isPremium': parts[9]
            };
        case 'k':
            return {
                'code': parts[0],
                'id': parts[1],
                'x': parts[2],
                'y': parts[3],
                'angle': parts[4],
                'hp': parts[5]
            };
        case 'l':
            return {
                'code': parts[0],
                'id': parts[1]
            };
        case 'm':
            return {
                'code': parts[0],
                'id': parts[1],
                'type': parts[2],
                'x': parts[3],
                'y': parts[4],
                'spdX': parts[5],
                'spdY': parts[6],
                'travelTime': parts[7],
                'emitting': parts[8],
                'emissionRadius': parts[9],
                'ownerId': parts[10],
                'teamCode': parts[11]
            };
        case 'n':
            return {
                'code': parts[0],
                'id': parts[1],
                'x': parts[2],
                'y': parts[3],
                'exploding': parts[4],
                'emitting': parts[5],
                'emissionRadius': parts[6]
            };
        case 'o':
            return {
                'code': parts[0],
                'id': parts[1]
            };
        case 'p':
            return {
                'code': parts[0],
                'level': parts[1]
            };
        case 'q':
            return {
                'code': parts[0],
                'x': parts[1],
                'y': parts[2]
            };
        case 'r':
            return {
                'code': parts[0],
                'type': parts[1],
                'content': parts[2]
            };
        case 's':
            return {
                'code': parts[0]
            };
        case 't':
            return {
                'code': parts[0]
            };
        case 'u':
            return {
                'code': parts[0]
            };
        case 'sq':
            return {
                'code': parts[0],
                'squareOneTeam': parts[1],
                'squareTwoTeam': parts[2],
                'squareThreeTeam': parts[3],
                'squareFourTeam': parts[4]
            };
        case 'v':
            let leaderboardObject = {
                'code': parts[0],
                'currentPlayers': parts[1],
                'leaderboard': []
            };
            for (let i = 2; i < parts.length; i++) {
                let splitData = parts[i].split(".");
                leaderboardObject.leaderboard.push({
                    'userId':(splitData[0]),
                    'isMember': parseInt(splitData[1]),
                    'score': splitData[2],
                    'kills': splitData[3],
                    'teamCode': splitData[4]
                });
            }
            return leaderboardObject;
        case 'w':
            return {
                'code': parts[0],
                'username': parts[1],
                'rememberCookie': parts[2],
                'isPremiumMember': parseInt(parts[3]),
                'isolatedUsername': parts[4]
            };
        case 'x':
            return {
                'code': parts[0],
                'error': parts[1]
            };
        case 'y':
            return {
                'code': parts[0],
                'username': parts[1],
                'email': parts[2],
                'password': parts[3]
            };
        case 'z':
            return {
                'code': parts[0],
                'status': parts[1]
            };
        case 'sz':
            return {
                'code': parts[0],
                'newSize': parts[1]
            };
        case 'sta':
            return {
                'code': parts[0],
                'score': parseInt(parts[1]),
                'kills': parseInt(parts[2]),
                'time': parseInt(parts[3]),
                'shotsFired': parseInt(parts[4]),
                'shotsHit': parseInt(parts[5]),
                'damageDealt': parseInt(parts[6]),
                'damageReceived': parseInt(parts[7]),
                'distanceCovered': parseInt(parts[8]),
                'shooterUsername': parts[9],
                'shooterIsPremiumMember': parseInt(parts[10]),
                'shooterClass': parts[11],
                'shooterArmor': parts[12],
                'shooterColor': parts[13],
                'shooterKills': parseInt(parts[14]),
                'shooterScore': parseInt(parts[15]),
                'shooterHp': parseInt(parts[16]),
                'shooterArmorAmount': parseInt(parts[17]),
                'shooterLevel1Powerup': parts[18],
                'shooterLevel2Powerup': parts[19],
                'shooterLevel3Powerup': parts[20]
            };
        case 're':
            return {
                'code': parts[0]
            };
        case 'version':
            return {
                'code': parts[0],
                'version': parts[1]
            };
        case 'highScores':
            return {
                'code': parts[0],
                'c39': parts[1]
            };
        case 'c22':
            return {
                'code': parts[0],
                'j50': parts[1]
            };
        case 'full':
            return {
                'code': parts[0]
            };
        default:
            return {
                'code': parts[0],
                'rawParts': parts.slice(1)
            };
    }
}

// [ passive perk, mid perk, passive perk ]
const chosenPerks = [ -1, -1, -1 ];
let firstPerkSelected = false;
let secondPerkSelected = false;
let thirdPerkSelected = false;

function selectPerks(score, perks) {
    if(score >= 100 && !firstPerkSelected) {
        sendUpgrade(perks[0], 1)
        firstPerkSelected = true;
    } else if(score >= 300 && !secondPerkSelected) {
        sendUpgrade(perks[1], 2)
        secondPerkSelected = true;
    } else if(score >= 600 && !thirdPerkSelected) {
        sendUpgrade(perks[2], 3)
        thirdPerkSelected = true;
    }
}

function sendUpgrade(index, level) {
    if(window.currentSocket && index !== -1) window.currentSocket.send(new TextEncoder().encode(`u,${index},${level}`));
}

function createPerkUI() {
    const passivePerks = [
        { name: "None", index: -1 },
        { name: "No Recoil", index: 0 },
        { name: "Binoculars", index: 1 },
        { name: "Thermal", index: 2 },
        { name: "Damage", index: 3 },
        { name: "Large Mag", index: 4 },
        { name: "Accuracy", index: 5 },
        { name: "Silencer", index: 6 },
        { name: "Speed", index: 7 },
        { name: "Range", index: 8 },
        { name: "Kevlar", index: 9 },
    ];

    const midPerks = [
        { name: "None", index: -1 },
        { name: "Shield", index: 10 },
        { name: "Medkit", index: 11 },
        { name: "Grenade", index: 12 },
        { name: "Knife", index: 13 },
        { name: "Camo", index: 14 },
        { name: "Build", index: 15 },
        { name: "Dash", index: 16 },
        { name: "Gas", index: 17 },
        { name: "Mines", index: 18 },
        { name: "Frag", index: 19 }
    ];

    const uiHTML = `
        <div id="perkContainer">
            <div id="perkToggle">Perks ⚙</div>
            <div id="perkMenu" style="display:none;">
                <label>Passive Perk #1:</label>
                <select id="perk1"></select>

                <label>Passive Perk #2:</label>
                <select id="perk2"></select>

                <label>Mid Perk:</label>
                <select id="perk3"></select>
            </div>
        </div>
    `;

    const style = `
        <style>
            #perkContainer {
                position: fixed;
                top: 10px;
                left: 10px;
                font-family: Arial, sans-serif;
                z-index: 999999;
            }

            #perkToggle {
                background: #111;
                color: white;
                padding: 6px 10px;
                cursor: pointer;
                border-radius: 4px;
                font-size: 14px;
            }

            #perkMenu {
                margin-top: 5px;
                background: rgba(20,20,20,0.95);
                padding: 10px;
                border-radius: 6px;
                display: flex;
                flex-direction: column;
                gap: 6px;
                width: 180px;
            }

            #perkMenu select {
                width: 100%;
                padding: 3px;
                background: #222;
                color: white;
                border: 1px solid #444;
                border-radius: 4px;
            }

            #perkMenu label {
                font-size: 12px;
                color: #aaa;
            }
        </style>
    `;

    document.body.insertAdjacentHTML("beforeend", style + uiHTML);

    const toggle = document.getElementById("perkToggle");
    const menu = document.getElementById("perkMenu");

    toggle.onclick = () => {
        menu.style.display = menu.style.display === "none" ? "flex" : "none";
    };

    const perk1Select = document.getElementById("perk1");
    const perk2Select = document.getElementById("perk2");
    const perk3Select = document.getElementById("perk3");

    function populate(select, data) {
        data.forEach(perk => {
            const option = document.createElement("option");
            option.textContent = perk.name;
            option.value = perk.index;
            select.appendChild(option);
        });
    }

    populate(perk1Select, passivePerks);
    populate(perk2Select, passivePerks);
    populate(perk3Select, midPerks);

    function updateChosenPerks() {
        chosenPerks[0] = parseInt(perk1Select.value);
        chosenPerks[1] = parseInt(perk3Select.value);
        chosenPerks[2] = parseInt(perk2Select.value);
    }

    perk1Select.onchange = updateChosenPerks;
    perk2Select.onchange = updateChosenPerks;
    perk3Select.onchange = updateChosenPerks;
}

function init() {
    const oldSend = WebSocket.prototype.send;
    const decoder = new TextDecoder();

    WebSocket.prototype.send = function(data) {
        //save current socket instance for global reference
        window.currentSocket = this;
        //add message listener
        if(!this._messageListener) {
            //make sure we don't create infinite message handlers
            this._messageListener = true;
            this.addEventListener("message", msg => {
                //decode packet
                const message = decoder.decode(msg.data);
                const chunks = message.split("|");
                //format packet
                for(const index in chunks) {
                    const chunk = chunks[index];
                    const parts = chunk.split(",");
                    const code = parts[0];
                    const formattedData = formatParts(parts);
                    //check for first person packet
                    if(code === 'f') {
                        const score = formattedData.score;
                        if(score !== undefined &&
                            !(
                                firstPerkSelected &&
                                secondPerkSelected &&
                                thirdPerkSelected
                            )
                        ) selectPerks(score, chosenPerks);
                    }
                }
            });
        }
        //call original send function
        oldSend.call(this, data);
    };

    createPerkUI();
}

init();