complete rename and init builder options

This commit is contained in:
ForestOfLight
2025-04-19 17:55:28 -07:00
Unverified
parent 818f2d0183
commit d6c9acdaab
33 changed files with 297 additions and 183 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"format_version": 2, "format_version": 2,
"header": { "header": {
"name": "StrucTool [BP] v1.0.0", "name": "Construct [BP] v1.0.0",
"description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.", "description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.",
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", "uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58",
"min_engine_version": [1, 21, 70], "min_engine_version": [1, 21, 70],
@@ -33,7 +33,7 @@
"version": "2.0.0-beta" "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] "version": [1, 0, 0]
}, },
{ {
@@ -36,12 +36,12 @@ export class BlockVerificationLevelRender {
const frontFace = new Vector(0.5, 0.5, 1); const frontFace = new Vector(0.5, 0.5, 1);
const backFace = new Vector(0.5, 0.5, 0); const backFace = new Vector(0.5, 0.5, 0);
return [ return [
{ particleType: "structool:blockoverlay_xz", location: this.location.add(topFace) }, { particleType: "construct:blockoverlay_xz", location: this.location.add(topFace) },
{ particleType: "structool:blockoverlay_xz", location: this.location.add(bottomFace) }, { particleType: "construct:blockoverlay_xz", location: this.location.add(bottomFace) },
{ particleType: "structool:blockoverlay_yz", location: this.location.add(leftFace) }, { particleType: "construct:blockoverlay_yz", location: this.location.add(leftFace) },
{ particleType: "structool:blockoverlay_yz", location: this.location.add(rightFace) }, { particleType: "construct:blockoverlay_yz", location: this.location.add(rightFace) },
{ particleType: "structool:blockoverlay_xy", location: this.location.add(frontFace) }, { particleType: "construct:blockoverlay_xy", location: this.location.add(frontFace) },
{ particleType: "structool:blockoverlay_xy", location: this.location.add(backFace) } { particleType: "construct:blockoverlay_xy", location: this.location.add(backFace) }
]; ];
} }
@@ -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);
}
}
@@ -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}`);
}
}
}
@@ -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());
}
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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));
@@ -1,34 +1,34 @@
import { structureCollection } from './StructureCollection'; import { structureCollection } from './StructureCollection';
import { MenuForm } from './MenuForm'; import { MenuForm } from '../MenuForm';
import { forceShow } from '../utils'; import { forceShow } from '../../utils';
import { InstanceEditButtons } from './enums/InstanceEditButtons'; import { InstanceButtons } from '../enums/InstanceButtons';
import { InstanceEditFormBuilder } from './InstanceEditFormBuilder'; import { InstanceFormBuilder } from './InstanceFormBuilder';
import { FormCancelationReason } from '@minecraft/server-ui'; import { FormCancelationReason } from '@minecraft/server-ui';
export class InstanceEditForm { export class InstanceForm {
instanceName; instanceName;
#buttons = { #buttons = {
isEnabled: [ isEnabled: [
InstanceEditButtons.NextLayer, InstanceButtons.NextLayer,
InstanceEditButtons.PreviousLayer, InstanceButtons.PreviousLayer,
InstanceEditButtons.SetLayer, InstanceButtons.SetLayer,
InstanceEditButtons.Move, InstanceButtons.Move,
InstanceEditButtons.Statistics, InstanceButtons.Statistics,
InstanceEditButtons.Settings, InstanceButtons.Settings,
InstanceEditButtons.Rename, InstanceButtons.Rename,
InstanceEditButtons.Disable, InstanceButtons.Disable,
], ],
isNotEnabledAndIsNotPlaced: [ isNotEnabledAndIsNotPlaced: [
InstanceEditButtons.Place, InstanceButtons.Place,
InstanceEditButtons.Rename InstanceButtons.Rename
], ],
isNotEnabledButIsPlaced: [ isNotEnabledButIsPlaced: [
InstanceEditButtons.Enable, InstanceButtons.Enable,
InstanceEditButtons.Rename InstanceButtons.Rename
], ],
common: [ common: [
InstanceEditButtons.Delete, InstanceButtons.Delete,
InstanceEditButtons.MainMenu InstanceButtons.MainMenu
] ]
} }
@@ -41,7 +41,7 @@ export class InstanceEditForm {
show() { show() {
const currentOptions = this.getActiveOptions(); 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; if (response.canceled) return;
this.handleOption(currentOptions[response.selection]); this.handleOption(currentOptions[response.selection]);
}); });
@@ -59,48 +59,48 @@ export class InstanceEditForm {
if (!this.instance.hasLayers()) if (!this.instance.hasLayers())
currentOptions = currentOptions.filter(option => currentOptions = currentOptions.filter(option =>
option !== InstanceEditButtons.SetLayer option !== InstanceButtons.SetLayer
&& option !== InstanceEditButtons.NextLayer && option !== InstanceButtons.NextLayer
&& option !== InstanceEditButtons.PreviousLayer && option !== InstanceButtons.PreviousLayer
); );
return currentOptions; return currentOptions;
} }
handleOption(option) { handleOption(option) {
switch (option) { switch (option) {
case InstanceEditButtons.Enable: case InstanceButtons.Enable:
this.instance.enable(); this.instance.enable();
break; break;
case InstanceEditButtons.Disable: case InstanceButtons.Disable:
this.instance.disable(); this.instance.disable();
break; break;
case InstanceEditButtons.Place: case InstanceButtons.Place:
this.instance.place(this.player.dimension.id, this.player.location); this.instance.place(this.player.dimension.id, this.player.location);
break; break;
case InstanceEditButtons.Rename: case InstanceButtons.Rename:
this.renameInstanceForm(); this.renameInstanceForm();
break; break;
case InstanceEditButtons.Delete: case InstanceButtons.Delete:
structureCollection.delete(this.instanceName); structureCollection.delete(this.instanceName);
break; break;
case InstanceEditButtons.NextLayer: case InstanceButtons.NextLayer:
this.instance.increaseLayer(); this.instance.increaseLayer();
new InstanceEditForm(this.player, this.instanceName); new InstanceForm(this.player, this.instanceName);
break; break;
case InstanceEditButtons.PreviousLayer: case InstanceButtons.PreviousLayer:
this.instance.decreaseLayer(); this.instance.decreaseLayer();
new InstanceEditForm(this.player, this.instanceName); new InstanceForm(this.player, this.instanceName);
break; break;
case InstanceEditButtons.Settings: case InstanceButtons.Settings:
this.settingsForm(); this.settingsForm();
break; break;
case InstanceEditButtons.Move: case InstanceButtons.Move:
this.instance.move(this.player.dimension.id, this.player.location); this.instance.move(this.player.dimension.id, this.player.location);
break; break;
case InstanceEditButtons.Statistics: case InstanceButtons.Statistics:
this.statisticsForm(); this.statisticsForm();
break; break;
case InstanceEditButtons.MainMenu: case InstanceButtons.MainMenu:
new MenuForm(this.player, { jumpToInstance: false }); new MenuForm(this.player, { jumpToInstance: false });
break; break;
default: default:
@@ -110,7 +110,7 @@ export class InstanceEditForm {
} }
renameInstanceForm() { renameInstanceForm() {
InstanceEditFormBuilder.buildRenameInstance(this.instanceName).show(this.player).then((response) => { InstanceFormBuilder.buildRenameInstance(this.instanceName).show(this.player).then((response) => {
if (response.canceled) if (response.canceled)
return; return;
const newName = response.formValues[0]; const newName = response.formValues[0];
@@ -129,7 +129,7 @@ export class InstanceEditForm {
} }
setLayerForm() { 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) if (response.canceled)
return; return;
this.instance.setLayer(parseInt(response.formValues[0])); this.instance.setLayer(parseInt(response.formValues[0]));
@@ -137,7 +137,7 @@ export class InstanceEditForm {
} }
async statisticsForm() { async statisticsForm() {
const statsForm = await InstanceEditFormBuilder.buildStatistics(this.instance) const statsForm = await InstanceFormBuilder.buildStatistics(this.instance)
statsForm.form.show(this.player).then((response) => { statsForm.form.show(this.player).then((response) => {
if (response.canceled && response.cancelationReason === FormCancelationReason.UserBusy) if (response.canceled && response.cancelationReason === FormCancelationReason.UserBusy)
this.player.sendMessage(statsForm.stats); this.player.sendMessage(statsForm.stats);
@@ -145,7 +145,7 @@ export class InstanceEditForm {
} }
settingsForm() { settingsForm() {
InstanceEditFormBuilder.buildSettings(this.instance).show(this.player).then((response) => { InstanceFormBuilder.buildSettings(this.instance).show(this.player).then((response) => {
if (response.canceled) if (response.canceled)
return; return;
this.instance.setVerifierEnabled(response.formValues[0]); this.instance.setVerifierEnabled(response.formValues[0]);
@@ -1,10 +1,10 @@
import { ActionFormData, ModalFormData } from '@minecraft/server-ui'; import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
import { MenuFormBuilder } from './MenuFormBuilder'; import { MenuFormBuilder } from '../MenuFormBuilder';
import { StructureVerifier } from './StructureVerifier'; import { StructureVerifier } from './StructureVerifier';
import { StructureStatistics } from './StructureStatistics'; import { StructureStatistics } from './StructureStatistics';
import { TicksPerSecond } from '@minecraft/server'; import { TicksPerSecond } from '@minecraft/server';
export class InstanceEditFormBuilder { export class InstanceFormBuilder {
static buildInstance(instance, options) { static buildInstance(instance, options) {
const location = instance.getLocation(); const location = instance.getLocation();
const form = new ActionFormData() const form = new ActionFormData()
@@ -41,7 +41,7 @@ export class InstanceEditFormBuilder {
return new ModalFormData() return new ModalFormData()
.title(MenuFormBuilder.menuTitle) .title(MenuFormBuilder.menuTitle)
.label('Use the slider to select the layer. Use 0 for all layers.') .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) .slider("Layer", 0, maxLayer, 1, currentLayer)
.submitButton('§aApply'); .submitButton('§aApply');
} }
@@ -1,7 +1,9 @@
import { Vector } from "../lib/Vector"; import { Vector } from "../../lib/Vector";
import { world } from "@minecraft/server"; import { world } from "@minecraft/server";
import { Option } from "../Option";
export class InstanceOptions { export class InstanceOptions extends Option {
#DP_NAMESPACE = "instanceOptions";
instanceName = void 0; instanceName = void 0;
structureId = void 0; structureId = void 0;
isEnabled = false; isEnabled = false;
@@ -26,26 +28,16 @@ export class InstanceOptions {
} }
save() { save() {
world.setDynamicProperty(`instanceOptions:${this.instanceName}`, JSON.stringify(this)); this.saveToDP(this.#DP_NAMESPACE, this.instanceName, this);
} }
load() { load() {
try { this.loadFromDP(this.#DP_NAMESPACE, this.instanceName);
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.worldLocation = Vector.from(this.worldLocation); this.worldLocation = Vector.from(this.worldLocation);
} }
clear() { clear() {
world.setDynamicProperty(`instanceOptions:${this.instanceName}`, void 0); this.clearDP(this.#DP_NAMESPACE, this.instanceName);
} }
getDimension() { getDimension() {
+3 -3
View File
@@ -1,7 +1,7 @@
import { forceShow } from '../utils'; import { forceShow } from '../utils';
import { structureCollection } from './StructureCollection'; import { structureCollection } from './StructureCollection';
import { MenuFormBuilder } from './MenuFormBuilder'; import { MenuFormBuilder } from './MenuFormBuilder';
import { InstanceEditForm } from './InstanceEditForm'; import { InstanceForm } from './InstanceForm';
export class MenuForm { export class MenuForm {
constructor(player, { jumpToInstance = false, instanceName = void 0 } = {}) { constructor(player, { jumpToInstance = false, instanceName = void 0 } = {}) {
@@ -14,14 +14,14 @@ export class MenuForm {
if (!instanceName) if (!instanceName)
instanceName = structureCollection.getStructure(this.player.dimension.id, this.player.location, { useActiveLayer: false })?.getName(); instanceName = structureCollection.getStructure(this.player.dimension.id, this.player.location, { useActiveLayer: false })?.getName();
if (structureCollection.get(instanceName)) { if (structureCollection.get(instanceName)) {
new InstanceEditForm(this.player, instanceName); new InstanceForm(this.player, instanceName);
return; return;
} }
} }
instanceName = await this.getInstanceNameFromForm(); instanceName = await this.getInstanceNameFromForm();
if (!instanceName) if (!instanceName)
return; return;
new InstanceEditForm(this.player, instanceName); new InstanceForm(this.player, instanceName);
} }
async getInstanceNameFromForm() { async getInstanceNameFromForm() {
@@ -2,7 +2,7 @@ import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
import { structureCollection } from './StructureCollection'; import { structureCollection } from './StructureCollection';
export class MenuFormBuilder { export class MenuFormBuilder {
static menuTitle = '§l§2StrucTool §8Menu'; static menuTitle = '§l§2Construct §8Menu';
static buildAllInstanceName() { static buildAllInstanceName() {
const allInstanceNameForm = new ActionFormData() const allInstanceNameForm = new ActionFormData()
+25
View File
@@ -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);
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ export class Outliner {
dimension; dimension;
min = new Vector(); min = new Vector();
max = new Vector(); max = new Vector();
drawParticle = "structool:outline"; drawParticle = "construct:outline";
drawFrequency = 10; drawFrequency = 10;
#drawParticles = []; #drawParticles = [];
@@ -9,7 +9,7 @@ export class Structure {
this.structureId = structureId; this.structureId = structureId;
this.#structure = world.structureManager.get(structureId); this.#structure = world.structureManager.get(structureId);
if (!this.#structure) if (!this.#structure)
throw new Error(`[StrucTool] Structure '${structureId}' not found.`); throw new Error(`[Construct] Structure '${structureId}' not found.`);
this.#structure.saveToWorld(); this.#structure.saveToWorld();
} }
@@ -17,7 +17,7 @@ class StructureCollection {
structureId = InstanceOptions.getInstanceStrucetureId(instanceName); structureId = InstanceOptions.getInstanceStrucetureId(instanceName);
this.structures[instanceName] = new StructureInstance(instanceName, structureId); this.structures[instanceName] = new StructureInstance(instanceName, structureId);
} catch (e) { } 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); world.setDynamicProperty(id, void 0);
throw e; throw e;
} }
@@ -70,7 +70,7 @@ export class StructureInstance {
getActiveBounds() { getActiveBounds() {
if (!this.options.isEnabled) 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()) if (this.hasLayerSelected())
return this.getLayerBounds(this.getLayer()); return this.getLayerBounds(this.getLayer());
return this.getBounds(); return this.getBounds();
@@ -78,7 +78,7 @@ export class StructureInstance {
getLayerBounds(layer) { getLayerBounds(layer) {
if (!this.options.isEnabled) 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 min = this.structure.getMin();
const max = this.structure.getMax(); const max = this.structure.getMax();
return { return {
@@ -105,7 +105,7 @@ export class StructureInstance {
getActiveBlocks() { getActiveBlocks() {
if (!this.options.isEnabled) 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()) if (this.hasLayerSelected())
return this.getLayerBlocks(this.getLayer()); return this.getLayerBlocks(this.getLayer());
return this.getAllBlocks(); return this.getAllBlocks();
@@ -126,7 +126,7 @@ export class StructureInstance {
getAllActiveLocations() { getAllActiveLocations() {
if (!this.options.isEnabled) 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()) if (this.hasLayerSelected())
return this.structure.getLayerLocations(this.getLayer()-1); return this.structure.getLayerLocations(this.getLayer()-1);
else else
@@ -187,7 +187,7 @@ export class StructureInstance {
setLayer(layer) { setLayer(layer) {
if (layer < 0 || layer > this.getMaxLayer()) 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.options.setLayer(layer);
this.refreshBox(); this.refreshBox();
} }
@@ -1,4 +1,4 @@
export const InstanceEditButtons = Object.freeze({ export const InstanceButtons = Object.freeze({
Unknown: 'Unknown', Unknown: 'Unknown',
MainMenu: '<<', MainMenu: '<<',
Place: '§aPlace Instance', Place: '§aPlace Instance',
+3 -3
View File
@@ -6,9 +6,9 @@ import { MenuForm } from '../classes/MenuForm';
const ACTION_ITEM = 'minecraft:paper'; const ACTION_ITEM = 'minecraft:paper';
const menuCmd = new Command({ const menuCmd = new Command({
name: 'structool', name: 'construct',
description: { text: 'Opens the StrucTool Menu. Using a paper will also open the menu.' }, description: { text: 'Opens the Construct Menu. Using a paper will also open the menu.' },
usage: 'structool', usage: 'construct',
callback: (sender) => openMenu(sender) callback: (sender) => openMenu(sender)
}); });
extension.addCommand(menuCmd); extension.addCommand(menuCmd);
+2 -2
View File
@@ -2,7 +2,7 @@ import { CanopyExtension } from './lib/canopy/CanopyExtension';
export const extension = new CanopyExtension({ export const extension = new CanopyExtension({
author: 'ForestOfLight', author: 'ForestOfLight',
name: 'StrucTool', name: 'Construct',
description: 'Survival building extension for §l§aCanopy§r!', description: 'Make building structures in survival easier!',
version: '1.0.0' version: '1.0.0'
}); });
-76
View File
@@ -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;
@@ -1,5 +1,4 @@
import { Rule } from '../lib/canopy/CanopyExtension'; import { BuilderOption } from '../classes/Builder/BuilderOption';
import { extension } from '../config';
import { BlockPermutation, EntityComponentTypes, GameMode, ItemStack, system, world } from '@minecraft/server'; import { BlockPermutation, EntityComponentTypes, GameMode, ItemStack, system, world } from '@minecraft/server';
import { structureCollection } from '../classes/StructureCollection'; import { structureCollection } from '../classes/StructureCollection';
import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, specialItemPlacementConversions, import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, specialItemPlacementConversions,
@@ -8,13 +7,14 @@ import { fetchMatchingItemSlot } from '../utils';
const ACTION_SLOT = 35; const ACTION_SLOT = 35;
const easyPlace = new Rule({ new BuilderOption({
identifier: 'easyPlace', 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); }, onEnableCallback: () => { world.beforeEvents.playerPlaceBlock.subscribe(onPlayerPlaceBlock); },
onDisableCallback: () => { world.beforeEvents.playerPlaceBlock.unsubscribe(onPlayerPlaceBlock); } onDisableCallback: () => { world.beforeEvents.playerPlaceBlock.unsubscribe(onPlayerPlaceBlock); }
}) })
extension.addRule(easyPlace);
function onPlayerPlaceBlock(event) { function onPlayerPlaceBlock(event) {
const { player, block, permutationBeingPlaced } = event; const { player, block, permutationBeingPlaced } = event;
@@ -30,7 +30,7 @@ function hasActionItemInCorrectSlot(player) {
if (!inventory) if (!inventory)
return false; return false;
const actionSlot = inventory.getSlot(ACTION_SLOT); 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) { function tryPlaceBlock(event, player, block, structureBlock) {
@@ -1,18 +1,18 @@
import { Rule } from '../lib/canopy/CanopyExtension'; import { BuilderOption } from '../classes/Builder/BuilderOption';
import { extension } from '../config';
import { BlockPermutation, EntityComponentTypes, EquipmentSlot, GameMode, ItemStack, system, world } from '@minecraft/server'; import { BlockPermutation, EntityComponentTypes, EquipmentSlot, GameMode, ItemStack, system, world } from '@minecraft/server';
import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, specialItemPlacementConversions, import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, specialItemPlacementConversions,
blockIdToItemStackMap } from '../data'; blockIdToItemStackMap } from '../data';
import { Raycaster } from '../classes/Raycaster'; import { Raycaster } from '../classes/Raycaster';
let runner = void 0; let runner = void 0;
const easyPlace = new Rule({ new BuilderOption({
identifier: 'fastEasyPlace', 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); }, onEnableCallback: () => { runner = system.runInterval(onTick, 2); },
onDisableCallback: () => { system.clearRun(runner); } onDisableCallback: () => { system.clearRun(runner); }
}) })
extension.addRule(easyPlace);
function onTick() { function onTick() {
for (const player of world.getAllPlayers()) { for (const player of world.getAllPlayers()) {
@@ -35,7 +35,7 @@ function isHoldingActionItem(player) {
const mainhandItemStack = player.getComponent(EntityComponentTypes.Equippable).getEquipment(EquipmentSlot.Mainhand); const mainhandItemStack = player.getComponent(EntityComponentTypes.Equippable).getEquipment(EquipmentSlot.Mainhand);
if (!mainhandItemStack) if (!mainhandItemStack)
return false; return false;
return mainhandItemStack.typeId === 'minecraft:paper' && mainhandItemStack.nameTag === 'easyPlace'; return mainhandItemStack.typeId === 'minecraft:paper' && mainhandItemStack.nameTag === 'Easy Place';
} }
function tryPlaceBlock(player, worldBlock, structureBlock) { function tryPlaceBlock(player, worldBlock, structureBlock) {
@@ -1,10 +1,11 @@
import { Rule } from '../lib/canopy/CanopyExtension'; import { BuilderOption } from '../classes/Builder/BuilderOption';
import { extension } from '../config';
import { world } from '@minecraft/server'; import { world } from '@minecraft/server';
const easyPlace = new Rule({ new BuilderOption({
identifier: 'materialGrabber', 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: () => { onEnableCallback: () => {
world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteract); world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteract);
world.beforeEvents.playerInteractWithEntity.subscribe(onPlayerInteract); world.beforeEvents.playerInteractWithEntity.subscribe(onPlayerInteract);
@@ -14,7 +15,6 @@ const easyPlace = new Rule({
world.beforeEvents.playerInteractWithEntity.unsubscribe(onPlayerInteract); world.beforeEvents.playerInteractWithEntity.unsubscribe(onPlayerInteract);
} }
}) })
extension.addRule(easyPlace);
function onPlayerInteract(event) { function onPlayerInteract(event) {
// get inventory // get inventory
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"format_version": 2, "format_version": 2,
"header": { "header": {
"name": "StrucTool [RP] v1.0.0", "name": "Construct [RP] v1.0.0",
"description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.", "description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.",
"uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4", "uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4",
"version": [1, 0, 0], "version": [1, 0, 0],
@@ -16,7 +16,7 @@
], ],
"dependencies": [ "dependencies": [
{ {
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", // StrucTool BP "uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", // Construct BP
"version": [1, 0, 0] "version": [1, 0, 0]
}, },
{ {
@@ -2,7 +2,7 @@
"format_version": "1.10.0", "format_version": "1.10.0",
"particle_effect": { "particle_effect": {
"description": { "description": {
"identifier": "structool:blockoverlay_xy", "identifier": "construct:blockoverlay_xy",
"basic_render_parameters": { "basic_render_parameters": {
"material": "particles_blend", "material": "particles_blend",
"texture": "textures/particle/white" "texture": "textures/particle/white"
@@ -2,7 +2,7 @@
"format_version": "1.10.0", "format_version": "1.10.0",
"particle_effect": { "particle_effect": {
"description": { "description": {
"identifier": "structool:blockoverlay_xz", "identifier": "construct:blockoverlay_xz",
"basic_render_parameters": { "basic_render_parameters": {
"material": "particles_blend", "material": "particles_blend",
"texture": "textures/particle/white" "texture": "textures/particle/white"
@@ -2,7 +2,7 @@
"format_version": "1.10.0", "format_version": "1.10.0",
"particle_effect": { "particle_effect": {
"description": { "description": {
"identifier": "structool:blockoverlay_yz", "identifier": "construct:blockoverlay_yz",
"basic_render_parameters": { "basic_render_parameters": {
"material": "particles_blend", "material": "particles_blend",
"texture": "textures/particle/white" "texture": "textures/particle/white"
@@ -2,7 +2,7 @@
"format_version": "1.10.0", "format_version": "1.10.0",
"particle_effect": { "particle_effect": {
"description": { "description": {
"identifier": "structool:outline", "identifier": "construct:outline",
"basic_render_parameters": { "basic_render_parameters": {
"material": "particles_alpha", "material": "particles_alpha",
"texture": "textures/particle/particles" "texture": "textures/particle/particles"