// ==UserScript==
// @name Bonk Skin Editor Overlay
// @version 0.3
// @author Salama
// @description Adds an image overlay to skin editor
// @match https://*.bonk.io/gameframe-release.html
// @match https://*.bonkisback.io/gameframe-release.html
// @run-at document-end
// @grant none
// @license GPL-3.0-or-later
// @supportURL https://discord.gg/Dj6usq7ww3
// @namespace https://greasyfork.org/users/824888
// ==/UserScript==
(function() {
'use strict';
let overlay = document.createElement("div");
let menu = document.createElement("div");
document.getElementById("skineditor_previewbox_skincontainer").appendChild(overlay);
document.getElementById("skineditor_previewbox").appendChild(menu);
overlay.outerHTML = `<div>
<img id="skineditor_imageoverlay" style="
touch-action: none;
cursor: inherit;
position: absolute;
left: 0;
top: 0;
pointer-events: none;
opacity: 0.8;
display: none;
opacity: 0.5;
z-index: 9998;
display: none;
">
<canvas id="skineditor_imageoverlaycanvas" width="245" height="245" style="
touch-action: none;
cursor: inherit;
position: absolute;
left: 0;
top: 0;
pointer-events: none;
opacity: 0.5;
"></canvas>
</div>
</div>`;
menu.outerHTML = `<div>
<div class="newbonklobby_playerentry_balancecontainer brownButton brownButton_classic" style="
position: absolute;
bottom: 58px;
width: 40%;
margin-left: 30%;
margin-right: 10%;
">
<div class="newbonklobby_playerentry_balancetext">Overlay Opacity</div>
<input id="skineditor_opacityslider" type="range" min="0" max="100" step="1" value="50" class="compactSlider slider newbonklobby_playerentry_balanceslider" style="
width: 90%;
">
</div>
<div id="skineditor_filechooser" class="brownButton brownButton_classic buttonShadow" style="
width: 64px;
bottom: 58px;
left: 5%;
position: absolute;
height: 35px;
font-size: 14px;
">Select Overlay</div>
</div>`;
// Preview size
const ps = 245;
let overlaySelected = false;
let imageOffset = {x:0,y:0}
let flipX = false;
let flipY = false;
let ctx = document.getElementById("skineditor_imageoverlaycanvas").getContext("2d");
let scale = 1, angle = 0, d, x, y, image = document.getElementById("skineditor_imageoverlay");
overlay = document.getElementById("skineditor_imageoverlay");
const drawOverlay = () => {
ctx.save();
ctx.clearRect(0, 0, ps, ps);
ctx.setTransform(scale * (flipX ? -1 : 1), 0, 0, scale * (flipY ? -1 : 1), ps/2 + imageOffset.x, ps/2 + imageOffset.y);
ctx.rotate(angle);
ctx.drawImage(document.getElementById("skineditor_imageoverlay"), -image.width/2, -image.height/2, image.width, image.height);
ctx.restore();
overlay.style.rotate = `${angle}rad`;
overlay.style.transform = `scale(${scale * (flipX ? -1 : 1)}, ${scale * (flipY ? -1 : 1)})`;
overlay.style.left = `${(ps - image.width) / 2 + imageOffset.x}px`;
overlay.style.top = `${(ps - image.height) / 2 + imageOffset.y}px`;
}
document.getElementById("skineditor_filechooser").addEventListener("click", () => {
let a = document.createElement("input");
a.type = 'file';
document.body.appendChild(a);
a.onchange = e => {
let file = e.target.files[0];
image.src = URL.createObjectURL(file);
document.getElementById("skineditor_imageoverlay_preview").src = image.src;
image.onload = () => {
image.style.visibility = "visible";
image.style.display = "";
ctx.clearRect(0, 0, ps, ps);
let imageRatio = image.naturalWidth / image.naturalHeight;
if(image.naturalWidth > image.naturalHeight) {
image.width = ps;
image.height = ps / imageRatio;
image.style.top = (ps - image.height) / 2;
}
else {
image.width = ps * imageRatio;
image.height = ps;
image.style.left = (ps - image.width) / 2;
}
angle = 0;
scale = 1;
imageOffset = {x:0,y:0};
image.style.rotate = "0rad";
image.style.scale = 1;
image.style.top = (ps - image.height) / 2;
image.style.left = (ps - image.width) / 2;
flipX = false;
flipY = false;
if(overlaySelected) {
document.getElementById("skineditor_propertiesbox_table_x").value = imageOffset.x;
document.getElementById("skineditor_propertiesbox_table_y").value = imageOffset.y;
document.getElementById("skineditor_propertiesbox_table_angle").value = angle / Math.PI * 180;
document.getElementById("skineditor_propertiesbox_table_scale").value = scale.toFixed(2);
document.getElementById("skineditor_propertiesbox_table_flipx").textContent = flipX ? "Yes" : "No";
document.getElementById("skineditor_propertiesbox_table_flipy").textContent = flipY ? "Yes" : "No";
}
drawOverlay();
}
};
a.click();
document.body.removeChild(a);
});
document.getElementById("skineditor_opacityslider").addEventListener("input", e => {
document.getElementById("skineditor_imageoverlaycanvas").style.opacity = e.target.value / 100;
document.getElementById("skineditor_imageoverlay").style.opacity = e.target.value / 100;
});
const layerObserver = new MutationObserver(mutations => {
for(let mutation of mutations) {
for(let node of mutation.addedNodes) {
if(node.nodeName === "TR" && node.id === "0") {
let overlayLayer = document.createElement("tr");
overlayLayer.classList = ["HOVERUNSELECTED"];
overlayLayer.id = "-1";
overlayLayer.addEventListener("click", () => {
node.onclick.apply(overlayLayer);
overlaySelected = true;
// Execution will be continued in shape window's mutation observer
});
document.getElementById("skineditor_layerbox_layertable").children[0].insertBefore(overlayLayer, node);
overlayLayer.innerHTML = `
<td>
<span class="skineditor_layerbox_layername">Overlay</span>
<div class="skineditor_layerbox_layerpreview">
<img src="${image.src}" onload="this.style.display=''" onerror="this.style.display='none'" class="skineditor_layerbox_layerpreview_image" id="skineditor_imageoverlay_preview" style="display:none;">
</div>
</td>`;
}
else if(node.id !== "-1") {
node.addEventListener("click", () => {
document.getElementById("skineditor_propertiesbox_table_shape").parentNode.parentNode.style.display = "";
document.getElementById("skineditor_propertiesbox_colorrect").parentNode.parentNode.style.display = "";
document.getElementById("skineditor_imageoverlaycanvas").style.display = "";
image.style.display = "none";
document.getElementById("skineditor_propertiesbox_upbutton").style.display = "";
document.getElementById("skineditor_propertiesbox_downbutton").style.display = "";
document.getElementById("skineditor_propertiesbox_deletebutton").style.display = "";
overlaySelected = false;
drawOverlay();
});
}
}
}
});
const shapePickerObserver = new MutationObserver(mutations => {
if(!overlaySelected) return;
for(let mutation of mutations) {
if(mutation.type === "attributes" && mutation.attributeName === "style" &&
mutation.target === document.getElementsByClassName("skineditor_shapewindow")[0]) {
// It's actually the shape window close button with a wrong class name
mutation.target.getElementsByClassName("skineditor_basiccolorpicker_closebutton")[0].click();
for(let child of document.getElementById("skineditor_layerbox_layertable").children[0].children) {
child.classList = ["HOVERUNSELECTED"];
}
document.getElementById("skineditor_layerbox_layertable").children[0].children[0].classList = ["HOVERSELECTED"];
// Show properties menu
document.getElementById("skineditor_propertiesbox_blocker").style.visibility = "hidden";
document.getElementById("skineditor_propertiesbox_table_x").value = imageOffset.x.toFixed(2);
document.getElementById("skineditor_propertiesbox_table_y").value = imageOffset.y.toFixed(2);
document.getElementById("skineditor_propertiesbox_table_angle").value = (angle / Math.PI * 180).toFixed(2);
document.getElementById("skineditor_propertiesbox_table_scale").value = scale.toFixed(2);
document.getElementById("skineditor_propertiesbox_table_flipx").textContent = flipX ? "Yes" : "No";
document.getElementById("skineditor_propertiesbox_table_flipy").textContent = flipY ? "Yes" : "No";
document.getElementById("skineditor_propertiesbox_table_shape").parentNode.parentNode.style.display = "none";
document.getElementById("skineditor_propertiesbox_colorrect").parentNode.parentNode.style.display = "none";
document.getElementById("skineditor_imageoverlaycanvas").style.display = "none";
document.getElementById("skineditor_imageoverlay").style.display = "";
document.getElementById("skineditor_propertiesbox_upbutton").style.display = "none";
document.getElementById("skineditor_propertiesbox_downbutton").style.display = "none";
document.getElementById("skineditor_propertiesbox_deletebutton").style.display = "none";
}
}
});
layerObserver.observe(document.getElementById("skineditor_layerbox_layertable"), {attributes: false, childList: true, subtree: true});
shapePickerObserver.observe(document.getElementById("skineditor_propertiesbox"), {attributes: true, childlist: false, subtree: true});
document.getElementById("skineditor_propertiesbox_table_x").addEventListener("input", e => {
if(!overlaySelected) return;
imageOffset.x = parseFloat(e.target.value);
if(isNaN(imageOffset.x)) {
imageOffset.x = 0;
}
drawOverlay();
});
document.getElementById("skineditor_propertiesbox_table_y").addEventListener("input", e => {
if(!overlaySelected) return;
imageOffset.y = parseFloat(e.target.value);
if(isNaN(imageOffset.y)) {
imageOffset.y = 0;
}
drawOverlay();
});
document.getElementById("skineditor_propertiesbox_table_angle").addEventListener("input", e => {
if(!overlaySelected) return;
angle = parseFloat(e.target.value) / 180 * Math.PI;
if(isNaN(angle)) {
angle = 0;
}
drawOverlay();
});
document.getElementById("skineditor_propertiesbox_table_scale").addEventListener("input", e => {
if(!overlaySelected) return;
scale = parseFloat(e.target.value);
if(isNaN(scale)) {
scale = 1;
}
drawOverlay();
});
document.getElementById("skineditor_propertiesbox_table_flipx").addEventListener("click", e => {
if(!overlaySelected) return;
flipX = !flipX;
e.target.textContent = flipX ? "Yes" : "No";
drawOverlay();
});
document.getElementById("skineditor_propertiesbox_table_flipy").addEventListener("click", e => {
if(!overlaySelected) return;
flipY = !flipY;
e.target.textContent = flipY ? "Yes" : "No";
drawOverlay();
});
const handleButton = e => {
switch(e.target.id) {
case "skineditor_x_up":
imageOffset.x += 0.1;
document.getElementById("skineditor_propertiesbox_table_x").value = imageOffset.x.toFixed(1);
break;
case "skineditor_x_down":
imageOffset.x -= 0.1;
document.getElementById("skineditor_propertiesbox_table_x").value = imageOffset.x.toFixed(1);
break;
case "skineditor_y_up":
imageOffset.y += 0.1;
document.getElementById("skineditor_propertiesbox_table_y").value = imageOffset.y.toFixed(1);
break;
case "skineditor_y_down":
imageOffset.y -= 0.1;
document.getElementById("skineditor_propertiesbox_table_y").value = imageOffset.y.toFixed(1);
break;
case "skineditor_angle_up":
angle += 1 / 180 * Math.PI;
document.getElementById("skineditor_propertiesbox_table_angle").value++;
break;
case "skineditor_angle_down":
angle -= 1 / 180 * Math.PI;
document.getElementById("skineditor_propertiesbox_table_angle").value--;
break;
case "skineditor_scale_up":
if(scale < 0.2) {
scale += 0.005;
}
else {
scale += 0.01;
}
document.getElementById("skineditor_propertiesbox_table_scale").value = scale.toFixed(2);
break;
case "skineditor_scale_down":
if(scale < 0.2) {
scale -= 0.005;
}
else {
scale -= 0.01;
}
document.getElementById("skineditor_propertiesbox_table_scale").value = scale.toFixed(2);
break;
}
drawOverlay();
}
let buttonTimeout;
const editorButtonUp = () => {
// Clears interval too
clearTimeout(buttonTimeout);
window.removeEventListener("mouseup", editorButtonUp);
}
const editorButtonDown = e => {
if(!overlaySelected) return;
handleButton(e);
window.addEventListener("mouseup", editorButtonUp);
buttonTimeout = setTimeout(() => {
handleButton(e);
buttonTimeout = setInterval(() => {
handleButton(e);
}, 30);
}, 400);
}
for(let button of document.getElementsByClassName("skineditor_field_button")) {
button.addEventListener("pointerdown", editorButtonDown);
}
document.getElementById("skinmanager_edit").addEventListener("click", () => {
angle = 0;
scale = 1;
imageOffset = {x:0,y:0};
image.style.rotate = "0rad";
image.style.scale = 1;
image.style.top = (ps - image.height) / 2;
image.style.left = (ps - image.width) / 2;
flipX = false;
flipY = false;
image.style.display = "none";
image.style.visibility = "hidden";
image.src = "";
if(document.getElementById("skineditor_imageoverlay_preview")) {
document.getElementById("skineditor_imageoverlay_preview").style.display = "none";
document.getElementById("skineditor_imageoverlay_preview").src = "";
}
ctx.clearRect(0, 0, ps, ps);
});
})();