Greasy Fork is available in English.

Splo Mod

Autobreak-trap, Autoheal, Hat macros, Anti-trap, AutoPush & more!

// ==UserScript==
// @name         Splo Mod
// @description  Autobreak-trap, Autoheal, Hat macros, Anti-trap, AutoPush & more!
// @version      2.8
// @author       Wealthy#8266 & Nuro#9999
// @match        *://sploop.io/*
// @run-at       document-start
// @require      http://code.jquery.com/jquery-3.3.1.min.js
// @require      https://code.jquery.com/ui/1.12.0/jquery-ui.min.js
// @grant        none
// @namespace https://greasyfork.org/users/761829
// ==/UserScript==

let version = "2.8"

let addHealMS = 0
let Game;
let Entity = new Array();
let Canvas;
let ctx;
let keyDown = [];
let user = {};
let tribe = [];
let enemy;
let encoder = new TextEncoder();
let decoder = new TextDecoder();
let server;

let Config = {
    update: (type) => {
        Config[type] += 1;
        setTimeout(() => (Config[type] -= 1), 1e3);
    },
    serverUpdate: 1e3 / 9,
    breaking: false,
    pushing: false,
    rate: 1e3,
    pps: 0,
    weapon: 0,
    cps: 0,
    tps: 0,
    fps: 0,
    ping: 0,
    freeze: {
        send: true,
        pps: true,
        message: false,
        setup: false
    },
    angle: 0,
    move: 0,
    messages: new Array([], []),
    counter: 0,
    resolver: function(){},
    last: Date.now(),
    isJson: (data) => {
        try {
            JSON.parse(data);
        } catch (e) {
            return false;
        }
        return true;

    },
    WS: null
};

Config.tick = () => {
    return new Promise((e) => (Config.resolver = e))
};

let Toggle = {
    UI: false,
    autoBreak: true,
    autoPush: true,
    autoPlace: true,
    autoSync: true
}

class Macro {
    constructor(advanced, spike, trap, mill, food){
        this.advanced = advanced;
        this.spike = spike;
        this.trap = trap;
        this.mill = mill;
        this.food = food;
    };

    update(){
        if(keyDown[this.spike]) Sploop.newPlace(4);
        if(keyDown[this.trap]) Sploop.newPlace(7);
        if(keyDown[this.mill]) Sploop.newPlace(5);
        if(keyDown[this.food]) Sploop.newPlace(2);
    };
};

let Placer = new Macro(true, 86, 70, 78, 81);

class Sploop {
    static place(id, angle = Config.angle){
        Config.update('cps');
        Sploop.take(id);
        Sploop.hit(angle);
        Sploop.take(Config.weapon);
    }

    static newPlace(id, angle = Config.angle){
        let increasor = Math.PI / 8; // 22.25 radians
        let offset = Math.PI / 4; // 45 radians

        this.place(id, angle, true)

        for(let newAngle = 0; newAngle < offset; newAngle += increasor){
            Sploop.place(id, angle - newAngle);
            Sploop.place(id, angle + newAngle);
        }

        Sploop.take(Config.weapon);
    }

    static quad(id, angle = 0){
        for(let newAngle = 0; newAngle < Math.PI * 2; newAngle += Math.PI / 8){
            let time = (Config.serverUpdate / 4) * (newAngle / (Math.PI / 8))

            setTimeout(() => Sploop.place(7, angle + newAngle), time);
        }
    }

    static heal(amount) {
        for(let count = 0; count <= amount; count++) Sploop.place(2);
    }

    static equip(id) {
        if(user.skin != id) Game.send(new Uint8Array([9, id]));
    }

    static walk(angle = Config.move) {
        if(typeof angle !== 'number') return Game.send(new Uint8Array([1, 0]));

        angle = (65535 * (angle + Math.PI)) / (2 * Math.PI);

        Game.send(new Uint8Array([15, 255 & angle, (angle >> 8) & 255]));
    }

    static take(id) {
        Game.send(new Uint8Array([8, id]));
    }

    static chat(text){
        text = encoder.encode(text);

        Game.send(new Uint8Array([10, ...text]))
    }

    static hit(angle) {
        angle = (65535 * (angle + Math.PI)) / (2 * Math.PI);

        Game.send(new Uint8Array([4, 255 & angle, (angle >> 8) & 255]));
        Game.send(new Uint8Array([5]));
    }

    static watch(angle){
        angle = (65535 * (angle + Math.PI)) / (2 * Math.PI);

        Game.send(new Uint8Array([2, 255 & angle, (angle >> 8) & 255]));
    }

    static offensive(){
        let offensive = () => {
            let distance = enemy ? Math.dist(enemy, user) : 0;

            if(user.y <= 9e3 && user.y >= 8e3) return 9;
            if(enemy && distance <= 300) return false && distance <= 150 ? 6 : 5;

            return 7;
        }

        Sploop.equip(offensive());
    }

    static healthChange(health, oldHealth){
        if(oldHealth > health){
            user.hitDate = Config.counter;
        };

        user.health = health;
    }

    static mine(build){
        if(user.id2 == build.id2) return true;
        if(user.team){
            let length = tribe.length;
            for(let index = 0; index < length; index++) {
                let teammate = tribe[index];
                if(build.id2 == teammate.id2) return true;
            }
        }
        return false;
    }

    static update(){
        Config.counter += 1;
        Config.resolver();
        Config.last = Date.now();

        if(user.alive){
            if(user.health < 100){
                setTimeout(() => {
                    let amount = 3;
                    Sploop.heal(amount);
                }, (Config.serverUpdate - 20 - Config.ping) + addHealMS);
            };

            let trap = Entity.find(c => c && Math.dist(c, user) <= 50 && c.type == 6 && !Sploop.mine(c));
            let wasBreaking = Config.breaking;
            Config.breaking = false;

            if(trap && Toggle.autoBreak){
                console.log(trap, user)
                let angle = Math.angle(trap, user);
                Config.breaking = true;

                Sploop.hit(angle);
                Sploop.equip(6);

                if(!wasBreaking) Sploop.quad(7, angle);
            } else if(wasBreaking){
                Sploop.offensive();
            }

            let wasPushing = Config.pushing;
            Config.pushing = false;

            if(enemy && !trap && user.alive){
                let distance = Math.dist(enemy, user);

                if(Toggle.autoPush && distance <= 250){
                    let trap = Entity.find(c => c && Math.dist(c, enemy) <= 50 && c.type == 6 && Sploop.mine(c));

                    if(trap){
                        let spikes = Entity.filter(c => c && [2, 7, 17].includes(c.type) && Sploop.mine(c) && Math.dist(c, trap) <= 130);

                        if(spikes.length){
                            let spike = spikes.sort((a, b) => Math.dist(a, trap) - Math.dist(b, trap))[0];
                            let angle = Math.angle(enemy, spike);
                            distance = Math.dist(enemy, spike) + 70;
                            let position = {
                                x: spike.x + (distance * Math.cos(angle)),
                                y: spike.y + (distance * Math.sin(angle))
                            };

                            distance = Math.dist(position, user);
                            angle = () => {
                                if(distance > 40){
                                    return Math.angle(position, user)
                                } else {
                                    let angleDifference = Math.abs(Math.angle(spike, position) - Math.angle(spike, user))
                                    let message = `diffence [${angleDifference / (Math.PI / 180)}]`

                                    // Sploop.chat(message);
                                    return Math.angle(enemy, user)
                                }
                            }

                            Config.pushing = true;
                            Sploop.walk(angle())
                        }
                    }
                }

                distance = Math.dist(enemy, user)
                if(Toggle.autoPlace && distance <= 200){
                    let trap = Entity.find(c => c && c.type == 6 && Sploop.mine(c) && Math.dist(c, enemy) <= 50);
                    let enemyPos = {
                        x: enemy.x + enemy.xVel,
                        y: enemy.y + enemy.yVel
                    }
                    let userPos = {
                        x: user.x + user.xVel,
                        y: user.y + user.yVel
                    }
                    distance = Math.dist(enemyPos, userPos);
                    let angle = Math.angle(enemyPos, userPos)
                    let range = 28 * 2 + 50;

                    if(trap){
                        angle = Math.angle(trap, userPos);

                        for(let newAngle = 0; newAngle < Math.PI / 2; newAngle += Math.PI / 9){
                            Sploop.place(4, angle + newAngle);
                            Sploop.place(4, angle - newAngle);
                        }
                    } else {
                        if(Toggle.autoSync && distance < 250){
                            let spike = Entity.find(c => c && [2, 7, 17].includes(c.type) && Sploop.mine(c) && Math.dist(c, enemyPos) <= 60);
                            if(spike){
                                Sploop.equip(2);
                                Sploop.take(0);
                                Sploop.hit(angle);
                                setTimeout(() => Sploop.offensive(), 2e3);
                            }
                            if(enemy.health <= (enemy.skin == 2 ? 78 : 85) && user.skin == 5 && user.health <= 70){
                                Sploop.equip(2);
                                Sploop.take(0);
                                Sploop.hit(angle);
                                setTimeout(() => Sploop.offensive(), 2e3);
                            }
                        }

                        if(range >= distance){
                            Sploop.place(7, angle);
                        }
                    }
                }
            }

            if(wasPushing && !Config.pushing) Sploop.walk('Stop');
        }

        Placer.update();
    }
}


class Script {
    setup(){
        this.run();
    };
    override(ws, data){
        !Config.freeze.send && this.log(`WebSocket`, `⬈`, data[0], '#8ecc51');
        ws.classic(data, true);
        let string = Config.isJson(data);
        data = string ? JSON.parse(data) : new Uint8Array(data);
        let item = data[0];
        switch(item) {
            case 6:
                user.name = data[1];
                break;
        }


        Config.update('pps');
    }

    send(data){
        this.ws && 1 === this.ws.readyState && (typeof data !== "string" && window.encoder.encode(data), this.ws.classic(data, true))
        Config.update('pps');
    }

    message(event){
        let data = event.data;
        let string = typeof data === 'string';
        let decoded = string ? JSON.parse(data) : new Uint8Array(data);
        let length = decoded.length;
        let id = Number(decoded[0]);
        let found = Config.messages[Number(string)].find(item => item && item.id == id);

        if(!found) return !Config.freeze.message && Game.log(`WebSocket`, `⬉`, `${decoded} | ${string}`, '#c7cc51');

        switch(found.name){
            case 'Player update':
                enemy = null;
                tribe = [];

                for(let index = 0; index < Entity.length; index++){
                    let player = Entity[index];
                    if(player) player.visible = false;
                }

                for (let int = 1; int < length; int += 18) {
                    let type = decoded[int],
                        owner = decoded[int + 1],
                        index = decoded[int + 2] | decoded[int + 3] << 8,
                        x = decoded[int + 4] | decoded[int + 5] << 8,
                        y = decoded[int + 6] | decoded[int + 7] << 8,
                        broken = decoded[int + 8],
                        skin = decoded[int + 11],
                        team = decoded[int + 12],
                        health = decoded[int + 13] / 255 * 100,
                        clown = decoded[int + 8];

                    let Default = {fd: 2, active: true, health: 100, x: 0, y: 0, xVel: 0, yVel: 0};
                    let temp = Entity[index] || Default;
                    temp.visible = true;


                    if (broken & 2) {
                        Entity[index] = null;
                    } else {
                        if (temp.fd & 2) {
                            temp.type = type;
                            temp.id = index;
                            temp.health = health;
                            temp.id3 = broken;
                            temp.xVel = temp.x - x;
                            temp.yVel = temp.y - y;
                            temp.speed = Math.hypot(y - temp.y, x - temp.x);
                            temp.move = Math.atan2(y - temp.y, x - temp.x);
                            temp.x = x;
                            temp.y = y;
                            temp.id2 = owner;
                            temp.skin = skin;
                            temp.team = team;
                            temp.clown = Boolean(clown);
                        }

                        Entity[index] = temp;

                        if(temp.id === user.id) {
                            Sploop.healthChange(temp.health, user.health);
                            Object.assign(user, temp)
                        } else if(!temp.type && (user.team && user.team == temp.team)){
                            tribe.push(temp);
                        } else if(!temp.type && (!user.team || temp.team != user.team)){
                            let distance = Math.hypot(user.y - temp.y, user.x - temp.x);
                            let distance2 = enemy ? Math.hypot(user.y - enemy.y, user.x - enemy.x) : null;
                            if(enemy){
                                if(distance < distance2) enemy = temp;
                            } else {
                                enemy = temp;
                            }
                        }

                    }
                }

                Config.update('tps');
                Sploop.update();
                break;
            case 'Spawn':
                user.id = decoded[1];
                user.alive = true;
                user.spawnDate = Date.now();
                user.health = 100;
                Config.weapon = 0;
                break;
            case 'Death':
                user.health = 0;
                user.speed = 0;
                user.alive = false;
                break;
            case 'Ping update':
                Config.ping = decoded[1] | (decoded[2] << 8);
                break;
        }

        Placer.update();
    }

    log(group, symbol, result, color){
        return console.log(`%c[${group}] %c${symbol}`, `color:${color};font-weight:bold`, `color:${color}`, result);
    }

    run(ws){
        !Config.freeze.setup && Game.log(`Hijacked Iframe`, `✔`, ws.url, '#0f0');
        let notifications = `<div class="notifications-holder"></div><style>.box span{font-size: 20px; white-space: nowrap;}.box{width: max-content; height: 40px; display: flex; align-items: center; padding-top: 3.5px; padding-left: 7px; padding-right: 7px; border-radius: 7px; background-color: rgb(40 45 34 / 60%); border: 4px solid #141414; margin-bottom: 5px; color: white; letter-spacing: 1px; font-weight: bold; box-shadow: inset 0 -3px 0 #333; text-shadow: rgb(20 20 20) 3px 0px 0px, rgb(20 20 20) 2.83487px 0.981584px 0px, rgb(20 20 20) 2.35766px 1.85511px 0px, rgb(20 20 20) 1.62091px 2.52441px 0px, rgb(20 20 20) 0.705713px 2.91581px 0px, rgb(20 20 20) -0.287171px 2.98622px 0px, rgb(20 20 20) -1.24844px 2.72789px 0px, rgb(20 20 20) -2.07227px 2.16926px 0px, rgb(20 20 20) -2.66798px 1.37182px 0px, rgb(20 20 20) -2.96998px 0.42336px 0px, rgb(20 20 20) -2.94502px -0.571704px 0px, rgb(20 20 20) -2.59586px -1.50383px 0px, rgb(20 20 20) -1.96093px -2.27041px 0px, rgb(20 20 20) -1.11013px -2.78704px 0px, rgb(20 20 20) -0.137119px -2.99686px 0px, rgb(20 20 20) 0.850987px -2.87677px 0px, rgb(20 20 20) 1.74541px -2.43999px 0px, rgb(20 20 20) 2.44769px -1.73459px 0px, rgb(20 20 20) 2.88051px -0.838247px 0px;}.notifications-holder{position: absolute; left: 20px; top: 20px; display: flex; flex-direction: column; z-index: 5;}</style>`
        $("body").append(notifications)
        this.ws = ws;

        let infoPanel = '\n<div class="info-panel-holder">\n  <div id="info-content">\n  <p id="health"></div>\n</div>\n<style>\n#info-content {\n  color: #fff;\n  font-size: 22px;\n  text-shadow: 0px 0px 5px black, 0px 0px 7px black;\n}\n.info-panel-holder {\n  position: absolute;\n  top: 20px;\n  left: 20px;\n}\n</style>\n';
        $("body").append(infoPanel)

        setInterval(() => {
            !Config.freeze.pps && this.log(`PPS`, `⬍`, Config.pps, '#516ecc');
        }, Config.rate);

        Config.width = Canvas.clientWidth;
        Config.height = Canvas.clientHeight;

        $(window).resize(() => {
            Config.width = Canvas.clientWidth;
            Config.height = Canvas.clientHeight;
        });

        Canvas.addEventListener('mousemove', (event) => {
            Config.mouseX = event.clientX;
            Config.mouseY = event.clientY;
            Config.angle = Math.atan2(Config.mouseY - Config.height / 2, Config.mouseX - Config.width / 2);
        });

    }

    constructor(){
        this.ws = null;
    }
};

const Setup = () => {
    Game = new Script();
    Game.log(`Setup`, `⦿`, '', '#000000');
    let data = Config.messages;

    data[0][1] = {name: 'Player update', string: false};
    data[0][2] = {name: 'Verify', string: false};
    data[0][5] = {name: 'Choose', string: false};
    data[0][7] = {name: 'Hit', string: false};
    data[0][14] = {name: 'Resource update', string: false};
    data[0][16] = {name: 'Projectile Hit', string: false};
    data[0][18] = {name: 'Chat', string: false};
    data[0][19] = {name: 'Choose x3', string: true};
    data[0][20] = {name: 'Choose x2', string: false};
    data[0][22] = {name: 'Ping update', string: false};
    data[0][23] = {name: 'Ping update', string: false};
    data[0][24] = {name: 'Create clan', string: false};
    data[0][25] = {name: 'Leave clan', string: false};
    data[0][26] = {name: 'Create clan', string: false};
    data[0][27] = {name: 'Leave clan', string: false};
    data[0][30] = {name: 'Place', string: false};

    data[1][2] = {name: 'Spawn', string: true};
    data[1][8] = {name: 'Player setup', string: true};
    data[1][9] = {name: 'Leaderboard update', string: true};
    data[1][11] = {name: 'Text', string: true};
    data[1][13] = {name: 'Death', string: true};
    data[1][19] = {name: 'Choose', string: true};
    data[1][35] = {name: 'new Verify', string: true};

    for(let index = 0; index <= 1; index++) {
        let length = data[index].length;
        for(let id = 0; id < length; id++) {
            if(data[index][id]) data[index][id].id = id;
        };
    };
};
Setup();

class Nuro extends WebSocket {
    constructor(url, protocols) {
        Config.WS = super(url, protocols);
        this.addEventListener('message', event => Game.message(event));
        this.classic = this.send;
        this.send = data => Game.override(this, data);
        window.ws = this;
        Game.run(this);
    }
    set onmessage(f) {
        !Config.freeze.setup && console.log('onmessage', f);
        super.onmessage = f;
    }
}

const hijacked = Symbol();

function hijack(window) {
    const getter = window.HTMLIFrameElement.prototype.__lookupGetter__('contentWindow');
    window.HTMLIFrameElement.prototype.__defineGetter__('contentWindow', function() {
        const hiddenWindow = getter.call(this);
        if (!hiddenWindow[hijacked]) {
            hijack(hiddenWindow);
            hiddenWindow.WebSocket = Nuro;
            hiddenWindow[hijacked] = true;
        }
        return hiddenWindow;
    });
}

hijack(window);

let blockReact = ['clan-menu-clan-name-input', 'nickname', 'chat'];

const keyChange = (event, down) => {
    if(blockReact.includes(document.activeElement.id.toLowerCase())) return `Blocked key change.`
    keyDown[event.keyCode] = down;

    let isPrimary = [49, 97].includes(event.keyCode);
    let isSecondary = [50, 98].includes(event.keyCode);

    if(down && (isPrimary || isSecondary)) Config.weapon = Number(isSecondary);

    switch(event.key.toUpperCase()) {
        case "T":
            Sploop.equip(4)
            break
        case "B":
            Sploop.equip(7)
            break;
        case "C":
            Sploop.equip(2)
            break
        case "G":
            Sploop.equip(5)
            break;
    }
    Placer.update();
};

setInterval(Placer.update, 50);

document.addEventListener("keydown", (event) => keyChange(event, true));
document.addEventListener("keyup", (event) => keyChange(event, false));

Math.dist = (player, player2) => {
    return Math.sqrt(Math.pow((player.x - player2.x), 2) + Math.pow((player.y - player2.y), 2));
}

Math.angle = (player, player2) => {
    return Math.atan2(player.y - player2.y, player.x - player2.x)
}

const encodeSym = Symbol();
Object.defineProperty(Object.prototype, 'encode', {
    get() {
        return this[encodeSym];
    },
    set(encode) {
        if(this.init) {
            window.encoder = this
        }
        this[encodeSym] = function() {
            return encode.apply(this, arguments)
        }
    }
});

const ReqFrame = requestAnimationFrame;
window.requestAnimationFrame = function() {
    Config.update("fps");
    ReqFrame.apply(this, arguments);
}


let updateInfo = () => {
    if(user && user.alive){
        let Display = ``;
        let addText = (text = '') => {
            Display += (text + '<br/>')
        }

        addText(`health: ${Math.round(user.health)}/100`);
        addText(`push: o${Config.pushing ? 'n' : 'ff'}line`);
        addText(`stuck: ${Config.breaking ? 'yes' : 'no'}`);
        addText(`speed: ${Math.round(user.speed)}`);
        addText();
        addText(`cps: ${Config.cps}`);
        addText(`pps: ${Config.pps}`);
        addText(`tps: ${Config.tps}`);
        addText(`fps: ${Config.fps}`);

        $("#info-content").html(Display)
    };
}

const gctx = CanvasRenderingContext2D.prototype.clearRect;
CanvasRenderingContext2D.prototype.clearRect = function() {
    if (this.canvas.id === "game-canvas") {
        Canvas = this.canvas
    }
    return gctx.apply(this, arguments);
}
const { fillText } = CanvasRenderingContext2D.prototype;
CanvasRenderingContext2D.prototype.fillText = function(text, x, y) {
    if(text == user.name && text.length > 1 || typeof text == "string" && text.startsWith(String.fromCharCode(0))) {
        let hue = 0;
        let step = 360 / user.name.length;
        for (let letter of text) {
            this.fillStyle = `hsl(${hue}, 100%, 50%)`;
            fillText.call(this, letter, x, y);
            x += this.measureText(letter).width;
            hue = (hue + step) % 360;
        }
        return;
    }
    return fillText.apply(this, arguments);
}