SaVeGe Mod (1.2.0)

Public mod for MooMoo.io

// ==UserScript==
// @name         SaVeGe Mod (1.2.0)
// @namespace    SaVeGeS
// @version      v1.2.0
// @description  Public mod for MooMoo.io
// @author       SaVeGe
// @match        http*://*.moomoo.io/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=moomoo.io
// @require      https://update.greasyfork.org/scripts/423602/1005014/msgpack.js
// @license      MIT
// @grant        none
// ==/UserScript==

/*
== - Mod Versions - ==
^^^^^^^^^^^^^^^^^^^^^^

1.0.0 - WS Sender & WS Receiver
1.1.0 - Auto heal & item types and enemy objects, my player objects 
1.2.0 - A lot more stuff (lazy to write down)
*/

/** VARIABLES **/
let { io, config, msgpack, jQuery: $ } = window;
let gameCanvas = document.getElementById("gameCanvas");
let mouseX, mouseY, width = innerWidth, height = innerHeight;
let moveKeys = { w: false, a: false, s: false, d: false };
let myPlayer = {
    id: null, x: null, y: null, dir: null, object: null, weapon: null, clan: null,
    isLeader: null, hat: null, accessory: null, isSkull: null, maxHealth: 100,
    currentHealth: 100, hitTime: 0, bTick: 0, inGame: false
};
let locked = false, gameTick = 0, enemy = [], ws = null, mainContext;
let players = [], nearestEnemy, enemyAngle, isEnemyNear;
let primary, secondary, foodType, wallType, spikeType, millType, mineType, boostType, spawnpadType, turretType, haveMine;
WebSocket.prototype.oldSend = WebSocket.prototype.send;

/** SEND PACKET **/
let sendPacket = (...data) => io.send(...data);

/** STORE FUNCTIONS **/
let storeBuy = (id, index) => sendPacket('c', 1, id, index);
let storeEquip = (id, index) => sendPacket('c', 0, id, index);

/** CHAT **/
let sendChat = message => sendPacket('6', message);

/** AUTO GATHER **/
let autoGather = () => sendPacket('K', 1, 1);

/** REQUEST ANIMATION FRAME **/
let requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || (callback => setTimeout(callback, 1000 / 60));

/** HANDLE MESSAGE **/
let handleMessage = (stuff) => {
    let decodedData = msgpack.decode(new Uint8Array(stuff.data));

    let data = Array.isArray(decodedData) && decodedData.length > 1 ? [decodedData[0], ...decodedData[1]] : decodedData;

    if (!data) return;

    let dataType = data[0];

    if (dataType === "io-init") setupCanvas();
    if (dataType === "C" && myPlayer.id == null) {
        myPlayer.id = data[1];
        myPlayer.inGame = true;
    }
    if (dataType == "D" && data[2]) {
        secondary = null;
        primary = 0;
        foodType = 0;
        wallType = 3;
        spikeType = 6;
        millType = 10;
        spawnpadType = 36;
    }
    if (dataType == "V") {
        if (data[2] == 1) {
            primary = data[1][0];
            secondary = data[1][1] ?? null;
        } else {
            foodType = data[1][0];
            wallType = data[1][1];
            spikeType = data[1][2];
            millType = data[1][3];
            boostType = data[1][4] ?? -1;
            haveMine = data[1][5] == 13 || data[1][4] == 14;
            if (haveMine) {
                mineType = data[1][5];
            }
            turretType = data[1][5 + (haveMine ? 1 : 0)];
        }
    }
    if (dataType == "a") updatePlayers(data);
    if (dataType === "P") myPlayer.inGame = false;
    if (dataType == "O" && data[1] == myPlayer.id) {
        let playerID = data[1];
        let health = data[2];
        updateHealth(health, playerID);
    }
};

/** UPDATE HEALTH **/
let updateHealth = (health, playerID) => {
    if (myPlayer.id == playerID) {
        let damage = 100 - myPlayer.health;
        if (damage >= 35) {
            setTimeout(() => {
                place(foodType, null);
                sendChat("SaVeGe: Damage Healing");
            }, 120);
        } else {
            setTimeout(() => {
                place(foodType, null);
                sendChat("SaVeGe: Damage Healing");
            }, 150);
        }
    }
};

/** PLACE **/
let place = (id, angle = Math.atan2(mouseY - height / 2, mouseX - width / 2)) => {
    if (typeof id !== "number" || id == -1) return;
    sendPacket("G", id, null);
    sendPacket("d", 1, angle);
    sendPacket("d", 0, angle);
    sendPacket("G", myPlayer.weapon, true);
}

/** UPDATE PLAYERS **/
let updatePlayers = data => {
    enemy = [];
    players = [];
    for (let i = 0; i < data[1].length / 13; i++) {
        let playerInfo = data[1].slice(13 * i, 13 * i + 13);

        players.push(playerInfo);
        if (playerInfo[0] == myPlayer.id) {
            myPlayer.x = playerInfo[1];
            myPlayer.y = playerInfo[2];
            myPlayer.dir = playerInfo[3];
            myPlayer.object = playerInfo[4];
            myPlayer.weapon = playerInfo[5];
            myPlayer.clan = playerInfo[7];
            myPlayer.isLeader = playerInfo[8];
            myPlayer.hat = playerInfo[9];
            myPlayer.accessory = playerInfo[10];
            myPlayer.isSkull = playerInfo[11];
        } else if (
            playerInfo[7] != myPlayer.clan ||
            playerInfo[7] === null
        ) {
            enemy.push(playerInfo);
        }
    }

    // Some stuff xd
    if (enemy) {
        nearestEnemy = enemy.sort(
            (a, b) => dist(a, myPlayer) - dist(b, myPlayer)
        )[0];
        enemyAngle = nearestEnemy ? Math.atan2(nearestEnemy[2] - myPlayer.y, nearestEnemy[1] - myPlayer.x) : (myPlayer?.dir ?? 0)
        isEnemyNear = nearestEnemy && Math.sqrt(Math.pow(myPlayer.y - nearestEnemy[2], 2) + Math.pow(myPlayer.x - nearestEnemy[1], 2)) < 300
    }
}

/** CALCULATE DISTANCE **/
let dist = (a, b) => {
    return Math.sqrt(Math.pow(b.y - a[2], 2) + Math.pow(b.x - a[1], 2));
}

/** SETUP CANVAS **/
let setupCanvas = () => {
    width = gameCanvas.clientWidth;
    height = gameCanvas.clientHeight;
    $(window).resize(() => {
        width = gameCanvas.clientWidth;
        height = gameCanvas.clientHeight;
    });
    gameCanvas.addEventListener("mousemove", ({ clientX, clientY }) => {
        mouseX = clientX;
        mouseY = clientY;
    });
    mainContext = gameCanvas?.getContext("2d");
};

/** HIT **/
let hit = () => {
    sendPacket("d", 1, enemyAngle);
    sendPacket("d", 0);
}

/** SOCKET **/
WebSocket.prototype.send = function (stuff) {
    if (!ws) {
        document.ws = this;
        ws = this;
        socketFound(this);
    }

    if (stuff instanceof Uint8Array || stuff instanceof ArrayBuffer) {
        this.oldSend(stuff);
    } else {
        this.oldSend(new Uint8Array(msgpack.encode(stuff)));
    }
};

/** SOCKET CONNECTION **/
let socketFound = stuff => {
    stuff.addEventListener("message", handleMessage);
    gameCanvas.addEventListener("mousemove", ({ x, y }) => {
        mouseX = x;
        mouseY = y;
    });
    window.addEventListener("resize", () => {
        height = innerHeight;
        width = innerWidth;
    });
    mainContext = gameCanvas?.getContext("2d");
};

/** MOVEMENT **/
let moveEz = (key, isKeyDown) => {
    moveKeys[key] = isKeyDown;
    if ((moveKeys.w || moveKeys.a || moveKeys.s || moveKeys.d) && !locked) {
        storeEquip(50, 0);
        locked = true;
    }
    if (!moveKeys.w && !moveKeys.a && !moveKeys.s && !moveKeys.d && locked) {
        storeEquip(51, 0);
        locked = false;
    }
};

/** INSTA KILL **/
let instaKill = (...instaType) => {
    let type = instaType[0];

    switch (type) {
        case "normal":
            sendChat("SaVeGe: Normal InstaKill");
            hit();
            break;

        case "reverse":
            sendChat("SaVeGe: Reverse InstaKill");

            break;

        case "reloadBased":
            sendChat("SaVeGe: Reload Based InstaKill");

            break;

        default:
            sendChat("SaVeGe: Invalid InstaKill Type");
            break;
    }
};

/** KEY EVENTS **/
document.addEventListener('keydown', ({ key }) => {
    if (key in moveKeys) moveEz(key, true);
    if (key === "r") instaKill("normal");
});

document.addEventListener('keyup', ({ key }) => {
    if (key in moveKeys) moveEz(key, false);
});