diff --git a/Construct [BP]/manifest.json b/Construct [BP]/manifest.json index c0c3d71..34eb3bc 100644 --- a/Construct [BP]/manifest.json +++ b/Construct [BP]/manifest.json @@ -1,8 +1,8 @@ { "format_version": 2, "header": { - "name": "StrucTool [BP] v1.0.0", - "description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.", + "name": "Construct [BP] v1.0.0", + "description": "Survival building addon by §aForestOfLight§r.", "uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", "min_engine_version": [1, 21, 70], "version": [1, 0, 0] @@ -33,16 +33,8 @@ "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] - }, - { - "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 [BP]/scripts/classes/Builder/Builder.js b/Construct [BP]/scripts/classes/Builder/Builder.js new file mode 100644 index 0000000..afadf1e --- /dev/null +++ b/Construct [BP]/scripts/classes/Builder/Builder.js @@ -0,0 +1,15 @@ +import { BuilderOptions } from "./BuilderOptions"; + +export class Builder { + constructor(playerId) { + this.playerId = playerId; + } + + isOptionEnabled(optionId) { + return BuilderOptions.isEnabled(optionId, this.playerId); + } + + 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 new file mode 100644 index 0000000..174a718 --- /dev/null +++ b/Construct [BP]/scripts/classes/Builder/BuilderForm.js @@ -0,0 +1,37 @@ +import { BuilderFormBuilder } from "./BuilderFormBuilder"; +import { BuilderOptions } from "./BuilderOptions"; +import { forceShow } from '../../utils'; + +export class BuilderForm { + constructor(player) { + this.player = player; + this.show(); + } + + show() { + forceShow(this.player, BuilderFormBuilder.buildBuilderOptions(this.player)).then((response) => { + if (response.canceled) return; + this.applySettings(response.formValues); + }); + } + + applySettings(formValues) { + const optionIds = BuilderOptions.getOptionIds(); + for (let i = 0; i < optionIds.length; i++) { + 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 new file mode 100644 index 0000000..736cc43 --- /dev/null +++ b/Construct [BP]/scripts/classes/Builder/BuilderFormBuilder.js @@ -0,0 +1,16 @@ +import { ModalFormData } from "@minecraft/server-ui"; +import { MenuFormBuilder } from "../MenuFormBuilder"; +import { BuilderOptions } from "./BuilderOptions"; + +export class BuilderFormBuilder { + static buildBuilderOptions(player) { + const form = new ModalFormData() + .title(MenuFormBuilder.menuTitle); + 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 new file mode 100644 index 0000000..cdd981c --- /dev/null +++ b/Construct [BP]/scripts/classes/Builder/BuilderOption.js @@ -0,0 +1,43 @@ +import { BuilderOptions } from "./BuilderOptions"; +import { world } from "@minecraft/server"; + +export class BuilderOption { + identifier; + displayName; + description; + howToUse; + #onEnable; + #onDisable; + #DP_NAMESPACE = "builderOptions"; + + constructor({ identifier, displayName, description, howToUse, onEnableCallback = () => {}, onDisableCallback = () => {} }) { + this.identifier = identifier; + this.displayName = displayName; + this.description = description; + this.howToUse = howToUse; + this.#onEnable = onEnableCallback; + this.#onDisable = onDisableCallback; + BuilderOptions.add(this); + } + + isEnabled(playerId) { + return world.getDynamicProperty(`${this.#DP_NAMESPACE}:${playerId}:${this.identifier}`) === true; + } + + setValue(playerId, value) { + if (value) + this.#onEnable(playerId); + else + 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 new file mode 100644 index 0000000..ad3c607 --- /dev/null +++ b/Construct [BP]/scripts/classes/Builder/BuilderOptions.js @@ -0,0 +1,23 @@ +export class BuilderOptions { + static options = {}; + + static add(builderOption) { + this.options[builderOption.identifier] = builderOption; + } + + static get(optionId) { + return this.options[optionId]; + } + + static getOptionIds() { + return Object.keys(this.options).sort((a, b) => a - b); + } + + static isEnabled(optionId, playerId) { + return this.options[optionId].isEnabled(playerId); + } + + 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 new file mode 100644 index 0000000..d612b72 --- /dev/null +++ b/Construct [BP]/scripts/classes/Builder/Builders.js @@ -0,0 +1,36 @@ +import { world } from "@minecraft/server"; +import { Builder } from "./Builder"; + +export class Builders { + static builders = {}; + + static add(playerId) { + if (this.builders[playerId]) + return; + this.builders[playerId] = new Builder(playerId); + } + + static remove(playerId) { + delete this.builders[playerId]; + } + + static get(id) { + return this.builders[id]; + } + + 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.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/InstanceEditForm.js b/Construct [BP]/scripts/classes/Instance/InstanceForm.js similarity index 60% rename from Construct [BP]/scripts/classes/InstanceEditForm.js rename to Construct [BP]/scripts/classes/Instance/InstanceForm.js index 208ef1c..afc401c 100644 --- a/Construct [BP]/scripts/classes/InstanceEditForm.js +++ b/Construct [BP]/scripts/classes/Instance/InstanceForm.js @@ -1,34 +1,33 @@ -import { structureCollection } from './StructureCollection'; -import { MenuForm } from './MenuForm'; -import { forceShow } from '../utils'; -import { InstanceEditButtons } from './enums/InstanceEditButtons'; -import { InstanceEditFormBuilder } from './InstanceEditFormBuilder'; +import { structureCollection } from '../Structure/StructureCollection'; +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.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 +40,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 +58,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 +109,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 +128,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 +136,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,11 +144,15 @@ 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]); - 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/InstanceEditFormBuilder.js b/Construct [BP]/scripts/classes/Instance/InstanceFormBuilder.js similarity index 73% rename from Construct [BP]/scripts/classes/InstanceEditFormBuilder.js rename to Construct [BP]/scripts/classes/Instance/InstanceFormBuilder.js index fae3ac3..2ac929c 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 { StructureVerifier } from './StructureVerifier'; -import { StructureStatistics } from './StructureStatistics'; +import { MenuFormBuilder } from '../MenuFormBuilder'; +import { StructureVerifier } from '../Verifier/StructureVerifier'; +import { StructureStatistics } from '../Structure/StructureStatistics'; import { TicksPerSecond } from '@minecraft/server'; -export class InstanceEditFormBuilder { +export class InstanceFormBuilder { static buildInstance(instance, options) { const location = instance.getLocation(); const form = new ActionFormData() @@ -29,7 +29,7 @@ export class InstanceEditFormBuilder { 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 InstanceEditFormBuilder { 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('Toggle block validation.', instance.options.verifier.isEnabled) - .slider("Layer", 0, maxLayer, 1, currentLayer) - .submitButton('§aApply'); + .toggle('Block Validation', instance.options.verifier.isEnabled) + .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/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..8558355 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; @@ -20,32 +22,23 @@ export class InstanceOptions { } constructor(instanceName, structureId) { + super(); this.instanceName = instanceName; this.structureId = structureId; this.load(); } 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/StructureInstance.js b/Construct [BP]/scripts/classes/Instance/StructureInstance.js similarity index 91% rename from Construct [BP]/scripts/classes/StructureInstance.js rename to Construct [BP]/scripts/classes/Instance/StructureInstance.js index 8104ff6..4835317 100644 --- a/Construct [BP]/scripts/classes/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() { @@ -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/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 03b8c50..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 { InstanceEditForm } from './InstanceEditForm'; +import { InstanceForm } from './Instance/InstanceForm'; +import { BuilderForm } from './Builder/BuilderForm'; export class MenuForm { constructor(player, { jumpToInstance = false, instanceName = void 0 } = {}) { @@ -13,15 +14,15 @@ export class MenuForm { if (jumpToInstance) { if (!instanceName) instanceName = structureCollection.getStructure(this.player.dimension.id, this.player.location, { useActiveLayer: false })?.getName(); - if (structureCollection.get(instanceName)) { - new InstanceEditForm(this.player, instanceName); + if (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() { @@ -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 390377a..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§2StrucTool §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/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/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.js b/Construct [BP]/scripts/classes/Structure/Structure.js similarity index 94% rename from Construct [BP]/scripts/classes/Structure.js rename to Construct [BP]/scripts/classes/Structure/Structure.js index 983d6bb..822f53e 100644 --- a/Construct [BP]/scripts/classes/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; @@ -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 86% rename from Construct [BP]/scripts/classes/StructureCollection.js rename to Construct [BP]/scripts/classes/Structure/StructureCollection.js index 299067c..91d1c4e 100644 --- a/Construct [BP]/scripts/classes/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 { @@ -17,7 +18,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; } @@ -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/StructureOutliner.js b/Construct [BP]/scripts/classes/Structure/StructureOutliner.js similarity index 92% rename from Construct [BP]/scripts/classes/StructureOutliner.js rename to Construct [BP]/scripts/classes/Structure/StructureOutliner.js index 023fde5..6d7e005 100644 --- a/Construct [BP]/scripts/classes/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/StructureStatistics.js b/Construct [BP]/scripts/classes/Structure/StructureStatistics.js similarity index 96% rename from Construct [BP]/scripts/classes/StructureStatistics.js rename to Construct [BP]/scripts/classes/Structure/StructureStatistics.js index 0b09187..20b96ff 100644 --- a/Construct [BP]/scripts/classes/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 81% rename from Construct [BP]/scripts/classes/BlockVerificationLevelRender.js rename to Construct [BP]/scripts/classes/Verifier/BlockVerificationLevelRender.js index 34ebde3..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; @@ -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/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/StructureVerifier.js b/Construct [BP]/scripts/classes/Verifier/StructureVerifier.js similarity index 84% rename from Construct [BP]/scripts/classes/StructureVerifier.js rename to Construct [BP]/scripts/classes/Verifier/StructureVerifier.js index 441b629..90ed15a 100644 --- a/Construct [BP]/scripts/classes/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/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..219b373 100644 --- a/Construct [BP]/scripts/commands/construct.js +++ b/Construct [BP]/scripts/commands/construct.js @@ -2,13 +2,14 @@ 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'; 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); @@ -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/config.js b/Construct [BP]/scripts/config.js index 39efd76..0f36f06 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: 'Survival building addon by §aForestOfLight§r.', 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 index cafae2f..3ba7b4f 100644 --- a/Construct [BP]/scripts/lib/canopy/Rule.js +++ b/Construct [BP]/scripts/lib/canopy/Rule.js @@ -30,13 +30,11 @@ class Rule { #contingentRules; #independentRules; - constructor({ identifier, description, contingentRules = [], independentRules = [], onEnableCallback = () => {}, onDisableCallback = () => {} }) { + constructor({ identifier, description, contingentRules = [], independentRules = [] }) { this.#identifier = identifier; this.#description = description; this.#contingentRules = contingentRules; this.#independentRules = independentRules; - this.onEnable = onEnableCallback; - this.onDisable = onDisableCallback; } getID() { @@ -65,10 +63,6 @@ class Rule { } setValue(value) { - if (value === true) - this.onEnable(); - else - this.onDisable(); world.setDynamicProperty(this.#identifier, value); } } 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/rules/easyPlace.js b/Construct [BP]/scripts/options/easyPlace.js similarity index 85% rename from Construct [BP]/scripts/rules/easyPlace.js rename to Construct [BP]/scripts/options/easyPlace.js index 519f1f6..3cd44b7 100644 --- a/Construct [BP]/scripts/rules/easyPlace.js +++ b/Construct [BP]/scripts/options/easyPlace.js @@ -1,24 +1,24 @@ -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 { 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; -const easyPlace = new Rule({ +const builderOption = new BuilderOption({ identifier: 'easyPlace', - 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); } -}) -extension.addRule(easyPlace); + displayName: 'Easy Place', + 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; @@ -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 91% rename from Construct [BP]/scripts/rules/fastEasyPlace.js rename to Construct [BP]/scripts/options/fastEasyPlace.js index fde0289..e7ba9fc 100644 --- a/Construct [BP]/scripts/rules/fastEasyPlace.js +++ b/Construct [BP]/scripts/options/fastEasyPlace.js @@ -1,22 +1,24 @@ -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'; +const PROCESS_INTERVAL = 2; + let runner = void 0; -const easyPlace = new Rule({ +const builderOption = new BuilderOption({ identifier: 'fastEasyPlace', - 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); } -}) -extension.addRule(easyPlace); + 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.", +}); + +system.runInterval(onTick, PROCESS_INTERVAL); function onTick() { for (const player of world.getAllPlayers()) { - if (!player) + if (!player || !builderOption.isEnabled(player.id)) continue; processEasyPlace(player); } @@ -35,7 +37,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/options/materialGrabber.js b/Construct [BP]/scripts/options/materialGrabber.js new file mode 100644 index 0000000..e11fec3 --- /dev/null +++ b/Construct [BP]/scripts/options/materialGrabber.js @@ -0,0 +1,19 @@ +import { BuilderOption } from '../classes/Builder/BuilderOption'; +import { world } from '@minecraft/server'; + +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.", +}); + +world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteract); +world.beforeEvents.playerInteractWithEntity.subscribe(onPlayerInteract); + +function onPlayerInteract(event) { + // get inventory + // analyze active structure items if not analyzed -- there needs to be a way to refresh this that is efficient + // find items in inventory that match items in structure + // transfer to player +} \ No newline at end of file diff --git a/Construct [BP]/scripts/rules/materialGrabber.js b/Construct [BP]/scripts/rules/materialGrabber.js deleted file mode 100644 index 411a9f3..0000000 --- a/Construct [BP]/scripts/rules/materialGrabber.js +++ /dev/null @@ -1,24 +0,0 @@ -import { Rule } from '../lib/canopy/CanopyExtension'; -import { extension } from '../config'; -import { world } from '@minecraft/server'; - -const easyPlace = new Rule({ - identifier: 'materialGrabber', - description: { text: "Automatically grabs structure materials out of inventories. Use a paper named 'materialGrabber' to get started." }, - onEnableCallback: () => { - world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteract); - world.beforeEvents.playerInteractWithEntity.subscribe(onPlayerInteract); - }, - onDisableCallback: () => { - world.beforeEvents.playerInteractWithBlock.unsubscribe(onPlayerInteract); - world.beforeEvents.playerInteractWithEntity.unsubscribe(onPlayerInteract); - } -}) -extension.addRule(easyPlace); - -function onPlayerInteract(event) { - // get inventory - // analyze active structure items if not analyzed -- there needs to be a way to refresh this that is efficient - // find items in inventory that match items in structure - // transfer to player -} \ No newline at end of file diff --git a/Construct [RP]/manifest.json b/Construct [RP]/manifest.json index 6ab353c..ebfc698 100644 --- a/Construct [RP]/manifest.json +++ b/Construct [RP]/manifest.json @@ -1,8 +1,8 @@ { "format_version": 2, "header": { - "name": "StrucTool [RP] v1.0.0", - "description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.", + "name": "Construct [RP] v1.0.0", + "description": "Survival building addon by §aForestOfLight§r.", "uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4", "version": [1, 0, 0], "min_engine_version": [1,17,0] @@ -16,12 +16,8 @@ ], "dependencies": [ { - "uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", // StrucTool BP + "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": { 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" 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!