From de04bc4d91a5c6214308cfa199039a83a284cd1d Mon Sep 17 00:00:00 2001 From: redbirdly <155550833+redbirdly@users.noreply.github.com> Date: Mon, 11 Aug 2025 14:19:44 +0800 Subject: [PATCH 1/3] Update worldEdit.js --- mods/worldEdit.js | 664 ++++++++++++++++++++++++---------------------- 1 file changed, 349 insertions(+), 315 deletions(-) diff --git a/mods/worldEdit.js b/mods/worldEdit.js index 2a592797..4a1fab2a 100644 --- a/mods/worldEdit.js +++ b/mods/worldEdit.js @@ -1,375 +1,409 @@ "use strict"; // WorldEdit.js (compiled) -// Version: 1.0.1 +// Version: 1.1.0 // Constants const w_accentColor = "#7cff62"; const w_style = { - strokeWidth: 1, - selectFill: "#57b64530", - selectStroke: w_accentColor, - selectDash: true, - pasteFill: "#00FFFF40", - pasteStroke: "#00FFFF" + strokeWidth: 1, + selectFill: "#57b64530", + selectStroke: w_accentColor, + selectDash: true, + pasteFill: "#00FFFF40", + pasteStroke: "#00FFFF" }; // Global variables let worldEditElements = {}; let pastePreviewCanvas; let w_state = { - firstSelectionPos: {x: 0, y: 0}, - selection: null, - clipboard: null, - prevNonWorldEditElement: "unknown" + firstSelectionPos: { x: 0, y: 0 }, + selection: null, + clipboard: null, + prevNonWorldEditElement: "unknown" }; // Define settings let w_settingsTab; let w_deselectOnResetSetting; dependOn("betterSettings.js", () => { - 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); + 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); }, true); - // Classes class Rect { - 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)); - } + 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)); + } } - // Functions function isPointInWorld(point) { - return point.x >= 0 && point.x <= width && point.y >= 0 && point.y <= height; + return point.x >= 0 && point.x <= width && point.y >= 0 && point.y <= height; } - function limitPointToWorld(point) { - return { - x: Math.max(0, Math.min(point.x, width)), - y: Math.max(0, Math.min(point.y, height)) - }; + return { + x: Math.max(0, Math.min(point.x, width)), + y: Math.max(0, Math.min(point.y, height)) + }; +} +function mousePosToWorldPos(pos) { + 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 }; } - function updatePastePreviewCanvas() { - 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); - for (let y = 0; y < clipboardRect.h; y++) { - for (let x = 0; x < clipboardRect.w; x++) { - if (clipboard[y][x]) - buffer[y * clipboardRect.w + x] = 0x44FFFF00; - } - } - pastePreviewCtx.putImageData(imageData, 0, 0); + 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); + for (let y = 0; y < clipboardRect.h; y++) { + for (let x = 0; x < clipboardRect.w; x++) { + if (clipboard[y][x]) + buffer[y * clipboardRect.w + x] = 0x44FFFF00; + } + } + pastePreviewCtx.putImageData(imageData, 0, 0); } - function renderSelection(ctx) { - const selection = w_state.selection; - if (!selection) - return; - const isSelecting = (mouseIsDown && mouseType === "left" && 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([]); + 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([]); } - function renderPastePreview(ctx) { - if (currentElement !== 'w_paste') - return; - const clipboard = w_state.clipboard; - if (!clipboard) - return; - const clipboardRect = Rect.fromGrid(clipboard, mousePos); - // 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); + 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); } - function addWorldEditKeybinds() { - 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.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(); + }; } - function modifySelectElement() { - const originalSelectElement = selectElement; - // @ts-ignore - selectElement = (element) => { - // Keep track of last non-worldEdit element - if (!worldEditElements.hasOwnProperty(currentElement)) - w_state.prevNonWorldEditElement = currentElement; - originalSelectElement(element); - }; + const originalSelectElement = selectElement; + // @ts-ignore + selectElement = (element) => { + // Keep track of last non-worldEdit element + if (!worldEditElements.hasOwnProperty(currentElement)) + w_state.prevNonWorldEditElement = currentElement; + originalSelectElement(element); + }; } - function addWorldEditElements(elementsToAdd) { - 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); - selectElement(w_state.prevNonWorldEditElement); - }; - } - } + 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); + selectElement(w_state.prevNonWorldEditElement); + }; + } + } } - -function updateSelection() { - if (!mouseIsDown) - return; - if (showingMenu) - return; - if (mouseType !== "left") - return; - if (currentElement !== "w_select") - return; - const rect = Rect.fromCorners(w_state.firstSelectionPos, limitPointToWorld(mousePos)).normalized(); - rect.x2 += 1; - rect.y2 += 1; - w_state.selection = rect; -} - // Elements worldEditElements.w_deselect = { - onSelect: function () { - w_state.selection = null; - if (pixelTicks != 0) - logMessage("Deselected area."); - } + onSelect: function () { + w_state.selection = null; + if (pixelTicks != 0) + logMessage("Deselected area."); + } }; worldEditElements.w_select_all = { - onSelect: function () { - w_state.selection = new Rect(0, 0, width + 1, height + 1); - logMessage("Selected everything."); - } + onSelect: function () { + w_state.selection = new Rect(0, 0, width + 1, height + 1); + logMessage("Selected everything."); + } }; worldEditElements.w_select = { - onMouseDown: function () { - if (showingMenu) - return; - if (!isPointInWorld(mousePos)) - return; - if (mouseType !== "left") - return; - w_state.firstSelectionPos = mousePos; - }, - shouldStaySelected: true + onPointerDown: function (e) { + const pos = mousePosToWorldPos({ x: e.clientX, y: e.clientY }); + if (showingMenu) + return; + if (!isPointInWorld(pos)) + return; + if (mouseType === "middle" || mouseType === "right") + return; + w_state.firstSelectionPos = pos; + }, + onPointerMove: function (e) { + const pos = mousePosToWorldPos({ x: e.clientX, y: e.clientY }); + if (!mouseIsDown) + return; + if (showingMenu) + return; + if (mouseType === "middle" || mouseType === "right") + 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 }; worldEditElements.w_copy = { - 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.`); - } + 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.`); + } }; worldEditElements.w_paste = { - onMouseDown: function () { - if (showingMenu) - return; - if (!isPointInWorld(mousePos)) - return; - if (mouseType !== "left") - 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 + onPointerDown: function () { + if (showingMenu) + return; + if (!isPointInWorld(mousePos)) + return; + if (mouseType === "middle" || mouseType === "right") + 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 }; worldEditElements.w_cut = { - 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.`); - } + 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.`); + } }; worldEditElements.w_delete = { - 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.`); - } + 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.`); + } +}; +worldEditElements.w_fill = { + onSelect: function () { + const selection = w_state.selection; + const fillElement = w_state.prevNonWorldEditElement; + 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++) { + const placed = currentPixels.push(new Pixel(x, y, fillElement)); + if (!placed) + return; + 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.`); + } }; // Setup and hooks modifySelectElement(); addWorldEditElements(worldEditElements); addWorldEditKeybinds(); runAfterReset(() => { - if (w_deselectOnResetSetting.value) - w_state.selection = null; + if (w_deselectOnResetSetting.value) + w_state.selection = null; }); runAfterReset(updatePastePreviewCanvas); -renderPrePixel(updateSelection); renderPostPixel(renderSelection); renderPostPixel(renderPastePreview); +// Mobile support +let addedCustomEventListeners = false; +runAfterReset(() => { + 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 }); + addedCustomEventListeners = true; +}); From 2afcb6d97cc3a879acbc569a13e11336f73ba2e5 Mon Sep 17 00:00:00 2001 From: redbirdly <155550833+redbirdly@users.noreply.github.com> Date: Mon, 11 Aug 2025 14:20:07 +0800 Subject: [PATCH 2/3] Update worldEdit.ts --- mods/worldEdit.ts | 125 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 97 insertions(+), 28 deletions(-) diff --git a/mods/worldEdit.ts b/mods/worldEdit.ts index 3b2652d3..b6405fa4 100644 --- a/mods/worldEdit.ts +++ b/mods/worldEdit.ts @@ -1,5 +1,5 @@ // WorldEdit.ts -// Version: 1.0.1 +// Version: 1.1.0 // Interfaces interface WorldEditState { @@ -132,6 +132,18 @@ function limitPointToWorld(point: Vec2D): Vec2D { } } +function mousePosToWorldPos(pos: Vec2D) { + 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} +} + function updatePastePreviewCanvas(): void { const clipboard = w_state.clipboard if (!clipboard) return @@ -161,7 +173,11 @@ function renderSelection(ctx: CanvasRenderingContext2D): void { const selection = w_state.selection if (!selection) return - const isSelecting = (mouseIsDown && mouseType === "left" && currentElement === "w_select") + const isSelecting = ( + mouseIsDown && + (mouseType !== "middle" && mouseType !== "right") && + currentElement === "w_select" + ) ctx.globalAlpha = 1.0 @@ -201,6 +217,8 @@ function renderPastePreview(ctx: CanvasRenderingContext2D): void { const clipboardRect = Rect.fromGrid(clipboard, mousePos) + ctx.globalAlpha = 1.0 + // Fill ctx.fillStyle = w_style.pasteFill ctx.fillRect( @@ -251,6 +269,9 @@ function addWorldEditKeybinds(): void { keybinds.Delete = () => { // Delete elements.w_delete.rawOnSelect() } + keybinds.g = () => { // Fill + elements.w_fill.rawOnSelect() + } } function modifySelectElement(): void { @@ -289,23 +310,6 @@ function addWorldEditElements(elementsToAdd: ElementsType): void { } } -function updateSelection(): void { - if (!mouseIsDown) return - if (showingMenu) return - if (mouseType !== "left") return - if (currentElement !== "w_select") return - - const rect = Rect.fromCorners( - w_state.firstSelectionPos, - limitPointToWorld(mousePos) - ).normalized() - - rect.x2 += 1 - rect.y2 += 1 - - w_state.selection = rect -} - // Elements worldEditElements.w_deselect = { onSelect: function (): void { @@ -325,12 +329,32 @@ worldEditElements.w_select_all = { } worldEditElements.w_select = { - onMouseDown: function (): void { - if (showingMenu) return - if (!isPointInWorld(mousePos)) return - if (mouseType !== "left") return + onPointerDown: function (e: PointerEvent): void { + const pos = mousePosToWorldPos({x: e.clientX, y: e.clientY}) - w_state.firstSelectionPos = mousePos + if (showingMenu) return + if (!isPointInWorld(pos)) return + if (mouseType === "middle" || mouseType === "right") return + + w_state.firstSelectionPos = pos + }, + onPointerMove: function (e: PointerEvent): void { + const pos = mousePosToWorldPos({x: e.clientX, y: e.clientY}) + + if (!mouseIsDown) return + if (showingMenu) return + if (mouseType === "middle" || mouseType === "right") 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 } @@ -364,10 +388,10 @@ worldEditElements.w_copy = { } worldEditElements.w_paste = { - onMouseDown: function (): void { + onPointerDown: function (): void { if (showingMenu) return if (!isPointInWorld(mousePos)) return - if (mouseType !== "left") return + if (mouseType === "middle" || mouseType === "right") return const clipboard = w_state.clipboard @@ -382,7 +406,7 @@ worldEditElements.w_paste = { 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 } + 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 @@ -462,6 +486,34 @@ worldEditElements.w_delete = { } } +worldEditElements.w_fill = { + onSelect: function (): void { + const selection = w_state.selection + const fillElement = w_state.prevNonWorldEditElement + + 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++) { + const placed = currentPixels.push(new Pixel(x, y, fillElement)) + if (!placed) return + + 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.`) + } +} + // Setup and hooks modifySelectElement() addWorldEditElements(worldEditElements) @@ -473,6 +525,23 @@ runAfterReset(() => { }) runAfterReset(updatePastePreviewCanvas) -renderPrePixel(updateSelection) renderPostPixel(renderSelection) renderPostPixel(renderPastePreview) + +// Mobile support +let addedCustomEventListeners = false +runAfterReset(() => { + if (addedCustomEventListeners) return + + gameCanvas.addEventListener("pointerdown", (e: PointerEvent) => { + if (elements[currentElement] && elements[currentElement].onPointerDown) + elements[currentElement].onPointerDown(e) + }, {passive: false}) + + gameCanvas.addEventListener("pointermove", (e: PointerEvent) => { + if (elements[currentElement] && elements[currentElement].onPointerMove) + elements[currentElement].onPointerMove(e) + }, {passive: false}) + + addedCustomEventListeners = true +}) From 5c38ac7c3826fa9d629482fdddd10c18715cacde Mon Sep 17 00:00:00 2001 From: redbirdly <155550833+redbirdly@users.noreply.github.com> Date: Mon, 11 Aug 2025 14:28:26 +0800 Subject: [PATCH 3/3] Update worldEdit.js --- mods/worldEdit.js | 700 ++++++++++++++++++++++++---------------------- 1 file changed, 360 insertions(+), 340 deletions(-) diff --git a/mods/worldEdit.js b/mods/worldEdit.js index 4a1fab2a..baec5a16 100644 --- a/mods/worldEdit.js +++ b/mods/worldEdit.js @@ -4,390 +4,410 @@ // Constants const w_accentColor = "#7cff62"; const w_style = { - strokeWidth: 1, - selectFill: "#57b64530", - selectStroke: w_accentColor, - selectDash: true, - pasteFill: "#00FFFF40", - pasteStroke: "#00FFFF" + strokeWidth: 1, + selectFill: "#57b64530", + selectStroke: w_accentColor, + selectDash: true, + pasteFill: "#00FFFF40", + pasteStroke: "#00FFFF" }; // Global variables let worldEditElements = {}; let pastePreviewCanvas; let w_state = { - firstSelectionPos: { x: 0, y: 0 }, - selection: null, - clipboard: null, - prevNonWorldEditElement: "unknown" + firstSelectionPos: {x: 0, y: 0}, + selection: null, + clipboard: null, + prevNonWorldEditElement: "unknown" }; // Define settings let w_settingsTab; let w_deselectOnResetSetting; dependOn("betterSettings.js", () => { - 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); + 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); }, true); + // Classes class Rect { - 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)); - } + 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)); + } } + // Functions function isPointInWorld(point) { - return point.x >= 0 && point.x <= width && point.y >= 0 && point.y <= height; + return point.x >= 0 && point.x <= width && point.y >= 0 && point.y <= height; } + function limitPointToWorld(point) { - return { - x: Math.max(0, Math.min(point.x, width)), - y: Math.max(0, Math.min(point.y, height)) - }; + return { + x: Math.max(0, Math.min(point.x, width)), + y: Math.max(0, Math.min(point.y, height)) + }; } + function mousePosToWorldPos(pos) { - 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 }; + 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}; } + function updatePastePreviewCanvas() { - 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); - for (let y = 0; y < clipboardRect.h; y++) { - for (let x = 0; x < clipboardRect.w; x++) { - if (clipboard[y][x]) - buffer[y * clipboardRect.w + x] = 0x44FFFF00; - } - } - pastePreviewCtx.putImageData(imageData, 0, 0); + 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); + for (let y = 0; y < clipboardRect.h; y++) { + for (let x = 0; x < clipboardRect.w; x++) { + if (clipboard[y][x]) + buffer[y * clipboardRect.w + x] = 0x44FFFF00; + } + } + pastePreviewCtx.putImageData(imageData, 0, 0); } + function renderSelection(ctx) { - 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([]); + 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([]); } + function renderPastePreview(ctx) { - 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); + 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); } + function addWorldEditKeybinds() { - 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(); - }; + 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(); + }; } + function modifySelectElement() { - const originalSelectElement = selectElement; - // @ts-ignore - selectElement = (element) => { - // Keep track of last non-worldEdit element - if (!worldEditElements.hasOwnProperty(currentElement)) - w_state.prevNonWorldEditElement = currentElement; - originalSelectElement(element); - }; + const originalSelectElement = selectElement; + // @ts-ignore + selectElement = (element) => { + // Keep track of last non-worldEdit element + if (!worldEditElements.hasOwnProperty(currentElement)) + w_state.prevNonWorldEditElement = currentElement; + originalSelectElement(element); + }; } + function addWorldEditElements(elementsToAdd) { - 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); - selectElement(w_state.prevNonWorldEditElement); - }; - } - } + 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); + selectElement(w_state.prevNonWorldEditElement); + }; + } + } } + // Elements worldEditElements.w_deselect = { - onSelect: function () { - w_state.selection = null; - if (pixelTicks != 0) - logMessage("Deselected area."); - } + onSelect: function () { + w_state.selection = null; + if (pixelTicks != 0) + logMessage("Deselected area."); + } }; worldEditElements.w_select_all = { - onSelect: function () { - w_state.selection = new Rect(0, 0, width + 1, height + 1); - logMessage("Selected everything."); - } + onSelect: function () { + w_state.selection = new Rect(0, 0, width + 1, height + 1); + logMessage("Selected everything."); + } }; worldEditElements.w_select = { - onPointerDown: function (e) { - const pos = mousePosToWorldPos({ x: e.clientX, y: e.clientY }); - if (showingMenu) - return; - if (!isPointInWorld(pos)) - return; - if (mouseType === "middle" || mouseType === "right") - return; - w_state.firstSelectionPos = pos; - }, - onPointerMove: function (e) { - const pos = mousePosToWorldPos({ x: e.clientX, y: e.clientY }); - if (!mouseIsDown) - return; - if (showingMenu) - return; - if (mouseType === "middle" || mouseType === "right") - 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 + onPointerDown: function (e) { + const pos = mousePosToWorldPos({x: e.clientX, y: e.clientY}); + if (showingMenu) + return; + if (!isPointInWorld(pos)) + return; + if (mouseType === "middle" || mouseType === "right") + return; + w_state.firstSelectionPos = pos; + }, + onPointerMove: function (e) { + const pos = mousePosToWorldPos({x: e.clientX, y: e.clientY}); + if (!mouseIsDown) + return; + if (showingMenu) + return; + if (mouseType === "middle" || mouseType === "right") + 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 }; worldEditElements.w_copy = { - 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.`); - } + 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.`); + } }; worldEditElements.w_paste = { - onPointerDown: function () { - if (showingMenu) - return; - if (!isPointInWorld(mousePos)) - return; - if (mouseType === "middle" || mouseType === "right") - 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 + onPointerDown: function () { + if (showingMenu) + return; + if (!isPointInWorld(mousePos)) + return; + if (mouseType === "middle" || mouseType === "right") + 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 }; worldEditElements.w_cut = { - 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.`); - } + 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.`); + } }; worldEditElements.w_delete = { - 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.`); - } + 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.`); + } }; worldEditElements.w_fill = { - onSelect: function () { - const selection = w_state.selection; - const fillElement = w_state.prevNonWorldEditElement; - 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++) { - const placed = currentPixels.push(new Pixel(x, y, fillElement)); - if (!placed) - return; - 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.`); - } + onSelect: function () { + const selection = w_state.selection; + const fillElement = w_state.prevNonWorldEditElement; + 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++) { + const placed = currentPixels.push(new Pixel(x, y, fillElement)); + if (!placed) + return; + 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.`); + } }; // Setup and hooks modifySelectElement(); addWorldEditElements(worldEditElements); addWorldEditKeybinds(); runAfterReset(() => { - if (w_deselectOnResetSetting.value) - w_state.selection = null; + if (w_deselectOnResetSetting.value) + w_state.selection = null; }); runAfterReset(updatePastePreviewCanvas); renderPostPixel(renderSelection); @@ -395,15 +415,15 @@ renderPostPixel(renderPastePreview); // Mobile support let addedCustomEventListeners = false; runAfterReset(() => { - 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 }); - addedCustomEventListeners = true; + 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}); + addedCustomEventListeners = true; });