SploMod - Leading hack for Sploop.io 2022

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

Versión del día 20/03/2022. Echa un vistazo a la versión más reciente.

// ==UserScript==
// @name         SploMod - Leading hack for Sploop.io 2022
// @description  Autobreak-trap, Autoheal, Hat macros, Anti-trap, AutoPush & more!
// @version      3.2.1
// @author       Wealthy#8266 & Nuro#9999 & Nudo
// @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 = "3.2.1"

let Game;
let Entity = new Array();
let Canvas;
let Context;
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,
    enemiesNear: [],
    freeze: {
        send: true,
        pps: true,
        message: false,
        setup: false
    },
    angle: 0,
    move: 0,
    messages: new Array([], []),
    counter: 0,
    resolver: function(){},
    last: Date.now(),
    chat: [ "If you're a lover", "you should know", "The lonely moments", "just get lonelier", "The longer you're in love", "Than if you were alone", "Memories turn into daydreams", "Become a taboooo", "I don't want to be afraid", "The deeper that I go", "It takes my breath away", "Soft hearts electric souls", "Heart to heart", "Eyes to eyes", "Is this taboo?", "Baby we built this house", "Baby we built this house", "On memories", "Take my picture now", "Shake it til you see it", "And when your fantasies", "Become your legacy", "Promise me a place", "In your house of memories", "I think of you from time to time", "More than I thought I would", "You were just too kind", "And I was too young to know", "That's all that really matters", "I was a fool..", "Baby we built this house", "On memories", "Take my picture now", "Shake it til you see it", "And when your fantasies", "Become your legacy", "Promise me a place", "In your house of memories", "Those thoughts of past lovers", "They'll always haunt me", "I wish I could believe", "You'd never wrong me", "Then will you remember", "Me in the same way", "As I remember you", "Baby we built this house", "On memories", "Take my picture now", "Shake it til you see it", "And when your fantasies", "Become your legacy", "Promise me a place", "In your house of memories", "Baby we built this house", "On memories", "Take my picture now", "Shake it til you see it", "And when your fantasies", "Become your legacy", "Promise me a place", "In your house of memories", "Woah Woah,", "Woah Woah", "Promise me a place", "In your house of memories.. 💔" ],
    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: true,
    autoBreak: true,
    autoPush: true,
    autoPlace: true,
    autoSync: true,
    autoRespawn: false,
    autoChat: false,
    tracers: true,
    autoHeal: true,
    optHeal: false,
}

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 && Toggle.autoHeal){
                console.log("damage")
                setTimeout(() => {
                    Sploop.heal(1);
                }, Toggle.optHeal ? (Config.serverUpdate - 20 - Config.ping) + parseInt(document.querySelector('[data-speed="addms"]').value) : parseInt(document.querySelector('[data-speed="heal"]').value));
            };

            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();
        updateInfo()
    }
}


class Script {
    setup(){
        this.run();
    };
    override(ws, data){
        !Config.freeze.send && this.log(`WebSocket`, `⬈`, data[0], '#8ecc51');
        if(Config.isJson(data)) {
            let oldws = JSON.parse(data)
            if(oldws[0] == 6) {
                user.name = oldws[1]
            }
        }
        ws.classic(data, true);
        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':
                Config.enemiesNear = []
                enemy = null;
                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 temp = Entity[index] || {fd: 2, active: true, health: 100, x: 0, y: 0};

                    if (!type && broken == 2) {
                        temp = {};
                    } else {
                        if (temp.fd & 2) {
                            temp.type = type;
                            temp.id = index;
                            temp.health = health;
                            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);
                            Config.enemiesNear.push(temp)
                        }
                        Entity[index] = temp;

                        if(temp.id === user.id) {
                            Sploop.healthChange(temp.health, user.health);
                            Object.assign(user, 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;
                if(Toggle.autoRespawn) {
                    setTimeout(() => {
                        Game.send(Config.respawnPacket)
                    }, 200)
                }
                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){
        let chatval = 0
        setInterval(() => {
            if(Toggle.autoChat) {
                if(chatval == Config.chat.length) {
                    chatval = 0;
                } else {
                    if(user.alive && Toggle.autoChat) {
                        chatval++
                        Sploop.chat(Config.chat[chatval])
                    }
                }
            }
        }, 1000)
        let val = [1,2,4,8]
        let lval = 0
        setInterval(() => {
            if(Toggle.antiAFK) {
                lval++
                Game.send(new Uint8Array([1,val[lval - 1]]))
                setTimeout(() => {
                    Game.send(new Uint8Array([1,0]))
                }, 200)
                if(lval == val.length) {
                    lval = 0
                }
            }
        }, 5000)
        !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;
        }
        .notifications-holder {
          position: absolute;
          left: 20px;
          top: 20px;
          display: flex;
          flex-direction: column;
          z-index: 5;
        }
        </style>
        `
        $("body").append(notifications)
        addNotifications(`The script is running!`, "#5c4ce6")
        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.code) {
        case "ShiftLeft":
            Sploop.take(Config.weapon);
            break
        case document.querySelector('[data-key="instakill"]').value:
            console.log("InstaKill")
            break
        case document.querySelector('[data-key="crystalgear"]').value:
            Sploop.equip(4)
            break
        case document.querySelector('[data-key="boosthat"]').value:
            Sploop.equip(7)
            break;
        case document.querySelector('[data-key="berserkergear"]').value:
            Sploop.equip(2)
            break
        case document.querySelector('[data-key="spikegear"]').value:
            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() {
           console.log(`%c[WebSocket] %c⮩`, 'color:#80f;font-weight:bold', 'color:#0f0', ...arguments);
            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}`);
        if(!Toggle.UI) {
            Display = ""
        }
        $("#info-content").html(Display)
    };
}
var ctx2d
const gctx = CanvasRenderingContext2D.prototype.clearRect;
CanvasRenderingContext2D.prototype.clearRect = function() {
    if (this.canvas.id === "game-canvas") {
        Canvas = this.canvas
        Context = Canvas.getContext("2d")
        ctx2d = this;
    }
    return gctx.apply(this, arguments);
}

/* Tracers */
let dlo = 0, dlod = Date.now()

function tracer(x, y, mx, my, color) {
    Context.save()
    Context.lineCap = "round"
    Context.lineWidth = 5
    Context.globalAlpha = .50
    Context.beginPath()
    Context.setLineDash([18, 6, 6, 6])
    Context.lineDashOffset = -dlo
    Context.strokeStyle = color
    Context.moveTo(mx, my)
    Context.lineTo(x, y)
    Context.stroke()
    Context.restore()
}
let tracerX = 0, tracerY = 0, tracermX = 0, tracermY = 0

const { fillRect } = CanvasRenderingContext2D.prototype;
CanvasRenderingContext2D.prototype.fillRect = function(x, y, width, height) {
    let cp = (tracerX > 0 && tracerY > 0 && tracermX > 0 && tracermY > 0)
    if (!dlod || Date.now() - dlod >= 10) {
        dlo++
        dlod = Date.now()
    }
    if (this.fillStyle === "#a4cc4f") {
        tracermX = x + 50
        tracermY = y - 70
        fillRect.call(this, x, y, width, height);
    } else if (this.fillStyle === "#cc5151") {
        tracerX = x + 50
        tracerY = y - 70
        fillRect.call(this, x, y, width, height);
    }
    if (user.alive && cp && Toggle.tracers) {
        tracer(tracerX, tracerY, tracermX, tracermY, "#cc5151")
    }
    fillRect.call(this, x, y, width, height);
    return fillRect.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);
}

let settingMenu = `
<div id="settingMenu" class="pop-box">
  <div class="menu-title">
    <div class="pop-title text-shadowed-4">Settings</div>
    <div class="pop-close-button">
      <img id="setting-menu-close-button" class="pop-close" draggable="false" src="https://images-ext-2.discordapp.net/external/mgjNglv928NY9v8XuIr2Z2mFbQHliKADNMZn9XsDibA/https/sploop.io/img/ui/close.png">
    </div>
  </div>
  <div class="navbar">
    <div class="nb-btn text-shadowed-3" id="hack" style="margin-right: 10px;">Hack</div>
    <div class="nb-btn text-shadowed-3" id="control">Control</div>
  </div>
  <div class="select pop-list-content scrollbar text-shadowed-3 content subcontent-bg" id="hat_menu_content" data-menu="1"></div>
  <div class="select pop-list-content scrollbar text-shadowed-3 content subcontent-bg" id="hat_menu_content" data-menu="2" style="display: none;">
    <div class="control-box">
      <p style="font-size: 20px;font-weight: bold;color: white;">HealSpeed</p>
      <input id="control-key" data-speed="heal" class="input text-shadowed-3" placeholder="Enter speed..." value="100">
    </div>
    <div class="control-box">
      <p style="font-size: 20px;font-weight: bold;color: white;">AddHealMS</p>
      <input id="control-key" data-speed="addms" class="input text-shadowed-3" placeholder="Enter speed..." value="0">
    </div>
    <div class="control-box">
      <p style="font-size: 20px;font-weight: bold;color: white;">InstaKill</p>
      <input id="control-key" data-key="instakill" onKeyPress=SupressInput(event); class="input text-shadowed-3" placeholder="Enter key..." value="KeyR">
    </div>
    <div class="control-box">
      <p style="font-size: 20px;font-weight: bold;color: white;">CrystalGear</p>
      <input id="control-key" data-key="crystalgear" onKeyPress=SupressInput(event); class="input text-shadowed-3" placeholder="Enter key..." value="KeyT">
    </div>
    <div class="control-box">
      <p style="font-size: 20px;font-weight: bold;color: white;">BerserkerGear</p>
      <input id="control-key" data-key="berserkergear" onKeyPress=SupressInput(event); class="input text-shadowed-3" placeholder="Enter key..." value="KeyC">
    </div>
    <div class="control-box">
      <p style="font-size: 20px;font-weight: bold;color: white;">SpikeGear</p>
      <input id="control-key" data-key="spikegear" onKeyPress=SupressInput(event); class="input text-shadowed-3" placeholder="Enter key..." value="KeyG">
    </div>
    <div class="control-box">
      <p style="font-size: 20px;font-weight: bold;color: white;">BoostHat</p>
      <input id="control-key" data-key="boosthat" onKeyPress=SupressInput(event); class="input text-shadowed-3" placeholder="Enter key..." value="KeyB">
    </div>
  </div>
</div>
<style>
.control-box {
  padding: 10px;
  display: flex;
  justify-content: space-between;
  width: 100%;
  align-items: center;
}
#control-key {
  width: 180px;
  text-align: left;
  text-indent: 16px;
  color: white;
  line-height: 70px;
  height: 40px;
  font-weight: 600;
}
.navbar {
  display: flex;
}
.nb-btn {
  margin-top: 5px;
  width: 150px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: inset 0 -3px 0 #333;
  border-radius: 7px;
  background-color: rgb(40 45 34 / 60%);
  border: 4px solid #141414;
  cursor: url(img/ui/cursor-pointer.png) 6 0, pointer;
}
#hat_menu_content {
  padding: 8px 0 0 0;
  margin-bottom: 0px;
  margin-top: 2.5px;
}
.togglerButton {
  margin-left: auto;
  outline: none;
  border: 4px solid #141414;
  padding: 7px;
  font-size: 16px;
  margin-right: 5px;
  cursor: url(https://sploop.io/img/ui/cursor-pointer.png) 6 0, pointer;
  margin-top: auto;
  margin-bottom: auto;
  color: #fff;
  border-radius: 10px;
  background-color: #96b943;
  box-shadow: inset 0 -5px 0 #809836;
}
.togglerButton:hover {
  background-color: #b5de53;
  box-shadow: inset 0 -5px 0 #95af44;
}
.name-desc {
  display: flex;
  flex-direction: column;
  padding-left: 10px;
  margin-bottom: 5px;
}
.item-container {
  width: 100%;
  display: flex;
  align-items: center;
  border-bottom: 3px solid #141414;
  flex-direction: row;
  justify-content: space-between;
  padding-bottom: 5px;
}
.menu-title {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
#settingMenu {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  height: 367px;
  width: 500px;
  display: none;
  opacity: 1;
  background: rgba(40, 45, 34, 0.6);
  color: #fff;
}
</style>
<script>
function SupressInput($event) {
  $event.preventDefault();
}
</script>
`

const __TOGGLERS__ = [{
    name: "AutoHeal",
    desc: "Auto heals you",
    id: "autoheal",
    active: true,
    last: false
}, {
    name: "Optimized Heal",
    desc: "Optimizes autoheal in some cases",
    id: "optheal",
    active: false,
    last: false
},{
    name: "AutoPush",
    desc: "Automatically pushes enemies into spikes",
    id: "autopush",
    active: true,
    last: false
},{
    name: "AutoPlace",
    desc: "Optimizes and automates spike and trap placement",
    id: "autoplace",
    active: true,
    last: false
},{
    name: "AutoSync",
    desc: "Optimized attacking & Auto hat switching",
    id: "autosync",
    active: true,
    last: false
},{
    name: "AutoBreak",
    desc: "Automatically breaks out of traps",
    id: "autobreak",
    active: true,
    last: false
},{
    name: "AutoRespawn",
    desc: "Auto respawn you",
    id: "autorespawn",
    active: false,
    last: false
},{
    name: "UI",
    desc: "visibility of the UI",
    id: "ui",
    active: true,
    last: false
},{
    name: "AutoChat",
    desc: "Sing cool song",
    id: "autochat",
    active: false,
    last: false
},{
    name: "Tracers",
    desc: "Draw tracers to enemies",
    id: "tracers",
    active: true,
    last: false
},{
    name: "AntiAFK",
    desc: "move every 5 seconds",
    id: "antiAFK",
    active: false,
    last: true
}]

function __TOGGLER__(name, desc, id, active, last) {
  return `
  <div class="item-container" style="margin-top: 4px; ${last ? "border-bottom: none;" : "border-bottom: 3px solid #141414;"}">
    <div class="name-desc">
      <p style="font-size: 16px;font-weight: bold;color: white;">${name}</p>
      <p style="font-size: 14px; color: #d2c396;">${desc}</p>
    </div>
    <button class="togglerButton text-shadowed-3" id="${id}" style="${active ? "outline: none;" : "background: #b94343; box-shadow: inset 0 -5px 0 #983636"}">
      ${active ? "Enabled" : "Disabled"}
    </button>
  </div>
  `
}

function genTogglers() {
    __TOGGLERS__.forEach(t => {
        $('[data-menu="1"]').append(__TOGGLER__(t.name, t.desc, t.id, t.active, t.last))
    })
}

function addNotifications(text, color = "#fff") {
    let idGen = () => "notification-" + ~~(Math.random() * 10000) + 1,
        readyId = idGen()
    let add = `
    <div class="box text-shadowed-3" id="${readyId}" style="display: block; opacity: 0;">
      <span style="color: ${color};">
        ${text}
      </span>
    </div>
    `
    $(".notifications-holder").prepend(add)
    $("#" + readyId).show().animate({ opacity: 1 }, 750)
    setTimeout(() => {
        $("#" + readyId).animate({ opacity: 0 }, 750, () => {
            $("#" + readyId).remove()
        });
    }, 3000)
}

window.rContextMenu = () => {
    let elm = document.getElementsByTagName('*')
    for(let i = 0; i < elm.length; ++i) {
        elm[i].oncontextmenu = null
    }
}

function cEl(e, t, n, g) {
    addNotifications(`${n} <span style='color: ${t ? "#8ecc51" : "#cc5151"}'>${t ? "enabled" : "disabled"}</span>`)
    $(e).css("background", !t ? "#b94343" : "#96b943")
    $(e).css("box-shadow", !t ? "inset 0 -5px 0 #983636" : "inset 0 -5px 0 #809836")
    $(e).text(!t ? "Disabled" : "Enabled")
    $(e).hover(() => {
        $(e).css("background", !t ? "#de5353" : "#b5de53")
        $(e).css("box-shadow", !t ? "inset 0 -5px 0 #af4444" : "inset 0 -5px 0 #95af44")
    }, function() {
        $(e).css("background", !t ? "#b94343" : "#96b943")
        $(e).css("box-shadow", !t ? "inset 0 -5px 0 #983636" : "inset 0 -5px 0 #809836")
    })
}

/* ChangeLog menu */
let changeLogMenu = `
<div class="centerMenu">
  <div class="pop-box popup-fade-in" id="script-changelog" style="display: flex;">
    <div class="pop-top">
      <div class="pop-title text-shadowed-4" style="display: flex;align-items: center;">
        <a class="pointer" style="margin-right: 5px;" href="https://discord.gg/EETcmmFgAt" target="_blank"><img class="pointer" draggable="false" src="img/ui/discord.png"></a>
        CHANGELOG
      </div>
      <div class="pop-close-button"><img class="pop-close" id="close-script-cl" draggable="false" src="img/ui/close.png"></div>
    </div>
    <div class="pop-list-content scrollbar text-shadowed-3" id="changeLog-s"></div>
  </div>
</div>
<style>
.centerMenu {
  position: absolute;
  top: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background: rgb(39 66 35 / 60%);
  z-index: 100;
  transform-origin: center;
}
#script-changelog {
  width: 615px;
  height: 475px;
}
</style>
`

let __CHANGELOGS__ = [
    {
        version: "3.2.1",
        date: "2022-3-20",
        added: false,
        remove: false,
        fixed: true,
        addedContent: [],
        removedContent: [],
        fixedContent: [
            "fixed tracers, they are now clean and have a better design.",
            "join our discord server! (click the discord icon)"
        ]
    },
    {
        version: "3.2",
        date: "2022-3-20",
        added: true,
        remove: false,
        fixed: true,
        addedContent: [
            "AntiAFK - moves every 5 seconds",
            "Tracers - draw lines to enemies"
        ],
        removedContent: [],
        fixedContent: [
            "Fixed UI toggle not working.",
            "improved optimized heal",
            "join our discord server! (click the discord icon)"
        ]
    },
    {
        version: "3.1.1",
        date: "2022-3-19",
        added: true,
        remove: false,
        fixed: true,
        addedContent: [
            "Added autochat",
        ],
        removedContent: [
            "nothing"
        ],
        fixedContent: [
            "join our discord server! (click the discord icon)"
        ]
    },
    {
        version: "3.1",
        date: "2022-3-19",
        added: true,
        remove: true,
        fixed: true,
        addedContent: [
            "Added changelog menu.",
        ],
        removedContent: [
            "Removed nothing."
        ],
        fixedContent: [
            "Fixed autoheal speed.",
            "join our discord server! (click the discord icon)"
        ]
    },{
        version: "3",
        date: "2022-3-19",
    added: true,
    remove: false,
    fixed: false,
    addedContent: [
        "Added game menu.",
        "Added optimized autoheal."
    ],
    removedContent: [
        "None.",
        "Nothing."
    ],
    fixedContent: [""]
}]

function __CHANGELOG__(version, date, added, remove, fixed, addedContent, removedContent, fixedContent) {
    let add = "",
        rem = "",
        fix = ""
    let at = '<li class="subcontent-subtitle">Added</li>',
        rt = '<li class="subcontent-subtitle">Removed</li>',
        ft = '<li class="subcontent-subtitle">Fixed</li>'
    let ac = () => {
        let res = ""
        let al = addedContent.length
        for (let i = 0; i < al; i++) {
            res += `<li>- ${addedContent[i]}</li>`
        }
        return res
    }
    let rc = () => {
        let res = ""
        let rl = removedContent.length
        for (let i = 0; i < rl; i++) {
            res += `<li>- ${removedContent[i]}</li>`
        }
        return res
    }
    let fc = () => {
        let res = ""
        let fl = fixedContent.length
        for (let i = 0; i < fl; i++) {
            res += `<li>- ${fixedContent[i]}</li>`
        }
        return res
    }
    if (added) {
        add = `
        <ul>
          ${ac()}
          <br>
        </ul>
        `
    }
    if (remove) {
        rem = `
        <ul>
          ${rc()}
          <br>
        </ul>
        `
    }
    if (fixed) {
        fix = `
        <ul>
          ${fc()}
          <br>
        </ul>
        `
    }
    return `
    <ul class="subcontent-bg">
      <li class="subcontent-title">[${version}] - ${date}</li>
      <ol>
        ${added ? at + add : ""}
        ${remove ? rt + rem : ""}
        ${fixed ? ft + fix : ""}
      </ol>
    </ul>
  `
}

function genChangeLogs() {
  __CHANGELOGS__.forEach(cl => {
      $("#changeLog-s").append(__CHANGELOG__(cl.version, cl.date, cl.added, cl.remove, cl.fixed, cl.addedContent, cl.removedContent, cl.fixedContent))
  })
}

let changeLog = true

function controlSaver() {
    let items = ["bha", "sga", "bga", "cga", "ika"]
    let datas = ["boosthat", "spikegear", "berserkergear", "crystalgear", "instakill"]
    for (let i = 0; i < datas.length; i++) {
        document.querySelector(`[data-key="${datas[i]}"]`).value = localStorage.getItem(items[i]) || document.querySelector(`[data-key="${datas[i]}"]`).value
    }
    document.querySelector('[data-speed="heal"]').value = localStorage.getItem("heal") || document.querySelector('[data-speed="heal"]').value
    document.querySelector('[data-speed="addms"]').value = localStorage.getItem("addms") || document.querySelector('[data-speed="addms"]').value
}

/* Add at event DOMContentLoaded so that there is no HTML substitution bug */
document.addEventListener("DOMContentLoaded", () => {
    $("body").append(settingMenu)
    $("body").append(changeLogMenu)
    $("#close-script-cl").click(() => $(".centerMenu").css("display", "none"))
    let bha = [false, document.querySelector('[data-key="boosthat"]')]
    let sga = [false, document.querySelector('[data-key="spikegear"]')]
    let bga = [false, document.querySelector('[data-key="berserkergear"]')]
    let cga = [false, document.querySelector('[data-key="crystalgear"]')]
    let ika = [false, document.querySelector('[data-key="instakill"]')]
    document.querySelector('[data-speed="heal"]').oninput = (e) => (e.target.value = e.target.value.replace(/\D/g, ''), localStorage.setItem("heal", document.querySelector('[data-speed="heal"]').value))
    document.querySelector('[data-speed="addms"]').oninput = (e) => (e.target.value = e.target.value.replace(/\D/g, ''), localStorage.setItem("addms", document.querySelector('[data-speed="addms"]').value))
    bha[1].onclick = () => (bha[1].value = "...", bha[0] = true)
    sga[1].onclick = () => (sga[1].value = "...", sga[0] = true)
    bga[1].onclick = () => (bga[1].value = "...", bga[0] = true)
    cga[1].onclick = () => (cga[1].value = "...", cga[0] = true)
    ika[1].onclick = () => (ika[1].value = "...", ika[0] = true)
    document.addEventListener("keydown", e => {
        if (bha[0])(bha[0] = false, bha[1].value = e.code, localStorage.setItem("bha", e.code))
        if (sga[0])(sga[0] = false, sga[1].value = e.code, localStorage.setItem("sga", e.code))
        if (bga[0])(bga[0] = false, bga[1].value = e.code, localStorage.setItem("bga", e.code))
        if (cga[0])(cga[0] = false, cga[1].value = e.code, localStorage.setItem("cga", e.code))
        if (ika[0])(ika[0] = false, ika[1].value = e.code, localStorage.setItem("ika", e.code))
    })
    genChangeLogs()
    controlSaver()
    if (changeLog) {
        $(".centerMenu").css("display", "flex")
        changeLog = false
    }
    genTogglers()
    $("#autoheal").click(() => (Toggle.autoHeal = !Toggle.autoHeal, cEl("#autoheal", Toggle.autoHeal, "AutoHeal")))
    $("#autopush").click(() => (Toggle.autoPush = !Toggle.autoPush, cEl("#autopush", Toggle.autoPush, "AutoPush")))
    $("#autosync").click(() => (Toggle.autoSync = !Toggle.autoSync, cEl("#autosync", Toggle.autoSync, "AutoSync")))
    $("#autoplace").click(() => (Toggle.autoPlace = !Toggle.autoPlace, cEl("#autoplace", Toggle.autoPlace, "AutoPlace")))
    $("#autobreak").click(() => (Toggle.autoBreak = !Toggle.autoBreak, cEl("#autobreak", Toggle.autoBreak, "AutoBreak")))
    $("#autochat").click(() => (console.log(Toggle.autoChat), Toggle.autoChat = !Toggle.autoChat, cEl("#autochat", Toggle.autoChat, "AutoChat")))
    $("#tracers").click(() => (Toggle.tracers = !Toggle.tracers, cEl("#tracers", Toggle.tracers, "Tracers")))
    $("#autorespawn").click(() => (Toggle.autoRespawn = !Toggle.autoRespawn, cEl("#autorespawn", Toggle.autoRespawn, "AutoRespawn")))
    $("#optheal").click(() => (Toggle.optHeal = !Toggle.optHeal, cEl("#optheal", Toggle.optHeal, "OptimizedAutoheal")))
    $("#ui").click(() => (Toggle.UI = !Toggle.UI, cEl("#ui", Toggle.UI, "UI")))
    $("#antiAFK").click(() => (Toggle.antiAFK = !Toggle.antiAFK, cEl("#antiAFK", Toggle.antiAFK, "antiAFK")))
    $("#setting-menu-close-button").click(() => {
        $("#settingMenu").css("display", "none")
    })
    $("#hack").click(() => {
        $('[data-menu="1"]').css("display", "block")
        $('[data-menu="2"]').css("display", "none")
    })
    $("#control").click(() => {
        $('[data-menu="1"]').css("display", "none")
        $('[data-menu="2"]').css("display", "block")
    })
    document.addEventListener("keydown", e => {
        if (e.code == "Escape") {
            if ($("#settingMenu").css("display") == "flex") {
                $("#settingMenu").css("display", "none")
            } else {
                $("#settingMenu").css("display", "flex")
                $("#clan-menu").css("display", "none")
                $("#hat-menu").css("display", "none")
            }
        }
    })
    setInterval(() => {
        if ($("#clan-menu").css("display") == "block" || $("#hat-menu").css("display") == "flex") {
            $("#settingMenu").css("display", "none")
        }
        if ($("#homepage").css("display") == "flex") {
            $("#settingMenu").css("display", "none")
        }
    }, 500)
})