r!PsAw Multibox public

optimised multibox (no precise aim & move to mouse for now)

// ==UserScript==
// @name         r!PsAw Multibox public
// @namespace    http://tampermonkey.net/
// @version      1.0.5
// @description  optimised multibox (no precise aim & move to mouse for now)
// @author       r!PsAw
// @match        https://diep.io/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=diep.io
// @grant        none
// @license      MIT
// ==/UserScript==

/*
TODO-List:
- finish importing world coordinates
 -> rework bot following other bots
- add Player detection tracers
- maybe add Tank upgrades option to auto upgrade to main tank

*/
//I do not recommend changing the code unless you understand it, since it might break
//In this code player is the tank that is running this code and clone usually is the main tab

//let other scripts know that this one is active
window.ripsaw_multibox = true;
const ctx = canvas.getContext('2d');
let you, him, inGame = false,
    connected = false;

function windowScaling() {
    const a = canvas.height / 1080;
    const b = canvas.width / 1920;
    return b < a ? a : b;
}

function window_2_canvas(a) {
    let b = a * (canvas.width / window.innerWidth);
    return b;
}

//options
let key_option = ["WASD", "Arrows"];
let mouse_modes = ["Copy", "Reversed"];
let movement_modes = ["Clump", "Copy"];
let repel_modes = ["long 50/50", "infinite 70/30", "infinity", "Necromancer"];

let config = {
    move_keys: key_option[0],
    copy_keys: false,
    copy_build: false,
    copy_mouse: false,
    movement_mode: movement_modes[0],
    mouse_mode: mouse_modes[0],
    afk: false,
    drone_repel: false,
    drone_repel_mode: repel_modes[0],
};

//GUI logic (visual menu)
function n2id(string) {
    return string.toLowerCase().replace(/ /g, "-");
}

class El {
    constructor(
        name,
        type,
        el_color,
        width,
        height,
        opacity = "1",
        zindex = "100"
    ) {
        this.el = document.createElement(type);
        this.el.style.backgroundColor = el_color;
        this.el.style.width = width;
        this.el.style.height = height;
        this.el.style.opacity = opacity;
        this.el.style.zIndex = zindex;
        this.el.id = n2id(name);
    }
    setPosition(position, display, top, left, translate) {
        this.el.style.position = position;
        this.el.style.display = display;
        this.el.style.top = top;
        this.el.style.left = left;
        this.el.style.transform = "translate(" + translate + ")";
        this.display = display; //store last display
    }
    margin(top, left, right, bottom) {
        this.el.style.marginTop = top;
        this.el.style.marginLeft = left;
        this.el.style.marginRight = right;
        this.el.style.marginBottom = bottom;
    }
    setText(text, txt_color, font, fontsize, stroke, align) {
        this.el.innerHTML = text;
        this.el.style.color = txt_color;
        this.el.style.fontFamily = font;
        this.el.style.fontSize = fontsize;
        this.el.style.textShadow = stroke;
        this.el.style.textAlign = align;
    }
    add(parent) {
        parent.appendChild(this.el);
    }
    remove() {
        if (this.el.parentElement) {
            this.el.parentElement.removeChild(this.el);
            console.log("Element removed successfully.");
        } else {
            console.warn("Attempted to remove element, but it's not in the DOM.");
        }
    }

    toggle(showOrHide) {
        switch (showOrHide) {
            case "hide":
                this.el.display = "none";
                break;
            case "show":
                this.el.display = this.display;
                break;
        }
    }
}

class Setting {
    constructor(name, text, type, configsett, cycleArray = []) {
        this.sett = new El(name, "button", "Indigo", "100px", "60px", "0.75");
        this.sett.setPosition("relative", "block");
        this.sett.margin("20px", "10px", "10px", "20px");
        this.sett.setText(
            text,
            "white",
            "Lucida Console, Courier New, monospace",
            "15px",
            "0 0 2px gray",
            "center"
        );
        const element = this.sett.el; // Reference the DOM element
        element.onclick = () => {
            switch (type) {
                case "boolean":
                    config[configsett] = !config[configsett];
                    element.style.backgroundColor = config[configsett] ?
                        "Navy" :
                        "Indigo";
                    break;

                case "cycle": {
                    const currentIndex = cycleArray.indexOf(config[configsett]);
                    const nextIndex = (currentIndex + 1) % cycleArray.length;
                    config[configsett] = cycleArray[nextIndex];
                    element.innerHTML = `${text.split(":")[0]}: ${config[configsett]}`;
                }
                break;

                default:
                    console.error("Invalid setting type:", type);
                    break;
            }
        };
    }
    add(parent) {
        this.sett.add(parent);
    }
    remove() {
        this.sett.remove();
    }
    toggle(showOrHide) {
        this.sett.toggle(showOrHide);
    }
}

let gui_loaded = false;
let hidden = false;

function load_GUI() {
    //define everything
    if (!gui_loaded) {
        let sett_classes = [];

        let cont = new El("Mb Container", "div", "purple", "350px", "675x", "0.75");
        cont.setPosition("absolute", "block", "50%", "100%", "-50%, -50%");

        let credit = new El(
            "credit Element",
            "div",
            "transparent",
            "150px",
            "25px",
            "0.75"
        );
        credit.setText(
            "Multibox by r!PsAw",
            "white",
            "Lucida Console, Courier New, monospace",
            "20px",
            "0 0 2px red",
            "center"
        );
        credit.setPosition("relative", "block");

        let mk_setting = new Setting(
            "Move Keys",
            `Move Keys: ${config.move_keys}`,
            "cycle",
            "move_keys",
            key_option
        );
        sett_classes.push(mk_setting);

        let ck_setting = new Setting(
            "Copy Keys",
            "Copy Keys",
            "boolean",
            "copy_keys"
        );
        sett_classes.push(ck_setting);

        let cb_setting = new Setting(
            "Copy Build",
            "Copy Build",
            "boolean",
            "copy_build"
        );
        sett_classes.push(cb_setting);

        let cm_setting = new Setting(
            "Copy Mouse",
            "Copy Mouse",
            "boolean",
            "copy_mouse"
        );
        sett_classes.push(cm_setting);


        let mvm_setting = new Setting(
            "Movement Mode",
            `Movement: ${config.movement_mode}`,
            "cycle",
            "movement_mode",
            movement_modes
        );
        sett_classes.push(mvm_setting);


        let mm_setting = new Setting(
            "Mouse Mode",
            `Mouse Mode: ${config.mouse_mode}`,
            "cycle",
            "mouse_mode",
            mouse_modes
        );
        sett_classes.push(mm_setting);

        let afk_setting = new Setting("Afk", "Afk", "boolean", "afk");
        sett_classes.push(afk_setting);

        let dr_setting = new Setting(
            "Drone Repel",
            "Repel Drones",
            "boolean",
            "drone_repel"
        );
        sett_classes.push(dr_setting);

        let dm_setting = new Setting(
            "Drone Repel Modes",
            `Repel Mode: ${config.drone_repel_mode}`,
            "cycle",
            "drone_repel_mode",
            repel_modes
        );
        sett_classes.push(dm_setting);
        //load elements if unloaded
        cont.add(document.body);
        credit.add(cont.el);
        let l = sett_classes.length;
        for (let i = 0; i < l; i++) {
            sett_classes[i].add(cont.el);
        }
        gui_loaded = true;
    }
}

//mouse
function click_at(x, y, delay1 = 150) {
    input.onTouchStart(-1, x, y);
    setTimeout(() => {
        input.onTouchEnd(-1, x, y);
    }, delay1);
}

function ghost_click_at(x, y, delay1 = 150) {
    input.onTouchStart(0, x, y);
    setTimeout(() => {
        input.onTouchEnd(0, x, y);
    }, delay1);
}

function mouse_move(x, y) {
    input.onTouchMove(-1, x, y);
}

//define keys
const RAW_MAPPING = [
    "KeyA",
    "KeyB",
    "KeyC",
    "KeyD",
    "KeyE",
    "KeyF",
    "KeyG",
    "KeyH",
    "KeyI",
    "KeyJ",
    "KeyK",
    "KeyL",
    "KeyM",
    "KeyN",
    "KeyO",
    "KeyP",
    "KeyQ",
    "KeyR",
    "KeyS",
    "KeyT",
    "KeyU",
    "KeyV",
    "KeyW",
    "KeyX",
    "KeyY",
    "KeyZ",
    "ArrowUp",
    "ArrowLeft",
    "ArrowDown",
    "ArrowRight",
    "Tab",
    "Enter",
    "NumpadEnter",
    "ShiftLeft",
    "ShiftRight",
    "Space",
    "Numpad0",
    "Numpad1",
    "Numpad2",
    "Numpad3",
    "Numpad4",
    "Numpad5",
    "Numpad6",
    "Numpad7",
    "Numpad8",
    "Numpad9",
    "Digit0",
    "Digit1",
    "Digit2",
    "Digit3",
    "Digit4",
    "Digit5",
    "Digit6",
    "Digit7",
    "Digit8",
    "Digit9",
    "F2",
    "End",
    "Home",
    "Semicolon",
    "Comma",
    "NumpadComma",
    "Period",
    "Backslash",
]

function key_down(keyString) {
    const index = RAW_MAPPING.indexOf(keyString);
    if (index === -1) {
        console.error(`Invalid key string: ${keyString}`);
        return;
    }
    const result = index + 1; // Add 1 to the index as per your requirement
    input.onKeyDown(result);
}

function key_up(keyString) {
    const index = RAW_MAPPING.indexOf(keyString);
    if (index === -1) {
        console.error(`Invalid key string: ${keyString}`);
        return;
    }
    const result = index + 1; // Add 1 to the index as per your requirement
    input.onKeyUp(result);
}

function key_press(keyString, delay = 100) {
    key_down(keyString);
    setTimeout(() => {
        key_up(keyString)
    }, delay);
}

//those are updating player.keys when the script executes them
/*
function script_key_down(keyString, player) {
    const index = RAW_MAPPING.indexOf(keyString);
    player.keys[index] = 1;
    //console.log(`index ${index} player.keys[index] ${player.keys[index]} keyString ${keyString}`);
    key_down(keyString);
}

function script_key_up(keyString, player) {
    const index = RAW_MAPPING.indexOf(keyString);
    player.keys[index] = 0;
    //console.log(`index ${index} player.keys[index] ${player.keys[index]} keyString ${keyString}`);
    key_up(keyString);
}
*/

//credits to mi300
const ARENA_WIDTH = 26000;
const ARENA_HEIGHT = 26000;
let minimapArrow = [0, 0];
let square_pos = [0, 0]
let leaderArrow = [0, 0];
let minimapPos = [0, 0];
let minimapDim = [0, 0];
let calls = 0;
let points = [];

function hook(target, callback) {

    function check() {
        window.requestAnimationFrame(check);
        if (window.arrows) {
            minimapArrow[0] = window.arrows[0][0];
            minimapArrow[1] = window.arrows[0][0];
            minimapPos[0] = window.arrows[2][0];
            minimapPos[1] = window.arrows[2][1];
            minimapDim[0] = window.arrows[3][0];
            minimapDim[1] = window.arrows[3][1];
            //console.warn("[r!PsAw] canceled!");
            //console.log(minimapArrow);
            return;
        }
        const func = CanvasRenderingContext2D.prototype[target]

        if (func.toString().includes(target)) {

            CanvasRenderingContext2D.prototype[target] = new Proxy(func, {
                apply(method, thisArg, args) {
                    callback(thisArg, args)

                    return Reflect.apply(method, thisArg, args)
                }
            });
        }
    }
    window.requestAnimationFrame(check)
}

hook('beginPath', function(thisArg, args) {
    calls = 1;
    points = [];
});
hook('moveTo', function(thisArg, args) {
    if (calls == 1) {
        calls += 1;
        points.push(args)
    } else {
        calls = 0;
    }
});
hook('lineTo', function(thisArg, args) {
    if (calls >= 2 && calls <= 6) {
        calls += 1;
        points.push(args)
    } else {
        calls = 0;
    }
});


function getCentre(vertices) {
    let centre = [0, 0];
    vertices.forEach(vertex => {
        centre[0] += vertex[0]
        centre[1] += vertex[1]
    });
    centre[0] /= vertices.length;
    centre[1] /= vertices.length;
    return centre;
}

hook('fill', function(thisArg, args) {
    if (calls >= 4 && calls <= 6) {
        if (!window.M_X && thisArg.fillStyle === "#000000" && thisArg.globalAlpha > 0.9) {
            minimapArrow = getCentre(points);
            return;
        }
    } else {
        calls = 0;
    }
});

hook('strokeRect', function(thisArg, args) {
    const t = thisArg.getTransform();
    minimapPos = [t.e, t.f];
    minimapDim = [t.a, t.d];
});

//detect if focused
let is_main = document.hasFocus();

function setFocusState(isFocused) {
    is_main = isFocused;
}
window.addEventListener('focus', () => setFocusState(true));
window.addEventListener('blur', () => setFocusState(false));

//status(0) = is in game? status(1) = is connected? everything else returns null
function status(type) {
    let s;
    switch (type) {
        case 0:
            s = extern.doesHaveTank() > 0;
            break
        case 1:
            s = !!window.lobby_ip;
            break
    }
    return s;
}

//create ingame Notifications
function rgbToNumber(r, g, b) {
    return (r << 16) | (g << 8) | b;
}
const notification_rbgs = {
    require: [255, 165, 0], //orange
    warning: [255, 0, 0], //red
    normal: [0, 0, 128] //blue
}

let notifications = [];

function new_notification(text, color, duration) {
    input.inGameNotification(text, color, duration);
}

function one_time_notification(text, color, duration){
    if(notifications.includes(text)){
        return;
    }
    if(!inGame){
        notifications = [];
    }else{
        new_notification(text, color, duration);
        notifications.push(text);
    }
}

//FOV finder
let FOV = 0.55;
let fov_factors = [0.699, 0.8, 0.85, 0.899];
let fov_tanks = {
    0.699: ["Ranger"],
    0.8: ["Assassin", "Stalker"],
    0.85: ["Predator", "Streamliner", "Hunter"],
    0.899: ["Sniper", "Overseer", "Overlord", "Necromancer", "Manager", "Trapper", "Gunner Trapper", "Overtrapper", "Mega Trapper", "Tri-Trapper", "Smasher", "Landmine", "Streamliner", "Auto Trapper", "Battleship", "Auto Smasher", "Spike", "Factory", "Skimmer", "Glider", "Rocketeer"]
};

function find_fieldFactor(tank) {
    let fieldFactor = 1;
    let l = fov_factors.length;
    for (let i = 0; i < l; i++) {
        if (fov_tanks[fov_factors[i]].includes(tank)) {
            fieldFactor = fov_factors[i];
        }
    }
    return fieldFactor;
}

function calculateFOV(Fv, l) {
    const numerator = 0.55 * Fv;
    const denominator = Math.pow(1.01, (l - 1) / 2);
    return (numerator / denominator);
}

const crx = CanvasRenderingContext2D.prototype;
let diepFont = "20"; //I'm just assigning some value to it here, so it's not null or undefined
let sf = {
    autoFire: false,
    autoSpin: false
};

//this function is for both main and slave tab
function update_sf(state, f_or_s) {
    switch (state) {
        case " ON":
            sf[f_or_s] = true;
            break
        case " OFF":
            sf[f_or_s] = false;
            break
    }
}



crx.fillText = new Proxy(crx.fillText, {
    apply: function(f, _this, args) {
        //detect Auto Spin & Auto Fire
        if (args[0] === "diep.io") {
            diepFont = _this.font.split("px")[0];
        }
        if (args[0].includes("Auto Fire: ") && _this.font.split("px")[0] === diepFont) {
            update_sf(args[0].split(':')[1], "autoFire");
        }
        if (args[0].includes("Auto Spin: ") && _this.font.split("px")[0] === diepFont) {
            update_sf(args[0].split(':')[1], "autoSpin");
        }
        //detect data for FOV
        if (args[0].startsWith("Lvl ") && inGame) {
            let words = args[0].split(" ");
            let level = words[1];
            let tank = words.slice(2).join(" ").trim();
            let fieldFactor = find_fieldFactor(tank);
            FOV = calculateFOV(fieldFactor, level);
            you.fov = FOV;
            console.log(`
            %c[r!PsAw Multibox] FOV value was changed, look :0

            tank: ${tank}
            level: ${level}
            fieldFactor: ${fieldFactor}
            FOV: ${you.fov}
            `, "color: purple");
        }

        f.apply(_this, args);
    }
});

//share & recieve information across tabs
/*
keys index explanation:
 0: 0 = undefined, 1 = wasd, 2 = arrow keys
value 0 for unpressed and 1 for pressed:
 1: rest_keys[0]
 2: rest_keys[1]
 3: rest_keys[2]
 4: rest_keys[3]
 5: Shift (Triggered by ShiftLeft, ShiftRight or Right Mouse click
 6: Space (Triggered by Space and Left Mouse Click)
 7: Backslash (since sandbox arena is unusual, it breaks the world coords system, so it won't be used for now)
auto Fire & Auto Spin (0 for off and 1 for on):
 8: Auto Fire
 9: Auto Spin

This setup reduces number of transfered values from 64 to 10
Because the build is stored in a different array
*/
class Player {
    constructor() {
        this.pos_xy = new Uint16Array(2); //remake in world coords
        this.afk_xy = new Uint16Array(2); //remake in world coords
        this.mouse_xy = new Uint16Array(4); //last 2 should be world coords
        this.keys = new Uint8Array(10);
        this.build = new Uint8Array(33);
        this.windowSizes = new Uint16Array(2);
        this.minimapScale = new Uint16Array(4);
        this.fov = FOV;
    }
}

function update_move_keys(player){
    let bool = 0;
    if(key_option.includes(config.move_keys)){
        bool = key_option.indexOf(config.move_keys)+1;
    }
    player.keys[0] = bool;
    //console.log(player.keys[0]);
}

function update_build(player) {
    player.build.fill(0);
    let raw = extern.get_convar("game_stats_build");
    let temp_arr = [...raw];
    let l = temp_arr.length;
    for (let i = 0; i < l; i++) {
        player.build[i] = temp_arr[i];
    }
}

/* OLD VERSION (with converter to 1/2/3/4/5/6/7/8)
function update_build(player) {
    player.build.fill(0);
    let raw = extern.get_convar("game_stats_build");
    let temp_arr = [...raw];
    let l = temp_arr.length;
    for (let i = 0; i < l; i++) {
        let increase_index = parseFloat(temp_arr[i]);
        player.build[increase_index - 1]++;
    }
}
*/

function update_pos(player) {
    player.pos_xy[0] = Math.floor(minimapArrow[0]);
    player.pos_xy[1] = Math.floor(minimapArrow[1]);
    //console.log(player.pos_xy);
}

function update_minimap_Scale(player) {
    player.minimapScale[0] = minimapPos[0];
    player.minimapScale[1] = minimapPos[1];
    player.minimapScale[2] = minimapDim[0];
    player.minimapScale[3] = minimapDim[1];
}

function update_window_sizes(player) {
    player.windowSizes[0] = window.innerWidth;
    player.windowSizes[1] = window.innerHeight;
}

function save_sf(player){
    player.keys[8] = sf.autoFire?1:0;
    player.keys[9] = sf.autoSpin?1:0;
    //console.log(player.keys);
}

function save_info(player) {
    localStorage.setItem("Multibox Player", JSON.stringify(player));
}

function get_info() {
    return JSON.parse(localStorage.getItem("Multibox Player"));
}

//define some values
let wasd = ["KeyW", "KeyA", "KeyS", "KeyD"];
let arrows = ["ArrowUp", "ArrowLeft", "ArrowDown", "ArrowRight"];
let build_keys = [
    "KeyU",
    "KeyM",
    "Numpad1",
    "Numpad2",
    "Numpad3",
    "Numpad4",
    "Numpad5",
    "Numpad6",
    "Numpad7",
    "Numpad8",
    "Digit1",
    "Digit2",
    "Digit3",
    "Digit4",
    "Digit5",
    "Digit6",
    "Digit7",
    "Digit8", ];

function move_keys() {
    switch (config.move_keys) {
        case "WASD":
            return wasd;
            break
        case "Arrows":
            return arrows;
            break
    }
}

function rest_keys() { //opposite to move_keys
    switch(config.move_keys){
        case "WASD":
            return arrows;
            break
        case "Arrows":
            return wasd;
            break
    }
}

//copy clone values

function copy_key_inputs(player) {
    //OLD
    /*
    for (let i = 0; i < RAW_MAPPING.length; i++) {
        const keyString = RAW_MAPPING[i];
        if (!keyString) {
            console.error(`Invalid keyString at index ${i}`);
            continue;
        }

        switch (player.keys[i]) {
            case 0:
                if (!rest_keys().includes(keyString) && !build_keys.includes(keyString)) { //DO NOT copy wasd/arrow keys OR build keys
                    key_up(keyString);
                }
                break;
            case 1:
                if (!rest_keys().includes(keyString) && !build_keys.includes(keyString)) { //DO NOT copy wasd/arrow keys OR build keys
                    key_down(keyString);
                    //console.log(keyString);
                }
                break;
            default:
                console.error(`Unexpected value for player.keys[${i}]: ${player.keys[i]}`);
        }
    }
    */
    //NEW
    for(let i = 1; i < 8; i++){
        copy_key_sub_func(i, player);
    }
}

function copy_key_sub_func(index, player){
    if(index < 5 && config.movement_mode != "Copy"){
        return;
    }
    let mk = move_keys();
    let allowed_keys = [mk[0], mk[1], mk[2], mk[3], "ShiftLeft", "Space", "Backslash"];
    let key = allowed_keys[index-1];
    if(player.keys[index] === 0){
        key_up(key);
    }else{
        key_down(key);
    }
}

//scaling from window to window
function scaleCoordinates(sourceX, sourceY, sourceWidth, sourceHeight, targetWidth, targetHeight) {
    // Scale factors for width and height
    const scaleX = targetWidth / sourceWidth;
    const scaleY = targetHeight / sourceHeight;

    // Scale the coordinates
    const scale = Math.min(scaleX, scaleY);
    const targetX = sourceX * scale;
    const targetY = sourceY * scale;


    return {
        x: targetX,
        y: targetY
    };
}

//copy mouse coords
function get_invert_mouse_coords(x, y, width, height) {
    let center = {
        x: width / 2,
        y: height / 2
    };
    let d = {
        x: x - center.x,
        y: y - center.y
    };
    let inverted_coords = {
        x: center.x - d.x,
        y: center.y - d.y
    };
    return inverted_coords;
}

function copy_mouse_inputs(player, clone) {
    console.log("copying inputs");
    switch (config.mouse_mode) {
        case "Copy": {
            console.log("Copy");
            let final = scaleCoordinates(clone.mouse_xy[0], clone.mouse_xy[1], clone.windowSizes[0], clone.windowSizes[1], player.windowSizes[0], player.windowSizes[1]);
            console.log(final);
            mouse_move(final.x, final.y);
        }
        break
        case "Reversed": { //haven't tested yet
            let modified = get_invert_mouse_coords(clone.mouse_xy[0], clone.mouse_xy[1], clone.windowSizes[0], clone.windowSizes[1]);
            let final = scaleCoordinates(modified.x, modified.y, clone.windowSizes[0], clone.windowSizes[1], player.windowSizes[0], player.windowSizes[1]);
            mouse_move(final.x, final.y);
        }
        break
    }
}

//update your values
//const KEY_MAP = new Map(RAW_MAPPING.map((key, index) => [key, index]));
window.addEventListener('keydown', function(e) {
    //OLD
    /*
    if (connected && inGame && is_main) {
        if (move_keys().includes(e.code) || build_keys.includes(e.code)) {
            return; //don't copy wasd/arrows or build keys
        }
        if (e.code === "KeyQ") {
            hidden = !hidden;
            toggle_GUI(hidden);
        }
        let index = KEY_MAP.get(e.code);
        if (index !== undefined) {
            you.keys[index] = 1;
            //console.log(`pressed ${e.code} status ${you.keys[index]} index ${index}`);
        }
    }
    */
    //NEW
    if (connected && is_main) {
        if (e.code === "KeyQ") {
            hidden = !hidden;
            toggle_GUI(hidden);
        }
        if(inGame){
            if(move_keys().includes(e.code)){
                let index = move_keys().indexOf(e.code);
                you.keys[index+1] = 1;
                //console.log(you.keys);
            }
            if(rest_keys().includes(e.code)){
                let rest_move = ["W, A, S, D", "Arrow Keys"];
                if(rest_keys()[0] === "KeyW"){
                    new_notification(`Warning, you are using ${rest_move[0]}, use ${rest_move[1]} instead OR change your move keys to ${rest_move[0]}`, rgbToNumber(243, 185, 26), 5000);
                }else{
                    new_notification(`Warning, you are using ${rest_move[1]}, use ${rest_move[0]} instead OR change your move keys to ${rest_move[1]}`, rgbToNumber(243, 185, 26), 5000);
                }
            }
            if(e.code === "Space"){
                you.keys[6] = 1;
                //console.log(you.keys);
            }
            if(e.code === "ShiftLeft" || e.code === "ShiftRight"){
                you.keys[5] = 1;
                //console.log(you.keys);
            }
            if(e.code === "Backslash"){
                you.keys[7] = 1;
                //console.log(you.keys);
            }
        }
    }
});

window.addEventListener('keyup', function(e) {
    //OLD
    /*
    if (connected && inGame && is_main) {
        let index = KEY_MAP.get(e.code);
        if (index !== undefined) {
            you.keys[index] = 0;
            //console.log(`unpressed ${e.code} status ${you.keys[index]} index ${index}`);
        }
    }
    */
    //NEW
    if (connected && inGame && is_main) {
        if(move_keys().includes(e.code)){
            let index = move_keys().indexOf(e.code);
            you.keys[index+1] = 0;
            //console.log(you.keys);
        }
        if(e.code === "Space"){
            you.keys[6] = 0;
            //console.log(you.keys);
        }
        if(e.code === "ShiftLeft" || e.code === "ShiftRight"){
            you.keys[5] = 0;
            //console.log(you.keys);
        }
        if(e.code === "Backslash"){
            you.keys[7] = 0;
            //console.log(you.keys);
        }
    }
});

window.addEventListener('mousemove', function(e) {
    if (connected && inGame && is_main) {
        you.mouse_xy[0] = e.clientX;
        you.mouse_xy[1] = e.clientY;
        //console.log(you.mouse_xy);
    }
});

window.addEventListener("mousedown", function(e) {
    if (connected && inGame && is_main) {
        if (e.button === 0){
            you.keys[6] = 1;
            //console.log(you.keys);
        }
        if (e.button === 2) {
            you.keys[5] = 1;
            //console.log(you.keys);
        }
    }
});

window.addEventListener("mouseup", function(e) {
    if (connected && inGame && is_main) {
        if (e.button === 0){
            you.keys[6] = 0;
            //console.log(you.keys);
        }
        if (e.button === 2) {
            you.keys[5] = 0;
            //console.log(you.keys);
        }
    }
});

//copy build
function read_build(player) {
    let buildStr = "";
    for (let i = 0; i < 33; i++) {
        if (player.build[i] === 0) {
            break
        }
        buildStr += player.build[i];
    }
    return buildStr;
}

function copy_build(string) {
    extern.execute(`game_stats_build ${string}`);
}

//AFK logic
let moving = false;
let goal = {
    x: 0,
    y: 0
};

function set_goal(x, y) {
    goal.x = x;
    goal.y = y;
}

function move_to_goal(player) {
    if (config.afk) {
        if (player.pos_xy[0] > goal.x) {
            key_up(rest_keys()[3]);
            key_down(rest_keys()[1]);
        } else {
            key_up(rest_keys()[1]);
            key_down(rest_keys()[3]);
        }
        if (player.pos_xy[1] > goal.y) {
            key_up(rest_keys()[2]);
            key_down(rest_keys()[0]);
        } else {
            key_up(rest_keys()[0]);
            key_down(rest_keys()[2]);
        }
        moving = true;
    } else {
        if (moving) {
            for (let i = 0; i < rest_keys().length; i++) {
                key_up(rest_keys()[i]);
            }
            moving = false;
        }
        set_goal(player.pos_xy[0], player.pos_xy[1]);
    }
}

//Tank Clump (enabled when copy keys enabled)
let debug = [
    0,
    0,
    0,
    0
];

function scale_minimap(PlayerInfo, CloneInfo) {
    //NOTE: This is only possible, because minimap is a square

    //player
    let pos1 = {
        x: PlayerInfo[0],
        y: PlayerInfo[1]
    };
    let m_pos1 = {
        x: PlayerInfo[2],
        y: PlayerInfo[3]
    };
    let m_dim1 = {
        w: PlayerInfo[4],
        h: PlayerInfo[5]
    };
    console.log("PlayerInfo");
    console.log(PlayerInfo);

    //clone
    let pos2 = {
        x: CloneInfo[0],
        y: CloneInfo[1]
    };
    let m_pos2 = {
        x: CloneInfo[2],
        y: CloneInfo[3]
    };
    let m_dim2 = {
        w: CloneInfo[4],
        h: CloneInfo[5]
    };
    console.log("CloneInfo");
    console.log(CloneInfo);

    //translate clone coords into player coords
    let distance_2_mpos = {
        x: pos2.x - m_pos2.x,
        y: pos2.y - m_pos2.y
    };
    let calc_percentage = {
        x: (distance_2_mpos.x / m_dim2.w) * 100,
        y: (distance_2_mpos.y / m_dim2.h) * 100
    };

    //use % from clone and transfer to player
    let scaled_clone = {
        x: m_pos1.x + (m_dim1.w / 100 * calc_percentage.x),
        y: m_pos1.y + (m_dim1.h / 100 * calc_percentage.y)
    }
    debug[0] = window_2_canvas(pos1.x);
    debug[1] = window_2_canvas(pos1.y);
    debug[2] = window_2_canvas(scaled_clone.x);
    debug[3] = window_2_canvas(scaled_clone.y);
    return scaled_clone;
}

function clump(player, clone) {
    if (config.copy_keys && config.movement_mode === "Clump") {
        if(config.afk){
            one_time_notification("Disabled Clump since you have afk on :)", rgbToNumber(243, 185, 26), 5000);
            if (moving) {
            for (let i = 0; i < move_keys().length; i++) {
                script_key_up(move_keys()[i], player);
            }
            moving = false;
            }
            return;
        }

        let scaled_clone = scale_minimap([
            player.pos_xy[0], player.pos_xy[1],
            player.minimapScale[0], player.minimapScale[1],
            player.minimapScale[2], player.minimapScale[3]
        ], [
            clone.pos_xy[0], clone.pos_xy[1],
            clone.minimapScale[0], clone.minimapScale[1],
            clone.minimapScale[2], clone.minimapScale[3]
        ]);
        console.log(`
        you ${player.pos_xy}
        clone ${scaled_clone.x} ${scaled_clone.y}
        1st cond ${player.pos_xy[0] > scaled_clone.x}
        2nd cond ${player.pos_xy[1] > scaled_clone.y}
        `);

        if (player.pos_xy[0] > scaled_clone.x) {
            key_up(rest_keys()[3]);
            key_down(rest_keys()[1]);
        } else {
            key_up(rest_keys()[1]);
            key_down(rest_keys()[3]);
        }
        if (player.pos_xy[1] > scaled_clone.y) {
            key_up(rest_keys()[2]);
            key_down(rest_keys()[0]);
        } else {
            key_up(rest_keys()[0]);
            key_down(rest_keys()[2]);
        }
        moving = true;
    } else {
        if (moving) {
            for (let i = 0; i < move_keys().length; i++) {
                key_up(move_keys()[i]);
            }
            moving = false;
        }
        set_goal(player.pos_xy[0], player.pos_xy[1]);
    }
}

//toggle GUI
function toggle_GUI(state) {
    let cont = document.getElementById("mb-container");
    if (state) {
        cont.style.display = "none";
    } else {
        cont.style.display = "block";
    }
}

//Drone Repel
let timings = { //long 50/50 by default
    main: 50000,
    inside: 25000
}

let reset_finished = false;

function repel_loop() {
    if (inGame && config.drone_repel) {
        reset_finished = false;
        key_down("ShiftLeft");
        setTimeout(() => {
           key_up("ShiftLeft");
        }, timings.inside);
    } else {
        if (!reset_finished) {
            key_up("ShiftLeft");
            reset_finished = true;
        }
    }
}
setInterval(repel_loop, timings.main);

function update_timings() {
    switch (config.drone_repel_mode) {
        case "long 50/50":
            timings.main = 50000;
            timings.inside = 25000;
            break
        case "infinite 70/30":
            timings.main = 10000;
            timings.inside = 7000;
            break
        case "infinity":
            timings.main = 100000;
            timings.inside = 99999;
            break
        case "Necromancer":
            timings.main = 50000;
            timings.inside = 20000;
            break
    }
}

//replace move keys of every clone to your main move keys
function new_move_keys(player, clone){
    if(player.keys[0] != clone.keys[0]){
        let btn = document.getElementById("move-keys");
        let index = clone.keys[0]-1;
        config.move_keys = key_option[index];
        btn.innerHTML = `Move Keys: ${key_option[index]}`;
    }
}

//this loop will handle your auto fire + auto spin, separately from init
function handle_sf(){
    if(connected && inGame && config.copy_keys){
        if(!is_main){
            if(you.keys[8] != him.keys[8]){
                key_press("KeyE");
                you.keys[8] = him.keys[8];
            }
            if(you.keys[9] != him.keys[9]){
                key_press("KeyC");
                you.keys[9] = him.keys[9];
            }
        }
    }
}

setInterval(handle_sf, 2500);

//initialise (I luv this shit)
function init() {
    window.requestAnimationFrame(init);
    inGame = status(0);
    connected = status(1);
    if (connected) {
        if (!you) {
            you = new Player();
            console.log("%c[r!PsAw] Player found, reading info for multibox... ^w^", "color: green");
        }
        load_GUI();
        if (inGame) {
            update_move_keys(you);
            update_build(you);
            update_window_sizes(you);
            update_minimap_Scale(you);
            update_pos(you);
            move_to_goal(you);
            update_timings();
            if (is_main) {
                save_sf(you);
                save_info(you);
            } else {
                him = get_info();
                if (config.copy_keys) {
                    copy_key_inputs(him);
                    clump(you, him);
                }
                new_move_keys(you, him);
                config.copy_build ? copy_build(read_build(him)) : null;
                config.copy_mouse ? copy_mouse_inputs(you, him) : null;
            }
        }else{
            update_sf(" OFF", "autoFire");
            update_sf(" OFF", "autoSpin");
        }
    }
}
window.requestAnimationFrame(init);

//canvas debug

setTimeout(() => {
    let gui = () => {
        if (!is_main) {
            ctx.beginPath();
            ctx.moveTo(debug[0], debug[1]);
            ctx.lineTo(debug[2], debug[3]);
            ctx.strokeStyle = "black";
            ctx.stroke();
        }
        window.requestAnimationFrame(gui);
    };
    gui();
    setTimeout(() => {
        gui();
    }, 5000);
}, 1000);