bonk editor

Makes editor a bit better

// ==UserScript==
// @name         bonk editor
// @version      e2.1_t1.4
// @description  Makes editor a bit better
// @author       Apx
// @match        https://bonk.io/gameframe-release.html
// @match        https://bonkisback.io/gameframe-release.html
// @run-at       document-end
// @namespace    https://greasyfork.org/users/1272759
// @grant        none
// ==/UserScript==


const scriptName = "editer";

const guiSettings = {
    noWindow: true,
    settingsContent: null,
    bonkLIBVersion: "1.1.3",
    modVersion: "e2.1_t1.4",
}

window.bonkEditor = {
    /*transparency mod*/ fromColor: function (color) {
        return {
            hex: color & 0xffffff,
            transparency: (color >> 24 & 127) == 0? 1 : ((color >> 24 & 127) - 1) / 100,
            noshadow: !!(color >> 31 & 1),
        };
    },
    /*transparency mod*/ toColor: function () { // for slider
        return (document.getElementById("mapeditor_colorpicker_transparency_slider").value != "1"? (document.getElementById("mapeditor_colorpicker_transparency_slider").valueAsNumber * 100 + 1) * 0x01000000 : 0) +
            (document.getElementById("mapeditor_colorpicker_noshadow").checked == true? 0x80000000 : 0);
    },
    /*transparency mod*/ elements: function () {
        return {
            slider: document.getElementById("mapeditor_colorpicker_transparency_slider"),
            noshadow: document.getElementById("mapeditor_colorpicker_noshadow"),
        };
    },
    /*transparency mod*/ updateElements: function (hex) {
        if(hex){
            if(window.bonkEditor.fromColor(hex).noshadow) document.getElementById("mapeditor_colorpicker_noshadow").checked = true;
            else document.getElementById("mapeditor_colorpicker_noshadow").checked = false;
            document.getElementById("mapeditor_colorpicker_transparency_slider").value = window.bonkEditor.fromColor(hex).transparency;
        }
        let label = document.getElementById("mapeditor_colorpicker_transparencylabel");
        switch (parseFloat(document.getElementById("mapeditor_colorpicker_transparency_slider").value)) {
            case 1:
                label.textContent = `Transparency: opaque`;
                break;
            case 0:
                label.textContent = `Transparency: transparent`;
                break;
            default:
                label.textContent = `Transparency: ${document.getElementById("mapeditor_colorpicker_transparency_slider").valueAsNumber * 100}%`;
        }
    },
    rangeView: 100 * 1024,
    isCrashed: false,
    degree: 1,
    scale: () => 1,
    lineWidth: function (width) {
        return width * 2 / window.bonkEditor.scale();
    },
    doAlignment: false,
    alignmentMode: 0,
    createVertexData: physics => {
        let vertexData = [];
        const bodies = physics.bodies;
        const shapes = physics.shapes;
        const fixtures = physics.fixtures;
        let calcCoordinates = (vertice, shapeAngle, shapePos, bodyAngle, bodyPos) => {
            let rotate = (vertex, angle) => {
                let n = [ Math.cos(angle) * vertex[0], Math.sin(angle) * vertex[0] ];
                let d = [ Math.cos(angle + Math.PI/2) * vertex[1], Math.sin(angle + Math.PI/2) * vertex[1] ];
                return [n[0] + d[0], n[1] + d[1]];
            }
            let result = rotate( rotate( vertice, shapeAngle ).map( ( x, i ) => x + shapePos[i] ), bodyAngle).map( ( x, i ) => x + bodyPos[i] ).map( x => Math.round( x * 1000000 ) ).map( x => x / 1000000);
            return result;
        }
        for(let body in bodies) {
            for(let fx in bodies[body].fx) {
                let shape = shapes[fixtures[bodies[body].fx[fx]].sh];
                if(shape.type == "po") {
                    shape.v.forEach(x => {
                        vertexData.push({
                            c: calcCoordinates(x, shape.a, shape.c, bodies[body].a, bodies[body].p),
                            type: "po"
                        });
                    });
                }
                else if(shape.type == "bx") {
                    vertexData.push(
                        {c: calcCoordinates([shape.w / 2, shape.h / 2], shape.a, shape.c, bodies[body].a, bodies[body].p), type: "bx"},
                        {c: calcCoordinates([-shape.w / 2, shape.h / 2], shape.a, shape.c, bodies[body].a, bodies[body].p), type: "bx"},
                        {c: calcCoordinates([-shape.w / 2, -shape.h / 2], shape.a, shape.c, bodies[body].a, bodies[body].p), type: "bx"},
                        {c: calcCoordinates([shape.w / 2, -shape.h / 2], shape.a, shape.c, bodies[body].a, bodies[body].p), type: "bx"}
                    );
                }
            }
        }
        window.bonkEditor.vertexData = vertexData;
    },
    vertexData: null,
    findClosestVertice: function (point, max = 20) {
        if(!this.vertexData) return;
        if(!point.x) point = {x: point[0], y: point[1]}
        let filtered = [];
        let closest = null;
        this.vertexData.forEach(elem => {
            if(Math.abs(point.x - elem.c[0]) < max && Math.abs(point.y - elem.c[1]) < max) {
                filtered.push([elem.c[0], elem.c[1]])
            }
        });
        filtered.forEach(elem => {
            let clc = Math.sqrt((point.x - elem[0])**2 + (point.y - elem[1])**2);
            if(clc < max) {
                if(!closest || closest.d > clc) closest = {d: clc, c: elem};
            }
        });
        return closest;
    },
    calcPos: (vertice, body) => {
        vertice = [vertice.x - body.p[0], vertice.y - body.p[1], -body.a];
        return [vertice[0] * Math.cos(vertice[2]) - vertice[1] * Math.sin(vertice[2]), vertice[0] * Math.sin(vertice[2]) + vertice[1] * Math.cos(vertice[2])];
    }
}

function injector(src){
    let newSrc = src;
    let CSS = document.createElement('style');
    CSS.innerHTML = `
        #mapeditor_colorpicker_transparency_slider{
	        margin-left: 0px;
	        background-color: transparent;
	        margin-top: -2px;
        }
        #mapeditor_colorpicker_transparencylabel, #mapeditor_colorpicker_shadowlabel {
            color: #ffffff;
        }
        #mapeditor_colorpicker_noshadow {
            position: relative;
            top: 1px;
            margin-left: 6px;
            margin-bottom: 11px;
        }
        .mapeditor_colorpicker_existingtile {
            background-repeat: no-repeat;
        }
    `;
    document.getElementsByTagName('head')[0].appendChild(CSS);

    let transparencylabel = document.createElement('span');
    document.getElementById('mapeditor_colorpicker').insertBefore(transparencylabel, document.getElementById("mapeditor_colorpicker_existingcontainer"));
    transparencylabel.outerHTML = `<span id="mapeditor_colorpicker_transparencylabel">Transparency: Opaque</span>`;
    let slider = document.createElement('input');
    document.getElementById('mapeditor_colorpicker').insertBefore(slider, document.getElementById("mapeditor_colorpicker_existingcontainer"));
    slider.outerHTML = `<input type="range" class="compactSlider compactSlider_classic" min="0" max="1" value="1" step="0.01" id="mapeditor_colorpicker_transparency_slider">`;
    let shadowlabel = document.createElement('span');
    document.getElementById('mapeditor_colorpicker').insertBefore(shadowlabel, document.getElementById("mapeditor_colorpicker_existingcontainer"));
    shadowlabel.outerHTML = `<span id="mapeditor_colorpicker_shadowlabel">No Shadow</span>`;
    let shadowcheckbox = document.createElement('input');
    document.getElementById('mapeditor_colorpicker').insertBefore(shadowcheckbox, document.getElementById("mapeditor_colorpicker_existingcontainer"));
    shadowcheckbox.outerHTML = `<input type="checkbox" id="mapeditor_colorpicker_noshadow"></input>`;

    // thanks salama
    const chatHandler = e => {
        if(e.keyCode === 13) {
            if(e.target.value.length > 0) {
                if(e.target.value[0] === "/") {
                    let command = e.target.value.split(" ")[0].substring(1);
                    let args = e.target.value.split(" ").slice(1);
                    newArgs = [];
                    for(let i = 0; i < args.length; i++) {
                        if(args[i][0] === '"' && args[i][args[i].length - 1] !== '"') {
                            let str = args[i];
                            for(let j = i + 1; j < args.length; j++) {
                                str += " " + args[j];
                                if(args[j][args[j].length - 1] === '"') {
                                    i = j;
                                    break;
                                }
                            }
                            newArgs.push(str.substring(1, str.length - 1));
                        }
                        else if(args[i][0] === '"' && args[i][args[i].length - 1] === '"') {
                            newArgs.push(args[i].substring(1, args[i].length - 1));
                        }
                        else {
                            newArgs.push(args[i]);
                        }
                    }
                    args = newArgs;
                    // Save without reference
                    let oldMsg = e.target.value + "";
                    e.target.value = "";
                    if(command == "help") {
                        window.bonkEditor.menu.showStatusMessage("/rangeview 0 to 1024 KB/MB -- Sets rangeview for maps (for loading huge maps). Default value is 100KB","#b53030",false);
                        //return;
                    }
                    if(command == "rangeview") {
                        if(args.length > 1) {
                            if(isNaN(args[0]) || (args[1] != "KB" && args[1] != "MB")) {
                                window.bonkEditor.menu.showStatusMessage("* Unknown parameters","#b53030",false);
                                return;
                            }
                            window.bonkEditor.rangeview = Math.max(0, Math.min(1024, parseInt(args[0])));
                            window.bonkEditor.degree = args[1] == "KB"? 1 : 2;
                            localStorage.setItem("bonkEditor", JSON.stringify({
                                rangeView: window.bonkEditor.rangeView,
                                degree: window.bonkEditor.degree,
                            }));
                        }
                        else if(args.length == 1) {
                            if(args[0].match(/[0-9]+/) == null || (args[0].match(/[A-Za-z]+/) != "KB" && args[0].match(/[A-Za-z]+/) != "MB")) {
                                window.bonkEditor.menu.showStatusMessage("* Unknown parameters","#b53030",false);
                                return;
                            }
                            window.bonkEditor.rangeview = Math.max(0, Math.min(1024, parseInt(args[0].match(/[0-9]+/))));
                            window.bonkEditor.degree = args[0].match(/[A-Za-z]+/) == "KB"? 1 : 2;
                            localStorage.setItem("bonkEditor", JSON.stringify({
                                rangeView: window.bonkEditor.rangeView,
                                degree: window.bonkEditor.degree,
                            }));
                        }
                        else {
                            window.bonkEditor.menu.showStatusMessage("* Unknown parameters","#b53030",false);
                        }
                        return;
                    }
                    else {
                        e.target.value = oldMsg;
                    }
                }
            }
        }
    }

    document.getElementById("newbonklobby_chat_input").addEventListener("keydown", chatHandler, true);
    document.getElementById("ingamechatinputtext").addEventListener("keydown", chatHandler, true);

    function patch (src, newsrc) {
        newSrc = newSrc.replace(src, newsrc);
    };
    function log(regex){
        console.log(typeof(regex) == "string"?regex:(Array.isArray(regex)?regex:newSrc.match(regex)));
    };


    // TRANSPARENCY (alpha)

    // add 4th channel
    patch(/<= 0xffffff\){/, `<= 0xffffffff){`);

    // add shape transparency to map preview
    const mapPreviewFixtColor = newSrc.match(/(?<=\(0xff0000\);).*?;/);
    let color = mapPreviewFixtColor[0].match(/(?<=\().*?\)/)[0].replace(")", "");
    patch(mapPreviewFixtColor, `${mapPreviewFixtColor[0].split("(")[0]}(window.bonkEditor.fromColor(${color}).hex, window.bonkEditor.fromColor(${color}).transparency);`);

    // add shape transparency to map game & editor
    const mapFixtColor = newSrc.match(/\];}}}else {this.*?(?=\))/);
    console.log(mapFixtColor)
    color = mapFixtColor[0].split("(")[1];
    patch(mapFixtColor,`${mapFixtColor[0].split("(")[0]}(window.bonkEditor.fromColor(${color}).hex, window.bonkEditor.fromColor(${color}).transparency`);

    // color picker (add transparency and no shadow)
    const colorPicker = newSrc.match(/,[a-zA-Z0-9\$_]{3}[[0-9]{1,3}]\);}}}(?=(?!catch)...[[0-9]{1,3}](?=\[))/);
    const onColorPicker = newSrc.match(/(?:...\[[0-9]{1,3}]=...\[0]\[[0-9]{1,3}];){3}(?=...\(false)/);
    const saveColor = newSrc.match(/\)\);}...\[[0-9]{1,3}]=null/);
    patch(colorPicker, colorPicker + `
    window.bonkEditor.elements().slider.oninput = function(){
        ${newSrc.match(new RegExp(`(?<=${colorPicker[0].replaceAll("[", "\\[").replace(")", "\\)")}.*?0.73;).*?false\\)`))};
    };
    window.bonkEditor.elements().slider.onchange=window.bonkEditor.elements().slider.oninput;
    window.bonkEditor.elements().noshadow.onchange=window.bonkEditor.elements().slider.oninput;
    `);
    patch(onColorPicker, onColorPicker[0] + `window.bonkEditor.updateElements(arguments[0]);`);
    patch(saveColor, saveColor[0].replace("))",`),window.bonkEditor.toColor())`));

    // right color picker
    const rightColorPicker = newSrc.match(/\]\],function\(...\){(?:[^\.]*?;){3}/);
    color = rightColorPicker[0].split("]]=");
    patch(rightColorPicker, `${color[0]}]]=${color[1].replace(";","")} + (arguments[1] == undefined? 0 : arguments[1]);window.bonkEditor.updateElements();`);

    // left color picker
    const leftColorPicker = newSrc.match(/;...\(\);},null\);};/);
    patch(leftColorPicker, ` + (arguments[1] == undefined? 0 : arguments[1]);window.bonkEditor.updateElements();` + leftColorPicker);

    // no shadows in game
    const gameShadow = newSrc.match(/if\(this.shapes\[...\].shadowTexture/);
    patch(gameShadow, `if(window.bonkEditor.fromColor(arguments[0].physics.fixtures[${gameShadow[0].match(/(?<=\[)...(?=\])/)}].f).noshadow){}else ` + gameShadow);

    // no shadows in preview
    const shadow = newSrc.match(/\){...\[[0-9]{1,3}]=0\.17;/);
    patch(shadow, `&&!window.bonkEditor.fromColor(${newSrc.match(new RegExp(`if\\([^;]*?` + shadow[0].replace(")","\\)").replace("[","\\[")))[0].match(/(?<=if\()...\[[0-9]{1,3}]/)}.f).noshadow` + shadow[0]);

    // existing tiles
    const tile = newSrc.match(/[a-zA-Z0-9\$_]{3}\(false\);};}}function ...\(...(?:,...){3}\)/)[0];
    patch(tile, `
    window.bonkEditor.updateElements(this.name);
    ` + tile.split("}}")[0] + `
    let elem = document.getElementById("mapeditor_colorpicker_existingcontainer").lastChild;
    let color = window.bonkEditor.fromColor(elem.name);
    if(color.transparency < 1) {
        elem.style.backgroundImage = \`url("data:image/svg+xml,%3Csvg viewBox='0 0 23 10' xmlns='http://www.w3.org/2000/svg' fill='\${elem.style.backgroundColor}'%3E%3Cpath d='m23,0 l-23,0v10'/%3E\${color.noshadow? "%3Ccircle style='fill:%23000' cx='11.5' cy='5' r='3.5'/%3E" : ''}%3C/svg%3E")\`;
        elem.style.backgroundColor = elem.style.backgroundColor.replace("rgb", "rgba").replace(")", \`, \${color.transparency})\`);
    }
    if(color.noshadow && elem.style.backgroundImage.length == 0) {
        elem.style.backgroundImage = \`url("data:image/svg+xml,%3Csvg viewBox='0 0 23 10' xmlns='http://www.w3.org/2000/svg' fill='%23000'%3E%3Ccircle style='fill:%23000' cx='11.5' cy='5' r='3.5'/%3E%3C/svg%3E")\`;
    }
    }}` + tile.split("}}")[1])

    // EDITOR
    const updateRenderer = newSrc.match(/Error\(\);break;}...\(true\)/)[0].split("}")[1].split("(")[0];
    const widthRoundingRegex = newSrc.match(/[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}]]\[[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}]\[[0-9]{1,3}]]=Math\[[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}]\[[0-9]{1,3}]]\([a-zA-Z0-9\$_]{3}\[[0-9]{2,3}]\);[a-zA-Z0-9\$_]{3}\(true\)/)[0];
    const rectPosRegex = newSrc.match(/[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}]\[[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}]\[[0-9]{1,3}]]\[[0-1]]=Math\[[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}]\[[0-9]{1,3}]]\([a-zA-Z0-9\$_\[\]]+\);/g).map((x) => {return x.split("=")});
    const platZindex = newSrc.match(/function [a-zA-Z0-9\$_]{3}\(\){[a-zA-Z0-9-+=_ \$;\(\)[\]{}\.,!]*?}}[a-zA-Z0-9\$_]{3}\(\);[a-zA-Z0-9\$_]{3}\(true\);}/g)[0];
    const arrayBufferRegex = newSrc.match(/(?<=new ArrayBuffer\()k7V\.Q5\$\(100,1024\)/)[0];
    const spawnId = newSrc.match(/[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}](?=--;}else {(?!if))/)[0];

    // move up / down spawns
    let vars = platZindex.match(/(?<![\.a-zA-Z])(?:[a-zA-Z0-9\$_]{3})(?=[\[=]{1})(?:\[[0-9]{1,3}])?/g);
    const modifiedPlatZindex = platZindex
    .replace(/}}(?!else)/, `
                }
            }
        }
        else{
            if(this==${vars[10]}){
                if(${vars[4]}.spawns[${spawnId}-1]!=undefined){
                    ${vars[15]}=${vars[4]}.spawns[${spawnId}-1];
                    ${vars[4]}.spawns[${spawnId}-1]=${vars[0]}[100];
                    ${vars[4]}.spawns[${spawnId}]=${vars[15]};
                    ${spawnId}--;
                }
            }
            else if(this==${vars[33]}){
                if(${vars[4]}.spawns[${spawnId}+1]!=undefined){
                    ${vars[38]}=${vars[4]}.spawns[${spawnId}+1];
                    ${vars[4]}.spawns[${spawnId}+1]=${vars[0]}[100];
                    ${vars[4]}.spawns[${spawnId}]=${vars[38]};
                    ${spawnId}++;
                }
            }
        }`)
    .replace(/if\(.*?\){.*?}/, "")
    .replace(/;if\(.*?\){.*?}/, `;${vars[0]}[100]=${vars[4]}.spawns[${spawnId}];if(${vars[3]} == -1 && ${vars[4]}.spawns.indexOf(${vars[0]}[100]) == -1){return;}if(${vars[3]}!=-1){`);
    patch(platZindex,modifiedPlatZindex);

    // disable width rounding to integers
    patch(widthRoundingRegex.split(";")[0] + ";", `${widthRoundingRegex.split("=")[0]}=${widthRoundingRegex.split("=")[1].split(";")[0].match(/.{7}(?=\))/)};`);

    // disable rectangle position rounding to integers
    for(let i = 0; i < 4; i++) patch(rectPosRegex[i].join("="), `${rectPosRegex[i][0]}=${rectPosRegex[i][1].match(/.{6}(?=\))/)};`);

    // replace the minimum number of width, height and radius with MIN_VALUE (values lower than 1e-100 are not recommended)
    const precission = newSrc.match(/function [a-zA-Z0-9\$_]{3}\([a-zA-Z0-9\$_]{3}\){[a-zA-Z0-9\$_\[\]= ]+;[a-zA-Z0-9\$_\[\]=]+;[a-zA-Z0-9\$_\[\]=]+\*=10000;[a-zA-Z0-9\$_\[\]=]+\([a-zA-Z0-9\$_\[\]=]+\);[a-zA-Z0-9\$_\[\]=]+\/=10000;return [a-zA-Z0-9\$_\[\]=]+;}/)[0];
    patch(`min:1,`, `min:0.0001,`);
    patch(precission ,`function ${precission.split(" ")[1].substring(0,3)}(arg_){return arg_;}`);

    // replace the constant with a variable
    patch(arrayBufferRegex ,`(window.bonkEditor.rangeView*(1024**window.bonkEditor.degree))`);

    // alignment
    const newPolyPlat = newSrc.match(/{x:0,y:0};...\[[0-9]{1,3}]=null;(?=[a-zA-Z0-9\$_]{3})/);
    const mapObject = newSrc.match(/0,999999\)\){...\[[0-9]{1,3}]/)[0].split("{")[1];
    const editorPreview = newSrc.match(/\< 3\){return;}...\[[0-9]{1,3}]/)[0].split("}")[1];
    patch(newPolyPlat, newPolyPlat + `window.`);
    const createPolygon = newSrc.match(/\)\);return;}}(?=if\()/);
    let thing = newSrc.match(/[a-zA-Z0-9\$_]{3}\([a-zA-Z0-9\$_]{3}\[0]\[0]\);...\[[0-9]{1,3}]={x:0[^;]*?;[^;]*?;window\./)[0];
    const scaleMouse = thing.split("(")[0];
    const body = thing.split(";")[2].split("=")[0];
    const whiteBlackCrossPos = thing.split(";")[1].split("=")[0];
    patch(newPolyPlat + 'window.', newPolyPlat + `
    window.bonkEditor.createVertexData(${mapObject}.physics);
    window.bonkEditor.polygonPreviewArgs = null;
    let input = e => {
        window.bonkEditor.doAlignment = e.shiftKey;
        if(window.bonkEditor.polygonPreviewArgs) ${editorPreview}.drawPolygonPreview(...window.bonkEditor.polygonPreviewArgs);
    }
    document.addEventListener("keydown", input);
    document.addEventListener("keyup", input);
    document.getElementById("mapeditor_midbox_cancel_drawing").addEventListener("click", () => {window.bonkEditor.polygonPreviewArgs = null;});
    let closestDefined = false;
    this.onmousemove = function (mouse) {
        if(!window.bonkEditor.doAlignment) return;
        mouse = ${scaleMouse}(mouse);
        let closest = window.bonkEditor.findClosestVertice(mouse, 20 / ${editorPreview}.getStageScale());
        let body = ${body};
        if(closest){
            closestDefined = true;
            ${editorPreview}.drawPolygonPreview(${whiteBlackCrossPos}, [window.bonkEditor.calcPos(mouse, body)], body.p[0], body.p[1], body.a);
        }else if (closestDefined) {
            closestDefined = false;
            ${editorPreview}.drawPolygonPreview(${whiteBlackCrossPos}, [], body.p[0], body.p[1], body.a);
        }
    };`);
    patch(createPolygon, `));document.removeEventListener("keydown", input);document.removeEventListener("keyup", input);window.bonkEditor.polygonPreviewArgs = null;` + createPolygon[0].substring(3) + `
    let closestPoint = window.bonkEditor.findClosestVertice(${scaleMouse}(arguments[0]), 20 / ${editorPreview}.getStageScale());
    if(closestPoint && window.bonkEditor.doAlignment){
        let body = ${body};
        let shape = ${mapObject}.physics.shapes[${mapObject}.physics.shapes.length-1];
        shape.v.push(closestPoint.c.map((x,i) => x - body.p[i]));
        let v = shape.v.slice();
        let mouse = ${scaleMouse}(arguments[0]);
        v.push(window.bonkEditor.calcPos(mouse, body));
        ${editorPreview}.drawPolygonPreview(${whiteBlackCrossPos}, v, body.p[0], body.p[1], body.a);
        return;
    }`);
    const polygonPreview = newSrc.match(/[a-zA-Z0-9\$_]{3}\[0]\[4];}[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}]/)[0].split("}")[1];
    patch(newSrc.match(/[a-zA-Z0-9\$_]{3}\[0]\[4];}(?=[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}])/), newSrc.match(/[a-zA-Z0-9\$_]{3}\[0]\[4];}(?=[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}])/) + `if(arguments[1].length > 0){
    window.bonkEditor.polygonPreviewArgs = arguments;
    let vertices = arguments[1].slice().reverse();
    let closestPoint = window.bonkEditor.findClosestVertice(vertices[0].map((x,i) => x + arguments[2+i]), 20 / this.getStageScale());
    if(Math.sqrt(((vertices[0][0] - arguments[1][0][0])**2) + ((vertices[0][1] - arguments[1][0][1])**2)) < 20 / this.getStageScale() && arguments[1].length > 2) {
        let line = new PIXI.Graphics();
        line.lineStyle(window.bonkEditor.lineWidth(4), 0x7777ff, 0.75);
        line.moveTo(...arguments[1][0]);
        line.lineTo(...vertices[1]);
        ${polygonPreview}.addChild(line);
    }
    else if(closestPoint && window.bonkEditor.doAlignment) {
        let line = new PIXI.Graphics();
        line.lineStyle(window.bonkEditor.lineWidth(4), 0x7777ff, 0.5);
        line.moveTo(closestPoint.c[0] - arguments[2], closestPoint.c[1] - arguments[3]);
        line.lineTo(...(vertices[1]?vertices[1]:vertices[0]));
        ${polygonPreview}.addChild(line);
    }
    }`);

    // anti-crash type 1
    const anticrash1 = newSrc.match(/(?<=null)\){var [a-zA-Z0-9\$_]{3}=[a-zA-Z0-9\$_]{3}\..*?;/g);
    for(let i = 1; i < anticrash1.length; i++) {
        patch(anticrash1[i], `&&this.shapes[${anticrash1[i].split("=")[1].replace(";","")}]!=undefined${anticrash1[i]}`);
    }
    // anti-crash type 2
    const anticrash2 = newSrc.match(/(?<=0xccbbaa;)if\(this...../);
    patch(anticrash2,`if(this.capFill==null){return;}${anticrash2}`);

    // 0fps fix
    const gameExit = newSrc.match(/\(\);[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}]=false;}else {.*?}/);
    const frame = newSrc.match(/requestAnimationFrame\([a-zA-Z0-9\$_]{1,3}\);(?=})/);
    const frameDispatch = newSrc.match(/[a-zA-Z0-9\$_]{1,3}\(\){}(;window.*?;(?=func))?function [a-zA-Z0-9\$_]{1,3}\(\){.*?;.*?;.*?(?=;)/)[0].split(";").reverse()[0];
    const visibilityChange = newSrc.match(/}\);}document.*?{/);
    patch(visibilityChange, visibilityChange + `window.bonkEditor.visibilityStateChanged = true;`);
    patch(frameDispatch, `try{${frameDispatch}}catch(e){window.bonkEditor.isCrashed = true;throw e;}`);
    patch(gameExit, gameExit + `if(window.bonkEditor.isCrashed){window.bonkEditor.isCrashed = false;${frame}}`);

    // anti circle crash
    let antiCircleCrash = newSrc.match(/createTexture\(...,...\) {/);
    patch(antiCircleCrash, antiCircleCrash + `return;`)


    // menu
    const menuRegex = newSrc.match(/== 13\){...\(\);}}/)[0];
    patch(menuRegex, menuRegex + "window.bonkEditor.menu = this;");

    // anti lobby kick
    const ws = window.WebSocket.prototype.send;
    window.WebSocket.prototype.send = function(args){
        if(this.url.includes("socket.io/?EIO=3&transport=websocket&sid=") && typeof(args) == "string" && args.length > 250000){
            window.bonkEditor.menu.showStatusMessage("* Protected from being kicked out of the room.","#b53030",false);
            return;
        }
        ws.call(this,args);
    }

    // cap zone highlight
    const capZoneColor = newSrc.match(/}else if\(...\[[0-9]{1,3}] == ...\[[0-9]{1,3}] \|\| ...\[[0-9]{1,3}] == ...\[[0-9]{1,3}]\){...\[[0-9]{1,3}]/)[0];
    const hasCZ = newSrc.match(/if\(...\[[0-9]{1,3}]\[...\[[0-9]{1,3}]]\){[^;]*?\(3,0x/)[0].substring(3).split(")")[0];
    patch(capZoneColor, `if(${capZoneColor.split("(")[1].split(")")[0]} || window.bonkEditor.highlightedCapZone == ${hasCZ}.capID){${capZoneColor.split("{")[1]}.lineStyle(3, 0xff0000, 1);}` + capZoneColor);
    const capZoneSelection = newSrc.match(/...\[[0-9]{1,3}],1\);...\[[0-9]{1,3}]=-1/)[0].split(",")[0];
    const addCZHighlightFunction = newSrc.match(/false;};...\[[0-9]{1,3}]=new TWEEN/);
    patch(addCZHighlightFunction, `false;};this.highlightCapZone=function(capZone){window.bonkEditor.highlightedCapZone=capZone};this.clearHighlightCapZone=function(){window.bonkEditor.highlightedCapZone=null};` + addCZHighlightFunction[0].split(";};")[1]);
    const onHoverCZFunction = newSrc.match(/};[^;]*?=function\(\){};[^;]*?=function\(\){};};for/);
    patch(onHoverCZFunction, onHoverCZFunction[0].replaceAll("function(){}", `(e)=>{if(e.type == "mouseover"){${editorPreview}.highlightCapZone(arguments[0]);${updateRenderer}(true);}else if(e.type == "mouseout"){${editorPreview}.clearHighlightCapZone();${updateRenderer}(true);}}`));

    // zoom
    const xdidkhowtoname = newSrc.match(/[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}]=0.5;[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}]=\[];/)
    patch(xdidkhowtoname, xdidkhowtoname[0] + `window.bonkEditor.arguments = arguments;`);
    const zoom = newSrc.match(/(?<=4,500\)\);)[a-zA-Z0-9\$_]{3}\[[0-9]{1,3}]/)[0];
    patch(new RegExp(zoom.replaceAll("[","\\[") + `=new.*?;`), zoom + `=new PIXI.Container();window.bonkEditor.scale = function(){return ${zoom}.transform.scale.x;};`);

    // update render on zoom in/out
    const mouseEvents = newSrc.match(/(...\[[0-9]{1,3}]\[.{0,12}\]\[[0-9]{1,3}\]]\(\)\[.{0,20}]=...;){2}/)[0];
    const theResetFunction = src.match(new RegExp("function ...\\(\\){.{0,40}\(...\\[\\d+\\]=-1;){2}.{0,40}(...\\(true\\);).{0,40}(...\\(\\);){2}[^}]+\\}"))[0]; // thanks kklkkj & salama
    patch(mouseEvents, `${mouseEvents.split(")[")[0]}).onwheel=function(){${theResetFunction.match(/...\(true\);/)}${newSrc.match(/function\(\){...\[[0-9]{1,3}]=!...\[[0-9]{1,3}];...\(\)/)[0].split(";")[1]};};`); // i dunno why on mouse wheel P5l not called if you have kklee enabled
    patch(new RegExp(`(?<=${newSrc.match(/(?<=\(0\.8\);if\()...\[[0-9]{1,3}]/)[0].replace("[","\\[")} == false\\){...\\()false`, "g"), `true`);

    // make line width variable
    patch(/2,0x7777ff/, `window.bonkEditor.lineWidth(2),0x7777ff`);
    patch(/1(?=,0xcccccc)/g, `window.bonkEditor.lineWidth(1)`);
    patch(/1(?=,0xf4a7a7)/g, `window.bonkEditor.lineWidth(1)`);

    patch(/4(?=,0x[F0]{6})/g, `window.bonkEditor.lineWidth(4)`);
    patch(/(?<=\(-?)10(?=,0\))/g, `window.bonkEditor.lineWidth(10)`);
    patch(/(?<=\(0,-?)10(?=\))/g, `window.bonkEditor.lineWidth(10)`);

    patch(/1,0xffffff,0.5/g, `window.bonkEditor.lineWidth(5),0xffffff,0.5`);
    patch(/0\.5,0xffffff/g, `window.bonkEditor.lineWidth(0.5),0xffffff`);

    if(src === newSrc) throw "Injection failed!";
    console.log(`${scriptName} injector run`);
    return newSrc;
}

if(!window.bonkCodeInjectors) window.bonkCodeInjectors = [];
window.bonkCodeInjectors.push(bonkCode => {
    try {
        return injector(bonkCode);
    } catch (error) {
        alert(`Whoops! ${scriptName} was unable to load.`);
        throw error;
    }
});

if (window.bonkHUD) {
    function updateVariables (variables) {
        for(let i = 0; i < Object.keys(variables).length; i++) window.bonkEditor[Object.keys(variables)[i]] = Object.values(variables)[i];
    }
    let storage = localStorage.getItem("bonkEditor");
    if(storage != null){
        storage = JSON.parse(storage);
        updateVariables(storage);
    } else storage = {};
    const label = (target, ...elements) => {
        let div = document.createElement("div");
        div.margin = "5px";
        target.appendChild(div);
        for(let element in elements) {
            if(typeof(elements[element]) == "string"){
                let labelElement = document.createElement("label");
                labelElement.classList.add("bonkhud-settings-label");
                labelElement.textContent = elements[element];
                labelElement.style.padding = "0 5px";
                labelElement.style.display = "inline-block";
                div.appendChild(labelElement);
            } else div.appendChild(elements[element]);
            div.lastChild.style.verticalAlign = "middle";
        }
    }
    // functions
    const notAppliedText = target => {
        if(target.lastChild.className == "bonkeditor_not_applied_text") return;
        let span = document.createElement("span");
        span.className = "bonkeditor_not_applied_text";
        span.textContent = "* not applied";
        span.style["margin-left"] = "5px";
        span.style.color = "#000000aa";
        span.style.verticalAlign = "middle";
        target.appendChild(span);
    }
    const checkbox = value => {
        let checkbox = document.createElement("input");
        checkbox.type = "checkbox";
        checkbox.checked = value;
        checkbox.style["vertical-align"] = "middle";
        return checkbox;
    }
    const inputText = value => {
        let text = document.createElement("input");
        text.style.width = "40px";
        text.style.height = "19px";
        text.style["vertical-align"] = "middle";
        text.value = value;
        return text;
    }
    let settings = window.bonkHUD.generateSection();
    guiSettings.settingsContent = settings;
    const ind = window.bonkHUD.createMod("bonk editor & transparency", guiSettings);

    // section  /(1024**window.bonkEditor.degree)
    let rangeView = inputText(storage.rangeView || window.bonkEditor.rangeView);
    rangeView.oninput = (event) => {
        event.target.value = event.target.value.replaceAll(/[^0-9]+/g, '');
        event.target.value = String(Math.min(Math.max(parseInt(event.target.value) || 0, 0), 1024));

        notAppliedText(event.target.parentNode);
    }

    let unit = document.createElement("select");
    let units = ["KB","MB"];
    units.forEach((text,key) => {
        unit.appendChild(new Option(text, key, false, (storage.degree || window.bonkEditor.degree) == key + 1));
    });
    unit.onchange = (event) => {
        window.bonkEditor.degree = parseInt(event.target.value);
        notAppliedText(event.target.parentNode);
    }

    // apply button
    let applyButton = window.bonkHUD.generateButton("Apply");
    applyButton.style.display = "inline-block";
    applyButton.style.padding = "0 5px";
    applyButton.onclick = () => {
        updateVariables({
            rangeView: parseInt(rangeView.value),
            degree: (parseInt(unit.selectedIndex) + 1),
        });
        [...settings.getElementsByClassName("bonkeditor_not_applied_text")].forEach(x => x.parentNode.removeChild(x));
        localStorage.setItem("bonkEditor", JSON.stringify({
            rangeView: window.bonkEditor.rangeView,
            degree: window.bonkEditor.degree,
        }));
    }

    // apply button
    let clearStorageButton = window.bonkHUD.generateButton("Clear Storage");
    clearStorageButton.style.display = "inline-block";
    clearStorageButton.style.padding = "0 5px";
    clearStorageButton.onclick = () => {
        localStorage.removeItem("bonkEditor");
    }

    label(settings, "Array buffer range view", rangeView, unit);
    label(settings, applyButton);
    label(settings, clearStorageButton);


    window.bonkHUD.updateStyleSettings();
}