r!PsAw Multibox public

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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);