From e21167aa4aef3f38651478247a8a6159c3d889e0 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Mon, 14 Apr 2025 13:37:37 -0700 Subject: [PATCH 1/3] add structure bounding box corners to layered outline --- scripts/classes/Outliner.js | 59 ++++++++++++++++++---------- scripts/classes/StructureInstance.js | 32 +++++++++------ scripts/classes/StructureOutliner.js | 38 ++++++++++++++++++ 3 files changed, 95 insertions(+), 34 deletions(-) create mode 100644 scripts/classes/StructureOutliner.js diff --git a/scripts/classes/Outliner.js b/scripts/classes/Outliner.js index f3a37a2..08d8821 100644 --- a/scripts/classes/Outliner.js +++ b/scripts/classes/Outliner.js @@ -1,38 +1,36 @@ import { system, world } from "@minecraft/server"; import { Vector } from "../lib/Vector"; -const drawFrequency = 8; -const drawParticle = "minecraft:villager_happy"; export class Outliner { dimension; min = new Vector(); max = new Vector(); - #shouldDraw = true; - + drawParticle = "minecraft:villager_happy"; + drawFrequency = 8; + #drawParticles = []; #runner = null; constructor(dimension, min, max) { - this.dimension = world.getDimension(dimension); + this.dimension = dimension; this.min = new Vector(min.x, min.y, min.z); this.max = new Vector(max.x, max.y, max.z); + this.vertices = this.getVertices(min, max); this.startDraw(); } startDraw() { - this.#shouldDraw = true; - this.#runner = system.runInterval(() => this.draw(), drawFrequency); + this.#runner = system.runInterval(() => this.draw(), this.drawFrequency); } stopDraw() { system.clearRun(this.#runner); - this.shouldDraw = false; } draw() { - if (!this.#shouldDraw) return; this.#drawParticles.length = 0; + this.#drawParticles.push(...this.getVerticeParticleLocations()); this.#drawParticles.push(...this.getCubiodParticleLocations()); for (const [particleType, location] of this.#drawParticles) { @@ -44,17 +42,31 @@ export class Outliner { } } - getCubiodParticleLocations() { - const vertices = [ - new Vector(this.min.x, this.min.y, this.min.z), - new Vector(this.max.x, this.min.y, this.min.z), - new Vector(this.min.x, this.max.y, this.min.z), - new Vector(this.max.x, this.max.y, this.min.z), - new Vector(this.min.x, this.min.y, this.max.z), - new Vector(this.max.x, this.min.y, this.max.z), - new Vector(this.min.x, this.max.y, this.max.z), - new Vector(this.max.x, this.max.y, this.max.z) + getVertices(min, max) { + return [ + new Vector(min.x, min.y, min.z), + new Vector(max.x, min.y, min.z), + new Vector(min.x, max.y, min.z), + new Vector(max.x, max.y, min.z), + new Vector(min.x, min.y, max.z), + new Vector(max.x, min.y, max.z), + new Vector(min.x, max.y, max.z), + new Vector(max.x, max.y, max.z) ]; + } + + setVertices(dimension, min, max) { + this.dimension = dimension; + this.min = new Vector(min.x, min.y, min.z); + this.max = new Vector(max.x, max.y, max.z); + this.vertices = this.getVertices(min, max); + } + + getVerticeParticleLocations() { + return this.vertices.map((v) => [this.drawParticle, v]); + } + + getCubiodParticleLocations() { const edges = [ [0, 1], [0, 2], @@ -71,13 +83,18 @@ export class Outliner { ]; const edgePoints = []; for (const edge of edges) { - const [startVertex, endVertex] = [vertices[edge[0]], vertices[edge[1]]]; + const [startVertex, endVertex] = [this.vertices[edge[0]], this.vertices[edge[1]]]; const resolution = Math.min(Math.floor(endVertex.subtract(startVertex).length), 16); for (let i = 1; i < resolution; i++) { const t = i / resolution; edgePoints.push(startVertex.lerp(endVertex, t)); } } - return vertices.concat(edgePoints).map((v) => [drawParticle, v]); + return edgePoints.map((v) => [this.drawParticle, v]); + } + + addStandaloneParticles(locations) { + for (const location of locations) + this.vertices.push(new Vector(location.x, location.y, location.z)); } } \ No newline at end of file diff --git a/scripts/classes/StructureInstance.js b/scripts/classes/StructureInstance.js index 1b35582..00d2696 100644 --- a/scripts/classes/StructureInstance.js +++ b/scripts/classes/StructureInstance.js @@ -1,5 +1,6 @@ import { world } from "@minecraft/server"; import { Outliner } from "./Outliner"; +import { StructureOutliner } from "./StructureOutliner"; export class StructureInstance { name; @@ -13,6 +14,7 @@ export class StructureInstance { mirror: false, currentLayer: 0 }; + outliner = void 0; constructor(instanceName, structureId) { this.name = instanceName; @@ -22,9 +24,8 @@ export class StructureInstance { this.#structure.saveToWorld(); this.#options = this.loadOptions(); this.#options.structureId = structureId; - if (this.#options.isEnabled) - this.refreshOutliner(); this.updateOptions(); + this.outliner = new StructureOutliner(this); } loadOptions() { @@ -75,6 +76,16 @@ export class StructureInstance { return this.#options.currentLayer || 0; } + getDimension() { + let dimension; + try { + dimension = world.getDimension(this.#options.dimensionId); + } catch (e) { + dimension = world.getDimension("minecraft:overworld"); + } + return dimension; + } + *getBlocks() { const max = this.#structure.size; for (let x = 0; x < max.x; x++) { @@ -135,7 +146,7 @@ export class StructureInstance { disable() { this.#options.isEnabled = false; this.updateOptions(); - this.outliner.stopDraw(); + this.refreshOutliner(); } move(dimensionId, location) { @@ -154,16 +165,7 @@ export class StructureInstance { } refreshOutliner() { - if (!this.#options.isEnabled) - return; - if (this.outliner) - this.outliner.stopDraw(); - if (this.#options.currentLayer > 0) { - const { min, max } = this.getLayeredBounds(); - this.outliner = new Outliner(this.#options.dimensionId, this.toGlobalCoords(min), this.toGlobalCoords(max)); - } else { - this.outliner = new Outliner(this.#options.dimensionId, this.toGlobalCoords(this.getBounds().min), this.toGlobalCoords(this.getBounds().max)); - } + this.outliner.refresh(); } isLocationInStructure(dimensionId, structureLocation) { @@ -216,6 +218,10 @@ export class StructureInstance { return this.#options.dimensionId && this.#options.worldLocation.x !== 0 && this.#options.worldLocation.y !== 0 && this.#options.worldLocation.z !== 0; } + isUsingLayers() { + return this.hasLayers() && this.#options.currentLayer !== 0; + } + hasLayers() { return this.#structure.size.y > 1; } diff --git a/scripts/classes/StructureOutliner.js b/scripts/classes/StructureOutliner.js new file mode 100644 index 0000000..fb31339 --- /dev/null +++ b/scripts/classes/StructureOutliner.js @@ -0,0 +1,38 @@ +import { Outliner } from '../classes/Outliner'; + +export class StructureOutliner { + constructor(instance) { + this.instance = instance; + this.dimension = instance.getDimension(); + this.bounds = instance.getBounds(); + this.bounds.min = instance.toGlobalCoords(this.bounds.min); + this.bounds.max = instance.toGlobalCoords(this.bounds.max); + this.outliner = new Outliner(this.dimension, this.bounds.min, this.bounds.max); + this.refresh(); + } + + refresh() { + this.outliner.stopDraw(); + if (!this.instance.isEnabled()) + return; + if (this.instance.isUsingLayers()) + this.layeredDraw(); + else + this.boxDraw(); + this.outliner.startDraw(); + } + + boxDraw() { + this.outliner.setVertices(this.dimension, this.bounds.min, this.bounds.max); + } + + layeredDraw() { + const { min, max } = this.instance.getLayeredBounds(); + this.outliner.setVertices(this.dimension, this.instance.toGlobalCoords(min), this.instance.toGlobalCoords(max)); + this.outliner.addStandaloneParticles(this.getCornerVertices()); + } + + getCornerVertices() { + return this.outliner.getVertices(this.bounds.min, this.bounds.max); + } +} \ No newline at end of file From 0c361a252364a0681bfb3c9ce26a41a5ccede7fc Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Mon, 14 Apr 2025 13:51:29 -0700 Subject: [PATCH 2/3] StructureOutliner now updates correctly when instance moves --- scripts/classes/StructureOutliner.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scripts/classes/StructureOutliner.js b/scripts/classes/StructureOutliner.js index fb31339..7e30a29 100644 --- a/scripts/classes/StructureOutliner.js +++ b/scripts/classes/StructureOutliner.js @@ -3,15 +3,24 @@ import { Outliner } from '../classes/Outliner'; export class StructureOutliner { constructor(instance) { this.instance = instance; - this.dimension = instance.getDimension(); - this.bounds = instance.getBounds(); - this.bounds.min = instance.toGlobalCoords(this.bounds.min); - this.bounds.max = instance.toGlobalCoords(this.bounds.max); + this.pullInstanceData(); this.outliner = new Outliner(this.dimension, this.bounds.min, this.bounds.max); this.refresh(); } + pullInstanceData() { + this.dimension = this.instance.getDimension(); + this.bounds = this.instance.getBounds(); + this.bounds.min = this.instance.toGlobalCoords(this.bounds.min); + this.bounds.max = this.instance.toGlobalCoords(this.bounds.max); + } + refresh() { + this.pullInstanceData(); + this.refreshDraw(); + } + + refreshDraw() { this.outliner.stopDraw(); if (!this.instance.isEnabled()) return; From 8a380dc17326172ecf3519e17591b2ab727d90dc Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Mon, 14 Apr 2025 23:27:05 -0700 Subject: [PATCH 3/3] Redo managing structure how-to page and rename ./menu to ./structool --- scripts/classes/MenuForm.js | 9 +++++---- scripts/classes/MenuFormBuilder.js | 12 +++++++----- scripts/classes/StructureCollection.js | 13 ++++++++++++- scripts/classes/StructureOutliner.js | 15 +++++++++++---- scripts/commands/{menu.js => structool.js} | 20 ++++++++------------ scripts/main.js | 3 +-- scripts/rules/easyPlace.js | 2 +- scripts/rules/fastEasyPlace.js | 2 +- 8 files changed, 46 insertions(+), 30 deletions(-) rename scripts/commands/{menu.js => structool.js} (54%) diff --git a/scripts/classes/MenuForm.js b/scripts/classes/MenuForm.js index d717e75..13cc1be 100644 --- a/scripts/classes/MenuForm.js +++ b/scripts/classes/MenuForm.js @@ -27,11 +27,8 @@ export class MenuForm { async getInstanceNameFromForm() { try { return forceShow(this.player, MenuFormBuilder.buildAllInstanceName()).then((response) => { - if (response.canceled) return; - if (response.selection === structureCollection.getInstanceNames().length + 1) { - MenuFormBuilder.buildHowToAddNewStructures().show(this.player); + if (response.canceled) return; - } const selectedInstanceName = structureCollection.getInstanceNames()[response.selection]; return selectedInstanceName || this.createNewInstance(); }); @@ -63,6 +60,10 @@ export class MenuForm { return MenuFormBuilder.buildAllStructures().show(this.player).then((response) => { if (response.canceled) return; + if (response.selection === structureCollection.getWorldStructureIds().length + 1) { + MenuFormBuilder.buildHowTo().show(this.player); + return; + } const selectedStructureId = structureCollection.getWorldStructureIds()[response.selection]; return selectedStructureId || this.getOtherStructureId(); }); diff --git a/scripts/classes/MenuFormBuilder.js b/scripts/classes/MenuFormBuilder.js index bd7038f..e049411 100644 --- a/scripts/classes/MenuFormBuilder.js +++ b/scripts/classes/MenuFormBuilder.js @@ -12,7 +12,6 @@ export class MenuFormBuilder { allInstanceNameForm.button(`§2${instanceName}`); }); allInstanceNameForm.button('Create New Instance'); - allInstanceNameForm.button('How to Add New Structures'); return allInstanceNameForm; } @@ -32,6 +31,7 @@ export class MenuFormBuilder { allStructuresForm.button(`§2${structureName}`); }); allStructuresForm.button('Other'); + allStructuresForm.button('How to Add/Remove Structures'); return allStructuresForm; } @@ -42,11 +42,13 @@ export class MenuFormBuilder { .submitButton('Submit'); } - static buildHowToAddNewStructures() { - let body = "How to Add Structures:\n" - body += "§7- Save a structure using a structure block or the /structure command.\n" - body += "§7OR\n" + static buildHowTo() { + let body = "§aHow to Add Structures:\n" + body += "§7- Save a structure using a §fstructure block§7 or the §f/structure§7 command.\n" + body += "§f§lOR§r\n" body += "§7- Add a .mcstructure file to this pack's structures folder. When selecting your structure, select the 'Other' option and then use the filename (without '.mcstructure') as the Structure ID. After its first use, it will be added to the list of structures."; + body += "\n\n§cHow to Remove Structures:\n" + body += "§7- Use the §f/structure delete§7 command to remove a structure from the world.\n" return new ActionFormData() .title(this.menuTitle) .body(body); diff --git a/scripts/classes/StructureCollection.js b/scripts/classes/StructureCollection.js index 4082036..42628a4 100644 --- a/scripts/classes/StructureCollection.js +++ b/scripts/classes/StructureCollection.js @@ -50,7 +50,18 @@ class StructureCollection { } getStructures(dimensionId, location, options = {}) { - return Object.values(this.structures).filter(structure => structure.isLocationActive(dimensionId, structure.toStructureCoords(location), options)); + return Object.values(this.structures).filter(structure => { + try { + return structure.isLocationActive(dimensionId, structure.toStructureCoords(location), options) + } catch (e) { + if (e.name === 'InvalidStructureError') { + structureCollection.delete(structure.name); + return false; + } else { + throw e; + } + } + }); } getStructure(dimensionId, location, options = {}) { diff --git a/scripts/classes/StructureOutliner.js b/scripts/classes/StructureOutliner.js index 7e30a29..2d7bda7 100644 --- a/scripts/classes/StructureOutliner.js +++ b/scripts/classes/StructureOutliner.js @@ -9,10 +9,17 @@ export class StructureOutliner { } pullInstanceData() { - this.dimension = this.instance.getDimension(); - this.bounds = this.instance.getBounds(); - this.bounds.min = this.instance.toGlobalCoords(this.bounds.min); - this.bounds.max = this.instance.toGlobalCoords(this.bounds.max); + try { + this.dimension = this.instance.getDimension(); + this.bounds = this.instance.getBounds(); + this.bounds.min = this.instance.toGlobalCoords(this.bounds.min); + this.bounds.max = this.instance.toGlobalCoords(this.bounds.max); + } catch (e) { + if (e.name === 'InvalidStructureError') + this.outliner.stopDraw(); + else + throw e; + } } refresh() { diff --git a/scripts/commands/menu.js b/scripts/commands/structool.js similarity index 54% rename from scripts/commands/menu.js rename to scripts/commands/structool.js index 02c06fb..39baffd 100644 --- a/scripts/commands/menu.js +++ b/scripts/commands/structool.js @@ -5,20 +5,16 @@ import { MenuForm } from '../classes/MenuForm'; const ACTION_ITEM = 'minecraft:paper'; -const structCmd = new Command({ - name: 'menu', - description: { text: 'Manages current StrucTool structures.' }, - usage: 'menu', - callback: structCommand +const structoolCmd = new Command({ + name: 'structool', + description: { text: 'Opens the StrucTool Menu. Using a paper will also open the menu.' }, + usage: 'structool', + callback: (sender) => new MenuForm(sender) }); -extension.addCommand(structCmd); +extension.addCommand(structoolCmd); world.beforeEvents.itemUse.subscribe((event) => { if (!event.source || event.itemStack?.typeId !== ACTION_ITEM) return; event.cancel = true; - system.run(() => structCommand(event.source)); -}); - -function structCommand(sender) { - new MenuForm(sender); -} \ No newline at end of file + system.run(() => structoolCmd.getCallback()(event.source)); +}); \ No newline at end of file diff --git a/scripts/main.js b/scripts/main.js index d7a4547..55d31a8 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -3,8 +3,7 @@ import './rules/easyPlace'; import './rules/fastEasyPlace'; // Commands -import './commands/struct'; -import './commands/menu'; +import './commands/structool'; // Other import './classes/BlockInfo'; diff --git a/scripts/rules/easyPlace.js b/scripts/rules/easyPlace.js index bb33e5d..519f1f6 100644 --- a/scripts/rules/easyPlace.js +++ b/scripts/rules/easyPlace.js @@ -10,7 +10,7 @@ const ACTION_SLOT = 35; const easyPlace = new Rule({ identifier: 'easyPlace', - description: { text: "Simplifies placing blocks in a structure (paper named 'easyPlace' in bottom right inventory slot)." }, + description: { text: "Automatically places the correct block in a structure (paper named 'easyPlace' in bottom right inventory slot)." }, onEnableCallback: () => { world.beforeEvents.playerPlaceBlock.subscribe(onPlayerPlaceBlock); }, onDisableCallback: () => { world.beforeEvents.playerPlaceBlock.unsubscribe(onPlayerPlaceBlock); } }) diff --git a/scripts/rules/fastEasyPlace.js b/scripts/rules/fastEasyPlace.js index df7b4b1..fde0289 100644 --- a/scripts/rules/fastEasyPlace.js +++ b/scripts/rules/fastEasyPlace.js @@ -8,7 +8,7 @@ import { Raycaster } from '../classes/Raycaster'; let runner = void 0; const easyPlace = new Rule({ identifier: 'fastEasyPlace', - description: { text: "Looking at structure blocks with a paper named 'easyPlace' in your hand will place them." }, + description: { text: "Looking at a structure block with a paper named 'easyPlace' in your hand will place it." }, onEnableCallback: () => { runner = system.runInterval(onTick, 2); }, onDisableCallback: () => { system.clearRun(runner); } })