bonk editor

Makes editor a bit better

当前为 2025-04-21 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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