2025-07-31 21:28:54 -04:00
|
|
|
"use strict";
|
|
|
|
|
// WorldEdit.js (compiled)
|
2025-08-20 23:06:55 -04:00
|
|
|
// Version: 1.2.0
|
2025-07-31 21:28:54 -04:00
|
|
|
// Constants
|
|
|
|
|
const w_accentColor = "#7cff62";
|
|
|
|
|
const w_style = {
|
2025-08-11 02:28:26 -04:00
|
|
|
strokeWidth: 1,
|
|
|
|
|
selectFill: "#57b64530",
|
|
|
|
|
selectStroke: w_accentColor,
|
|
|
|
|
selectDash: true,
|
|
|
|
|
pasteFill: "#00FFFF40",
|
2025-08-20 23:06:55 -04:00
|
|
|
pasteStroke: "#00FFFF",
|
|
|
|
|
pastePixelColor: "#00FFFF44"
|
2025-07-31 21:28:54 -04:00
|
|
|
};
|
|
|
|
|
// Global variables
|
|
|
|
|
let worldEditElements = {};
|
|
|
|
|
let pastePreviewCanvas;
|
|
|
|
|
let w_state = {
|
2025-08-11 02:28:26 -04:00
|
|
|
firstSelectionPos: {x: 0, y: 0},
|
|
|
|
|
selection: null,
|
|
|
|
|
clipboard: null,
|
2025-08-20 23:06:55 -04:00
|
|
|
lastNonWorldEditElement: "unknown"
|
2025-07-31 21:28:54 -04:00
|
|
|
};
|
|
|
|
|
// Define settings
|
|
|
|
|
let w_settingsTab;
|
|
|
|
|
let w_deselectOnResetSetting;
|
|
|
|
|
dependOn("betterSettings.js", () => {
|
2025-08-11 02:28:26 -04:00
|
|
|
w_settingsTab = new SettingsTab("WorldEdit");
|
|
|
|
|
w_deselectOnResetSetting = new Setting("Deselect on reset", "deselectOnReset", settingType.BOOLEAN, false, true);
|
|
|
|
|
w_settingsTab.registerSettings("Selection", w_deselectOnResetSetting);
|
|
|
|
|
settingsManager.registerTab(w_settingsTab);
|
2025-07-31 21:28:54 -04:00
|
|
|
}, true);
|
2025-08-11 02:28:26 -04:00
|
|
|
|
2025-07-31 21:28:54 -04:00
|
|
|
// Classes
|
|
|
|
|
class Rect {
|
2025-08-11 02:28:26 -04:00
|
|
|
constructor(x, y, w, h) {
|
|
|
|
|
this.x = x;
|
|
|
|
|
this.y = y;
|
|
|
|
|
this.w = w;
|
|
|
|
|
this.h = h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static fromCorners(start, end) {
|
|
|
|
|
return new Rect(start.x, start.y, end.x - start.x, end.y - start.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static fromCornersXYXY(x, y, x2, y2) {
|
|
|
|
|
return new Rect(x, y, x2 - x, y2 - y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static fromGrid(grid, origin = {x: 0, y: 0}) {
|
|
|
|
|
return new Rect(origin.x, origin.y, grid[0].length, grid.length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get area() {
|
|
|
|
|
return this.w * this.h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get x2() {
|
|
|
|
|
return this.x + this.w;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get y2() {
|
|
|
|
|
return this.y + this.h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set x2(val) {
|
|
|
|
|
this.w = val - this.x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set y2(val) {
|
|
|
|
|
this.h = val - this.y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
copy() {
|
|
|
|
|
return new Rect(this.x, this.y, this.w, this.h);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
normalized() {
|
|
|
|
|
return Rect.fromCornersXYXY(Math.min(this.x, this.x2), Math.min(this.y, this.y2), Math.max(this.x, this.x2), Math.max(this.y, this.y2));
|
|
|
|
|
}
|
2025-07-31 21:28:54 -04:00
|
|
|
}
|
2025-08-11 02:28:26 -04:00
|
|
|
|
2025-07-31 21:28:54 -04:00
|
|
|
// Functions
|
2025-08-20 23:06:55 -04:00
|
|
|
function reverseString(str) {
|
|
|
|
|
return [...str].reverse().join("");
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 21:28:54 -04:00
|
|
|
function isPointInWorld(point) {
|
2025-08-11 02:28:26 -04:00
|
|
|
return point.x >= 0 && point.x <= width && point.y >= 0 && point.y <= height;
|
2025-07-31 21:28:54 -04:00
|
|
|
}
|
2025-08-11 02:28:26 -04:00
|
|
|
|
2025-07-31 21:28:54 -04:00
|
|
|
function limitPointToWorld(point) {
|
2025-08-11 02:28:26 -04:00
|
|
|
return {
|
|
|
|
|
x: Math.max(0, Math.min(point.x, width)),
|
|
|
|
|
y: Math.max(0, Math.min(point.y, height))
|
|
|
|
|
};
|
2025-08-11 02:19:44 -04:00
|
|
|
}
|
2025-08-11 02:28:26 -04:00
|
|
|
|
2025-08-11 02:19:44 -04:00
|
|
|
function mousePosToWorldPos(pos) {
|
2025-08-11 02:28:26 -04:00
|
|
|
const rect = canvas.getBoundingClientRect();
|
|
|
|
|
let x = pos.x - rect.left;
|
|
|
|
|
let y = pos.y - rect.top;
|
|
|
|
|
x = Math.floor((x / canvas.clientWidth) * (width + 1));
|
|
|
|
|
y = Math.floor((y / canvas.clientHeight) * (height + 1));
|
|
|
|
|
return {x: x, y: y};
|
2025-07-31 21:28:54 -04:00
|
|
|
}
|
2025-08-11 02:28:26 -04:00
|
|
|
|
2025-07-31 21:28:54 -04:00
|
|
|
function updatePastePreviewCanvas() {
|
2025-08-11 02:28:26 -04:00
|
|
|
const clipboard = w_state.clipboard;
|
|
|
|
|
if (!clipboard)
|
|
|
|
|
return;
|
|
|
|
|
const clipboardRect = Rect.fromGrid(clipboard);
|
|
|
|
|
// Create canvas
|
|
|
|
|
pastePreviewCanvas = new OffscreenCanvas(clipboardRect.w, clipboardRect.h);
|
|
|
|
|
const pastePreviewCtx = pastePreviewCanvas.getContext("2d");
|
|
|
|
|
const imageData = pastePreviewCtx.createImageData(clipboardRect.w, clipboardRect.h);
|
|
|
|
|
const buffer = new Uint32Array(imageData.data.buffer);
|
|
|
|
|
buffer.fill(0x00000000);
|
2025-08-20 23:06:55 -04:00
|
|
|
const pixelColorBinary = parseInt(reverseString(w_style.pastePixelColor.slice(1)), 16);
|
2025-08-11 02:28:26 -04:00
|
|
|
for (let y = 0; y < clipboardRect.h; y++) {
|
|
|
|
|
for (let x = 0; x < clipboardRect.w; x++) {
|
|
|
|
|
if (clipboard[y][x])
|
2025-08-20 23:06:55 -04:00
|
|
|
buffer[y * clipboardRect.w + x] = pixelColorBinary;
|
2025-08-11 02:28:26 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pastePreviewCtx.putImageData(imageData, 0, 0);
|
2025-07-31 21:28:54 -04:00
|
|
|
}
|
2025-08-11 02:28:26 -04:00
|
|
|
|
2025-07-31 21:28:54 -04:00
|
|
|
function renderSelection(ctx) {
|
2025-08-11 02:28:26 -04:00
|
|
|
const selection = w_state.selection;
|
|
|
|
|
if (!selection)
|
|
|
|
|
return;
|
|
|
|
|
const isSelecting = (mouseIsDown &&
|
|
|
|
|
(mouseType !== "middle" && mouseType !== "right") &&
|
|
|
|
|
currentElement === "w_select");
|
|
|
|
|
ctx.globalAlpha = 1.0;
|
|
|
|
|
// Fill
|
|
|
|
|
if (!isSelecting) {
|
|
|
|
|
ctx.fillStyle = w_style.selectFill;
|
|
|
|
|
ctx.fillRect(selection.x * pixelSize, selection.y * pixelSize, selection.w * pixelSize, selection.h * pixelSize);
|
|
|
|
|
}
|
|
|
|
|
// Dash if selection is big enough
|
|
|
|
|
if (w_style.selectDash && selection.w >= 2 && selection.h >= 2)
|
|
|
|
|
ctx.setLineDash([pixelSize, pixelSize]);
|
|
|
|
|
// Stroke
|
|
|
|
|
ctx.strokeStyle = w_style.selectStroke;
|
|
|
|
|
ctx.lineWidth = w_style.strokeWidth;
|
|
|
|
|
ctx.strokeRect(selection.x * pixelSize, selection.y * pixelSize, selection.w * pixelSize, selection.h * pixelSize);
|
|
|
|
|
ctx.setLineDash([]);
|
2025-07-31 21:28:54 -04:00
|
|
|
}
|
2025-08-11 02:28:26 -04:00
|
|
|
|
2025-07-31 21:28:54 -04:00
|
|
|
function renderPastePreview(ctx) {
|
2025-08-11 02:28:26 -04:00
|
|
|
if (currentElement !== 'w_paste')
|
|
|
|
|
return;
|
|
|
|
|
const clipboard = w_state.clipboard;
|
|
|
|
|
if (!clipboard)
|
|
|
|
|
return;
|
|
|
|
|
const clipboardRect = Rect.fromGrid(clipboard, mousePos);
|
|
|
|
|
ctx.globalAlpha = 1.0;
|
|
|
|
|
// Fill
|
|
|
|
|
ctx.fillStyle = w_style.pasteFill;
|
|
|
|
|
ctx.fillRect(clipboardRect.x * pixelSize, clipboardRect.y * pixelSize, clipboardRect.w * pixelSize, clipboardRect.h * pixelSize);
|
|
|
|
|
// Stroke
|
|
|
|
|
ctx.strokeStyle = w_style.pasteStroke;
|
|
|
|
|
ctx.lineWidth = w_style.strokeWidth;
|
|
|
|
|
ctx.strokeRect(clipboardRect.x * pixelSize, clipboardRect.y * pixelSize, clipboardRect.w * pixelSize, clipboardRect.h * pixelSize);
|
|
|
|
|
if (pastePreviewCanvas)
|
|
|
|
|
ctx.drawImage(pastePreviewCanvas, mousePos.x * pixelSize, mousePos.y * pixelSize, clipboardRect.w * pixelSize, clipboardRect.h * pixelSize);
|
2025-07-31 21:28:54 -04:00
|
|
|
}
|
2025-08-11 02:28:26 -04:00
|
|
|
|
2025-07-31 21:28:54 -04:00
|
|
|
function addWorldEditKeybinds() {
|
2025-08-11 02:28:26 -04:00
|
|
|
keybinds.w = () => {
|
|
|
|
|
selectCategory("worldEdit");
|
|
|
|
|
};
|
|
|
|
|
keybinds.d = () => {
|
|
|
|
|
elements.w_deselect.rawOnSelect();
|
|
|
|
|
};
|
|
|
|
|
keybinds.a = () => {
|
|
|
|
|
elements.w_select_all.rawOnSelect();
|
|
|
|
|
};
|
|
|
|
|
keybinds.s = () => {
|
|
|
|
|
selectElement("w_select");
|
|
|
|
|
selectCategory("worldEdit");
|
|
|
|
|
};
|
|
|
|
|
keybinds.c = () => {
|
|
|
|
|
elements.w_copy.rawOnSelect();
|
|
|
|
|
};
|
|
|
|
|
keybinds.v = () => {
|
|
|
|
|
selectElement("w_paste");
|
|
|
|
|
selectCategory("worldEdit");
|
|
|
|
|
};
|
|
|
|
|
keybinds.x = () => {
|
|
|
|
|
elements.w_cut.rawOnSelect();
|
|
|
|
|
};
|
|
|
|
|
keybinds.Delete = () => {
|
|
|
|
|
elements.w_delete.rawOnSelect();
|
|
|
|
|
};
|
|
|
|
|
keybinds.g = () => {
|
|
|
|
|
elements.w_fill.rawOnSelect();
|
|
|
|
|
};
|
2025-07-31 21:28:54 -04:00
|
|
|
}
|
2025-08-11 02:28:26 -04:00
|
|
|
|
2025-07-31 21:28:54 -04:00
|
|
|
function modifySelectElement() {
|
2025-08-11 02:28:26 -04:00
|
|
|
const originalSelectElement = selectElement;
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
selectElement = (element) => {
|
|
|
|
|
// Keep track of last non-worldEdit element
|
2025-08-20 23:06:55 -04:00
|
|
|
if (!worldEditElements.hasOwnProperty(element))
|
|
|
|
|
w_state.lastNonWorldEditElement = element;
|
2025-08-11 02:28:26 -04:00
|
|
|
originalSelectElement(element);
|
|
|
|
|
};
|
2025-07-31 21:28:54 -04:00
|
|
|
}
|
2025-08-11 02:28:26 -04:00
|
|
|
|
2025-07-31 21:28:54 -04:00
|
|
|
function addWorldEditElements(elementsToAdd) {
|
2025-08-11 02:28:26 -04:00
|
|
|
for (const elementName in elementsToAdd) {
|
|
|
|
|
const element = elementsToAdd[elementName];
|
|
|
|
|
elements[elementName] = element;
|
|
|
|
|
// Apply base settings for every worldEdit element
|
|
|
|
|
element.category ?? (element.category = "worldEdit");
|
|
|
|
|
element.color ?? (element.color = w_accentColor);
|
|
|
|
|
element.tool ?? (element.tool = () => null);
|
|
|
|
|
element.maxSize ?? (element.maxSize = 1);
|
|
|
|
|
// Some elements will auto-deselect themselves
|
|
|
|
|
if (!element.shouldStaySelected) {
|
|
|
|
|
const originalOnSelect = element.onSelect;
|
|
|
|
|
element.rawOnSelect = originalOnSelect;
|
|
|
|
|
element.onSelect = function (...args) {
|
|
|
|
|
originalOnSelect(...args);
|
2025-08-20 23:06:55 -04:00
|
|
|
selectElement(w_state.lastNonWorldEditElement);
|
2025-08-11 02:28:26 -04:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-31 21:28:54 -04:00
|
|
|
}
|
2025-08-11 02:28:26 -04:00
|
|
|
|
2025-07-31 21:28:54 -04:00
|
|
|
// Elements
|
|
|
|
|
worldEditElements.w_deselect = {
|
2025-08-11 02:28:26 -04:00
|
|
|
onSelect: function () {
|
|
|
|
|
w_state.selection = null;
|
|
|
|
|
if (pixelTicks != 0)
|
|
|
|
|
logMessage("Deselected area.");
|
|
|
|
|
}
|
2025-07-31 21:28:54 -04:00
|
|
|
};
|
|
|
|
|
worldEditElements.w_select_all = {
|
2025-08-11 02:28:26 -04:00
|
|
|
onSelect: function () {
|
|
|
|
|
w_state.selection = new Rect(0, 0, width + 1, height + 1);
|
|
|
|
|
logMessage("Selected everything.");
|
|
|
|
|
}
|
2025-07-31 21:28:54 -04:00
|
|
|
};
|
|
|
|
|
worldEditElements.w_select = {
|
2025-08-11 02:28:26 -04:00
|
|
|
onPointerDown: function (e) {
|
|
|
|
|
const pos = mousePosToWorldPos({x: e.clientX, y: e.clientY});
|
|
|
|
|
if (showingMenu)
|
|
|
|
|
return;
|
|
|
|
|
if (!isPointInWorld(pos))
|
|
|
|
|
return;
|
2025-08-20 23:06:55 -04:00
|
|
|
if (e.button === 1 || e.button === 2)
|
2025-08-11 02:28:26 -04:00
|
|
|
return;
|
|
|
|
|
w_state.firstSelectionPos = pos;
|
|
|
|
|
},
|
2025-08-20 23:06:55 -04:00
|
|
|
onPointerMoveAnywhere: function (e) {
|
2025-08-11 02:28:26 -04:00
|
|
|
const pos = mousePosToWorldPos({x: e.clientX, y: e.clientY});
|
|
|
|
|
if (!mouseIsDown)
|
|
|
|
|
return;
|
|
|
|
|
if (showingMenu)
|
|
|
|
|
return;
|
2025-08-20 23:06:55 -04:00
|
|
|
if (e.button === 1 || e.button === 2)
|
2025-08-11 02:28:26 -04:00
|
|
|
return;
|
|
|
|
|
if (currentElement !== "w_select")
|
|
|
|
|
return;
|
|
|
|
|
const rect = Rect.fromCorners(w_state.firstSelectionPos, limitPointToWorld(pos)).normalized();
|
|
|
|
|
rect.x2 += 1;
|
|
|
|
|
rect.y2 += 1;
|
|
|
|
|
w_state.selection = rect;
|
|
|
|
|
},
|
|
|
|
|
shouldStaySelected: true
|
2025-07-31 21:28:54 -04:00
|
|
|
};
|
|
|
|
|
worldEditElements.w_copy = {
|
2025-08-11 02:28:26 -04:00
|
|
|
onSelect: function () {
|
|
|
|
|
const selection = w_state.selection;
|
|
|
|
|
if (!selection) {
|
|
|
|
|
logMessage("Error: Nothing is selected.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Copy pixels
|
|
|
|
|
w_state.clipboard = [];
|
|
|
|
|
let clipboard = w_state.clipboard;
|
|
|
|
|
for (let y = selection.y; y < selection.y2; y++) {
|
|
|
|
|
const row = [];
|
|
|
|
|
for (let x = selection.x; x < selection.x2; x++) {
|
|
|
|
|
row.push(structuredClone(pixelMap[x][y]));
|
|
|
|
|
}
|
|
|
|
|
clipboard.push(row);
|
|
|
|
|
}
|
|
|
|
|
updatePastePreviewCanvas();
|
|
|
|
|
logMessage(`Copied ${selection.w}x${selection.h}=${selection.area} pixel area.`);
|
|
|
|
|
}
|
2025-07-31 21:28:54 -04:00
|
|
|
};
|
|
|
|
|
worldEditElements.w_paste = {
|
2025-08-20 23:06:55 -04:00
|
|
|
onPointerDown: function (e) {
|
2025-08-11 02:28:26 -04:00
|
|
|
if (showingMenu)
|
|
|
|
|
return;
|
|
|
|
|
if (!isPointInWorld(mousePos))
|
|
|
|
|
return;
|
2025-08-20 23:06:55 -04:00
|
|
|
if (e.button === 1 || e.button === 2)
|
2025-08-11 02:28:26 -04:00
|
|
|
return;
|
|
|
|
|
const clipboard = w_state.clipboard;
|
|
|
|
|
if (!clipboard) {
|
|
|
|
|
logMessage("Error: Nothing in clipboard.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const pasteOrigin = mousePos;
|
|
|
|
|
// Paste pixels
|
|
|
|
|
for (let y = 0; y < clipboard.length; y++) {
|
|
|
|
|
for (let x = 0; x < clipboard[0].length; x++) {
|
|
|
|
|
const clipboardPixel = clipboard[y][x];
|
|
|
|
|
const dest = {x: pasteOrigin.x + x, y: pasteOrigin.y + y};
|
|
|
|
|
if (!isPointInWorld(dest))
|
|
|
|
|
continue; // Skip if out of bounds
|
|
|
|
|
if (pixelMap[dest.x][dest.y])
|
|
|
|
|
continue; // Skip if pixel already there
|
|
|
|
|
if (!clipboardPixel)
|
|
|
|
|
continue; // Skip if new pixel is air
|
|
|
|
|
// Create pixel
|
|
|
|
|
const newPixel = structuredClone(clipboardPixel);
|
|
|
|
|
Object.assign(newPixel, dest);
|
|
|
|
|
pixelMap[dest.x][dest.y] = newPixel;
|
|
|
|
|
currentPixels.push(newPixel);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const area = Rect.fromGrid(clipboard).area;
|
|
|
|
|
logMessage(`Pasted ${clipboard[0].length}x${clipboard.length}=${area} pixel area.`);
|
|
|
|
|
},
|
|
|
|
|
shouldStaySelected: true
|
2025-07-31 21:28:54 -04:00
|
|
|
};
|
|
|
|
|
worldEditElements.w_cut = {
|
2025-08-11 02:28:26 -04:00
|
|
|
onSelect: function () {
|
|
|
|
|
const selection = w_state.selection;
|
|
|
|
|
if (!selection) {
|
|
|
|
|
logMessage("Error: Nothing is selected.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Cut pixels
|
|
|
|
|
w_state.clipboard = [];
|
|
|
|
|
let clipboard = w_state.clipboard;
|
|
|
|
|
for (let y = selection.y; y < selection.y2; y++) {
|
|
|
|
|
const row = [];
|
|
|
|
|
for (let x = selection.x; x < selection.x2; x++) {
|
|
|
|
|
row.push(structuredClone(pixelMap[x][y]));
|
|
|
|
|
const pixel = pixelMap[x][y];
|
|
|
|
|
const index = currentPixels.indexOf(pixel);
|
|
|
|
|
if (index !== -1)
|
|
|
|
|
currentPixels.splice(index, 1);
|
|
|
|
|
if (pixel) {
|
|
|
|
|
delete pixelMap[x][y];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
clipboard.push(row);
|
|
|
|
|
}
|
|
|
|
|
updatePastePreviewCanvas();
|
|
|
|
|
logMessage(`Cut ${selection.w}x${selection.h}=${selection.area} pixel area.`);
|
|
|
|
|
}
|
2025-07-31 21:28:54 -04:00
|
|
|
};
|
|
|
|
|
worldEditElements.w_delete = {
|
2025-08-11 02:28:26 -04:00
|
|
|
onSelect: function () {
|
|
|
|
|
const selection = w_state.selection;
|
|
|
|
|
if (!selection) {
|
|
|
|
|
logMessage("Error: Nothing is selected.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Delete pixels
|
|
|
|
|
for (let y = selection.y; y < selection.y2; y++) {
|
|
|
|
|
for (let x = selection.x; x < selection.x2; x++) {
|
|
|
|
|
const pixel = pixelMap[x][y];
|
|
|
|
|
const index = currentPixels.indexOf(pixel);
|
|
|
|
|
if (index !== -1)
|
|
|
|
|
currentPixels.splice(index, 1);
|
|
|
|
|
if (pixel) {
|
|
|
|
|
delete pixelMap[x][y];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
logMessage(`Deleted ${selection.w}x${selection.h}=${selection.area} pixel area.`);
|
|
|
|
|
}
|
2025-08-11 02:19:44 -04:00
|
|
|
};
|
|
|
|
|
worldEditElements.w_fill = {
|
2025-08-11 02:28:26 -04:00
|
|
|
onSelect: function () {
|
|
|
|
|
const selection = w_state.selection;
|
2025-08-20 23:06:55 -04:00
|
|
|
const fillElement = w_state.lastNonWorldEditElement;
|
2025-08-11 02:28:26 -04:00
|
|
|
if (!selection) {
|
|
|
|
|
logMessage("Error: Nothing is selected.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Fill area
|
|
|
|
|
for (let y = selection.y; y < selection.y2; y++) {
|
|
|
|
|
for (let x = selection.x; x < selection.x2; x++) {
|
2025-08-20 23:06:55 -04:00
|
|
|
if (pixelMap[x][y])
|
|
|
|
|
continue;
|
2025-08-11 02:28:26 -04:00
|
|
|
const placed = currentPixels.push(new Pixel(x, y, fillElement));
|
|
|
|
|
if (!placed)
|
2025-08-20 23:06:55 -04:00
|
|
|
continue;
|
2025-08-11 02:28:26 -04:00
|
|
|
if (currentPixels.length > maxPixelCount || !fillElement) {
|
|
|
|
|
currentPixels[currentPixels.length - 1].del = true;
|
|
|
|
|
} else if (elements[fillElement] && elements[fillElement].onPlace !== undefined) {
|
|
|
|
|
elements[fillElement].onPlace(currentPixels[currentPixels.length - 1]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
logMessage(`Filled in ${selection.w}x${selection.h}=${selection.area} pixel area.`);
|
|
|
|
|
}
|
2025-07-31 21:28:54 -04:00
|
|
|
};
|
|
|
|
|
// Setup and hooks
|
|
|
|
|
modifySelectElement();
|
|
|
|
|
addWorldEditElements(worldEditElements);
|
|
|
|
|
addWorldEditKeybinds();
|
|
|
|
|
runAfterReset(() => {
|
2025-08-11 02:28:26 -04:00
|
|
|
if (w_deselectOnResetSetting.value)
|
|
|
|
|
w_state.selection = null;
|
2025-07-31 21:28:54 -04:00
|
|
|
});
|
|
|
|
|
runAfterReset(updatePastePreviewCanvas);
|
|
|
|
|
renderPostPixel(renderSelection);
|
|
|
|
|
renderPostPixel(renderPastePreview);
|
2025-08-11 02:19:44 -04:00
|
|
|
// Mobile support
|
|
|
|
|
let addedCustomEventListeners = false;
|
|
|
|
|
runAfterReset(() => {
|
2025-08-11 02:28:26 -04:00
|
|
|
if (addedCustomEventListeners)
|
|
|
|
|
return;
|
|
|
|
|
gameCanvas.addEventListener("pointerdown", (e) => {
|
|
|
|
|
if (elements[currentElement] && elements[currentElement].onPointerDown)
|
|
|
|
|
elements[currentElement].onPointerDown(e);
|
|
|
|
|
}, {passive: false});
|
|
|
|
|
gameCanvas.addEventListener("pointermove", (e) => {
|
|
|
|
|
if (elements[currentElement] && elements[currentElement].onPointerMove)
|
|
|
|
|
elements[currentElement].onPointerMove(e);
|
|
|
|
|
}, {passive: false});
|
2025-08-20 23:06:55 -04:00
|
|
|
document.addEventListener("pointermove", (e) => {
|
|
|
|
|
if (elements[currentElement] && elements[currentElement].onPointerMoveAnywhere)
|
|
|
|
|
elements[currentElement].onPointerMoveAnywhere(e);
|
|
|
|
|
});
|
2025-08-11 02:28:26 -04:00
|
|
|
addedCustomEventListeners = true;
|
2025-08-11 02:19:44 -04:00
|
|
|
});
|