From d6c9acdaabd77ecb8763191ca534230876b72f45 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sat, 19 Apr 2025 17:55:28 -0700 Subject: [PATCH 1/4] complete rename and init builder options --- Construct [BP]/manifest.json | 4 +- .../classes/BlockVerificationLevelRender.js | 12 +-- .../scripts/classes/Builder/Builder.js | 22 +++++ .../scripts/classes/Builder/BuilderForm.js | 24 ++++++ .../classes/Builder/BuilderFormBuilder.js | 15 ++++ .../scripts/classes/Builder/BuilderOption.js | 53 ++++++++++++ .../scripts/classes/Builder/BuilderOptions.js | 34 ++++++++ .../scripts/classes/Builder/Builders.js | 25 ++++++ .../InstanceForm.js} | 80 +++++++++---------- .../InstanceFormBuilder.js} | 6 +- .../classes/{ => Instance}/InstanceOptions.js | 22 ++--- Construct [BP]/scripts/classes/MenuForm.js | 6 +- .../scripts/classes/MenuFormBuilder.js | 2 +- Construct [BP]/scripts/classes/Option.js | 25 ++++++ Construct [BP]/scripts/classes/Outliner.js | 2 +- .../classes/{ => Structure}/Structure.js | 2 +- .../{ => Structure}/StructureCollection.js | 2 +- .../{ => Structure}/StructureInstance.js | 10 +-- .../{ => Structure}/StructureOutliner.js | 0 .../{ => Structure}/StructureStatistics.js | 0 .../{ => Structure}/StructureVerifier.js | 0 ...tanceEditButtons.js => InstanceButtons.js} | 2 +- Construct [BP]/scripts/commands/construct.js | 6 +- Construct [BP]/scripts/config.js | 4 +- Construct [BP]/scripts/lib/canopy/Rule.js | 76 ------------------ .../scripts/{rules => options}/easyPlace.js | 12 +-- .../{rules => options}/fastEasyPlace.js | 12 +-- .../{rules => options}/materialGrabber.js | 10 +-- Construct [RP]/manifest.json | 4 +- .../particles/blockoverlay_xy.particle.json | 2 +- .../particles/blockoverlay_xz.particle.json | 2 +- .../particles/blockoverlay_yz.particle.json | 2 +- .../particles/outline.particle.json | 2 +- 33 files changed, 297 insertions(+), 183 deletions(-) create mode 100644 Construct [BP]/scripts/classes/Builder/Builder.js create mode 100644 Construct [BP]/scripts/classes/Builder/BuilderForm.js create mode 100644 Construct [BP]/scripts/classes/Builder/BuilderFormBuilder.js create mode 100644 Construct [BP]/scripts/classes/Builder/BuilderOption.js create mode 100644 Construct [BP]/scripts/classes/Builder/BuilderOptions.js create mode 100644 Construct [BP]/scripts/classes/Builder/Builders.js rename Construct [BP]/scripts/classes/{InstanceEditForm.js => Instance/InstanceForm.js} (61%) rename Construct [BP]/scripts/classes/{InstanceEditFormBuilder.js => Instance/InstanceFormBuilder.js} (91%) rename Construct [BP]/scripts/classes/{ => Instance}/InstanceOptions.js (68%) create mode 100644 Construct [BP]/scripts/classes/Option.js rename Construct [BP]/scripts/classes/{ => Structure}/Structure.js (96%) rename Construct [BP]/scripts/classes/{ => Structure}/StructureCollection.js (98%) rename Construct [BP]/scripts/classes/{ => Structure}/StructureInstance.js (95%) rename Construct [BP]/scripts/classes/{ => Structure}/StructureOutliner.js (100%) rename Construct [BP]/scripts/classes/{ => Structure}/StructureStatistics.js (100%) rename Construct [BP]/scripts/classes/{ => Structure}/StructureVerifier.js (100%) rename Construct [BP]/scripts/classes/enums/{InstanceEditButtons.js => InstanceButtons.js} (87%) delete mode 100644 Construct [BP]/scripts/lib/canopy/Rule.js rename Construct [BP]/scripts/{rules => options}/easyPlace.js (92%) rename Construct [BP]/scripts/{rules => options}/fastEasyPlace.js (93%) rename Construct [BP]/scripts/{rules => options}/materialGrabber.js (71%) diff --git a/Construct [BP]/manifest.json b/Construct [BP]/manifest.json index c0c3d71..6001919 100644 --- a/Construct [BP]/manifest.json +++ b/Construct [BP]/manifest.json @@ -1,7 +1,7 @@ { "format_version": 2, "header": { - "name": "StrucTool [BP] v1.0.0", + "name": "Construct [BP] v1.0.0", "description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.", "uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", "min_engine_version": [1, 21, 70], @@ -33,7 +33,7 @@ "version": "2.0.0-beta" }, { - "uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4", // StrucTool RP + "uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4", // Construct RP "version": [1, 0, 0] }, { diff --git a/Construct [BP]/scripts/classes/BlockVerificationLevelRender.js b/Construct [BP]/scripts/classes/BlockVerificationLevelRender.js index 34ebde3..70a6716 100644 --- a/Construct [BP]/scripts/classes/BlockVerificationLevelRender.js +++ b/Construct [BP]/scripts/classes/BlockVerificationLevelRender.js @@ -36,12 +36,12 @@ export class BlockVerificationLevelRender { const frontFace = new Vector(0.5, 0.5, 1); const backFace = new Vector(0.5, 0.5, 0); return [ - { particleType: "structool:blockoverlay_xz", location: this.location.add(topFace) }, - { particleType: "structool:blockoverlay_xz", location: this.location.add(bottomFace) }, - { particleType: "structool:blockoverlay_yz", location: this.location.add(leftFace) }, - { particleType: "structool:blockoverlay_yz", location: this.location.add(rightFace) }, - { particleType: "structool:blockoverlay_xy", location: this.location.add(frontFace) }, - { particleType: "structool:blockoverlay_xy", location: this.location.add(backFace) } + { particleType: "construct:blockoverlay_xz", location: this.location.add(topFace) }, + { particleType: "construct:blockoverlay_xz", location: this.location.add(bottomFace) }, + { particleType: "construct:blockoverlay_yz", location: this.location.add(leftFace) }, + { particleType: "construct:blockoverlay_yz", location: this.location.add(rightFace) }, + { particleType: "construct:blockoverlay_xy", location: this.location.add(frontFace) }, + { particleType: "construct:blockoverlay_xy", location: this.location.add(backFace) } ]; } diff --git a/Construct [BP]/scripts/classes/Builder/Builder.js b/Construct [BP]/scripts/classes/Builder/Builder.js new file mode 100644 index 0000000..0cc67a9 --- /dev/null +++ b/Construct [BP]/scripts/classes/Builder/Builder.js @@ -0,0 +1,22 @@ +import { BuilderOptions } from "./BuilderOptions"; +import { Builders } from "./Builders"; + +export class Builder { + constructor(playerId) { + this.playerId = playerId; + this.options = new BuilderOptions(playerId); + Builders.add(this); + } + + getOptionIds() { + return Object.keys(this.options.options).sort((a, b) => a.localeCompare(b)); + } + + getOption(id) { + return this.options.get(id); + } + + setOption(id, value) { + return this.options.setValue(id, value); + } +} \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Builder/BuilderForm.js b/Construct [BP]/scripts/classes/Builder/BuilderForm.js new file mode 100644 index 0000000..c3621e4 --- /dev/null +++ b/Construct [BP]/scripts/classes/Builder/BuilderForm.js @@ -0,0 +1,24 @@ +import { BuilderFormBuilder } from "./BuilderFormBuilder"; +import { Builders } from "./Builders"; + +export class BuilderForm { + constructor(player) { + this.player = player; + } + + show() { + forceShow(this.player, BuilderFormBuilder.buildSettings(this.player)).then((response) => { + if (response.canceled) return; + this.applySettings(response.formValues); + }); + } + + applySettings(formValues) { + const optionIds = Builders.get(this.player.id).getOptionIds(); + for (let i = 0; i < optionIds.length; i++) { + const option = optionIds[i]; + if (option.setValue(formValues[i]) && formValues[i] === true) + this.player.sendMessage(`§a${option.displayName} is enabled!§7 ${option.howToUse}`); + } + } +} \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Builder/BuilderFormBuilder.js b/Construct [BP]/scripts/classes/Builder/BuilderFormBuilder.js new file mode 100644 index 0000000..b5a43c1 --- /dev/null +++ b/Construct [BP]/scripts/classes/Builder/BuilderFormBuilder.js @@ -0,0 +1,15 @@ +import { ModalFormData } from "@minecraft/server-ui"; +import { MenuFormBuilder } from "../MenuFormBuilder"; +import { Builders } from "./Builders"; + +export class BuilderFormBuilder { + static buildSettings(player) { + const form = new ModalFormData() + .title(MenuFormBuilder.menuTitle); + const builder = Builders.get(player.id); + for (const optionId of builder.getOptionIds()) { + const option = builder.getOption(optionId); + form.toggle(`${option.displayName} - ${option.description}`, option.getValue()); + } + } +} \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Builder/BuilderOption.js b/Construct [BP]/scripts/classes/Builder/BuilderOption.js new file mode 100644 index 0000000..3153100 --- /dev/null +++ b/Construct [BP]/scripts/classes/Builder/BuilderOption.js @@ -0,0 +1,53 @@ +import { BuilderOptions } from "./BuilderOptions"; +import { Option } from "../Option"; + +export class BuilderOption extends Option { + playerId; + identifier; + displayName; + description; + value; + #onEnable; + #onDisable; + #DP_NAMESPACE = "builderOptions"; + + constructor({ player, identifier, displayName, description, howToUse, onEnableCallback = () => {}, onDisableCallback = () => {} }) { + this.playerId = player.id; + this.identifier = identifier; + this.displayName = displayName; + this.description = description; + this.howToUse = howToUse; + this.#onEnable = onEnableCallback; + this.#onDisable = onDisableCallback; + BuilderOptions.add(this); + } + + save() { + this.saveToDP(this.#DP_NAMESPACE, `${this.playerId}:${this.identifier}`, this); + } + + load() { + this.loadFromDP(this.#DP_NAMESPACE, `${this.playerId}:${this.identifier}`); + this.value = this.value ?? false; + } + + clear() { + this.clearDP(this.#DP_NAMESPACE, `${this.playerId}:${this.identifier}`); + } + + getValue() { + return this.value; + } + + setValue(value) { + if (value) + this.#onEnable(); + else + this.#onDisable(); + if (this.value !== value) { + this.value = value; + return value; + } + return void 0; + } +} \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Builder/BuilderOptions.js b/Construct [BP]/scripts/classes/Builder/BuilderOptions.js new file mode 100644 index 0000000..9a3927d --- /dev/null +++ b/Construct [BP]/scripts/classes/Builder/BuilderOptions.js @@ -0,0 +1,34 @@ +import { world } from "@minecraft/server"; +import { BuilderOption } from "./BuilderOption"; + +export class BuilderOptions { + playerId = void 0; + options = {}; + + constructor(playerId) { + this.playerId = playerId; + this.loadOptions(); + } + + loadOptions() { + const optionDPs = world.getDynamicPropertyIds().filter((id) => id.startsWith(`builderOptions:${this.playerId}:`)); + for (const optionDP of optionDPs) + new BuilderOption(JSON.parse(world.getDynamicProperty(optionDP))); + } + + add(builderOption) { + this.options[builderOption.identifer] = builderOption; + } + + get(id) { + return this.options[id]; + } + + getValue(id) { + return this.options[id].getValue(); + } + + setValue(id, value) { + return this.options[id].setValue(value); + } +} \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Builder/Builders.js b/Construct [BP]/scripts/classes/Builder/Builders.js new file mode 100644 index 0000000..2cd6458 --- /dev/null +++ b/Construct [BP]/scripts/classes/Builder/Builders.js @@ -0,0 +1,25 @@ +import { world } from "@minecraft/server"; +import { Builder } from "./Builder"; + +export class Builders { + static builders = {}; + + add(playerId) { + this.builders[playerId] = new Builder(playerId); + } + + remove(playerId) { + delete this.builders[playerId]; + } + + get(id) { + return this.builders[id]; + } + + onJoin(playerId) { + this.add(playerId); + } +} + +world.afterEvents.playerJoin.subscribe((event) => Builders.onJoin(event.playerId)); +world.beforeEvents.playerLeave.subscribe((event) => Builders.onLeave(event.player)); \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/InstanceEditForm.js b/Construct [BP]/scripts/classes/Instance/InstanceForm.js similarity index 61% rename from Construct [BP]/scripts/classes/InstanceEditForm.js rename to Construct [BP]/scripts/classes/Instance/InstanceForm.js index 208ef1c..ebdf90e 100644 --- a/Construct [BP]/scripts/classes/InstanceEditForm.js +++ b/Construct [BP]/scripts/classes/Instance/InstanceForm.js @@ -1,34 +1,34 @@ import { structureCollection } from './StructureCollection'; -import { MenuForm } from './MenuForm'; -import { forceShow } from '../utils'; -import { InstanceEditButtons } from './enums/InstanceEditButtons'; -import { InstanceEditFormBuilder } from './InstanceEditFormBuilder'; +import { MenuForm } from '../MenuForm'; +import { forceShow } from '../../utils'; +import { InstanceButtons } from '../enums/InstanceButtons'; +import { InstanceFormBuilder } from './InstanceFormBuilder'; import { FormCancelationReason } from '@minecraft/server-ui'; -export class InstanceEditForm { +export class InstanceForm { instanceName; #buttons = { isEnabled: [ - InstanceEditButtons.NextLayer, - InstanceEditButtons.PreviousLayer, - InstanceEditButtons.SetLayer, - InstanceEditButtons.Move, - InstanceEditButtons.Statistics, - InstanceEditButtons.Settings, - InstanceEditButtons.Rename, - InstanceEditButtons.Disable, + InstanceButtons.NextLayer, + InstanceButtons.PreviousLayer, + InstanceButtons.SetLayer, + InstanceButtons.Move, + InstanceButtons.Statistics, + InstanceButtons.Settings, + InstanceButtons.Rename, + InstanceButtons.Disable, ], isNotEnabledAndIsNotPlaced: [ - InstanceEditButtons.Place, - InstanceEditButtons.Rename + InstanceButtons.Place, + InstanceButtons.Rename ], isNotEnabledButIsPlaced: [ - InstanceEditButtons.Enable, - InstanceEditButtons.Rename + InstanceButtons.Enable, + InstanceButtons.Rename ], common: [ - InstanceEditButtons.Delete, - InstanceEditButtons.MainMenu + InstanceButtons.Delete, + InstanceButtons.MainMenu ] } @@ -41,7 +41,7 @@ export class InstanceEditForm { show() { const currentOptions = this.getActiveOptions(); - forceShow(this.player, InstanceEditFormBuilder.buildInstance(this.instance, currentOptions)).then((response) => { + forceShow(this.player, InstanceFormBuilder.buildInstance(this.instance, currentOptions)).then((response) => { if (response.canceled) return; this.handleOption(currentOptions[response.selection]); }); @@ -59,48 +59,48 @@ export class InstanceEditForm { if (!this.instance.hasLayers()) currentOptions = currentOptions.filter(option => - option !== InstanceEditButtons.SetLayer - && option !== InstanceEditButtons.NextLayer - && option !== InstanceEditButtons.PreviousLayer + option !== InstanceButtons.SetLayer + && option !== InstanceButtons.NextLayer + && option !== InstanceButtons.PreviousLayer ); return currentOptions; } handleOption(option) { switch (option) { - case InstanceEditButtons.Enable: + case InstanceButtons.Enable: this.instance.enable(); break; - case InstanceEditButtons.Disable: + case InstanceButtons.Disable: this.instance.disable(); break; - case InstanceEditButtons.Place: + case InstanceButtons.Place: this.instance.place(this.player.dimension.id, this.player.location); break; - case InstanceEditButtons.Rename: + case InstanceButtons.Rename: this.renameInstanceForm(); break; - case InstanceEditButtons.Delete: + case InstanceButtons.Delete: structureCollection.delete(this.instanceName); break; - case InstanceEditButtons.NextLayer: + case InstanceButtons.NextLayer: this.instance.increaseLayer(); - new InstanceEditForm(this.player, this.instanceName); + new InstanceForm(this.player, this.instanceName); break; - case InstanceEditButtons.PreviousLayer: + case InstanceButtons.PreviousLayer: this.instance.decreaseLayer(); - new InstanceEditForm(this.player, this.instanceName); + new InstanceForm(this.player, this.instanceName); break; - case InstanceEditButtons.Settings: + case InstanceButtons.Settings: this.settingsForm(); break; - case InstanceEditButtons.Move: + case InstanceButtons.Move: this.instance.move(this.player.dimension.id, this.player.location); break; - case InstanceEditButtons.Statistics: + case InstanceButtons.Statistics: this.statisticsForm(); break; - case InstanceEditButtons.MainMenu: + case InstanceButtons.MainMenu: new MenuForm(this.player, { jumpToInstance: false }); break; default: @@ -110,7 +110,7 @@ export class InstanceEditForm { } renameInstanceForm() { - InstanceEditFormBuilder.buildRenameInstance(this.instanceName).show(this.player).then((response) => { + InstanceFormBuilder.buildRenameInstance(this.instanceName).show(this.player).then((response) => { if (response.canceled) return; const newName = response.formValues[0]; @@ -129,7 +129,7 @@ export class InstanceEditForm { } setLayerForm() { - InstanceEditFormBuilder.buildSetLayer(this.instance.getBounds().max.y, this.instance.getLayer()).show(this.player).then((response) => { + InstanceFormBuilder.buildSetLayer(this.instance.getBounds().max.y, this.instance.getLayer()).show(this.player).then((response) => { if (response.canceled) return; this.instance.setLayer(parseInt(response.formValues[0])); @@ -137,7 +137,7 @@ export class InstanceEditForm { } async statisticsForm() { - const statsForm = await InstanceEditFormBuilder.buildStatistics(this.instance) + const statsForm = await InstanceFormBuilder.buildStatistics(this.instance) statsForm.form.show(this.player).then((response) => { if (response.canceled && response.cancelationReason === FormCancelationReason.UserBusy) this.player.sendMessage(statsForm.stats); @@ -145,7 +145,7 @@ export class InstanceEditForm { } settingsForm() { - InstanceEditFormBuilder.buildSettings(this.instance).show(this.player).then((response) => { + InstanceFormBuilder.buildSettings(this.instance).show(this.player).then((response) => { if (response.canceled) return; this.instance.setVerifierEnabled(response.formValues[0]); diff --git a/Construct [BP]/scripts/classes/InstanceEditFormBuilder.js b/Construct [BP]/scripts/classes/Instance/InstanceFormBuilder.js similarity index 91% rename from Construct [BP]/scripts/classes/InstanceEditFormBuilder.js rename to Construct [BP]/scripts/classes/Instance/InstanceFormBuilder.js index fae3ac3..848efb7 100644 --- a/Construct [BP]/scripts/classes/InstanceEditFormBuilder.js +++ b/Construct [BP]/scripts/classes/Instance/InstanceFormBuilder.js @@ -1,10 +1,10 @@ import { ActionFormData, ModalFormData } from '@minecraft/server-ui'; -import { MenuFormBuilder } from './MenuFormBuilder'; +import { MenuFormBuilder } from '../MenuFormBuilder'; import { StructureVerifier } from './StructureVerifier'; import { StructureStatistics } from './StructureStatistics'; import { TicksPerSecond } from '@minecraft/server'; -export class InstanceEditFormBuilder { +export class InstanceFormBuilder { static buildInstance(instance, options) { const location = instance.getLocation(); const form = new ActionFormData() @@ -41,7 +41,7 @@ export class InstanceEditFormBuilder { return new ModalFormData() .title(MenuFormBuilder.menuTitle) .label('Use the slider to select the layer. Use 0 for all layers.') - .toggle('Toggle block validation.', instance.options.verifier.isEnabled) + .toggle('Block Validation', instance.options.verifier.isEnabled) .slider("Layer", 0, maxLayer, 1, currentLayer) .submitButton('§aApply'); } diff --git a/Construct [BP]/scripts/classes/InstanceOptions.js b/Construct [BP]/scripts/classes/Instance/InstanceOptions.js similarity index 68% rename from Construct [BP]/scripts/classes/InstanceOptions.js rename to Construct [BP]/scripts/classes/Instance/InstanceOptions.js index afac847..34f834d 100644 --- a/Construct [BP]/scripts/classes/InstanceOptions.js +++ b/Construct [BP]/scripts/classes/Instance/InstanceOptions.js @@ -1,7 +1,9 @@ -import { Vector } from "../lib/Vector"; +import { Vector } from "../../lib/Vector"; import { world } from "@minecraft/server"; +import { Option } from "../Option"; -export class InstanceOptions { +export class InstanceOptions extends Option { + #DP_NAMESPACE = "instanceOptions"; instanceName = void 0; structureId = void 0; isEnabled = false; @@ -26,26 +28,16 @@ export class InstanceOptions { } save() { - world.setDynamicProperty(`instanceOptions:${this.instanceName}`, JSON.stringify(this)); + this.saveToDP(this.#DP_NAMESPACE, this.instanceName, this); } load() { - try { - const options = JSON.parse(world.getDynamicProperty(`instanceOptions:${this.instanceName}`)); - if (options) - Object.assign(this, options); - else - throw new Error("Options not found"); - } catch { - this.save(); - const options = JSON.parse(world.getDynamicProperty(`instanceOptions:${this.instanceName}`) || "{}"); - Object.assign(this, options); - } + this.loadFromDP(this.#DP_NAMESPACE, this.instanceName); this.worldLocation = Vector.from(this.worldLocation); } clear() { - world.setDynamicProperty(`instanceOptions:${this.instanceName}`, void 0); + this.clearDP(this.#DP_NAMESPACE, this.instanceName); } getDimension() { diff --git a/Construct [BP]/scripts/classes/MenuForm.js b/Construct [BP]/scripts/classes/MenuForm.js index 03b8c50..10158e6 100644 --- a/Construct [BP]/scripts/classes/MenuForm.js +++ b/Construct [BP]/scripts/classes/MenuForm.js @@ -1,7 +1,7 @@ import { forceShow } from '../utils'; import { structureCollection } from './StructureCollection'; import { MenuFormBuilder } from './MenuFormBuilder'; -import { InstanceEditForm } from './InstanceEditForm'; +import { InstanceForm } from './InstanceForm'; export class MenuForm { constructor(player, { jumpToInstance = false, instanceName = void 0 } = {}) { @@ -14,14 +14,14 @@ export class MenuForm { if (!instanceName) instanceName = structureCollection.getStructure(this.player.dimension.id, this.player.location, { useActiveLayer: false })?.getName(); if (structureCollection.get(instanceName)) { - new InstanceEditForm(this.player, instanceName); + new InstanceForm(this.player, instanceName); return; } } instanceName = await this.getInstanceNameFromForm(); if (!instanceName) return; - new InstanceEditForm(this.player, instanceName); + new InstanceForm(this.player, instanceName); } async getInstanceNameFromForm() { diff --git a/Construct [BP]/scripts/classes/MenuFormBuilder.js b/Construct [BP]/scripts/classes/MenuFormBuilder.js index 390377a..dcde0bd 100644 --- a/Construct [BP]/scripts/classes/MenuFormBuilder.js +++ b/Construct [BP]/scripts/classes/MenuFormBuilder.js @@ -2,7 +2,7 @@ import { ActionFormData, ModalFormData } from '@minecraft/server-ui'; import { structureCollection } from './StructureCollection'; export class MenuFormBuilder { - static menuTitle = '§l§2StrucTool §8Menu'; + static menuTitle = '§l§2Construct §8Menu'; static buildAllInstanceName() { const allInstanceNameForm = new ActionFormData() diff --git a/Construct [BP]/scripts/classes/Option.js b/Construct [BP]/scripts/classes/Option.js new file mode 100644 index 0000000..72d39b0 --- /dev/null +++ b/Construct [BP]/scripts/classes/Option.js @@ -0,0 +1,25 @@ +import { world } from "@minecraft/server"; + +export class Option { + saveToDP(namespace, id, data) { + world.setDynamicProperty(`${namespace}:${id}`, JSON.stringify(data)); + } + + loadFromDP(namespace, id) { + try { + const options = JSON.parse(world.getDynamicProperty(`${namespace}:${id}`)); + if (options) + Object.assign(this, options); + else + throw new Error("Options not found"); + } catch { + this.saveToDP(namespace, id, {}); + const options = JSON.parse(world.getDynamicProperty(`${namespace}:${id}`) || "{}"); + Object.assign(this, options); + } + } + + clearDP(namespace, id) { + world.setDynamicProperty(`${namespace}:${id}`, void 0); + } +} \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Outliner.js b/Construct [BP]/scripts/classes/Outliner.js index 6343e58..be1baf8 100644 --- a/Construct [BP]/scripts/classes/Outliner.js +++ b/Construct [BP]/scripts/classes/Outliner.js @@ -5,7 +5,7 @@ export class Outliner { dimension; min = new Vector(); max = new Vector(); - drawParticle = "structool:outline"; + drawParticle = "construct:outline"; drawFrequency = 10; #drawParticles = []; diff --git a/Construct [BP]/scripts/classes/Structure.js b/Construct [BP]/scripts/classes/Structure/Structure.js similarity index 96% rename from Construct [BP]/scripts/classes/Structure.js rename to Construct [BP]/scripts/classes/Structure/Structure.js index 983d6bb..58a36ef 100644 --- a/Construct [BP]/scripts/classes/Structure.js +++ b/Construct [BP]/scripts/classes/Structure/Structure.js @@ -9,7 +9,7 @@ export class Structure { this.structureId = structureId; this.#structure = world.structureManager.get(structureId); if (!this.#structure) - throw new Error(`[StrucTool] Structure '${structureId}' not found.`); + throw new Error(`[Construct] Structure '${structureId}' not found.`); this.#structure.saveToWorld(); } diff --git a/Construct [BP]/scripts/classes/StructureCollection.js b/Construct [BP]/scripts/classes/Structure/StructureCollection.js similarity index 98% rename from Construct [BP]/scripts/classes/StructureCollection.js rename to Construct [BP]/scripts/classes/Structure/StructureCollection.js index 299067c..26f1907 100644 --- a/Construct [BP]/scripts/classes/StructureCollection.js +++ b/Construct [BP]/scripts/classes/Structure/StructureCollection.js @@ -17,7 +17,7 @@ class StructureCollection { structureId = InstanceOptions.getInstanceStrucetureId(instanceName); this.structures[instanceName] = new StructureInstance(instanceName, structureId); } catch (e) { - world.sendMessage(`§c[StrucTool] Error loading structure instance '${instanceName}'. It will be removed.`); + world.sendMessage(`§c[Construct] Error loading structure instance '${instanceName}'. It will be removed.`); world.setDynamicProperty(id, void 0); throw e; } diff --git a/Construct [BP]/scripts/classes/StructureInstance.js b/Construct [BP]/scripts/classes/Structure/StructureInstance.js similarity index 95% rename from Construct [BP]/scripts/classes/StructureInstance.js rename to Construct [BP]/scripts/classes/Structure/StructureInstance.js index 8104ff6..8117fef 100644 --- a/Construct [BP]/scripts/classes/StructureInstance.js +++ b/Construct [BP]/scripts/classes/Structure/StructureInstance.js @@ -70,7 +70,7 @@ export class StructureInstance { getActiveBounds() { if (!this.options.isEnabled) - throw new Error(`[StrucTool] Instance '${this.options.instanceName}' is not placed.`); + throw new Error(`[Construct] Instance '${this.options.instanceName}' is not placed.`); if (this.hasLayerSelected()) return this.getLayerBounds(this.getLayer()); return this.getBounds(); @@ -78,7 +78,7 @@ export class StructureInstance { getLayerBounds(layer) { if (!this.options.isEnabled) - throw new Error(`[StrucTool] Instance '${this.options.instanceName}' is not placed.`); + throw new Error(`[Construct] Instance '${this.options.instanceName}' is not placed.`); const min = this.structure.getMin(); const max = this.structure.getMax(); return { @@ -105,7 +105,7 @@ export class StructureInstance { getActiveBlocks() { if (!this.options.isEnabled) - throw new Error(`[StrucTool] Instance '${this.options.instanceName}' is not placed.`); + throw new Error(`[Construct] Instance '${this.options.instanceName}' is not placed.`); if (this.hasLayerSelected()) return this.getLayerBlocks(this.getLayer()); return this.getAllBlocks(); @@ -126,7 +126,7 @@ export class StructureInstance { getAllActiveLocations() { if (!this.options.isEnabled) - throw new Error(`[StrucTool] Instance '${this.options.instanceName}' is not placed.`); + throw new Error(`[Construct] Instance '${this.options.instanceName}' is not placed.`); if (this.hasLayerSelected()) return this.structure.getLayerLocations(this.getLayer()-1); else @@ -187,7 +187,7 @@ export class StructureInstance { setLayer(layer) { if (layer < 0 || layer > this.getMaxLayer()) - throw new Error(`[StrucTool] Layer ${layer} is out of bounds.`); + throw new Error(`[Construct] Layer ${layer} is out of bounds.`); this.options.setLayer(layer); this.refreshBox(); } diff --git a/Construct [BP]/scripts/classes/StructureOutliner.js b/Construct [BP]/scripts/classes/Structure/StructureOutliner.js similarity index 100% rename from Construct [BP]/scripts/classes/StructureOutliner.js rename to Construct [BP]/scripts/classes/Structure/StructureOutliner.js diff --git a/Construct [BP]/scripts/classes/StructureStatistics.js b/Construct [BP]/scripts/classes/Structure/StructureStatistics.js similarity index 100% rename from Construct [BP]/scripts/classes/StructureStatistics.js rename to Construct [BP]/scripts/classes/Structure/StructureStatistics.js diff --git a/Construct [BP]/scripts/classes/StructureVerifier.js b/Construct [BP]/scripts/classes/Structure/StructureVerifier.js similarity index 100% rename from Construct [BP]/scripts/classes/StructureVerifier.js rename to Construct [BP]/scripts/classes/Structure/StructureVerifier.js diff --git a/Construct [BP]/scripts/classes/enums/InstanceEditButtons.js b/Construct [BP]/scripts/classes/enums/InstanceButtons.js similarity index 87% rename from Construct [BP]/scripts/classes/enums/InstanceEditButtons.js rename to Construct [BP]/scripts/classes/enums/InstanceButtons.js index 08fcd06..a066923 100644 --- a/Construct [BP]/scripts/classes/enums/InstanceEditButtons.js +++ b/Construct [BP]/scripts/classes/enums/InstanceButtons.js @@ -1,4 +1,4 @@ -export const InstanceEditButtons = Object.freeze({ +export const InstanceButtons = Object.freeze({ Unknown: 'Unknown', MainMenu: '<<', Place: '§aPlace Instance', diff --git a/Construct [BP]/scripts/commands/construct.js b/Construct [BP]/scripts/commands/construct.js index f6454c5..34ad208 100644 --- a/Construct [BP]/scripts/commands/construct.js +++ b/Construct [BP]/scripts/commands/construct.js @@ -6,9 +6,9 @@ import { MenuForm } from '../classes/MenuForm'; const ACTION_ITEM = 'minecraft:paper'; const menuCmd = new Command({ - name: 'structool', - description: { text: 'Opens the StrucTool Menu. Using a paper will also open the menu.' }, - usage: 'structool', + name: 'construct', + description: { text: 'Opens the Construct Menu. Using a paper will also open the menu.' }, + usage: 'construct', callback: (sender) => openMenu(sender) }); extension.addCommand(menuCmd); diff --git a/Construct [BP]/scripts/config.js b/Construct [BP]/scripts/config.js index 39efd76..e1de1d4 100644 --- a/Construct [BP]/scripts/config.js +++ b/Construct [BP]/scripts/config.js @@ -2,7 +2,7 @@ import { CanopyExtension } from './lib/canopy/CanopyExtension'; export const extension = new CanopyExtension({ author: 'ForestOfLight', - name: 'StrucTool', - description: 'Survival building extension for §l§aCanopy§r!', + name: 'Construct', + description: 'Make building structures in survival easier!', version: '1.0.0' }); \ No newline at end of file diff --git a/Construct [BP]/scripts/lib/canopy/Rule.js b/Construct [BP]/scripts/lib/canopy/Rule.js deleted file mode 100644 index cafae2f..0000000 --- a/Construct [BP]/scripts/lib/canopy/Rule.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @license - * MIT License - * - * Copyright (c) 2024 ForestOfLight - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -import { world } from '@minecraft/server'; - -class Rule { - #identifier; - #description; - #contingentRules; - #independentRules; - - constructor({ identifier, description, contingentRules = [], independentRules = [], onEnableCallback = () => {}, onDisableCallback = () => {} }) { - this.#identifier = identifier; - this.#description = description; - this.#contingentRules = contingentRules; - this.#independentRules = independentRules; - this.onEnable = onEnableCallback; - this.onDisable = onDisableCallback; - } - - getID() { - return this.#identifier; - } - - getDescription() { - return this.#description; - } - - getContigentRules() { - return this.#contingentRules; - } - - getIndependentRules() { - return this.#independentRules; - } - - getValue() { - const value = world.getDynamicProperty(this.#identifier); - if (String(value) === 'true') - return true; - if (['false', 'undefined'].includes(String(value))) - return false; - throw new Error(`Rule ${this.#identifier} has an invalid value: ${value} (${typeof value})`); - } - - setValue(value) { - if (value === true) - this.onEnable(); - else - this.onDisable(); - world.setDynamicProperty(this.#identifier, value); - } -} - -export default Rule; \ No newline at end of file diff --git a/Construct [BP]/scripts/rules/easyPlace.js b/Construct [BP]/scripts/options/easyPlace.js similarity index 92% rename from Construct [BP]/scripts/rules/easyPlace.js rename to Construct [BP]/scripts/options/easyPlace.js index 519f1f6..0192982 100644 --- a/Construct [BP]/scripts/rules/easyPlace.js +++ b/Construct [BP]/scripts/options/easyPlace.js @@ -1,5 +1,4 @@ -import { Rule } from '../lib/canopy/CanopyExtension'; -import { extension } from '../config'; +import { BuilderOption } from '../classes/Builder/BuilderOption'; import { BlockPermutation, EntityComponentTypes, GameMode, ItemStack, system, world } from '@minecraft/server'; import { structureCollection } from '../classes/StructureCollection'; import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, specialItemPlacementConversions, @@ -8,13 +7,14 @@ import { fetchMatchingItemSlot } from '../utils'; const ACTION_SLOT = 35; -const easyPlace = new Rule({ +new BuilderOption({ identifier: 'easyPlace', - description: { text: "Automatically places the correct block in a structure (paper named 'easyPlace' in bottom right inventory slot)." }, + displayName: 'Easy Place', + description: 'Always place the correct block.', + howToUse: "Place blocks in a structure with a paper named 'Easy Place' in the bottom right slot of your inventory to always place the correct block.", onEnableCallback: () => { world.beforeEvents.playerPlaceBlock.subscribe(onPlayerPlaceBlock); }, onDisableCallback: () => { world.beforeEvents.playerPlaceBlock.unsubscribe(onPlayerPlaceBlock); } }) -extension.addRule(easyPlace); function onPlayerPlaceBlock(event) { const { player, block, permutationBeingPlaced } = event; @@ -30,7 +30,7 @@ function hasActionItemInCorrectSlot(player) { if (!inventory) return false; const actionSlot = inventory.getSlot(ACTION_SLOT); - return actionSlot.hasItem() && actionSlot.typeId === 'minecraft:paper' && actionSlot.nameTag === 'easyPlace'; + return actionSlot.hasItem() && actionSlot.typeId === 'minecraft:paper' && actionSlot.nameTag === 'Easy Place'; } function tryPlaceBlock(event, player, block, structureBlock) { diff --git a/Construct [BP]/scripts/rules/fastEasyPlace.js b/Construct [BP]/scripts/options/fastEasyPlace.js similarity index 93% rename from Construct [BP]/scripts/rules/fastEasyPlace.js rename to Construct [BP]/scripts/options/fastEasyPlace.js index fde0289..8bef942 100644 --- a/Construct [BP]/scripts/rules/fastEasyPlace.js +++ b/Construct [BP]/scripts/options/fastEasyPlace.js @@ -1,18 +1,18 @@ -import { Rule } from '../lib/canopy/CanopyExtension'; -import { extension } from '../config'; +import { BuilderOption } from '../classes/Builder/BuilderOption'; import { BlockPermutation, EntityComponentTypes, EquipmentSlot, GameMode, ItemStack, system, world } from '@minecraft/server'; import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, specialItemPlacementConversions, blockIdToItemStackMap } from '../data'; import { Raycaster } from '../classes/Raycaster'; let runner = void 0; -const easyPlace = new Rule({ +new BuilderOption({ identifier: 'fastEasyPlace', - description: { text: "Looking at a structure block with a paper named 'easyPlace' in your hand will place it." }, + displayName: 'Fast Easy Place', + description: 'Place structure blocks just by looking at them.', + howToUse: "Look at structure blocks with a paper named 'Easy Place' in your hand to place them.", onEnableCallback: () => { runner = system.runInterval(onTick, 2); }, onDisableCallback: () => { system.clearRun(runner); } }) -extension.addRule(easyPlace); function onTick() { for (const player of world.getAllPlayers()) { @@ -35,7 +35,7 @@ function isHoldingActionItem(player) { const mainhandItemStack = player.getComponent(EntityComponentTypes.Equippable).getEquipment(EquipmentSlot.Mainhand); if (!mainhandItemStack) return false; - return mainhandItemStack.typeId === 'minecraft:paper' && mainhandItemStack.nameTag === 'easyPlace'; + return mainhandItemStack.typeId === 'minecraft:paper' && mainhandItemStack.nameTag === 'Easy Place'; } function tryPlaceBlock(player, worldBlock, structureBlock) { diff --git a/Construct [BP]/scripts/rules/materialGrabber.js b/Construct [BP]/scripts/options/materialGrabber.js similarity index 71% rename from Construct [BP]/scripts/rules/materialGrabber.js rename to Construct [BP]/scripts/options/materialGrabber.js index 411a9f3..4d5352f 100644 --- a/Construct [BP]/scripts/rules/materialGrabber.js +++ b/Construct [BP]/scripts/options/materialGrabber.js @@ -1,10 +1,11 @@ -import { Rule } from '../lib/canopy/CanopyExtension'; -import { extension } from '../config'; +import { BuilderOption } from '../classes/Builder/BuilderOption'; import { world } from '@minecraft/server'; -const easyPlace = new Rule({ +new BuilderOption({ identifier: 'materialGrabber', - description: { text: "Automatically grabs structure materials out of inventories. Use a paper named 'materialGrabber' to get started." }, + displayName: 'Material Grabber', + description: 'Pulls structure items from inventories.', + howToUse: "Interact with inventories using a paper named 'Material Grabber' to pull structure items from them.", onEnableCallback: () => { world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteract); world.beforeEvents.playerInteractWithEntity.subscribe(onPlayerInteract); @@ -14,7 +15,6 @@ const easyPlace = new Rule({ world.beforeEvents.playerInteractWithEntity.unsubscribe(onPlayerInteract); } }) -extension.addRule(easyPlace); function onPlayerInteract(event) { // get inventory diff --git a/Construct [RP]/manifest.json b/Construct [RP]/manifest.json index 6ab353c..abb11fc 100644 --- a/Construct [RP]/manifest.json +++ b/Construct [RP]/manifest.json @@ -1,7 +1,7 @@ { "format_version": 2, "header": { - "name": "StrucTool [RP] v1.0.0", + "name": "Construct [RP] v1.0.0", "description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.", "uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4", "version": [1, 0, 0], @@ -16,7 +16,7 @@ ], "dependencies": [ { - "uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", // StrucTool BP + "uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", // Construct BP "version": [1, 0, 0] }, { diff --git a/Construct [RP]/particles/blockoverlay_xy.particle.json b/Construct [RP]/particles/blockoverlay_xy.particle.json index 541e12a..c3d1e9d 100644 --- a/Construct [RP]/particles/blockoverlay_xy.particle.json +++ b/Construct [RP]/particles/blockoverlay_xy.particle.json @@ -2,7 +2,7 @@ "format_version": "1.10.0", "particle_effect": { "description": { - "identifier": "structool:blockoverlay_xy", + "identifier": "construct:blockoverlay_xy", "basic_render_parameters": { "material": "particles_blend", "texture": "textures/particle/white" diff --git a/Construct [RP]/particles/blockoverlay_xz.particle.json b/Construct [RP]/particles/blockoverlay_xz.particle.json index 4140060..408c139 100644 --- a/Construct [RP]/particles/blockoverlay_xz.particle.json +++ b/Construct [RP]/particles/blockoverlay_xz.particle.json @@ -2,7 +2,7 @@ "format_version": "1.10.0", "particle_effect": { "description": { - "identifier": "structool:blockoverlay_xz", + "identifier": "construct:blockoverlay_xz", "basic_render_parameters": { "material": "particles_blend", "texture": "textures/particle/white" diff --git a/Construct [RP]/particles/blockoverlay_yz.particle.json b/Construct [RP]/particles/blockoverlay_yz.particle.json index 76b4ef5..17246a0 100644 --- a/Construct [RP]/particles/blockoverlay_yz.particle.json +++ b/Construct [RP]/particles/blockoverlay_yz.particle.json @@ -2,7 +2,7 @@ "format_version": "1.10.0", "particle_effect": { "description": { - "identifier": "structool:blockoverlay_yz", + "identifier": "construct:blockoverlay_yz", "basic_render_parameters": { "material": "particles_blend", "texture": "textures/particle/white" diff --git a/Construct [RP]/particles/outline.particle.json b/Construct [RP]/particles/outline.particle.json index 7fd6ad6..304db04 100644 --- a/Construct [RP]/particles/outline.particle.json +++ b/Construct [RP]/particles/outline.particle.json @@ -2,7 +2,7 @@ "format_version": "1.10.0", "particle_effect": { "description": { - "identifier": "structool:outline", + "identifier": "construct:outline", "basic_render_parameters": { "material": "particles_alpha", "texture": "textures/particle/particles" From 1ca91a09930d7042eb0223f87dc9d5d551edd15e Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sun, 20 Apr 2025 02:06:55 -0700 Subject: [PATCH 2/4] Now is standalone from Canopy --- .../scripts/classes/Builder/Builder.js | 15 ++-- .../scripts/classes/Builder/BuilderForm.js | 25 +++++-- .../classes/Builder/BuilderFormBuilder.js | 13 ++-- .../scripts/classes/Builder/BuilderOption.js | 42 +++++------ .../scripts/classes/Builder/BuilderOptions.js | 33 +++------ .../scripts/classes/Builder/Builders.js | 21 ++++-- .../classes/Errors/InvalidInstanceError.js | 6 ++ .../scripts/classes/Instance/InstanceForm.js | 13 ++-- .../classes/Instance/InstanceFormBuilder.js | 13 ++-- .../classes/Instance/InstanceOptions.js | 1 + .../StructureInstance.js | 10 +-- .../{ => Materials}/MaterialCounter.js | 0 Construct [BP]/scripts/classes/MenuForm.js | 18 +++-- .../scripts/classes/MenuFormBuilder.js | 5 +- Construct [BP]/scripts/classes/Raycaster.js | 2 +- .../scripts/classes/Structure/Structure.js | 2 +- .../classes/Structure/StructureCollection.js | 12 ++-- .../classes/Structure/StructureOutliner.js | 4 +- .../classes/Structure/StructureStatistics.js | 2 +- .../BlockVerificationLevelRender.js | 4 +- .../classes/{ => Verifier}/BlockVerifier.js | 2 +- .../StructureVerifier.js | 27 +++++-- Construct [BP]/scripts/commands/construct.js | 9 ++- Construct [BP]/scripts/lib/canopy/Rule.js | 70 +++++++++++++++++++ Construct [BP]/scripts/main.js | 11 +-- Construct [BP]/scripts/options/easyPlace.js | 20 +++--- .../scripts/options/fastEasyPlace.js | 12 ++-- .../scripts/options/materialGrabber.js | 15 ++-- 28 files changed, 256 insertions(+), 151 deletions(-) create mode 100644 Construct [BP]/scripts/classes/Errors/InvalidInstanceError.js rename Construct [BP]/scripts/classes/{Structure => Instance}/StructureInstance.js (96%) rename Construct [BP]/scripts/classes/{ => Materials}/MaterialCounter.js (100%) rename Construct [BP]/scripts/classes/{ => Verifier}/BlockVerificationLevelRender.js (95%) rename Construct [BP]/scripts/classes/{ => Verifier}/BlockVerifier.js (95%) rename Construct [BP]/scripts/classes/{Structure => Verifier}/StructureVerifier.js (84%) create mode 100644 Construct [BP]/scripts/lib/canopy/Rule.js diff --git a/Construct [BP]/scripts/classes/Builder/Builder.js b/Construct [BP]/scripts/classes/Builder/Builder.js index 0cc67a9..afadf1e 100644 --- a/Construct [BP]/scripts/classes/Builder/Builder.js +++ b/Construct [BP]/scripts/classes/Builder/Builder.js @@ -1,22 +1,15 @@ import { BuilderOptions } from "./BuilderOptions"; -import { Builders } from "./Builders"; export class Builder { constructor(playerId) { this.playerId = playerId; - this.options = new BuilderOptions(playerId); - Builders.add(this); } - getOptionIds() { - return Object.keys(this.options.options).sort((a, b) => a.localeCompare(b)); + isOptionEnabled(optionId) { + return BuilderOptions.isEnabled(optionId, this.playerId); } - getOption(id) { - return this.options.get(id); - } - - setOption(id, value) { - return this.options.setValue(id, value); + setOption(optionId, value) { + return BuilderOptions.setValue(optionId, this.playerId, value); } } \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Builder/BuilderForm.js b/Construct [BP]/scripts/classes/Builder/BuilderForm.js index c3621e4..174a718 100644 --- a/Construct [BP]/scripts/classes/Builder/BuilderForm.js +++ b/Construct [BP]/scripts/classes/Builder/BuilderForm.js @@ -1,24 +1,37 @@ import { BuilderFormBuilder } from "./BuilderFormBuilder"; -import { Builders } from "./Builders"; +import { BuilderOptions } from "./BuilderOptions"; +import { forceShow } from '../../utils'; export class BuilderForm { constructor(player) { this.player = player; + this.show(); } show() { - forceShow(this.player, BuilderFormBuilder.buildSettings(this.player)).then((response) => { + forceShow(this.player, BuilderFormBuilder.buildBuilderOptions(this.player)).then((response) => { if (response.canceled) return; this.applySettings(response.formValues); }); } applySettings(formValues) { - const optionIds = Builders.get(this.player.id).getOptionIds(); + const optionIds = BuilderOptions.getOptionIds(); for (let i = 0; i < optionIds.length; i++) { - const option = optionIds[i]; - if (option.setValue(formValues[i]) && formValues[i] === true) - this.player.sendMessage(`§a${option.displayName} is enabled!§7 ${option.howToUse}`); + const option = BuilderOptions.get(optionIds[i]); + const changedToValue = option.setValue(this.player.id, formValues[i]); + if (changedToValue === true) + this.player.sendMessage(`§a${option.displayName} is now enabled!§7 ${option.howToUse}`); + else if (changedToValue === false) + this.player.sendMessage(`§c${option.displayName} is now disabled.`) } } + + valueChangedToEnabled(hasChanged, newValue) { + return hasChanged && newValue === true; + } + + valueChangedToDisabled(hasChanged, newValue) { + return hasChanged && newValue === false; + } } \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Builder/BuilderFormBuilder.js b/Construct [BP]/scripts/classes/Builder/BuilderFormBuilder.js index b5a43c1..736cc43 100644 --- a/Construct [BP]/scripts/classes/Builder/BuilderFormBuilder.js +++ b/Construct [BP]/scripts/classes/Builder/BuilderFormBuilder.js @@ -1,15 +1,16 @@ import { ModalFormData } from "@minecraft/server-ui"; import { MenuFormBuilder } from "../MenuFormBuilder"; -import { Builders } from "./Builders"; +import { BuilderOptions } from "./BuilderOptions"; export class BuilderFormBuilder { - static buildSettings(player) { + static buildBuilderOptions(player) { const form = new ModalFormData() .title(MenuFormBuilder.menuTitle); - const builder = Builders.get(player.id); - for (const optionId of builder.getOptionIds()) { - const option = builder.getOption(optionId); - form.toggle(`${option.displayName} - ${option.description}`, option.getValue()); + for (const optionId of BuilderOptions.getOptionIds()) { + const option = BuilderOptions.get(optionId); + form.toggle(`${option.displayName} - ${option.description}`, option.isEnabled(player.id)); } + form.submitButton('§2Apply'); + return form; } } \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Builder/BuilderOption.js b/Construct [BP]/scripts/classes/Builder/BuilderOption.js index 3153100..cdd981c 100644 --- a/Construct [BP]/scripts/classes/Builder/BuilderOption.js +++ b/Construct [BP]/scripts/classes/Builder/BuilderOption.js @@ -1,18 +1,16 @@ import { BuilderOptions } from "./BuilderOptions"; -import { Option } from "../Option"; +import { world } from "@minecraft/server"; -export class BuilderOption extends Option { - playerId; +export class BuilderOption { identifier; displayName; description; - value; + howToUse; #onEnable; #onDisable; #DP_NAMESPACE = "builderOptions"; - constructor({ player, identifier, displayName, description, howToUse, onEnableCallback = () => {}, onDisableCallback = () => {} }) { - this.playerId = player.id; + constructor({ identifier, displayName, description, howToUse, onEnableCallback = () => {}, onDisableCallback = () => {} }) { this.identifier = identifier; this.displayName = displayName; this.description = description; @@ -22,32 +20,24 @@ export class BuilderOption extends Option { BuilderOptions.add(this); } - save() { - this.saveToDP(this.#DP_NAMESPACE, `${this.playerId}:${this.identifier}`, this); - } - - load() { - this.loadFromDP(this.#DP_NAMESPACE, `${this.playerId}:${this.identifier}`); - this.value = this.value ?? false; - } - - clear() { - this.clearDP(this.#DP_NAMESPACE, `${this.playerId}:${this.identifier}`); - } - - getValue() { - return this.value; + isEnabled(playerId) { + return world.getDynamicProperty(`${this.#DP_NAMESPACE}:${playerId}:${this.identifier}`) === true; } - setValue(value) { + setValue(playerId, value) { if (value) - this.#onEnable(); + this.#onEnable(playerId); else - this.#onDisable(); - if (this.value !== value) { - this.value = value; + this.#onDisable(playerId); + if (this.isEnabled(playerId) !== value) { + this.save(playerId, value); return value; } + this.save(playerId, value); return void 0; } + + save(playerId, value) { + world.setDynamicProperty(`${this.#DP_NAMESPACE}:${playerId}:${this.identifier}`, value); + } } \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Builder/BuilderOptions.js b/Construct [BP]/scripts/classes/Builder/BuilderOptions.js index 9a3927d..ad3c607 100644 --- a/Construct [BP]/scripts/classes/Builder/BuilderOptions.js +++ b/Construct [BP]/scripts/classes/Builder/BuilderOptions.js @@ -1,34 +1,23 @@ -import { world } from "@minecraft/server"; -import { BuilderOption } from "./BuilderOption"; - export class BuilderOptions { - playerId = void 0; - options = {}; + static options = {}; - constructor(playerId) { - this.playerId = playerId; - this.loadOptions(); + static add(builderOption) { + this.options[builderOption.identifier] = builderOption; } - loadOptions() { - const optionDPs = world.getDynamicPropertyIds().filter((id) => id.startsWith(`builderOptions:${this.playerId}:`)); - for (const optionDP of optionDPs) - new BuilderOption(JSON.parse(world.getDynamicProperty(optionDP))); + static get(optionId) { + return this.options[optionId]; } - add(builderOption) { - this.options[builderOption.identifer] = builderOption; + static getOptionIds() { + return Object.keys(this.options).sort((a, b) => a - b); } - get(id) { - return this.options[id]; + static isEnabled(optionId, playerId) { + return this.options[optionId].isEnabled(playerId); } - getValue(id) { - return this.options[id].getValue(); - } - - setValue(id, value) { - return this.options[id].setValue(value); + static setValue(optionId, playerId, value) { + return this.options[optionId].setValue(playerId, value); } } \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Builder/Builders.js b/Construct [BP]/scripts/classes/Builder/Builders.js index 2cd6458..d612b72 100644 --- a/Construct [BP]/scripts/classes/Builder/Builders.js +++ b/Construct [BP]/scripts/classes/Builder/Builders.js @@ -4,22 +4,33 @@ import { Builder } from "./Builder"; export class Builders { static builders = {}; - add(playerId) { + static add(playerId) { + if (this.builders[playerId]) + return; this.builders[playerId] = new Builder(playerId); } - remove(playerId) { + static remove(playerId) { delete this.builders[playerId]; } - get(id) { + static get(id) { return this.builders[id]; } - onJoin(playerId) { + static onJoin(playerId) { this.add(playerId); } + + static onLeave(playerId) { + this.remove(playerId); + } } world.afterEvents.playerJoin.subscribe((event) => Builders.onJoin(event.playerId)); -world.beforeEvents.playerLeave.subscribe((event) => Builders.onLeave(event.player)); \ No newline at end of file +world.beforeEvents.playerLeave.subscribe((event) => Builders.onLeave(event.player.id)); +world.afterEvents.worldLoad.subscribe((event) => { + for (const player of world.getAllPlayers()) { + Builders.onJoin(player.id); + } +}); \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Errors/InvalidInstanceError.js b/Construct [BP]/scripts/classes/Errors/InvalidInstanceError.js new file mode 100644 index 0000000..612e2dc --- /dev/null +++ b/Construct [BP]/scripts/classes/Errors/InvalidInstanceError.js @@ -0,0 +1,6 @@ +export class InvalidInstanceError extends Error { + constructor(message) { + super(message); + this.name = 'InvalidInstanceError'; + } +} \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Instance/InstanceForm.js b/Construct [BP]/scripts/classes/Instance/InstanceForm.js index ebdf90e..afc401c 100644 --- a/Construct [BP]/scripts/classes/Instance/InstanceForm.js +++ b/Construct [BP]/scripts/classes/Instance/InstanceForm.js @@ -1,7 +1,7 @@ -import { structureCollection } from './StructureCollection'; +import { structureCollection } from '../Structure/StructureCollection'; import { MenuForm } from '../MenuForm'; import { forceShow } from '../../utils'; -import { InstanceButtons } from '../enums/InstanceButtons'; +import { InstanceButtons } from '../Enums/InstanceButtons'; import { InstanceFormBuilder } from './InstanceFormBuilder'; import { FormCancelationReason } from '@minecraft/server-ui'; @@ -11,12 +11,11 @@ export class InstanceForm { isEnabled: [ InstanceButtons.NextLayer, InstanceButtons.PreviousLayer, - InstanceButtons.SetLayer, InstanceButtons.Move, InstanceButtons.Statistics, InstanceButtons.Settings, InstanceButtons.Rename, - InstanceButtons.Disable, + InstanceButtons.Disable ], isNotEnabledAndIsNotPlaced: [ InstanceButtons.Place, @@ -149,7 +148,11 @@ export class InstanceForm { if (response.canceled) return; this.instance.setVerifierEnabled(response.formValues[0]); - this.instance.setLayer(parseInt(response.formValues[1])); + if (response.formValues[1]) + this.instance.setVerifierDistance(5); + else + this.instance.setVerifierDistance(0); + this.instance.setLayer(parseInt(response.formValues[2])); }); } } \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Instance/InstanceFormBuilder.js b/Construct [BP]/scripts/classes/Instance/InstanceFormBuilder.js index 848efb7..2ac929c 100644 --- a/Construct [BP]/scripts/classes/Instance/InstanceFormBuilder.js +++ b/Construct [BP]/scripts/classes/Instance/InstanceFormBuilder.js @@ -1,7 +1,7 @@ import { ActionFormData, ModalFormData } from '@minecraft/server-ui'; import { MenuFormBuilder } from '../MenuFormBuilder'; -import { StructureVerifier } from './StructureVerifier'; -import { StructureStatistics } from './StructureStatistics'; +import { StructureVerifier } from '../Verifier/StructureVerifier'; +import { StructureStatistics } from '../Structure/StructureStatistics'; import { TicksPerSecond } from '@minecraft/server'; export class InstanceFormBuilder { @@ -29,7 +29,7 @@ export class InstanceFormBuilder { static async buildStatistics(instance) { const buildStatisticsForm = new ActionFormData() .title(MenuFormBuilder.menuTitle) - const structureVerifier = new StructureVerifier(instance, { isEnabled: true, trackPlayerDistance: 0, intervalOrLifetime: 30 * TicksPerSecond }); + const structureVerifier = new StructureVerifier(instance, { isEnabled: true, trackPlayerDistance: 0, intervalOrLifetime: 30 * TicksPerSecond, isStandalone: true }); const verification = await structureVerifier.verifyStructure(); const statistics = new StructureStatistics(instance, verification); const statsMessage = statistics.getMessage(); @@ -39,10 +39,11 @@ export class InstanceFormBuilder { static buildSettings(instance) { return new ModalFormData() - .title(MenuFormBuilder.menuTitle) + .title(MenuFormBuilder.menuTitle) .label('Use the slider to select the layer. Use 0 for all layers.') .toggle('Block Validation', instance.options.verifier.isEnabled) - .slider("Layer", 0, maxLayer, 1, currentLayer) - .submitButton('§aApply'); + .toggle('Distance-Based Block Validation', instance.verifier.getTrackPlayerDistance() !== 0) + .slider("Layer", 0, instance.getMaxLayer(), 1, instance.getLayer()) + .submitButton('§2Apply'); } } \ No newline at end of file diff --git a/Construct [BP]/scripts/classes/Instance/InstanceOptions.js b/Construct [BP]/scripts/classes/Instance/InstanceOptions.js index 34f834d..8558355 100644 --- a/Construct [BP]/scripts/classes/Instance/InstanceOptions.js +++ b/Construct [BP]/scripts/classes/Instance/InstanceOptions.js @@ -22,6 +22,7 @@ export class InstanceOptions extends Option { } constructor(instanceName, structureId) { + super(); this.instanceName = instanceName; this.structureId = structureId; this.load(); diff --git a/Construct [BP]/scripts/classes/Structure/StructureInstance.js b/Construct [BP]/scripts/classes/Instance/StructureInstance.js similarity index 96% rename from Construct [BP]/scripts/classes/Structure/StructureInstance.js rename to Construct [BP]/scripts/classes/Instance/StructureInstance.js index 8117fef..4835317 100644 --- a/Construct [BP]/scripts/classes/Structure/StructureInstance.js +++ b/Construct [BP]/scripts/classes/Instance/StructureInstance.js @@ -1,8 +1,8 @@ -import { Vector } from "../lib/Vector"; -import { StructureOutliner } from "./StructureOutliner"; -import { StructureVerifier } from "./StructureVerifier"; +import { Vector } from "../../lib/Vector"; +import { StructureOutliner } from "../Structure/StructureOutliner"; +import { StructureVerifier } from "../Verifier/StructureVerifier"; +import { Structure } from "../Structure/Structure"; import { InstanceOptions } from "./InstanceOptions"; -import { Structure } from "./Structure"; import { TicksPerSecond } from "@minecraft/server"; export class StructureInstance { @@ -19,11 +19,11 @@ export class StructureInstance { delete() { this.disable(); + this.options.clear(); delete this.options; delete this.structure; delete this.outliner; delete this.verifier; - this.options.clear(); } refreshBox() { diff --git a/Construct [BP]/scripts/classes/MaterialCounter.js b/Construct [BP]/scripts/classes/Materials/MaterialCounter.js similarity index 100% rename from Construct [BP]/scripts/classes/MaterialCounter.js rename to Construct [BP]/scripts/classes/Materials/MaterialCounter.js diff --git a/Construct [BP]/scripts/classes/MenuForm.js b/Construct [BP]/scripts/classes/MenuForm.js index 10158e6..a70eb45 100644 --- a/Construct [BP]/scripts/classes/MenuForm.js +++ b/Construct [BP]/scripts/classes/MenuForm.js @@ -1,7 +1,8 @@ import { forceShow } from '../utils'; -import { structureCollection } from './StructureCollection'; +import { structureCollection } from './Structure/StructureCollection'; import { MenuFormBuilder } from './MenuFormBuilder'; -import { InstanceForm } from './InstanceForm'; +import { InstanceForm } from './Instance/InstanceForm'; +import { BuilderForm } from './Builder/BuilderForm'; export class MenuForm { constructor(player, { jumpToInstance = false, instanceName = void 0 } = {}) { @@ -13,7 +14,7 @@ export class MenuForm { if (jumpToInstance) { if (!instanceName) instanceName = structureCollection.getStructure(this.player.dimension.id, this.player.location, { useActiveLayer: false })?.getName(); - if (structureCollection.get(instanceName)) { + if (instanceName) { new InstanceForm(this.player, instanceName); return; } @@ -29,8 +30,15 @@ export class MenuForm { return forceShow(this.player, MenuFormBuilder.buildAllInstanceName()).then((response) => { if (response.canceled) return; - const selectedInstanceName = structureCollection.getInstanceNames()[response.selection]; - return selectedInstanceName || this.createNewInstance(); + let selection = response.selection; + if (selection === 0) { + new BuilderForm(this.player); + return void 0; + } else { + selection--; + const selectedInstanceName = structureCollection.getInstanceNames()[selection]; + return selectedInstanceName || this.createNewInstance(); + } }); } catch (e) { if (e.message === 'Menu timed out.') { diff --git a/Construct [BP]/scripts/classes/MenuFormBuilder.js b/Construct [BP]/scripts/classes/MenuFormBuilder.js index dcde0bd..cd24324 100644 --- a/Construct [BP]/scripts/classes/MenuFormBuilder.js +++ b/Construct [BP]/scripts/classes/MenuFormBuilder.js @@ -1,13 +1,14 @@ import { ActionFormData, ModalFormData } from '@minecraft/server-ui'; -import { structureCollection } from './StructureCollection'; +import { structureCollection } from './Structure/StructureCollection'; export class MenuFormBuilder { - static menuTitle = '§l§2Construct §8Menu'; + static menuTitle = '§l§2Construct'; static buildAllInstanceName() { const allInstanceNameForm = new ActionFormData() .title(this.menuTitle) .body('Select an instance:'); + allInstanceNameForm.button('Builder Settings'); structureCollection.getInstanceNames().forEach(instanceName => { allInstanceNameForm.button(`§2${instanceName}`); }); diff --git a/Construct [BP]/scripts/classes/Raycaster.js b/Construct [BP]/scripts/classes/Raycaster.js index 0e36355..3cc5ead 100644 --- a/Construct [BP]/scripts/classes/Raycaster.js +++ b/Construct [BP]/scripts/classes/Raycaster.js @@ -1,4 +1,4 @@ -import { structureCollection } from "./StructureCollection"; +import { structureCollection } from "./Structure/StructureCollection"; import { world } from "@minecraft/server"; export class Raycaster { diff --git a/Construct [BP]/scripts/classes/Structure/Structure.js b/Construct [BP]/scripts/classes/Structure/Structure.js index 58a36ef..822f53e 100644 --- a/Construct [BP]/scripts/classes/Structure/Structure.js +++ b/Construct [BP]/scripts/classes/Structure/Structure.js @@ -1,5 +1,5 @@ import { world } from "@minecraft/server"; -import { Vector } from "../lib/Vector"; +import { Vector } from "../../lib/Vector"; export class Structure { structureId; diff --git a/Construct [BP]/scripts/classes/Structure/StructureCollection.js b/Construct [BP]/scripts/classes/Structure/StructureCollection.js index 26f1907..91d1c4e 100644 --- a/Construct [BP]/scripts/classes/Structure/StructureCollection.js +++ b/Construct [BP]/scripts/classes/Structure/StructureCollection.js @@ -1,5 +1,6 @@ -import { InstanceOptions } from './InstanceOptions'; -import { StructureInstance } from './StructureInstance'; +import { InvalidInstanceError } from '../Errors/InvalidInstanceError'; +import { InstanceOptions } from '../Instance/InstanceOptions'; +import { StructureInstance } from '../Instance/StructureInstance'; import { world } from '@minecraft/server'; class StructureCollection { @@ -26,7 +27,7 @@ class StructureCollection { add(instanceName, structureId) { if (this.structures[instanceName]) - throw new Error(`Instance ${instanceName} already exists.`); + throw new InvalidInstanceError(`Instance ${instanceName} already exists.`); const structure = new StructureInstance(instanceName, structureId); this.structures[instanceName] = structure; return structure; @@ -34,9 +35,8 @@ class StructureCollection { get(instanceName) { const structure = this.structures[instanceName]; - if (!structure) { - throw new Error(`Instance ${instanceName} not found.`); - } + if (!structure) + throw new InvalidInstanceError(`Instance ${instanceName} not found.`); return structure; } diff --git a/Construct [BP]/scripts/classes/Structure/StructureOutliner.js b/Construct [BP]/scripts/classes/Structure/StructureOutliner.js index 023fde5..6d7e005 100644 --- a/Construct [BP]/scripts/classes/Structure/StructureOutliner.js +++ b/Construct [BP]/scripts/classes/Structure/StructureOutliner.js @@ -1,4 +1,4 @@ -import { Outliner } from './Outliner'; +import { Outliner } from '../Outliner'; export class StructureOutliner { constructor(instance) { @@ -42,7 +42,7 @@ export class StructureOutliner { } layeredDraw() { - const { min, max } = this.instance.getLayeredBounds(); + const { min, max } = this.instance.getLayerBounds(this.instance.getLayer()); this.outliner.setVertices(this.dimension, this.instance.toGlobalCoords(min), this.instance.toGlobalCoords(max)); this.outliner.addStandaloneParticles(this.getCornerVertices()); } diff --git a/Construct [BP]/scripts/classes/Structure/StructureStatistics.js b/Construct [BP]/scripts/classes/Structure/StructureStatistics.js index 0b09187..20b96ff 100644 --- a/Construct [BP]/scripts/classes/Structure/StructureStatistics.js +++ b/Construct [BP]/scripts/classes/Structure/StructureStatistics.js @@ -1,4 +1,4 @@ -import { BlockVerificationLevel } from './enums/BlockVerificationLevel.js'; +import { BlockVerificationLevel } from '../Enums/BlockVerificationLevel.js'; export class StructureStatistics { constructor(instance, verification) { diff --git a/Construct [BP]/scripts/classes/BlockVerificationLevelRender.js b/Construct [BP]/scripts/classes/Verifier/BlockVerificationLevelRender.js similarity index 95% rename from Construct [BP]/scripts/classes/BlockVerificationLevelRender.js rename to Construct [BP]/scripts/classes/Verifier/BlockVerificationLevelRender.js index 70a6716..bef6cc4 100644 --- a/Construct [BP]/scripts/classes/BlockVerificationLevelRender.js +++ b/Construct [BP]/scripts/classes/Verifier/BlockVerificationLevelRender.js @@ -1,6 +1,6 @@ import { MolangVariableMap } from "@minecraft/server"; -import { BlockVerificationLevel } from "./enums/BlockVerificationLevel"; -import { Vector } from "../lib/Vector"; +import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel"; +import { Vector } from "../../lib/Vector"; export class BlockVerificationLevelRender { opacity = 0.2; diff --git a/Construct [BP]/scripts/classes/BlockVerifier.js b/Construct [BP]/scripts/classes/Verifier/BlockVerifier.js similarity index 95% rename from Construct [BP]/scripts/classes/BlockVerifier.js rename to Construct [BP]/scripts/classes/Verifier/BlockVerifier.js index 81b9a4e..2c5d593 100644 --- a/Construct [BP]/scripts/classes/BlockVerifier.js +++ b/Construct [BP]/scripts/classes/Verifier/BlockVerifier.js @@ -1,4 +1,4 @@ -import { BlockVerificationLevel } from "./enums/BlockVerificationLevel"; +import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel"; export class BlockVerifier { constructor(block, instance) { diff --git a/Construct [BP]/scripts/classes/Structure/StructureVerifier.js b/Construct [BP]/scripts/classes/Verifier/StructureVerifier.js similarity index 84% rename from Construct [BP]/scripts/classes/Structure/StructureVerifier.js rename to Construct [BP]/scripts/classes/Verifier/StructureVerifier.js index 441b629..90ed15a 100644 --- a/Construct [BP]/scripts/classes/Structure/StructureVerifier.js +++ b/Construct [BP]/scripts/classes/Verifier/StructureVerifier.js @@ -1,8 +1,8 @@ import { BlockVerifier } from "./BlockVerifier"; -import { BlockVerificationLevel } from "./enums/BlockVerificationLevel"; -import { BlockVerificationLevelRender } from "./BlockVerificationLevelRender"; +import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel"; +import { BlockVerificationLevelRender } from "../Verifier/BlockVerificationLevelRender"; import { system, TicksPerSecond } from "@minecraft/server"; -import { Vector } from "../lib/Vector"; +import { Vector } from "../../lib/Vector"; const MIN_TRACK_PLAYER_DISTANCE = 0; const MAX_TRACK_PLAYER_DISTANCE = 7; @@ -21,11 +21,17 @@ export class StructureVerifier { #verifyJob; #populateJob = {}; - constructor(instance, { isEnabled = false, trackPlayerDistance = 0, intervalOrLifetime = 10 } = {}) { + constructor(instance, { isEnabled = false, trackPlayerDistance = 0, intervalOrLifetime = 10, isStandalone: isIndependent = false } = {}) { this.instance = instance; this.intervalOrLifetime = Math.max(intervalOrLifetime, MIN_LIFETIME); - this.instance.options.setVerifierEnabled(isEnabled); - this.instance.options.setVerifierDistance(trackPlayerDistance); + if (isIndependent) { + this.isIndependent = isIndependent; + this.enabled = isEnabled; + this.trackPlayerDistance = trackPlayerDistance; + } else { + this.instance.options.setVerifierEnabled(isEnabled); + this.instance.options.setVerifierDistance(trackPlayerDistance); + } this.locationsToVerify = new Set(); } @@ -50,11 +56,18 @@ export class StructureVerifier { } isEnabled() { + if (this.isIndependent) + return this.enabled; return this.instance.options.verifier.isEnabled; } getTrackPlayerDistance() { - return Math.min(MAX_TRACK_PLAYER_DISTANCE, Math.max(MIN_TRACK_PLAYER_DISTANCE, this.instance.options.verifier.trackPlayerDistance)); + let distance; + if (this.isIndependent) + distance = this.trackPlayerDistance; + else + distance = this.instance.options.verifier.trackPlayerDistance + return Math.min(MAX_TRACK_PLAYER_DISTANCE, Math.max(MIN_TRACK_PLAYER_DISTANCE, distance)); } init() { diff --git a/Construct [BP]/scripts/commands/construct.js b/Construct [BP]/scripts/commands/construct.js index 34ad208..219b373 100644 --- a/Construct [BP]/scripts/commands/construct.js +++ b/Construct [BP]/scripts/commands/construct.js @@ -2,6 +2,7 @@ import { Command } from '../lib/canopy/CanopyExtension'; import { extension } from '../config'; import { world, system } from '@minecraft/server'; import { MenuForm } from '../classes/MenuForm'; +import { structureCollection } from '../classes/Structure/StructureCollection' const ACTION_ITEM = 'minecraft:paper'; @@ -21,7 +22,11 @@ world.beforeEvents.itemUse.subscribe((event) => { function openMenu(sender, event = void 0) { const options = { jumpToInstance: true } - if (event) - options.instanceName = event.itemStack?.typeId; + if (event) { + const instanceNames = structureCollection.getInstanceNames(); + const instanceName = event.itemStack?.nameTag; + if (instanceNames.includes(instanceName)) + options.instanceName = instanceName; + } new MenuForm(sender, options); } \ No newline at end of file diff --git a/Construct [BP]/scripts/lib/canopy/Rule.js b/Construct [BP]/scripts/lib/canopy/Rule.js new file mode 100644 index 0000000..3ba7b4f --- /dev/null +++ b/Construct [BP]/scripts/lib/canopy/Rule.js @@ -0,0 +1,70 @@ +/** + * @license + * MIT License + * + * Copyright (c) 2024 ForestOfLight + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import { world } from '@minecraft/server'; + +class Rule { + #identifier; + #description; + #contingentRules; + #independentRules; + + constructor({ identifier, description, contingentRules = [], independentRules = [] }) { + this.#identifier = identifier; + this.#description = description; + this.#contingentRules = contingentRules; + this.#independentRules = independentRules; + } + + getID() { + return this.#identifier; + } + + getDescription() { + return this.#description; + } + + getContigentRules() { + return this.#contingentRules; + } + + getIndependentRules() { + return this.#independentRules; + } + + getValue() { + const value = world.getDynamicProperty(this.#identifier); + if (String(value) === 'true') + return true; + if (['false', 'undefined'].includes(String(value))) + return false; + throw new Error(`Rule ${this.#identifier} has an invalid value: ${value} (${typeof value})`); + } + + setValue(value) { + world.setDynamicProperty(this.#identifier, value); + } +} + +export default Rule; \ No newline at end of file diff --git a/Construct [BP]/scripts/main.js b/Construct [BP]/scripts/main.js index 4bc4dda..508d3f0 100644 --- a/Construct [BP]/scripts/main.js +++ b/Construct [BP]/scripts/main.js @@ -1,7 +1,10 @@ -// Rules -import './rules/easyPlace'; -import './rules/fastEasyPlace'; -import './rules/materialGrabber'; +// Setup +import './classes/Builder/Builders'; + +// Options +import './options/easyPlace'; +import './options/fastEasyPlace'; +import './options/materialGrabber'; // Commands import './commands/construct'; diff --git a/Construct [BP]/scripts/options/easyPlace.js b/Construct [BP]/scripts/options/easyPlace.js index 0192982..3cd44b7 100644 --- a/Construct [BP]/scripts/options/easyPlace.js +++ b/Construct [BP]/scripts/options/easyPlace.js @@ -1,24 +1,24 @@ import { BuilderOption } from '../classes/Builder/BuilderOption'; import { BlockPermutation, EntityComponentTypes, GameMode, ItemStack, system, world } from '@minecraft/server'; -import { structureCollection } from '../classes/StructureCollection'; +import { structureCollection } from '../classes/Structure/StructureCollection'; import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, specialItemPlacementConversions, blockIdToItemStackMap } from '../data'; import { fetchMatchingItemSlot } from '../utils'; -const ACTION_SLOT = 35; +const ACTION_SLOT = 27; -new BuilderOption({ +const builderOption = new BuilderOption({ identifier: 'easyPlace', displayName: 'Easy Place', - description: 'Always place the correct block.', - howToUse: "Place blocks in a structure with a paper named 'Easy Place' in the bottom right slot of your inventory to always place the correct block.", - onEnableCallback: () => { world.beforeEvents.playerPlaceBlock.subscribe(onPlayerPlaceBlock); }, - onDisableCallback: () => { world.beforeEvents.playerPlaceBlock.unsubscribe(onPlayerPlaceBlock); } -}) + description: 'Always place the correct structure block.', + howToUse: "Place blocks in a structure with a paper named 'Easy Place' in the inventory slot above your first hotbar slot to always place the correct block." +}); + +world.beforeEvents.playerPlaceBlock.subscribe(onPlayerPlaceBlock); function onPlayerPlaceBlock(event) { - const { player, block, permutationBeingPlaced } = event; - if (!player || !block || !hasActionItemInCorrectSlot(player)) return; + const { player, block } = event; + if (!player || !block || !builderOption.isEnabled(player.id) || !hasActionItemInCorrectSlot(player)) return; const structureBlock = structureCollection.fetchStructureBlock(block.dimension.id, block.location); if (!structureBlock) return; diff --git a/Construct [BP]/scripts/options/fastEasyPlace.js b/Construct [BP]/scripts/options/fastEasyPlace.js index 8bef942..e7ba9fc 100644 --- a/Construct [BP]/scripts/options/fastEasyPlace.js +++ b/Construct [BP]/scripts/options/fastEasyPlace.js @@ -4,19 +4,21 @@ import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlo blockIdToItemStackMap } from '../data'; import { Raycaster } from '../classes/Raycaster'; +const PROCESS_INTERVAL = 2; + let runner = void 0; -new BuilderOption({ +const builderOption = new BuilderOption({ identifier: 'fastEasyPlace', displayName: 'Fast Easy Place', description: 'Place structure blocks just by looking at them.', howToUse: "Look at structure blocks with a paper named 'Easy Place' in your hand to place them.", - onEnableCallback: () => { runner = system.runInterval(onTick, 2); }, - onDisableCallback: () => { system.clearRun(runner); } -}) +}); + +system.runInterval(onTick, PROCESS_INTERVAL); function onTick() { for (const player of world.getAllPlayers()) { - if (!player) + if (!player || !builderOption.isEnabled(player.id)) continue; processEasyPlace(player); } diff --git a/Construct [BP]/scripts/options/materialGrabber.js b/Construct [BP]/scripts/options/materialGrabber.js index 4d5352f..e11fec3 100644 --- a/Construct [BP]/scripts/options/materialGrabber.js +++ b/Construct [BP]/scripts/options/materialGrabber.js @@ -1,20 +1,15 @@ import { BuilderOption } from '../classes/Builder/BuilderOption'; import { world } from '@minecraft/server'; -new BuilderOption({ +const builderOption = new BuilderOption({ identifier: 'materialGrabber', displayName: 'Material Grabber', description: 'Pulls structure items from inventories.', howToUse: "Interact with inventories using a paper named 'Material Grabber' to pull structure items from them.", - onEnableCallback: () => { - world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteract); - world.beforeEvents.playerInteractWithEntity.subscribe(onPlayerInteract); - }, - onDisableCallback: () => { - world.beforeEvents.playerInteractWithBlock.unsubscribe(onPlayerInteract); - world.beforeEvents.playerInteractWithEntity.unsubscribe(onPlayerInteract); - } -}) +}); + +world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteract); +world.beforeEvents.playerInteractWithEntity.subscribe(onPlayerInteract); function onPlayerInteract(event) { // get inventory From 009f7decee0b10aa82e7cc2f1147b83dcafd2e04 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sun, 20 Apr 2025 02:13:56 -0700 Subject: [PATCH 3/4] Packs no longer depend on Canopy --- Construct [BP]/manifest.json | 8 -------- Construct [RP]/manifest.json | 4 ---- 2 files changed, 12 deletions(-) diff --git a/Construct [BP]/manifest.json b/Construct [BP]/manifest.json index 6001919..812ce5c 100644 --- a/Construct [BP]/manifest.json +++ b/Construct [BP]/manifest.json @@ -35,14 +35,6 @@ { "uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4", // Construct RP "version": [1, 0, 0] - }, - { - "uuid": "bcf34368-ed0c-4cf7-938e-582cccf9950d", // Canopy RP - "version": [1, 0, 3] - }, - { - "uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0", // Canopy BP - "version": [1, 3, 9] } ], "metadata": { diff --git a/Construct [RP]/manifest.json b/Construct [RP]/manifest.json index abb11fc..3d0eed8 100644 --- a/Construct [RP]/manifest.json +++ b/Construct [RP]/manifest.json @@ -18,10 +18,6 @@ { "uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", // Construct BP "version": [1, 0, 0] - }, - { - "uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0", // Canopy BP - "version": [1, 3, 9] } ], "metadata": { From 0a9b09048e1bfa2e2b88f430df595aa716239cf3 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sun, 20 Apr 2025 02:41:52 -0700 Subject: [PATCH 4/4] Upgrade pack descriptions and README --- Construct [BP]/manifest.json | 2 +- Construct [BP]/scripts/config.js | 2 +- Construct [RP]/manifest.json | 2 +- README.md | 39 ++++++++++++++++++++++---------- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Construct [BP]/manifest.json b/Construct [BP]/manifest.json index 812ce5c..34eb3bc 100644 --- a/Construct [BP]/manifest.json +++ b/Construct [BP]/manifest.json @@ -2,7 +2,7 @@ "format_version": 2, "header": { "name": "Construct [BP] v1.0.0", - "description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.", + "description": "Survival building addon by §aForestOfLight§r.", "uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", "min_engine_version": [1, 21, 70], "version": [1, 0, 0] diff --git a/Construct [BP]/scripts/config.js b/Construct [BP]/scripts/config.js index e1de1d4..0f36f06 100644 --- a/Construct [BP]/scripts/config.js +++ b/Construct [BP]/scripts/config.js @@ -3,6 +3,6 @@ import { CanopyExtension } from './lib/canopy/CanopyExtension'; export const extension = new CanopyExtension({ author: 'ForestOfLight', name: 'Construct', - description: 'Make building structures in survival easier!', + description: 'Survival building addon by §aForestOfLight§r.', version: '1.0.0' }); \ No newline at end of file diff --git a/Construct [RP]/manifest.json b/Construct [RP]/manifest.json index 3d0eed8..ebfc698 100644 --- a/Construct [RP]/manifest.json +++ b/Construct [RP]/manifest.json @@ -2,7 +2,7 @@ "format_version": 2, "header": { "name": "Construct [RP] v1.0.0", - "description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.", + "description": "Survival building addon by §aForestOfLight§r.", "uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4", "version": [1, 0, 0], "min_engine_version": [1,17,0] diff --git a/README.md b/README.md index 07fbcff..710b5ef 100644 --- a/README.md +++ b/README.md @@ -12,33 +12,48 @@ --- -Intro paragraph. +The closest thing Bedrock has to **Litematica**. This addon allows you to create and manage ghost structures in your world, making it easier to build them in survival. Construct offers these convenient features: + +- **Block Validation**: Highlights incorrectly placed blocks. +- **Easy Place**: Always places blocks correctly. +- **Structure Management**: Create, edit, and delete structures. + +[IMAGE HERE] > [!IMPORTANT] -> This addon is a **Canopy Extension**, which means **Canopy** must be installed in your world for it to work. +> This is a standalone addon, but it is also a **Canopy Extension**, which means **Canopy** can be installed in your world for a few extra features. > [Download **Canopy** here!](https://github.com/ForestOfLight/Canopy) ## Usage -All commands are prefixed with `./`. Do `./help` for more information. +### Construct Menu -**Usage: `./struct add `** -Adds a new structure. +The main Construct menu can be accessed using a `paper` item. Using it will open a GUI where you can manage your structures instances. You can create new instances of structures, edit existing ones, and delete them when you no longer need them. There are plenty options available to customize your building style, including the ability build in layers. -**Usage: `./struct remove `** -Removes a structure. +*Pro tip: You can name the paper after a structure instance to automagically select it when you open the menu!* + +### Adding New Structures + +Construct uses Minecraft's vanilla structure system so that you can easily create and build new structures. + +- The simplest way to add a new structure to the structure list is to save it using a structure block. +- Importing structures from other worlds is as simple as dropping the `.mcstructure` file in Construct's `Construct [BP]/structures` folder. Then, to select your structure in the Construct menu, choose "Other" when presented with the structures and enter the filename (without including ".mcstructure"). You only need to do this once with new structures, since Construct will save them for future use! + +### Commands + +**Usage: `./construct`** +Shows the construct form. Only available when **Canopy** is installed. ## Roadmap -- [x] Form to manage structure +- [x] Form to manage structures - [x] Structure naming & movement -- [x] easyPlace rule -- [ ] Automatic Armor stand posing +- [x] Easyplace +- [x] Correct block placement checking - [ ] Automatic material gathering from inventories -- [ ] Correct block placement checking - [ ] Structure Mirroring & Rotation - [ ] Structure Merging into SuperStructures ## Issues & Suggestions -If you have any issues or suggestions, please open an issue on this repo. Additionally, if you're interested in contributing to the project, feel free to open a pull request! +If you have any issues or suggestions, please don't hesitate to open an issue on this repo. Additionally, if you're interested in contributing to the project, feel free to open a pull request!